import { useCallback, useEffect } from 'react'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useSyncedAppData, type MergeStrategy } from '@/hooks/useSyncedAppData'; /** * usePreferences — cross-device user preferences. * * Non-secret-but-personal UI settings (bottom-nav layout, desktop primary tabs, * language) that should follow a skater's npub between phone and laptop instead * of being stranded per-browser in localStorage. * * Same shape as `useReadState`: localStorage is the instant, offline-first, * anon-friendly fast path, and one encrypted `kind-30078` event is the * cross-device mirror. The difference is the MERGE policy — a preference is a * genuine replacement, not a monotonic marker, so we use LAST-WRITE-WINS keyed * by a per-blob `updatedAt`: the object with the newer `updatedAt` wins whole. */ export interface Preferences { /** Mobile bottom-nav: chosen middle tab ids. */ navMiddleIds?: string[]; /** Desktop header: chosen primary tab ids (order preserved). */ desktopPrimaryIds?: string[]; /** UI language code (e.g. "en", "th"). */ language?: string; /** Unix seconds of the last change — drives last-write-wins merge. */ updatedAt: number; } const EMPTY: Preferences = { updatedAt: 0 }; const D_TAG = 'skatespots:preferences'; /** Whole-object last-write-wins by `updatedAt` (newer replaces older). */ const mergePreferences: MergeStrategy = (remote, patch) => patch.updatedAt >= remote.updatedAt ? patch : remote; export function usePreferences() { const { user } = useCurrentUser(); const lsKey = user ? `skatespots:preferences:${user.pubkey}` : 'skatespots:preferences:anon'; const [local, setLocal] = useLocalStorage(lsKey, EMPTY); const synced = useSyncedAppData({ dTag: D_TAG, alt: 'SkateSpots preferences (encrypted)', empty: EMPTY, merge: mergePreferences, }); // When the relay copy arrives and it's newer, adopt it locally. const remote = synced.data; useEffect(() => { if (!remote) return; setLocal((prev) => (remote.updatedAt > prev.updatedAt ? remote : prev)); }, [remote, setLocal]); /** Patch one or more preference fields (bumps `updatedAt`, mirrors + syncs). */ const setPreferences = useCallback( (patch: Partial>) => { const now = Math.floor(Date.now() / 1000); setLocal((prev) => { const next: Preferences = { ...prev, ...patch, updatedAt: now }; void synced.patch(next); return next; }); }, [setLocal, synced], ); return { preferences: local, setPreferences }; }