'use client'; import { type PropsWithChildren, createContext, useCallback, useContext, } from 'react'; import { CheckCircle, File, Loader2, Upload, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { type UseSupabaseUploadReturn } from '../hooks/use-supabase-upload'; import { cn } from '../lib/utils'; import { Button } from '../shadcn/button'; import { Trans } from './trans'; export const formatBytes = ( bytes: number, decimals = 2, size?: 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB', ) => { const k = 1000; const dm = decimals < 0 ? 0 : decimals; const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; if (bytes === 0 || bytes === undefined) { return size !== undefined ? `0 ${size}` : '0 bytes'; } const i = size !== undefined ? sizes.indexOf(size) : Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; }; type DropzoneContextType = Omit< UseSupabaseUploadReturn, 'getRootProps' | 'getInputProps' >; const DropzoneContext = createContext( undefined, ); type DropzoneProps = UseSupabaseUploadReturn & { className?: string; }; const Dropzone = ({ className, children, getRootProps, getInputProps, ...restProps }: PropsWithChildren) => { const isSuccess = restProps.isSuccess; const isActive = restProps.isDragActive; const isInvalid = (restProps.isDragActive && restProps.isDragReject) || (restProps.errors.length > 0 && !restProps.isSuccess) || restProps.files.some((file) => file.errors.length !== 0); return (
{children}
); }; const DropzoneContent = ({ className }: { className?: string }) => { const { files, setFiles, onUpload, loading, successes, errors, maxFileSize, maxFiles, isSuccess, } = useDropzoneContext(); const { t } = useTranslation(); const exceedMaxFiles = files.length > maxFiles; const handleRemoveFile = useCallback( (fileName: string) => { setFiles(files.filter((file) => file.name !== fileName)); }, [files, setFiles], ); if (isSuccess) { return (

); } return (
{files.map((file, idx) => { const fileError = errors.find((e) => e.name === file.name); const isSuccessfullyUploaded = !!successes.find((e) => e === file.name); return (
{file.type.startsWith('image/') ? (
{/* eslint-disable-next-line @next/next/no-img-element */} {file.name}
) : (
)}

{file.name}

{file.errors.length > 0 ? (

{file.errors .map((e) => e.message.startsWith('File is larger than') ? t('common:dropzone.errorMessageFileSizeTooLarge', { size: formatBytes(file.size, 2), maxSize: formatBytes(maxFileSize, 2), }) : e.message, ) .join(', ')}

) : loading && !isSuccessfullyUploaded ? (

) : fileError ? (

) : isSuccessfullyUploaded ? (

) : (

{formatBytes(file.size, 2)}

)}
{!loading && !isSuccessfullyUploaded && ( )}
); })} {exceedMaxFiles && (

)} {files.length > 0 && !exceedMaxFiles && (
)}
); }; const DropzoneEmptyState = ({ className }: { className?: string }) => { const { maxFiles, maxFileSize, inputRef, isSuccess } = useDropzoneContext(); if (isSuccess) { return null; } return (

{' '} inputRef.current?.click()} className="hover:text-foreground cursor-pointer underline transition" > {' '}

{maxFileSize !== Number.POSITIVE_INFINITY && (

)}
); }; const useDropzoneContext = () => { const context = useContext(DropzoneContext); if (!context) { throw new Error('useDropzoneContext must be used within a Dropzone'); } return context; }; export { Dropzone, DropzoneContent, DropzoneEmptyState, useDropzoneContext };