From 6d8e7b322402745da2a080570ef0c01e2db6fbf0 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 21 Nov 2025 15:19:34 +0100 Subject: [PATCH] Implement card quantity management in collection with increment and decrement functionality --- src/components/Collection.tsx | 204 +++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 5 deletions(-) diff --git a/src/components/Collection.tsx b/src/components/Collection.tsx index fd12af0..2b882c2 100644 --- a/src/components/Collection.tsx +++ b/src/components/Collection.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; -import { Search, Loader2, Trash2, CheckCircle, XCircle, RefreshCw } from 'lucide-react'; +import { Search, Loader2, Trash2, CheckCircle, XCircle, RefreshCw, Plus, Minus, X } from 'lucide-react'; import { Card } from '../types'; -import { getUserCollection, getCardsByIds } from '../services/api'; +import { getUserCollection, getCardsByIds, addCardToCollection } from '../services/api'; import { useAuth } from '../contexts/AuthContext'; +import { supabase } from '../lib/supabase'; export default function Collection() { const { user } = useAuth(); @@ -11,8 +12,10 @@ export default function Collection() { const [filteredCollection, setFilteredCollection] = useState<{ card: Card; quantity: number }[]>([]); const [isLoadingCollection, setIsLoadingCollection] = useState(true); const [hoveredCard, setHoveredCard] = useState(null); + const [selectedCard, setSelectedCard] = useState<{ card: Card; quantity: number } | null>(null); const [cardFaceIndex, setCardFaceIndex] = useState>(new Map()); const [snackbar, setSnackbar] = useState<{ message: string; type: 'success' | 'error' } | null>(null); + const [isUpdating, setIsUpdating] = useState(false); // Helper function to check if a card has an actual back face (not adventure/split/etc) const isDoubleFaced = (card: Card) => { @@ -114,6 +117,71 @@ export default function Collection() { setFilteredCollection(filtered); }, [searchQuery, collection]); + // Update card quantity in collection + const updateCardQuantity = async (cardId: string, newQuantity: number) => { + if (!user || newQuantity < 0) return; + + try { + setIsUpdating(true); + + if (newQuantity === 0) { + // Remove card from collection + const { error } = await supabase + .from('collections') + .delete() + .eq('user_id', user.id) + .eq('card_id', cardId); + + if (error) throw error; + + // Update local state + setCollection(prev => prev.filter(item => item.card.id !== cardId)); + setSelectedCard(null); + setSnackbar({ message: 'Card removed from collection', type: 'success' }); + } else { + // Update quantity + const { error } = await supabase + .from('collections') + .update({ quantity: newQuantity, updated_at: new Date().toISOString() }) + .eq('user_id', user.id) + .eq('card_id', cardId); + + if (error) throw error; + + // Update local state + setCollection(prev => + prev.map(item => + item.card.id === cardId ? { ...item, quantity: newQuantity } : item + ) + ); + + if (selectedCard && selectedCard.card.id === cardId) { + setSelectedCard({ ...selectedCard, quantity: newQuantity }); + } + + setSnackbar({ message: 'Quantity updated', type: 'success' }); + } + } catch (error) { + console.error('Error updating card quantity:', error); + setSnackbar({ message: 'Failed to update quantity', type: 'error' }); + } finally { + setIsUpdating(false); + setTimeout(() => setSnackbar(null), 3000); + } + }; + + // Add one to quantity + const incrementQuantity = async (cardId: string, currentQuantity: number) => { + await updateCardQuantity(cardId, currentQuantity + 1); + }; + + // Remove one from quantity + const decrementQuantity = async (cardId: string, currentQuantity: number) => { + if (currentQuantity > 0) { + await updateCardQuantity(cardId, currentQuantity - 1); + } + }; + return (
@@ -168,6 +236,7 @@ export default function Collection() { className="relative group cursor-pointer" onMouseEnter={() => setHoveredCard(card)} onMouseLeave={() => setHoveredCard(null)} + onClick={() => setSelectedCard({ card, quantity })} > {/* Small card thumbnail */}
@@ -207,8 +276,8 @@ export default function Collection() {
- {/* Hover Card Preview */} - {hoveredCard && (() => { + {/* Hover Card Preview - only show if no card is selected */} + {hoveredCard && !selectedCard && (() => { const currentFaceIndex = getCurrentFaceIndex(hoveredCard.id); const isMultiFaced = isDoubleFaced(hoveredCard); const currentFace = isMultiFaced && hoveredCard.card_faces @@ -220,7 +289,7 @@ export default function Collection() { const displayOracleText = currentFace?.oracle_text || hoveredCard.oracle_text; return ( -
+
{ + const currentFaceIndex = getCurrentFaceIndex(selectedCard.card.id); + const isMultiFaced = isDoubleFaced(selectedCard.card); + const currentFace = isMultiFaced && selectedCard.card.card_faces + ? selectedCard.card.card_faces[currentFaceIndex] + : null; + + const displayName = currentFace?.name || selectedCard.card.name; + const displayTypeLine = currentFace?.type_line || selectedCard.card.type_line; + const displayOracleText = currentFace?.oracle_text || selectedCard.card.oracle_text; + + return ( + <> + {/* Backdrop */} +
setSelectedCard(null)} + /> + + {/* Sliding Panel */} +
+
+ {/* Close button */} + + + {/* Card Image */} +
+ {displayName} + {isMultiFaced && ( + <> +
+ Face {currentFaceIndex + 1}/{selectedCard.card.card_faces!.length} +
+ + + )} +
+ + {/* Card Info */} +
+
+

{displayName}

+

{displayTypeLine}

+
+ + {displayOracleText && ( +
+

{displayOracleText}

+
+ )} + + {selectedCard.card.prices?.usd && ( +
+
+ ${selectedCard.card.prices.usd} each +
+
+ Total value: ${(parseFloat(selectedCard.card.prices.usd) * selectedCard.quantity).toFixed(2)} +
+
+ )} + + {/* Quantity Management */} +
+

Quantity in Collection

+
+ + +
+
{selectedCard.quantity}
+
copies
+
+ + +
+ + {/* Remove from collection button */} + +
+
+
+
+ + ); + })()} + {/* Snackbar */} {snackbar && (