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