import { cn } from '@/lib/utils' import { type UseSupabaseUploadReturn } from '@/hooks/use-supabase-upload' import { Button } from '@/components/ui/button' import { CheckCircle, File, Loader2, Upload, X } from 'lucide-react' import { createContext, type PropsWithChildren, useCallback, useContext } from 'react' 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 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 exceedMaxFiles = files.length > maxFiles const handleRemoveFile = useCallback( (fileName: string) => { setFiles(files.filter((file) => file.name !== fileName)) }, [files, setFiles] ) if (isSuccess) { return (

Successfully uploaded {files.length} file{files.length > 1 ? 's' : ''}

) } 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/') ? (
{file.name}
) : (
)}

{file.name}

{file.errors.length > 0 ? (

{file.errors .map((e) => e.message.startsWith('File is larger than') ? `File is larger than ${formatBytes(maxFileSize, 2)} (Size: ${formatBytes(file.size, 2)})` : e.message ) .join(', ')}

) : loading && !isSuccessfullyUploaded ? (

Uploading file...

) : !!fileError ? (

Failed to upload: {fileError.message}

) : isSuccessfullyUploaded ? (

Successfully uploaded file

) : (

{formatBytes(file.size, 2)}

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

You may upload only up to {maxFiles} files, please remove {files.length - maxFiles} file {files.length - maxFiles > 1 ? 's' : ''}.

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

Upload{!!maxFiles && maxFiles > 1 ? ` ${maxFiles}` : ''} file {!maxFiles || maxFiles > 1 ? 's' : ''}

Drag and drop or{' '} inputRef.current?.click()} className="underline cursor-pointer transition hover:text-foreground" > select {maxFiles === 1 ? `file` : 'files'} {' '} to upload

{maxFileSize !== Number.POSITIVE_INFINITY && (

Maximum file size: {formatBytes(maxFileSize, 2)}

)}
) } 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 }