'use client' import React, { useState, useRef, useCallback } from 'react' import { Button } from '@/components/ui/button' import { Upload, Image as ImageIcon, X } from 'lucide-react' import { cn } from '@/lib/utils' import Image from 'next/image' import { useToast } from '@/hooks/use-toast' interface ImageUploadProps { value?: string | File onChange: (value: File | null) => void className?: string label?: string disabled?: boolean aspectRatio?: 'video' | 'square' | 'auto' currentImage?: string } const ImageUpload: React.FC = ({ value, onChange, className, label = 'Upload Cover Image', disabled = false, aspectRatio = 'video', }) => { const [isUploading, setIsUploading] = useState(false) const [previewUrl, setPreviewUrl] = useState(null) const [isDragging, setIsDragging] = useState(false) const fileInputRef = useRef(null) const { toast } = useToast() // Create preview URL when value changes React.useEffect(() => { if (value instanceof File) { const url = URL.createObjectURL(value) setPreviewUrl(url) return () => URL.revokeObjectURL(url) } else if (typeof value === 'string') { setPreviewUrl(value) } else { setPreviewUrl(null) } }, [value]) const validateFile = React.useCallback( (file: File): boolean => { // Check file type if (!file.type.startsWith('image/')) { toast({ title: 'Invalid file type', description: 'Please select an image file (JPEG, PNG, GIF, WebP)', variant: 'destructive', }) return false } // Check file size (5MB limit) const maxSize = 5 * 1024 * 1024 // 5MB if (file.size > maxSize) { toast({ title: 'File too large', description: 'Please select an image smaller than 5MB', variant: 'destructive', }) return false } return true }, [toast] ) const handleFile = useCallback( async (file: File) => { if (!validateFile(file)) return try { setIsUploading(true) onChange(file) toast({ title: 'Image selected', description: 'Cover image has been updated', }) } catch (error) { console.error('Failed to handle file:', error) toast({ title: 'Upload failed', description: 'Failed to process the image', variant: 'destructive', }) } finally { setIsUploading(false) } }, [onChange, toast, validateFile] ) const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return await handleFile(file) // Clear the input value to allow uploading the same file again if (fileInputRef.current) { fileInputRef.current.value = '' } } const handleClick = (e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() if (!disabled) { fileInputRef.current?.click() } } const handleRemove = (e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() onChange(null) setPreviewUrl(null) toast({ title: 'Image removed', description: 'Cover image has been removed', }) } const handleDragOver = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (!disabled) { setIsDragging(true) } } const handleDragLeave = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(false) } const handleDrop = async (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(false) if (disabled) return const files = Array.from(e.dataTransfer.files) const file = files[0] if (file) { await handleFile(file) } } const getAspectRatioClass = () => { switch (aspectRatio) { case 'video': return 'aspect-video' case 'square': return 'aspect-square' default: return 'min-h-[200px]' } } return (
{previewUrl ? (
Cover image preview
) : (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() handleClick(e as unknown as React.MouseEvent) } }} onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} role="button" tabIndex={disabled ? -1 : 0} aria-label={`${label} - click to browse or drag and drop`} > {isUploading ? (

Uploading image...

) : (
{isDragging ? ( ) : ( )}

{label}

{isDragging ? 'Drop your image here' : 'Click to browse or drag and drop'}

Supports JPEG, PNG, GIF, WebP (max 5MB)

)}
)}
) } export default ImageUpload