import { useState, useRef } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import type { ResumeAnalysisResult } from '../types/resumeAnalysis'; import Modal from '../components/Modal'; import { API_ENDPOINTS } from '../config/api'; import '../assets/css/upload.css'; /** * Upload page component for resume and job description input * Trace: SDD_LLD_0020 - Provides responsive file-picker and text input area * Trace: SDD_LLD_0021 - Accept textual job description input * Trace: SDD_LLD_0022 - Display resume preview * Trace: SDD_LLD_0027 - Manages global "loading" state with progress spinner * Trace: SDD_HLD_0001 - Accept PDF resume input * Trace: SDD_HLD_0004 - Read string of text from textbox */ export default function UploadPage() { const [resumeFile, setResumeFile] = useState(null); const [resumePreview, setResumePreview] = useState(null); const [jobDescription, setJobDescription] = useState(''); const [isDragging, setIsDragging] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false); const [modalState, setModalState] = useState<{ isOpen: boolean; title: string; message: string; type: 'error' | 'warning' | 'info'; }>({ isOpen: false, title: '', message: '', type: 'error' }); const fileInputRef = useRef(null); const navigate = useNavigate(); // Helper to show modal const showModal = (title: string, message: string, type: 'error' | 'warning' | 'info' = 'error') => { setModalState({ isOpen: true, title, message, type }); }; const closeModal = () => { setModalState(prev => ({ ...prev, isOpen: false })); }; // Trace: SDD_LLD_0001 - Accept only PDF uploads (validation) // Trace: SDD_HLD_0001 - Accept only PDF formatting for input resumes const handleFileSelect = (file: File) => { if (file && (file.type === 'application/pdf' || file.type.startsWith('image/'))) { setResumeFile(file); // Trace: SDD_LLD_0022 - Display resume preview // Create preview URL const reader = new FileReader(); reader.onloadend = () => { setResumePreview(reader.result as string); }; if (file.type === 'application/pdf') { // For PDF, we'll show a placeholder or use an iframe reader.readAsDataURL(file); } else { reader.readAsDataURL(file); } } else { // Trace: SDD_LLD_0002 - Reject non-PDF uploads // Trace: SRD_InterfaceReq_0015 - Display errors in persistent manner showModal( 'Invalid File Type', 'Please upload a PDF or image file. Other file formats are not supported.', 'warning' ); } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const file = e.dataTransfer.files[0]; if (file) { handleFileSelect(file); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }; const handleFileInputChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { handleFileSelect(file); } }; const handleRemoveFile = () => { setResumeFile(null); setResumePreview(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; // Trace: SDD_LLD_0005 - Integrate with upload interface to send data to backend // Trace: SDD_LLD_0027 - Show loading spinner during active API calls const handleAnalyze = async () => { if (!resumeFile) { showModal( 'Resume Required', 'Please upload a resume before analyzing.', 'warning' ); return; } if (!jobDescription.trim()) { showModal( 'Job Description Required', 'Please enter a job description to compare your resume against.', 'warning' ); return; } // Trace: SDD_LLD_0027 - Manages loading state and disables submission buttons setIsAnalyzing(true); try { // Trace: SDD_LLD_0005 - Send multipart/form data to backend endpoint const formData = new FormData(); formData.append('resume', resumeFile); formData.append('job_description', jobDescription); // Trace: SDD_HLD_0005 - Provide structured inputs to AI grader const response = await fetch(API_ENDPOINTS.analyze, { method: 'POST', body: formData }); // Trace: SRD_UseCase_0006 - Display error message when rate limit reached // Trace: SRD_QualAssurReq_0006 - Handle rate limit error appropriately // Trace: SRD_InterfaceReq_0015 - Display errors in persistent manner if (response.status === 429) { const errorData = await response.json(); showModal( 'Rate Limit Exceeded', errorData.error || 'You can make up to 10 requests per hour. Please try again later.', 'warning' ); setIsAnalyzing(false); return; } if (!response.ok) throw new Error(`Server error: ${response.status}`); // Trace: SDD_LLD_0019 - Receive JSON output from backend const analysisData: ResumeAnalysisResult = await response.json(); // Trace: SDD_LLD_0028 - Navigate to results page navigate('/results', { state: analysisData }); } catch (error) { // Trace: SDD_LLD_0030 - Display persistent error messages // Trace: SRD_InterfaceReq_0015 - Display errors in persistent manner console.error('Error analyzing resume:', error); showModal( 'Analysis Failed', 'Failed to analyze resume. Please check your connection and try again.', 'error' ); setIsAnalyzing(false); } }; return (
{/* Header */}
← Back to Home

Upload Your Resume

Upload your resume and add a job description to get personalized feedback

{/* Left Column - Upload Area */}

Resume Upload

{resumeFile && ( )}
{!resumeFile ? (
fileInputRef.current?.click()} >
📄

Click to upload or drag and drop

PDF (MAX. 10MB)

) : (
📄

{resumeFile.name}

{(resumeFile.size / 1024 / 1024).toFixed(2)} MB

)} {/* Resume Preview */} {/* Trace: SDD_LLD_0022 - Display resume preview */} {resumePreview && (

Resume Preview

{resumeFile?.type === 'application/pdf' ? (