"use client"; import { createContext, useContext, useState, useCallback, useSyncExternalStore, type ReactNode } from "react"; import dayjs from "dayjs"; import { type TimeRange, getTimeRange } from "@/lib/utils"; interface TimeRangeContextType { range: TimeRange; setRange: (r: TimeRange) => void; customStart: string; customEnd: string; setCustomStart: (s: string) => void; setCustomEnd: (s: string) => void; /** Returns { start?, end? } unix timestamps (seconds) ready for API calls */ getEffectiveRange: () => { start?: number; end?: number }; } const TimeRangeContext = createContext({ range: "30d", setRange: () => {}, customStart: "", customEnd: "", setCustomStart: () => {}, setCustomEnd: () => {}, getEffectiveRange: () => ({}), }); // Use useSyncExternalStore to safely read localStorage without hydration mismatch const STORAGE_KEY = "time-range"; const DEFAULT_RANGE = "30d"; let listeners: Array<() => void> = []; function subscribe(cb: () => void) { listeners = [...listeners, cb]; return () => { listeners = listeners.filter(l => l !== cb); }; } function emitChange() { for (const l of listeners) l(); } function getSnapshot(): string { return localStorage.getItem(STORAGE_KEY) ?? ""; } function getServerSnapshot(): string { return ""; } function persist(range: TimeRange, customStart: string, customEnd: string) { localStorage.setItem(STORAGE_KEY, JSON.stringify({ range, customStart, customEnd })); emitChange(); } export function TimeRangeProvider({ children }: { children: ReactNode }) { const raw = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); const saved = (() => { if (!raw) return null; try { return JSON.parse(raw); } catch { return null; } })(); const [range, setRangeState] = useState(saved?.range ?? DEFAULT_RANGE); const [customStart, setCustomStartState] = useState( saved?.customStart || dayjs().subtract(7, "day").format("YYYY-MM-DD") ); const [customEnd, setCustomEndState] = useState( saved?.customEnd || dayjs().format("YYYY-MM-DD") ); const setRange = useCallback((r: TimeRange) => { setRangeState(r); setCustomStartState((prev: string) => { setCustomEndState((end: string) => { persist(r, prev, end); return end; }); return prev; }); }, []); const setCustomStart = useCallback((s: string) => { setCustomStartState(s); setRangeState((r: TimeRange) => { setCustomEndState((end: string) => { persist(r, s, end); return end; }); return r; }); }, []); const setCustomEnd = useCallback((e: string) => { setCustomEndState(e); setRangeState((r: TimeRange) => { setCustomStartState((start: string) => { persist(r, start, e); return start; }); return r; }); }, []); const getEffectiveRange = useCallback(() => { if (range === "custom") { const result: { start?: number; end?: number } = {}; if (customStart) result.start = dayjs(customStart).startOf("day").unix(); if (customEnd) result.end = dayjs(customEnd).endOf("day").unix(); return result; } return getTimeRange(range); }, [range, customStart, customEnd]); return ( {children} ); } export function useTimeRange() { return useContext(TimeRangeContext); }