import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDropzone } from 'react-dropzone'; import { twMerge } from 'tailwind-merge'; import Cross from '../assets/cross.svg'; import ImagesIcon from '../assets/images.svg'; interface FileUploadProps { onUpload: (files: File[]) => void; onRemove?: (file: File) => void; multiple?: boolean; maxFiles?: number; maxSize?: number; // in bytes accept?: Record; // e.g. { 'image/*': ['.png', '.jpg'] } showPreview?: boolean; previewSize?: number; children?: React.ReactNode; className?: string; activeClassName?: string; acceptClassName?: string; rejectClassName?: string; uploadText?: string | { text: string; colorClass?: string }[]; dragActiveText?: string; fileTypeText?: string; sizeLimitText?: string; disabled?: boolean; validator?: (file: File) => { isValid: boolean; error?: string }; } export const FileUpload = ({ onUpload, onRemove, multiple = false, maxFiles = 1, maxSize = 5 * 1024 * 1024, accept = { 'image/*': ['.jpeg', '.png', '.jpg'] }, showPreview = false, previewSize = 80, children, className = 'border-2 border-dashed rounded-3xl p-6 text-center cursor-pointer transition-colors border-silver dark:border-[#7E7E7E]', activeClassName = 'border-blue-500 bg-blue-50', acceptClassName = 'border-green-500 dark:border-green-500 bg-green-50 dark:bg-green-50/10', rejectClassName = 'border-red-500 bg-red-50 dark:bg-red-500/10 dark:border-red-500', uploadText, dragActiveText, fileTypeText, sizeLimitText, disabled = false, validator, }: FileUploadProps) => { const { t } = useTranslation(); const [errors, setErrors] = useState([]); const [preview, setPreview] = useState(null); const [currentFile, setCurrentFile] = useState(null); const validateFile = (file: File) => { const defaultValidation = { isValid: true, error: '', }; if (validator) { const customValidation = validator(file); if (!customValidation.isValid) { return customValidation; } } if (file.size > maxSize) { return { isValid: false, error: t('components.fileUpload.fileSizeError', { size: maxSize / 1024 / 1024, }), }; } return defaultValidation; }; const onDrop = useCallback( (acceptedFiles: File[], fileRejections: any[]) => { setErrors([]); if (fileRejections.length > 0) { const newErrors = fileRejections .map(({ errors }) => errors.map((e: any) => e.message)) .flat(); setErrors(newErrors); return; } const validationResults = acceptedFiles.map(validateFile); const invalidFiles = validationResults.filter((r) => !r.isValid); if (invalidFiles.length > 0) { setErrors(invalidFiles.map((f) => f.error!)); return; } const filesToUpload = multiple ? acceptedFiles : [acceptedFiles[0]]; onUpload(filesToUpload); const file = acceptedFiles[0]; setCurrentFile(file); if (showPreview && file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = () => setPreview(reader.result as string); reader.readAsDataURL(file); } }, [onUpload, multiple, maxSize, validator], ); const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, } = useDropzone({ onDrop, multiple, maxFiles, maxSize, accept, disabled, }); const currentClassName = twMerge( 'border-2 border-dashed rounded-3xl p-8 text-center cursor-pointer transition-colors border-silver dark:border-[#7E7E7E]', className, isDragActive && activeClassName, isDragAccept && acceptClassName, isDragReject && rejectClassName, disabled && 'opacity-50 cursor-not-allowed', ); const handleRemove = () => { setPreview(null); setCurrentFile(null); if (onRemove && currentFile) onRemove(currentFile); }; const renderPreview = () => (
preview
); const renderUploadText = () => { if (Array.isArray(uploadText)) { return (

{uploadText.map((segment, i) => ( {segment.text} ))}

); } return (

{uploadText || t('components.fileUpload.clickToUpload')}

); }; const defaultContent = (
{showPreview && preview ? ( renderPreview() ) : (
)}
{isDragActive ? (

{dragActiveText || t('components.fileUpload.dropFiles')}

) : ( renderUploadText() )}

{fileTypeText || t('components.fileUpload.fileTypes')}{' '} {maxSize / 1024 / 1024} {sizeLimitText || t('components.fileUpload.sizeLimitUnit')}

); return (
{children || defaultContent} {errors.length > 0 && (
{errors.map((error, i) => (

{error}

))}
)}
); };