diff --git a/COLLECTION_API.md b/COLLECTION_API.md deleted file mode 100644 index f332871..0000000 --- a/COLLECTION_API.md +++ /dev/null @@ -1,483 +0,0 @@ -# Collection API Documentation - -## Overview - -This document describes the backend API implementation for managing user card collections in the Deckerr application. The implementation uses Supabase as the backend service with Row Level Security (RLS) enabled to ensure users can only access their own collection data. - -## Architecture - -### Database Schema - -The collection feature uses the existing `collections` table with the following structure: - -```sql -CREATE TABLE public.collections ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - user_id uuid REFERENCES public.profiles(id) NOT NULL, - card_id text NOT NULL, - quantity integer DEFAULT 1, - created_at timestamptz DEFAULT now(), - updated_at timestamptz DEFAULT now() -); -``` - -### Security - -- **Authentication**: All endpoints require the user to be authenticated via Supabase Auth -- **Authorization**: Row Level Security (RLS) policies ensure users can only access their own collections -- **Validation**: Input validation is performed on all operations to prevent invalid data - -## API Service - -### Location - -- **Service File**: `/home/node/projects/deckerr/src/services/collectionService.ts` -- **Hook File**: `/home/node/projects/deckerr/src/hooks/useCollection.ts` - -### Core Functions - -#### 1. getUserCollection() - -Get all cards in the authenticated user's collection. - -**Returns**: `Promise` - -**Example**: -```typescript -import { getUserCollection } from '../services/collectionService'; - -const collection = await getUserCollection(); -// Returns: [{ id, user_id, card_id, quantity, created_at, updated_at }, ...] -``` - ---- - -#### 2. getCardInCollection(cardId: string) - -Check if a single card exists in the user's collection. - -**Parameters**: -- `cardId` (string): The Scryfall card ID - -**Returns**: `Promise` - -**Example**: -```typescript -import { getCardInCollection } from '../services/collectionService'; - -const card = await getCardInCollection('card-uuid-123'); -// Returns: { id, user_id, card_id, quantity, created_at, updated_at } or null -``` - ---- - -#### 3. checkCardsOwnership(cardIds: string[]) - -Check ownership status for multiple cards at once. - -**Parameters**: -- `cardIds` (string[]): Array of Scryfall card IDs - -**Returns**: `Promise>` - -**Example**: -```typescript -import { checkCardsOwnership } from '../services/collectionService'; - -const ownership = await checkCardsOwnership(['card-1', 'card-2', 'card-3']); -// Returns: Map { 'card-1' => 4, 'card-2' => 2, 'card-3' => 0 } -``` - ---- - -#### 4. getDeckCardOwnership(deckId: string) - -Get detailed ownership information for all cards in a specific deck. - -**Parameters**: -- `deckId` (string): The deck ID - -**Returns**: `Promise` - -**Response Type**: -```typescript -interface CardOwnershipInfo { - card_id: string; - owned: boolean; // true if user has enough copies - quantity_in_collection: number; // how many user owns - quantity_in_deck: number; // how many needed for deck - quantity_needed: number; // how many more needed -} -``` - -**Example**: -```typescript -import { getDeckCardOwnership } from '../services/collectionService'; - -const ownership = await getDeckCardOwnership('deck-uuid-123'); -// Returns: [ -// { card_id: 'card-1', owned: true, quantity_in_collection: 4, quantity_in_deck: 2, quantity_needed: 0 }, -// { card_id: 'card-2', owned: false, quantity_in_collection: 1, quantity_in_deck: 3, quantity_needed: 2 } -// ] -``` - -**Security**: Verifies that the user owns the deck before returning ownership info. - ---- - -#### 5. getMissingCardsFromDeck(deckId: string) - -Get only the cards that are missing from a deck (quantity needed > 0). - -**Parameters**: -- `deckId` (string): The deck ID - -**Returns**: `Promise` - -**Response Type**: -```typescript -interface MissingCardInfo { - card_id: string; - quantity_needed: number; - quantity_in_collection: number; -} -``` - -**Example**: -```typescript -import { getMissingCardsFromDeck } from '../services/collectionService'; - -const missingCards = await getMissingCardsFromDeck('deck-uuid-123'); -// Returns: [ -// { card_id: 'card-2', quantity_needed: 2, quantity_in_collection: 1 }, -// { card_id: 'card-5', quantity_needed: 4, quantity_in_collection: 0 } -// ] -``` - ---- - -#### 6. addCardToCollection(cardId: string, quantity?: number) - -Add a single card to the user's collection. If the card already exists, increments the quantity. - -**Parameters**: -- `cardId` (string): The Scryfall card ID -- `quantity` (number, optional): Quantity to add (default: 1) - -**Returns**: `Promise` - -**Validation**: -- Card ID must be a non-empty string -- Quantity must be at least 1 - -**Example**: -```typescript -import { addCardToCollection } from '../services/collectionService'; - -const result = await addCardToCollection('card-uuid-123', 2); -// Returns: { id, user_id, card_id, quantity, created_at, updated_at } -``` - ---- - -#### 7. addCardsToCollectionBulk(cards: Array<{card_id: string, quantity: number}>) - -Add multiple cards to the collection in a single operation. More efficient than multiple individual calls. - -**Parameters**: -- `cards` (Array): Array of objects with `card_id` and `quantity` - -**Returns**: `Promise>` - -**Features**: -- Automatically merges with existing collection entries -- Processes in batches of 1000 for optimal performance -- Returns individual success/failure status for each card - -**Example**: -```typescript -import { addCardsToCollectionBulk } from '../services/collectionService'; - -const cards = [ - { card_id: 'card-1', quantity: 4 }, - { card_id: 'card-2', quantity: 2 }, - { card_id: 'card-3', quantity: 1 } -]; - -const results = await addCardsToCollectionBulk(cards); -// Returns: [ -// { card_id: 'card-1', success: true }, -// { card_id: 'card-2', success: true }, -// { card_id: 'card-3', success: false, error: 'Database error' } -// ] -``` - ---- - -#### 8. addMissingDeckCardsToCollection(deckId: string) - -Convenience function to add all missing cards from a deck to the collection in one operation. - -**Parameters**: -- `deckId` (string): The deck ID - -**Returns**: `Promise>` - -**Security**: Verifies deck ownership before adding cards. - -**Example**: -```typescript -import { addMissingDeckCardsToCollection } from '../services/collectionService'; - -const results = await addMissingDeckCardsToCollection('deck-uuid-123'); -// Returns: [ -// { card_id: 'card-1', success: true }, -// { card_id: 'card-2', success: true } -// ] -``` - ---- - -#### 9. removeCardFromCollection(cardId: string, quantity?: number) - -Remove a card from the collection, or decrease its quantity. - -**Parameters**: -- `cardId` (string): The Scryfall card ID -- `quantity` (number, optional): Quantity to remove. If not specified or >= existing quantity, removes the card entirely. - -**Returns**: `Promise` - -**Example**: -```typescript -import { removeCardFromCollection } from '../services/collectionService'; - -// Remove 2 copies -await removeCardFromCollection('card-uuid-123', 2); - -// Remove card entirely -await removeCardFromCollection('card-uuid-123'); -``` - ---- - -## React Hook: useCollection - -A custom hook that wraps the collection service with state management, loading states, and error handling. - -### Usage - -```typescript -import { useCollection } from '../hooks/useCollection'; - -function MyComponent() { - const { - loading, - error, - clearError, - getCollection, - checkCardOwnership, - checkMultipleCardsOwnership, - getDeckOwnership, - getMissingCards, - addCard, - addCardsBulk, - addMissingDeckCards, - removeCard - } = useCollection(); - - // Use the functions - const handleAddCard = async (cardId: string) => { - const success = await addCard(cardId, 1); - if (success) { - console.log('Card added successfully!'); - } else { - console.error('Error:', error); - } - }; - - return ( -
- {loading &&

Loading...

} - {error &&

Error: {error}

} - {/* Your component UI */} -
- ); -} -``` - -### Hook API - -All functions from the service are available with the same signatures, plus: - -- `loading` (boolean): True when any operation is in progress -- `error` (string | null): Error message if an operation failed -- `clearError` (() => void): Function to clear the error state - ---- - -## Error Handling - -All functions include comprehensive error handling: - -1. **Authentication Errors**: Thrown if user is not authenticated -2. **Authorization Errors**: Thrown if user tries to access data they don't own -3. **Validation Errors**: Thrown if input parameters are invalid -4. **Database Errors**: Supabase errors are caught and wrapped with user-friendly messages - -**Example Error Handling**: -```typescript -try { - await addCardToCollection('invalid-card', -5); -} catch (error) { - console.error(error.message); // "Quantity must be at least 1" -} -``` - ---- - -## Performance Considerations - -1. **Bulk Operations**: Use `addCardsToCollectionBulk()` for adding multiple cards instead of calling `addCardToCollection()` in a loop. - -2. **Batch Size**: Bulk operations are automatically batched at 1000 cards to optimize database performance. - -3. **Caching**: Consider implementing client-side caching for collection data that doesn't change frequently. - -4. **RLS Performance**: Supabase RLS policies are indexed on `user_id` for optimal query performance. - ---- - -## Integration Examples - -### Example 1: Show Missing Cards in Deck Editor - -```typescript -import { useEffect, useState } from 'react'; -import { useCollection } from '../hooks/useCollection'; -import { CardOwnershipInfo } from '../services/collectionService'; - -function DeckEditorWithCollection({ deckId }: { deckId: string }) { - const { getDeckOwnership, loading } = useCollection(); - const [ownership, setOwnership] = useState([]); - - useEffect(() => { - const fetchOwnership = async () => { - const data = await getDeckOwnership(deckId); - if (data) setOwnership(data); - }; - fetchOwnership(); - }, [deckId]); - - return ( -
-

Deck Cards

- {loading &&

Loading...

} - {ownership.map(card => ( -
-

Card: {card.card_id}

- {!card.owned && ( -

- Missing {card.quantity_needed} copies -

- )} -
- ))} -
- ); -} -``` - -### Example 2: Add All Missing Cards Button - -```typescript -import { useCollection } from '../hooks/useCollection'; - -function AddAllMissingButton({ deckId }: { deckId: string }) { - const { addMissingDeckCards, loading, error } = useCollection(); - - const handleAddAll = async () => { - const results = await addMissingDeckCards(deckId); - if (results) { - const successCount = results.filter(r => r.success).length; - const failCount = results.filter(r => !r.success).length; - alert(`Added ${successCount} cards. ${failCount} failed.`); - } - }; - - return ( -
- - {error &&

{error}

} -
- ); -} -``` - -### Example 3: Add Individual Card Button - -```typescript -import { useCollection } from '../hooks/useCollection'; - -function AddCardButton({ cardId, quantity }: { cardId: string; quantity: number }) { - const { addCard, loading } = useCollection(); - - const handleAdd = async () => { - const success = await addCard(cardId, quantity); - if (success) { - alert('Card added to collection!'); - } - }; - - return ( - - ); -} -``` - ---- - -## Testing - -The project currently has no automated tests configured. To manually test the collection API: - -1. **Build the project**: `npm run build` - Verifies TypeScript compilation -2. **Run the linter**: `npm run lint` - Checks code quality -3. **Manual testing**: Start the dev server with `npm run dev` and test through the UI - -### Manual Test Checklist - -- [ ] Add a single card to collection -- [ ] Add the same card again (should increment quantity) -- [ ] Add multiple cards in bulk -- [ ] View collection -- [ ] Check card ownership for a deck -- [ ] Add missing cards from a deck -- [ ] Remove a card from collection -- [ ] Verify RLS: User A cannot see User B's collection - ---- - -## Future Enhancements - -1. **Caching**: Add client-side caching to reduce database queries -2. **Optimistic Updates**: Update UI immediately before server confirmation -3. **Websocket Updates**: Real-time collection updates using Supabase Realtime -4. **Import/Export**: Bulk import collection from CSV or other formats -5. **Statistics**: Track collection value, completion percentage, etc. -6. **Trade Lists**: Mark cards as available for trade -7. **Wishlists**: Separate table for cards user wants to acquire - ---- - -## Support - -For issues or questions: -- Check Supabase logs for backend errors -- Review browser console for client-side errors -- Verify authentication status -- Check RLS policies in Supabase dashboard diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 9c5d48e..0000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,414 +0,0 @@ -# ISSUE-10 Implementation Summary - -## Ticket: Add information if we have cards from deck we create in our cards collection - -**Branch**: `feature/issue-10-deck-card-collection` - ---- - -## Overview - -Implemented comprehensive backend services for checking card ownership in user collections and adding missing cards to collections. This provides the foundation for the frontend to display which cards from a deck are missing from the user's collection and allow users to add them individually or in bulk. - ---- - -## Files Created - -### 1. `/home/node/projects/deckerr/src/services/collectionService.ts` (541 lines) - -Complete backend service for collection management with the following functionality: - -**Core Functions:** -- `getUserCollection()` - Get user's entire collection -- `getCardInCollection(cardId)` - Check if a single card exists -- `checkCardsOwnership(cardIds[])` - Batch check ownership for multiple cards -- `getDeckCardOwnership(deckId)` - Get detailed ownership info for all cards in a deck -- `getMissingCardsFromDeck(deckId)` - Get only missing cards from a deck - -**Add Cards:** -- `addCardToCollection(cardId, quantity)` - Add single card (increments if exists) -- `addCardsToCollectionBulk(cards[])` - Bulk add multiple cards efficiently -- `addMissingDeckCardsToCollection(deckId)` - Add all missing deck cards at once - -**Remove Cards:** -- `removeCardFromCollection(cardId, quantity?)` - Remove or decrease card quantity - -**Key Features:** -- Full authentication and authorization checks -- Comprehensive input validation -- Automatic user ID resolution via Supabase Auth -- Batch processing for bulk operations (1000 cards per batch) -- Detailed error messages for debugging -- TypeScript interfaces for all data structures - -### 2. `/home/node/projects/deckerr/src/hooks/useCollection.ts` (204 lines) - -Custom React hook that wraps the collection service with state management: - -**Features:** -- Loading state tracking -- Error state management with `clearError()` function -- All service functions exposed with consistent error handling -- Ready-to-use in React components - -**Functions Exposed:** -- `getCollection()` -- `checkCardOwnership(cardId)` -- `checkMultipleCardsOwnership(cardIds[])` -- `getDeckOwnership(deckId)` -- `getMissingCards(deckId)` -- `addCard(cardId, quantity)` -- `addCardsBulk(cards[])` -- `addMissingDeckCards(deckId)` -- `removeCard(cardId, quantity?)` - -### 3. `/home/node/projects/deckerr/COLLECTION_API.md` (486 lines) - -Comprehensive API documentation including: -- Architecture overview -- Database schema documentation -- Security model explanation -- Detailed function documentation with examples -- React hook usage guide -- Integration examples for common use cases -- Manual testing checklist -- Future enhancement suggestions - -### 4. `/home/node/projects/deckerr/IMPLEMENTATION_SUMMARY.md` (This file) - -Summary of all changes made for this ticket. - ---- - -## Files Modified - -### `/home/node/projects/deckerr/src/types/index.ts` - -**Changes:** -- Added `prices` field to `Card` interface (for displaying card prices) -- Added `Collection` interface for typed collection data - -**Before:** -```typescript -export interface Card { - id: string; - name: string; - // ... other fields - colors?: string[]; -} -``` - -**After:** -```typescript -export interface Card { - id: string; - name: string; - // ... other fields - colors?: string[]; - prices?: { - usd?: string; - usd_foil?: string; - eur?: string; - }; -} - -export interface Collection { - id: string; - user_id: string; - card_id: string; - quantity: number; - created_at: string; - updated_at: string; -} -``` - ---- - -## Technical Implementation Details - -### Architecture Decisions - -1. **Supabase Backend**: Leveraging Supabase's client-side SDK eliminates need for separate API server -2. **Row Level Security**: All data access is secured at the database level -3. **TypeScript First**: Full type safety throughout the codebase -4. **Service Layer Pattern**: Business logic separated from UI components -5. **Custom Hooks**: React patterns for clean component integration - -### Security Implementation - -**Authentication:** -- All service functions call `getCurrentUserId()` to verify user is authenticated -- Throws descriptive errors if authentication fails - -**Authorization:** -- Supabase RLS policies ensure users can only access their own data -- Additional verification for deck ownership before operations -- No SQL injection vulnerabilities (using Supabase's query builder) - -**Validation:** -- Card IDs validated as non-empty strings -- Quantities validated as positive integers -- Bulk operations validate all cards before processing - -### Performance Optimizations - -1. **Batch Processing**: Bulk operations process up to 1000 cards per batch -2. **Single Queries**: `checkCardsOwnership()` uses a single query with `IN` clause -3. **Efficient Updates**: Bulk add separates updates vs inserts for optimal performance -4. **No N+1 Queries**: All card checks done in single batch queries - -### Error Handling Strategy - -**Three-Layer Approach:** -1. **Input Validation**: Catches invalid parameters before database calls -2. **Service Layer**: Wraps Supabase errors with user-friendly messages -3. **Hook Layer**: Provides component-level error state management - -**Example Error Flow:** -``` -Invalid Input → Validation Error → Hook Error State → UI Display -Database Error → Service Error → Hook Error State → UI Display -Auth Error → Service Error → Hook Error State → UI Display -``` - ---- - -## Database Schema (Existing) - -The implementation uses the existing `collections` table: - -```sql -CREATE TABLE public.collections ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - user_id uuid REFERENCES public.profiles(id) NOT NULL, - card_id text NOT NULL, - quantity integer DEFAULT 1, - created_at timestamptz DEFAULT now(), - updated_at timestamptz DEFAULT now() -); - --- RLS Enabled -ALTER TABLE public.collections ENABLE ROW LEVEL SECURITY; - --- Policies -CREATE POLICY "Users can view their own collection" - ON public.collections FOR SELECT - TO authenticated - USING (user_id = auth.uid()); - -CREATE POLICY "Users can manage their own collection" - ON public.collections FOR ALL - TO authenticated - USING (user_id = auth.uid()); -``` - -**No schema changes were required.** - ---- - -## Testing Results - -### Build Test -```bash -npm run build -``` -**Result**: ✅ SUCCESS - Built in 5.94s with no TypeScript errors - -### Lint Test -```bash -npm run lint -``` -**Result**: ✅ SUCCESS - No linting errors in new files - -**Pre-existing linting issues** (not related to this implementation): -- CardCarousel.tsx: unused 'index' variable -- DeckList.tsx: unused 'getCardById' import -- Profile.tsx: unused 'error' variable -- AuthContext.tsx: React Fast Refresh warning (common pattern) - -### TypeScript Compilation -- All types properly defined with no `any` types -- Full IntelliSense support in IDEs -- No type errors in service or hook files - ---- - -## Integration Guidelines for Frontend - -### Step 1: Import the Hook - -```typescript -import { useCollection } from '../hooks/useCollection'; -``` - -### Step 2: Use in Component - -```typescript -function DeckEditor({ deckId }) { - const { - loading, - error, - getDeckOwnership, - addCard, - addMissingDeckCards - } = useCollection(); - - // Your component logic -} -``` - -### Step 3: Display Missing Cards - -```typescript -// Get ownership info -const ownership = await getDeckOwnership(deckId); - -// Filter for missing cards -const missingCards = ownership.filter(card => !card.owned); - -// Display in UI -missingCards.map(card => ( -
-

Need {card.quantity_needed} more copies

- -
-)); -``` - -### Step 4: Bulk Add Button - -```typescript - -``` - ---- - -## API Endpoints Summary - -| Function | Purpose | Returns | -|----------|---------|---------| -| `getUserCollection()` | Get full collection | `CollectionCard[]` | -| `getCardInCollection(id)` | Check single card | `CollectionCard \| null` | -| `checkCardsOwnership(ids[])` | Batch ownership check | `Map` | -| `getDeckCardOwnership(deckId)` | Deck ownership details | `CardOwnershipInfo[]` | -| `getMissingCardsFromDeck(deckId)` | Missing cards only | `MissingCardInfo[]` | -| `addCardToCollection(id, qty)` | Add single card | `CollectionCard` | -| `addCardsToCollectionBulk(cards[])` | Bulk add cards | `Result[]` | -| `addMissingDeckCardsToCollection(deckId)` | Add all missing | `Result[]` | -| `removeCardFromCollection(id, qty?)` | Remove card | `boolean` | - ---- - -## Security Verification - -✅ **Authentication Required**: All functions check user authentication -✅ **Authorization Enforced**: RLS policies prevent unauthorized access -✅ **Input Validation**: All inputs validated before database operations -✅ **No SQL Injection**: Using Supabase query builder (parameterized queries) -✅ **Error Messages Safe**: No sensitive data exposed in error messages -✅ **Deck Ownership**: Verified before allowing operations on deck data - ---- - -## Next Steps / Frontend Integration Tasks - -1. **Update DeckEditor Component** - - Import `useCollection` hook - - Call `getDeckOwnership(deckId)` on component mount - - Display ownership status for each card - - Show "Add to Collection" button for missing cards - - Show "Add All Missing Cards" button - -2. **Update DeckManager Component** - - Add collection status indicators - - Show quantity needed vs quantity owned - - Implement individual card add buttons - - Implement bulk add button - -3. **Update Collection Component** - - Use `getUserCollection()` to load actual collection data - - Replace local state with Supabase data - - Update `addToCollection` to use `addCard()` from hook - - Persist collection changes to database - -4. **UI Enhancements** - - Add loading spinners during operations - - Display error messages from hook - - Show success notifications after adding cards - - Add visual indicators (icons/colors) for owned/missing cards - -5. **Optional Enhancements** - - Add confirmation dialog for bulk operations - - Show total cost of missing cards - - Add "Preview" mode before bulk add - - Implement undo functionality - ---- - -## Dependencies - -**No new dependencies added.** All functionality implemented using existing packages: -- `@supabase/supabase-js` (already installed) -- `react` (already installed) -- TypeScript types (already installed) - ---- - -## Known Limitations - -1. **No Automated Tests**: Project has no test framework configured -2. **No Caching**: Each query hits the database (consider React Query for future) -3. **No Optimistic Updates**: UI waits for server confirmation -4. **No Real-time Updates**: Changes not reflected in other open tabs/devices -5. **Basic Error Messages**: Could be more user-friendly with specific guidance - ---- - -## Recommendations for Future Improvements - -### High Priority -1. Add unit tests for service functions -2. Add integration tests for hook -3. Implement optimistic UI updates -4. Add caching layer (React Query or SWR) - -### Medium Priority -1. Add Supabase Realtime for live collection updates -2. Implement collection statistics (total value, completion %) -3. Add import/export functionality for collections -4. Create collection sharing features - -### Low Priority -1. Add collection analytics dashboard -2. Implement trade/wishlist features -3. Add collection version history -4. Create collection comparison tools - ---- - -## Conclusion - -The backend implementation is **complete and production-ready** with: -- ✅ Full authentication and authorization -- ✅ Comprehensive error handling -- ✅ Input validation -- ✅ TypeScript type safety -- ✅ Efficient batch operations -- ✅ Clean separation of concerns -- ✅ Extensive documentation -- ✅ Build and lint passing - -The frontend team can now integrate these services to display collection status and allow users to add cards to their collection. - ---- - -## Questions or Issues? - -Refer to `/home/node/projects/deckerr/COLLECTION_API.md` for detailed API documentation and integration examples. diff --git a/src/components/Collection.tsx b/src/components/Collection.tsx index 2b882c2..420a14e 100644 --- a/src/components/Collection.tsx +++ b/src/components/Collection.tsx @@ -4,6 +4,7 @@ import { Card } from '../types'; import { getUserCollection, getCardsByIds, addCardToCollection } from '../services/api'; import { useAuth } from '../contexts/AuthContext'; import { supabase } from '../lib/supabase'; +import ConfirmModal from './ConfirmModal'; export default function Collection() { const { user } = useAuth(); @@ -16,6 +17,11 @@ export default function Collection() { const [cardFaceIndex, setCardFaceIndex] = useState>(new Map()); const [snackbar, setSnackbar] = useState<{ message: string; type: 'success' | 'error' } | null>(null); const [isUpdating, setIsUpdating] = useState(false); + const [confirmModal, setConfirmModal] = useState<{ + isOpen: boolean; + cardId: string; + cardName: string; + }>({ isOpen: false, cardId: '', cardName: '' }); // Helper function to check if a card has an actual back face (not adventure/split/etc) const isDoubleFaced = (card: Card) => { @@ -429,9 +435,11 @@ export default function Collection() { {/* Remove from collection button */} + + + + + ); +} diff --git a/src/components/DeckManager.tsx b/src/components/DeckManager.tsx index cac94fc..e2141d3 100644 --- a/src/components/DeckManager.tsx +++ b/src/components/DeckManager.tsx @@ -456,11 +456,11 @@ export default function DeckManager({ initialDeck, onSave }: DeckManagerProps) { cardsToAdd.push({ card, quantity }); } else { console.warn(`Card not found: ${cardName}`); - alert(`Card not found: ${cardName}`); + setSnackbar({ message: `Card not found: ${cardName}`, type: 'error' }); } } catch (error) { console.error(`Failed to search card ${cardName}:`, error); - alert(`Failed to search card ${cardName}: ${error}`); + setSnackbar({ message: `Failed to import card: ${cardName}`, type: 'error' }); } } diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..82f6b71 --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,80 @@ +import React, { useEffect } from 'react'; +import { X } from 'lucide-react'; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; + size?: 'sm' | 'md' | 'lg'; + showCloseButton?: boolean; +} + +export default function Modal({ + isOpen, + onClose, + children, + size = 'md', + showCloseButton = true +}: ModalProps) { + // Close modal on ESC key + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, onClose]); + + // Prevent body scroll when modal is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + + return () => { + document.body.style.overflow = 'unset'; + }; + }, [isOpen]); + + if (!isOpen) return null; + + const sizeClasses = { + sm: 'max-w-md', + md: 'max-w-lg', + lg: 'max-w-2xl', + }; + + return ( + <> + {/* Backdrop */} +
+ + {/* Modal */} +
+
e.stopPropagation()} + > + {showCloseButton && ( + + )} + + {children} +
+
+ + ); +}