← back to My Childhood Pics
components/ui/childhood-photo-recreator.tsx
512 lines
"use client"
import type React from "react"
import { useState, useEffect, useCallback } from "react"
import Image from "next/image"
import {
MessageCircle,
Sparkles,
Wand2,
Loader2,
AlertCircle,
Upload,
User,
Users,
MapPin,
Download,
RefreshCw,
Check,
} from "lucide-react"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
type UploadMode = "person1" | "person2" | "environment"
interface GenerationSettings {
person1Image: string | null
person2Image: string | null
environmentImage: string | null
selectedEnvironment: string
customPrompt: string
style: string
lighting: string
}
interface SuggestedEnvironment {
id: string
name: string
image: string
description: string
}
const suggestedEnvironments: SuggestedEnvironment[] = [
{
id: "beach",
name: "Tropical Beach",
image: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400",
description: "Sandy beach with crystal clear water and palm trees"
},
{
id: "mountain",
name: "Mountain Vista",
image: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400",
description: "Majestic mountain peaks with scenic views"
},
{
id: "city",
name: "Urban Cityscape",
image: "https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=400",
description: "Modern city skyline with bustling streets"
},
{
id: "forest",
name: "Enchanted Forest",
image: "https://images.unsplash.com/photo-1448375240586-882707db888b?w=400",
description: "Dense forest with sunlight streaming through trees"
},
{
id: "paris",
name: "Paris, France",
image: "https://images.unsplash.com/photo-1502602898657-3e91760cbb34?w=400",
description: "Romantic Parisian streets with Eiffel Tower"
},
{
id: "tokyo",
name: "Tokyo, Japan",
image: "https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?w=400",
description: "Vibrant Tokyo streets with neon lights"
},
{
id: "desert",
name: "Desert Dunes",
image: "https://images.unsplash.com/photo-1509316785289-025f5b846b35?w=400",
description: "Golden sand dunes under clear blue sky"
},
{
id: "garden",
name: "Botanical Garden",
image: "https://images.unsplash.com/photo-1585320806297-9794b3e4eeae?w=400",
description: "Beautiful garden with colorful flowers"
},
]
export function ChildhoodPhotoRecreator() {
const [mode, setMode] = useState<UploadMode>("person1")
const [isGenerating, setIsGenerating] = useState(false)
const [error, setError] = useState<string | null>(null)
const [generatedImage, setGeneratedImage] = useState<string | null>(null)
const [progress, setProgress] = useState(0)
const [dragActive, setDragActive] = useState(false)
const [settings, setSettings] = useState<GenerationSettings>({
person1Image: null,
person2Image: null,
environmentImage: null,
selectedEnvironment: "",
customPrompt: "",
style: "realistic",
lighting: "natural",
})
// Handle file upload
const handleFileUpload = useCallback((files: FileList | null, uploadMode: UploadMode) => {
if (files && files[0]) {
const file = files[0]
const reader = new FileReader()
reader.onload = (e) => {
const result = e.target?.result as string
if (uploadMode === "person1") {
setSettings(prev => ({ ...prev, person1Image: result }))
} else if (uploadMode === "person2") {
setSettings(prev => ({ ...prev, person2Image: result }))
} else {
setSettings(prev => ({ ...prev, environmentImage: result, selectedEnvironment: "custom" }))
}
}
reader.readAsDataURL(file)
}
}, [])
// Handle drag and drop
const handleDrag = useCallback((e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true)
} else if (e.type === "dragleave") {
setDragActive(false)
}
}, [])
const handleDrop = useCallback((e: React.DragEvent, uploadMode: UploadMode) => {
e.preventDefault()
e.stopPropagation()
setDragActive(false)
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
handleFileUpload(e.dataTransfer.files, uploadMode)
}
}, [handleFileUpload])
// Generate recreation
const handleGenerate = async () => {
if (!settings.person1Image) {
setError("Please upload at least one person's photo")
return
}
if (!settings.selectedEnvironment && !settings.environmentImage) {
setError("Please select or upload an environment")
return
}
setIsGenerating(true)
setError(null)
setProgress(0)
try {
// Simulate progress
const interval = setInterval(() => {
setProgress(prev => Math.min(prev + 10, 90))
}, 300)
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 3000))
clearInterval(interval)
setProgress(100)
// Set a generated image (in real app, this would come from API)
setGeneratedImage("https://images.unsplash.com/photo-1516627145497-ae6968895b74?w=800")
} catch (err) {
setError("Failed to generate recreation. Please try again.")
} finally {
setIsGenerating(false)
setProgress(0)
}
}
const getUploadAreaContent = () => {
const currentImage = mode === "person1" ? settings.person1Image :
mode === "person2" ? settings.person2Image :
settings.environmentImage
if (currentImage) {
return (
<div className="relative w-full h-full">
<Image
src={currentImage}
alt={`Uploaded ${mode}`}
fill
className="object-cover rounded-lg"
/>
<button
onClick={() => {
if (mode === "person1") setSettings(prev => ({ ...prev, person1Image: null }))
else if (mode === "person2") setSettings(prev => ({ ...prev, person2Image: null }))
else setSettings(prev => ({ ...prev, environmentImage: null }))
}}
className="absolute top-2 right-2 p-2 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
)
}
return (
<div className="flex flex-col items-center justify-center h-full p-8 text-center">
<Upload className={`w-12 h-12 mb-4 ${dragActive ? 'text-violet-500' : 'text-zinc-400'}`} />
<p className="text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
{mode === "person1" ? "Upload Person 1 Photo" :
mode === "person2" ? "Upload Person 2 Photo (Optional)" :
"Upload Custom Environment (Optional)"}
</p>
<p className="text-xs text-zinc-500">
Drag & drop or click to upload
</p>
<p className="text-xs text-zinc-400 mt-2">
JPG, PNG up to 10MB
</p>
</div>
)
}
return (
<div className="w-full max-w-6xl mx-auto space-y-6">
{/* Header */}
<div className="text-center space-y-2">
<h1 className="text-4xl font-bold bg-gradient-to-r from-violet-500 to-fuchsia-500 bg-clip-text text-transparent">
Childhood Photo Recreator
</h1>
<p className="text-zinc-600 dark:text-zinc-400">
Upload your childhood photos and recreate them in amazing new environments
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Upload Section */}
<Card className="border-zinc-200 dark:border-zinc-800">
<CardContent className="p-6">
<div className="space-y-4">
{/* Upload Mode Tabs */}
<Tabs value={mode} onValueChange={(value) => setMode(value as UploadMode)}>
<TabsList className="grid grid-cols-3 w-full">
<TabsTrigger value="person1" className="flex items-center gap-2">
<User className="w-4 h-4" />
<span>Person 1</span>
{settings.person1Image && <Check className="w-3 h-3 text-green-500" />}
</TabsTrigger>
<TabsTrigger value="person2" className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span>Person 2</span>
{settings.person2Image && <Check className="w-3 h-3 text-green-500" />}
</TabsTrigger>
<TabsTrigger value="environment" className="flex items-center gap-2">
<MapPin className="w-4 h-4" />
<span>Environment</span>
{(settings.environmentImage || settings.selectedEnvironment) && <Check className="w-3 h-3 text-green-500" />}
</TabsTrigger>
</TabsList>
</Tabs>
{/* Upload Area */}
<div
className={`relative h-64 border-2 border-dashed rounded-lg transition-colors ${
dragActive ? 'border-violet-500 bg-violet-50 dark:bg-violet-900/10' : 'border-zinc-300 dark:border-zinc-700'
}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={(e) => handleDrop(e, mode)}
>
<input
type="file"
accept="image/*"
onChange={(e) => handleFileUpload(e.target.files, mode)}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
{getUploadAreaContent()}
</div>
{/* Environment Selection (only show in environment mode) */}
{mode === "environment" && (
<div className="space-y-3">
<Label className="text-sm font-medium">Or Choose Suggested Environment</Label>
<div className="grid grid-cols-2 gap-2 max-h-48 overflow-y-auto">
{suggestedEnvironments.map((env) => (
<button
key={env.id}
onClick={() => setSettings(prev => ({
...prev,
selectedEnvironment: env.id,
environmentImage: null
}))}
className={`relative h-24 rounded-lg overflow-hidden border-2 transition-all ${
settings.selectedEnvironment === env.id
? 'border-violet-500 ring-2 ring-violet-500/20'
: 'border-zinc-200 dark:border-zinc-700 hover:border-violet-300'
}`}
>
<Image
src={env.image}
alt={env.name}
fill
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute bottom-1 left-2 right-2">
<p className="text-xs text-white font-medium truncate">{env.name}</p>
</div>
{settings.selectedEnvironment === env.id && (
<div className="absolute top-1 right-1 bg-violet-500 rounded-full p-1">
<Check className="w-3 h-3 text-white" />
</div>
)}
</button>
))}
</div>
</div>
)}
{/* Settings */}
<div className="space-y-3 border-t pt-4">
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs">Style</Label>
<Select value={settings.style} onValueChange={(value) => setSettings(prev => ({ ...prev, style: value }))}>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="realistic">Realistic</SelectItem>
<SelectItem value="artistic">Artistic</SelectItem>
<SelectItem value="cartoon">Cartoon</SelectItem>
<SelectItem value="vintage">Vintage</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label className="text-xs">Lighting</Label>
<Select value={settings.lighting} onValueChange={(value) => setSettings(prev => ({ ...prev, lighting: value }))}>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="natural">Natural</SelectItem>
<SelectItem value="studio">Studio</SelectItem>
<SelectItem value="dramatic">Dramatic</SelectItem>
<SelectItem value="soft">Soft</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label className="text-xs">Additional Instructions (Optional)</Label>
<Textarea
value={settings.customPrompt}
onChange={(e) => setSettings(prev => ({ ...prev, customPrompt: e.target.value }))}
placeholder="E.g., Make us look older, add sunset, include props..."
className="h-16 text-xs resize-none"
/>
</div>
</div>
{/* Generate Button */}
<Button
onClick={handleGenerate}
disabled={isGenerating || !settings.person1Image || (!settings.selectedEnvironment && !settings.environmentImage)}
className="w-full bg-gradient-to-r from-violet-500 to-fuchsia-500 hover:from-violet-600 hover:to-fuchsia-600"
>
{isGenerating ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Recreating... {progress}%
</>
) : (
<>
<Sparkles className="w-4 h-4 mr-2" />
Generate Recreation
</>
)}
</Button>
{error && (
<div className="p-3 bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-600 dark:text-red-400 flex items-center gap-2">
<AlertCircle className="w-4 h-4" />
{error}
</p>
</div>
)}
</div>
</CardContent>
</Card>
{/* Result Section */}
<Card className="border-zinc-200 dark:border-zinc-800">
<CardContent className="p-6">
<div className="space-y-4">
<h3 className="text-lg font-semibold">Your Recreated Photo</h3>
{generatedImage ? (
<div className="space-y-4">
<div className="relative aspect-square rounded-lg overflow-hidden bg-zinc-100 dark:bg-zinc-800">
<Image
src={generatedImage}
alt="Recreated photo"
fill
className="object-cover"
/>
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1">
<RefreshCw className="w-4 h-4 mr-2" />
Regenerate
</Button>
<Button className="flex-1 bg-gradient-to-r from-violet-500 to-fuchsia-500 hover:from-violet-600 hover:to-fuchsia-600">
<Download className="w-4 h-4 mr-2" />
Download
</Button>
</div>
<div className="grid grid-cols-2 gap-4 pt-4 border-t">
<div className="text-center">
<p className="text-xs text-zinc-500 mb-1">Original</p>
<div className="grid grid-cols-2 gap-1">
{settings.person1Image && (
<div className="relative aspect-square rounded overflow-hidden">
<Image
src={settings.person1Image}
alt="Person 1"
fill
className="object-cover"
/>
</div>
)}
{settings.person2Image && (
<div className="relative aspect-square rounded overflow-hidden">
<Image
src={settings.person2Image}
alt="Person 2"
fill
className="object-cover"
/>
</div>
)}
</div>
</div>
<div className="text-center">
<p className="text-xs text-zinc-500 mb-1">Environment</p>
<div className="relative aspect-square rounded overflow-hidden">
{settings.environmentImage ? (
<Image
src={settings.environmentImage}
alt="Custom environment"
fill
className="object-cover"
/>
) : settings.selectedEnvironment ? (
<Image
src={suggestedEnvironments.find(e => e.id === settings.selectedEnvironment)?.image || ""}
alt="Selected environment"
fill
className="object-cover"
/>
) : (
<div className="w-full h-full bg-zinc-200 dark:bg-zinc-700" />
)}
</div>
</div>
</div>
</div>
) : (
<div className="h-96 flex flex-col items-center justify-center text-center p-8 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
<div className="w-16 h-16 mb-4 rounded-full bg-gradient-to-r from-violet-500/20 to-fuchsia-500/20 flex items-center justify-center">
<Wand2 className="w-8 h-8 text-violet-500" />
</div>
<p className="text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
Your recreation will appear here
</p>
<p className="text-xs text-zinc-500 max-w-xs">
Upload photos of 1-2 people and select an environment to create your magical recreation
</p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
</div>
)
}