Seth
update
5301ae9
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>
);
}