[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>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Card } from '../types';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
const SCRYFALL_API = 'https://api.scryfall.com';
|
||||
|
||||
@@ -52,3 +53,127 @@ export const getCardsByIds = async (cardIds: string[]): Promise<Card[]> => {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user