Add user collection management and card face toggling in CardSearch and Collection components

This commit is contained in:
Matthieu
2025-11-21 15:14:37 +01:00
parent 247f2205b8
commit 8bb80dac2e
4 changed files with 419 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Plus, Search, Save, Trash2, Loader2, CheckCircle, XCircle, AlertCircle, PackagePlus } from 'lucide-react';
import { Plus, Search, Save, Trash2, Loader2, CheckCircle, XCircle, AlertCircle, PackagePlus, RefreshCw } from 'lucide-react';
import { Card, Deck } from '../types';
import { searchCards, getUserCollection, addCardToCollection, addMultipleCardsToCollection } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
@@ -120,6 +120,7 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
const [isLoadingCollection, setIsLoadingCollection] = useState(true);
const [addingCardId, setAddingCardId] = useState<string | null>(null);
const [isAddingAll, setIsAddingAll] = useState(false);
const [cardFaceIndex, setCardFaceIndex] = useState<Map<string, number>>(new Map());
// Load user collection on component mount
useEffect(() => {
@@ -141,6 +142,33 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
loadUserCollection();
}, [user]);
// Helper functions for double-faced cards
const isDoubleFaced = (card: Card) => {
const backFaceLayouts = ['transform', 'modal_dfc', 'double_faced_token', 'reversible_card'];
return card.card_faces && card.card_faces.length > 1 && backFaceLayouts.includes(card.layout);
};
const getCurrentFaceIndex = (cardId: string) => {
return cardFaceIndex.get(cardId) || 0;
};
const toggleCardFace = (cardId: string, totalFaces: number) => {
setCardFaceIndex(prev => {
const newMap = new Map(prev);
const currentIndex = prev.get(cardId) || 0;
const nextIndex = (currentIndex + 1) % totalFaces;
newMap.set(cardId, nextIndex);
return newMap;
});
};
const getCardImageUri = (card: Card, faceIndex: number = 0) => {
if (isDoubleFaced(card) && card.card_faces) {
return card.card_faces[faceIndex]?.image_uris?.normal || card.card_faces[faceIndex]?.image_uris?.small;
}
return card.image_uris?.normal || card.image_uris?.small || card.card_faces?.[0]?.image_uris?.normal;
};
// Helper function to check if a card is in the collection
const isCardInCollection = (cardId: string, requiredQuantity: number = 1): boolean => {
const ownedQuantity = userCollection.get(cardId) || 0;
@@ -491,24 +519,82 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) {
</form>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{searchResults.map(card => (
<div
key={card.id}
className="bg-gray-800 rounded-lg overflow-hidden hover:ring-2 hover:ring-blue-500 transition-all"
>
<MagicCard card={card} />
<div className="p-4">
<h3 className="font-bold mb-2">{card.name}</h3>
<button
onClick={() => addCardToDeck(card)}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg flex items-center justify-center gap-2"
>
<Plus size={20} />
Add to Deck
</button>
{searchResults.map(card => {
const currentFaceIndex = getCurrentFaceIndex(card.id);
const isMultiFaced = isDoubleFaced(card);
const inCollection = userCollection.get(card.id) || 0;
const isAddingThisCard = addingCardId === card.id;
const displayName = isMultiFaced && card.card_faces
? card.card_faces[currentFaceIndex]?.name || card.name
: card.name;
return (
<div
key={card.id}
className="bg-gray-800 rounded-lg overflow-hidden hover:ring-2 hover:ring-blue-500 transition-all"
>
<div className="relative">
{getCardImageUri(card, currentFaceIndex) ? (
<img
src={getCardImageUri(card, currentFaceIndex)}
alt={displayName}
className="w-full h-auto"
/>
) : (
<MagicCard card={card} />
)}
{isMultiFaced && (
<button
onClick={(e) => {
e.stopPropagation();
toggleCardFace(card.id, card.card_faces!.length);
}}
className="absolute bottom-2 right-2 bg-purple-600 hover:bg-purple-700 text-white p-2 rounded-full shadow-lg transition-all"
title="Flip card"
>
<RefreshCw size={16} />
</button>
)}
</div>
<div className="p-4">
<div className="flex items-center justify-between mb-2">
<h3 className="font-bold">{displayName}</h3>
{inCollection > 0 && (
<span className="text-xs bg-green-600 px-2 py-0.5 rounded-full flex items-center gap-1">
<CheckCircle size={12} />
x{inCollection}
</span>
)}
</div>
{card.prices?.usd && (
<div className="text-sm text-gray-400 mb-2">${card.prices.usd}</div>
)}
<div className="flex gap-2">
<button
onClick={() => addCardToDeck(card)}
className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg flex items-center justify-center gap-2"
>
<Plus size={20} />
Add to Deck
</button>
<button
onClick={() => handleAddCardToCollection(card.id, 1)}
disabled={isAddingThisCard}
className="px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg flex items-center justify-center gap-2"
title="Add to collection"
>
{isAddingThisCard ? (
<Loader2 className="animate-spin" size={20} />
) : (
<PackagePlus size={20} />
)}
</button>
</div>
</div>
</div>
</div>
))}
);
})}
</div>
</div>