fix: critical issues - settings sync, error handling, bookmarks persistence

- Fix settings synchronization: ReadingView now listens to storage events for real-time preference updates
- Add comprehensive error handling to loadChapter with proper state management
- Add comprehensive error handling to loadBooks with booksLoading state
- Add localStorage persistence for bookmarks (load on mount, save on change)
- Display error messages in UI with reload button and proper loading states

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-11 20:29:20 +00:00
parent 1dc4d761b5
commit b8652b9f0a
2 changed files with 89 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Box } from '@mui/material' import { Box, Typography, Button } from '@mui/material'
import { BibleChapter, BibleVerse } from '@/types' import { BibleChapter, BibleVerse } from '@/types'
import { getCachedChapter, cacheChapter } from '@/lib/cache-manager' import { getCachedChapter, cacheChapter } from '@/lib/cache-manager'
import { SearchNavigator } from './search-navigator' import { SearchNavigator } from './search-navigator'
@@ -27,6 +27,8 @@ export function BibleReaderApp() {
const [bookmarks, setBookmarks] = useState<Set<string>>(new Set()) const [bookmarks, setBookmarks] = useState<Set<string>>(new Set())
const [books, setBooks] = useState<BookInfo[]>([]) const [books, setBooks] = useState<BookInfo[]>([])
const [versionId, setVersionId] = useState<string>('') const [versionId, setVersionId] = useState<string>('')
const [error, setError] = useState<string | null>(null)
const [booksLoading, setBooksLoading] = useState(true)
// Load books on mount // Load books on mount
useEffect(() => { useEffect(() => {
@@ -35,40 +37,52 @@ export function BibleReaderApp() {
// Load chapter when bookId or chapter changes // Load chapter when bookId or chapter changes
useEffect(() => { useEffect(() => {
if (books.length > 0) { if (!booksLoading && books.length > 0) {
loadChapter(bookId, chapter) loadChapter(bookId, chapter)
} }
}, [bookId, chapter, books]) }, [bookId, chapter, booksLoading, books.length])
async function loadBooks() { async function loadBooks() {
setBooksLoading(true)
setError(null)
try { try {
const response = await fetch('/api/bible/books') const response = await fetch('/api/bible/books')
if (response.ok) { if (!response.ok) {
const json = await response.json() throw new Error(`Failed to load books: ${response.status}`)
if (json.success && json.books) { }
const bookMap: BookInfo[] = json.books.map((book: any) => ({
id: book.id, const data = await response.json()
orderNum: book.orderNum, if (data.books && Array.isArray(data.books)) {
bookKey: book.bookKey, const bookMap: BookInfo[] = data.books.map((book: any) => ({
name: book.name, id: book.id,
chapterCount: book.chapters.length orderNum: book.orderNum,
})) bookKey: book.bookKey,
setBooks(bookMap) name: book.name,
setVersionId(json.version.id) chapterCount: book.chapters.length
} }))
setBooks(bookMap)
setVersionId(data.version?.id || 'unknown')
} else {
throw new Error('Invalid books response format')
} }
} catch (error) { } catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error loading books'
setError(errorMsg)
console.error('Error loading books:', error) console.error('Error loading books:', error)
} finally {
setBooksLoading(false)
} }
} }
async function loadChapter(numericBookId: number, chapterNum: number) { async function loadChapter(numericBookId: number, chapterNum: number) {
setLoading(true) setLoading(true)
setError(null)
try { try {
// Find the book by orderNum
const book = books.find(b => b.orderNum === numericBookId) const book = books.find(b => b.orderNum === numericBookId)
if (!book) { if (!book) {
console.error('Book not found for orderNum:', numericBookId) setError(`Book not found (ID: ${numericBookId})`)
setCurrentChapter(null) setCurrentChapter(null)
return return
} }
@@ -80,24 +94,26 @@ export function BibleReaderApp() {
// If not cached, fetch from API // If not cached, fetch from API
if (!data) { if (!data) {
const response = await fetch(`/api/bible/chapter?book=${book.id}&chapter=${chapterNum}`) const response = await fetch(`/api/bible/chapter?book=${book.id}&chapter=${chapterNum}`)
if (response.ok) {
const json = await response.json()
data = json.chapter
// Cache it if (!response.ok) {
if (data) { throw new Error(`Failed to load chapter: ${response.status} ${response.statusText}`)
data.id = chapterId }
data.bookId = numericBookId // Keep numeric ID for consistency
data.chapter = chapterNum const json = await response.json()
await cacheChapter(data) data = json.chapter
}
} else { // Cache it
console.error('Failed to load chapter:', response.status) if (data) {
data.id = chapterId
await cacheChapter(data).catch(e => console.error('Cache error:', e))
} }
} }
setCurrentChapter(data) setCurrentChapter(data)
} catch (error) { } catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error loading chapter'
setError(errorMsg)
setCurrentChapter(null)
console.error('Error loading chapter:', error) console.error('Error loading chapter:', error)
} finally { } finally {
setLoading(false) setLoading(false)
@@ -125,6 +141,25 @@ export function BibleReaderApp() {
console.log('Bookmarks updated:', Array.from(newBookmarks)) 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) => { const handleAddNote = (note: string) => {
if (!selectedVerse) return if (!selectedVerse) return
// TODO: Save note to backend in Phase 2 // TODO: Save note to backend in Phase 2
@@ -156,7 +191,22 @@ export function BibleReaderApp() {
{/* Reading area */} {/* Reading area */}
<Box sx={{ flex: 1, mt: '80px', overflow: 'auto' }}> <Box sx={{ flex: 1, mt: '80px', overflow: 'auto' }}>
{currentChapter ? ( {!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 <ReadingView
chapter={currentChapter} chapter={currentChapter}
loading={loading} loading={loading}
@@ -177,7 +227,7 @@ export function BibleReaderApp() {
/> />
) : ( ) : (
<Box sx={{ p: 4, textAlign: 'center' }}> <Box sx={{ p: 4, textAlign: 'center' }}>
{loading ? 'Loading Bible reader...' : 'Failed to load chapter. Please try again.'} Failed to load chapter. Please try again.
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -32,7 +32,14 @@ export function ReadingView({
const [showControls, setShowControls] = useState(!isMobile) const [showControls, setShowControls] = useState(!isMobile)
useEffect(() => { useEffect(() => {
const handleStorageChange = () => {
setPreferences(loadPreferences())
}
setPreferences(loadPreferences()) setPreferences(loadPreferences())
window.addEventListener('storage', handleStorageChange)
return () => window.removeEventListener('storage', handleStorageChange)
}, []) }, [])
const cssVars = getCSSVariables(preferences) const cssVars = getCSSVariables(preferences)