feat(dashboard/offline): persist React Query cache for offline reads
First slice of offline-first (Phase 1). Makes every dashboard area *viewable* offline with last-synced data, instead of empty lists on an offline reload (previously only next-pwa's 10-min API cache survived). - offline-db: add a generic `kv` IndexedDB store (DB v2, preserves order_queue) with kvGet/kvSet/kvDelete; all degrade silently on quota/unavailable. - query-persister: debounced snapshot of the React Query cache via dehydrate/hydrate (no new dependency). Restore is guarded by a schema buster, 24h max-age, and a café scope so one tenant never hydrates another's data. - providers: gcTime 24h so hydrated data isn't GC'd; restore on mount + persist on cache changes, re-scoped when the signed-in café changes. No write-path changes; the existing POS order queue is untouched. Next in Phase 1: generalize that queue into an idempotent outbox with client→server ID remapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ConfirmProvider } from "@/components/providers/confirm-provider";
|
||||
import { MeeziToaster } from "@/components/ui/meezi-toaster";
|
||||
import { useAuthStore } from "@/lib/stores/auth.store";
|
||||
import { restoreQueryCache, startPersisting } from "@/lib/offline/query-persister";
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { staleTime: 30_000, retry: 1 },
|
||||
queries: {
|
||||
staleTime: 30_000,
|
||||
retry: 1,
|
||||
// Keep data in memory long enough to back offline reads; it is also
|
||||
// persisted to IndexedDB by the persister below.
|
||||
gcTime: 24 * 60 * 60 * 1000,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Persist the query cache to IndexedDB so the dashboard is viewable offline.
|
||||
// Scoped to the current café so a different tenant never hydrates this data.
|
||||
const cafeId = useAuthStore((s) => s.user?.cafeId);
|
||||
useEffect(() => {
|
||||
const scope = cafeId ?? "anon";
|
||||
let active = true;
|
||||
let stop: () => void = () => {};
|
||||
void (async () => {
|
||||
await restoreQueryCache(queryClient, scope);
|
||||
if (!active) return;
|
||||
stop = startPersisting(queryClient, scope);
|
||||
})();
|
||||
return () => {
|
||||
active = false;
|
||||
stop();
|
||||
};
|
||||
}, [queryClient, cafeId]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ConfirmProvider>
|
||||
|
||||
Reference in New Issue
Block a user