- Redesigned card display with 2.5:3.5 aspect ratio and image-only view - Added infinite scroll to replace pagination - Implemented authentic card back placeholders for MTG, Pokemon, and Lorcana - Added rarity-based particle effects with tiered intensity (mythic/enchanted/rare/uncommon) - Enhanced hover details panel with structured card information - Fixed search functionality with debouncing and Enter key support - Improved filter system with working TCG, rarity, set, and price filters - Added favorite system for cards in both hover and detail views - Updated card detail page with comprehensive metadata and actions - Fixed API filtering with proper Vercel Postgres implementation - Added particle animations and rarity glow effects - Improved overall UX with better visual hierarchy and interactions
212 lines
No EOL
7.7 KiB
JavaScript
212 lines
No EOL
7.7 KiB
JavaScript
import { sql } from '@vercel/postgres';
|
|
|
|
export default async function handler(req, res) {
|
|
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;
|
|
|
|
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
|
|
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}
|
|
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}
|
|
`;
|
|
} else {
|
|
// No filters - get all cards
|
|
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
|
|
ORDER BY name ASC
|
|
LIMIT ${parseInt(limit)} OFFSET ${(parseInt(page) - 1) * parseInt(limit)}
|
|
`;
|
|
countResult = await sql`
|
|
SELECT COUNT(*) as total FROM cards
|
|
`;
|
|
}
|
|
|
|
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) : []
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Card search error:', error);
|
|
res.status(500).json({
|
|
error: 'Search failed',
|
|
details: error.message
|
|
});
|
|
}
|
|
}
|