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 ? (
+

+ ) : (
+
+ 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 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 */}
+
);
}