'use client'
import { useState } from 'react'
import Image from 'next/image'
import {
IoImageOutline,
IoDownloadOutline,
IoTrashOutline,
IoSettingsOutline,
IoCloseOutline,
IoSparklesOutline,
IoTimeOutline,
IoSendOutline
} from 'react-icons/io5'
import { useImageGeneration, type GeneratedImage, type ImageGenerationSettings } from '@/lib/hooks/useImageGeneration'
interface ImageGalleryProps {
isOpen: boolean
onClose: () => void
bookId?: string
}
interface AdvancedSettingsProps {
settings: ImageGenerationSettings
onSettingsChange: (settings: Partial<ImageGenerationSettings>) => void
isOpen: boolean
onToggle: () => void
}
function AdvancedSettings({ settings, onSettingsChange, isOpen, onToggle }: AdvancedSettingsProps) {
if (!isOpen) {
return (
<button
onClick={onToggle}
className="flex items-center gap-2 px-3 py-2 text-sm text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-lg transition-all duration-200"
>
<IoSettingsOutline className="w-4 h-4" />
Advanced Settings
</button>
)
}
return (
<div className="space-y-4 p-4 bg-slate-800/50 rounded-xl border border-slate-700/50">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-200">Advanced Settings</h3>
<button
onClick={onToggle}
className="p-1 text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded transition-colors"
>
<IoCloseOutline className="w-4 h-4" />
</button>
</div>
{/* Size Settings */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Image Size</label>
<div className="grid grid-cols-3 gap-2">
{[
{ value: '1024x1024', label: 'Square', desc: '1:1' },
{ value: '1536x1024', label: 'Landscape', desc: '3:2' },
{ value: '1024x1536', label: 'Portrait', desc: '2:3' }
].map((size) => (
<button
key={size.value}
onClick={() => onSettingsChange({ size: size.value as any })}
className={`p-3 text-center rounded-lg border transition-all duration-200 ${
settings.size === size.value
? 'border-blue-500 bg-blue-500/10 text-blue-400'
: 'border-slate-600 hover:border-slate-500 text-slate-300'
}`}
>
<div className="font-medium text-sm">{size.label}</div>
<div className="text-xs text-slate-500">{size.desc}</div>
</button>
))}
</div>
</div>
{/* Quality Settings */}
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">Quality</label>
<div className="grid grid-cols-2 gap-2">
{[
{ value: 'auto', label: 'Auto', desc: 'Optimal quality' },
{ value: 'high', label: 'High', desc: 'Best quality' }
].map((quality) => (
<button
key={quality.value}
onClick={() => onSettingsChange({ quality: quality.value as any })}
className={`p-3 text-center rounded-lg border transition-all duration-200 ${
settings.quality === quality.value
? 'border-blue-500 bg-blue-500/10 text-blue-400'
: 'border-slate-600 hover:border-slate-500 text-slate-300'
}`}
>
<div className="font-medium text-sm">{quality.label}</div>
<div className="text-xs text-slate-500">{quality.desc}</div>
</button>
))}
</div>
</div>
</div>
)
}
function ImageCard({ image, onDelete }: { image: GeneratedImage; onDelete: (id: string) => void }) {
const [isImageLoading, setIsImageLoading] = useState(true)
const handleDownload = async () => {
try {
const response = await fetch(image.image_url)
const blob = await response.blob()
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `bookwiz-generated-${image.id}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
} catch (error) {
// Silently fail for download errors
}
}
return (
<div className="group relative bg-slate-800/50 rounded-xl border border-slate-700/50 overflow-hidden hover:border-slate-600/50 transition-all duration-200">
<div className="aspect-square relative">
{isImageLoading && (
<div className="absolute inset-0 bg-slate-700/50 animate-pulse flex items-center justify-center">
<div className="w-8 h-8 bg-white/20 rounded animate-pulse"></div>
</div>
)}
<Image
src={image.image_url}
alt={image.prompt}
fill
className="object-cover"
onLoad={() => setIsImageLoading(false)}
onError={() => setIsImageLoading(false)}
/>
{/* Overlay with actions */}
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center gap-2">
<button
onClick={handleDownload}
className="p-2 bg-white/20 backdrop-blur-sm rounded-lg hover:bg-white/30 transition-colors"
title="Download image"
>
<IoDownloadOutline className="w-5 h-5 text-white" />
</button>
<button
onClick={() => onDelete(image.id)}
className="p-2 bg-red-500/20 backdrop-blur-sm rounded-lg hover:bg-red-500/30 transition-colors"
title="Delete image"
>
<IoTrashOutline className="w-5 h-5 text-red-400" />
</button>
</div>
</div>
{/* Image info */}
<div className="p-3">
<p className="text-sm text-slate-300 line-clamp-2 mb-2">{image.prompt}</p>
<div className="flex items-center justify-between text-xs text-slate-500">
<span className="flex items-center gap-1">
<IoTimeOutline className="w-3 h-3" />
{new Date(image.created_at).toLocaleDateString()}
</span>
<span className="flex items-center gap-1">
{image.size} • {image.quality}
</span>
</div>
</div>
</div>
)
}
export default function ImageGallery({ isOpen, onClose, bookId }: ImageGalleryProps) {
const [prompt, setPrompt] = useState('')
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false)
const {
images,
isGenerating,
isLoading,
error,
settings,
generateImage,
deleteImage,
updateSettings,
clearError
} = useImageGeneration()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!prompt.trim() || isGenerating) return
const generatedImage = await generateImage(prompt, bookId)
if (generatedImage) {
setPrompt('') // Clear the prompt on successful generation
}
}
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm">
<div className="flex">
{/* Click outside to close */}
<div className="flex-1" onClick={onClose} />
{/* Sidebar */}
<div className="w-96 h-screen bg-slate-900/95 backdrop-blur-xl border-l border-slate-700/50 flex flex-col">
{/* Header */}
<div className="p-6 border-b border-slate-700/50">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center">
<IoImageOutline className="w-5 h-5 text-white" />
</div>
<div>
<h2 className="text-lg font-bold text-white">Image Gallery</h2>
<p className="text-sm text-slate-400">AI-generated images</p>
</div>
</div>
<button
onClick={onClose}
className="p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-lg transition-colors"
>
<IoCloseOutline className="w-5 h-5" />
</button>
</div>
{/* Prompt Input */}
<form onSubmit={handleSubmit} className="space-y-3">
<div>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe the image you want to generate..."
className="w-full p-3 bg-slate-800/50 border border-slate-700/50 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/50 resize-none"
rows={3}
disabled={isGenerating}
/>
</div>
<div className="flex items-center justify-between">
<AdvancedSettings
settings={settings}
onSettingsChange={updateSettings}
isOpen={showAdvancedSettings}
onToggle={() => setShowAdvancedSettings(!showAdvancedSettings)}
/>
<button
type="submit"
disabled={!prompt.trim() || isGenerating}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-purple-500 to-pink-500 text-white font-semibold rounded-lg hover:from-purple-600 hover:to-pink-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
>
{isGenerating ? (
<>
<IoSparklesOutline className="w-4 h-4 animate-spin" />
Generating...
</>
) : (
<>
<IoSendOutline className="w-4 h-4" />
Generate
</>
)}
</button>
</div>
</form>
{/* Error Display */}
{error && (
<div className="mt-3 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
<div className="flex items-center justify-between">
<p className="text-sm text-red-400">{error}</p>
<button
onClick={clearError}
className="text-red-400 hover:text-red-300"
>
<IoCloseOutline className="w-4 h-4" />
</button>
</div>
</div>
)}
</div>
{/* Images Grid */}
<div className="flex-1 overflow-y-auto p-4">
{isLoading ? (
<div className="flex items-center justify-center h-32">
<div className="flex items-center gap-2 text-slate-400">
<IoSparklesOutline className="w-5 h-5 animate-spin" />
Loading images...
</div>
</div>
) : images.length === 0 ? (
<div className="flex flex-col items-center justify-center h-32 text-center">
<IoImageOutline className="w-12 h-12 text-slate-600 mb-3" />
<p className="text-slate-400 mb-1">No images yet</p>
<p className="text-sm text-slate-500">Generate your first AI image above</p>
</div>
) : (
<div className="grid grid-cols-1 gap-4">
{images.map((image) => (
<ImageCard
key={image.id}
image={image}
onDelete={deleteImage}
/>
))}
</div>
)}
</div>
</div>
</div>
</div>
)
}