"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { api, ApiClientError } from "@/lib/api"; import { useAdmin } from "./store"; interface QueryState { path: string | null; data: T | null; error: ApiClientError | null; at: number; } export interface QueryResult { /** Latest data — kept (and flagged `stale`) while a new path is loading, so tables never flash. */ data: T | null; error: ApiClientError | null; /** True until the first response for the current path arrives. */ loading: boolean; /** `data` belongs to a previous path. */ stale: boolean; /** A user-triggered refresh is in flight. */ refreshing: boolean; refresh: () => Promise; /** Replace the cached data locally (optimistic updates). */ mutate: (fn: (prev: T) => T) => void; updatedAt: number; } /** Client-side data hook for `/api/admin/*`. A 401 flips the admin gate back to the login form. */ export function useAdminQuery(path: string | null, opts: { refreshMs?: number } = {}): QueryResult { const [state, setState] = useState>({ path: null, data: null, error: null, at: 0 }); const [refreshing, setRefreshing] = useState(false); const seq = useRef(0); const refreshMs = opts.refreshMs ?? 0; const run = useCallback(async (p: string) => { const my = ++seq.current; try { const data = await api(p); if (my !== seq.current) return; setState({ path: p, data, error: null, at: Date.now() }); } catch (e) { if (my !== seq.current) return; const err = e instanceof ApiClientError ? e : new ApiClientError(0, "UNKNOWN", e instanceof Error ? e.message : "Unexpected error."); if (err.status === 401) useAdmin.getState().signedOut(); setState((s) => ({ path: p, data: s.path === p ? s.data : null, error: err, at: Date.now() })); } }, []); useEffect(() => { if (!path) return; const first = setTimeout(() => void run(path), 0); const t = refreshMs ? setInterval(() => { if (document.visibilityState === "visible") void run(path); }, refreshMs) : null; return () => { clearTimeout(first); if (t) clearInterval(t); }; }, [path, run, refreshMs]); const refresh = useCallback(async () => { if (!path) return; setRefreshing(true); await run(path); setRefreshing(false); }, [path, run]); const mutate = useCallback((fn: (prev: T) => T) => { setState((s) => (s.data ? { ...s, data: fn(s.data) } : s)); }, []); const current = state.path === path; return { data: state.data, error: current ? state.error : null, loading: path !== null && !current, stale: path !== null && !current && state.data !== null, refreshing, refresh, mutate, updatedAt: state.at, }; }