import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Layout from '../components/Layout'; import DashboardFeaturedCollection from '../components/DashboardFeaturedCollection'; import DashboardRecentActivity from '../components/DashboardRecentActivity'; import DashboardCardSpotlight from '../components/DashboardCardSpotlight'; import { Button, StatCard } from '../components/ui'; import { useAuth } from '../lib/use-auth'; // Dashboard rebuild — redesign-v2 sub-convoys #7 + #8 (2026-06-04). // Layout per operator mockup: // row 1: 4-up StatCard grid (Total Cards / Rare Cards / Collection // Value / Wishlist Items) — locked by § 7.1 of umbrella convoy. // row 2: 2-col layout (lg:grid-cols-3) — left col (2/3) holds // Featured Collection grid + Recent Activity feed; // right col (1/3) holds the Card Spotlight rail. // Mobile: stacks vertically. // // Data: // - Collections (real) drives Total Cards + Collection Value. // - Most-recent 8 user-owned cards (real) drives Featured Collection. // - Rare Cards / Wishlist / Recent Activity / Card Spotlight all // ship with operator-approved placeholders + TODO comments to // the follow-up convoys that will land real data. export default function Dashboard() { const router = useRouter(); const { user, loading: authLoading } = useAuth(); const [collections, setCollections] = useState([]); const [recentCards, setRecentCards] = useState([]); const [loading, setLoading] = useState(true); const [cardsLoading, setCardsLoading] = useState(true); // Redirect to login if not authenticated useEffect(() => { if (!authLoading && !user) { router.push('/login'); } }, [authLoading, user, router]); useEffect(() => { if (!user) return; let cancelled = false; const fetchAll = async () => { const token = localStorage.getItem('auth_token'); const headers = { 'Content-Type': 'application/json' }; if (token) headers.Authorization = `Bearer ${token}`; try { const [collectionsRes, cardsRes] = await Promise.all([ fetch('/api/collections', { headers }), fetch('/api/user-cards', { headers }), ]); if (!cancelled) { if (collectionsRes.ok) { const data = await collectionsRes.json(); setCollections(Array.isArray(data) ? data : []); } else { setCollections([]); } if (cardsRes.ok) { const data = await cardsRes.json(); // /api/user-cards returns rows ordered by created_at DESC; // take the 8 most-recent for the Featured Collection grid. setRecentCards(Array.isArray(data) ? data.slice(0, 8) : []); } else { setRecentCards([]); } } } catch (error) { console.error('[dashboard] fetch error:', error); if (!cancelled) { setCollections([]); setRecentCards([]); } } finally { if (!cancelled) { setLoading(false); setCardsLoading(false); } } }; fetchAll(); return () => { cancelled = true; }; }, [user]); const totalCards = collections.reduce( (total, col) => total + (col.cardCount || 0), 0 ); const collectionValue = collections.reduce( (total, col) => total + (col.value || 0), 0 ); return (
{/* Page heading lives inside the content area, not in a heavy header strip. The top chrome (search, notifs, avatar) is provided by in Layout. */}

Welcome back {user?.username ? `, ${user.username}` : ''}

Here's what's happening with your collection today.

{loading ? (
) : ( <> {/* Stats row — 4-up grid (umbrella § 7.1 locked metrics) */}
} /> {/* TODO(rarity-aggregation convoy): swap placeholder for real count once user_cards.rarity column is populated. */} } /> } /> {/* TODO(wishlist-feature convoy): real wishlist count ships when the wishlist table + API land. */} } />
{/* Main content + right rail */}
)}
); }