Favorites improvements
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
import { ArrowLeft, FileText, Star, ChevronRight, Search } from 'lucide-react';
|
||||
import { getBook } from '../services/api';
|
||||
import { Section } from '../types/favorites';
|
||||
import SectionPicker from './SectionPicker';
|
||||
|
||||
interface ChapterSelectorProps {
|
||||
book: string;
|
||||
@@ -11,7 +13,9 @@ interface ChapterSelectorProps {
|
||||
onFavoriteChange?: () => void;
|
||||
version?: string;
|
||||
onSearchClick?: () => void;
|
||||
favorites?: Set<string>; // Favorites passed from parent (centralized state)
|
||||
favorites?: Set<string>;
|
||||
sections?: Section[];
|
||||
onSectionChange?: () => void;
|
||||
}
|
||||
|
||||
const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
@@ -23,35 +27,39 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
onFavoriteChange,
|
||||
version = 'esv',
|
||||
onSearchClick,
|
||||
favorites = new Set() // Default to empty set if not provided
|
||||
favorites = new Set(),
|
||||
sections = [],
|
||||
onSectionChange
|
||||
}) => {
|
||||
const [chapters, setChapters] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showSectionPicker, setShowSectionPicker] = useState(false);
|
||||
const [pendingChapter, setPendingChapter] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadChapters();
|
||||
}, [book]);
|
||||
|
||||
const toggleFavorite = async (chapter: string, event: React.MouseEvent) => {
|
||||
event.stopPropagation(); // Prevent chapter selection when clicking star
|
||||
|
||||
const toggleFavorite = async (chapter: string, event: React.MouseEvent, sectionId?: number | null) => {
|
||||
event.stopPropagation();
|
||||
|
||||
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) =>
|
||||
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',
|
||||
@@ -59,27 +67,21 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
});
|
||||
|
||||
console.log('Removed chapter favorite:', chapter);
|
||||
onFavoriteChange?.(); // Notify parent to refresh favorites
|
||||
onFavoriteChange?.();
|
||||
}
|
||||
}
|
||||
} 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,
|
||||
version: version
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('Added chapter favorite:', chapter);
|
||||
onFavoriteChange?.(); // Notify parent to refresh favorites
|
||||
// Add favorite - check if we need section picker
|
||||
if (sections.length > 0 && sectionId === undefined) {
|
||||
const defaultSection = sections.find(s => s.is_default);
|
||||
if (defaultSection) {
|
||||
await addFavoriteWithSection(chapter, defaultSection.id);
|
||||
} else {
|
||||
setPendingChapter(chapter);
|
||||
setShowSectionPicker(true);
|
||||
}
|
||||
} else {
|
||||
await addFavoriteWithSection(chapter, sectionId ?? null);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -87,14 +89,45 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
}
|
||||
};
|
||||
|
||||
const addFavoriteWithSection = async (chapter: string, sectionId: number | null) => {
|
||||
try {
|
||||
const response = await fetch('/api/favorites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
book: book,
|
||||
chapter: chapter,
|
||||
version: version,
|
||||
section_id: sectionId
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log('Added chapter favorite:', chapter, 'to section:', sectionId);
|
||||
onFavoriteChange?.();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add favorite:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSectionSelect = async (sectionId: number | null) => {
|
||||
if (pendingChapter) {
|
||||
await addFavoriteWithSection(pendingChapter, sectionId);
|
||||
setPendingChapter(null);
|
||||
}
|
||||
setShowSectionPicker(false);
|
||||
};
|
||||
|
||||
const loadChapters = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getBook(book, version);
|
||||
|
||||
// The API now returns { chapters: ["1", "2", "3", ...] }
|
||||
if (response.chapters) {
|
||||
// Sort chapters numerically to ensure proper order
|
||||
const sortedChapters = response.chapters.sort((a, b) => parseInt(a, 10) - parseInt(b, 10));
|
||||
setChapters(sortedChapters);
|
||||
} else {
|
||||
@@ -103,7 +136,6 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load chapters:', error);
|
||||
// Don't show fallback chapters - just show an empty list
|
||||
setChapters([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -160,7 +192,7 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
</p>
|
||||
{user && (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
Click the ★ to add chapters to your favorites
|
||||
Click the star to add chapters to your favorites
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -178,18 +210,17 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
{chapter}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Star button - only show for authenticated users */}
|
||||
|
||||
{user && (
|
||||
<button
|
||||
onClick={(e) => toggleFavorite(chapter, e)}
|
||||
className="absolute top-1 right-1 p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
title={favorites.has(chapter) ? 'Remove from favorites' : 'Add to favorites'}
|
||||
>
|
||||
<Star
|
||||
<Star
|
||||
className={`h-3 w-3 ${
|
||||
favorites.has(chapter)
|
||||
? 'text-yellow-500 fill-yellow-500'
|
||||
favorites.has(chapter)
|
||||
? 'text-yellow-500 fill-yellow-500'
|
||||
: 'text-gray-400 hover:text-yellow-500'
|
||||
} transition-colors`}
|
||||
/>
|
||||
@@ -205,6 +236,19 @@ const ChapterSelector: React.FC<ChapterSelectorProps> = memo(({
|
||||
{chapters.length} chapters available
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Section Picker Modal */}
|
||||
{showSectionPicker && (
|
||||
<SectionPicker
|
||||
sections={sections}
|
||||
onSelect={handleSectionSelect}
|
||||
onCancel={() => {
|
||||
setShowSectionPicker(false);
|
||||
setPendingChapter(null);
|
||||
}}
|
||||
onSectionCreated={onSectionChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user