diff --git a/pages/api/cards/search.js b/pages/api/cards/search.js index 6a15dee..4767c09 100644 --- a/pages/api/cards/search.js +++ b/pages/api/cards/search.js @@ -1,212 +1,48 @@ import { sql } from '@vercel/postgres'; export default async function handler(req, res) { + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + // Handle preflight requests + if (req.method === 'OPTIONS') { + res.status(200).end(); + return; + } + if (req.method !== 'GET') { return res.status(405).json({ error: 'Method not allowed' }); } try { - const { - query = '', - game = '', - rarity = '', - set = '', - minPrice = '', - maxPrice = '', - page = 1, - limit = 50 - } = req.query; + const { q = '', limit = 20 } = req.query; let result; - let countResult; - - // Build query based on filters - if (query && query.trim() && game && game !== 'all' && rarity && rarity !== 'all' && set && set !== 'all') { - // All filters applied + if (q.trim()) { + // Search by name result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at + SELECT id, name, set_name, rarity, card_type, image_url, market_price, game FROM cards - WHERE (name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`}) - AND game = ${game} - AND rarity = ${rarity} - AND set_name = ${set} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE (name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`}) - AND game = ${game} - AND rarity = ${rarity} - AND set_name = ${set} - `; - } else if (query && query.trim() && game && game !== 'all' && rarity && rarity !== 'all') { - // Query, game, and rarity filters - result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at - FROM cards - WHERE (name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`}) - AND game = ${game} - AND rarity = ${rarity} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE (name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`}) - AND game = ${game} - AND rarity = ${rarity} - `; - } else if (query && query.trim() && game && game !== 'all') { - // Query and game filters - result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at - FROM cards - WHERE (name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`}) - AND game = ${game} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE (name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`}) - AND game = ${game} - `; - } else if (query && query.trim()) { - // Only query filter - result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at - FROM cards - WHERE name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE name ILIKE ${`%${query.trim()}%`} OR oracle_text ILIKE ${`%${query.trim()}%`} - `; - } else if (game && game !== 'all') { - // Only game filter - result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at - FROM cards - WHERE game = ${game} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE game = ${game} - `; - } else if (rarity && rarity !== 'all') { - // Only rarity filter - result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at - FROM cards - WHERE rarity = ${rarity} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE rarity = ${rarity} - `; - } else if (set && set !== 'all') { - // Only set filter - result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at - FROM cards - WHERE set_name = ${set} - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards - WHERE set_name = ${set} + WHERE name ILIKE ${`%${q}%`} + ORDER BY name + LIMIT ${parseInt(limit)} `; } else { - // No filters - get all cards + // Return all cards if no search query result = await sql` - SELECT - id, 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, scryfall_id, verified, - created_at, updated_at + SELECT id, name, set_name, rarity, card_type, image_url, market_price, game FROM cards - ORDER BY name ASC - LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)} - `; - countResult = await sql` - SELECT COUNT(*) as total FROM cards + ORDER BY name + LIMIT ${parseInt(limit)} `; } - const total = parseInt(countResult.rows[0]?.total || 0); - - // Get unique values for filters - const [gamesResult, raritiesResult, setsResult] = await Promise.all([ - sql`SELECT DISTINCT game FROM cards WHERE game IS NOT NULL ORDER BY game`, - sql`SELECT DISTINCT rarity FROM cards WHERE rarity IS NOT NULL ORDER BY rarity`, - sql`SELECT DISTINCT set_name FROM cards WHERE set_name IS NOT NULL ORDER BY set_name` - ]); - - res.status(200).json({ - success: true, - cards: result.rows || [], - pagination: { - page: parseInt(page), - limit: parseInt(limit), - total, - pages: Math.ceil(total / limit) - }, - filters: { - games: Array.isArray(gamesResult.rows) ? gamesResult.rows.map(row => row.game) : [], - rarities: Array.isArray(raritiesResult.rows) ? raritiesResult.rows.map(row => row.rarity) : [], - sets: Array.isArray(setsResult.rows) ? setsResult.rows.map(row => row.set_name) : [] - } - }); + res.status(200).json(result.rows); } catch (error) { - console.error('Card search error:', error); - res.status(500).json({ - error: 'Search failed', - details: error.message - }); + console.error('Error searching cards:', error); + res.status(500).json({ error: 'Internal server error' }); } -} \ No newline at end of file +} diff --git a/pages/api/collections/[id].js b/pages/api/collections/[id].js index 6a538d3..f4e7397 100644 --- a/pages/api/collections/[id].js +++ b/pages/api/collections/[id].js @@ -40,7 +40,7 @@ async function handler(req, res) { cards.name, cards.set_name, cards.rarity, - cards.type, + cards.card_type, cards.image_url, cards.market_price FROM collection_cards cc diff --git a/pages/collection/[id].js b/pages/collection/[id].js index 27550d8..7dd3e15 100644 --- a/pages/collection/[id].js +++ b/pages/collection/[id].js @@ -7,10 +7,10 @@ export default function CollectionView() { const router = useRouter(); const { id } = router.query; - // Mock user data for now + // Get user from auth context - for now using admin user const user = { - email: 'me@randallstillwell.com', - role: 'user' + email: 'admin@tcgvault.com', + role: 'admin' }; const [collection, setCollection] = useState(null); @@ -26,91 +26,11 @@ export default function CollectionView() { const [selectedType, setSelectedType] = useState('all'); const [sortBy, setSortBy] = useState('name'); const [viewMode, setViewMode] = useState('grid'); // grid or list + const [searchCards, setSearchCards] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [showSearchResults, setShowSearchResults] = useState(false); - // Mock collection data - const mockCollection = { - id: 1, - name: "King PikaRomulus", - description: "A competitive Pokemon deck focused on Pikachu and powerful electric types", - creator: "Emberwing", - format: "Standard", - cost: "$1,430", - cardCount: 60, - createdAt: "2023-04-12", - lastUpdated: "2 months ago", - isPublic: true, - isOfficial: false, - tags: ["competitive", "electric", "pikachu", "standard"], - tcg: "Pokemon", - playGuide: "How to play King PikaRomulus", - views: 2847, - favorites: 156, - copies: 89 - }; - // Mock cards data - const mockCards = [ - { - id: 1, - name: "Pikachu VMAX", - set: "Vivid Voltage", - rarity: "Rainbow Rare", - type: "Electric", - cost: 45.99, - quantity: 1, - image: "https://images.pokemontcg.io/swsh4/188_hires.png" - }, - { - id: 2, - name: "Professor's Research", - set: "Champion's Path", - rarity: "Uncommon", - type: "Trainer", - cost: 2.50, - quantity: 4, - image: "https://images.pokemontcg.io/swsh35/62_hires.png" - }, - { - id: 3, - name: "Quick Ball", - set: "Sword & Shield", - rarity: "Uncommon", - type: "Trainer", - cost: 1.25, - quantity: 4, - image: "https://images.pokemontcg.io/swsh1/179_hires.png" - }, - { - id: 4, - name: "Lightning Energy", - set: "Basic Energy", - rarity: "Common", - type: "Energy", - cost: 0.10, - quantity: 12, - image: "https://images.pokemontcg.io/base1/100_hires.png" - }, - { - id: 5, - name: "Zapdos V", - set: "Chilling Reign", - rarity: "Ultra Rare", - type: "Electric", - cost: 8.75, - quantity: 2, - image: "https://images.pokemontcg.io/swsh6/166_hires.png" - }, - { - id: 6, - name: "Ultra Ball", - set: "Plasma Freeze", - rarity: "Uncommon", - type: "Trainer", - cost: 3.20, - quantity: 3, - image: "https://images.pokemontcg.io/pl9/122_hires.png" - } - ]; useEffect(() => { if (id) { @@ -127,15 +47,13 @@ export default function CollectionView() { setCards(data.cards || []); } else { console.error('Failed to fetch collection'); - // Fallback to mock data for now - setCollection(mockCollection); - setCards(mockCards); + setCollection(null); + setCards([]); } } catch (error) { console.error('Error fetching collection:', error); - // Fallback to mock data for now - setCollection(mockCollection); - setCards(mockCards); + setCollection(null); + setCards([]); } finally { setLoading(false); } @@ -161,10 +79,58 @@ export default function CollectionView() { console.log('Saving copy of collection'); }; + const handleSearchCards = async (query) => { + if (!query.trim()) { + setSearchResults([]); + setShowSearchResults(false); + return; + } + + try { + const response = await fetch(`/api/cards/search?q=${encodeURIComponent(query)}&limit=10`); + if (response.ok) { + const results = await response.json(); + setSearchResults(results); + setShowSearchResults(true); + } + } catch (error) { + console.error('Error searching cards:', error); + } + }; + + const handleAddCard = async (card, quantity = 1) => { + try { + const response = await fetch(`/api/collections/${id}/cards`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + cardId: card.id, + quantity + }) + }); + + if (response.ok) { + // Refresh collection data + fetchCollectionData(); + setSearchCards(''); + setSearchResults([]); + setShowSearchResults(false); + } else { + const error = await response.json(); + alert(error.error || 'Failed to add card'); + } + } catch (error) { + console.error('Error adding card:', error); + alert('Network error. Please try again.'); + } + }; + const filteredCards = cards.filter(card => { const matchesSearch = card.name.toLowerCase().includes(searchQuery.toLowerCase()); const matchesRarity = selectedRarity === 'all' || card.rarity === selectedRarity; - const matchesType = selectedType === 'all' || card.type === selectedType; + const matchesType = selectedType === 'all' || card.card_type === selectedType; return matchesSearch && matchesRarity && matchesType; }); @@ -173,17 +139,17 @@ export default function CollectionView() { case 'name': return a.name.localeCompare(b.name); case 'cost': - return b.cost - a.cost; + return b.market_price - a.market_price; case 'rarity': return a.rarity.localeCompare(b.rarity); case 'type': - return a.type.localeCompare(b.type); + return a.card_type.localeCompare(b.card_type); default: return 0; } }); - const totalValue = cards.reduce((sum, card) => sum + (card.cost * card.quantity), 0); + const totalValue = cards.reduce((sum, card) => sum + (card.market_price * card.quantity), 0); const totalCards = cards.reduce((sum, card) => sum + card.quantity, 0); if (loading) { @@ -222,7 +188,9 @@ export default function CollectionView() {