From 16efafc11189e5af3a98b725e5bba183a69852f1 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 28 Aug 2026 00:19:12 +0200 Subject: [PATCH] fix(boss-dashboard): guard fetch races, surface errors, tighten response type --- src/app/(mobile)/m/dashboard-view.tsx | 60 +++++++++++++----------- src/lib/server/boss-dashboard-service.ts | 10 ++++ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/app/(mobile)/m/dashboard-view.tsx b/src/app/(mobile)/m/dashboard-view.tsx index 28f652b..0ebf2af 100644 --- a/src/app/(mobile)/m/dashboard-view.tsx +++ b/src/app/(mobile)/m/dashboard-view.tsx @@ -6,6 +6,7 @@ import { RefreshCw } from "lucide-react"; import getPocketBase from "@/lib/pocketbase"; import type { BossDashboardData, + BossDashboardResponse, BossWindow, } from "@/lib/server/boss-dashboard-service"; @@ -17,42 +18,43 @@ export default function BossDashboardView() { const [window, setWindow] = useState("today"); const [loading, setLoading] = useState(true); const [lastRefreshed, setLastRefreshed] = useState(null); + const [error, setError] = useState(null); + const reqIdRef = useRef(0); const fetchData = useCallback(async () => { + // Monotonic request id — discards late responses when a newer fetch + // has started (e.g. tab switch). Survives unmount because refs outlive + // the component instance. + const myId = ++reqIdRef.current; + setLoading(true); + setError(null); try { const pb = getPocketBase(); - const pbToken = pb.authStore?.token; - const headers: Record = {}; - if (pbToken) headers.Authorization = pbToken; - + const token = pb.authStore?.token ?? ""; const res = await fetch(`/api/v1/boss-dashboard?window=${window}`, { - headers, + headers: { Authorization: token }, cache: "no-store", }); - if (res.status === 401) { - document.location.href = "/login"; - return; - } if (!res.ok) { - const body = (await res.json().catch(() => ({}))) as { error?: string }; - throw new Error(body.error ?? `HTTP ${res.status}`); + if (res.status === 401) { + document.location.href = "/login"; + return; + } + throw new Error(`HTTP ${res.status}`); } - const next = (await res.json()) as BossDashboardData & { viewerName?: string }; - setData({ - window: next.window, - rangeStart: next.rangeStart, - rangeEnd: next.rangeEnd, - orderTotals: next.orderTotals, - reps: next.reps, - drivers: next.drivers, - }); - setViewerName(next.viewerName ?? null); + const body = (await res.json()) as BossDashboardResponse; + if (myId !== reqIdRef.current) return; // stale — newer fetch in flight + setData(body); + setViewerName(body.viewerName); setLastRefreshed(new Date()); } catch (err) { - const message = err instanceof Error ? err.message : "Failed to load"; - toast.error(message); + if (myId !== reqIdRef.current) return; + const message = + err instanceof Error ? err.message : "Failed to load dashboard"; + toast.error("Couldn't refresh", { description: message }); + setError(message); } finally { - setLoading(false); + if (myId === reqIdRef.current) setLoading(false); } }, [window]); @@ -73,9 +75,13 @@ export default function BossDashboardView() { Good {greeting()}, {firstName}.

- {lastRefreshed - ? `Updated ${timeAgo(lastRefreshed)}` - : "Loading…"} + {error + ? "Couldn't refresh" + : loading + ? "Loading…" + : lastRefreshed + ? `Updated ${timeAgo(lastRefreshed)}` + : "Loading…"}