import React, { useCallback, useState } from 'react'; import { useDropzone } from 'react-dropzone'; import { Upload, X, Image as ImageIcon, Loader2 } from 'lucide-react'; import { Button } from '../ui/Button'; import Typography from '../ui/Typography'; import { cn } from '../../lib/utils'; interface ProfileImageUploadProps { value?: string; onChange: (value: string | undefined) => void; disabled?: boolean; } const MAX_FILE_SIZE = 5 * 1024 * 1024; const ACCEPTED_FORMATS = { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg'], 'image/gif': ['.gif'], 'image/webp': ['.webp'], }; /** * Profile image upload component with drag-and-drop */ export function ProfileImageUpload({ value, onChange, disabled = false, }: ProfileImageUploadProps) { const [uploadError, setUploadError] = useState(null); const [isUploading, setIsUploading] = useState(false); /** * Handle file drop/selection */ const onDrop = useCallback( async (acceptedFiles: File[]) => { setUploadError(null); if (acceptedFiles.length === 0) { return; } const file = acceptedFiles[0]; if (file.size > MAX_FILE_SIZE) { setUploadError('Image size must be less than 5MB'); return; } try { setIsUploading(true); const reader = new FileReader(); reader.onload = (e) => { const base64 = e.target?.result as string; onChange(base64); setIsUploading(false); }; reader.onerror = () => { setUploadError('Failed to read image file'); setIsUploading(false); }; reader.readAsDataURL(file); } catch (error) { setUploadError('Failed to process image'); setIsUploading(false); } }, [onChange] ); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: ACCEPTED_FORMATS, maxFiles: 1, disabled: disabled || isUploading, multiple: false, }); /** * Remove uploaded image */ const handleRemove = () => { onChange(undefined); setUploadError(null); }; return (
{value ? (
Profile {!disabled && (
)}
) : (
{isUploading ? ( ) : ( )}
{isDragActive ? 'Drop image here' : 'Click or drag image to upload'} PNG, JPG, GIF or WebP up to 5MB
)} {uploadError && ( {uploadError} )}
); }