import { useState, useEffect, useRef } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Layout from '../components/Layout'; import CardItem from '../components/CardItem'; import BulkSelectionToolbar from '../components/BulkSelectionToolbar'; import CollectionSelectionModal from '../components/CollectionSelectionModal'; import { ManaCost, ColorFilterSymbol } from '../components/ManaSymbols'; import ManaSymbolSettings from '../components/ManaSymbolSettings'; import { useAuth } from '../lib/use-auth'; import { VOCAB } from '../lib/collection-vocabulary.js'; export default function MyCards() { const router = useRouter(); const { user, loading: authLoading } = useAuth(); const [cards, setCards] = useState([]); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedTCG, setSelectedTCG] = useState('all'); const [selectedRarity, setSelectedRarity] = useState('all'); const [selectedSet, setSelectedSet] = useState('all'); const [selectedValueRange, setSelectedValueRange] = useState('all'); const [viewMode, setViewMode] = useState('grid'); // grid or list const [pagination, setPagination] = useState({ page: 1, limit: 50, total: 0, pages: 0 }); const [filters, setFilters] = useState({ games: [], rarities: [], sets: [] }); const [hasMore, setHasMore] = useState(true); const loadingMoreRef = useRef(false); const hasMoreRef = useRef(true); // Bulk selection state const [selectedCards, setSelectedCards] = useState([]); const [favoritedCards, setFavoritedCards] = useState(new Set()); // Modal states const [showCollectionModal, setShowCollectionModal] = useState(false); const [cardsToAdd, setCardsToAdd] = useState([]); // Mana symbol settings const [manaSymbolSettings, setManaSymbolSettings] = useState({ useSVG: false }); // Redirect to login if not authenticated useEffect(() => { if (!authLoading && !user) { router.push('/login'); } }, [authLoading, user, router]); // Fetch owned cards from database const fetchCards = async (isLoadMore = false) => { try { if (isLoadMore) { setLoadingMore(true); loadingMoreRef.current = true; } else { setLoading(true); setPagination(prev => ({ ...prev, page: 1 })); } const currentPage = isLoadMore ? pagination.page + 1 : 1; // Build query parameters for owned cards only const params = new URLSearchParams({ page: currentPage.toString(), limit: pagination.limit.toString(), ...(searchQuery && { search: searchQuery }), ...(selectedTCG !== 'all' && { tcg: selectedTCG }), ...(selectedRarity !== 'all' && { rarity: selectedRarity }), ...(selectedSet !== 'all' && { set: selectedSet }), ...(selectedValueRange !== 'all' && { valueRange: selectedValueRange }) }); const token = localStorage.getItem('auth_token'); const headers = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const response = await fetch(`/api/cards/owned?${params}`, { headers }); if (response.ok) { const data = await response.json(); if (isLoadMore) { setCards(prev => [...prev, ...data.cards]); setPagination(prev => ({ ...prev, page: currentPage })); } else { setCards(data.cards); setPagination(data.pagination); } // Update hasMore state const newHasMore = currentPage < data.pagination.pages; setHasMore(newHasMore); hasMoreRef.current = newHasMore; // Load filter options on first load if (!isLoadMore && data.filters) { setFilters(data.filters); } } else { console.error('Failed to fetch owned cards'); } } catch (error) { console.error('Error fetching owned cards:', error); } finally { setLoading(false); setLoadingMore(false); loadingMoreRef.current = false; } }; // Load favorited cards const loadFavoritedCards = async () => { try { const token = localStorage.getItem('auth_token'); const headers = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const response = await fetch('/api/favorites?type=card', { headers }); if (response.ok) { const data = await response.json(); // Handle both array and object responses const favorites = Array.isArray(data) ? data : (data.favorites || []); setFavoritedCards(new Set(favorites.map(fav => fav.item_id))); } } catch (error) { console.error('Error loading favorited cards:', error); // Set empty set on error to prevent crashes setFavoritedCards(new Set()); } }; // Initial load useEffect(() => { if (user) { fetchCards(); loadFavoritedCards(); } }, [user, searchQuery, selectedTCG, selectedRarity, selectedSet, selectedValueRange]); // Infinite scroll useEffect(() => { const observer = new IntersectionObserver( (entries) => { const target = entries[0]; if (target.isIntersecting && hasMoreRef.current && !loadingMoreRef.current) { fetchCards(true); } }, { threshold: 0.1 } ); const sentinel = document.getElementById('scroll-sentinel'); if (sentinel) { observer.observe(sentinel); } return () => { if (sentinel) { observer.unobserve(sentinel); } }; }, [cards]); // Handle favorite toggle const handleFavoriteToggle = async (cardId) => { try { const token = localStorage.getItem('auth_token'); const headers = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const isFavorited = favoritedCards.has(cardId); if (isFavorited) { // Remove from favorites const response = await fetch('/api/favorites', { method: 'DELETE', headers, body: JSON.stringify({ itemType: 'card', itemId: cardId }) }); if (response.ok) { setFavoritedCards(prev => { const newSet = new Set(prev); newSet.delete(cardId); return newSet; }); } } else { // Add to favorites const response = await fetch('/api/favorites', { method: 'POST', headers, body: JSON.stringify({ itemType: 'card', itemId: cardId }) }); if (response.ok) { setFavoritedCards(prev => { const newSet = new Set(prev); newSet.add(cardId); return newSet; }); } } } catch (error) { console.error('Error toggling favorite:', error); } }; // Handle bulk actions const handleAddToCollections = async (selectedCollectionIds) => { // Implementation for adding cards to collections console.log('Adding cards to collections:', selectedCollectionIds); setShowCollectionModal(false); setSelectedCards([]); }; if (loading && cards.length === 0) { return (
); } return ( {/* Header */}

{VOCAB.MY_COLLECTION}

Browse and manage cards in {VOCAB.MY_COLLECTION}

{/* Filters */}
setSearchQuery(e.target.value)} className="flex-1 min-w-[200px] px-4 py-2 rounded-xl border" style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)', color: 'var(--text-primary)' }} />
{/* Results Summary */}

{pagination.total} cards in {VOCAB.MY_COLLECTION}

{selectedCards.length > 0 && (

{selectedCards.length} cards selected

)}
{/* Bulk Selection Toolbar */} {selectedCards.length > 0 && ( { setCardsToAdd(selectedCards); setShowCollectionModal(true); }} onClearSelection={() => setSelectedCards([])} /> )} {/* Cards Grid */}
{cards.map((card) => ( { setSelectedCards(prev => prev.includes(cardId) ? prev.filter(id => id !== cardId) : [...prev, cardId] ); }} onFavorite={(cardId) => { handleFavoriteToggle(cardId); }} /> ))}
{/* Loading More */} {loadingMore && (
)} {/* Infinite Scroll Sentinel */} {hasMore &&
} {/* No More Cards */} {!hasMore && cards.length > 0 && (

You've reached the end of {VOCAB.MY_COLLECTION}!

)} {/* No Cards Found */} {cards.length === 0 && !loading && (

No cards in {VOCAB.MY_COLLECTION}

{searchQuery ? 'Try adjusting your search filters' : `Start adding cards to ${VOCAB.MY_COLLECTION} by browsing available cards`}

)}
{/* Collection Selection Modal */} setShowCollectionModal(false)} cards={cardsToAdd} onAddToCollections={handleAddToCollections} />
); }