diff --git a/src/App.jsx b/src/App.jsx index cd2fdd6..d445a39 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import { lazy, Suspense, useEffect, useRef, useState } from 'react'; import { AppContext, useApp } from './context/AppContext.js'; import { useAudio } from './hooks/useAudio.js'; +import { useAuth } from './hooks/useAuth.js'; import { useBibleReader } from './hooks/useBibleReader.js'; import HomePage from './pages/HomePage.jsx'; @@ -42,8 +43,6 @@ import { deleteRemoteProject, listRemoteProjects, loadRemoteProject, - getCurrentUser, - logoutUser, getShareStatus, enableSharing, disableSharing, @@ -81,30 +80,6 @@ const App = () => { title: 'Titus 1 Study', }); const [titleEdited, setTitleEdited] = useState(false); - // authUser: undefined = still checking · null = signed out · object = signed in - const [authUser, setAuthUser] = useState(undefined); - const [authServerDown, setAuthServerDown] = useState(false); - const [authMode, setAuthMode] = useState('login'); // 'login' | 'register' - const [authForm, setAuthForm] = useState({ email: '', password: '' }); - const [authError, setAuthError] = useState(''); - const [authBusy, setAuthBusy] = useState(false); - const [authMfaPending, setAuthMfaPending] = useState(false); // password ok, waiting on TOTP/backup code - const [authMfaCode, setAuthMfaCode] = useState(''); - const [authMfaUseBackup, setAuthMfaUseBackup] = useState(false); - const [mfaSetup, setMfaSetup] = useState(null); // { secret, qrCodeDataUrl } while setup is in progress - const [mfaSetupCode, setMfaSetupCode] = useState(''); - const [mfaSetupError, setMfaSetupError] = useState(''); - const [mfaSetupBusy, setMfaSetupBusy] = useState(false); - const [mfaBackupCodes, setMfaBackupCodes] = useState(null); // shown once, right after enabling - const [mfaDisablePassword, setMfaDisablePassword] = useState(''); - const [mfaDisableError, setMfaDisableError] = useState(''); - const [podcastNameInput, setPodcastNameInput] = useState(''); - const [podcastNameSaving, setPodcastNameSaving] = useState(false); - const [podcastNameSaved, setPodcastNameSaved] = useState(false); - const [changePasswordForm, setChangePasswordForm] = useState({ current: '', next: '', confirm: '' }); - const [changePasswordBusy, setChangePasswordBusy] = useState(false); - const [changePasswordError, setChangePasswordError] = useState(''); - const [changePasswordSaved, setChangePasswordSaved] = useState(false); const [exportMenuOpen, setExportMenuOpen] = useState(false); // Read-only share links const [shareToken, setShareToken] = useState(null); @@ -168,6 +143,20 @@ const App = () => { const [importError, setImportError] = useState(''); const reader = useBibleReader(currentPage); const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter); + const { authUser, setAuthUser, authServerDown, signIn, logout } = useAuth({ + onSignIn: (user) => { + setProjectIndex(switchStorageUser(user.id)); + // Load reader ink from the server; server wins over any stale localStorage copy + fetch('/api/reader/ink') + .then((r) => r.ok ? r.json() : null) + .then((serverInk) => { if (serverInk) reader.mergeServerInk(serverInk); }) + .catch(() => {}); + }, + onSignOut: () => { + setProjectIndex(switchStorageUser(null)); + goHome(); + }, + }); const [studyLayout, setStudyLayout] = useState(() => { const saved = localStorage.getItem('studyLayout'); return saved === 'stacked' ? 'stacked' : 'split'; // 'annotate' (Draw) was removed @@ -288,34 +277,8 @@ const App = () => { useEffect(() => { migrateOldStorageKeys(); setProjectIndex(loadProjectIndex()); - - getCurrentUser().then((result) => { - if (result.ok) { - setAuthUser(result.user); - if (result.user) { - setProjectIndex(switchStorageUser(result.user.id)); - // Load reader ink from the server; server wins over any stale localStorage copy - fetch('/api/reader/ink') - .then((r) => r.ok ? r.json() : null) - .then((serverInk) => { - if (!serverInk) return; - reader.mergeServerInk(serverInk); - }) - .catch(() => {}); - } - } else { - // Genuine network failure (server unreachable) — fall back to local-only mode. - setAuthServerDown(true); - setAuthUser(null); - } - }); }, []); - // Keep the Settings page's podcast-name field in sync with whichever account is signed in. - useEffect(() => { - setPodcastNameInput(authUser?.podcastName ?? ''); - }, [authUser?.id]); - // A ?share=TOKEN URL loads a read-only view of someone else's project — no // login required, so this runs independent of auth state entirely. useEffect(() => { @@ -2028,12 +1991,7 @@ const deleteProject = (id) => {