diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5fdf2b8b..4be0dc5d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,6 +4,7 @@ import { Book, ChevronRight, Moon, Sun } from 'lucide-react'; import BookSelector from './components/BookSelector'; import ChapterSelector from './components/ChapterSelector'; import BibleReader from './components/BibleReader'; +import FavoritesMenu from './components/FavoritesMenu'; import { getBooks } from './services/api'; interface BookData { @@ -223,7 +224,7 @@ function App() { // Helper function to convert display name back to file name const getBookFileName = (displayName: string): string => { // Find the book that matches the display name - const book = books.find(b => formatBookName(b) === displayName); + const book = books.find((b: string) => formatBookName(b) === displayName); return book || displayName; }; @@ -352,8 +353,15 @@ function App() { )} - {/* User Authentication & Dark Mode */} + {/* User Authentication, Favorites & Dark Mode */}
+ {/* Favorites Menu - Only for authenticated users */} + + {/* Authentication Button */} {authAvailable && (
diff --git a/frontend/src/components/FavoritesMenu.tsx b/frontend/src/components/FavoritesMenu.tsx new file mode 100644 index 00000000..dbfb2f18 --- /dev/null +++ b/frontend/src/components/FavoritesMenu.tsx @@ -0,0 +1,180 @@ +import React, { useState, useEffect } from 'react'; +import { Star, ChevronDown, ChevronUp, X } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; + +interface Favorite { + id: number; + book: string; + chapter?: string; + verse_start?: number; + verse_end?: number; + note?: string; + created_at: string; +} + +interface FavoritesMenuProps { + user: any; + formatBookName: (bookName: string) => string; + getBookUrlName: (bookName: string) => string; +} + +const FavoritesMenu: React.FC = ({ user, formatBookName, getBookUrlName }) => { + const [isOpen, setIsOpen] = useState(false); + const [favorites, setFavorites] = useState([]); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + // 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(); + setFavorites(data.favorites || []); + } + } catch (error) { + console.error('Failed to load favorites:', error); + } finally { + setLoading(false); + } + }; + + const removeFavorite = async (favoriteId: number) => { + try { + const response = await fetch(`/api/favorites/${favoriteId}`, { + method: 'DELETE', + credentials: 'include' + }); + + if (response.ok) { + setFavorites(favorites.filter(f => f.id !== favoriteId)); + } + } catch (error) { + console.error('Failed to remove favorite:', error); + } + }; + + const navigateToFavorite = (favorite: Favorite) => { + const urlBookName = getBookUrlName(favorite.book); + + if (favorite.chapter) { + // Navigate to chapter + navigate(`/book/${urlBookName}/chapter/${favorite.chapter}`); + } else { + // Navigate to book + navigate(`/book/${urlBookName}`); + } + setIsOpen(false); + }; + + const getFavoriteDisplayText = (favorite: Favorite) => { + const bookName = formatBookName(favorite.book); + + if (favorite.verse_start && favorite.verse_end) { + return `${bookName} ${favorite.chapter}:${favorite.verse_start}-${favorite.verse_end}`; + } else if (favorite.verse_start) { + return `${bookName} ${favorite.chapter}:${favorite.verse_start}`; + } else if (favorite.chapter) { + return `${bookName} Chapter ${favorite.chapter}`; + } else { + return bookName; + } + }; + + if (!user) { + return null; // Don't show favorites menu for non-authenticated users + } + + return ( +
+ {/* Favorites Toggle Button */} + + + {/* Favorites Dropdown */} + {isOpen && ( +
+
+
+

My Favorites

+ +
+
+ +
+ {loading ? ( +
Loading favorites...
+ ) : favorites.length === 0 ? ( +
+ +

No favorites yet

+

Click the ★ next to books, chapters, or verses to add them here

+
+ ) : ( +
+ {favorites.map((favorite) => ( +
+ + +
+ ))} +
+ )} +
+
+ )} +
+ ); +}; + +export default FavoritesMenu;