From f96cd9231e7428060b255e11aad4705ea3ead3d0 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 13 Oct 2025 06:09:50 +0000 Subject: [PATCH] feat: integrate reading plans with Bible reader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bible Reader Integration: - Fetch and display active reading plan progress in Bible reader - Progress bar shows plan completion percentage when user has active plan - Progress bar color changes to green for active plans - Display "Plan Name Progress" with days completed below bar Reading Plan Navigation: - Add "Read the Bible" button to active reading plan detail page - Button shows current/next reading selection (Day X: Book Chapter) - Navigate directly to Bible reader with correct book and chapter - Smart selection: current day if incomplete, next incomplete day if current is done - Only shown for ACTIVE plans Technical: - Load active plans via /api/user/reading-plans endpoint - Calculate progress from completedDays vs plan duration - Use getCurrentReading() helper to determine next reading - URL encoding for book names with spaces 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/[locale]/bible/reader.tsx | 49 ++++++++++++++++- app/[locale]/reading-plans/[id]/page.tsx | 68 +++++++++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/app/[locale]/bible/reader.tsx b/app/[locale]/bible/reader.tsx index c877a99..811a182 100644 --- a/app/[locale]/bible/reader.tsx +++ b/app/[locale]/bible/reader.tsx @@ -280,6 +280,9 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha const [readingProgress, setReadingProgress] = useState(null) const [hasLoadedInitialProgress, setHasLoadedInitialProgress] = useState(false) + // Active reading plan state + const [activeReadingPlan, setActiveReadingPlan] = useState(null) + // Page transition state const [isTransitioning, setIsTransitioning] = useState(false) @@ -588,6 +591,33 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha } }, [selectedVersion, debouncedVersion]) + // Load active reading plan + useEffect(() => { + if (typeof window === 'undefined') return + + const loadActiveReadingPlan = async () => { + if (user) { + const token = localStorage.getItem('authToken') + if (!token) return + + try { + const response = await fetch('/api/user/reading-plans', { + headers: { 'Authorization': `Bearer ${token}` } + }) + const data = await response.json() + if (data.success && data.plans) { + // Find the first active plan + const activePlan = data.plans.find((p: any) => p.status === 'ACTIVE') + setActiveReadingPlan(activePlan || null) + } + } catch (error) { + console.error('Error loading active reading plan:', error) + } + } + } + loadActiveReadingPlan() + }, [user]) + // Load reading progress when version changes useEffect(() => { // Only run on client side to avoid hydration mismatch @@ -1501,6 +1531,16 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha // Calculate reading progress percentage const calculateProgress = () => { + // If user has an active reading plan, show plan progress instead + if (activeReadingPlan) { + const planDuration = activeReadingPlan.plan?.duration || activeReadingPlan.targetEndDate + ? Math.ceil((new Date(activeReadingPlan.targetEndDate).getTime() - new Date(activeReadingPlan.startDate).getTime()) / (1000 * 60 * 60 * 24)) + : 365 + const completedDays = activeReadingPlan.completedDays || 0 + return Math.min(Math.round((completedDays / planDuration) * 100), 100) + } + + // Default: Calculate progress based on chapters read in entire Bible if (!books.length || !selectedBook || !selectedChapter) return 0 // Find current book index and total chapters before current position @@ -1939,7 +1979,7 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha - Reading Progress + {activeReadingPlan ? `${activeReadingPlan.name} Progress` : 'Reading Progress'} {calculateProgress()}% @@ -1954,10 +1994,15 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha backgroundColor: 'action.hover', '& .MuiLinearProgress-bar': { borderRadius: 3, - backgroundColor: 'primary.main' + backgroundColor: activeReadingPlan ? 'success.main' : 'primary.main' } }} /> + {activeReadingPlan && ( + + {activeReadingPlan.completedDays} of {activeReadingPlan.plan?.duration || 'custom'} days completed + + )} )} diff --git a/app/[locale]/reading-plans/[id]/page.tsx b/app/[locale]/reading-plans/[id]/page.tsx index cf9969f..a0b6ced 100644 --- a/app/[locale]/reading-plans/[id]/page.tsx +++ b/app/[locale]/reading-plans/[id]/page.tsx @@ -40,7 +40,8 @@ import { EmojiEvents, TrendingUp, Edit, - Save + Save, + MenuBook } from '@mui/icons-material' import Link from 'next/link' @@ -210,6 +211,39 @@ export default function ReadingPlanDetailPage() { return entry?.notes || '' } + const getCurrentReading = () => { + if (!plan) return null + + const schedule = plan.plan?.schedule || plan.customSchedule + if (!schedule || !Array.isArray(schedule)) return null + + // Get the current day's reading (or first incomplete day) + let dayToRead = plan.currentDay + + // If current day is completed, find the next incomplete day + if (isDayCompleted(dayToRead)) { + for (let i = dayToRead; i <= schedule.length; i++) { + if (!isDayCompleted(i)) { + dayToRead = i + break + } + } + } + + const daySchedule = schedule[dayToRead - 1] + if (!daySchedule || !daySchedule.readings || daySchedule.readings.length === 0) { + return null + } + + const reading = daySchedule.readings[0] + return { + day: dayToRead, + book: reading.book, + chapter: reading.chapter, + verses: reading.verses + } + } + if (loading) { return ( @@ -363,6 +397,38 @@ export default function ReadingPlanDetailPage() { + {/* Read the Bible Button */} + {plan.status === 'ACTIVE' && (() => { + const currentReading = getCurrentReading() + return currentReading ? ( + + + + Day {currentReading.day} of {duration} + + + ) : null + })()} + {/* Reading Schedule */}