'use client'
import { useState, useRef } from 'react'
import Image from 'next/image'
import {
PhotoIcon,
PlusIcon,
XMarkIcon,
LinkIcon,
SparklesIcon,
ArrowUpTrayIcon
} from '@heroicons/react/24/outline'
import { useImageGeneration, type GeneratedImage } from '@/lib/hooks/useImageGeneration'
import { supabase } from '@/lib/supabase'
interface CoverImageSelectorProps {
currentImageUrl?: string | null
onImageSelect: (imageUrl: string | null) => void
bookId?: string
userId?: string
}
interface ImageOptionProps {
image: GeneratedImage
isSelected: boolean
onSelect: () => void
}
function ImageOption({ image, isSelected, onSelect }: ImageOptionProps) {
const [isLoading, setIsLoading] = useState(true)
return (
<button
type="button"
onClick={onSelect}
className={`relative aspect-square rounded-lg overflow-hidden border-2 transition-all duration-200 ${
isSelected
? 'border-teal-500 ring-2 ring-teal-500/50'
: 'border-slate-600 hover:border-slate-500'
}`}
>
{isLoading && (
<div className="absolute inset-0 bg-slate-700/50 animate-pulse flex items-center justify-center">
<div className="w-6 h-6 bg-white/20 rounded animate-pulse"></div>
</div>
)}
<Image
src={image.image_url}
alt={image.prompt}
fill
className="object-cover"
onLoad={() => setIsLoading(false)}
onError={() => setIsLoading(false)}
/>
{isSelected && (
<div className="absolute inset-0 bg-teal-500/20 flex items-center justify-center">
<div className="w-6 h-6 rounded-full bg-teal-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
</div>
)}
</button>
)
}
export default function CoverImageSelector({ currentImageUrl, onImageSelect, bookId, userId }: CoverImageSelectorProps) {
const [isExpanded, setIsExpanded] = useState(false)
const [urlInput, setUrlInput] = useState('')
const [showUrlInput, setShowUrlInput] = useState(false)
const [prompt, setPrompt] = useState('')
const [isUploading, setIsUploading] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const { images, isGenerating, generateImage } = useImageGeneration()
const handleUrlSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (urlInput.trim()) {
onImageSelect(urlInput.trim())
setUrlInput('')
setShowUrlInput(false)
setIsExpanded(false)
}
}
const handleGenerateImage = async (e: React.FormEvent) => {
e.preventDefault()
if (!prompt.trim() || isGenerating) return
const generatedImage = await generateImage(prompt, bookId)
if (generatedImage) {
onImageSelect(generatedImage.image_url)
setPrompt('')
setIsExpanded(false)
}
}
const handleRemoveImage = () => {
onImageSelect(null)
setIsExpanded(false)
}
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
// Validate file type
if (!file.type.startsWith('image/')) {
alert('Please select an image file')
return
}
// Validate file size (max 5MB)
if (file.size > 5 * 1024 * 1024) {
alert('Image size must be less than 5MB')
return
}
try {
setIsUploading(true)
// Generate unique filename
const fileExt = file.name.split('.').pop()
const fileName = `${Date.now()}-${Math.random().toString(36).substring(2)}.${fileExt}`
const filePath = userId ? `book-covers/${userId}/${fileName}` : `book-covers/${fileName}`
// Upload to Supabase Storage
const { data, error } = await supabase.storage
.from('book-covers')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false
})
if (error) {
console.error('Upload error:', error)
alert('Failed to upload image. Please try again.')
return
}
// Get public URL
const { data: { publicUrl } } = supabase.storage
.from('book-covers')
.getPublicUrl(data.path)
// Set the uploaded image URL
onImageSelect(publicUrl)
setIsExpanded(false)
} catch (error) {
console.error('Upload error:', error)
alert('Failed to upload image. Please try again.')
} finally {
setIsUploading(false)
// Reset file input
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}
}
const handleUploadClick = () => {
fileInputRef.current?.click()
}
if (!isExpanded) {
return (
<div className="space-y-3">
<div className="flex items-center space-x-2">
<PhotoIcon className="h-4 w-4 text-purple-400" />
<label className="text-base font-semibold text-white">Cover Image</label>
<span className="text-sm text-slate-400">(optional)</span>
</div>
<div className="flex items-center space-x-3">
{/* Current Image Display */}
<div
className={`relative w-20 h-20 rounded-lg border-2 border-dashed overflow-hidden transition-all duration-200 ${
currentImageUrl
? 'border-slate-600 bg-slate-800/50'
: 'border-slate-600 bg-slate-800/30 hover:border-slate-500 hover:bg-slate-800/50'
}`}
>
{currentImageUrl ? (
<>
<Image
src={currentImageUrl}
alt="Book cover"
fill
className="object-cover"
/>
<button
type="button"
onClick={handleRemoveImage}
className="absolute -top-1 -right-1 w-6 h-6 bg-red-500 hover:bg-red-600 rounded-full flex items-center justify-center transition-colors"
title="Remove cover image"
>
<XMarkIcon className="w-3 h-3 text-white" />
</button>
</>
) : (
<div className="w-full h-full flex items-center justify-center">
<PhotoIcon className="w-8 h-8 text-slate-500" />
</div>
)}
</div>
{/* Expand Button */}
<button
type="button"
onClick={() => setIsExpanded(true)}
className="flex items-center space-x-2 px-4 py-2 bg-slate-800/50 hover:bg-slate-700/50 text-slate-300 hover:text-white rounded-lg border border-slate-600 hover:border-slate-500 transition-all duration-200"
>
<PlusIcon className="w-4 h-4" />
<span className="text-sm font-medium">
{currentImageUrl ? 'Change Cover' : 'Add Cover'}
</span>
</button>
</div>
</div>
)
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<PhotoIcon className="h-4 w-4 text-purple-400" />
<label className="text-base font-semibold text-white">Choose Cover Image</label>
</div>
<button
type="button"
onClick={() => setIsExpanded(false)}
className="p-1 text-slate-400 hover:text-white rounded"
>
<XMarkIcon className="w-4 h-4" />
</button>
</div>
<div className="space-y-6">
{/* Generated Images Grid */}
{images.length > 0 && (
<div className="space-y-3">
<h4 className="text-sm font-medium text-slate-300">Your Generated Images</h4>
<div className="grid grid-cols-4 gap-3">
{images.slice(0, 8).map((image) => (
<ImageOption
key={image.id}
image={image}
isSelected={currentImageUrl === image.image_url}
onSelect={() => {
onImageSelect(image.image_url)
setIsExpanded(false)
}}
/>
))}
</div>
</div>
)}
{/* Upload Image */}
<div className="space-y-3">
<h4 className="text-sm font-medium text-slate-300">Upload Your Own Image</h4>
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileUpload}
className="hidden"
/>
<button
type="button"
onClick={handleUploadClick}
disabled={isUploading}
className="w-full px-4 py-3 bg-slate-800/50 hover:bg-slate-700/50 border border-slate-600 hover:border-slate-500 text-slate-300 hover:text-white rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 text-sm border-dashed"
>
{isUploading ? (
<>
<ArrowUpTrayIcon className="w-4 h-4 animate-pulse" />
<span>Uploading...</span>
</>
) : (
<>
<ArrowUpTrayIcon className="w-4 h-4" />
<span>Choose Image File</span>
</>
)}
</button>
<p className="text-xs text-slate-500 text-center">
Supports JPG, PNG, WebP up to 5MB
</p>
</div>
{/* Generate New Image */}
<div className="space-y-3">
<h4 className="text-sm font-medium text-slate-300">Generate New Cover</h4>
<form onSubmit={handleGenerateImage} className="space-y-3">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe your book cover... (e.g., 'Fantasy novel cover with dragons and mountains')"
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 resize-none text-sm"
rows={2}
disabled={isGenerating}
/>
<button
type="submit"
disabled={!prompt.trim() || isGenerating}
className="w-full px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 text-sm"
>
{isGenerating ? (
<>
<SparklesIcon className="w-4 h-4 animate-spin" />
<span>Generating...</span>
</>
) : (
<>
<SparklesIcon className="w-4 h-4" />
<span>Generate Image</span>
</>
)}
</button>
</form>
</div>
{/* URL Input */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium text-slate-300">Or Use Image URL</h4>
<button
type="button"
onClick={() => setShowUrlInput(!showUrlInput)}
className="text-xs text-slate-400 hover:text-slate-300"
>
{showUrlInput ? 'Hide' : 'Show'}
</button>
</div>
{showUrlInput && (
<form onSubmit={handleUrlSubmit} className="space-y-3">
<input
type="url"
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
placeholder="https://example.com/image.jpg"
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500 text-sm"
/>
<button
type="submit"
disabled={!urlInput.trim()}
className="w-full px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white font-medium rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 text-sm"
>
<LinkIcon className="w-4 h-4" />
<span>Use This URL</span>
</button>
</form>
)}
</div>
{/* Actions */}
<div className="flex space-x-3 pt-2">
<button
type="button"
onClick={handleRemoveImage}
className="flex-1 px-4 py-2 bg-red-600/20 hover:bg-red-600/30 text-red-400 font-medium rounded-lg transition-colors duration-200 text-sm"
disabled={!currentImageUrl}
>
Remove Cover
</button>
<button
type="button"
onClick={() => setIsExpanded(false)}
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white font-medium rounded-lg transition-colors duration-200 text-sm"
>
Done
</button>
</div>
</div>
</div>
)
}