// The signed-in user's saved projects: opening, renaming, deleting, and keeping // this device's copies in step with the server. import { useEffect, useState } from 'react'; import { loadProjectIndex } from '../utils/bibleUtils.jsx'; import { saveProjectToStorage, deleteProjectFromStorage, loadProjectById, } from '../utils/projectStorage.js'; import { saveRemoteProject, deleteRemoteProject, listRemoteProjects, loadRemoteProject, } from '../syncService.js'; export function useProjectLibrary({ authUser, project, setProject, setProjectIndex, setCurrentPage, }) { const [staleLocalProjects, setStaleLocalProjects] = useState([]); // projects where server is newer const [autoRestoredCount, setAutoRestoredCount] = useState(null); // brief "synced from another device" toast // Once we know who's signed in, reconcile the local project index against the server. // Runs on every authUser change (including logout -> different login) so a previous // account's stale suggestions never linger after switching users. Projects that exist // on the server but not on this device (e.g. a brand new browser) are pulled down // automatically instead of requiring a manual "Restore" click per project. useEffect(() => { if (!authUser) { setStaleLocalProjects([]); return; } let cancelled = false; const localIndex = loadProjectIndex(); listRemoteProjects().then(async (result) => { if (!result.ok || cancelled) return; const localMap = new Map(localIndex.map((e) => [e.id, e])); const missing = result.data.filter((e) => !localMap.has(e.id)); if (missing.length > 0) { const fetched = await Promise.all(missing.map((e) => loadRemoteProject(e.id))); fetched.filter((r) => r.ok).forEach((r) => saveProjectToStorage(r.data)); if (cancelled) return; const restoredCount = fetched.filter((r) => r.ok).length; if (restoredCount > 0) { setProjectIndex(loadProjectIndex()); setAutoRestoredCount(restoredCount); window.setTimeout(() => setAutoRestoredCount(null), 4000); } } const stale = result.data.filter((e) => { const local = localMap.get(e.id); return local && (e.lastEdited ?? 0) > (local.lastEdited ?? 0); }); if (!cancelled) setStaleLocalProjects(stale); }); return () => { cancelled = true; }; }, [authUser]); const resumeProject = (id) => { const loaded = loadProjectById(id); if (!loaded) return; setProject(loaded); const loadedChunks = loaded.chapters.flatMap((ch) => ch.chunks); const hasSelectedChunk = loaded.selectedChunkId && loadedChunks.some((c) => c.id === loaded.selectedChunkId); setCurrentPage(hasSelectedChunk ? 'study' : 'setup'); }; const renameProjectInStorage = (id, title) => { const loaded = loadProjectById(id); if (!loaded) return; const updated = { ...loaded, title, lastEdited: Date.now() }; saveProjectToStorage(updated); setProjectIndex(loadProjectIndex()); saveRemoteProject(updated); if (project?.id === id) { setProject((current) => (current ? { ...current, title } : current)); } }; const deleteProject = (id) => { if (!window.confirm('Delete this project? This cannot be undone.')) return; deleteProjectFromStorage(id); setProjectIndex(loadProjectIndex()); deleteRemoteProject(id); // fire-and-forget }; const pullLatestFromServer = async (id) => { const result = await loadRemoteProject(id); if (!result.ok) { alert('Could not pull latest version from server.'); return; } saveProjectToStorage(result.data); setProjectIndex(loadProjectIndex()); setStaleLocalProjects((prev) => prev.filter((e) => e.id !== id)); }; return { staleLocalProjects, autoRestoredCount, resumeProject, renameProjectInStorage, deleteProject, pullLatestFromServer, }; }