import { useState, useCallback, useEffect, useRef } from 'react'; import { api } from '../api'; import { takePrefetchOrFetch } from '../prefetch'; import { toast } from '../components/ui/sonner'; import { initLastMessageTimes, loadLocalStorageLastMessageTimes, loadLocalStorageSortOrder, clearLocalStorageConversationState, } from '../utils/conversationState'; import { isFavorite, loadLocalStorageFavorites, clearLocalStorageFavorites, } from '../utils/favorites'; import type { AppSettings, AppSettingsUpdate, Favorite } from '../types'; export function useAppSettings() { const [appSettings, setAppSettings] = useState(null); // Stable empty array prevents a new reference every render when there are none. const emptyFavorites = useRef([]).current; const favorites: Favorite[] = appSettings?.favorites ?? emptyFavorites; // One-time migration guard const hasMigratedRef = useRef(false); const fetchAppSettings = useCallback(async () => { try { const data = await takePrefetchOrFetch('settings', api.getSettings); setAppSettings(data); initLastMessageTimes(data.last_message_times ?? {}); } catch (err) { console.error('Failed to fetch app settings:', err); } }, []); const handleSaveAppSettings = useCallback( async (update: AppSettingsUpdate) => { await api.updateSettings(update); await fetchAppSettings(); }, [fetchAppSettings] ); const handleToggleBlockedKey = useCallback(async (key: string) => { const normalizedKey = key.toLowerCase(); setAppSettings((prev) => { if (!prev) return prev; const current = prev.blocked_keys ?? []; const wasBlocked = current.includes(normalizedKey); const optimistic = wasBlocked ? current.filter((k) => k !== normalizedKey) : [...current, normalizedKey]; return { ...prev, blocked_keys: optimistic }; }); try { const updatedSettings = await api.toggleBlockedKey(key); setAppSettings(updatedSettings); } catch (err) { console.error('Failed to toggle blocked key:', err); try { const settings = await api.getSettings(); setAppSettings(settings); } catch { // If refetch also fails, leave optimistic state } toast.error('Failed to update blocked key'); } }, []); const handleToggleBlockedName = useCallback(async (name: string) => { setAppSettings((prev) => { if (!prev) return prev; const current = prev.blocked_names ?? []; const wasBlocked = current.includes(name); const optimistic = wasBlocked ? current.filter((n) => n !== name) : [...current, name]; return { ...prev, blocked_names: optimistic }; }); try { const updatedSettings = await api.toggleBlockedName(name); setAppSettings(updatedSettings); } catch (err) { console.error('Failed to toggle blocked name:', err); try { const settings = await api.getSettings(); setAppSettings(settings); } catch { // If refetch also fails, leave optimistic state } toast.error('Failed to update blocked name'); } }, []); const handleToggleFavorite = useCallback(async (type: 'channel' | 'contact', id: string) => { setAppSettings((prev) => { if (!prev) return prev; const currentFavorites = prev.favorites ?? []; const wasFavorited = isFavorite(currentFavorites, type, id); const optimisticFavorites = wasFavorited ? currentFavorites.filter((f) => !(f.type === type && f.id === id)) : [...currentFavorites, { type, id }]; return { ...prev, favorites: optimisticFavorites }; }); try { const updatedSettings = await api.toggleFavorite(type, id); setAppSettings(updatedSettings); } catch (err) { console.error('Failed to toggle favorite:', err); try { const settings = await api.getSettings(); setAppSettings(settings); } catch { // If refetch also fails, leave optimistic state } toast.error('Failed to update favorite'); } }, []); // One-time migration of localStorage preferences to server useEffect(() => { if (!appSettings || hasMigratedRef.current) return; if (appSettings.preferences_migrated) { clearLocalStorageFavorites(); clearLocalStorageConversationState(); hasMigratedRef.current = true; return; } const localFavorites = loadLocalStorageFavorites(); const localSortOrder = loadLocalStorageSortOrder(); const localLastMessageTimes = loadLocalStorageLastMessageTimes(); const hasLocalData = localFavorites.length > 0 || localSortOrder !== 'recent' || Object.keys(localLastMessageTimes).length > 0; if (!hasLocalData) { hasMigratedRef.current = true; return; } hasMigratedRef.current = true; const migratePreferences = async () => { try { const result = await api.migratePreferences({ favorites: localFavorites, sort_order: localSortOrder, last_message_times: localLastMessageTimes, }); if (result.migrated) { toast.success('Preferences migrated', { description: `Migrated ${localFavorites.length} favorites to server`, }); } setAppSettings(result.settings); initLastMessageTimes(result.settings.last_message_times ?? {}); clearLocalStorageFavorites(); clearLocalStorageConversationState(); } catch (err) { console.error('Failed to migrate preferences:', err); } }; migratePreferences(); }, [appSettings]); return { appSettings, favorites, fetchAppSettings, handleSaveAppSettings, handleToggleFavorite, handleToggleBlockedKey, handleToggleBlockedName, }; }