dev1/src/components/ResumeRewrite.js

197 lines
6.6 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import api from '../auth/apiClient.js';
function ResumeRewrite() {
const [resumeFile, setResumeFile] = useState(null);
const [jobTitle, setJobTitle] = useState('');
const [jobDescription, setJobDescription] = useState('');
const [optimizedResume, setOptimizedResume] = useState('');
const [error, setError] = useState('');
const [remainingOptimizations, setRemainingOptimizations] = useState(null);
const [resetDate, setResetDate] = useState(null);
const [loading, setLoading] = useState(false);
const ALLOWED_TYPES = [
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
];
const MAX_MB = 5;
const handleFileChange = (e) => {
const f = e.target.files?.[0] || null;
setOptimizedResume('');
setError('');
if (!f) {
setResumeFile(null);
return;
}
// Basic client-side validation
if (!ALLOWED_TYPES.includes(f.type)) {
setError('Please upload a PDF or DOCX file.');
setResumeFile(null);
return;
}
if (f.size > MAX_MB * 1024 * 1024) {
setError(`File is too large. Maximum ${MAX_MB}MB.`);
setResumeFile(null);
return;
}
setResumeFile(f);
};
const fetchRemainingOptimizations = async () => {
try {
const res = await api.get('/api/premium/resume/remaining', { withCredentials: true });
setRemainingOptimizations(res.data.remainingOptimizations);
setResetDate(res.data.resetDate ? new Date(res.data.resetDate).toLocaleDateString() : null);
} catch (err) {
console.error('Error fetching optimizations:', err);
setError('Could not fetch optimization limits.');
}
};
useEffect(() => {
fetchRemainingOptimizations();
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setOptimizedResume('');
if (!resumeFile || !jobTitle.trim() || !jobDescription.trim()) {
setError('Please fill in all fields.');
return;
}
// If a previous error existed, reset the file input to prompt a fresh pick
if (error) {
// reset the native input if accessible
const el = e.target?.querySelector('input[type="file"]');
if (el) el.value = '';
}
setLoading(true);
try {
const formData = new FormData();
formData.append('resumeFile', resumeFile);
formData.append('jobTitle', jobTitle.trim());
formData.append('jobDescription', jobDescription.trim());
// Let axios/browser set multipart boundary automatically; just include credentials.
const res = await api.post('/api/premium/resume/optimize', formData, {
withCredentials: true,
});
setOptimizedResume(res.data.optimizedResume || '');
setError('');
fetchRemainingOptimizations();
} catch (err) {
console.error('Resume optimization error:', err);
const status = err?.response?.status;
const code = err?.response?.data?.error;
if (status === 413 || code === 'file_too_large') {
setError(`File is too large. Maximum ${MAX_MB}MB.`);
} else if (status === 415 || code === 'unsupported_type') {
setError('Unsupported file type. Please upload a PDF or DOCX.');
} else if (status === 429) {
setError('Too many requests. Please wait a moment and try again.');
} else if (status === 400) {
setError('Bad upload. Please re-select your file and try again.');
} else {
setError('Failed to optimize resume. Please try again.');
}
} finally {
setLoading(false);
}
};
return (
<div className="max-w-4xl mx-auto mt-8 p-6 bg-white rounded shadow">
<h2 className="text-2xl font-bold mb-4">Resume Optimizer</h2>
{remainingOptimizations !== null && (
<div className="mb-4 p-2 bg-blue-50 rounded border border-blue-200 text-blue-700">
<strong>{remainingOptimizations}</strong> Resume Optimizations Remaining This Week
{resetDate && <span className="ml-2">(Resets on {resetDate})</span>}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block font-medium text-gray-700 mb-1">
Upload Resume (PDF or DOCX):
</label>
<input
type="file"
accept=".pdf,.docx"
onChange={handleFileChange}
className="file:mr-4 file:py-2 file:px-4 file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer text-gray-600"
/>
</div>
<div>
<label className="block font-medium text-gray-700 mb-1">Job Title:</label>
<input
type="text"
value={jobTitle}
onChange={(e) => {
setJobTitle(e.target.value);
if (error) setError('');
}}
className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:ring-blue-200"
placeholder="e.g., Software Engineer"
/>
</div>
<div>
<label className="block font-medium text-gray-700 mb-1">Job Description:</label>
<textarea
value={jobDescription}
onChange={(e) => {
setJobDescription(e.target.value);
if (error) setError('');
}}
rows={4}
className="w-full border rounded px-3 py-2 focus:outline-none focus:ring focus:ring-blue-200"
placeholder="Paste the job listing or requirements here..."
/>
</div>
{error && <p className="text-red-600 font-semibold">{error}</p>}
<button
type="submit"
disabled={loading}
className={`inline-block font-semibold px-5 py-2 rounded transition-colors ${
loading ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'
} text-white`}
>
{loading ? 'Optimizing Resume...' : 'Optimize Resume'}
</button>
{loading && (
<div className="flex items-center justify-center mt-4">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
<span className="ml-3 text-blue-700 font-semibold">Optimizing your resume...</span>
</div>
)}
</form>
{optimizedResume && (
<div className="mt-8">
<h3 className="text-xl font-bold mb-2">Optimized Resume</h3>
<pre className="whitespace-pre-wrap bg-gray-50 p-4 rounded border">
{optimizedResume}
</pre>
</div>
)}
</div>
);
}
export default ResumeRewrite;