diff --git a/pages/admin/card-editor.js b/pages/admin/card-editor.js new file mode 100644 index 0000000..3f393ad --- /dev/null +++ b/pages/admin/card-editor.js @@ -0,0 +1,753 @@ +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import Layout from '../../components/Layout'; + +export default function CardEditor() { + const router = useRouter(); + const { id } = router.query; + + const user = { + email: 'admin@tcgvault.com', + role: 'admin' + }; + + const [card, setCard] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [message, setMessage] = useState(''); + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [searchLoading, setSearchLoading] = useState(false); + + // Form state + const [formData, setFormData] = useState({ + name: '', + set_name: '', + set_code: '', + card_number: '', + rarity: '', + game: '', + mana_cost: '', + cmc: '', + card_type: '', + colors: [], + oracle_text: '', + power: '', + toughness: '', + image_url: '', + stock_image_url: '', + current_price: '', + market_price: '' + }); + + // Load card data if ID is provided + useEffect(() => { + if (id) { + fetchCard(id); + } else { + setLoading(false); + } + }, [id]); + + const fetchCard = async (cardId) => { + try { + const response = await fetch(`/api/cards/${cardId}`); + if (response.ok) { + const cardData = await response.json(); + setCard(cardData); + setFormData({ + name: cardData.name || '', + set_name: cardData.set_name || '', + set_code: cardData.set_code || '', + card_number: cardData.card_number || '', + rarity: cardData.rarity || '', + game: cardData.game || '', + mana_cost: cardData.mana_cost || '', + cmc: cardData.cmc || '', + card_type: cardData.card_type || '', + colors: Array.isArray(cardData.colors) ? cardData.colors : [], + oracle_text: cardData.oracle_text || '', + power: cardData.power || '', + toughness: cardData.toughness || '', + image_url: cardData.image_url || '', + stock_image_url: cardData.stock_image_url || '', + current_price: cardData.current_price || '', + market_price: cardData.market_price || '' + }); + } else { + setMessage('Card not found'); + } + } catch (error) { + setMessage('Error loading card: ' + error.message); + } finally { + setLoading(false); + } + }; + + const searchCards = async (query) => { + if (!query.trim()) { + setSearchResults([]); + return; + } + + setSearchLoading(true); + try { + const response = await fetch(`/api/cards/search?query=${encodeURIComponent(query)}&limit=20`); + if (response.ok) { + const data = await response.json(); + setSearchResults(data.cards || []); + } + } catch (error) { + console.error('Search error:', error); + } finally { + setSearchLoading(false); + } + }; + + const handleInputChange = (field, value) => { + setFormData(prev => ({ + ...prev, + [field]: value + })); + }; + + const handleColorChange = (color, checked) => { + setFormData(prev => ({ + ...prev, + colors: checked + ? [...prev.colors, color] + : prev.colors.filter(c => c !== color) + })); + }; + + const handleSave = async () => { + if (!card?.id) { + setMessage('No card selected to edit'); + return; + } + + setSaving(true); + setMessage(''); + + try { + const response = await fetch(`/api/cards/${card.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...formData, + cmc: formData.cmc ? parseInt(formData.cmc) : null, + current_price: formData.current_price ? parseFloat(formData.current_price) : null, + market_price: formData.market_price ? parseFloat(formData.market_price) : null + }) + }); + + if (response.ok) { + const updatedCard = await response.json(); + setCard(updatedCard); + setMessage('Card updated successfully!'); + + // Clear message after 3 seconds + setTimeout(() => setMessage(''), 3000); + } else { + const errorData = await response.json(); + setMessage('Error updating card: ' + (errorData.error || 'Unknown error')); + } + } catch (error) { + setMessage('Error updating card: ' + error.message); + } finally { + setSaving(false); + } + }; + + const selectCard = (selectedCard) => { + router.push(`/admin/card-editor?id=${selectedCard.id}`); + }; + + const rarityOptions = [ + 'common', 'uncommon', 'rare', 'mythic', 'legendary', + 'holographic', 'enchanted', 'super rare', 'ultra rare' + ]; + + const gameOptions = ['MTG', 'Pokemon', 'Lorcana']; + + const colorOptions = ['White', 'Blue', 'Black', 'Red', 'Green', 'Colorless']; + + if (loading) { + return ( + +
+
+
+
+ ); + } + + return ( + +
+ {/* Admin Navigation */} +
+
+

+ Admin Tools +

+
+ + +
+
+
+ +
+

+ Admin Card Editor +

+

+ Search for a card to edit its details, or continue editing the selected card. +

+
+ + {/* Card Search */} +
+

+ Find Card to Edit +

+ +
+
+ { + setSearchQuery(e.target.value); + searchCards(e.target.value); + }} + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + {/* Search Results */} + {searchLoading && ( +
+
+
+ )} + + {searchResults.length > 0 && ( +
+ {searchResults.map((searchCard) => ( +
selectCard(searchCard)} + className="p-4 rounded-lg border cursor-pointer transition-all duration-200 hover:shadow-lg hover:scale-105" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)' + }} + > +
+
+ {searchCard.image_url ? ( + {searchCard.name} + ) : ( +
+ {searchCard.game} +
+ )} +
+
+

+ {searchCard.name} +

+

+ {searchCard.set_name} • {searchCard.game} +

+

+ {searchCard.rarity} +

+
+
+
+ ))} +
+ )} +
+ + {/* Card Editor Form */} + {card && ( +
+ {/* Card Preview */} +
+
+

+ Card Preview +

+
+
+
+ {formData.image_url ? ( + {formData.name} { + e.target.style.display = 'none'; + e.target.nextSibling.style.display = 'flex'; + }} + /> + ) : null} +
+
+
🃏
+
+ {formData.name || 'Card Name'} +
+
+ {formData.set_name || 'Set Name'} +
+
+
+
+
+ +
+

+ {formData.name || 'Card Name'} +

+

+ {formData.set_name} • {formData.game} +

+

+ {formData.rarity} • {formData.card_type} +

+ {formData.current_price && ( +

+ ${parseFloat(formData.current_price).toFixed(2)} +

+ )} +
+
+
+
+ + {/* Editor Form */} +
+
+

+ Edit Card Details +

+ +
+ + {message && ( +
+ {message} +
+ )} + +
+ {/* Basic Information */} +
+

+ Basic Information +

+
+
+ + handleInputChange('name', e.target.value)} + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + +
+
+ + handleInputChange('set_name', e.target.value)} + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + handleInputChange('set_code', e.target.value)} + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + handleInputChange('card_number', e.target.value)} + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + +
+
+
+ + {/* Game Mechanics */} +
+

+ Game Mechanics +

+
+
+ + handleInputChange('card_type', e.target.value)} + placeholder="e.g., Creature, Instant, Pokemon, Character" + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + handleInputChange('mana_cost', e.target.value)} + placeholder="e.g., {2}{U}{U}, 3, etc." + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + handleInputChange('cmc', e.target.value)} + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + handleInputChange('power', e.target.value)} + placeholder="e.g., 2, *, X" + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + handleInputChange('toughness', e.target.value)} + placeholder="e.g., 2, *, X" + className="w-full p-3 rounded-lg border transition-all duration-200" + style={{ + backgroundColor: 'var(--bg-primary)', + borderColor: 'var(--border)', + color: 'var(--text-primary)' + }} + /> +
+
+ + {/* Colors */} +
+ +
+ {colorOptions.map(color => ( + + ))} +
+
+
+ + {/* Card Text */} +
+

+ Card Text +

+
+ +