diff --git a/app/[locale]/bible/page.tsx b/app/[locale]/bible/page.tsx
index 1dd257b..2465153 100644
--- a/app/[locale]/bible/page.tsx
+++ b/app/[locale]/bible/page.tsx
@@ -1,66 +1,10 @@
-import { Suspense } from 'react'
-import { redirect } from 'next/navigation'
-import BibleReader from './reader'
-import { prisma } from '@/lib/db'
+import { BibleReaderApp } from '@/components/bible/bible-reader-app'
-interface PageProps {
- searchParams: Promise<{
- version?: string
- book?: string
- chapter?: string
- verse?: string
- }>
- params: Promise<{
- locale: string
- }>
+export const metadata = {
+ title: 'Read Bible',
+ description: 'Modern Bible reader with offline support'
}
-// Helper function to convert UUIDs to SEO-friendly slugs
-async function convertToSeoUrl(versionId: string, bookId: string, chapter: string, locale: string) {
- try {
- const version = await prisma.bibleVersion.findUnique({
- where: { id: versionId }
- })
-
- const book = await prisma.bibleBook.findUnique({
- where: { id: bookId }
- })
-
- if (version && book) {
- const versionSlug = version.abbreviation.toLowerCase()
- const bookSlug = book.bookKey.toLowerCase()
- return `/${locale}/bible/${versionSlug}/${bookSlug}/${chapter}`
- }
- } catch (error) {
- console.error('Error converting to SEO URL:', error)
- }
- return null
+export default function BiblePage() {
+ return
}
-
-export default async function BiblePage({ searchParams, params }: PageProps) {
- const { version, book, chapter } = await searchParams
- const { locale } = await params
-
- // If we have the old URL format with UUIDs, redirect to SEO-friendly URL
- if (version && book && chapter) {
- const seoUrl = await convertToSeoUrl(version, book, chapter, locale)
- if (seoUrl) {
- redirect(seoUrl)
- }
- }
-
- return (
-
- Loading Bible reader...
-
- }>
-
-
- )
-}
\ No newline at end of file
diff --git a/components/bible/bible-reader-app.tsx b/components/bible/bible-reader-app.tsx
new file mode 100644
index 0000000..59b6430
--- /dev/null
+++ b/components/bible/bible-reader-app.tsx
@@ -0,0 +1,144 @@
+'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(null)
+ const [selectedVerse, setSelectedVerse] = useState(null)
+ const [detailsPanelOpen, setDetailsPanelOpen] = useState(false)
+ const [settingsOpen, setSettingsOpen] = useState(false)
+ const [loading, setLoading] = useState(false)
+ const [bookmarks, setBookmarks] = useState>(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 (
+
+ {/* Header with search */}
+
+ {
+ setBookId(newBookId)
+ setChapter(newChapter)
+ }}
+ />
+
+
+ {/* Reading area */}
+
+ {currentChapter ? (
+ 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
+ />
+ ) : (
+
+ {loading ? 'Loading Bible reader...' : 'Failed to load chapter. Please try again.'}
+
+ )}
+
+
+ {/* Details panel */}
+ setDetailsPanelOpen(false)}
+ isBookmarked={selectedVerse ? bookmarks.has(selectedVerse.id) : false}
+ onToggleBookmark={handleToggleBookmark}
+ onAddNote={handleAddNote}
+ />
+
+ {/* Settings panel */}
+ {settingsOpen && (
+ setSettingsOpen(false)} />
+ )}
+
+ )
+}
diff --git a/components/bible/reading-settings.tsx b/components/bible/reading-settings.tsx
new file mode 100644
index 0000000..e2a360e
--- /dev/null
+++ b/components/bible/reading-settings.tsx
@@ -0,0 +1,182 @@
+'use client'
+import { useState, useEffect } from 'react'
+import { Box, Paper, Typography, Button, Slider, FormControl, InputLabel, Select, MenuItem, useMediaQuery, useTheme, IconButton } from '@mui/material'
+import { Close } from '@mui/icons-material'
+import { ReadingPreference } from '@/types'
+import { getPreset, loadPreferences, savePreferences } from '@/lib/reading-preferences'
+
+const FONTS = [
+ { value: 'georgia', label: 'Georgia (Serif)' },
+ { value: 'merriweather', label: 'Merriweather (Serif)' },
+ { value: 'inter', label: 'Inter (Sans)' },
+ { value: 'atkinson', label: 'Atkinson (Dyslexia-friendly)' },
+]
+
+interface ReadingSettingsProps {
+ onClose: () => void
+}
+
+export function ReadingSettings({ onClose }: ReadingSettingsProps) {
+ const theme = useTheme()
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
+ const [preferences, setPreferences] = useState(loadPreferences())
+
+ // Reload preferences on mount
+ useEffect(() => {
+ setPreferences(loadPreferences())
+ }, [])
+
+ const applyPreset = (presetName: string) => {
+ const preset = getPreset(presetName as any)
+ setPreferences(preset)
+ savePreferences(preset)
+ // Trigger a storage event to notify other components
+ window.dispatchEvent(new Event('storage'))
+ }
+
+ const handleChange = (key: keyof ReadingPreference, value: any) => {
+ const updated: ReadingPreference = {
+ ...preferences,
+ [key]: value,
+ preset: 'custom' as const
+ }
+ setPreferences(updated)
+ savePreferences(updated)
+ // Trigger a storage event to notify other components
+ window.dispatchEvent(new Event('storage'))
+ }
+
+ const content = (
+
+
+ Reading Settings
+
+
+
+
+
+ {/* Presets */}
+
+ Presets
+
+ {['default', 'dyslexia', 'highContrast', 'minimal'].map((preset) => (
+
+ ))}
+
+
+
+ {/* Font */}
+
+ Font
+
+
+
+ {/* Font Size */}
+
+ Size: {preferences.fontSize}px
+ handleChange('fontSize', value)}
+ min={12}
+ max={32}
+ step={1}
+ marks={[
+ { value: 12, label: '12' },
+ { value: 22, label: '22' },
+ { value: 32, label: '32' },
+ ]}
+ />
+
+
+ {/* Line Height */}
+
+ Line Height: {preferences.lineHeight.toFixed(1)}x
+ handleChange('lineHeight', value)}
+ min={1.4}
+ max={2.2}
+ step={0.1}
+ marks={[
+ { value: 1.4, label: '1.4' },
+ { value: 1.8, label: '1.8' },
+ { value: 2.2, label: '2.2' },
+ ]}
+ />
+
+
+ {/* Background Color */}
+
+ Background
+
+
+
+ )
+
+ if (isMobile) {
+ return (
+
+ {content}
+
+ )
+ }
+
+ return (
+
+ {content}
+
+ )
+}