From 4e71e299222612b840219586f9801ae5ec80468f Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Tue, 22 Jul 2025 19:51:38 -0500 Subject: [PATCH] Complete CRUD implementation for Cards, Collections, and Decks with comprehensive data modeling and API services --- src/components/CardImageDisplay.tsx | 2 +- src/components/cards/CardManager.tsx | 365 ++++++++++ src/components/cards/CardSearch.tsx | 279 ++++++++ .../collections/CollectionManager.tsx | 356 ++++++++++ src/components/decks/DeckManager.tsx | 408 +++++++++++ src/components/tags/TagManager.tsx | 222 ++++++ src/pages/Cards.tsx | 637 ++++++++++-------- src/pages/Collections.tsx | 322 ++++++++- src/pages/Decks.tsx | 374 +++++++++- src/services/tcgApi.ts | 410 +++++++++++ src/types/index.ts | 295 ++++++++ 11 files changed, 3356 insertions(+), 314 deletions(-) create mode 100644 src/components/cards/CardManager.tsx create mode 100644 src/components/cards/CardSearch.tsx create mode 100644 src/components/collections/CollectionManager.tsx create mode 100644 src/components/decks/DeckManager.tsx create mode 100644 src/components/tags/TagManager.tsx create mode 100644 src/services/tcgApi.ts create mode 100644 src/types/index.ts diff --git a/src/components/CardImageDisplay.tsx b/src/components/CardImageDisplay.tsx index 43e139a..904e538 100644 --- a/src/components/CardImageDisplay.tsx +++ b/src/components/CardImageDisplay.tsx @@ -3,7 +3,7 @@ import { use3DTilt } from '../hooks/use3DTilt'; interface CardImageDisplayProps { card: { - id: number; + id: string; name: string; game: string; stock_image_url?: string; diff --git a/src/components/cards/CardManager.tsx b/src/components/cards/CardManager.tsx new file mode 100644 index 0000000..ca340ea --- /dev/null +++ b/src/components/cards/CardManager.tsx @@ -0,0 +1,365 @@ +import React, { useState, useEffect } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { tcgApi } from '../../services/tcgApi'; + +import type { Card, CreateCardData } from '../../types'; + +interface CardManagerProps { + isOpen: boolean; + onClose: () => void; + cardId?: string; // For editing existing card + initialCardData?: Card; // For adding card from database +} + +const CardManager: React.FC = ({ + isOpen, + onClose, + cardId, + initialCardData +}) => { + + const queryClient = useQueryClient(); + + const [formData, setFormData] = useState({ + cardId: initialCardData?.id || '', + status: 'owned', + quantity: 1, + condition: 'near_mint', + notes: '', + tags: [], + collectionIds: [], + deckIds: [], + }); + + const [selectedTags, setSelectedTags] = useState([]); + const [selectedCollections, setSelectedCollections] = useState([]); + + + // Queries + const { data: existingCard } = useQuery({ + queryKey: ['user-card', cardId], + queryFn: () => tcgApi.cards.getUserCard(cardId!), + enabled: !!cardId, + }); + + const { data: cardInfo } = useQuery({ + queryKey: ['card-info', formData.cardId], + queryFn: () => tcgApi.cards.getCard(formData.cardId), + enabled: !!formData.cardId, + }); + + const { data: tags = [] } = useQuery({ + queryKey: ['tags'], + queryFn: () => tcgApi.tags.getTags(), + }); + + const { data: collections = [] } = useQuery({ + queryKey: ['collections'], + queryFn: () => tcgApi.collections.getCollections(), + }); + + // Mutations + const addCardMutation = useMutation({ + mutationFn: tcgApi.cards.addCard, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['user-cards'] }); + queryClient.invalidateQueries({ queryKey: ['collections'] }); + onClose(); + }, + }); + + const updateCardMutation = useMutation({ + mutationFn: ({ id, data }: { id: string; data: Partial }) => + tcgApi.cards.updateCard(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['user-cards'] }); + queryClient.invalidateQueries({ queryKey: ['user-card', cardId] }); + onClose(); + }, + }); + + // Initialize form with existing card data + useEffect(() => { + if (existingCard) { + setFormData({ + cardId: existingCard.cardId, + status: existingCard.status, + quantity: existingCard.quantity, + condition: existingCard.condition || 'near_mint', + notes: existingCard.notes || '', + tags: existingCard.tags, + collectionIds: existingCard.collectionIds, + }); + setSelectedTags(existingCard.tags); + setSelectedCollections(existingCard.collectionIds); + } + }, [existingCard]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const submitData = { + ...formData, + tags: selectedTags, + collectionIds: selectedCollections, + }; + + if (cardId && existingCard) { + updateCardMutation.mutate({ id: cardId, data: submitData }); + } else { + addCardMutation.mutate(submitData); + } + }; + + + + if (!isOpen) return null; + + return ( +
+
+
+
+ + {/* Header */} +
+

+ {cardId ? 'Edit Card' : 'Add Card'} +

+ +
+ +
+ {/* Card Preview */} + {cardInfo && ( +
+
+
+ {cardInfo.image_url ? ( + {cardInfo.name} + ) : ( + + + + )} +
+
+

{cardInfo.name}

+

{cardInfo.set_name}

+
+ + {cardInfo.game} + + + {cardInfo.rarity} + +
+
+
+
+ )} + +
e.stopPropagation()}> + {/* Status Toggle */} +
+ +
+ + +
+
+ + {/* Quantity */} +
+ +
+ + + {formData.quantity} + + +
+
+ + {/* Condition */} + {formData.status === 'owned' && ( +
+ + +
+ )} + + {/* Tags */} +
+ +
+ {tags.map((tag) => ( + + ))} +
+
+ + {/* Collections */} +
+ +
+ {collections.map((collection) => ( + + ))} +
+
+ + {/* Notes */} +
+ +