"use client"; import { useEffect, useMemo, useState } from "react"; import { useStudentActivityVersion } from "@/hooks/useStudentActivity"; import { getSyncedProgress, syncProgressEvents, } from "@/lib/api/progress-sync"; import { learnerScopeId } from "@/lib/phase3-learning"; import { listActivities, mergeActivityEvents, type ActivityEvent, } from "@/lib/student-activity"; export type ActivitySyncState = "device" | "syncing" | "synced" | "error"; /** * One account-aware activity ledger for Home and Progress. Offline events stay * visible immediately, then upload idempotently and merge with activity from * the student's other devices. */ export function useSyncedStudentActivity(limit = 50) { const version = useStudentActivityVersion(); const scopeId = learnerScopeId(); const localEvents = useMemo(() => { void version; return listActivities(Math.min(limit, 100)); }, [limit, version]); const [remoteEvents, setRemoteEvents] = useState([]); const [syncState, setSyncState] = useState(() => scopeId ? "syncing" : "device", ); useEffect(() => { let cancelled = false; async function refresh() { // Defer state changes so the effect never performs a synchronous state // write during React's commit phase. await Promise.resolve(); if (cancelled) return; if (!scopeId) { setRemoteEvents([]); setSyncState("device"); return; } setSyncState("syncing"); try { if (localEvents.length) await syncProgressEvents(localEvents); const remote = await getSyncedProgress(); if (cancelled) return; setRemoteEvents(remote.recent_events); setSyncState("synced"); } catch { if (!cancelled) setSyncState("error"); } } void refresh(); return () => { cancelled = true; }; }, [localEvents, scopeId]); const events = useMemo( () => mergeActivityEvents(remoteEvents, localEvents).slice(0, limit), [limit, localEvents, remoteEvents], ); return { events, syncState, version }; }