Mr-Haseeb786
Clean deployment build
88da18c
Raw History Blame Contribute Delete
4.97 kB
import React from 'react';
import { motion } from 'framer-motion';
import { Upload, FileText, AlertCircle } from 'lucide-react';
import { Card, Button } from '@/components/common';
import { useJobStore } from '@/store/appStore';
import { jobsAPI } from '@/utils/api';
import { readFileAsArrayBuffer } from '@/utils/helpers';
interface CVUploadProps {
onSuccess?: (filename: string) => void;
onError?: (error: string) => void;
}
const CVUpload: React.FC<CVUploadProps> = ({ onSuccess, onError }) => {
const [isDragging, setIsDragging] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);
const { cvTemplate, setCVTemplate } = useJobStore();
const fileInputRef = React.useRef<HTMLInputElement>(null);
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => {
setIsDragging(false);
};
const handleFileSelect = async (files: FileList | null) => {
if (!files || files.length === 0) return;
const file = files[0];
setError(null);
// Validate file type
if (!file.name.endsWith('.docx')) {
setError('Only .docx files are supported');
onError?.('Only .docx files are supported');
return;
}
// Validate file size (max 10MB)
if (file.size > 10 * 1024 * 1024) {
setError('File size must be less than 10MB');
onError?.('File size must be less than 10MB');
return;
}
try {
const arrayBuffer = await readFileAsArrayBuffer(file);
const response = await jobsAPI.uploadCV(file);
if (response.success) {
setCVTemplate({
filename: file.name,
uploadedAt: new Date().toISOString(),
size: file.size,
content: arrayBuffer,
});
onSuccess?.(file.name);
}
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to upload CV';
setError(message);
onError?.(message);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
handleFileSelect(e.dataTransfer.files);
};
return (
<div className="w-full">
{cvTemplate ? (
<Card glow innerGlow>
<div className="flex items-start gap-4">
<div className="p-3 bg-emerald-500/20 rounded-lg">
<FileText className="w-6 h-6 text-emerald-400" />
</div>
<div className="flex-1">
<h3 className="font-semibold text-white mb-1">
{cvTemplate.filename}
</h3>
<p className="text-sm text-slate-400 mb-3">
Uploaded on{' '}
{new Date(cvTemplate.uploadedAt).toLocaleDateString()}
</p>
<Button
size="sm"
variant="outline"
onClick={() => {
setCVTemplate(null);
setError(null);
}}
>
Change CV
</Button>
</div>
</div>
</Card>
) : (
<motion.div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
animate={isDragging ? { scale: 1.02 } : { scale: 1 }}
className={`border-2 border-dashed rounded-xl p-8 text-center transition-all cursor-pointer ${
isDragging
? 'border-emerald-500 bg-emerald-500/10'
: 'border-slate-600 bg-slate-800/30 hover:border-emerald-400'
}`}
onClick={() => fileInputRef.current?.click()}
>
<motion.div
animate={isDragging ? { scale: 1.1, rotate: 12 } : { scale: 1, rotate: 0 }}
className="flex justify-center mb-4"
>
<div className="p-3 bg-emerald-500/20 rounded-lg">
<Upload className="w-8 h-8 text-emerald-400" />
</div>
</motion.div>
<h3 className="text-lg font-semibold text-white mb-2">
Drop your CV or click to browse
</h3>
<p className="text-slate-400 text-sm mb-4">
Supported format: .docx (Word Document)
</p>
<p className="text-xs text-slate-500">Max file size: 10MB</p>
<input
ref={fileInputRef}
type="file"
accept=".docx"
onChange={(e) => handleFileSelect(e.target.files)}
className="hidden"
/>
</motion.div>
)}
{error && (
<motion.div
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
className="mt-4 p-4 bg-red-500/10 border border-red-500/30 rounded-lg flex gap-3"
>
<AlertCircle className="w-5 h-5 text-red-400 flex-shrink-0" />
<p className="text-sm text-red-200">{error}</p>
</motion.div>
)}
</div>
);
};
export default CVUpload;