'use client'; import { useState, useCallback, useId, type ReactNode } from 'react'; import { useDropzone } from 'react-dropzone'; import { UploadIcon } from '@/components/icons/Icons'; import { useDocuments } from '@/contexts/DocumentContext'; import { useFeatureFlag } from '@/contexts/RuntimeConfigContext'; import { dropzoneSurfaceClass } from '@/components/ui'; interface DocumentUploaderProps { className?: string; variant?: 'default' | 'compact' | 'overlay'; children?: ReactNode; onUploadBatchChange?: (state: UploadBatchState) => void; onClick?: () => void; } export interface UploadBatchState { uploaderId: string; isActive: boolean; totalFiles: number; completedFiles: number; phase: 'uploading'; currentFileName: string | null; } export function DocumentUploader({ className = '', variant = 'default', children, onUploadBatchChange, onClick, }: DocumentUploaderProps) { const uploaderId = useId(); const enableDocx = useFeatureFlag('enableDocxConversion'); const { uploadDocuments, } = useDocuments(); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(null); const emitBatchState = useCallback((state: Omit) => { onUploadBatchChange?.({ uploaderId, ...state }); }, [onUploadBatchChange, uploaderId]); const onDrop = useCallback(async (acceptedFiles: File[]) => { if (!acceptedFiles || acceptedFiles.length === 0) return; const totalFiles = acceptedFiles.length; let completedFiles = 0; setIsUploading(true); setError(null); emitBatchState({ isActive: true, totalFiles, completedFiles, phase: 'uploading', currentFileName: acceptedFiles[0]?.name ?? null, }); try { await uploadDocuments(acceptedFiles); completedFiles = acceptedFiles.length; } catch (err) { setError('Failed to upload file. Please try again.'); console.error('Upload error:', err); } finally { setIsUploading(false); emitBatchState({ isActive: false, totalFiles, completedFiles, phase: 'uploading', currentFileName: null, }); } }, [uploadDocuments, emitBatchState]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'application/pdf': ['.pdf'], 'application/epub+zip': ['.epub'], 'text/plain': ['.txt'], 'text/markdown': ['.md'], ...(enableDocx ? { 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'] } : {}) }, multiple: true, disabled: isUploading, noClick: variant === 'overlay' || !!onClick, noKeyboard: variant === 'overlay' || !!onClick }); const isDisabled = isUploading; if (variant === 'overlay') { const rootProps = getRootProps(); return (
{children} {isDragActive && (

Drop files here to upload

{enableDocx ? 'Accepts PDF, EPUB, TXT, MD, or DOCX' : 'Accepts PDF, EPUB, TXT, or MD'}

{error && (

Upload failed: {error} — try again.

)}
)} {!isDragActive && error && (
Upload failed: {error} — try again.
)}
); } return (
{ if (onClick) { e.stopPropagation(); onClick(); } }} className={dropzoneSurfaceClass({ variant: variant === 'compact' ? 'compact' : 'default', active: isDragActive, disabled: isDisabled, className, })} > {variant === 'compact' ? (
{isUploading ? (

Uploading…

) : (

{isDragActive ? 'Drop files here' : 'Upload documents'}

{error &&

{error}

}
)}
) : (
{isUploading ? (

Uploading file...

) : ( <>

{isDragActive ? 'Drop your file(s) here' : 'Drop your file(s) here, or click to select'}

{enableDocx ? 'PDF, EPUB, TXT, MD, or DOCX files are accepted' : 'PDF, EPUB, TXT, or MD files are accepted'}

{error &&

{error}

} )}
)}
); }