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