Files
biblical-guide.com/components/bible/bible-reader-app.tsx
Andrei 5ec48cd2b2 fix: resolve critical MVP issues - search bar overlap and language selection
- Fix search bar covering main menu: removed fixed positioning from header and use flex layout instead
- Fix Bible not displaying in selected language: pass locale parameter to /api/bible/books endpoint
- Add locale dependency to loadBooks useEffect so Bible content updates when language changes

These fixes make the MVP fully usable for all languages (en, ro, es, it).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 20:43:51 +00:00

250 lines
7.7 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { useLocale } from 'next-intl'
import { Box, Typography, Button } 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'
interface BookInfo {
id: string // UUID
orderNum: number
bookKey: string
name: string
chapterCount: number
}
export function BibleReaderApp() {
const locale = useLocale()
const [bookId, setBookId] = useState(1) // Genesis (numeric ID from search)
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(true)
const [bookmarks, setBookmarks] = useState<Set<string>>(new Set())
const [books, setBooks] = useState<BookInfo[]>([])
const [versionId, setVersionId] = useState<string>('')
const [error, setError] = useState<string | null>(null)
const [booksLoading, setBooksLoading] = useState(true)
// Load books on mount or when locale changes
useEffect(() => {
loadBooks()
}, [locale])
// Load chapter when bookId or chapter changes
useEffect(() => {
if (!booksLoading && books.length > 0) {
loadChapter(bookId, chapter)
}
}, [bookId, chapter, booksLoading, books.length])
async function loadBooks() {
setBooksLoading(true)
setError(null)
try {
const response = await fetch(`/api/bible/books?locale=${locale}`)
if (!response.ok) {
throw new Error(`Failed to load books: ${response.status}`)
}
const data = await response.json()
if (data.books && Array.isArray(data.books)) {
const bookMap: BookInfo[] = data.books.map((book: any) => ({
id: book.id,
orderNum: book.orderNum,
bookKey: book.bookKey,
name: book.name,
chapterCount: book.chapters.length
}))
setBooks(bookMap)
setVersionId(data.version?.id || 'unknown')
} else {
throw new Error('Invalid books response format')
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error loading books'
setError(errorMsg)
console.error('Error loading books:', error)
} finally {
setBooksLoading(false)
}
}
async function loadChapter(numericBookId: number, chapterNum: number) {
setLoading(true)
setError(null)
try {
const book = books.find(b => b.orderNum === numericBookId)
if (!book) {
setError(`Book not found (ID: ${numericBookId})`)
setCurrentChapter(null)
return
}
// Try cache first
const chapterId = `${book.id}-${chapterNum}`
let data = await getCachedChapter(chapterId)
// If not cached, fetch from API
if (!data) {
const response = await fetch(`/api/bible/chapter?book=${book.id}&chapter=${chapterNum}`)
if (!response.ok) {
throw new Error(`Failed to load chapter: ${response.status} ${response.statusText}`)
}
const json = await response.json()
data = json.chapter
// Cache it
if (data) {
data.id = chapterId
await cacheChapter(data).catch(e => console.error('Cache error:', e))
}
}
setCurrentChapter(data)
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error loading chapter'
setError(errorMsg)
setCurrentChapter(null)
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))
}
useEffect(() => {
// Persist bookmarks to localStorage
const bookmarkArray = Array.from(bookmarks)
localStorage.setItem('bible-reader-bookmarks', JSON.stringify(bookmarkArray))
}, [bookmarks])
// On mount, load bookmarks from localStorage
useEffect(() => {
const stored = localStorage.getItem('bible-reader-bookmarks')
if (stored) {
try {
const bookmarkArray = JSON.parse(stored) as string[]
setBookmarks(new Set(bookmarkArray))
} catch (e) {
console.error('Failed to load bookmarks:', e)
}
}
}, [])
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: 'auto', overflow: 'hidden' }}>
{/* Header with search */}
<Box
sx={{
p: 2,
bgcolor: 'background.paper',
boxShadow: 1,
flexShrink: 0
}}
>
<SearchNavigator
onNavigate={(newBookId, newChapter) => {
setBookId(newBookId)
setChapter(newChapter)
}}
/>
</Box>
{/* Reading area */}
<Box sx={{ flex: 1, overflow: 'auto' }}>
{!booksLoading && error ? (
<Box sx={{ p: 4, textAlign: 'center' }}>
<Typography color="error" variant="h6">{error}</Typography>
<Button
variant="contained"
onClick={() => location.reload()}
sx={{ mt: 2 }}
>
Reload
</Button>
</Box>
) : booksLoading ? (
<Box sx={{ p: 4, textAlign: 'center' }}>Initializing Bible reader...</Box>
) : loading ? (
<Box sx={{ p: 4, textAlign: 'center' }}>Loading chapter...</Box>
) : currentChapter ? (
<ReadingView
chapter={currentChapter}
loading={loading}
onPrevChapter={() => chapter > 1 && setChapter(chapter - 1)}
onNextChapter={() => {
const book = books.find(b => b.orderNum === bookId)
if (book && chapter < book.chapterCount) {
setChapter(chapter + 1)
}
}}
onVerseClick={handleVerseClick}
onSettingsOpen={() => setSettingsOpen(true)}
hasPrevChapter={chapter > 1}
hasNextChapter={(() => {
const book = books.find(b => b.orderNum === bookId)
return book ? chapter < book.chapterCount : false
})()}
/>
) : (
<Box sx={{ p: 4, textAlign: 'center' }}>
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>
)
}