/** * useUserPreferences — loads user preferences from API and syncs with uiStore. * * On load: applies server-side preferences to the Zustand uiStore (theme, locale, * sidebar state, AI sidebar tab, etc.). * On change: persists uiStore state to the server via upsert mutation. */ import { useEffect, useCallback } from 'react'; import { useUIStore, type Theme, type Locale, type AISidebarTab } from '@/store/uiStore'; import { useUserPreferences, useUpsertUserPreference, useDeleteUserPreference, } from '@/api/userPreferences'; // Known preference keys that map to uiStore fields const PREF_KEYS = { THEME: 'theme', LOCALE: 'locale', SIDEBAR_OPEN: 'sidebar_open', AI_SIDEBAR_COLLAPSED: 'ai_sidebar_collapsed', AI_SIDEBAR_TAB: 'ai_sidebar_tab', MESSAGE_SIDEBAR_COLLAPSED: 'message_sidebar_collapsed', SUGGESTION_SIDEBAR_OPEN: 'suggestion_sidebar_open', } as const; export function useUserPreferencesSync() { const { data, isLoading } = useUserPreferences(); const upsertMutation = useUpsertUserPreference(); const deleteMutation = useDeleteUserPreference(); const { theme, locale, sidebarOpen, aiSidebarCollapsed, aiSidebarTab, messageSidebarCollapsed, suggestionSidebarOpen: _suggestionSidebarOpen, setTheme, setLocale, setSidebarOpen, setAISidebarCollapsed, setAISidebarTab, setMessageSidebarCollapsed, } = useUIStore(); // Apply server preferences to uiStore on initial load useEffect(() => { if (!data?.preferences) return; const prefMap = new Map(); for (const pref of data.preferences) { prefMap.set(pref.key, pref.value); } // Only apply if the server has a value — don't override local defaults with null if (prefMap.has(PREF_KEYS.THEME)) { const serverTheme = prefMap.get(PREF_KEYS.THEME) as Theme; if (serverTheme && serverTheme !== theme) { setTheme(serverTheme); } } if (prefMap.has(PREF_KEYS.LOCALE)) { const serverLocale = prefMap.get(PREF_KEYS.LOCALE) as Locale; if (serverLocale && serverLocale !== locale) { setLocale(serverLocale); } } if (prefMap.has(PREF_KEYS.SIDEBAR_OPEN)) { const serverSidebar = prefMap.get(PREF_KEYS.SIDEBAR_OPEN) as boolean; if (serverSidebar !== sidebarOpen) { setSidebarOpen(serverSidebar); } } if (prefMap.has(PREF_KEYS.AI_SIDEBAR_COLLAPSED)) { const serverCollapsed = prefMap.get( PREF_KEYS.AI_SIDEBAR_COLLAPSED ) as boolean; if (serverCollapsed !== aiSidebarCollapsed) { setAISidebarCollapsed(serverCollapsed); } } if (prefMap.has(PREF_KEYS.AI_SIDEBAR_TAB)) { const serverTab = prefMap.get(PREF_KEYS.AI_SIDEBAR_TAB) as AISidebarTab; if (serverTab && serverTab !== aiSidebarTab) { setAISidebarTab(serverTab); } } if (prefMap.has(PREF_KEYS.MESSAGE_SIDEBAR_COLLAPSED)) { const serverCollapsed = prefMap.get( PREF_KEYS.MESSAGE_SIDEBAR_COLLAPSED ) as boolean; if (serverCollapsed !== messageSidebarCollapsed) { setMessageSidebarCollapsed(serverCollapsed); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); // Persist a single preference to the server const savePreference = useCallback( (key: string, value: unknown) => { upsertMutation.mutate({ key, value }); }, [upsertMutation] ); // Delete a preference from the server const removePreference = useCallback( (key: string) => { deleteMutation.mutate(key); }, [deleteMutation] ); return { isLoading, savePreference, removePreference, isSaving: upsertMutation.isPending, isDeleting: deleteMutation.isPending, }; }