From aca8f9bd644f410960d58d6d1d4f72a3afe1d538 Mon Sep 17 00:00:00 2001 From: Ryderjj89 Date: Sat, 13 Sep 2025 18:18:50 -0400 Subject: [PATCH] Add star buttons to ChapterSelector and BibleReader for complete favorites system --- frontend/src/components/BibleReader.tsx | 121 +++++++++++++++++- frontend/src/components/ChapterSelector.tsx | 134 ++++++++++++++++++-- 2 files changed, 238 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/BibleReader.tsx b/frontend/src/components/BibleReader.tsx index a88439e0..4cb096fa 100644 --- a/frontend/src/components/BibleReader.tsx +++ b/frontend/src/components/BibleReader.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { ArrowLeft, BookOpen, ChevronLeft, ChevronRight } from 'lucide-react'; +import { ArrowLeft, BookOpen, ChevronLeft, ChevronRight, Star } from 'lucide-react'; import { getChapter, getBook } from '../services/api'; interface BibleReaderProps { @@ -7,12 +7,14 @@ interface BibleReaderProps { chapter: string; onBack: () => void; formatBookName: (bookName: string) => string; + user?: any; } -const BibleReader: React.FC = ({ book, chapter, onBack, formatBookName }) => { +const BibleReader: React.FC = ({ book, chapter, onBack, formatBookName, user }) => { const [content, setContent] = useState(''); const [loading, setLoading] = useState(true); const [chapters, setChapters] = useState([]); + const [favorites, setFavorites] = useState>(new Set()); const [fontSize, setFontSize] = useState<'small' | 'medium' | 'large'>(() => { // Load font size preference from localStorage const saved = localStorage.getItem('fontSize'); @@ -24,6 +26,92 @@ const BibleReader: React.FC = ({ book, chapter, onBack, format loadChapters(); }, [book, chapter]); + // Load favorites when user is available + useEffect(() => { + if (user) { + loadFavorites(); + } + }, [user, book, chapter]); + + const loadFavorites = async () => { + if (!user) return; + + try { + const response = await fetch('/api/favorites', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + const verseFavorites = new Set( + data.favorites + .filter((fav: any) => fav.book === book && fav.chapter === chapter && fav.verse_start) // Only verse-level favorites for this chapter + .map((fav: any) => fav.verse_end ? `${fav.verse_start}-${fav.verse_end}` : fav.verse_start.toString()) + ); + setFavorites(verseFavorites); + } + } catch (error) { + console.error('Failed to load favorites:', error); + } + }; + + const toggleFavorite = async (verseNumber: string) => { + if (!user) return; + + const isFavorited = favorites.has(verseNumber); + + try { + if (isFavorited) { + // Remove favorite + const response = await fetch('/api/favorites', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + const verseFavorite = data.favorites.find((fav: any) => + fav.book === book && + fav.chapter === chapter && + fav.verse_start === parseInt(verseNumber) + ); + + if (verseFavorite) { + await fetch(`/api/favorites/${verseFavorite.id}`, { + method: 'DELETE', + credentials: 'include' + }); + + setFavorites(prev => { + const newFavorites = new Set(prev); + newFavorites.delete(verseNumber); + return newFavorites; + }); + } + } + } else { + // Add favorite + const response = await fetch('/api/favorites', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + book: book, + chapter: chapter, + verse_start: parseInt(verseNumber) + }) + }); + + if (response.ok) { + setFavorites(prev => new Set(prev).add(verseNumber)); + } + } + } catch (error) { + console.error('Failed to toggle favorite:', error); + } + }; + const loadChapters = async () => { try { const response = await getBook(book); @@ -123,9 +211,27 @@ const BibleReader: React.FC = ({ book, chapter, onBack, format const verseText = verseMatch[2]; verses.push( -
- {verseNumber} - {verseText} +
+ {/* Star button - only show for authenticated users */} + {user && ( + + )} +
+ {verseNumber} + {verseText} +
); } else if (line.startsWith('#')) { @@ -213,6 +319,11 @@ const BibleReader: React.FC = ({ book, chapter, onBack, format

{formatBookName(book)} {chapter}

+ {user && ( +

+ Hover over verses to see ★ and add them to your favorites +

+ )}
{/* Bible Content */} diff --git a/frontend/src/components/ChapterSelector.tsx b/frontend/src/components/ChapterSelector.tsx index ffbd646a..f48ef70c 100644 --- a/frontend/src/components/ChapterSelector.tsx +++ b/frontend/src/components/ChapterSelector.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { ArrowLeft, FileText } from 'lucide-react'; +import { ArrowLeft, FileText, Star } from 'lucide-react'; import { getBook } from '../services/api'; interface ChapterSelectorProps { @@ -7,16 +7,103 @@ interface ChapterSelectorProps { onChapterSelect: (chapter: string) => void; onBack: () => void; formatBookName: (bookName: string) => string; + user?: any; } -const ChapterSelector: React.FC = ({ book, onChapterSelect, onBack, formatBookName }) => { +const ChapterSelector: React.FC = ({ book, onChapterSelect, onBack, formatBookName, user }) => { const [chapters, setChapters] = useState([]); const [loading, setLoading] = useState(true); + const [favorites, setFavorites] = useState>(new Set()); useEffect(() => { loadChapters(); }, [book]); + // Load favorites when user is available + useEffect(() => { + if (user) { + loadFavorites(); + } + }, [user, book]); + + const loadFavorites = async () => { + if (!user) return; + + try { + const response = await fetch('/api/favorites', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + const chapterFavorites = new Set( + data.favorites + .filter((fav: any) => fav.book === book && fav.chapter && !fav.verse_start) // Only chapter-level favorites for this book + .map((fav: any) => fav.chapter) + ); + setFavorites(chapterFavorites); + } + } catch (error) { + console.error('Failed to load favorites:', error); + } + }; + + const toggleFavorite = async (chapter: string, event: React.MouseEvent) => { + event.stopPropagation(); // Prevent chapter selection when clicking star + + if (!user) return; + + const isFavorited = favorites.has(chapter); + + try { + if (isFavorited) { + // Remove favorite + const response = await fetch('/api/favorites', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + const chapterFavorite = data.favorites.find((fav: any) => + fav.book === book && fav.chapter === chapter && !fav.verse_start + ); + + if (chapterFavorite) { + await fetch(`/api/favorites/${chapterFavorite.id}`, { + method: 'DELETE', + credentials: 'include' + }); + + setFavorites(prev => { + const newFavorites = new Set(prev); + newFavorites.delete(chapter); + return newFavorites; + }); + } + } + } else { + // Add favorite + const response = await fetch('/api/favorites', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + book: book, + chapter: chapter + }) + }); + + if (response.ok) { + setFavorites(prev => new Set(prev).add(chapter)); + } + } + } catch (error) { + console.error('Failed to toggle favorite:', error); + } + }; + const loadChapters = async () => { try { setLoading(true); @@ -70,21 +157,44 @@ const ChapterSelector: React.FC = ({ book, onChapterSelect

Select a chapter to read

+ {user && ( +

+ Click the ★ to add chapters to your favorites +

+ )} {/* Chapters Grid */}
{chapters.map((chapter) => ( - +
+ + + {/* Star button - only show for authenticated users */} + {user && ( + + )} +
))}