"use client"; import { useState, useCallback, useMemo, useRef } from 'react'; import { createPortal } from 'react-dom'; interface QuickAddSubmitPayload { input: string; mode: 'link' | 'text'; description?: string; } interface QuickAddInputProps { onSubmit: (payload: QuickAddSubmitPayload) => Promise; isOpen?: boolean; onClose?: () => void; } type SourceType = 'link' | 'text' | 'upload'; function detectSourceType(raw: string): Exclude { const trimmed = raw.trim(); if (!trimmed) return 'text'; const isSingleLine = !trimmed.includes('\n'); if (isSingleLine) { if (/^https?:\/\//i.test(trimmed)) return 'link'; } return 'text'; } const SOURCE_LABELS: Record = { link: 'Link', text: 'Text', upload: 'Upload', }; const SOURCE_COLORS: Record = { link: '#3b82f6', text: '#22c55e', upload: '#f59e0b', }; export default function QuickAddInput({ onSubmit, isOpen, onClose }: QuickAddInputProps) { const [input, setInput] = useState(''); const [isPosting, setIsPosting] = useState(false); const [isExpandedInternal, setIsExpandedInternal] = useState(false); const [dragOver, setDragOver] = useState(false); const [uploadedFile, setUploadedFile] = useState(null); const [uploadError, setUploadError] = useState(null); const [sourceOverride, setSourceOverride] = useState | null>(null); const fileInputRef = useRef(null); const isControlled = isOpen !== undefined; const isExpanded = isControlled ? isOpen : isExpandedInternal; const setIsExpanded = isControlled ? (value: boolean) => { if (!value && onClose) onClose(); } : setIsExpandedInternal; const detectedSource = useMemo(() => detectSourceType(input), [input]); const effectiveSource: SourceType = uploadedFile ? 'upload' : (sourceOverride ?? detectedSource); const handleFileUpload = useCallback(async (file: File) => { setIsPosting(true); setUploadError(null); try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/extract/pdf/upload', { method: 'POST', body: formData, }); const result = await response.json(); if (!response.ok || !result.success) { throw new Error(result.error || 'Upload failed'); } setUploadedFile(null); setInput(''); setSourceOverride(null); setIsExpanded(false); } catch (error) { console.error('[QuickAddInput] Upload error:', error); setUploadError(error instanceof Error ? error.message : 'Upload failed'); } finally { setIsPosting(false); } }, []); const handleSubmit = async () => { if (uploadedFile) { await handleFileUpload(uploadedFile); return; } if (!input.trim() || isPosting) return; setIsPosting(true); try { await onSubmit({ input: input.trim(), mode: effectiveSource === 'link' ? 'link' : 'text', }); setInput(''); setSourceOverride(null); setIsExpanded(false); } catch (error) { console.error('[QuickAddInput] Submit error:', error); } finally { setIsPosting(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); handleSubmit(); } if (e.key === 'Escape') { setIsExpanded(false); setInput(''); setUploadedFile(null); setUploadError(null); setSourceOverride(null); } }; const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragOver(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragOver(false); }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragOver(false); setUploadError(null); const file = e.dataTransfer?.files[0]; if (!file) return; if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { setUploadError('Only PDF files are supported'); return; } if (file.size > 50 * 1024 * 1024) { setUploadError(`File too large (${Math.round(file.size / 1024 / 1024)}MB). Max 50MB.`); return; } setUploadedFile(file); setInput(''); setSourceOverride(null); }, []); const hasContent = input.trim() || uploadedFile; const handleClose = () => { setIsExpanded(false); setInput(''); setUploadedFile(null); setUploadError(null); setSourceOverride(null); }; const handleSourceSelect = (source: Exclude) => { setSourceOverride(source); setUploadedFile(null); setUploadError(null); }; const handleUploadClick = () => { setUploadError(null); fileInputRef.current?.click(); }; const handleFileInputChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { setUploadError('Only PDF files are supported'); event.target.value = ''; return; } if (file.size > 50 * 1024 * 1024) { setUploadError(`File too large (${Math.round(file.size / 1024 / 1024)}MB). Max 50MB.`); event.target.value = ''; return; } setUploadedFile(file); setInput(''); setSourceOverride(null); event.target.value = ''; }; if (!isExpanded) { if (isControlled) return null; return ( ); } const modalContent = (
e.stopPropagation()} > {uploadedFile && (
{uploadedFile.name} {uploadedFile.size < 1024 * 1024 ? `${Math.round(uploadedFile.size / 1024)} KB` : `${(uploadedFile.size / 1024 / 1024).toFixed(1)} MB`}
)} {uploadError && (
{uploadError}
)} {!uploadedFile && (
{dragOver && (
Drop PDF
)}