diff --git a/components/BulkSelectionToolbar.js b/components/BulkSelectionToolbar.js new file mode 100644 index 0000000..b2cbf10 --- /dev/null +++ b/components/BulkSelectionToolbar.js @@ -0,0 +1,164 @@ +import { useState } from 'react'; + +export default function BulkSelectionToolbar({ + selectedCards, + onClearSelection, + onAddToCollection, + onAddToDeck, + onMarkAsOwned, + onRemoveFromOwned, + onBulkFavorite, + onBulkDelete +}) { + const [showActions, setShowActions] = useState(false); + const selectedCount = selectedCards.length; + + if (selectedCount === 0) return null; + + const handleAddToCollection = () => { + onAddToCollection(selectedCards); + }; + + const handleAddToDeck = () => { + onAddToDeck(selectedCards); + }; + + const handleMarkAsOwned = () => { + onMarkAsOwned(selectedCards); + }; + + const handleRemoveFromOwned = () => { + onRemoveFromOwned(selectedCards); + }; + + const handleBulkFavorite = () => { + onBulkFavorite(selectedCards); + }; + + const handleBulkDelete = () => { + if (confirm(`Are you sure you want to delete ${selectedCount} selected cards?`)) { + onBulkDelete(selectedCards); + } + }; + + return ( +
+
+ {/* Selection Count */} +
+
+ {selectedCount} +
+ + {selectedCount} card{selectedCount !== 1 ? 's' : ''} selected + +
+ + {/* Divider */} +
+ + {/* Quick Actions */} +
+ + + + + +
+ + {/* More Actions Button */} +
+ + + {/* Dropdown Actions */} + {showActions && ( +
+ + + + +
+ + +
+ )} +
+ + {/* Clear Selection */} + +
+
+ ); +} \ No newline at end of file diff --git a/components/CardItem.js b/components/CardItem.js new file mode 100644 index 0000000..fc484b5 --- /dev/null +++ b/components/CardItem.js @@ -0,0 +1,237 @@ +import { useState } from 'react'; + +export default function CardItem({ + card, + viewMode = 'grid', + isSelected = false, + onToggleSelect, + onAddToCollection, + onAddToDeck, + onToggleFavorite, + isFavorited = false +}) { + const [imageError, setImageError] = useState(false); + + const handleSelectChange = (e) => { + e.stopPropagation(); + onToggleSelect(card); + }; + + const handleImageError = () => { + setImageError(true); + }; + + if (viewMode === 'list') { + return ( +
+ {/* Selection Checkbox */} +
+ +
+ + {/* Card Image */} +
+
+ {!imageError ? ( + {card.name} + ) : ( +
+ No Image +
+ )} +
+
+ + {/* Card Info */} +
+

{card.name}

+

{card.set_name} • {card.rarity}

+

{card.card_type}

+
+ + {/* Game Badge */} +
+ + {card.game} + +
+ + {/* Price */} +
+ + ${card.market_price} + +
+ + {/* Actions */} +
+ + + + + +
+
+ ); + } + + // Grid view + return ( +
+ {/* Selection Checkbox */} +
+ +
+ + {/* Favorite Button */} +
+ +
+ + {/* Card Image */} +
+ {!imageError ? ( + {card.name} + ) : ( +
+
+ + + +

No Image

+
+
+ )} +
+ + {/* Card Info */} +
+
+

+ {card.name} +

+ + ${card.market_price} + +
+ +

{card.set_name}

+

{card.rarity} • {card.card_type}

+ + {/* Game Badge */} +
+ + {card.game} + + + {/* Quick Actions */} +
+ + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/pages/cards.js b/pages/cards.js index b1c9d25..f3a43fd 100644 --- a/pages/cards.js +++ b/pages/cards.js @@ -1,6 +1,8 @@ import { useState, useEffect, useRef } from 'react'; import { useRouter } from 'next/router'; import Layout from '../components/Layout'; +import CardItem from '../components/CardItem'; +import BulkSelectionToolbar from '../components/BulkSelectionToolbar'; export default function Cards() { const router = useRouter(); @@ -32,6 +34,10 @@ export default function Cards() { const [hasMore, setHasMore] = useState(true); const loadingMoreRef = useRef(false); const hasMoreRef = useRef(true); + + // Bulk selection state + const [selectedCards, setSelectedCards] = useState([]); + const [favoritedCards, setFavoritedCards] = useState(new Set()); // Fetch cards from database const fetchCards = async (isLoadMore = false) => { @@ -298,7 +304,110 @@ export default function Cards() { return rarityOption ? rarityOption.label : rarity; }; + // Bulk selection handlers + const handleToggleSelect = (card) => { + setSelectedCards(prev => { + const isSelected = prev.some(c => c.id === card.id); + if (isSelected) { + return prev.filter(c => c.id !== card.id); + } else { + return [...prev, card]; + } + }); + }; + const handleClearSelection = () => { + setSelectedCards([]); + }; + + const handleToggleFavorite = async (card) => { + try { + const isFavorited = favoritedCards.has(card.id); + const method = isFavorited ? 'DELETE' : 'POST'; + + const response = await fetch('/api/favorites', { + method, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` + }, + body: JSON.stringify({ + itemType: 'card', + itemId: card.id + }) + }); + + if (response.ok) { + setFavoritedCards(prev => { + const newSet = new Set(prev); + if (isFavorited) { + newSet.delete(card.id); + } else { + newSet.add(card.id); + } + return newSet; + }); + } + } catch (error) { + console.error('Error toggling favorite:', error); + } + }; + + // Bulk action handlers + const handleBulkAddToCollection = (cards) => { + console.log('Adding to collection:', cards); + alert(`Adding ${cards.length} cards to collection (functionality coming soon)`); + }; + + const handleBulkAddToDeck = (cards) => { + console.log('Adding to deck:', cards); + alert(`Adding ${cards.length} cards to deck (functionality coming soon)`); + }; + + const handleBulkMarkAsOwned = (cards) => { + console.log('Marking as owned:', cards); + alert(`Marking ${cards.length} cards as owned (functionality coming soon)`); + }; + + const handleBulkRemoveFromOwned = (cards) => { + console.log('Removing from owned:', cards); + alert(`Removing ${cards.length} cards from owned (functionality coming soon)`); + }; + + const handleBulkFavorite = async (cards) => { + try { + for (const card of cards) { + if (!favoritedCards.has(card.id)) { + await fetch('/api/favorites', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` + }, + body: JSON.stringify({ + itemType: 'card', + itemId: card.id + }) + }); + } + } + + setFavoritedCards(prev => { + const newSet = new Set(prev); + cards.forEach(card => newSet.add(card.id)); + return newSet; + }); + + alert(`Added ${cards.length} cards to favorites`); + } catch (error) { + console.error('Error bulk favoriting:', error); + } + }; + + const handleBulkDelete = (cards) => { + console.log('Bulk delete:', cards); + alert(`Bulk delete functionality coming soon for ${cards.length} cards`); + }; if (loading && cards.length === 0) { return ( @@ -590,18 +699,19 @@ export default function Cards() { ) : ( <> -
+
{cards.map(card => ( -
router.push(`/card/${card.id}`)} className="flex justify-center"> -
- -
-
+ c.id === card.id)} + onToggleSelect={handleToggleSelect} + onAddToCollection={handleBulkAddToCollection} + onAddToDeck={handleBulkAddToDeck} + onToggleFavorite={handleToggleFavorite} + isFavorited={favoritedCards.has(card.id)} + /> ))}
@@ -641,6 +751,18 @@ export default function Cards() { )}
+ + {/* Bulk Selection Toolbar */} + ); }