feat: add camera/gallery choice for photos & sync AI profile pref to server

This commit is contained in:
2026-06-06 20:37:21 +02:00
parent 8c6ab59d67
commit 318f5e65da
11 changed files with 141 additions and 31 deletions
+8 -4
View File
@@ -26,6 +26,7 @@ describe('appearancePrefs', () => {
await expect(fetchAppearancePrefs()).resolves.toEqual({
theme: 'auto',
colorScheme: 'auto',
aiAuthorized: false,
persisted: false
})
expect(mockedApiJson).not.toHaveBeenCalled()
@@ -36,6 +37,7 @@ describe('appearancePrefs', () => {
mockedApiJson.mockResolvedValueOnce({
theme: 'ocean',
colorScheme: 'dark',
aiAuthorized: true,
persisted: true
})
@@ -46,6 +48,7 @@ describe('appearancePrefs', () => {
expect(localStorage.getItem(`user_pref_theme_${USER_ID}`)).toBe('ocean')
expect(localStorage.getItem(`user_pref_color_scheme_${USER_ID}`)).toBe('dark')
expect(localStorage.getItem(`user_pref_ai_authorized_${USER_ID}`)).toBe('true')
expect(changed).toHaveBeenCalledTimes(1)
})
@@ -53,20 +56,20 @@ describe('appearancePrefs', () => {
localStorage.setItem('active_userid', USER_ID)
setThemePreference(USER_ID, 'material')
mockedApiJson
.mockResolvedValueOnce({ theme: 'auto', colorScheme: 'auto', persisted: false })
.mockResolvedValueOnce({ theme: 'material', colorScheme: 'auto', persisted: true })
.mockResolvedValueOnce({ theme: 'auto', colorScheme: 'auto', aiAuthorized: false, persisted: false })
.mockResolvedValueOnce({ theme: 'material', colorScheme: 'auto', aiAuthorized: false, persisted: true })
await syncAppearancePrefs(USER_ID)
expect(mockedApiJson).toHaveBeenCalledTimes(2)
expect(mockedApiJson).toHaveBeenLastCalledWith('/api/auth/appearance-prefs', {
method: 'PUT',
body: JSON.stringify({ theme: 'material', colorScheme: 'auto' })
body: JSON.stringify({ theme: 'material', colorScheme: 'auto', aiAuthorized: false })
})
})
it('saveAppearancePrefsToServer skips when not authenticated', async () => {
await saveAppearancePrefsToServer('ocean', 'light')
await saveAppearancePrefsToServer('ocean', 'light', true)
expect(mockedApiJson).not.toHaveBeenCalled()
})
@@ -76,6 +79,7 @@ describe('appearancePrefs', () => {
mockedApiJson.mockResolvedValue({
theme: 'material',
colorScheme: 'dark',
aiAuthorized: false,
persisted: true
})
+16 -5
View File
@@ -5,7 +5,9 @@ import {
getColorSchemePreference,
getThemePreference,
setColorSchemePreference,
setThemePreference
setThemePreference,
getAiAuthorized,
setAiAuthorized
} from './userPreferences.js'
const API_BASE = '/api/auth/appearance-prefs'
@@ -13,13 +15,15 @@ const API_BASE = '/api/auth/appearance-prefs'
export interface AppearancePrefs {
theme: string
colorScheme: string
aiAuthorized: boolean
persisted: boolean
}
function hasLocalAppearancePrefs(userId: string): boolean {
return (
localStorage.getItem(`user_pref_theme_${userId}`) != null ||
localStorage.getItem(`user_pref_color_scheme_${userId}`) != null
localStorage.getItem(`user_pref_color_scheme_${userId}`) != null ||
localStorage.getItem(`user_pref_ai_authorized_${userId}`) != null
)
}
@@ -35,7 +39,7 @@ function resolveSyncedUserId(userId?: string | null): string | null {
export async function fetchAppearancePrefs(userId?: string | null): Promise<AppearancePrefs> {
if (!resolveSyncedUserId(userId)) {
return { theme: 'auto', colorScheme: 'auto', persisted: false }
return { theme: 'auto', colorScheme: 'auto', aiAuthorized: false, persisted: false }
}
return apiJson<AppearancePrefs>(API_BASE)
@@ -44,13 +48,14 @@ export async function fetchAppearancePrefs(userId?: string | null): Promise<Appe
export async function saveAppearancePrefsToServer(
theme: string,
colorScheme: string,
aiAuthorized: boolean,
userId?: string | null
): Promise<void> {
if (!resolveSyncedUserId(userId)) return
await apiJson<AppearancePrefs>(API_BASE, {
method: 'PUT',
body: JSON.stringify({ theme, colorScheme })
body: JSON.stringify({ theme, colorScheme, aiAuthorized })
})
}
@@ -65,8 +70,14 @@ export async function syncAppearancePrefs(userId?: string | null): Promise<void>
if (server.persisted) {
setThemePreference(id, server.theme)
setColorSchemePreference(id, server.colorScheme)
setAiAuthorized(id, server.aiAuthorized)
} else if (hasLocalAppearancePrefs(id)) {
await saveAppearancePrefsToServer(getThemePreference(id), getColorSchemePreference(id), id)
await saveAppearancePrefsToServer(
getThemePreference(id),
getColorSchemePreference(id),
getAiAuthorized(id),
id
)
}
} catch (err) {
console.warn('Failed to sync appearance preferences:', err)