import { createContext, useCallback, useContext, useRef, useState, ReactNode } from 'react'; import { useFormSubmissionsService, type FormSubmission } from './FormSubmissionsService.tsx'; import { DemoVisitRoleContext } from '../pages/Demo/DemoRoleSwitch'; /** `ready` once any read has succeeded; `failed` only while no read ever has; `unavailable` in a demo visit. */ export type NewFormSubmissionsStatus = 'loading' | 'failed' | 'ready' | 'unavailable'; interface FormSubmissionNotificationContextType { /** Meaningful only when `newFormSubmissionsStatus` is `ready`; 0 otherwise is not a claim. */ newFormSubmissionsCount: number; /** The submissions behind the count; both come from the same read. */ newFormSubmissions: FormSubmission[]; newFormSubmissionsStatus: NewFormSubmissionsStatus; refresh: () => Promise; } const Ctx = createContext(undefined); export const FormSubmissionNotificationProvider = ({ children }: { children: ReactNode }) => { const { fetchSubmissions } = useFormSubmissionsService(); // A demo visit has no public forms: the server refuses the list, so it is never asked. const inDemoVisit = useContext(DemoVisitRoleContext) !== undefined; const [newFormSubmissionsCount, setCount] = useState(0); const [newFormSubmissions, setSubmissions] = useState([]); const [newFormSubmissionsStatus, setStatus] = useState(inDemoVisit ? 'unavailable' : 'loading'); const inFlight = useRef | null>(null); const readAgain = useRef(false); // status='new' page count, capped at 100 for beta volume. A failed read keeps the last good // snapshot; with none yet, the status says so instead of a zero. const read = useCallback(async () => { if (inDemoVisit) return; setStatus((s) => (s === 'failed' ? 'loading' : s)); try { const { items } = await fetchSubmissions({ status: 'new', limit: 100 }); setCount(items.length); setSubmissions(items); setStatus('ready'); } catch { setStatus((s) => (s === 'ready' ? 'ready' : 'failed')); } }, [fetchSubmissions, inDemoVisit]); // Calls during a read coalesce into one follow-up read, so a burst of broadcasts costs two reads. const refresh = useCallback(() => { if (inFlight.current) { readAgain.current = true; return inFlight.current; } const run = (async () => { do { readAgain.current = false; await read(); } while (readAgain.current); })().finally(() => { inFlight.current = null; }); inFlight.current = run; return run; }, [read]); return ( {children} ); }; export const useFormSubmissionNotificationContext = () => { const ctx = useContext(Ctx); if (!ctx) throw new Error('useFormSubmissionNotificationContext must be used within FormSubmissionNotificationProvider'); return ctx; };