Add reading plans: catalog, progress tracking, and custom plans

Replaces the old single-book reading goal with a full Reading Plans page:
9 built-in plans (Whole Bible, OT/NT, topical) with cover photos and
color-themed cards, day-by-day checklists synced to the server per user,
inline "mark today read" in the Bible reader, and user-created plans with
their own book picker and an optional uploaded cover photo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5 committed 2026-09-23 12:51:28 -04:00
1 parent f1026dc194
commit 600b3d3ab9
21 files changed
+1166 -159

No files matched your search

+21 -5
View File
@@ -4,10 +4,10 @@ import { useAudio } from './hooks/useAudio.js';
import { useAuth } from './hooks/useAuth.js';
import { useBibleReader } from './hooks/useBibleReader.js';
import { useProjectLibrary } from './hooks/useProjectLibrary.js';
import { useReadingPlan } from './hooks/useReadingPlan.js';
import { useReadingPlans } from './hooks/useReadingPlans.js';
import { useSharedView } from './hooks/useSharedView.js';
import HomePage from './pages/HomePage.jsx';
import { saveRemoteProject } from './syncService.js';
import { getRemoteReadingProgress, saveRemoteProject } from './syncService.js';
import { buildExportHtml, loadProjectIndex, switchStorageUser } from './utils/bibleUtils.jsx';
import { migrateOldStorageKeys, saveProjectToStorage } from './utils/projectStorage.js';
@@ -21,6 +21,7 @@ const SettingsPage = lazy(() => import('./pages/SettingsPage.jsx'));
const SetupPage = lazy(() => import('./pages/SetupPage.jsx'));
const StudyPage = lazy(() => import('./pages/StudyPage.jsx'));
const PronunciationPage = lazy(() => import('./pages/PronunciationPage.jsx'));
const ReadingPlansPage = lazy(() => import('./pages/ReadingPlansPage.jsx'));
// ---------------------------------------------------------------------------
// App component
@@ -28,7 +29,7 @@ const PronunciationPage = lazy(() => import('./pages/PronunciationPage.jsx'));
const App = () => {
const [project, setProject] = useState(null);
// 'home' | 'setup' | 'study' | 'reader' | 'import' | 'pronunciation' | 'settings' | 'admin'
// 'home' | 'setup' | 'study' | 'reader' | 'import' | 'pronunciation' | 'settings' | 'admin' | 'reading-plans'
const [currentPage, setCurrentPage] = useState('home');
const [projectIndex, setProjectIndex] = useState([]);
const [saveStatus, setSaveStatus] = useState('');
@@ -36,7 +37,7 @@ const App = () => {
const [syncStatus, setSyncStatus] = useState(''); // '' | 'syncing' | 'synced' | 'error'
const reader = useBibleReader(currentPage);
const audio = useAudio(reader.readerBookAbbrev, reader.readerChapter);
const readingPlan = useReadingPlan();
const readingPlans = useReadingPlans();
const { authUser, setAuthUser, authServerDown, signIn, logout } = useAuth({
onSignIn: (user) => {
setProjectIndex(switchStorageUser(user.id));
@@ -45,6 +46,10 @@ const App = () => {
.then((r) => r.ok ? r.json() : null)
.then((serverInk) => { if (serverInk) reader.mergeServerInk(serverInk); })
.catch(() => {});
// Same for reading plan progress
getRemoteReadingProgress().then((result) => {
if (result.ok) readingPlans.mergeServerReadingProgress(result.data);
});
},
onSignOut: () => {
setProjectIndex(switchStorageUser(null));
@@ -110,6 +115,14 @@ const App = () => {
// Navigation
const openBibleReader = () => setCurrentPage('reader');
const openReadingPlans = () => setCurrentPage('reading-plans');
// Jumps straight into a specific plan day's first chapter and opens the reader.
const readReadingPlanDay = (bookAbbrev, chapter) => {
reader.jumpToReaderVerse(bookAbbrev, chapter, 1);
setCurrentPage('reader');
};
const openNewProject = () => {
setProject(null);
setCurrentPage('setup');
@@ -155,8 +168,10 @@ const App = () => {
// Home
projectIndex,
...library,
...readingPlan,
...readingPlans,
openBibleReader,
openReadingPlans,
readReadingPlanDay,
openImportProject,
openNewProject,
// Audio (book player on Home + chapter player in the Reader)
@@ -238,6 +253,7 @@ function AppRouter() {
if (currentPage === 'admin') return <AdminPage />;
if (currentPage === 'settings') return <SettingsPage />;
if (currentPage === 'reading-plans') return <ReadingPlansPage />;
if (currentPage === 'reader') return <ReaderPage />;
if (currentPage === 'import') return <ImportPage />;
if (currentPage === 'study') return <StudyPage />;