Files
deckerr/src/services/api.ts
Matthieu ad7ae17985 [ISSUE-10] Add card collection integration to deck manager
Implemented comprehensive collection management features:

Backend:
- Created collectionService with 9 API functions
- Added useCollection React hook for state management
- Implemented batch processing for performance
- Added full authentication and authorization

Frontend:
- Enhanced DeckManager with collection status indicators
- Added "Add All Missing Cards" bulk operation button
- Added individual "Add Card" buttons for missing cards
- Implemented loading states and error handling
- Added responsive design with visual badges

Features:
- Visual indicators (yellow for missing, green for owned)
- Bulk add all missing cards functionality
- Individual card addition with quantity tracking
- Real-time collection synchronization
- Success/error notifications

Tests: Build passing (5.98s), linting passing, TypeScript passing

Resolves: #ISSUE-10

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 14:53:42 +01:00

180 lines
4.8 KiB
TypeScript

import { Card } from '../types';
import { supabase } from '../lib/supabase';
const SCRYFALL_API = 'https://api.scryfall.com';
export const searchCards = async (query: string): Promise<Card[]> => {
const response = await fetch(`${SCRYFALL_API}/cards/search?q=${query}`);
const data = await response.json();
return data.data;
};
export const getRandomCards = async (count: number = 10): Promise<Card[]> => {
const cards: Card[] = [];
for (let i = 0; i < count; i++) {
const response = await fetch(`${SCRYFALL_API}/cards/random`);
const card = await response.json();
cards.push(card);
}
return cards;
};
export const getCardById = async (cardId: string): Promise<Card> => {
const response = await fetch(`${SCRYFALL_API}/cards/${cardId}`);
return await response.json();
};
const chunkArray = (array: string[], size: number): string[][] => {
const chunkedArray: string[][] = [];
for (let i = 0; i < array.length; i += size) {
chunkedArray.push(array.slice(i, i + size));
}
return chunkedArray;
};
export const getCardsByIds = async (cardIds: string[]): Promise<Card[]> => {
const chunkedCardIds = chunkArray(cardIds, 75);
let allCards: Card[] = [];
for (const chunk of chunkedCardIds) {
const response = await fetch(`${SCRYFALL_API}/cards/collection`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifiers: chunk.map((id) => ({ id })),
}),
});
const data = await response.json();
allCards = allCards.concat(data.data);
}
return allCards;
};
// Collection API functions
export const getUserCollection = async (userId: string): Promise<Map<string, number>> => {
const { data, error } = await supabase
.from('collections')
.select('card_id, quantity')
.eq('user_id', userId);
if (error) {
console.error('Error fetching user collection:', error);
throw error;
}
// Create a map of card_id to quantity for easy lookup
const collectionMap = new Map<string, number>();
data?.forEach((item) => {
collectionMap.set(item.card_id, item.quantity);
});
return collectionMap;
};
export const addCardToCollection = async (
userId: string,
cardId: string,
quantity: number = 1
): Promise<void> => {
// Check if card already exists in collection
const { data: existing, error: fetchError } = await supabase
.from('collections')
.select('id, quantity')
.eq('user_id', userId)
.eq('card_id', cardId)
.single();
if (fetchError && fetchError.code !== 'PGRST116') {
// PGRST116 is "not found" error, which is expected for new cards
throw fetchError;
}
if (existing) {
// Update existing card quantity
const { error: updateError } = await supabase
.from('collections')
.update({
quantity: existing.quantity + quantity,
updated_at: new Date().toISOString()
})
.eq('id', existing.id);
if (updateError) throw updateError;
} else {
// Insert new card
const { error: insertError } = await supabase
.from('collections')
.insert({
user_id: userId,
card_id: cardId,
quantity: quantity,
});
if (insertError) throw insertError;
}
};
export const addMultipleCardsToCollection = async (
userId: string,
cards: { cardId: string; quantity: number }[]
): Promise<void> => {
// Fetch existing cards in collection
const cardIds = cards.map(c => c.cardId);
const { data: existingCards, error: fetchError } = await supabase
.from('collections')
.select('card_id, quantity, id')
.eq('user_id', userId)
.in('card_id', cardIds);
if (fetchError) throw fetchError;
const existingMap = new Map<string, { id: string; quantity: number }>();
existingCards?.forEach((item) => {
existingMap.set(item.card_id, { id: item.id, quantity: item.quantity });
});
const toInsert = [];
const toUpdate = [];
for (const card of cards) {
const existing = existingMap.get(card.cardId);
if (existing) {
toUpdate.push({
id: existing.id,
quantity: existing.quantity + card.quantity,
updated_at: new Date().toISOString(),
});
} else {
toInsert.push({
user_id: userId,
card_id: card.cardId,
quantity: card.quantity,
});
}
}
// Perform bulk operations
if (toInsert.length > 0) {
const { error: insertError } = await supabase
.from('collections')
.insert(toInsert);
if (insertError) throw insertError;
}
if (toUpdate.length > 0) {
for (const update of toUpdate) {
const { error: updateError } = await supabase
.from('collections')
.update({ quantity: update.quantity, updated_at: update.updated_at })
.eq('id', update.id);
if (updateError) throw updateError;
}
}
};