import React, { useCallback, useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { booksApi } from "../api/books"; import type { BookListItem, BookSearchParams } from "../types/book"; import { READING_STATUS_OPTIONS } from "../types/book"; import { useAuth } from "../context/AuthContext"; interface FilterState { genre: string; author: string; reading_status: string; } function useDebounce(value: T, delay: number): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } export function LibraryPage() { const navigate = useNavigate(); const { logout } = useAuth(); const [books, setBooks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [filters, setFilters] = useState({ genre: "", author: "", reading_status: "" }); const [genres, setGenres] = useState([]); const [authors, setAuthors] = useState([]); const [totalCount, setTotalCount] = useState(0); const [showFilters, setShowFilters] = useState(false); const debouncedSearch = useDebounce(searchQuery, 300); const loadedRef = useRef(false); // Load filter options once useEffect(() => { if (loadedRef.current) return; loadedRef.current = true; void Promise.all([booksApi.getGenres(), booksApi.getAuthors()]).then( ([genreList, authorList]) => { setGenres(genreList); setAuthors(authorList); }, () => { // Filters degrade gracefully if discovery endpoints fail }, ); }, []); const loadBooks = useCallback(async (params: BookSearchParams) => { setLoading(true); setError(null); try { const response = await booksApi.searchBooks(params); setBooks(response.results); setTotalCount(response.count); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load books"); setBooks([]); setTotalCount(0); } finally { setLoading(false); } }, []); // Reload when search or filters change useEffect(() => { const params: BookSearchParams = {}; if (debouncedSearch) params.q = debouncedSearch; if (filters.genre) params.genre = filters.genre; if (filters.author) params.author = filters.author; if (filters.reading_status) params.reading_status = filters.reading_status; void loadBooks(params); }, [debouncedSearch, filters, loadBooks]); const handleFilterChange = (key: keyof FilterState, value: string) => { setFilters((prev) => ({ ...prev, [key]: value })); }; const clearAllFilters = () => { setSearchQuery(""); setFilters({ genre: "", author: "", reading_status: "" }); }; const hasActiveFilters = !!searchQuery || !!filters.genre || !!filters.author || !!filters.reading_status; return (
{/* Header */}

Library

{!loading &&

{totalCount} book{totalCount !== 1 ? "s" : ""}

}
{/* Search Bar */}
setSearchQuery(e.target.value)} style={{ width: "100%", padding: "12px 16px 12px 44px", borderRadius: 10, border: "1px solid #e5e7eb", fontSize: 15, background: "#fff", outline: "none", boxSizing: "border-box", }} /> 🔍
{/* Filters Panel */} {showFilters && (
{hasActiveFilters && ( )}
)} {/* Error State */} {error && (

{error}

)} {/* Loading State */} {loading && (
{Array.from({ length: 8 }).map((_, i) => (
))}
)} {/* Empty State */} {!loading && !error && books.length === 0 && (
{hasActiveFilters ? "🔍" : "📚"}

{hasActiveFilters ? "No books found" : "Your library is empty"}

{hasActiveFilters ? "Try adjusting your search query or filters to discover more books." : "Add a book to get started building your collection."}

{hasActiveFilters ? ( ) : ( )}
)} {/* Results Grid */} {!loading && books.length > 0 && (
{books.map((book) => { const statusColors: Record = { want_to_read: { bg: "#dbeafe", text: "#1d4ed8" }, reading: { bg: "#dcfce7", text: "#16a34a" }, finished: { bg: "#f3e8ff", text: "#9333ea" }, dnf: { bg: "#fef3c7", text: "#b45309" }, }; const sc = statusColors[book.reading_status] ?? { bg: "#f3f4f6", text: "#6b7280" }; return (
navigate(`/books/${book.id}`)} style={{ background: "#fff", borderRadius: 12, overflow: "hidden", boxShadow: "0 2px 8px rgba(0,0,0,0.06)", cursor: "pointer", transition: "transform 0.15s, box-shadow 0.15s", }} onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.transform = "translateY(-2px)"; (e.currentTarget as HTMLElement).style.boxShadow = "0 4px 16px rgba(0,0,0,0.1)"; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.transform = ""; (e.currentTarget as HTMLElement).style.boxShadow = "0 2px 8px rgba(0,0,0,0.06)"; }} >
{book.cover_image ? {book.title} : 📖} {book.reading_status_display}

{book.title}

{book.author || "Unknown Author"}

{book.genre && ( {book.genre} )}
); })}
)}
); }