'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { useTranslations, useLocale } from 'next-intl' import { useAuth } from '@/hooks/use-auth' import { useSearchParams, useRouter } from 'next/navigation' import { Box, Typography, IconButton, Drawer, List, ListItem, ListItemButton, ListItemText, AppBar, Toolbar, Button, Menu, MenuItem, Slider, Switch, FormControlLabel, Tooltip, Fab, Paper, Divider, Chip, ButtonGroup, useTheme, useMediaQuery, Collapse, ListItemIcon, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Snackbar, Alert, Backdrop, CircularProgress, Grid, FormControl, InputLabel, Select, Container, Autocomplete } from '@mui/material' import { Menu as MenuIcon, Settings, Bookmark, BookmarkBorder, Share, ContentCopy, ArrowBack, ArrowForward, FullscreenExit, Fullscreen, KeyboardArrowUp, KeyboardArrowDown, FormatSize, Palette, Note, Close, ExpandLess, ExpandMore, MenuBook, Visibility, Speed, Chat } from '@mui/icons-material' interface BibleVerse { id: string verseNum: number text: string } interface BibleChapter { id: string chapterNum: number verses: BibleVerse[] } interface BibleVersion { id: string name: string abbreviation: string language: string isDefault?: boolean } interface BibleBook { id: string versionId: string name: string testament: string orderNum: number bookKey: string chapters: BibleChapter[] } interface ReadingPreferences { fontSize: number lineHeight: number fontFamily: string theme: 'light' | 'dark' | 'sepia' showVerseNumbers: boolean columnLayout: boolean readingMode: boolean } const defaultPreferences: ReadingPreferences = { fontSize: 18, lineHeight: 1.6, fontFamily: 'serif', theme: 'light', showVerseNumbers: true, columnLayout: false, readingMode: false } export default function BibleReaderNew() { const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('md')) const t = useTranslations('pages.bible') const locale = useLocale() const router = useRouter() const searchParams = useSearchParams() const { user } = useAuth() // Core state const [books, setBooks] = useState([]) const [versions, setVersions] = useState([]) const [selectedVersion, setSelectedVersion] = useState('') const [selectedBook, setSelectedBook] = useState('') const [selectedChapter, setSelectedChapter] = useState(1) const [verses, setVerses] = useState([]) const [loading, setLoading] = useState(true) const [versionsLoading, setVersionsLoading] = useState(true) const [showAllVersions, setShowAllVersions] = useState(false) // UI state const [settingsOpen, setSettingsOpen] = useState(false) const [preferences, setPreferences] = useState(defaultPreferences) const [highlightedVerse, setHighlightedVerse] = useState(null) const [showScrollTop, setShowScrollTop] = useState(false) const [previousVerses, setPreviousVerses] = useState([]) // Keep previous content during loading // Bookmark state const [isChapterBookmarked, setIsChapterBookmarked] = useState(false) const [verseBookmarks, setVerseBookmarks] = useState<{[key: string]: any}>({}) const [bookmarkLoading, setBookmarkLoading] = useState(false) // Note dialog state const [noteDialog, setNoteDialog] = useState<{ open: boolean verse?: BibleVerse note: string }>({ open: false, note: '' }) // Copy feedback const [copyFeedback, setCopyFeedback] = useState<{ open: boolean message: string }>({ open: false, message: '' }) // Refs const contentRef = useRef(null) const verseRefs = useRef<{[key: number]: HTMLDivElement}>({}) // Load user preferences from localStorage useEffect(() => { const savedPrefs = localStorage.getItem('bibleReaderPreferences') if (savedPrefs) { try { const parsed = JSON.parse(savedPrefs) setPreferences({ ...defaultPreferences, ...parsed }) } catch (e) { console.error('Failed to parse preferences:', e) } } // Load saved version preference const savedVersion = localStorage.getItem('selectedBibleVersion') if (savedVersion && versions.length > 0) { const version = versions.find(v => v.id === savedVersion) if (version) { setSelectedVersion(savedVersion) } } }, [versions]) // Save preferences to localStorage useEffect(() => { localStorage.setItem('bibleReaderPreferences', JSON.stringify(preferences)) }, [preferences]) // Handle full screen mode - add/remove CSS class to body useEffect(() => { if (preferences.readingMode) { document.body.classList.add('bible-fullscreen-mode') } else { document.body.classList.remove('bible-fullscreen-mode') } // Cleanup on unmount return () => { document.body.classList.remove('bible-fullscreen-mode') } }, [preferences.readingMode]) // Save selected version to localStorage useEffect(() => { if (selectedVersion) { localStorage.setItem('selectedBibleVersion', selectedVersion) } }, [selectedVersion]) // Scroll handler for show scroll to top button useEffect(() => { const handleScroll = () => { setShowScrollTop(window.scrollY > 300) } window.addEventListener('scroll', handleScroll) return () => window.removeEventListener('scroll', handleScroll) }, []) // Fetch versions based on showAllVersions state and locale useEffect(() => { setVersionsLoading(true) const url = showAllVersions ? '/api/bible/versions?all=true&limit=200' // Limit to first 200 for performance : `/api/bible/versions?language=${locale}` fetch(url) .then(res => res.json()) .then(data => { if (data.success && data.versions) { setVersions(data.versions) // Keep current selection if it exists in new list, otherwise select default/first const currentVersionExists = data.versions.some((v: BibleVersion) => v.id === selectedVersion) if (!currentVersionExists || !selectedVersion) { const defaultVersion = data.versions.find((v: BibleVersion) => v.isDefault) || data.versions[0] if (defaultVersion) { setSelectedVersion(defaultVersion.id) } } } setVersionsLoading(false) }) .catch(err => { console.error('Error fetching versions:', err) setVersionsLoading(false) }) }, [locale, showAllVersions, selectedVersion]) // Handle URL parameters for bookmark navigation useEffect(() => { const urlVersion = searchParams.get('version') const urlBook = searchParams.get('book') const urlChapter = searchParams.get('chapter') const urlVerse = searchParams.get('verse') if (urlVersion && versions.length > 0) { // Check if this version exists const version = versions.find(v => v.id === urlVersion) if (version && selectedVersion !== urlVersion) { setSelectedVersion(urlVersion) } } if (urlBook && books.length > 0) { const book = books.find(b => b.id === urlBook) if (book && selectedBook !== urlBook) { setSelectedBook(urlBook) } } if (urlChapter) { const chapter = parseInt(urlChapter) if (chapter && selectedChapter !== chapter) { setSelectedChapter(chapter) } } if (urlVerse && verses.length > 0) { const verseNum = parseInt(urlVerse) if (verseNum) { // Highlight the verse setTimeout(() => { const verseElement = verseRefs.current[verseNum] if (verseElement) { verseElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) setHighlightedVerse(verseNum) } }, 500) } } }, [searchParams, versions, books, verses, selectedVersion, selectedBook, selectedChapter]) // Function to update URL without causing full page reload const updateUrl = useCallback((bookId?: string, chapter?: number, versionId?: string) => { const params = new URLSearchParams() if (versionId || selectedVersion) { params.set('version', versionId || selectedVersion) } if (bookId || selectedBook) { params.set('book', bookId || selectedBook) } if (chapter || selectedChapter) { params.set('chapter', String(chapter || selectedChapter)) } const newUrl = `/${locale}/bible?${params.toString()}` router.replace(newUrl, { scroll: false }) }, [locale, selectedVersion, selectedBook, selectedChapter, router]) // Fetch books when version changes useEffect(() => { if (selectedVersion) { setLoading(true) fetch(`/api/bible/books?locale=${locale}&version=${selectedVersion}`) .then(res => res.json()) .then(data => { setBooks(data.books || []) if (data.books && data.books.length > 0) { setSelectedBook(data.books[0].id) } setLoading(false) }) .catch(err => { console.error('Error fetching books:', err) setLoading(false) }) } }, [locale, selectedVersion]) // Handle URL parameters useEffect(() => { if (books.length > 0 && versions.length > 0) { const bookParam = searchParams.get('book') const chapterParam = searchParams.get('chapter') const verseParam = searchParams.get('verse') const versionParam = searchParams.get('version') // Handle version parameter if (versionParam && versions.find(v => v.id === versionParam)) { setSelectedVersion(versionParam) } if (bookParam) { const book = books.find(b => b.id === bookParam) || books.find(b => b.bookKey === bookParam) if (book) { setSelectedBook(book.id) if (chapterParam) { const chapter = parseInt(chapterParam) if (chapter > 0) { setSelectedChapter(chapter) } } if (verseParam) { const verse = parseInt(verseParam) if (verse > 0) { setHighlightedVerse(verse) setTimeout(() => { scrollToVerse(verse) setTimeout(() => setHighlightedVerse(null), 3000) }, 500) } } } } } }, [books, versions, searchParams]) // Fetch verses when book/chapter changes useEffect(() => { if (selectedBook && selectedChapter) { setLoading(true) // Store scroll position to prevent jumping const scrollPosition = window.pageYOffset fetch(`/api/bible/verses?bookId=${selectedBook}&chapter=${selectedChapter}`) .then(res => res.json()) .then(data => { const newVerses = data.verses || [] // Store previous verses before updating setPreviousVerses(verses) // Use requestAnimationFrame to ensure smooth transition requestAnimationFrame(() => { setVerses(newVerses) // Small delay to allow content to render before removing loading state setTimeout(() => { setLoading(false) setPreviousVerses([]) // Clear previous content after transition // Restore scroll position if we're not navigating to a specific verse const urlVerse = new URLSearchParams(window.location.search).get('verse') if (!urlVerse) { // Maintain scroll position for better UX window.scrollTo({ top: Math.min(scrollPosition, document.body.scrollHeight), behavior: 'auto' }) } }, 50) // Small delay for smoother transition }) }) .catch(err => { console.error('Error fetching verses:', err) setLoading(false) }) } }, [selectedBook, selectedChapter]) // Check chapter bookmark status useEffect(() => { if (selectedBook && selectedChapter && user) { const token = localStorage.getItem('authToken') if (token) { fetch(`/api/bookmarks/chapter/check?bookId=${selectedBook}&chapterNum=${selectedChapter}&locale=${locale}`, { headers: { 'Authorization': `Bearer ${token}` } }) .then(res => res.json()) .then(data => setIsChapterBookmarked(data.isBookmarked || false)) .catch(err => console.error('Error checking bookmark:', err)) } } else { setIsChapterBookmarked(false) } }, [selectedBook, selectedChapter, user, locale]) // Check verse bookmarks useEffect(() => { if (verses.length > 0 && user) { const token = localStorage.getItem('authToken') if (token) { const verseIds = verses.map(verse => verse.id) fetch(`/api/bookmarks/verse/bulk-check?locale=${locale}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ verseIds }) }) .then(res => res.json()) .then(data => setVerseBookmarks(data.bookmarks || {})) .catch(err => console.error('Error checking verse bookmarks:', err)) } } else { setVerseBookmarks({}) } }, [verses, user, locale]) // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Don't trigger shortcuts if user is typing in an input field const activeElement = document.activeElement as HTMLElement if (activeElement && ( activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable || activeElement.closest('[role="dialog"]') // Don't trigger if a dialog/modal is open )) { return } // Check if the floating chat is open const floatingChat = document.querySelector('[data-floating-chat="true"]') if (floatingChat) { return } if (e.ctrlKey || e.metaKey) return switch (e.key) { case 'ArrowLeft': e.preventDefault() handlePreviousChapter() break case 'ArrowRight': e.preventDefault() handleNextChapter() break case 's': e.preventDefault() setSettingsOpen(prev => !prev) break case 'r': e.preventDefault() setPreferences(prev => ({ ...prev, readingMode: !prev.readingMode })) break case 'Escape': e.preventDefault() if (preferences.readingMode) { setPreferences(prev => ({ ...prev, readingMode: false })) } break } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [selectedBook, selectedChapter, books, preferences.readingMode]) const currentBook = books.find(book => book.id === selectedBook) const maxChapters = currentBook?.chapters?.length || 1 const handleShare = async () => { if (!selectedBook || !selectedChapter) return const shareUrl = `${window.location.origin}/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}` const shareText = currentBook ? `${currentBook.name} ${selectedChapter}` : `Chapter ${selectedChapter}` if (navigator.share) { try { await navigator.share({ title: shareText, url: shareUrl }) } catch (error) { // User cancelled sharing or fallback to clipboard try { await navigator.clipboard.writeText(shareUrl) } catch (clipError) { console.error('Failed to copy link:', clipError) } } } else { // Fallback: copy to clipboard try { await navigator.clipboard.writeText(shareUrl) } catch (error) { console.error('Failed to copy link:', error) } } } const scrollToVerse = (verseNum: number) => { const verseElement = verseRefs.current[verseNum] if (verseElement) { verseElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) } } const handlePreviousChapter = () => { if (selectedChapter > 1) { const newChapter = selectedChapter - 1 setSelectedChapter(newChapter) updateUrl(selectedBook, newChapter, selectedVersion) } else { const currentBookIndex = books.findIndex(book => book.id === selectedBook) if (currentBookIndex > 0) { const previousBook = books[currentBookIndex - 1] const lastChapter = previousBook.chapters?.length || 1 setSelectedBook(previousBook.id) setSelectedChapter(lastChapter) updateUrl(previousBook.id, lastChapter, selectedVersion) } } } const handleNextChapter = () => { if (selectedChapter < maxChapters) { const newChapter = selectedChapter + 1 setSelectedChapter(newChapter) updateUrl(selectedBook, newChapter, selectedVersion) } else { const currentBookIndex = books.findIndex(book => book.id === selectedBook) if (currentBookIndex < books.length - 1) { const nextBook = books[currentBookIndex + 1] setSelectedBook(nextBook.id) setSelectedChapter(1) updateUrl(nextBook.id, 1, selectedVersion) } } } const handleChapterBookmark = async () => { if (!selectedBook || !selectedChapter) return // If user is not authenticated, redirect to login if (!user) { router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}`)}`) return } setBookmarkLoading(true) const token = localStorage.getItem('authToken') if (!token) { setBookmarkLoading(false) return } try { if (isChapterBookmarked) { const response = await fetch(`/api/bookmarks/chapter?bookId=${selectedBook}&chapterNum=${selectedChapter}&locale=${locale}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { setIsChapterBookmarked(false) } } else { const response = await fetch(`/api/bookmarks/chapter?locale=${locale}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ bookId: selectedBook, chapterNum: selectedChapter }) }) if (response.ok) { setIsChapterBookmarked(true) } } } catch (error) { console.error('Error toggling bookmark:', error) } finally { setBookmarkLoading(false) } } const handleVerseBookmark = async (verse: BibleVerse) => { // If user is not authenticated, redirect to login if (!user) { router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}&verse=${verse.verseNum}`)}`) return } const token = localStorage.getItem('authToken') if (!token) return try { const isCurrentlyBookmarked = !!verseBookmarks[verse.id] if (isCurrentlyBookmarked) { const response = await fetch(`/api/bookmarks/verse?verseId=${verse.id}&locale=${locale}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { setVerseBookmarks(prev => { const newBookmarks = { ...prev } delete newBookmarks[verse.id] return newBookmarks }) } } else { const response = await fetch(`/api/bookmarks/verse?locale=${locale}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ verseId: verse.id }) }) if (response.ok) { const data = await response.json() setVerseBookmarks(prev => ({ ...prev, [verse.id]: data.bookmark })) } } } catch (error) { console.error('Error toggling verse bookmark:', error) } } const handleCopyVerse = (verse: BibleVerse) => { const text = `${currentBook?.name} ${selectedChapter}:${verse.verseNum} - ${verse.text}` navigator.clipboard.writeText(text).then(() => { setCopyFeedback({ open: true, message: t('copied') }) }) } const handleVerseChat = (verse: BibleVerse) => { // If user is not authenticated, redirect to login if (!user) { router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}&verse=${verse.verseNum}`)}`) return } const versionName = versions.find(v => v.id === selectedVersion)?.name || selectedVersion const bookName = currentBook?.name || 'Unknown Book' const initialMessage = `Explain in depth this verse "${verse.text}" from ${versionName}, ${bookName} ${selectedChapter}:${verse.verseNum} and its meaning` // Dispatch event to open floating chat with the pre-filled message window.dispatchEvent(new CustomEvent('floating-chat:open', { detail: { initialMessage: initialMessage, fullscreen: false } })) } const getThemeStyles = () => { switch (preferences.theme) { case 'dark': return { backgroundColor: '#1a1a1a', color: '#e0e0e0', borderColor: '#333' } case 'sepia': return { backgroundColor: '#f7f3e9', color: '#5c4b3a', borderColor: '#d4c5a0' } default: return { backgroundColor: '#ffffff', color: '#000000', borderColor: '#e0e0e0' } } } const renderVerse = (verse: BibleVerse) => { const isBookmarked = !!verseBookmarks[verse.id] const isHighlighted = highlightedVerse === verse.verseNum return ( { if (el) verseRefs.current[verse.verseNum] = el }} data-verse-container sx={{ mb: 1, display: 'flex', alignItems: 'flex-start', gap: 1, '&:hover .verse-actions': { opacity: 1 } }} > {preferences.showVerseNumbers && ( {verse.verseNum} )} {verse.text} {!preferences.readingMode && ( handleVerseBookmark(verse)} sx={{ color: isBookmarked ? 'warning.main' : 'action.active' }} > {isBookmarked ? : } handleCopyVerse(verse)} sx={{ color: 'action.active' }} > handleVerseChat(verse)} sx={{ color: 'action.active' }} > )} ) } const renderNavigation = () => ( {/* First Row: Navigation Filters */} {/* Version Selection */} v.id === selectedVersion) || null} onChange={(event, newValue) => { if (newValue) { setSelectedVersion(newValue.id) // Reset to first book when version changes if (books.length > 0) { setSelectedBook(books[0].id) setSelectedChapter(1) updateUrl(books[0].id, 1, newValue.id) } } }} options={versions} getOptionLabel={(option) => `${option.name} - ${option.abbreviation}`} renderInput={(params) => ( )} renderOption={(props, option) => ( {option.name} {option.abbreviation} • {option.language.toUpperCase()} )} disabled={versionsLoading} loading={versionsLoading} filterOptions={(options, { inputValue }) => { const filtered = options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()) || option.abbreviation.toLowerCase().includes(inputValue.toLowerCase()) || option.language.toLowerCase().includes(inputValue.toLowerCase()) ) return filtered }} ListboxProps={{ style: { maxHeight: 400 } }} /> setShowAllVersions(prev => !prev)} sx={{ color: showAllVersions ? 'primary.main' : 'inherit' }} > {/* Books Selection */} b.id === selectedBook) || null} onChange={(event, newValue) => { if (newValue) { setSelectedBook(newValue.id) setSelectedChapter(1) updateUrl(newValue.id, 1, selectedVersion) } }} options={books} getOptionLabel={(option) => option.name} renderInput={(params) => ( )} renderOption={(props, option) => ( {option.name} {option.testament} • {option.chapters?.length || 0} chapters )} filterOptions={(options, { inputValue }) => { return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()) || option.testament.toLowerCase().includes(inputValue.toLowerCase()) ) }} ListboxProps={{ style: { maxHeight: 300 } }} /> {/* Chapter Selection */} {t('chapter')} {/* Second Row: Settings and Controls */} {/* Font Size Controls */} setPreferences(prev => ({ ...prev, fontSize: Math.max(12, prev.fontSize - 1) }))} disabled={preferences.fontSize <= 12} > A⁻ setPreferences(prev => ({ ...prev, fontSize: Math.min(28, prev.fontSize + 1) }))} disabled={preferences.fontSize >= 28} > A⁺ {/* Action Buttons */} setPreferences(prev => ({ ...prev, readingMode: !prev.readingMode }))} sx={{ color: preferences.readingMode ? 'primary.main' : 'inherit' }} > {preferences.readingMode ? : } setSettingsOpen(true)}> {(isChapterBookmarked && user) ? : } ) const renderSettings = () => ( setSettingsOpen(false)} maxWidth="sm" fullWidth > {t('readingSettings')} {t('fontSize')} setPreferences(prev => ({ ...prev, fontSize: value as number }))} min={12} max={24} marks valueLabelDisplay="auto" /> {t('lineHeight')} setPreferences(prev => ({ ...prev, lineHeight: value as number }))} min={1.2} max={2.0} step={0.1} marks valueLabelDisplay="auto" /> {t('fontFamily')} {t('theme')} setPreferences(prev => ({ ...prev, showVerseNumbers: e.target.checked }))} /> } label={t('showVerseNumbers')} /> setPreferences(prev => ({ ...prev, readingMode: e.target.checked }))} /> } label={t('readingMode')} /> ) if (loading && books.length === 0) { return ( ) } return ( {/* Top Toolbar - Simplified */} {!preferences.readingMode && ( {currentBook?.name} {selectedChapter} )} {/* Main Content */} {/* Navigation Section - Always show but with different styling in reading mode */} {renderNavigation()} {/* Reading Content */} {loading && ( )} {/* Chapter Header */} {currentBook?.name} {selectedChapter} {(loading && previousVerses.length > 0 ? previousVerses : verses).length} {t('verses')} {/* Verses */} {(loading && previousVerses.length > 0 ? previousVerses : verses).map(renderVerse)} {/* Chapter Navigation */} {/* Floating Action Buttons */} {!preferences.readingMode && ( <> {showScrollTop && ( window.scrollTo({ top: 0, behavior: 'smooth' })} sx={{ position: 'fixed', bottom: 16, right: 16 }} > )} )} {preferences.readingMode && ( setPreferences(prev => ({ ...prev, readingMode: false }))} sx={{ position: 'fixed', top: 16, right: 16 }} > )} {/* Settings Dialog */} {renderSettings()} {/* Copy Feedback */} setCopyFeedback({ open: false, message: '' })} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} > setCopyFeedback({ open: false, message: '' })}> {copyFeedback.message} ) }