feat(06-05): global session-expiry interstitial via QueryCache/MutationCache onError

- Add sessionExpired boolean + setSessionExpired action to Zustand calendarStore
- main.tsx: construct QueryClient with QueryCache+MutationCache onError (v5 pattern, not defaultOptions.onError)
- onGlobalError: checks instanceof SessionExpiredError, calls setSessionExpired(true) via store.getState()
- CalendarShell: read sessionExpired from store; render AuthSplash(state=redirecting, 'Session expired', 'Signing you back in…')
- CalendarShell: useEffect fires clearLoginRedirect + maybeRedirectToLogin after 1.5s when sessionExpired (one-shot guard re-armed)
- Context7 /tanstack/query confirmed v5 QueryCache/MutationCache constructor + onError signature
This commit is contained in:
Lucas Berger
2026-06-10 11:32:45 -04:00
parent e7b34a5ce2
commit 139ef00ed4
3 changed files with 79 additions and 2 deletions
+19
View File
@@ -50,6 +50,12 @@ export interface CalendarStore {
/** UID of the most recently enqueued write. SyncStateToast polls sync-status for this. */
lastSyncedUid: string | null
// ── Session-expiry flag (Plan 06-05 / D-11) ─────────────────────────────
// Set to true by the global QueryCache/MutationCache onError handler in main.tsx
// when a SessionExpiredError is detected from any query or mutation.
// Drives the "Session expired / Signing you back in…" interstitial in CalendarShell.
sessionExpired: boolean
setSelectedView: (view: string) => void
setSelectedDate: (date: string) => void
setOpenEventId: (id: string | null) => void
@@ -77,6 +83,14 @@ export interface CalendarStore {
* Pass null to dismiss the toast.
*/
setLastSyncedUid: (uid: string | null) => void
/**
* Arm the session-expiry interstitial (D-11).
* Called imperatively from the global QueryCache/MutationCache onError handler
* (outside React, via getState().setSessionExpired) when a SessionExpiredError
* is caught from any query or mutation.
*/
setSessionExpired: (expired: boolean) => void
}
// ── Helpers ────────────────────────────────────────────────────────────────
@@ -141,6 +155,9 @@ export const useCalendarStore = create<CalendarStore>((set) => ({
deleteDialogUid: null,
lastSyncedUid: null,
// Session-expiry flag — false by default; set by global QueryCache/MutationCache handler
sessionExpired: false,
setSelectedView: (view: string) => {
set({ selectedView: view })
// Persist to localStorage keyed by breakpoint group
@@ -166,4 +183,6 @@ export const useCalendarStore = create<CalendarStore>((set) => ({
set({ deleteDialogOpen: open, deleteDialogUid: uid }),
setLastSyncedUid: (uid: string | null) => set({ lastSyncedUid: uid }),
setSessionExpired: (expired: boolean) => set({ sessionExpired: expired }),
}))