Files
TREK/client/src/store/settingsStore.ts
T
Maurice 25bdf56d16 add mapbox gl option, gps location, journey reorder + polish
- Mapbox GL provider alongside Leaflet for trip and journey maps (opt-in in
  settings with token, style presets incl. 3D on satellite, quality mode,
  experimental badge).
- GPS "blue dot" with heading cone on mobile; three-state FAB (off / show /
  follow), geodesic accuracy circle, desktop-hidden since browser IP geo is
  too coarse for navigation.
- Marker drift fix: outer wrap no longer carries inline position/transform,
  so mapbox's translate keeps the pin pinned at every zoom and pitch.
- Journey map popup (mapbox-gl): Apple-Maps-style tooltip on marker
  highlight/click showing entry title + location / date subline.
- Journey feed reorder: up/down controls to the left of each entry reorder
  sort_order within a day. Server endpoint, optimistic store update, rollback
  on failure.
- Journey entry editor: desktop modal now centers over the feed column only,
  backdrop still blurs the whole page (map included).
- Scroll-sync guard on journey: marker click locks the sync so smooth-scroll
  can't steer the highlight to a neighbouring entry mid-animation.
- Misc: map top-padding aligned with hero, live/synced badges replaced by a
  compact back-button in the hero, skeleton entries no longer pollute the
  journey map, journey detail no longer shows map on mobile path when
  combined view is active.
2026-04-19 01:41:02 +02:00

94 lines
3.1 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,
map_provider: 'leaflet',
mapbox_access_token: '',
mapbox_style: 'mapbox://styles/mapbox/standard',
mapbox_3d_enabled: true,
mapbox_quality_mode: 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'))
}
},
}))