diff --git a/components/CardDetailView.js b/components/CardDetailView.js
new file mode 100644
index 0000000..6e3687c
--- /dev/null
+++ b/components/CardDetailView.js
@@ -0,0 +1,494 @@
+/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
+import CollectionSelectionModal from './CollectionSelectionModal';
+import CardDetailQuantityModal from './CardDetailQuantityModal';
+import CardDetailDeckModal from './CardDetailDeckModal';
+import { ManaCost, ColorIdentity, AdvancedManaCost } from './ManaSymbols';
+import ManaSymbolSettings from './ManaSymbolSettings';
+import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js';
+export default function CardDetailView(props) {
+ const {
+ activeTab,
+ adminLoading,
+ card,
+ cardCollections,
+ cardDecks,
+ collections,
+ decks,
+ formatCurrency,
+ getParticleColor,
+ getParticleCount,
+ getRarityColor,
+ getRarityGradient,
+ getRarityLabel,
+ getTCGIcon,
+ handleAddToCollection,
+ handleAddToDeck,
+ handleOwnershipUpdate,
+ handleToggleFavorite,
+ id,
+ isAdmin,
+ isFavorited,
+ loading,
+ manaSymbolSettings,
+ ownedQuantity,
+ particleStyles,
+ quantity,
+ router,
+ selectedCollection,
+ selectedDeck,
+ setActiveTab,
+ setCard,
+ setCardCollections,
+ setCardDecks,
+ setCollections,
+ setDecks,
+ setIsFavorited,
+ setLoading,
+ setManaSymbolSettings,
+ setOwnedQuantity,
+ setQuantity,
+ setSelectedCollection,
+ setSelectedDeck,
+ setShowCollectionModal,
+ setShowDeckModal,
+ setShowQuantityModal,
+ showCollectionModal,
+ showDeckModal,
+ showInitialLoading,
+ showQuantityModal
+ } = props;
+
+ return (
+ <>
+
+ {/* Hero Section with Card Image and Basic Info */}
+
+ {/* Animated Particles Background */}
+ {getParticleCount(card.rarity) > 0 && (
+
+ {particleStyles.map((style, i) => (
+
+ ))}
+
+ )}
+
+ {/* Background Pattern */}
+
+
+
+
+ {/* Card Image */}
+
+
+ {/* Rarity Glow Effect */}
+
+
+ {card.image_url ? (
+

+ ) : (
+
+
+
{getTCGIcon(card.game)}
+
{card.name}
+
{card.set_name}
+
+
+ {getRarityLabel(card.rarity)}
+
+
+
+
+ )}
+
+
+
+
+ {/* Card Info */}
+
+
+
{card.name}
+
{card.oracle_text || card.card_type}
+
+ {/* Ownership Status and Actions */}
+
+
+
Ownership Status
+
+ {ownedQuantity > 0 && (
+
+ In {VOCAB.MY_COLLECTION} ({ownedQuantity})
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {getTCGIcon(card.game)}
+ {card.game}
+
+ {getRarityLabel(card.rarity)}
+
+
+
+ {formatCurrency(card.current_price || 0)}
+
+
+ {/* Admin Edit Button */}
+ {isAdmin && !adminLoading && (
+
+
+
+ )}
+
+
+ {/* Current Collections and Decks */}
+ {(cardCollections.length > 0 || cardDecks.length > 0) && (
+
+
Currently In:
+
+ {cardCollections.map(collection => (
+
+ 📁
+ {collectionDisplayName(collection)}
+
+ ))}
+ {cardDecks.map(deck => (
+
+ 🎴
+ {deck.name}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+ {/* Content Tabs */}
+
+
+ {['details', 'price-history', 'purchase'].map((tab) => (
+
+ ))}
+
+
+ {/* Tab Content */}
+
+ {activeTab === 'details' && (
+
+ {/* Card Metadata */}
+
+
+ Card Information
+
+
+
+ TCG
+ {card.game}
+
+
+ Set
+ {card.set_name}
+
+
+ Card Number
+ {card.card_number}
+
+
+ Type
+ {card.card_type}
+
+
+ Rarity
+ {getRarityLabel(card.rarity)}
+
+ {card.mana_cost && (
+
+ Cost to Play
+
+
+ )}
+ {card.power && (
+
+ Power
+ {card.power}
+
+ )}
+ {card.toughness && (
+
+ Toughness
+ {card.toughness}
+
+ )}
+ {card.current_price && (
+
+ Current Price
+ {formatCurrency(card.current_price)}
+
+ )}
+
+
+
+ {/* Card Text */}
+
+
+ Card Text
+
+
+ {card.oracle_text && (
+
+ )}
+
+
+
+ )}
+
+ {activeTab === 'price-history' && (
+
+
+ Price History
+
+
+ {/* Price Graph Placeholder */}
+
+
+ Price Trend
+
+
+
+ Price history data will be available soon
+
+
+
+
+ {/* Price Statistics Cards */}
+
+ {/* Current Price */}
+
+
+
💰
+
+ Current Price
+
+
+ {formatCurrency(card.current_price || 0)}
+
+
+
+
+
+ )}
+
+ {activeTab === 'purchase' && (
+
+ )}
+
+
+
+
setShowQuantityModal(false)}
+ />
+
+ {/* Collection Selection Modal */}
+ setShowCollectionModal(false)}
+ cards={card ? [card] : []}
+ onAddToCollections={(results, selectedCollectionIds, cards) => {
+ const successCount = results.filter(r => r.success).length;
+ if (successCount > 0) {
+ // Refresh card collections
+ const fetchCardCollections = async () => {
+ try {
+ const token = localStorage.getItem('auth_token');
+ const response = await fetch(`/api/cards/${id}/collections`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (response.ok) {
+ const data = await response.json();
+ setCardCollections(data);
+ }
+ } catch (error) {
+ console.error('Error refreshing card collections:', error);
+ }
+ };
+ fetchCardCollections();
+ }
+ }}
+ />
+
+ {
+ setShowDeckModal(false);
+ setSelectedDeck('');
+ }}
+ />
+
+ >
+ );
+}
diff --git a/lib/use-card-detail.js b/lib/use-card-detail.js
new file mode 100644
index 0000000..b500524
--- /dev/null
+++ b/lib/use-card-detail.js
@@ -0,0 +1,400 @@
+import { useState, useEffect, useMemo } from 'react';
+import { useRouter } from 'next/router';
+import { VOCAB, collectionDisplayName } from './collection-vocabulary.js';
+
+/**
+ * Card/collection page state and handlers (god-component split).
+ */
+export function useCardDetail({ user = null, authLoading = true } = {}) {
+ const router = useRouter();
+ const isAdmin = user?.role === 'admin';
+ const adminLoading = authLoading;
+ const { id } = router.query;
+
+ const [card, setCard] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [activeTab, setActiveTab] = useState('details');
+ const [ownedQuantity, setOwnedQuantity] = useState(0);
+ const [showQuantityModal, setShowQuantityModal] = useState(false);
+ const [showCollectionModal, setShowCollectionModal] = useState(false);
+ const [showDeckModal, setShowDeckModal] = useState(false);
+ const [selectedCollection, setSelectedCollection] = useState('');
+ const [selectedDeck, setSelectedDeck] = useState('');
+ const [quantity, setQuantity] = useState(1);
+ const [isFavorited, setIsFavorited] = useState(false);
+ const [collections, setCollections] = useState([]);
+ const [decks, setDecks] = useState([]);
+ const [cardCollections, setCardCollections] = useState([]);
+ const [cardDecks, setCardDecks] = useState([]);
+
+ // Mana symbol settings
+ const [manaSymbolSettings, setManaSymbolSettings] = useState({ useSVG: false });
+
+ // Fetch card data from API
+ useEffect(() => {
+ const fetchCard = async () => {
+ if (!id) return;
+
+ try {
+ const response = await fetch(`/api/cards/${id}`);
+ if (response.ok) {
+ const cardData = await response.json();
+ setCard(cardData);
+
+ // Fetch user's ownership of this card
+ const token = localStorage.getItem('auth_token');
+ if (token) {
+ try {
+ const ownershipResponse = await fetch(`/api/cards/${id}/ownership`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (ownershipResponse.ok) {
+ const ownershipData = await ownershipResponse.json();
+ setOwnedQuantity(ownershipData.quantity || 0);
+ }
+ } catch (error) {
+ console.error('Error fetching ownership:', error);
+ }
+
+ // Check if card is favorited
+ try {
+ const favoritesResponse = await fetch(`/api/favorites?type=card`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (favoritesResponse.ok) {
+ const favoritesData = await favoritesResponse.json();
+ const isCardFavorited = favoritesData.favorites.some(fav => fav.item_id == id);
+ setIsFavorited(isCardFavorited);
+ }
+ } catch (error) {
+ console.error('Error checking favorites:', error);
+ }
+ }
+ } else {
+ console.error('Failed to fetch card');
+ }
+ } catch (error) {
+ console.error('Error fetching card:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchCard();
+ }, [id]);
+
+ // Fetch user's collections and decks
+ useEffect(() => {
+ const fetchUserData = async () => {
+ try {
+ const token = localStorage.getItem('auth_token');
+ const headers = {
+ 'Authorization': `Bearer ${token}`
+ };
+
+ // Fetch collections
+ const collectionsResponse = await fetch('/api/collections', { headers });
+ if (collectionsResponse.ok) {
+ const collectionsData = await collectionsResponse.json();
+ setCollections(collectionsData);
+ }
+
+ // Fetch decks
+ const decksResponse = await fetch('/api/decks', { headers });
+ if (decksResponse.ok) {
+ const decksData = await decksResponse.json();
+ setDecks(decksData);
+ }
+
+ // Fetch card's current collections and decks
+ if (card) {
+ const cardCollectionsResponse = await fetch(`/api/cards/${id}/collections`, { headers });
+ if (cardCollectionsResponse.ok) {
+ const cardCollectionsData = await cardCollectionsResponse.json();
+ setCardCollections(cardCollectionsData);
+ }
+
+ const cardDecksResponse = await fetch(`/api/cards/${id}/decks`, { headers });
+ if (cardDecksResponse.ok) {
+ const cardDecksData = await cardDecksResponse.json();
+ setCardDecks(cardDecksData);
+ }
+ }
+ } catch (error) {
+ console.error('Error fetching user data:', error);
+ }
+ };
+
+ if (card) {
+ fetchUserData();
+ }
+ }, [card, id]);
+
+ const getRarityGradient = (rarity) => {
+ const rarityKey = rarity?.toLowerCase();
+ const gradients = {
+ 'common': '#9ca3af, #6b7280, #4b5563', // Subtle gray glow
+ 'uncommon': '#10b981, #059669, #047857', // Green glow
+ 'rare': '#f59e0b, #d97706, #b45309', // Gold glow
+ 'mythic': '#fbbf24, #f59e0b, #d97706', // Rich gold glow
+ 'holographic': '#ec4899, #db2777, #be185d', // Pink glow
+ 'enchanted': '#a855f7, #9333ea, #7c3aed', // Purple glow
+ 'super rare': '#3b82f6, #2563eb, #1d4ed8', // Blue glow
+ 'legendary': '#fbbf24, #f59e0b, #ea580c' // Vibrant gold-orange glow
+ };
+ return gradients[rarityKey] || '#6b7280, #4b5563, #374151';
+ };
+
+ const getParticleCount = (rarity) => {
+ const rarityKey = rarity?.toLowerCase();
+ const particleCounts = {
+ 'common': 0,
+ 'uncommon': 15,
+ 'rare': 25,
+ 'mythic': 40,
+ 'holographic': 50,
+ 'enchanted': 60,
+ 'super rare': 45,
+ 'legendary': 80
+ };
+ return particleCounts[rarityKey] || 0;
+ };
+
+ const particleStyles = useMemo(() => {
+ if (!card) return [];
+ const count = getParticleCount(card.rarity);
+ return Array.from({ length: count }, (_, i) => ({
+ left: `${((i * 37) % 100)}%`,
+ top: `${((i * 53) % 100)}%`,
+ animationDelay: `${i % 3}s`,
+ animationDuration: `${3 + (i % 2)}s`,
+ }));
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- stable decorative layout per card id/rarity
+ }, [card?.id, card?.rarity]);
+
+ const getParticleColor = (rarity) => {
+ const rarityKey = rarity?.toLowerCase();
+ const colors = {
+ 'common': '#ffffff',
+ 'uncommon': '#10b981',
+ 'rare': '#f59e0b',
+ 'mythic': '#ffd700',
+ 'holographic': '#ff6b6b',
+ 'enchanted': '#a855f7',
+ 'super rare': '#3b82f6',
+ 'legendary': '#ffd700'
+ };
+ return colors[rarityKey] || '#ffffff';
+ };
+
+ const getTCGIcon = (game) => {
+ const icons = {
+ 'MTG': '🔮',
+ 'Pokemon': '⚡',
+ 'Lorcana': '✨'
+ };
+ return icons[game] || '🃏';
+ };
+
+ const formatCurrency = (amount) => {
+ if (!amount) return '$0.00';
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD'
+ }).format(amount);
+ };
+
+ const getRarityLabel = (rarity) => {
+ const rarityMap = {
+ 'common': 'Common',
+ 'uncommon': 'Uncommon',
+ 'rare': 'Rare',
+ 'mythic': 'Mythic',
+ 'holographic': 'Holographic',
+ 'enchanted': 'Enchanted',
+ 'super rare': 'Super Rare',
+ 'legendary': 'Legendary'
+ };
+ return rarityMap[rarity?.toLowerCase()] || rarity;
+ };
+
+ const getRarityColor = (rarity) => {
+ const colors = {
+ 'common': '#6B7280',
+ 'uncommon': '#10B981',
+ 'rare': '#F59E0B',
+ 'mythic': '#FFD700',
+ 'holographic': '#FF6B6B',
+ 'enchanted': '#A855F7',
+ 'super rare': '#3B82F6',
+ 'legendary': '#FFD700'
+ };
+ return colors[rarity?.toLowerCase()] || '#6B7280';
+ };
+
+ const handleOwnershipUpdate = async (newQuantity) => {
+ try {
+ const token = localStorage.getItem('auth_token');
+ const response = await fetch(`/api/cards/${id}/ownership`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ },
+ body: JSON.stringify({ quantity: newQuantity })
+ });
+
+ if (response.ok) {
+ setOwnedQuantity(newQuantity);
+ setShowQuantityModal(false);
+ } else {
+ console.error('Failed to update ownership');
+ }
+ } catch (error) {
+ console.error('Error updating ownership:', error);
+ }
+ };
+
+ const handleAddToCollection = async () => {
+ try {
+ const token = localStorage.getItem('auth_token');
+ const headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ };
+
+ const response = await fetch(`/api/cards/${id}/collections`, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ collectionId: selectedCollection })
+ });
+
+ if (response.ok) {
+ // Refresh card collections
+ const cardCollectionsResponse = await fetch(`/api/cards/${id}/collections`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (cardCollectionsResponse.ok) {
+ const cardCollectionsData = await cardCollectionsResponse.json();
+ setCardCollections(cardCollectionsData);
+ }
+ setShowCollectionModal(false);
+ setSelectedCollection('');
+ } else {
+ console.error('Failed to add to collection');
+ }
+ } catch (error) {
+ console.error('Error adding to collection:', error);
+ }
+ };
+
+ const handleAddToDeck = async () => {
+ try {
+ const token = localStorage.getItem('auth_token');
+ const headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ };
+
+ const response = await fetch(`/api/cards/${id}/decks`, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ deckId: selectedDeck })
+ });
+
+ if (response.ok) {
+ // Refresh card decks
+ const cardDecksResponse = await fetch(`/api/cards/${id}/decks`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (cardDecksResponse.ok) {
+ const cardDecksData = await cardDecksResponse.json();
+ setCardDecks(cardDecksData);
+ }
+ setShowDeckModal(false);
+ setSelectedDeck('');
+ } else {
+ console.error('Failed to add to deck');
+ }
+ } catch (error) {
+ console.error('Error adding to deck:', error);
+ }
+ };
+
+ const handleToggleFavorite = async () => {
+ try {
+ const token = localStorage.getItem('auth_token');
+ const response = await fetch(`/api/cards/${id}/favorite`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ },
+ body: JSON.stringify({ favorited: !isFavorited })
+ });
+
+ if (response.ok) {
+ setIsFavorited(!isFavorited);
+ } else {
+ console.error('Failed to toggle favorite');
+ }
+ } catch (error) {
+ console.error('Error toggling favorite:', error);
+ }
+ };
+
+ const showInitialLoading = authLoading || loading;
+
+ return {
+ activeTab,
+ card,
+ cardCollections,
+ cardDecks,
+ collections,
+ decks,
+ formatCurrency,
+ getParticleColor,
+ getParticleCount,
+ getRarityColor,
+ getRarityGradient,
+ getRarityLabel,
+ getTCGIcon,
+ handleAddToCollection,
+ handleAddToDeck,
+ handleOwnershipUpdate,
+ handleToggleFavorite,
+ id,
+ isAdmin,
+ adminLoading,
+ isFavorited,
+ loading,
+ manaSymbolSettings,
+ ownedQuantity,
+ particleStyles,
+ getParticleCount,
+ quantity,
+ router,
+ selectedCollection,
+ selectedDeck,
+ setActiveTab,
+ setCard,
+ setCardCollections,
+ setCardDecks,
+ setCollections,
+ setDecks,
+ setIsFavorited,
+ setLoading,
+ setManaSymbolSettings,
+ setOwnedQuantity,
+ setQuantity,
+ setSelectedCollection,
+ setSelectedDeck,
+ setShowCollectionModal,
+ setShowDeckModal,
+ setShowQuantityModal,
+ showCollectionModal,
+ showDeckModal,
+ showInitialLoading,
+ showQuantityModal
+ };
+}
diff --git a/pages/card/[id].js b/pages/card/[id].js
index aa1b700..b4d08d6 100644
--- a/pages/card/[id].js
+++ b/pages/card/[id].js
@@ -1,378 +1,38 @@
-/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
-import { useState, useEffect, useMemo } from 'react';
import { useRouter } from 'next/router';
import Layout from '../../components/Layout';
+import CardDetailView from '../../components/CardDetailView';
import { useAuth } from '../../lib/use-auth';
-import CollectionSelectionModal from '../../components/CollectionSelectionModal';
-import CardDetailQuantityModal from '../../components/CardDetailQuantityModal';
-import CardDetailDeckModal from '../../components/CardDetailDeckModal';
-import { ManaCost, ColorIdentity, AdvancedManaCost } from '../../components/ManaSymbols';
-import ManaSymbolSettings from '../../components/ManaSymbolSettings';
-import { VOCAB, collectionDisplayName } from '../../lib/collection-vocabulary.js';
+import { useCardDetail } from '../../lib/use-card-detail.js';
export default function CardDetail() {
const router = useRouter();
- const { id } = router.query;
const { user, loading: authLoading } = useAuth();
+ const detail = useCardDetail({ user, authLoading });
- const [card, setCard] = useState(null);
- const [loading, setLoading] = useState(true);
- const [activeTab, setActiveTab] = useState('details');
- const [ownedQuantity, setOwnedQuantity] = useState(0);
- const [showQuantityModal, setShowQuantityModal] = useState(false);
- const [showCollectionModal, setShowCollectionModal] = useState(false);
- const [showDeckModal, setShowDeckModal] = useState(false);
- const [selectedCollection, setSelectedCollection] = useState('');
- const [selectedDeck, setSelectedDeck] = useState('');
- const [quantity, setQuantity] = useState(1);
- const [isFavorited, setIsFavorited] = useState(false);
- const [collections, setCollections] = useState([]);
- const [decks, setDecks] = useState([]);
- const [cardCollections, setCardCollections] = useState([]);
- const [cardDecks, setCardDecks] = useState([]);
-
- // Mana symbol settings
- const [manaSymbolSettings, setManaSymbolSettings] = useState({ useSVG: false });
-
- const isAdmin = user?.role === 'admin';
- const adminLoading = authLoading;
-
- // Fetch card data from API
- useEffect(() => {
- const fetchCard = async () => {
- if (!id) return;
-
- try {
- const response = await fetch(`/api/cards/${id}`);
- if (response.ok) {
- const cardData = await response.json();
- setCard(cardData);
-
- // Fetch user's ownership of this card
- const token = localStorage.getItem('auth_token');
- if (token) {
- try {
- const ownershipResponse = await fetch(`/api/cards/${id}/ownership`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (ownershipResponse.ok) {
- const ownershipData = await ownershipResponse.json();
- setOwnedQuantity(ownershipData.quantity || 0);
- }
- } catch (error) {
- console.error('Error fetching ownership:', error);
- }
-
- // Check if card is favorited
- try {
- const favoritesResponse = await fetch(`/api/favorites?type=card`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (favoritesResponse.ok) {
- const favoritesData = await favoritesResponse.json();
- const isCardFavorited = favoritesData.favorites.some(fav => fav.item_id == id);
- setIsFavorited(isCardFavorited);
- }
- } catch (error) {
- console.error('Error checking favorites:', error);
- }
- }
- } else {
- console.error('Failed to fetch card');
- }
- } catch (error) {
- console.error('Error fetching card:', error);
- } finally {
- setLoading(false);
- }
- };
-
- fetchCard();
- }, [id]);
-
- // Fetch user's collections and decks
- useEffect(() => {
- const fetchUserData = async () => {
- try {
- const token = localStorage.getItem('auth_token');
- const headers = {
- 'Authorization': `Bearer ${token}`
- };
-
- // Fetch collections
- const collectionsResponse = await fetch('/api/collections', { headers });
- if (collectionsResponse.ok) {
- const collectionsData = await collectionsResponse.json();
- setCollections(collectionsData);
- }
-
- // Fetch decks
- const decksResponse = await fetch('/api/decks', { headers });
- if (decksResponse.ok) {
- const decksData = await decksResponse.json();
- setDecks(decksData);
- }
-
- // Fetch card's current collections and decks
- if (card) {
- const cardCollectionsResponse = await fetch(`/api/cards/${id}/collections`, { headers });
- if (cardCollectionsResponse.ok) {
- const cardCollectionsData = await cardCollectionsResponse.json();
- setCardCollections(cardCollectionsData);
- }
-
- const cardDecksResponse = await fetch(`/api/cards/${id}/decks`, { headers });
- if (cardDecksResponse.ok) {
- const cardDecksData = await cardDecksResponse.json();
- setCardDecks(cardDecksData);
- }
- }
- } catch (error) {
- console.error('Error fetching user data:', error);
- }
- };
-
- if (card) {
- fetchUserData();
- }
- }, [card, id]);
-
- const getRarityGradient = (rarity) => {
- const rarityKey = rarity?.toLowerCase();
- const gradients = {
- 'common': '#9ca3af, #6b7280, #4b5563', // Subtle gray glow
- 'uncommon': '#10b981, #059669, #047857', // Green glow
- 'rare': '#f59e0b, #d97706, #b45309', // Gold glow
- 'mythic': '#fbbf24, #f59e0b, #d97706', // Rich gold glow
- 'holographic': '#ec4899, #db2777, #be185d', // Pink glow
- 'enchanted': '#a855f7, #9333ea, #7c3aed', // Purple glow
- 'super rare': '#3b82f6, #2563eb, #1d4ed8', // Blue glow
- 'legendary': '#fbbf24, #f59e0b, #ea580c' // Vibrant gold-orange glow
- };
- return gradients[rarityKey] || '#6b7280, #4b5563, #374151';
- };
-
- const getParticleCount = (rarity) => {
- const rarityKey = rarity?.toLowerCase();
- const particleCounts = {
- 'common': 0,
- 'uncommon': 15,
- 'rare': 25,
- 'mythic': 40,
- 'holographic': 50,
- 'enchanted': 60,
- 'super rare': 45,
- 'legendary': 80
- };
- return particleCounts[rarityKey] || 0;
- };
-
- const particleStyles = useMemo(() => {
- if (!card) return [];
- const count = getParticleCount(card.rarity);
- return Array.from({ length: count }, (_, i) => ({
- left: `${((i * 37) % 100)}%`,
- top: `${((i * 53) % 100)}%`,
- animationDelay: `${i % 3}s`,
- animationDuration: `${3 + (i % 2)}s`,
- }));
- // eslint-disable-next-line react-hooks/exhaustive-deps -- stable decorative layout per card id/rarity
- }, [card?.id, card?.rarity]);
-
- const getParticleColor = (rarity) => {
- const rarityKey = rarity?.toLowerCase();
- const colors = {
- 'common': '#ffffff',
- 'uncommon': '#10b981',
- 'rare': '#f59e0b',
- 'mythic': '#ffd700',
- 'holographic': '#ff6b6b',
- 'enchanted': '#a855f7',
- 'super rare': '#3b82f6',
- 'legendary': '#ffd700'
- };
- return colors[rarityKey] || '#ffffff';
- };
-
- const getTCGIcon = (game) => {
- const icons = {
- 'MTG': '🔮',
- 'Pokemon': '⚡',
- 'Lorcana': '✨'
- };
- return icons[game] || '🃏';
- };
-
- const formatCurrency = (amount) => {
- if (!amount) return '$0.00';
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: 'USD'
- }).format(amount);
- };
-
- const getRarityLabel = (rarity) => {
- const rarityMap = {
- 'common': 'Common',
- 'uncommon': 'Uncommon',
- 'rare': 'Rare',
- 'mythic': 'Mythic',
- 'holographic': 'Holographic',
- 'enchanted': 'Enchanted',
- 'super rare': 'Super Rare',
- 'legendary': 'Legendary'
- };
- return rarityMap[rarity?.toLowerCase()] || rarity;
- };
-
- const getRarityColor = (rarity) => {
- const colors = {
- 'common': '#6B7280',
- 'uncommon': '#10B981',
- 'rare': '#F59E0B',
- 'mythic': '#FFD700',
- 'holographic': '#FF6B6B',
- 'enchanted': '#A855F7',
- 'super rare': '#3B82F6',
- 'legendary': '#FFD700'
- };
- return colors[rarity?.toLowerCase()] || '#6B7280';
- };
-
- const handleOwnershipUpdate = async (newQuantity) => {
- try {
- const token = localStorage.getItem('auth_token');
- const response = await fetch(`/api/cards/${id}/ownership`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify({ quantity: newQuantity })
- });
-
- if (response.ok) {
- setOwnedQuantity(newQuantity);
- setShowQuantityModal(false);
- } else {
- console.error('Failed to update ownership');
- }
- } catch (error) {
- console.error('Error updating ownership:', error);
- }
- };
-
- const handleAddToCollection = async () => {
- try {
- const token = localStorage.getItem('auth_token');
- const headers = {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- };
-
- const response = await fetch(`/api/cards/${id}/collections`, {
- method: 'POST',
- headers,
- body: JSON.stringify({ collectionId: selectedCollection })
- });
-
- if (response.ok) {
- // Refresh card collections
- const cardCollectionsResponse = await fetch(`/api/cards/${id}/collections`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (cardCollectionsResponse.ok) {
- const cardCollectionsData = await cardCollectionsResponse.json();
- setCardCollections(cardCollectionsData);
- }
- setShowCollectionModal(false);
- setSelectedCollection('');
- } else {
- console.error('Failed to add to collection');
- }
- } catch (error) {
- console.error('Error adding to collection:', error);
- }
- };
-
- const handleAddToDeck = async () => {
- try {
- const token = localStorage.getItem('auth_token');
- const headers = {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- };
-
- const response = await fetch(`/api/cards/${id}/decks`, {
- method: 'POST',
- headers,
- body: JSON.stringify({ deckId: selectedDeck })
- });
-
- if (response.ok) {
- // Refresh card decks
- const cardDecksResponse = await fetch(`/api/cards/${id}/decks`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (cardDecksResponse.ok) {
- const cardDecksData = await cardDecksResponse.json();
- setCardDecks(cardDecksData);
- }
- setShowDeckModal(false);
- setSelectedDeck('');
- } else {
- console.error('Failed to add to deck');
- }
- } catch (error) {
- console.error('Error adding to deck:', error);
- }
- };
-
- const handleToggleFavorite = async () => {
- try {
- const token = localStorage.getItem('auth_token');
- const response = await fetch(`/api/cards/${id}/favorite`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify({ favorited: !isFavorited })
- });
-
- if (response.ok) {
- setIsFavorited(!isFavorited);
- } else {
- console.error('Failed to toggle favorite');
- }
- } catch (error) {
- console.error('Error toggling favorite:', error);
- }
- };
-
- if (loading) {
+ if (detail.loading) {
return (
);
}
- if (!card) {
+ if (!detail.card) {
return (
🃏
-
+
Card Not Found
The card you're looking for doesn't exist.