This completes Task 5 of the Bible Reader 2025 implementation plan, integrating all previously built components into a cohesive reading experience. Components added: - BibleReaderApp: Main orchestrator component with state management - ReadingSettings: Settings panel with presets and customization options Key features: - Chapter navigation with prev/next controls - SearchNavigator integration for book/chapter lookup - ReadingView with customizable reading preferences - VersDetailsPanel for verse interactions (notes, bookmarks) - ReadingSettings panel with 4 presets and custom controls - IndexedDB caching for offline chapter access - Mobile-responsive bottom sheet and desktop sidebar layouts The app now provides: - Bookmark management (client-side Set for now, backend sync in Phase 2) - Note taking (console logging for now, persistence in Phase 2) - Font customization (4 font families including dyslexia-friendly) - Size and spacing controls (font size 12-32px, line height 1.4-2.2x) - Background themes (warm, white, light gray, dark) - Preset modes (default, dyslexia, high contrast, minimal) Technical implementation: - State management via React hooks (useState, useEffect) - Cache-first loading strategy with API fallback - Storage events for cross-component preference updates - TypeScript with proper type annotations - Material-UI components for consistent styling Next steps (Phase 2): - Backend persistence for bookmarks and notes - Sync annotations across devices - Highlight system with color selection - Cross-references integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
145 lines
4.4 KiB
TypeScript
145 lines
4.4 KiB
TypeScript
'use client'
|
|
import { useState, useEffect } from 'react'
|
|
import { Box } from '@mui/material'
|
|
import { BibleChapter, BibleVerse } from '@/types'
|
|
import { getCachedChapter, cacheChapter } from '@/lib/cache-manager'
|
|
import { SearchNavigator } from './search-navigator'
|
|
import { ReadingView } from './reading-view'
|
|
import { VersDetailsPanel } from './verse-details-panel'
|
|
import { ReadingSettings } from './reading-settings'
|
|
|
|
export function BibleReaderApp() {
|
|
const [bookId, setBookId] = useState(1) // Genesis
|
|
const [chapter, setChapter] = useState(1)
|
|
const [currentChapter, setCurrentChapter] = useState<BibleChapter | null>(null)
|
|
const [selectedVerse, setSelectedVerse] = useState<BibleVerse | null>(null)
|
|
const [detailsPanelOpen, setDetailsPanelOpen] = useState(false)
|
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
const [loading, setLoading] = useState(false)
|
|
const [bookmarks, setBookmarks] = useState<Set<string>>(new Set())
|
|
|
|
// Load chapter on navigation
|
|
useEffect(() => {
|
|
loadChapter(bookId, chapter)
|
|
}, [bookId, chapter])
|
|
|
|
async function loadChapter(bookId: number, chapterNum: number) {
|
|
setLoading(true)
|
|
try {
|
|
// Try cache first
|
|
const chapterId = `${bookId}-${chapterNum}`
|
|
let data = await getCachedChapter(chapterId)
|
|
|
|
// If not cached, fetch from API
|
|
if (!data) {
|
|
const response = await fetch(`/api/bible/chapter?book=${bookId}&chapter=${chapterNum}`)
|
|
if (response.ok) {
|
|
const json = await response.json()
|
|
data = json.chapter
|
|
|
|
// Cache it
|
|
if (data) {
|
|
data.id = chapterId
|
|
await cacheChapter(data)
|
|
}
|
|
} else {
|
|
console.error('Failed to load chapter:', response.status)
|
|
}
|
|
}
|
|
|
|
setCurrentChapter(data)
|
|
} catch (error) {
|
|
console.error('Error loading chapter:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleVerseClick = (verseId: string) => {
|
|
const verse = currentChapter?.verses.find(v => v.id === verseId)
|
|
if (verse) {
|
|
setSelectedVerse(verse)
|
|
setDetailsPanelOpen(true)
|
|
}
|
|
}
|
|
|
|
const handleToggleBookmark = () => {
|
|
if (!selectedVerse) return
|
|
const newBookmarks = new Set(bookmarks)
|
|
if (newBookmarks.has(selectedVerse.id)) {
|
|
newBookmarks.delete(selectedVerse.id)
|
|
} else {
|
|
newBookmarks.add(selectedVerse.id)
|
|
}
|
|
setBookmarks(newBookmarks)
|
|
// TODO: Sync to backend in Phase 2
|
|
console.log('Bookmarks updated:', Array.from(newBookmarks))
|
|
}
|
|
|
|
const handleAddNote = (note: string) => {
|
|
if (!selectedVerse) return
|
|
// TODO: Save note to backend in Phase 2
|
|
console.log(`Note for verse ${selectedVerse.id}:`, note)
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }}>
|
|
{/* Header with search */}
|
|
<Box
|
|
sx={{
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
zIndex: 50,
|
|
p: 2,
|
|
bgcolor: 'background.paper',
|
|
boxShadow: 1
|
|
}}
|
|
>
|
|
<SearchNavigator
|
|
onNavigate={(newBookId, newChapter) => {
|
|
setBookId(newBookId)
|
|
setChapter(newChapter)
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Reading area */}
|
|
<Box sx={{ flex: 1, mt: '80px', overflow: 'auto' }}>
|
|
{currentChapter ? (
|
|
<ReadingView
|
|
chapter={currentChapter}
|
|
loading={loading}
|
|
onPrevChapter={() => chapter > 1 && setChapter(chapter - 1)}
|
|
onNextChapter={() => setChapter(chapter + 1)}
|
|
onVerseClick={handleVerseClick}
|
|
onSettingsOpen={() => setSettingsOpen(true)}
|
|
hasPrevChapter={chapter > 1}
|
|
hasNextChapter={true} // TODO: Check actual chapter count based on book
|
|
/>
|
|
) : (
|
|
<Box sx={{ p: 4, textAlign: 'center' }}>
|
|
{loading ? 'Loading Bible reader...' : 'Failed to load chapter. Please try again.'}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Details panel */}
|
|
<VersDetailsPanel
|
|
verse={selectedVerse}
|
|
isOpen={detailsPanelOpen}
|
|
onClose={() => setDetailsPanelOpen(false)}
|
|
isBookmarked={selectedVerse ? bookmarks.has(selectedVerse.id) : false}
|
|
onToggleBookmark={handleToggleBookmark}
|
|
onAddNote={handleAddNote}
|
|
/>
|
|
|
|
{/* Settings panel */}
|
|
{settingsOpen && (
|
|
<ReadingSettings onClose={() => setSettingsOpen(false)} />
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|