import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import { VOCAB, collectionDisplayName } from './collection-vocabulary.js'; import { downloadCollectionCardsCsv } from './collection-cards-csv.js'; /** * Card/collection page state and handlers (god-component split). */ export function useCollectionView({ user = null, authLoading = true } = {}) { const router = useRouter(); const { identifier } = router.query; const [collection, setCollection] = useState(null); const [cards, setCards] = useState([]); const [loading, setLoading] = useState(true); const [isFavorited, setIsFavorited] = useState(false); const [showShareModal, setShowShareModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [copySuccess, setCopySuccess] = useState(false); const [selectedTCG, setSelectedTCG] = useState('MTG'); // Edit form state const [editForm, setEditForm] = useState({ name: '', description: '', isPublic: false, image: '', tags: [] }); // Filter states const [searchQuery, setSearchQuery] = useState(''); const [selectedRarity, setSelectedRarity] = useState('All Rarities'); const [selectedType, setSelectedType] = useState('All Types'); const [groupBy, setGroupBy] = useState('Group by Game'); const [sortBy, setSortBy] = useState('Sort by Name'); const [viewMode, setViewMode] = useState('grid'); const [searchCards, setSearchCards] = useState(''); const [searchResults, setSearchResults] = useState([]); const [showSearchResults, setShowSearchResults] = useState(false); const [showUploadModal, setShowUploadModal] = useState(false); // Card interaction states const [selectedCards, setSelectedCards] = useState([]); const [favoritedCards, setFavoritedCards] = useState(new Set()); const loadFavoritedCards = async () => { try { const token = localStorage.getItem('auth_token'); if (!token) return; const response = await fetch('/api/favorites?type=card', { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); const favoriteIds = new Set(data.favorites.map(fav => parseInt(fav.item_id))); setFavoritedCards(favoriteIds); } } catch (error) { console.error('Error loading favorited cards:', error); } }; const checkIfFavorited = async () => { try { const response = await fetch(`/api/favorites?type=collection`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` } }); if (response.ok) { const data = await response.json(); const isFav = data.favorites.some(fav => fav.item_id === collection?.id); setIsFavorited(isFav); } else { console.error('Failed to check favorites:', response.status); } } catch (error) { console.error('Error checking favorites:', error); } }; const fetchCollectionData = async () => { try { // First try to fetch without authentication (for public collections) let response = await fetch(`/api/collections/${identifier}`); // If that fails and we have a user, try with authentication if (!response.ok && user) { response = await fetch(`/api/collections/${identifier}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` } }); } if (response.ok) { const data = await response.json(); // Check if we accessed via numeric ID and need to redirect to slug if (data.slug && identifier !== data.slug && !isNaN(parseInt(identifier))) { // Redirect to slug URL router.replace(`/collection/${data.slug}`, undefined, { shallow: false }); return; } setCollection(data); setEditForm({ name: data.name || '', description: data.description || '', isPublic: data.isPublic || false, image: data.image || '', tags: Array.isArray(data.tags) ? data.tags : (data.tags ? data.tags.split(',') : []) }); // Fetch collection cards (try public first, then authenticated) let cardsResponse = await fetch(`/api/collections/${identifier}/cards`); if (!cardsResponse.ok && user) { cardsResponse = await fetch(`/api/collections/${identifier}/cards`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` } }); } if (cardsResponse.ok) { const cardsData = await cardsResponse.json(); setCards(cardsData.cards || []); } // Check if collection is favorited checkIfFavorited(); } else if (response.status === 401 || response.status === 403) { // Collection is private and user is not authenticated/authorized if (!user) { router.push('/login'); } else { // User is authenticated but doesn't have access router.push('/collections'); } } } catch (error) { console.error('Error fetching collection:', error); } finally { setLoading(false); } }; useEffect(() => { if (identifier) { // eslint-disable-next-line react-hooks/set-state-in-effect -- load list when slug or auth changes fetchCollectionData(); if (user) { loadFavoritedCards(); } } // eslint-disable-next-line react-hooks/exhaustive-deps -- reload when list slug or auth changes }, [identifier, user]); const handleEditCollection = async () => { try { const response = await fetch(`/api/collections/${identifier}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ name: editForm.name, description: editForm.description, isPublic: editForm.isPublic, image: editForm.image, tags: editForm.tags }) }); if (response.ok) { const updatedCollection = await response.json(); // If the name changed and we got a new slug, redirect if (updatedCollection.slug && updatedCollection.slug !== identifier) { router.push(`/collection/${updatedCollection.slug}`); } else { // Just refresh the data fetchCollectionData(); } setShowEditModal(false); } else { const error = await response.json(); alert(error.error || 'Failed to update list'); } } catch (error) { console.error('Error updating collection:', error); alert('Network error. Please try again.'); } }; const handleDeleteCollection = async () => { try { const response = await fetch(`/api/collections/${identifier}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` } }); if (response.ok) { router.push('/collections'); } else { const error = await response.json(); alert(error.error || 'Failed to delete list'); } } catch (error) { console.error('Error deleting collection:', error); alert('Network error. Please try again.'); } }; const handleSearchCards = async (query) => { if (query.length < 2) { setSearchResults([]); setShowSearchResults(false); return; } try { const response = await fetch(`/api/cards/search?q=${encodeURIComponent(query)}&limit=10`); if (response.ok) { const data = await response.json(); setSearchResults(data.cards || []); setShowSearchResults(true); } } catch (error) { console.error('Error searching cards:', error); } }; const handleAddCard = async (card) => { try { const response = await fetch(`/api/collections/${identifier}/cards`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ cardId: card.id, quantity: 1 }) }); if (response.ok) { setSearchCards(''); setShowSearchResults(false); fetchCollectionData(); // Refresh the collection data } } catch (error) { console.error('Error adding card:', error); } }; const toggleFavorite = async () => { try { if (isFavorited) { // Remove from favorites const response = await fetch('/api/favorites', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ itemType: 'collection', itemId: collection.id }) }); if (response.ok) { setIsFavorited(false); } else { console.error('Failed to remove favorite:', response.status); } } else { // Add to favorites const response = await fetch('/api/favorites', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ itemType: 'collection', itemId: collection.id }) }); if (response.ok) { setIsFavorited(true); } else { console.error('Failed to add favorite:', response.status); } } } catch (error) { console.error('Error toggling favorite:', error); } }; const togglePublic = async () => { try { const response = await fetch(`/api/collections/${identifier}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ isPublic: !collection.isPublic }) }); if (response.ok) { setCollection(prev => ({ ...prev, isPublic: !prev.isPublic })); } } catch (error) { console.error('Error updating collection:', error); } }; const handleImageUpload = async (imageUrl) => { try { const response = await fetch(`/api/collections/${identifier}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ image: imageUrl }) }); if (response.ok) { setCollection(prev => ({ ...prev, image: imageUrl })); } } catch (error) { console.error('Error updating collection image:', error); } }; // Card interaction handlers const handleToggleSelect = (card) => { setSelectedCards(prev => { const isSelected = prev.some(c => c.id === card.id); if (isSelected) { return prev.filter(c => c.id !== card.id); } else { return [...prev, card]; } }); }; const handleToggleFavorite = async (card) => { try { const isFavorited = favoritedCards.has(card.id); const method = isFavorited ? 'DELETE' : 'POST'; const response = await fetch('/api/favorites', { method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ itemType: 'card', itemId: card.id }) }); if (response.ok) { setFavoritedCards(prev => { const newSet = new Set(prev); if (isFavorited) { newSet.delete(card.id); } else { newSet.add(card.id); } return newSet; }); } } catch (error) { console.error('Error toggling favorite:', error); } }; const handleAddToCollection = (card) => { // This would open a collection selection modal console.log('Add to collection:', card); }; const handleAddToDeck = (card) => { // This would open a deck selection modal console.log('Add to deck:', card); }; const handleDownloadCSV = () => { downloadCollectionCardsCsv(cards, collection?.name); }; // Group cards by game const groupedCards = cards.reduce((acc, card) => { const game = card.game || 'Other'; if (!acc[game]) acc[game] = []; acc[game].push(card); return acc; }, {}); // Get game display names and counts const gameStats = {}; Object.keys(groupedCards).forEach(game => { if (game && game !== 'Other') { gameStats[game] = groupedCards[game].length; } }); const showInitialLoading = authLoading || loading; return { cards, collection, copySuccess, editForm, favoritedCards, gameStats, groupBy, groupedCards, handleAddCard, handleAddToCollection, handleAddToDeck, handleDeleteCollection, handleDownloadCSV, handleEditCollection, handleImageUpload, handleSearchCards, handleToggleFavorite, handleToggleSelect, identifier, isFavorited, loading, router, searchCards, searchQuery, searchResults, selectedCards, selectedRarity, selectedTCG, selectedType, setCards, setCollection, setCopySuccess, setEditForm, setFavoritedCards, setGroupBy, setIsFavorited, setLoading, setSearchCards, setSearchQuery, setSearchResults, setSelectedCards, setSelectedRarity, setSelectedTCG, setSelectedType, setShowDeleteModal, setShowEditModal, setShowSearchResults, setShowShareModal, setShowUploadModal, setSortBy, setViewMode, showDeleteModal, showEditModal, showInitialLoading, showSearchResults, showShareModal, showUploadModal, sortBy, toggleFavorite, togglePublic, viewMode }; }