diff --git a/components/bible/bible-reader-app.tsx b/components/bible/bible-reader-app.tsx index a2e6f8f..355d0fe 100644 --- a/components/bible/bible-reader-app.tsx +++ b/components/bible/bible-reader-app.tsx @@ -1,13 +1,15 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { useLocale } from 'next-intl' import { Box, Typography, Button } from '@mui/material' -import { BibleChapter, BibleVerse } from '@/types' +import { BibleChapter, BibleVerse, BibleHighlight, HighlightColor } 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' +import { HighlightSyncManager } from '@/lib/highlight-sync-manager' +import { addHighlight, updateHighlight, getHighlightsByVerse, deleteHighlight, getAllHighlights } from '@/lib/highlight-manager' interface BookInfo { id: string // UUID @@ -31,6 +33,8 @@ export function BibleReaderApp() { const [versionId, setVersionId] = useState('') const [error, setError] = useState(null) const [booksLoading, setBooksLoading] = useState(true) + const [highlights, setHighlights] = useState>(new Map()) + const syncManager = useRef(null) // Load books on mount or when locale changes useEffect(() => { @@ -44,6 +48,24 @@ export function BibleReaderApp() { } }, [bookId, chapter, booksLoading, books.length]) + // Initialize sync manager on mount + useEffect(() => { + syncManager.current = new HighlightSyncManager() + syncManager.current.init() + syncManager.current.startAutoSync(30000, () => { + performSync() + }) + + return () => { + syncManager.current?.stopAutoSync() + } + }, []) + + // Load all highlights on mount + useEffect(() => { + loadAllHighlights() + }, []) + async function loadBooks() { setBooksLoading(true) setError(null) @@ -168,6 +190,95 @@ export function BibleReaderApp() { console.log(`Note for verse ${selectedVerse.id}:`, note) } + async function loadAllHighlights() { + try { + const highlightList = await getAllHighlights() + const map = new Map(highlightList.map(h => [h.verseId, h])) + setHighlights(map) + } catch (error) { + console.error('Failed to load highlights:', error) + } + } + + async function handleHighlightVerse(color: HighlightColor = 'yellow') { + if (!selectedVerse) return + + const highlight: BibleHighlight = { + id: `h-${selectedVerse.id}-${Date.now()}`, + verseId: selectedVerse.id, + color, + createdAt: Date.now(), + updatedAt: Date.now(), + syncStatus: 'pending' + } + + try { + await addHighlight(highlight) + const newMap = new Map(highlights) + newMap.set(selectedVerse.id, highlight) + setHighlights(newMap) + } catch (error) { + console.error('Failed to highlight verse:', error) + } + } + + async function handleChangeHighlightColor(color: HighlightColor) { + if (!selectedVerse) return + + const existing = highlights.get(selectedVerse.id) + if (existing) { + const updated = { + ...existing, + color, + updatedAt: Date.now(), + syncStatus: 'pending' as const + } + try { + await updateHighlight(updated) + const newMap = new Map(highlights) + newMap.set(selectedVerse.id, updated) + setHighlights(newMap) + } catch (error) { + console.error('Failed to update highlight color:', error) + } + } + } + + async function handleRemoveHighlight() { + if (!selectedVerse) return + + try { + // Find and delete all highlights for this verse + const existing = highlights.get(selectedVerse.id) + if (existing) { + await deleteHighlight(existing.id) + const newMap = new Map(highlights) + newMap.delete(selectedVerse.id) + setHighlights(newMap) + } + } catch (error) { + console.error('Failed to remove highlight:', error) + } + } + + async function performSync() { + if (!syncManager.current) return + + try { + const pending = await syncManager.current.getPendingSyncItems() + if (pending.length === 0) return + + await syncManager.current.markSyncing(pending.map(h => h.id)) + + // TODO: POST to /api/highlights/bulk in Phase 2.1B + + await syncManager.current.markSynced(pending.map(h => h.id)) + } catch (error) { + console.error('Sync failed:', error) + // Mark items with error status + } + } + return ( {/* Header with search */} @@ -238,6 +349,11 @@ export function BibleReaderApp() { isBookmarked={selectedVerse ? bookmarks.has(selectedVerse.id) : false} onToggleBookmark={handleToggleBookmark} onAddNote={handleAddNote} + isHighlighted={highlights.has(selectedVerse?.id || '')} + currentHighlightColor={highlights.get(selectedVerse?.id || '')?.color} + onHighlightVerse={handleHighlightVerse} + onChangeHighlightColor={handleChangeHighlightColor} + onRemoveHighlight={handleRemoveHighlight} /> {/* Settings panel */} diff --git a/components/bible/verse-details-panel.tsx b/components/bible/verse-details-panel.tsx index 13f74f7..543baaa 100644 --- a/components/bible/verse-details-panel.tsx +++ b/components/bible/verse-details-panel.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { Box, Paper, Typography, Tabs, Tab, IconButton, useMediaQuery, useTheme, TextField, Button } from '@mui/material' import { Close, Bookmark, BookmarkBorder } from '@mui/icons-material' -import { BibleVerse } from '@/types' +import { BibleVerse, HighlightColor } from '@/types' import { HighlightsTab } from './highlights-tab' interface VersDetailsPanelProps { @@ -12,6 +12,11 @@ interface VersDetailsPanelProps { isBookmarked: boolean onToggleBookmark: () => void onAddNote: (note: string) => void + isHighlighted?: boolean + currentHighlightColor?: HighlightColor | null + onHighlightVerse?: (color: HighlightColor) => void + onChangeHighlightColor?: (color: HighlightColor) => void + onRemoveHighlight?: () => void } export function VersDetailsPanel({ @@ -21,6 +26,11 @@ export function VersDetailsPanel({ isBookmarked, onToggleBookmark, onAddNote, + isHighlighted, + currentHighlightColor, + onHighlightVerse, + onChangeHighlightColor, + onRemoveHighlight, }: VersDetailsPanelProps) { const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('sm')) @@ -121,10 +131,16 @@ export function VersDetailsPanel({ {tabValue === 1 && ( {}} // TODO: implement - onColorChange={() => {}} // TODO: implement + isHighlighted={isHighlighted || false} + currentColor={currentHighlightColor || null} + onToggleHighlight={() => { + if (isHighlighted) { + onRemoveHighlight?.() + } else { + onHighlightVerse?.('yellow') + } + }} + onColorChange={(color) => onChangeHighlightColor?.(color)} /> )}