"use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { createContext, useCallback, useContext, useEffect, useState, type ReactNode, } from "react"; import { I18nProvider } from "@/lib/i18n/context"; import type { Locale } from "@/lib/i18n/config"; import { Toaster } from "@/components/ui/sonner"; import { AppSettingsProvider } from "@/components/app-settings-provider"; // ── Theme Context ──────────────────────────────────── type Theme = "dark" | "light" | "system"; interface ThemeContextValue { theme: Theme; resolvedTheme: "dark" | "light"; setTheme: (theme: Theme) => void; } const ThemeContext = createContext({ theme: "system", resolvedTheme: "dark", setTheme: () => {}, }); export function useTheme() { return useContext(ThemeContext); } function getSystemTheme(): "dark" | "light" { if (typeof window === "undefined") return "dark"; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } function applyTheme(resolved: "dark" | "light") { document.documentElement.classList.remove("dark", "light"); document.documentElement.classList.add(resolved); } function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setThemeState] = useState(() => { if (typeof window === "undefined") return "system"; const saved = localStorage.getItem("healthlog-theme") as Theme | null; return saved === "light" || saved === "dark" ? saved : "system"; }); const [resolvedTheme, setResolvedTheme] = useState<"dark" | "light">(() => { if (typeof window === "undefined") return "dark"; const saved = localStorage.getItem("healthlog-theme") as Theme | null; const t = saved === "light" || saved === "dark" ? saved : "system"; return t === "system" ? getSystemTheme() : t; }); // Apply theme on mount (inline script handles FOUC, this ensures classes are in sync) useEffect(() => { applyTheme(resolvedTheme); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Listen for OS preference changes when in system mode useEffect(() => { if (theme !== "system") return; const mq = window.matchMedia("(prefers-color-scheme: dark)"); const handler = (e: MediaQueryListEvent) => { const resolved = e.matches ? "dark" : "light"; setResolvedTheme(resolved); applyTheme(resolved); }; mq.addEventListener("change", handler); return () => mq.removeEventListener("change", handler); }, [theme]); const setTheme = useCallback((next: Theme) => { setThemeState(next); if (next === "system") { localStorage.removeItem("healthlog-theme"); const resolved = getSystemTheme(); setResolvedTheme(resolved); applyTheme(resolved); } else { localStorage.setItem("healthlog-theme", next); setResolvedTheme(next); applyTheme(next); } }, []); return ( {children} ); } // ── Root Providers ─────────────────────────────────── export function Providers({ children, initialLocale, }: { children: ReactNode; initialLocale?: Locale; }) { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, refetchOnWindowFocus: false, retry: 1, }, }, }), ); return ( {children} ); }