deckhearth/pages/card/[id].js
Randall Stillwell 0d6c6f1957 Implemented admin authentication and seamless card editing
- Created admin authentication system with useIsAdmin hook
- Added AdminProtected component for route protection
- Added prominent 'Edit Card (Admin)' button on card detail pages
- Protected all admin routes (/admin/*) with authentication
- Added admin navigation item to main layout sidebar
- Updated auth verification API to return mock admin user
- Integrated admin edit button that redirects to card editor with card ID
- Added proper access denied page for non-admin users
- Admin-only features now show/hide based on user role
- Seamless workflow: spot incorrect card → click edit → fix immediately
2025-07-24 16:31:29 -05:00

819 lines
No EOL
33 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Layout from '../../components/Layout';
import { useIsAdmin } from '../../lib/admin-auth';
export default function CardDetail() {
const router = useRouter();
const { id } = router.query;
const user = {
email: 'me@randallstillwell.com',
role: 'user'
};
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([]);
// Check admin status
const { isAdmin, loading: adminLoading } = useIsAdmin();
// 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);
// Set initial owned quantity if available
if (cardData.quantity) {
setOwnedQuantity(cardData.quantity);
}
} 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 {
// Fetch collections
const collectionsResponse = await fetch('/api/collections');
if (collectionsResponse.ok) {
const collectionsData = await collectionsResponse.json();
setCollections(collectionsData);
}
// Fetch decks
const decksResponse = await fetch('/api/decks');
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`);
if (cardCollectionsResponse.ok) {
const cardCollectionsData = await cardCollectionsResponse.json();
setCardCollections(cardCollectionsData);
}
const cardDecksResponse = await fetch(`/api/cards/${id}/decks`);
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 getTCGGradient = (game) => {
const gradients = {
'MTG': 'from-purple-600 via-purple-500 to-indigo-600',
'Pokemon': 'from-blue-600 via-blue-500 to-cyan-600',
'Lorcana': 'from-pink-600 via-pink-500 to-purple-600'
};
return gradients[game] || 'from-gray-600 to-gray-700';
};
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 response = await fetch(`/api/cards/${id}/ownership`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
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 response = await fetch(`/api/cards/${id}/collections`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ collectionId: selectedCollection })
});
if (response.ok) {
// Refresh card collections
const cardCollectionsResponse = await fetch(`/api/cards/${id}/collections`);
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 response = await fetch(`/api/cards/${id}/decks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ deckId: selectedDeck })
});
if (response.ok) {
// Refresh card decks
const cardDecksResponse = await fetch(`/api/cards/${id}/decks`);
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 response = await fetch(`/api/cards/${id}/favorite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
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) {
return (
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2" style={{ borderColor: 'var(--text-accent-light)' }}></div>
</div>
</Layout>
);
}
if (!card) {
return (
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="text-6xl mb-4">🃏</div>
<h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
Card Not Found
</h2>
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
The card you're looking for doesn't exist.
</p>
<button
onClick={() => router.push('/cards')}
className="px-6 py-3 rounded-xl font-medium gradient-bg-purple text-white hover:shadow-lg transition-all duration-200"
>
Back to Cards
</button>
</div>
</div>
</Layout>
);
}
return (
<Layout user={user}>
{/* Hero Section with Card Image and Basic Info */}
<div
className="relative min-h-96 flex items-center justify-center overflow-hidden"
style={{
background: `linear-gradient(135deg, ${getTCGGradient(card.game).replace('from-', '').replace('via-', '').replace('to-', '')})`
}}
>
{/* Background Pattern */}
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0" style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.1'%3E%3Ccircle cx='30' cy='30' r='2'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`
}}></div>
</div>
<div className="relative z-10 container mx-auto px-6 py-12">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{/* Card Image */}
<div className="flex justify-center">
<div className="relative group">
<div
className="w-80 h-112 rounded-2xl overflow-hidden shadow-2xl transform transition-all duration-300 hover:scale-105"
style={{
background: `linear-gradient(135deg, ${getRarityColor(card.rarity)}20, ${getRarityColor(card.rarity)}10)`,
boxShadow: `0 20px 40px rgba(0, 0, 0, 0.3)`
}}
>
{card.image_url ? (
<img
src={card.image_url}
alt={card.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-white">
<div className="text-center p-8">
<div className="text-6xl mb-4">{getTCGIcon(card.game)}</div>
<h1 className="text-2xl font-bold mb-2">{card.name}</h1>
<p className="text-sm opacity-90">{card.set_name}</p>
<div className="mt-4">
<span className="px-3 py-1 rounded-full text-xs font-medium bg-white bg-opacity-20 backdrop-blur-sm">
{getRarityLabel(card.rarity)}
</span>
</div>
</div>
</div>
)}
</div>
</div>
</div>
{/* Card Info */}
<div className="text-white">
<div className="mb-6">
<h1 className="text-4xl font-bold mb-2">{card.name}</h1>
<p className="text-xl opacity-90 mb-4">{card.oracle_text || card.card_type}</p>
{/* Ownership Status and Actions */}
<div className="mb-4 p-4 rounded-xl bg-white bg-opacity-10 backdrop-blur-sm">
<div className="flex items-center justify-between mb-3">
<span className="font-semibold">Ownership Status</span>
<div className="flex items-center gap-2">
{ownedQuantity > 0 && (
<span className="px-3 py-1 rounded-full text-sm font-medium bg-green-500 bg-opacity-80">
Owned ({ownedQuantity})
</span>
)}
<button
onClick={handleToggleFavorite}
className={`p-2 rounded-full transition-all duration-200 ${
isFavorited
? 'bg-red-500 bg-opacity-80 text-white'
: 'bg-white bg-opacity-20 backdrop-blur-sm hover:bg-opacity-30'
}`}
>
{isFavorited ? '❤️' : '🤍'}
</button>
</div>
</div>
<div className="flex items-center gap-4 flex-wrap">
<button
onClick={() => setShowQuantityModal(true)}
className="px-4 py-2 rounded-xl font-medium bg-white bg-opacity-20 backdrop-blur-sm hover:bg-opacity-30 transition-all duration-200 flex items-center gap-2"
>
<span>📦</span>
{ownedQuantity > 0 ? 'Update Quantity' : 'Mark as Owned'}
</button>
<button
onClick={() => setShowCollectionModal(true)}
className="px-4 py-2 rounded-xl font-medium bg-white bg-opacity-20 backdrop-blur-sm hover:bg-opacity-30 transition-all duration-200 flex items-center gap-2"
>
<span>📁</span>
Add to Collection
</button>
<button
onClick={() => setShowDeckModal(true)}
className="px-4 py-2 rounded-xl font-medium bg-white bg-opacity-20 backdrop-blur-sm hover:bg-opacity-30 transition-all duration-200 flex items-center gap-2"
>
<span>🎴</span>
Add to Deck
</button>
</div>
</div>
<div className="flex items-center gap-4 mb-6">
<span className="text-3xl">{getTCGIcon(card.game)}</span>
<span className="text-lg">{card.game}</span>
<span className="px-3 py-1 rounded-full text-sm font-medium bg-white bg-opacity-20 backdrop-blur-sm">
{getRarityLabel(card.rarity)}
</span>
</div>
<div className="text-3xl font-bold gradient-text-gold">
{formatCurrency(card.current_price || 0)}
</div>
{/* Admin Edit Button */}
{isAdmin && !adminLoading && (
<div className="mt-4">
<button
onClick={() => router.push(`/admin/card-editor?id=${card.id}`)}
className="px-4 py-2 rounded-xl font-medium bg-orange-500 hover:bg-orange-600 text-white transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl"
>
<span></span>
Edit Card (Admin)
</button>
</div>
)}
</div>
{/* Current Collections and Decks */}
{(cardCollections.length > 0 || cardDecks.length > 0) && (
<div className="mb-6 p-4 rounded-xl bg-white bg-opacity-10 backdrop-blur-sm">
<h4 className="font-semibold mb-3">Currently In:</h4>
<div className="space-y-2">
{cardCollections.map(collection => (
<div key={collection.id} className="flex items-center gap-2 text-sm">
<span>📁</span>
<span>{collection.name}</span>
</div>
))}
{cardDecks.map(deck => (
<div key={deck.id} className="flex items-center gap-2 text-sm">
<span>🎴</span>
<span>{deck.name}</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
{/* Content Tabs */}
<div className="container mx-auto px-6 py-8">
<div className="flex border-b" style={{ borderColor: 'var(--border)' }}>
{['details', 'price-history', 'purchase'].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-6 py-3 font-medium transition-all duration-200 ${
activeTab === tab
? 'border-b-2 font-semibold'
: 'hover:opacity-70'
}`}
style={{
color: activeTab === tab ? 'var(--text-accent)' : 'var(--text-secondary)',
borderColor: activeTab === tab ? 'var(--text-accent)' : 'transparent'
}}
>
{tab === 'details' && 'Card Details'}
{tab === 'price-history' && 'Price History'}
{tab === 'purchase' && 'Purchase Options'}
</button>
))}
</div>
{/* Tab Content */}
<div className="mt-8">
{activeTab === 'details' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Card Metadata */}
<div className="space-y-6">
<h3 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
Card Information
</h3>
<div className="space-y-4">
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>TCG</span>
<span style={{ color: 'var(--text-primary)' }}>{card.game}</span>
</div>
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Set</span>
<span style={{ color: 'var(--text-primary)' }}>{card.set_name}</span>
</div>
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Card Number</span>
<span style={{ color: 'var(--text-primary)' }}>{card.card_number}</span>
</div>
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Type</span>
<span style={{ color: 'var(--text-primary)' }}>{card.card_type}</span>
</div>
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Rarity</span>
<span style={{ color: 'var(--text-primary)' }}>{getRarityLabel(card.rarity)}</span>
</div>
{card.mana_cost && (
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Cost to Play</span>
<span style={{ color: 'var(--text-primary)' }}>{card.mana_cost}</span>
</div>
)}
{card.power && (
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Power</span>
<span style={{ color: 'var(--text-primary)' }}>{card.power}</span>
</div>
)}
{card.toughness && (
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Toughness</span>
<span style={{ color: 'var(--text-primary)' }}>{card.toughness}</span>
</div>
)}
{card.current_price && (
<div className="flex justify-between py-3 border-b" style={{ borderColor: 'var(--border)' }}>
<span style={{ color: 'var(--text-secondary)' }}>Current Price</span>
<span style={{ color: 'var(--text-primary)' }}>{formatCurrency(card.current_price)}</span>
</div>
)}
</div>
</div>
{/* Card Text */}
<div className="space-y-6">
<h3 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
Card Text
</h3>
<div className="space-y-4">
{card.oracle_text && (
<div className="p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
<p style={{ color: 'var(--text-primary)' }}>{card.oracle_text}</p>
</div>
)}
</div>
</div>
</div>
)}
{activeTab === 'price-history' && (
<div className="space-y-6">
<h3 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
Price History
</h3>
{/* Price Graph Placeholder */}
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
<h4 className="text-lg font-semibold mb-6" style={{ color: 'var(--text-primary)' }}>
Price Trend
</h4>
<div className="h-64 flex items-center justify-center">
<p style={{ color: 'var(--text-secondary)' }}>
Price history data will be available soon
</p>
</div>
</div>
{/* Price Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Current Price */}
<div className="p-6 rounded-xl border transition-all duration-200 hover:shadow-lg hover:scale-105"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)'
}}>
<div className="text-center">
<div className="text-2xl mb-2">💰</div>
<h5 className="font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
Current Price
</h5>
<div className="text-xl font-bold gradient-text-gold">
{formatCurrency(card.current_price || 0)}
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'purchase' && (
<div className="space-y-6">
<h3 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
Where to Buy
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<a
href={`https://www.tcgplayer.com/search/${card.game.toLowerCase()}/product?productName=${encodeURIComponent(card.name)}`}
target="_blank"
rel="noopener noreferrer"
className="p-6 rounded-xl border transition-all duration-200 hover:shadow-lg hover:scale-105"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)'
}}
>
<div className="flex items-center gap-3">
<span className="text-2xl">🃏</span>
<div>
<h4 className="font-semibold" style={{ color: 'var(--text-primary)' }}>
TCGPlayer
</h4>
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
View on TCGPlayer
</p>
</div>
</div>
</a>
<a
href={`https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(card.name + ' ' + card.game)}`}
target="_blank"
rel="noopener noreferrer"
className="p-6 rounded-xl border transition-all duration-200 hover:shadow-lg hover:scale-105"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)'
}}
>
<div className="flex items-center gap-3">
<span className="text-2xl">🛒</span>
<div>
<h4 className="font-semibold" style={{ color: 'var(--text-primary)' }}>
eBay
</h4>
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
View on eBay
</p>
</div>
</div>
</a>
</div>
</div>
)}
</div>
</div>
{/* Quantity Modal */}
{showQuantityModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 max-w-md w-full mx-4" style={{ backgroundColor: 'var(--bg-primary)' }}>
<h3 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
{ownedQuantity > 0 ? 'Update Quantity' : 'Mark as Owned'}
</h3>
<div className="mb-6">
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
Quantity
</label>
<div className="flex items-center gap-4">
<button
onClick={() => setQuantity(Math.max(1, quantity - 1))}
className="w-10 h-10 rounded-lg flex items-center justify-center border transition-all duration-200 hover:shadow-md"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
-
</button>
<span className="text-2xl font-bold px-4" style={{ color: 'var(--text-primary)' }}>
{quantity}
</span>
<button
onClick={() => setQuantity(quantity + 1)}
className="w-10 h-10 rounded-lg flex items-center justify-center border transition-all duration-200 hover:shadow-md"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
+
</button>
</div>
</div>
<div className="flex gap-3">
<button
onClick={() => handleOwnershipUpdate(quantity)}
className="flex-1 px-4 py-2 rounded-xl font-medium gradient-bg-purple text-white hover:shadow-lg transition-all duration-200"
>
{ownedQuantity > 0 ? 'Update' : 'Mark as Owned'}
</button>
<button
onClick={() => setShowQuantityModal(false)}
className="flex-1 px-4 py-2 rounded-xl font-medium border transition-all duration-200"
style={{
borderColor: 'var(--border)',
color: 'var(--text-secondary)'
}}
>
Cancel
</button>
</div>
</div>
</div>
)}
{/* Collection Modal */}
{showCollectionModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 max-w-md w-full mx-4" style={{ backgroundColor: 'var(--bg-primary)' }}>
<h3 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
Add to Collection
</h3>
<div className="mb-6">
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
Select Collection
</label>
<select
value={selectedCollection}
onChange={(e) => setSelectedCollection(e.target.value)}
className="w-full p-3 rounded-xl border transition-all duration-200"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
<option value="">Choose a collection...</option>
{collections
.filter(collection => collection.game === card.game)
.map(collection => (
<option key={collection.id} value={collection.id}>
{collection.name}
</option>
))}
</select>
</div>
<div className="flex gap-3">
<button
onClick={handleAddToCollection}
disabled={!selectedCollection}
className={`flex-1 px-4 py-2 rounded-xl font-medium transition-all duration-200 ${
selectedCollection
? 'gradient-bg-purple text-white hover:shadow-lg'
: 'opacity-50 cursor-not-allowed'
}`}
>
Add to Collection
</button>
<button
onClick={() => {
setShowCollectionModal(false);
setSelectedCollection('');
}}
className="flex-1 px-4 py-2 rounded-xl font-medium border transition-all duration-200"
style={{
borderColor: 'var(--border)',
color: 'var(--text-secondary)'
}}
>
Cancel
</button>
</div>
</div>
</div>
)}
{/* Deck Modal */}
{showDeckModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 max-w-md w-full mx-4" style={{ backgroundColor: 'var(--bg-primary)' }}>
<h3 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
Add to Deck
</h3>
<div className="mb-6">
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
Select Deck
</label>
<select
value={selectedDeck}
onChange={(e) => setSelectedDeck(e.target.value)}
className="w-full p-3 rounded-xl border transition-all duration-200"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
<option value="">Choose a deck...</option>
{decks
.filter(deck => deck.game === card.game)
.map(deck => (
<option key={deck.id} value={deck.id}>
{deck.name}
</option>
))}
</select>
</div>
<div className="flex gap-3">
<button
onClick={handleAddToDeck}
disabled={!selectedDeck}
className={`flex-1 px-4 py-2 rounded-xl font-medium transition-all duration-200 ${
selectedDeck
? 'gradient-bg-purple text-white hover:shadow-lg'
: 'opacity-50 cursor-not-allowed'
}`}
>
Add to Deck
</button>
<button
onClick={() => {
setShowDeckModal(false);
setSelectedDeck('');
}}
className="flex-1 px-4 py-2 rounded-xl font-medium border transition-all duration-200"
style={{
borderColor: 'var(--border)',
color: 'var(--text-secondary)'
}}
>
Cancel
</button>
</div>
</div>
</div>
)}
</Layout>
);
}