"use client" import { useState, useEffect } from 'react' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import Image from 'next/image' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' import { useToast } from '@/hooks/use-toast' import { Loader2, Upload, Edit, Trash2, Eye, EyeOff, X, Plus } from 'lucide-react' import type { PortfolioImage } from '@/types/database' const imageEditSchema = z.object({ caption: z.string().optional(), tags: z.array(z.string()), isPublic: z.boolean(), }) type ImageEditData = z.infer interface PortfolioManagerProps { artistId: string onImagesUpdate?: () => void } export function PortfolioManager({ artistId, onImagesUpdate }: PortfolioManagerProps) { const { toast } = useToast() const [images, setImages] = useState([]) const [loading, setLoading] = useState(true) const [uploading, setUploading] = useState(false) const [editingImage, setEditingImage] = useState(null) const [deletingImage, setDeletingImage] = useState(null) const [newTag, setNewTag] = useState('') const { register, handleSubmit, watch, setValue, reset, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(imageEditSchema), defaultValues: { caption: '', tags: [], isPublic: true, } }) const tags = watch('tags') const fetchImages = async () => { try { setLoading(true) const response = await fetch(`/api/artists/${artistId}`) if (!response.ok) throw new Error('Failed to fetch images') const data = await response.json() setImages(data.portfolioImages || []) } catch (error) { console.error('Error fetching images:', error) toast({ title: 'Error', description: 'Failed to load portfolio images', variant: 'destructive', }) } finally { setLoading(false) } } useEffect(() => { fetchImages() }, [artistId]) const handleFileUpload = async (files: FileList | null) => { if (!files || files.length === 0) return setUploading(true) const formData = new FormData() formData.append('artistId', artistId) Array.from(files).forEach((file) => { formData.append('files', file) }) try { const response = await fetch('/api/portfolio', { method: 'POST', body: formData, }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Upload failed') } toast({ title: 'Success', description: 'Images uploaded successfully', }) fetchImages() onImagesUpdate?.() } catch (error) { console.error('Upload error:', error) toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to upload images', variant: 'destructive', }) } finally { setUploading(false) } } const openEditDialog = (image: PortfolioImage) => { setEditingImage(image) reset({ caption: image.caption || '', tags: image.tags || [], isPublic: image.isPublic, }) } const closeEditDialog = () => { setEditingImage(null) reset() } const addTag = () => { if (newTag.trim() && !tags.includes(newTag.trim())) { setValue('tags', [...tags, newTag.trim()]) setNewTag('') } } const removeTag = (tag: string) => { setValue('tags', tags.filter(t => t !== tag)) } const onSubmitEdit = async (data: ImageEditData) => { if (!editingImage) return try { const response = await fetch(`/api/portfolio/${editingImage.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Update failed') } toast({ title: 'Success', description: 'Image updated successfully', }) closeEditDialog() fetchImages() onImagesUpdate?.() } catch (error) { console.error('Update error:', error) toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to update image', variant: 'destructive', }) } } const handleDelete = async () => { if (!deletingImage) return try { const response = await fetch(`/api/portfolio/${deletingImage.id}`, { method: 'DELETE', }) if (!response.ok) { const error = await response.json() throw new Error(error.error || 'Delete failed') } toast({ title: 'Success', description: 'Image deleted successfully', }) setDeletingImage(null) fetchImages() onImagesUpdate?.() } catch (error) { console.error('Delete error:', error) toast({ title: 'Error', description: error instanceof Error ? error.message : 'Failed to delete image', variant: 'destructive', }) } } if (loading) { return ( Portfolio Images
) } return ( <> Portfolio Images ({images.length}) {/* Upload Section */}
handleFileUpload(e.target.files)} disabled={uploading} />
{uploading && (
)}
{/* Images Grid */} {images.length === 0 ? (

No portfolio images yet. Upload some to get started!

) : (
{images.map((image) => (
{/* Image */} {image.caption {/* Visibility Badge */}
{image.isPublic ? ( <> Public ) : ( <> Private )}
{/* Hover Overlay */}
{/* Caption */} {image.caption && (

{image.caption}

)}
))}
)}
{/* Edit Dialog */} !open && closeEditDialog()}> Edit Portfolio Image Update image details, tags, and visibility {editingImage && (
{/* Image Preview */}
{editingImage.caption
{/* Caption */}