husseinelsaadi's picture
resume parser implemented
af02e64
{% extends "base.html" %}
{% block title %}Apply for {{ job.role }} - Codingo{% endblock %}
{% block hero %}
<section class="hero" style="padding: 3rem 1rem;">
<div class="container">
<div class="hero-content">
<h1>Apply for {{ job.role }}</h1>
<p>{{ job.company }}{% if job.seniority %} • {{ job.seniority }}{% endif %}</p>
</div>
</div>
</section>
{% endblock %}
{% block content %}
<section class="content-section">
<!-- <ul class="breadcrumbs">
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('jobs') }}">Jobs</a></li>
<li><a href="{{ url_for('job_detail', job_id=job.id) }}">{{ job.role }}</a></li>
<li>Apply</li>
</ul> -->
<div class="card">
<div class="card-header">
<h2>Submit Your Application</h2>
<p>Please upload your resume (PDF, DOCX). Your file will be saved securely for recruiters to review.</p>
</div>
<div class="card-body">
<!-- Application Form -->
<form method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="resume">Upload Resume</label>
<!-- Resume upload remains mandatory. The file will be stored for recruiter review but is no longer parsed automatically. -->
<input type="file" name="resume" id="resume" class="form-control" required accept=".pdf,.doc,.docx">
<!-- Parse Resume button sits beside the upload to allow users to extract information from their CV. It uses a
type="button" so that clicking it does not submit the form. The actual parsing logic is defined in
the script at the bottom of this template. -->
<button type="button" id="parse-resume" class="btn btn-secondary" style="margin-top:0.5rem;">Parse Resume</button>
</div>
<!-- Name field added to capture the applicant's full name. This input can be autofilled by the resume parser,
but remains editable so applicants can correct any mistakes. -->
<div class="form-group">
<label for="full-name">Full Name</label>
<input type="text" name="full_name" id="full-name" class="form-control" placeholder="e.g. Jane Doe" required>
</div>
<!--
Collect the candidate's skills, experience and education manually.
These fields allow applicants to highlight their background even when resume
parsing is disabled. Entries can be separated by commas, semicolons or newlines;
the backend will normalise them into lists.
-->
<div class="form-group">
<label for="skills">Skills</label>
<textarea name="skills" id="skills" class="form-control" rows="3" placeholder="e.g. Python, Data Analysis, Project Management" required></textarea>
</div>
<div class="form-group">
<label for="experience">Experience</label>
<textarea name="experience" id="experience" class="form-control" rows="3" placeholder="e.g. 3 years at TechCorp as a Backend Developer" required></textarea>
</div>
<div class="form-group">
<label for="education">Education</label>
<textarea name="education" id="education" class="form-control" rows="3" placeholder="e.g. B.Sc. in Computer Science, M.Sc. in Data Science" required></textarea>
</div>
<!-- Interview guidelines removed from this page. They are now displayed on the
"My Applications" page so applicants see them before taking the interview. -->
<div class="application-actions" style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Submit Application</button>
</div>
</form>
<div style="margin-top: 1.5rem; text-align: center;">
<a href="{{ url_for('job_detail', job_id=job.id) }}" class="btn btn-outline">Back to Job Details</a>
</div>
</div>
</div>
</section>
<style>
.form-group label {
font-weight: 600;
color: var(--primary);
margin-bottom: 0.5rem;
display: block;
}
.form-control {
width: 100%;
padding: 0.75rem;
font-size: 1rem;
border-radius: 6px;
border: 1px solid #ccc;
}
.application-actions {
text-align: center;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
padding: 0.75rem 1.5rem;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
}
.btn-primary:hover {
opacity: 0.9;
}
/* Secondary button styling used for the "Parse Resume" control */
.btn-secondary {
background: var(--secondary);
color: white;
padding: 0.75rem 1.5rem;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
}
.btn-secondary:hover {
opacity: 0.9;
}
</style>
{# Resume parsing script: attaches click handler to the Parse Resume button. It performs an asynchronous
POST to a placeholder endpoint (`/parse_resume`) with the uploaded file and, upon success,
populates the corresponding form fields. Users can still edit the populated fields. #}
<script>
document.addEventListener('DOMContentLoaded', function() {
const parseBtn = document.getElementById('parse-resume');
if (!parseBtn) return;
parseBtn.addEventListener('click', function() {
const resumeInput = document.getElementById('resume');
if (!resumeInput || !resumeInput.files || resumeInput.files.length === 0) {
alert('Please upload your resume before parsing.');
return;
}
const formData = new FormData();
formData.append('resume', resumeInput.files[0]);
fetch('/parse_resume', {
method: 'POST',
body: formData
}).then(resp => resp.json())
.then(data => {
if (data) {
if (data.name && document.getElementById('full-name')) {
document.getElementById('full-name').value = data.name;
}
if (data.skills && document.getElementById('skills')) {
document.getElementById('skills').value = data.skills;
}
if (data.education && document.getElementById('education')) {
document.getElementById('education').value = data.education;
}
if (data.experience && document.getElementById('experience')) {
document.getElementById('experience').value = data.experience;
}
}
})
.catch(err => {
console.error(err);
alert('Unable to parse resume. Please try again later.');
});
});
});
</script>
{% endblock %}