import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Layout from '../components/Layout'; import { Modal, Input, Button } from '../components/ui'; import { useAuth } from '../lib/use-auth'; /* 2026-06-04 design-sweep pass: the Tailwind token classes that this page relied on (bg-bg-secondary, text-text-primary, border-border, bg-accent-ember, hover:bg-accent-ember-dark, focus:ring-accent-ember) are NOT defined in tailwind.config.js — they produced zero CSS, leaving the page visually unstyled (transparent backgrounds, no borders, no hover states). Sweep replaces those with inline style={{ ... CSS vars ... }} and the Button / SearchBar primitives so the page actually renders with the Liquid Glass + corner-border design system. `rounded-lg` → `rounded-xl` across the board to match the system standard. */ export default function Decks() { const { user } = useAuth(); const router = useRouter(); const [decks, setDecks] = useState([]); const [loading, setLoading] = useState(true); const [showCreateModal, setShowCreateModal] = useState(false); const [editingDeck, setEditingDeck] = useState(null); const [newDeck, setNewDeck] = useState({ name: '', description: '', format: 'Commander', is_public: false, }); const fetchDecks = async () => { try { const token = localStorage.getItem('auth_token'); const response = await fetch('/api/decks', { headers: { Authorization: `Bearer ${token}`, }, }); if (response.ok) { const data = await response.json(); setDecks(data); } else { console.error('Failed to fetch decks'); } } catch (error) { console.error('Error fetching decks:', error); } finally { setLoading(false); } }; useEffect(() => { if (user) { // eslint-disable-next-line react-hooks/set-state-in-effect -- load decks when user is available fetchDecks(); } }, [user]); const handleCreateDeck = async (e) => { e.preventDefault(); try { const token = localStorage.getItem('auth_token'); const response = await fetch('/api/decks', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify(newDeck), }); if (response.ok) { const createdDeck = await response.json(); setDecks([createdDeck, ...decks]); setShowCreateModal(false); setNewDeck({ name: '', description: '', format: 'Commander', is_public: false }); router.push(`/deck-builder?deck=${createdDeck.id}`); } else { console.error('Failed to create deck'); } } catch (error) { console.error('Error creating deck:', error); } }; const handleEditDeck = async (e) => { e.preventDefault(); try { const token = localStorage.getItem('auth_token'); const response = await fetch(`/api/decks/${editingDeck.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify(editingDeck), }); if (response.ok) { const updatedDeck = await response.json(); setDecks(decks.map((deck) => (deck.id === updatedDeck.id ? updatedDeck : deck))); setEditingDeck(null); } else { console.error('Failed to update deck'); } } catch (error) { console.error('Error updating deck:', error); } }; const handleDeleteDeck = async (deckId) => { if (!confirm('Are you sure you want to delete this deck? This action cannot be undone.')) { return; } try { const token = localStorage.getItem('auth_token'); const response = await fetch(`/api/decks/${deckId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { setDecks(decks.filter((deck) => deck.id !== deckId)); } else { console.error('Failed to delete deck'); } } catch (error) { console.error('Error deleting deck:', error); } }; const getFormatIcon = (format) => { switch (format) { case 'Commander': return '⚔️'; case 'Standard': return '🏆'; case 'Modern': return '🔥'; case 'Legacy': return '💎'; default: return '🃏'; } }; // Single neutral chip style for format badges — the prior // getFormatColor() returned 5 stale Tailwind colour pairs // (bg-purple-100, bg-blue-100, etc.) that don't fit the // Deck Hearth palette and don't render in dark theme anyway. const formatBadgeStyle = { backgroundColor: 'var(--bg-tertiary)', color: 'var(--text-primary)', }; // Reusable inline-style for the .card surfaces used as stat // cards + deck cards. We can't use the .glass-panel class + // rounded-xl directly because we want the same gradient-border // catch-light effect that the chrome chips have, and inline // styles for that pattern are verbose. Easiest path: opt into // the existing .glass-panel class for the multi-layer bg, then // override border-radius via className. (.glass-panel doesn't // set border-radius so we control it via Tailwind.) const chipBgStyle = {}; // textarea / select use .input-field which is the Deck-Hearth // standard input class (rounded-2xl + ember focus ring). const inputFieldClass = 'input-field w-full'; if (!user) { return ( Please log in to view your decks Go to Login ); } if (loading) { return ( ); } return ( {/* Header */} My Decks Build and manage your MTG decks setShowCreateModal(true)}> Create New Deck {/* Stats */} {decks.length} Total Decks {decks.filter((d) => d.format === 'Commander').length} Commander {decks.filter((d) => d.is_public).length} Public {decks.reduce((sum, deck) => sum + (deck.card_count || 0), 0)} Total Cards {/* Decks Grid */} {decks.length === 0 ? ( 🃏 No decks yet Create your first deck to get started setShowCreateModal(true)}> Create Your First Deck ) : ( {decks.map((deck) => ( {getFormatIcon(deck.format)} {deck.format} setEditingDeck({ ...deck })} className="transition-colors" style={{ color: 'var(--text-secondary)' }} aria-label="Edit deck" > ✏️ handleDeleteDeck(deck.id)} className="transition-colors" style={{ color: 'var(--text-secondary)' }} aria-label="Delete deck" > 🗑️ {deck.name} {deck.description && ( {deck.description} )} {deck.card_count || 0} cards {deck.is_public && ( Public )} Edit Deck View ))} )} setShowCreateModal(false)} title="Create New Deck" size="md" > setNewDeck({ ...newDeck, name: e.target.value })} placeholder="Enter deck name" /> Format setNewDeck({ ...newDeck, format: e.target.value })} className={inputFieldClass} > Commander Standard Modern Legacy Description setNewDeck({ ...newDeck, description: e.target.value })} className={inputFieldClass} rows="3" placeholder="Describe your deck strategy..." /> setNewDeck({ ...newDeck, is_public: e.target.checked })} className="mr-2" style={{ accentColor: 'var(--accent-ember)' }} /> Make deck public setShowCreateModal(false)} className="flex-1" > Cancel Create Deck setEditingDeck(null)} title="Edit Deck" size="md" > {editingDeck && ( setEditingDeck({ ...editingDeck, name: e.target.value })} /> Format setEditingDeck({ ...editingDeck, format: e.target.value })} className={inputFieldClass} > Commander Standard Modern Legacy Description setEditingDeck({ ...editingDeck, description: e.target.value })} className={inputFieldClass} rows="3" /> setEditingDeck({ ...editingDeck, is_public: e.target.checked })} className="mr-2" style={{ accentColor: 'var(--accent-ember)' }} /> Make deck public setEditingDeck(null)} className="flex-1" > Cancel Save Changes )} ); }
Build and manage your MTG decks
Create your first deck to get started
{deck.description}