"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import type { AppSettings } from "@/lib/db/appSettings"; import { setBoolSetting, setNumberSetting, setColorSetting, setPaginationMode } from "@/app/actions/settings"; type BoolKey = "fadeTransitions" | "purgeFilesOnDelete" | "useRecycleBin"; type NumKey = "fadeDurationMs" | "trashRetentionDays" | "gridColumns" | "gridColumnsPortrait" | "supersededRetentionDays" | "coverPageSize"; type ColorKey = "accentPrimary" | "accentSecondary"; type PaginationModeKey = "paginationMode"; interface Ctx { settings: AppSettings; set(key: BoolKey, value: boolean): void; set(key: NumKey, value: number): void; set(key: ColorKey, value: string): void; set(key: PaginationModeKey, value: AppSettings["paginationMode"]): void; } const SettingsCtx = createContext(null); const NUM_KEYS = new Set(["fadeDurationMs", "trashRetentionDays", "gridColumns", "gridColumnsPortrait", "supersededRetentionDays", "coverPageSize"]); const COLOR_KEYS = new Set(["accentPrimary", "accentSecondary"]); const PAGINATION_MODE_KEYS = new Set(["paginationMode"]); const ACCENT_VARS: Record = { accentPrimary: ["--color-cyan", "--color-cyan-glow"], accentSecondary: ["--color-violet", "--color-violet-glow"], }; export function SettingsProvider({ initial, children, }: { initial: AppSettings; children: React.ReactNode; }) { const [settings, setSettings] = useState(initial); // The server is the source of truth, but the parent layout passes the latest // server values on every render. Keep our state in sync after server mutations. useEffect(() => { setSettings(initial); }, [initial]); useEffect(() => { document.documentElement.dataset.fade = settings.fadeTransitions ? "on" : "off"; document.documentElement.style.setProperty("--fade-duration", `${settings.fadeDurationMs}ms`); }, [settings.fadeTransitions, settings.fadeDurationMs]); useEffect(() => { const n = Math.max(2, Math.min(4, settings.gridColumns || 3)); document.documentElement.style.setProperty("--grid-cols", String(n)); }, [settings.gridColumns]); useEffect(() => { const n = Math.max(4, Math.min(10, settings.gridColumnsPortrait || 6)); document.documentElement.style.setProperty("--grid-cols-portrait", String(n)); }, [settings.gridColumnsPortrait]); useEffect(() => { const root = document.documentElement; for (const [key, [base, glow]] of Object.entries(ACCENT_VARS) as [ColorKey, [string, string]][]) { const value = settings[key]; if (value) { root.style.setProperty(base, value); root.style.setProperty(glow, value); } else { root.style.removeProperty(base); root.style.removeProperty(glow); } } }, [settings.accentPrimary, settings.accentSecondary]); const set = useCallback((key: BoolKey | NumKey | ColorKey | PaginationModeKey, value: boolean | number | string) => { setSettings((cur) => ({ ...cur, [key]: value })); if (NUM_KEYS.has(key)) { void setNumberSetting(key as NumKey, value as number); } else if (COLOR_KEYS.has(key)) { void setColorSetting(key as ColorKey, value as string); } else if (PAGINATION_MODE_KEYS.has(key)) { void setPaginationMode(value as AppSettings["paginationMode"]); } else { void setBoolSetting(key as BoolKey, value as boolean); } }, []) as Ctx["set"]; const value = useMemo(() => ({ settings, set }), [settings, set]); return {children}; } export function useSettings() { const ctx = useContext(SettingsCtx); if (!ctx) throw new Error("useSettings must be used within SettingsProvider"); return ctx; }