|
|
'use client'; |
|
|
|
|
|
import { useState, useCallback } from 'react'; |
|
|
import { useDropzone } from 'react-dropzone'; |
|
|
import { motion, AnimatePresence } from 'framer-motion'; |
|
|
import { Upload, FileText, X, Loader2, CheckCircle2, AlertCircle } from 'lucide-react'; |
|
|
import { Button } from '@/components/ui/button'; |
|
|
import { Card } from '@/components/ui/card'; |
|
|
import { Progress } from '@/components/ui/progress'; |
|
|
import { cn } from '@/lib/utils'; |
|
|
import { formatFileSize } from '@/lib/api'; |
|
|
|
|
|
interface PatentUploadProps { |
|
|
onUpload: (file: File) => Promise<void>; |
|
|
uploading?: boolean; |
|
|
error?: string | null; |
|
|
} |
|
|
|
|
|
export function PatentUpload({ onUpload, uploading = false, error = null }: PatentUploadProps) { |
|
|
const [file, setFile] = useState<File | null>(null); |
|
|
const [uploadProgress, setUploadProgress] = useState(0); |
|
|
|
|
|
const onDrop = useCallback((acceptedFiles: File[]) => { |
|
|
if (acceptedFiles.length > 0) { |
|
|
setFile(acceptedFiles[0]); |
|
|
} |
|
|
}, []); |
|
|
|
|
|
const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ |
|
|
onDrop, |
|
|
accept: { |
|
|
'application/pdf': ['.pdf'], |
|
|
}, |
|
|
maxSize: 50 * 1024 * 1024, |
|
|
multiple: false, |
|
|
}); |
|
|
|
|
|
const handleUpload = async () => { |
|
|
console.log('🚀 handleUpload called!'); |
|
|
console.log('File:', file); |
|
|
|
|
|
if (!file) { |
|
|
console.error('❌ No file selected!'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
console.log('📤 Starting upload for:', file.name); |
|
|
|
|
|
|
|
|
setUploadProgress(0); |
|
|
const interval = setInterval(() => { |
|
|
setUploadProgress((prev) => { |
|
|
if (prev >= 90) { |
|
|
clearInterval(interval); |
|
|
return 90; |
|
|
} |
|
|
return prev + 10; |
|
|
}); |
|
|
}, 200); |
|
|
|
|
|
console.log('📡 Calling onUpload callback...'); |
|
|
await onUpload(file); |
|
|
|
|
|
clearInterval(interval); |
|
|
setUploadProgress(100); |
|
|
console.log('✅ Upload completed!'); |
|
|
} catch (err) { |
|
|
console.error('❌ Upload failed:', err); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleRemoveFile = () => { |
|
|
setFile(null); |
|
|
setUploadProgress(0); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="w-full max-w-2xl mx-auto space-y-4"> |
|
|
{/* Dropzone */} |
|
|
<motion.div |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
animate={{ opacity: 1, y: 0 }} |
|
|
transition={{ duration: 0.5 }} |
|
|
> |
|
|
<Card |
|
|
{...getRootProps()} |
|
|
className={cn( |
|
|
'border-2 border-dashed p-12 text-center cursor-pointer transition-all', |
|
|
isDragActive && 'border-blue-500 bg-blue-50 scale-105', |
|
|
isDragReject && 'border-red-500 bg-red-50', |
|
|
!isDragActive && !isDragReject && 'border-gray-300 hover:border-blue-400 hover:bg-gray-50', |
|
|
uploading && 'pointer-events-none opacity-50' |
|
|
)} |
|
|
> |
|
|
<input {...getInputProps()} /> |
|
|
|
|
|
<div className="flex flex-col items-center space-y-4"> |
|
|
<motion.div |
|
|
animate={{ |
|
|
scale: isDragActive ? 1.1 : 1, |
|
|
rotate: isDragActive ? 5 : 0, |
|
|
}} |
|
|
transition={{ duration: 0.2 }} |
|
|
> |
|
|
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-blue-100 to-purple-100"> |
|
|
<Upload className="h-10 w-10 text-blue-600" /> |
|
|
</div> |
|
|
</motion.div> |
|
|
|
|
|
{isDragReject ? ( |
|
|
<div className="text-red-600"> |
|
|
<p className="font-medium">Invalid file type</p> |
|
|
<p className="text-sm">Only PDF files up to 50MB are accepted</p> |
|
|
</div> |
|
|
) : isDragActive ? ( |
|
|
<div className="text-blue-600"> |
|
|
<p className="text-lg font-medium">Drop your patent here</p> |
|
|
</div> |
|
|
) : ( |
|
|
<div className="space-y-2"> |
|
|
<p className="text-lg font-medium text-gray-900"> |
|
|
Drag & drop your patent PDF here |
|
|
</p> |
|
|
<p className="text-sm text-gray-500"> |
|
|
or click to browse files (Max 50MB) |
|
|
</p> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
<div className="flex items-center space-x-4 text-xs text-gray-400"> |
|
|
<div className="flex items-center space-x-1"> |
|
|
<FileText className="h-4 w-4" /> |
|
|
<span>PDF only</span> |
|
|
</div> |
|
|
<div className="h-4 w-px bg-gray-300" /> |
|
|
<span>Max 50MB</span> |
|
|
</div> |
|
|
</div> |
|
|
</Card> |
|
|
</motion.div> |
|
|
|
|
|
{/* Selected File Display */} |
|
|
<AnimatePresence> |
|
|
{file && ( |
|
|
<motion.div |
|
|
initial={{ opacity: 0, height: 0 }} |
|
|
animate={{ opacity: 1, height: 'auto' }} |
|
|
exit={{ opacity: 0, height: 0 }} |
|
|
> |
|
|
<Card className="p-4"> |
|
|
<div className="flex items-center justify-between"> |
|
|
<div className="flex items-center space-x-3 flex-1 min-w-0"> |
|
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-blue-50"> |
|
|
<FileText className="h-6 w-6 text-blue-600" /> |
|
|
</div> |
|
|
<div className="flex-1 min-w-0"> |
|
|
<p className="font-medium text-gray-900 truncate">{file.name}</p> |
|
|
<p className="text-sm text-gray-500">{formatFileSize(file.size)}</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
{!uploading && uploadProgress === 0 && ( |
|
|
<Button |
|
|
variant="ghost" |
|
|
size="sm" |
|
|
onClick={handleRemoveFile} |
|
|
className="shrink-0" |
|
|
> |
|
|
<X className="h-4 w-4" /> |
|
|
</Button> |
|
|
)} |
|
|
|
|
|
{uploading && ( |
|
|
<Loader2 className="h-5 w-5 animate-spin text-blue-600 shrink-0" /> |
|
|
)} |
|
|
|
|
|
{uploadProgress === 100 && ( |
|
|
<CheckCircle2 className="h-5 w-5 text-green-600 shrink-0" /> |
|
|
)} |
|
|
</div> |
|
|
|
|
|
{/* Upload Progress */} |
|
|
{uploading && uploadProgress > 0 && uploadProgress < 100 && ( |
|
|
<div className="mt-3 space-y-1"> |
|
|
<Progress value={uploadProgress} className="h-2" /> |
|
|
<p className="text-xs text-gray-500 text-right">{uploadProgress}%</p> |
|
|
</div> |
|
|
)} |
|
|
</Card> |
|
|
</motion.div> |
|
|
)} |
|
|
</AnimatePresence> |
|
|
|
|
|
{/* Error Display */} |
|
|
{error && ( |
|
|
<motion.div |
|
|
initial={{ opacity: 0, y: -10 }} |
|
|
animate={{ opacity: 1, y: 0 }} |
|
|
> |
|
|
<Card className="border-red-200 bg-red-50 p-4"> |
|
|
<div className="flex items-start space-x-3"> |
|
|
<AlertCircle className="h-5 w-5 text-red-600 shrink-0 mt-0.5" /> |
|
|
<div> |
|
|
<p className="font-medium text-red-900">Upload Failed</p> |
|
|
<p className="text-sm text-red-700">{error}</p> |
|
|
</div> |
|
|
</div> |
|
|
</Card> |
|
|
</motion.div> |
|
|
)} |
|
|
|
|
|
{/* Upload Button */} |
|
|
{file && !uploading && uploadProgress === 0 && ( |
|
|
<div> |
|
|
<Button |
|
|
onClick={() => { |
|
|
console.log('🔴 BUTTON CLICKED!'); |
|
|
alert('Button clicked! Check console.'); |
|
|
handleUpload(); |
|
|
}} |
|
|
disabled={uploading} |
|
|
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 h-12 text-base font-medium" |
|
|
> |
|
|
{uploading ? ( |
|
|
<> |
|
|
<Loader2 className="mr-2 h-5 w-5 animate-spin" /> |
|
|
Uploading... |
|
|
</> |
|
|
) : ( |
|
|
<> |
|
|
<Upload className="mr-2 h-5 w-5" /> |
|
|
Upload & Analyze Patent |
|
|
</> |
|
|
)} |
|
|
</Button> |
|
|
</div> |
|
|
)} |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|