Files
TREK/client/src/store/settingsStore.ts
T
jubnl a07e76c740 fix(login): address review feedback on language dropdown PR
- Fix import path: use i18n barrel instead of TranslationContext directly
- Encapsulate localStorage key behind hasStoredLanguage() helper in settingsStore
- Fix pt-BR detection: only map pt-BR to br, pt-PT now returns null correctly
- Add comment linking server SUPPORTED_LANG_CODES to canonical client source
- Extract /api/config inline handler to routes/publicConfig.ts
- Add aria-haspopup, aria-expanded, role=listbox/option, aria-selected to dropdown
- Add 8 tests for detectBrowserLanguage (FE-COMP-I18N-016–023)
- Add 3 tests for setLanguageTransient (FE-STORE-SETTINGS-015–017)
2026-04-15 03:04:25 +02:00

89 lines
3.0 KiB
TypeScript

import { create } from 'zustand'
import { settingsApi } from '../api/client'
import type { Settings } from '../types'
import { getApiErrorMessage } from '../types'
import { SUPPORTED_LANGUAGE_CODES } from '../i18n/supportedLanguages'
interface SettingsState {
settings: Settings
isLoaded: boolean
loadSettings: () => Promise<void>
updateSetting: (key: keyof Settings, value: Settings[keyof Settings]) => Promise<void>
setLanguageLocal: (lang: string) => void
setLanguageTransient: (lang: string) => void
updateSettings: (settingsObj: Partial<Settings>) => Promise<void>
}
// Returns true when the user has explicitly chosen a language (persisted in localStorage).
// Use this instead of reading localStorage directly so the key stays encapsulated here.
export const hasStoredLanguage = (): boolean =>
typeof localStorage !== 'undefined' && !!localStorage.getItem('app_language')
export const useSettingsStore = create<SettingsState>((set, get) => ({
settings: {
map_tile_url: '',
default_lat: 48.8566,
default_lng: 2.3522,
default_zoom: 10,
dark_mode: false,
default_currency: 'USD',
language: localStorage.getItem('app_language') || 'en',
temperature_unit: 'fahrenheit',
time_format: '12h',
show_place_description: false,
},
isLoaded: false,
loadSettings: async () => {
try {
const data = await settingsApi.get()
set((state) => ({
settings: { ...state.settings, ...data.settings },
isLoaded: true,
}))
} catch (err: unknown) {
set({ isLoaded: true })
console.error('Failed to load settings:', err)
}
},
updateSetting: async (key: keyof Settings, value: Settings[keyof Settings]) => {
set((state) => ({
settings: { ...state.settings, [key]: value },
}))
if (key === 'language') localStorage.setItem('app_language', value as string)
try {
await settingsApi.set(key, value)
} catch (err: unknown) {
console.error('Failed to save setting:', err)
throw new Error(getApiErrorMessage(err, 'Error saving setting'))
}
},
setLanguageLocal: (lang: string) => {
localStorage.setItem('app_language', lang)
set((state) => ({ settings: { ...state.settings, language: lang } }))
},
// Applies a language for the current session without persisting to localStorage.
// Used for automatic detection (browser/server default) — only explicit user
// choices via the UI should be persisted.
setLanguageTransient: (lang: string) => {
if (!SUPPORTED_LANGUAGE_CODES.includes(lang)) return
set((state) => ({ settings: { ...state.settings, language: lang } }))
},
updateSettings: async (settingsObj: Partial<Settings>) => {
set((state) => ({
settings: { ...state.settings, ...settingsObj },
}))
try {
await settingsApi.setBulk(settingsObj)
} catch (err: unknown) {
console.error('Failed to save settings:', err)
throw new Error(getApiErrorMessage(err, 'Error saving settings'))
}
},
}))