import mediaUpload, { UPLOAD_ABORTED_ERROR_MSG } from '@/services/media-upload.service' import { useRef } from 'react' import { toast } from 'sonner' import logger from '@/lib/logger' export default function Uploader({ children, onUploadSuccess, onUploadStart, onUploadEnd, onProgress, onUploadCompressPhase, onUploadCompressProgress, className, accept = 'image/*', maxFileSizeMb, maxCompressedSizeMb }: { children: React.ReactNode onUploadSuccess: (result: { url: string; tags: string[][]; file?: File }) => void onUploadStart?: (file: File, cancel: () => void) => void onUploadEnd?: (file: File) => void onProgress?: (file: File, progress: number) => void /** After local compression (before network upload). */ onUploadCompressPhase?: (file: File, phase: 'compressing' | 'uploading') => void /** 0–100 during local compression only. */ onUploadCompressProgress?: (file: File, percent: number) => void className?: string accept?: string /** Reject files whose original size exceeds this limit (before compression). */ maxFileSizeMb?: number /** Reject when compressed size exceeds this limit (after local encode, before upload). */ maxCompressedSizeMb?: number }) { const fileInputRef = useRef(null) const handleFileChange = async (event: React.ChangeEvent) => { if (!event.target.files) return const abortControllerMap = new Map() for (const file of event.target.files) { const abortController = new AbortController() abortControllerMap.set(file, abortController) onUploadStart?.(file, () => abortController.abort()) } for (const file of event.target.files) { if (maxFileSizeMb !== undefined && file.size > maxFileSizeMb * 1024 * 1024) { toast.error( `"${file.name}" is too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Maximum is ${maxFileSizeMb} MB.` ) onUploadEnd?.(file) continue } try { const abortController = abortControllerMap.get(file) const result = await mediaUpload.upload(file, { onProgress: (p) => onProgress?.(file, p), signal: abortController?.signal, onCompressStart: () => onUploadCompressPhase?.(file, 'compressing'), onCompressEnd: () => onUploadCompressPhase?.(file, 'uploading'), onCompressProgress: (p) => onUploadCompressProgress?.(file, p), maxCompressedSizeMb }) onUploadSuccess({ ...result, file }) onUploadEnd?.(file) } catch (error) { logger.error('Error uploading file', { error, file: file.name }) const message = (error as Error).message if (message !== UPLOAD_ABORTED_ERROR_MSG) { toast.error(`Failed to upload file: ${message}`) } if (fileInputRef.current) { fileInputRef.current.value = '' } onUploadEnd?.(file) } } } const handleUploadClick = () => { if (fileInputRef.current) { fileInputRef.current.value = '' // clear the value so that the same file can be uploaded again fileInputRef.current.click() } } return (
{children}
) }