feat: multiple TCG

This commit is contained in:
xavidop
2025-11-21 09:59:33 +01:00
parent 2209d61879
commit abd98f8009
34 changed files with 1763 additions and 3303 deletions
@@ -1,12 +1,12 @@
import { NextRequest, NextResponse } from 'next/server';
import { generatePokemonCardFromPhoto } from '@/ai/flows/generate-pokemon-card-from-photo';
import { generateTCGCardFromPhoto } from '@/ai/flows/generate-tcg-card-from-photo';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// Validate required fields - including userId
const requiredFields = ['userId', 'photoDataUri', 'pokemonName', 'pokemonType', 'styleDescription'];
const requiredFields = ['userId', 'photoDataUri', 'characterName', 'characterType', 'styleDescription', 'game'];
const missingFields = requiredFields.filter(field => !body[field]);
if (missingFields.length > 0) {
@@ -17,7 +17,7 @@ export async function POST(request: NextRequest) {
}
// Call the AI flow
const result = await generatePokemonCardFromPhoto(body);
const result = await generateTCGCardFromPhoto(body);
if (result.error) {
return NextResponse.json({ error: result.error }, { status: 500 });
+7 -5
View File
@@ -1,12 +1,12 @@
import { NextRequest, NextResponse } from 'next/server';
import { generatePokemonCard } from '@/ai/flows/generate-pokemon-card';
import { generateTCGCard } from '@/ai/flows/generate-tcg-card';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// Validate required fields - including userId
const requiredFields = ['userId', 'pokemonName', 'pokemonType', 'backgroundDescription', 'pokemonDescription'];
// New multi-game flow
const requiredFields = ['userId', 'game', 'characterName', 'characterType', 'backgroundDescription', 'characterDescription'];
const missingFields = requiredFields.filter(field => !body[field]);
if (missingFields.length > 0) {
@@ -16,14 +16,15 @@ export async function POST(request: NextRequest) {
);
}
// Call the AI flow
const result = await generatePokemonCard(body);
// Call the new AI flow
const result = await generateTCGCard(body);
if (result.error) {
return NextResponse.json({ error: result.error }, { status: 500 });
}
return NextResponse.json(result);
} catch (error) {
console.error('API route error:', error);
return NextResponse.json(
@@ -32,3 +33,4 @@ export async function POST(request: NextRequest) {
);
}
}
@@ -12,7 +12,9 @@ import { Loader2, Save } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { compressBase64Image, getBase64Size, formatBytes } from '@/utils/imageUtils';
import { parsePhotoGenerationParams } from '@/utils/generationParamsUtils';
import { getGameConfig } from '@/config/tcg-games';
import type { GenerateFromPhotoInput } from '@/components/cards/PhotoCardGeneratorFormOnly';
import type { TCGGame } from '@/types';
import Image from 'next/image';
export default function EditGenerateFromPhotoPage() {
@@ -25,12 +27,18 @@ export default function EditGenerateFromPhotoPage() {
const [isSaving, setIsSaving] = useState(false);
const [initialValues, setInitialValues] = useState<Partial<GenerateFromPhotoInput> | undefined>(undefined);
const [showComparison, setShowComparison] = useState(true);
const [currentGame, setCurrentGame] = useState<TCGGame>('pokemon');
useEffect(() => {
// Parse parameters from URL search params
const params = parsePhotoGenerationParams(searchParams);
setInitialValues(params);
// Set the game from params
if (params.game) {
setCurrentGame(params.game as TCGGame);
}
// Extract original card data
const originalCardId = searchParams.get('originalCardId');
const originalCardName = searchParams.get('originalCardName');
@@ -83,13 +91,17 @@ export default function EditGenerateFromPhotoPage() {
}
}
const game = generatedCard.params?.game || currentGame;
const gameName = getGameConfig(game as TCGGame).name;
// Exclude photoDataUri from params when saving to Firestore (too large for document storage)
const { photoDataUri, ...paramsWithoutPhoto } = generatedCard.params;
await addCardToCollection(user.uid, {
name: 'Photo-Generated Pokemon Card',
name: `Photo-Generated ${gameName} Card`,
set: 'AI Photo Generated',
rarity: 'Photo Special',
game: game,
imageDataUrl: finalImageDataUrl,
isGenerated: true,
isPhotoGenerated: true,
@@ -99,7 +111,7 @@ export default function EditGenerateFromPhotoPage() {
toast({
title: 'Card Saved',
description: 'Your photo-generated Pokemon card has been added to your collection!',
description: `Your photo-generated ${gameName} card has been added to your collection!`,
});
// Clear the generated card after saving
@@ -124,9 +136,9 @@ export default function EditGenerateFromPhotoPage() {
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Edit Pokemon Card Generator from Photo</h1>
<h1 className="text-3xl font-bold mb-2">Edit {getGameConfig(currentGame).name} Card Generator from Photo</h1>
<p className="text-muted-foreground">
Modify your photo-based Pokemon card parameters and regenerate the card
Modify your photo-based {getGameConfig(currentGame).name} card parameters and regenerate the card
</p>
</div>
+15 -6
View File
@@ -3,7 +3,7 @@
import { useState } from 'react';
import { useAuth } from '@/hooks/useAuth';
import { useToast } from '@/hooks/use-toast';
import { PhotoCardGenerator } from '@/components/cards/PhotoCardGenerator';
import { PhotoCardGeneratorFormOnly as PhotoCardGenerator } from '@/components/cards/PhotoCardGeneratorFormOnly';
import { addCardToCollection } from '@/lib/firestore';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@@ -11,6 +11,8 @@ import { Loader2, Save } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { compressBase64Image, getBase64Size, formatBytes } from '@/utils/imageUtils';
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
import { getGameConfig } from '@/config/tcg-games';
import type { TCGGame } from '@/types';
export default function GenerateFromPhotoPage() {
const { user } = useAuth();
@@ -57,13 +59,17 @@ export default function GenerateFromPhotoPage() {
}
}
const game = generatedCard.params?.game || 'pokemon';
const gameName = getGameConfig(game as TCGGame).name;
// Exclude photoDataUri from params when saving to Firestore (too large for document storage)
const { photoDataUri, ...paramsWithoutPhoto } = generatedCard.params;
await addCardToCollection(user.uid, {
name: 'Photo-Generated Pokemon Card',
name: `Photo-Generated ${gameName} Card`,
set: 'AI Photo Generated',
rarity: 'Photo Special',
game: game,
imageDataUrl: finalImageDataUrl,
isGenerated: true,
isPhotoGenerated: true,
@@ -73,7 +79,7 @@ export default function GenerateFromPhotoPage() {
toast({
title: 'Card Saved',
description: 'Your photo-generated Pokemon card has been added to your collection!',
description: `Your photo-generated ${gameName} card has been added to your collection!`,
});
// Clear the generated card after saving
@@ -94,9 +100,12 @@ export default function GenerateFromPhotoPage() {
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Pokemon Card Generator from Photo</h1>
<h1 className="text-3xl font-bold mb-2">TCG Card Generator from Photo</h1>
<p className="text-muted-foreground">
Upload your own photo and transform it into a custom Pokemon card using AI-powered image generation
Upload your own photo and transform it into a custom trading card using AI-powered image generation
</p>
<p className="text-sm text-muted-foreground mt-2">
Supports Pokémon, One Piece, Lorcana, Magic: The Gathering, and Dragon Ball cards
</p>
</div>
@@ -115,7 +124,7 @@ export default function GenerateFromPhotoPage() {
<CardHeader>
<CardTitle>Save Your Card</CardTitle>
<CardDescription>
Your photo-inspired Pokemon card is ready! Save it to your collection.
Your photo-inspired trading card is ready! Save it to your collection.
</CardDescription>
</CardHeader>
<CardContent>
+19 -8
View File
@@ -2,7 +2,7 @@
import { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { CardGeneratorFormOnly } from '@/components/cards/CardGeneratorFormOnly';
import { TCGCardGenerator } from '@/components/cards/TCGCardGenerator';
import { Button } from '@/components/ui/button';
import { useAuth } from '@/hooks/useAuth';
import { addCardToCollection } from '@/lib/firestore';
@@ -10,7 +10,8 @@ import { useToast } from '@/hooks/use-toast';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { compressBase64Image, getBase64Size, formatBytes } from '@/utils/imageUtils';
import { parseGenerationParams } from '@/utils/generationParamsUtils';
import type { GeneratePokemonCardInput } from '@/components/cards/CardGeneratorFormOnly';
import { getGameConfig } from '@/config/tcg-games';
import type { TCGGame, CardGenerationParams } from '@/types';
import Image from 'next/image';
export default function EditGenerateCardPage() {
@@ -20,14 +21,20 @@ export default function EditGenerateCardPage() {
const [generatedCard, setGeneratedCard] = useState<{ imageBase64: string; prompt: string; params?: any } | null>(null);
const [originalCard, setOriginalCard] = useState<{ id: string; name: string; imageUrl: string } | null>(null);
const [isSaving, setIsSaving] = useState(false);
const [initialValues, setInitialValues] = useState<Partial<GeneratePokemonCardInput> | undefined>(undefined);
const [initialValues, setInitialValues] = useState<Partial<CardGenerationParams> | undefined>(undefined);
const [showComparison, setShowComparison] = useState(true);
const [currentGame, setCurrentGame] = useState<TCGGame>('pokemon');
useEffect(() => {
// Parse parameters from URL search params
const params = parseGenerationParams(searchParams);
setInitialValues(params);
// Set the game from params
if (params.game) {
setCurrentGame(params.game as TCGGame);
}
// Extract original card data
const originalCardId = searchParams.get('originalCardId');
const originalCardName = searchParams.get('originalCardName');
@@ -82,10 +89,14 @@ export default function EditGenerateCardPage() {
}
}
const game = generatedCard.params?.game || currentGame;
const gameName = getGameConfig(game as TCGGame).name;
await addCardToCollection(user.uid, {
name: 'Generated Pokemon Card',
name: `Generated ${gameName} Card`,
set: 'AI Generated',
rarity: 'Special',
game: game,
imageDataUrl: imageDataUrl,
isGenerated: true,
prompt: generatedCard.prompt,
@@ -94,7 +105,7 @@ export default function EditGenerateCardPage() {
toast({
title: 'Card Saved',
description: 'Your generated Pokemon card has been added to your collection!',
description: `Your generated ${gameName} card has been added to your collection!`,
});
// Clear the generated card after saving
@@ -118,16 +129,16 @@ export default function EditGenerateCardPage() {
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Edit Pokemon Card Generator</h1>
<h1 className="text-3xl font-bold mb-2">Edit {getGameConfig(currentGame).name} Card Generator</h1>
<p className="text-muted-foreground">
Modify your Pokemon card parameters and regenerate the card
Modify your {getGameConfig(currentGame).name} card parameters and regenerate the card
</p>
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* Form Section - Takes up 2 columns */}
<div className="xl:col-span-2">
<CardGeneratorFormOnly onCardGenerated={handleCardGenerated} initialValues={initialValues} />
<TCGCardGenerator onCardGenerated={handleCardGenerated} initialValues={initialValues} />
</div>
{/* Card Preview Section - Takes up 1 column */}
+12 -6
View File
@@ -1,7 +1,7 @@
'use client';
import { useState } from 'react';
import { CardGenerator } from '@/components/cards/CardGenerator';
import { TCGCardGenerator } from '@/components/cards/TCGCardGenerator';
import { Button } from '@/components/ui/button';
import { useAuth } from '@/hooks/useAuth';
import { addCardToCollection } from '@/lib/firestore';
@@ -9,6 +9,8 @@ import { useToast } from '@/hooks/use-toast';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { compressBase64Image, getBase64Size, formatBytes } from '@/utils/imageUtils';
import ApiKeysWarning from '@/components/cards/ApiKeysWarning';
import { getGameConfig } from '@/config/tcg-games';
import type { TCGGame } from '@/types';
export default function GenerateCardPage() {
const { user } = useAuth();
@@ -56,10 +58,14 @@ export default function GenerateCardPage() {
}
}
const game = generatedCard.params?.game || 'pokemon';
const gameName = getGameConfig(game as TCGGame).name;
await addCardToCollection(user.uid, {
name: 'Generated Pokemon Card',
name: `Generated ${gameName} Card`,
set: 'AI Generated',
rarity: 'Special',
game: game,
imageDataUrl: imageDataUrl,
isGenerated: true,
prompt: generatedCard.prompt,
@@ -68,7 +74,7 @@ export default function GenerateCardPage() {
toast({
title: 'Card Saved',
description: 'Your generated Pokemon card has been added to your collection!',
description: `Your generated ${gameName} card has been added to your collection!`,
});
// Clear the generated card after saving
@@ -88,9 +94,9 @@ export default function GenerateCardPage() {
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Pokemon Card Generator</h1>
<h1 className="text-3xl font-bold mb-2">TCG Card Generator</h1>
<p className="text-muted-foreground">
Create your own custom Pokemon cards using AI-powered image generation
Create your own custom trading cards for Pokémon, One Piece, Lorcana, Magic, and Dragon Ball using AI
</p>
</div>
@@ -102,7 +108,7 @@ export default function GenerateCardPage() {
/>
</div>
<CardGenerator onCardGenerated={handleCardGenerated} />
<TCGCardGenerator onCardGenerated={handleCardGenerated} />
{generatedCard && (
<Card className="mt-8">
+5 -5
View File
@@ -15,7 +15,7 @@ export default function DashboardPage() {
<div className="text-center mb-8">
<h1 className="text-3xl font-bold font-headline mb-2">Welcome to your Cardex Dashboard</h1>
<p className="text-lg text-muted-foreground">
Manage your Pokémon card collection with ease.
Manage your TCG card collection with ease.
</p>
</div>
@@ -32,7 +32,7 @@ export default function DashboardPage() {
Scan Cards
</CardTitle>
<CardDescription>
Use your camera to scan and identify Pokemon cards
Use your camera to scan and identify TCG cards
</CardDescription>
</CardHeader>
<CardContent>
@@ -51,7 +51,7 @@ export default function DashboardPage() {
Generate Cards
</CardTitle>
<CardDescription>
Create custom Pokemon cards using AI image generation
Create custom TCG cards using AI image generation
</CardDescription>
</CardHeader>
<CardContent>
@@ -70,7 +70,7 @@ export default function DashboardPage() {
Photo Cards
</CardTitle>
<CardDescription>
Transform your photos into Pokemon cards using AI
Transform your photos into TCG cards using AI
</CardDescription>
</CardHeader>
<CardContent>
@@ -89,7 +89,7 @@ export default function DashboardPage() {
My Collection
</CardTitle>
<CardDescription>
View and manage your Pokemon card collection
View and manage your TCG card collection
</CardDescription>
</CardHeader>
<CardContent>
+5 -2
View File
@@ -5,9 +5,12 @@ export default function ScanCardPage() {
return (
<div className="space-y-6">
<div className="text-center space-y-2">
<h1 className="text-3xl font-bold tracking-tight">Scan Pokémon Card</h1>
<h1 className="text-3xl font-bold tracking-tight">Scan Trading Card</h1>
<p className="text-muted-foreground text-lg">
Upload an image of your Pokémon card to automatically identify and add it to your collection
Upload an image of your trading card to automatically identify and add it to your collection
</p>
<p className="text-sm text-muted-foreground">
Supports Pokémon, One Piece, Lorcana, Magic: The Gathering, and Dragon Ball cards
</p>
</div>