diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4be0dc5d..33145418 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,72 +11,6 @@ interface BookData { books: string[]; } -// Component for the home page -function HomePage({ books, formatBookName, getBookUrlName }: { books: string[], formatBookName: (name: string) => string, getBookUrlName: (name: string) => string }) { - const navigate = useNavigate(); - - const handleBookSelect = (book: string) => { - const urlName = getBookUrlName(book); - navigate(`/book/${urlName}`); - }; - - return ; -} - -// Component for book chapters page -function BookPage({ books, formatBookName, getBookFromUrl }: { books: string[], formatBookName: (name: string) => string, getBookFromUrl: (urlName: string) => string }) { - const { bookName } = useParams<{ bookName: string }>(); - const navigate = useNavigate(); - - const actualBookName = bookName ? getBookFromUrl(bookName) : ''; - - const handleChapterSelect = (chapter: string) => { - navigate(`/book/${bookName}/chapter/${chapter}`); - }; - - const handleBack = () => { - navigate('/'); - }; - - if (!bookName || !actualBookName || !books.includes(actualBookName)) { - return
Book not found
; - } - - return ( - - ); -} - -// Component for chapter reading page -function ChapterPage({ formatBookName, getBookFromUrl }: { formatBookName: (name: string) => string, getBookFromUrl: (urlName: string) => string }) { - const { bookName, chapterNumber } = useParams<{ bookName: string, chapterNumber: string }>(); - const navigate = useNavigate(); - - const actualBookName = bookName ? getBookFromUrl(bookName) : ''; - - const handleBack = () => { - navigate(`/book/${bookName}`); - }; - - if (!bookName || !chapterNumber || !actualBookName) { - return
Chapter not found
; - } - - return ( - - ); -} - function App() { const [books, setBooks] = useState([]); const [loading, setLoading] = useState(true); @@ -274,6 +208,75 @@ function App() { const { currentBook, currentChapter } = getCurrentNavInfo(); + // Component for the home page + const HomePage = () => { + const handleBookSelect = (book: string) => { + const urlName = getBookUrlName(book); + navigate(`/book/${urlName}`); + }; + + return ( + + ); + }; + + // Component for book chapters page + const BookPage = () => { + const { bookName } = useParams<{ bookName: string }>(); + const actualBookName = bookName ? getBookFromUrl(bookName) : ''; + + const handleChapterSelect = (chapter: string) => { + navigate(`/book/${bookName}/chapter/${chapter}`); + }; + + const handleBack = () => { + navigate('/'); + }; + + if (!bookName || !actualBookName || !books.includes(actualBookName)) { + return
Book not found
; + } + + return ( + + ); + }; + + // Component for chapter reading page + const ChapterPage = () => { + const { bookName, chapterNumber } = useParams<{ bookName: string, chapterNumber: string }>(); + const actualBookName = bookName ? getBookFromUrl(bookName) : ''; + + const handleBack = () => { + navigate(`/book/${bookName}`); + }; + + if (!bookName || !chapterNumber || !actualBookName) { + return
Chapter not found
; + } + + return ( + + ); + }; + if (loading) { return (
@@ -407,9 +410,9 @@ function App() { {/* Main Content */}
- } /> - } /> - } /> + } /> + } /> + } />
diff --git a/frontend/src/components/BookSelector.tsx b/frontend/src/components/BookSelector.tsx index 9552fc6f..1edceef8 100644 --- a/frontend/src/components/BookSelector.tsx +++ b/frontend/src/components/BookSelector.tsx @@ -1,13 +1,102 @@ -import React from 'react'; -import { BookOpen } from 'lucide-react'; +import React, { useState, useEffect } from 'react'; +import { BookOpen, Star } from 'lucide-react'; interface BookSelectorProps { books: string[]; onBookSelect: (book: string) => void; formatBookName: (bookName: string) => string; + user?: any; } -const BookSelector: React.FC = ({ books, onBookSelect, formatBookName }) => { +const BookSelector: React.FC = ({ books, onBookSelect, formatBookName, user }) => { + const [favorites, setFavorites] = useState>(new Set()); + const [loading, setLoading] = useState(false); + + // Load favorites when user is available + useEffect(() => { + if (user) { + loadFavorites(); + } + }, [user]); + + const loadFavorites = async () => { + if (!user) return; + + setLoading(true); + try { + const response = await fetch('/api/favorites', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + const bookFavorites = new Set( + data.favorites + .filter((fav: any) => !fav.chapter) // Only book-level favorites + .map((fav: any) => fav.book) + ); + setFavorites(bookFavorites); + } + } catch (error) { + console.error('Failed to load favorites:', error); + } finally { + setLoading(false); + } + }; + + const toggleFavorite = async (book: string, event: React.MouseEvent) => { + event.stopPropagation(); // Prevent book selection when clicking star + + if (!user) return; + + const isFavorited = favorites.has(book); + + try { + if (isFavorited) { + // Remove favorite + const response = await fetch('/api/favorites', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + const bookFavorite = data.favorites.find((fav: any) => fav.book === book && !fav.chapter); + + if (bookFavorite) { + await fetch(`/api/favorites/${bookFavorite.id}`, { + method: 'DELETE', + credentials: 'include' + }); + + setFavorites(prev => { + const newFavorites = new Set(prev); + newFavorites.delete(book); + 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 + }) + }); + + if (response.ok) { + setFavorites(prev => new Set(prev).add(book)); + } + } + } catch (error) { + console.error('Failed to toggle favorite:', error); + } + }; + // Group books by testament const oldTestament = books.slice(0, 39); // First 39 books const newTestament = books.slice(39); // Remaining books @@ -19,16 +108,34 @@ const BookSelector: React.FC = ({ books, onBookSelect, format
{books.map((book) => ( - +
+ + + {/* Star button - only show for authenticated users */} + {user && ( + + )} +
))}
@@ -43,6 +150,11 @@ const BookSelector: React.FC = ({ books, onBookSelect, format

Select a book to begin reading

+ {user && ( +

+ Click the ★ to add books to your favorites +

+ )}