diff --git a/src/App.jsx b/src/App.jsx index 150824f..74e9207 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1003,8 +1003,8 @@ const App = () => { const [typedChunkBulk, setTypedChunkBulk] = useState(''); const saveTimerRef = useRef(null); const [syncStatus, setSyncStatus] = useState(''); // '' | 'syncing' | 'synced' | 'error' - const [remoteOnlyProjects, setRemoteOnlyProjects] = useState([]); // projects on server not in localStorage const [staleLocalProjects, setStaleLocalProjects] = useState([]); // projects where server is newer + const [autoRestoredCount, setAutoRestoredCount] = useState(null); // brief "synced from another device" toast const [suggestingGreekForChunkId, setSuggestingGreekForChunkId] = useState(null); const [suggestingHebrewForChunkId, setSuggestingHebrewForChunkId] = useState(null); // suggestModal: null | { chunkId, words: [{ strongKey, lexeme, translit, def }] } @@ -1389,25 +1389,40 @@ const App = () => { // 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 remote-only/stale suggestions never linger after switching users. + // 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) { - setRemoteOnlyProjects([]); setStaleLocalProjects([]); return; } + let cancelled = false; const localIndex = loadProjectIndex(); - listRemoteProjects().then((result) => { - if (!result.ok) return; + 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)); - setRemoteOnlyProjects(missing); + 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); }); - setStaleLocalProjects(stale); + if (!cancelled) setStaleLocalProjects(stale); }); + return () => { cancelled = true; }; }, [authUser]); useEffect(() => { @@ -3088,17 +3103,6 @@ const deleteProject = (id) => { deleteRemoteProject(id); // fire-and-forget }; -const restoreRemoteProject = async (id) => { - const result = await loadRemoteProject(id); - if (!result.ok) { - alert('Could not restore project from server.'); - return; - } - saveProjectToStorage(result.data); - setProjectIndex(loadProjectIndex()); - setRemoteOnlyProjects((prev) => prev.filter((e) => e.id !== id)); - }; - const pullLatestFromServer = async (id) => { const result = await loadRemoteProject(id); if (!result.ok) { @@ -3635,23 +3639,11 @@ const restoreRemoteProject = async (id) => {
- {remoteOnlyProjects.length > 0 && ( -
-

- 📥 {remoteOnlyProjects.length} project{remoteOnlyProjects.length > 1 ? 's' : ''} found on the server that aren't saved locally: + {autoRestoredCount !== null && ( +

+

+ 📥 Synced {autoRestoredCount} project{autoRestoredCount > 1 ? 's' : ''} from another device.

-
- {remoteOnlyProjects.map((entry) => ( - - ))} -
)} {staleLocalProjects.length > 0 && (