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:
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user