/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */ import { useState, useEffect, useRef } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Layout from '../components/Layout'; import DeckBuilderStatsBar from '../components/DeckBuilderStatsBar'; import { ManaCost, ColorIdentity, ColorFilterSymbol } from '../components/ManaSymbols'; import ManaSymbolSettings from '../components/ManaSymbolSettings'; import { useAuth } from '../lib/use-auth'; import { computeDeckStats, isBasicLand } from '../lib/deck-builder-stats'; import { getColorIdentity, getColorSymbol } from '../lib/mana-symbols'; export default function DeckBuilder() { const { user } = useAuth(); const router = useRouter(); const { deck: deckId } = router.query; const [deck, setDeck] = useState(null); const [deckCards, setDeckCards] = useState([]); const [searchResults, setSearchResults] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [loading, setLoading] = useState(true); const [searchLoading, setSearchLoading] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(true); const [selectedCard, setSelectedCard] = useState(null); const [showFilters, setShowFilters] = useState(false); const [showSettings, setShowSettings] = useState(false); const [viewMode, setViewMode] = useState('list'); // 'list' or 'thumbnail' const [manaSymbolSettings, setManaSymbolSettings] = useState({ useSVG: false }); const [filters, setFilters] = useState({ colors: [], types: [], cmc: '', rarity: '' }); const searchTimeoutRef = useRef(null); const fetchDeck = async () => { try { const token = localStorage.getItem('auth_token'); const response = await fetch(`/api/decks/${deckId}`, { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); setDeck(data); setDeckCards(data.cards || []); } else { console.error('Failed to fetch deck'); router.push('/decks'); } } catch (error) { console.error('Error fetching deck:', error); router.push('/decks'); } finally { setLoading(false); } }; const searchCards = async () => { setSearchLoading(true); try { const token = localStorage.getItem('auth_token'); const params = new URLSearchParams({ game: 'MTG', limit: '50' }); // Only add search if there's a query if (searchQuery.trim()) { params.append('search', searchQuery); } if (filters.colors.length > 0) { params.append('colors', filters.colors.join(',')); } if (filters.types.length > 0) { params.append('types', filters.types.join(',')); } if (filters.cmc) { params.append('cmc', filters.cmc); } if (filters.rarity) { params.append('rarity', filters.rarity); } const response = await fetch(`/api/cards/search?${params}`, { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const data = await response.json(); setSearchResults(data.cards || []); } } catch (error) { console.error('Error searching cards:', error); } finally { setSearchLoading(false); } }; useEffect(() => { if (user && deckId) { // eslint-disable-next-line react-hooks/set-state-in-effect -- load deck when route id or user changes fetchDeck(); } // eslint-disable-next-line react-hooks/exhaustive-deps -- load deck when route id or user changes }, [user, deckId]); // Load initial cards when component mounts useEffect(() => { if (user) { // eslint-disable-next-line react-hooks/set-state-in-effect -- initial catalog load once user is known searchCards(); } // eslint-disable-next-line react-hooks/exhaustive-deps -- initial catalog load once user is known }, [user]); useEffect(() => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } searchTimeoutRef.current = setTimeout(() => { searchCards(); }, 300); return () => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps -- debounced search; searchCards reads latest filter state }, [searchQuery, filters]); const addCardToDeck = async (card, quantity = 1) => { // Commander format validation if (deck.format === 'Commander') { const existingCard = deckCards.find(dc => dc.card_id === card.id); const currentQuantity = existingCard ? existingCard.quantity : 0; // Check singleton rule (except basic lands) if (!isBasicLand(card) && currentQuantity + quantity > 1) { alert('Commander format allows only 1 copy of each non-basic land card.'); return; } // Check total deck size const totalCards = deckCards.reduce((sum, dc) => sum + dc.quantity, 0); if (totalCards + quantity > 100) { alert('Commander decks can have a maximum of 100 cards.'); return; } } try { const token = localStorage.getItem('auth_token'); const response = await fetch(`/api/decks/${deckId}/cards`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ cardId: card.id, quantity }) }); if (response.ok) { // Refresh deck cards fetchDeck(); } else { console.error('Failed to add card to deck'); } } catch (error) { console.error('Error adding card to deck:', error); } }; const removeCardFromDeck = async (cardId, quantity = 1) => { try { const token = localStorage.getItem('auth_token'); const response = await fetch(`/api/decks/${deckId}/cards`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ cardId, quantity }) }); if (response.ok) { // Refresh deck cards fetchDeck(); } else { console.error('Failed to remove card from deck'); } } catch (error) { console.error('Error removing card from deck:', error); } }; const toggleColorFilter = (color) => { setFilters(prev => ({ ...prev, colors: prev.colors.includes(color) ? prev.colors.filter(c => c !== color) : [...prev.colors, color] })); }; const clearFilters = () => { setFilters({ colors: [], types: [], cmc: '', rarity: '' }); }; if (!user) { return (

Please log in to use the deck builder

Go to Login
); } if (loading) { return (
); } if (!deck) { return (

Deck not found

Back to My Decks
); } const stats = computeDeckStats(deckCards); return (
{/* Header */}
← Back to Decks

{deck.name}

{deck.format} • {stats.totalCards}/100 cards

View Deck
{/* Main Deck View - Left Side */}

Deck Cards ({stats.totalCards})

{/* Deck Cards List */}
{deckCards.length === 0 ? (
🃏

Empty Deck

Start building your deck by searching for cards

{!sidebarOpen && ( )}
) : ( deckCards .sort((a, b) => a.name.localeCompare(b.name)) .map((card) => (
{card.image_url && ( {card.name} )}

{card.name}

{card.set_name}

{card.mana_cost && (
)} {card.rarity && ( {card.rarity} )}
{card.quantity}x
)) )}
{/* Full Height Right Sidebar - Card Browser */}
{!selectedCard ? ( <> {/* Header */}

Card Browser

{/* View Mode Toggle */}
{/* Search */}
setSearchQuery(e.target.value)} placeholder="Search for cards..." className="flex-1 px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-primary text-text-primary text-sm" />
{/* Settings Panel */} {showSettings && (
)} {/* Quick Filters */} {showFilters && (
{/* Color Filters */}
{(filters.colors.length > 0 || filters.cmc || filters.rarity) && ( )}
{['W', 'U', 'B', 'R', 'G'].map(color => ( ))}
{/* CMC and Rarity */}
)} {/* Card List */}
{viewMode === 'list' ? ( /* List View */
{searchLoading ? (

Searching...

) : searchResults.length === 0 ? (
🔍

{searchQuery ? 'No cards found' : 'No cards available'}

) : ( searchResults.map((card) => (
setSelectedCard(card)} >
{card.image_url && ( {card.name} )}

{card.name}

{card.set_name}

{card.mana_cost && ( )} {card.rarity && ( {card.rarity[0].toUpperCase()} )}
)) )}
) : ( /* Thumbnail View */
{searchLoading ? (

Searching...

) : searchResults.length === 0 ? (
🔍

{searchQuery ? 'No cards found' : 'No cards available'}

) : (
{searchResults.map((card) => (
setSelectedCard(card)} > {card.image_url ? (
{card.name} {/* Hover overlay with card name */}

{card.name}

{card.set_name}

{/* Rarity indicator */} {card.rarity && (
)}
) : (

{card.name}

{card.set_name}

)}
))}
)}
)}
) : ( /* Card Detail View */ <> {/* Detail Header */}

{selectedCard.name}

{/* Card Detail Content */}
{/* Card Image */} {selectedCard.image_url && (
{selectedCard.name}
)} {/* Card Info */}

{selectedCard.name}

{selectedCard.set_name}

{selectedCard.mana_cost && (
)} {selectedCard.card_type && (
{selectedCard.card_type}
)} {selectedCard.oracle_text && (
{selectedCard.oracle_text}
)}
{selectedCard.rarity && (
{selectedCard.rarity}
)} {selectedCard.cmc !== undefined && (
{selectedCard.cmc}
)}
)} {/* Floating Action Button */} {selectedCard && (
)}
); }