"use client"; import { useCallback, useState } from "react"; /** * localStorage-backed persistence for a boolean UI preference. * * Mirror of `useSplitRatio` for boolean-shaped state. Used by the * invoicing page's "Detailed" toggle so each operator's preferred * row density sticks across reloads. * * SSR-safe: `localStorage` is gated on `typeof window`. The hook * reads the stored value lazily on first render and ignores writes * during SSR so server-rendered HTML matches the client's initial * paint (avoids hydration warnings). * * Read semantics: a stored value of `"true"` parses to `true`; * anything else (including `"false"`, an unparseable string, or a * throwing `localStorage`) is treated as "nothing stored" and the * caller's `initial` wins. This is the conservative pick — if a * future migration changes the storage format, a stale string falls * back to the default rather than mis-interpreted as `false`. */ const isBrowser = typeof window !== "undefined"; function readStored(key: string): boolean | null { if (!isBrowser) return null; try { const raw = window.localStorage.getItem(key); if (raw === null) return null; if (raw === "true") return true; if (raw === "false") return false; return null; } catch { // Safari private mode / quota errors / SecurityError — treat as no value. return null; } } function writeStored(key: string, value: boolean): void { if (!isBrowser) return; try { window.localStorage.setItem(key, value ? "true" : "false"); } catch { // Best-effort persistence. A failed write must not break the UI — // the in-memory state is the source of truth for the current session. } } export type UseBooleanPreferenceOptions = { /** localStorage key. Namespace it per-feature to avoid collisions. */ storageKey: string; /** Value to use when nothing is stored. */ initial: boolean; }; export type UseBooleanPreferenceResult = { value: boolean; setValue: (next: boolean) => void; }; /** * Persistent boolean preference hook. * * Returns the current value and a setter. The setter persists the * new value synchronously, so flipping the toggle and reloading the * page lands on the chosen state on the very first render (no * flicker — `useState`'s initialiser reads localStorage). */ export function useBooleanPreference({ storageKey, initial, }: UseBooleanPreferenceOptions): UseBooleanPreferenceResult { // Lazy init from localStorage so the very first render uses the // stored value when present. We don't need a follow-up `useEffect` // to re-read on mount — `useState`'s initialiser already runs once // per mount with the current localStorage value. const [value, setValueState] = useState(() => { const stored = readStored(storageKey); return stored ?? initial; }); const setValue = useCallback( (next: boolean) => { setValueState(next); writeStored(storageKey, next); }, [storageKey], ); return { value, setValue }; }