| import React, { useState, useRef } from 'react'; | |
| import { Upload, FileSpreadsheet, CheckCircle2, X, Users } from 'lucide-react'; | |
| import { Button } from "@/components/ui/button"; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| export default function UploadStep({ onFileUploaded, uploadedFile, onRemoveFile }) { | |
| const [isDragging, setIsDragging] = useState(false); | |
| const fileInputRef = useRef(null); | |
| const handleDragOver = (e) => { | |
| e.preventDefault(); | |
| setIsDragging(true); | |
| }; | |
| const handleDragLeave = (e) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| }; | |
| const handleDrop = async (e) => { | |
| e.preventDefault(); | |
| setIsDragging(false); | |
| const file = e.dataTransfer.files[0]; | |
| if (file && file.name.endsWith('.csv')) { | |
| await handleFileUpload(file); | |
| } | |
| }; | |
| const handleFileSelect = async (e) => { | |
| const file = e.target.files[0]; | |
| if (file && file.name.endsWith('.csv')) { | |
| await handleFileUpload(file); | |
| } | |
| }; | |
| const handleFileUpload = async (file) => { | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/api/upload-csv', { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| onFileUploaded({ | |
| name: file.name, | |
| size: file.size, | |
| contactCount: data.contact_count, | |
| fileId: data.file_id | |
| }); | |
| } else { | |
| alert('Failed to upload file. Please try again.'); | |
| } | |
| } catch (error) { | |
| console.error('Upload error:', error); | |
| alert('Error uploading file. Please try again.'); | |
| } | |
| }; | |
| return ( | |
| <div className="w-full"> | |
| <AnimatePresence mode="wait"> | |
| {!uploadedFile ? ( | |
| <motion.div | |
| key="upload" | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -10 }} | |
| transition={{ duration: 0.3 }} | |
| > | |
| <div | |
| onDragOver={handleDragOver} | |
| onDragLeave={handleDragLeave} | |
| onDrop={handleDrop} | |
| onClick={() => fileInputRef.current?.click()} | |
| className={` | |
| relative cursor-pointer rounded-2xl border-2 border-dashed | |
| transition-all duration-300 ease-out | |
| ${isDragging | |
| ? 'border-violet-500 bg-violet-50 scale-[1.02]' | |
| : 'border-slate-200 bg-slate-50/50 hover:border-violet-300 hover:bg-violet-50/30' | |
| } | |
| `} | |
| > | |
| <div className="flex flex-col items-center justify-center py-16 px-8"> | |
| <div className={` | |
| mb-6 rounded-2xl p-4 transition-all duration-300 | |
| ${isDragging ? 'bg-violet-100' : 'bg-white shadow-sm'} | |
| `}> | |
| <Upload className={` | |
| h-8 w-8 transition-colors duration-300 | |
| ${isDragging ? 'text-violet-600' : 'text-slate-400'} | |
| `} /> | |
| </div> | |
| <h3 className="text-lg font-semibold text-slate-800 mb-2"> | |
| Upload your Apollo CSV | |
| </h3> | |
| <p className="text-sm text-slate-500 text-center max-w-sm"> | |
| Drag and drop your contact list here, or click to browse | |
| </p> | |
| <div className="mt-6 flex items-center gap-2 text-xs text-slate-400"> | |
| <FileSpreadsheet className="h-4 w-4" /> | |
| <span>Supports .csv files exported from Apollo</span> | |
| </div> | |
| </div> | |
| <input | |
| ref={fileInputRef} | |
| type="file" | |
| accept=".csv" | |
| onChange={handleFileSelect} | |
| className="hidden" | |
| /> | |
| </div> | |
| </motion.div> | |
| ) : ( | |
| <motion.div | |
| key="uploaded" | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| exit={{ opacity: 0, scale: 0.95 }} | |
| transition={{ duration: 0.3 }} | |
| className="rounded-2xl border border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-6" | |
| > | |
| <div className="flex items-start justify-between"> | |
| <div className="flex items-start gap-4"> | |
| <div className="rounded-xl bg-green-100 p-3"> | |
| <CheckCircle2 className="h-6 w-6 text-green-600" /> | |
| </div> | |
| <div> | |
| <h3 className="font-semibold text-slate-800 mb-1"> | |
| {uploadedFile.name} | |
| </h3> | |
| <div className="flex items-center gap-4 text-sm text-slate-500"> | |
| <span>{(uploadedFile.size / 1024).toFixed(1)} KB</span> | |
| <div className="flex items-center gap-1.5 text-green-600 font-medium"> | |
| <Users className="h-4 w-4" /> | |
| <span>{uploadedFile.contactCount} contacts found</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={onRemoveFile} | |
| className="h-8 w-8 text-slate-400 hover:text-red-500 hover:bg-red-50" | |
| > | |
| <X className="h-4 w-4" /> | |
| </Button> | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| ); | |
| } | |