mirror of
https://github.com/xavidop/cardex.git
synced 2026-08-31 02:08:52 +00:00
feat: multiple TCG
This commit is contained in:
@@ -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 });
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user