/** * Candidate Self-Service Portal. * * "Check status" covers the one real read endpoint for candidates * (GET /api/candidates/by-email/{email}) in full. * * "Submit a new application" is now a real, working form — POST * /api/applications (azure-functions/api_blueprint.py), which uploads the * resume to Blob Storage and lets the existing blob-triggered function * (Phase 3) pick it up and run the normal ResumeParser.py pipeline. In * mock mode (the default here, see frontend/README.md) this validates * input the same way but doesn't actually upload or process anything — * switch config.js to mode: "real" against a deployed API to exercise it * for real. jd_id/cover_note are collected and sent (visible in the * audit log) but NOT acted on by the scoring pipeline yet — see the * endpoint's docstring for why; not faked as if they influenced ranking. */ function CandidatePortal(props) { var api = props.api; var state = React.useState({ email: "", result: null, error: null, loading: false, checked: false }); var data = state[0]; var setData = state[1]; function checkStatus(e) { e.preventDefault(); if (!data.email.trim()) return; setData(function (d) { return Object.assign({}, d, { loading: true, error: null, checked: false }); }); api.getCandidateByEmail(data.email.trim()) .then(function (result) { setData(function (d) { return Object.assign({}, d, { loading: false, result: result, checked: true }); }); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, result: null, error: err.message, checked: true }); }); }); } return (

Candidate Portal

Check your application status

{data.checked && data.result && (

Hi {data.result.Name || "there"} — your application is currently:

{data.result.ingested_at && (

Submitted {new Date(data.result.ingested_at).toLocaleDateString()}

)} {data.result.pipeline_stages && data.result.pipeline_stages.length > 0 && ( )}
)} {data.checked && data.error && (
{data.error}
)}
); } var EMPTY_APPLICATION = { fullName: "", email: "", phone: "", linkedin: "", jdId: "", coverNote: "", resumeFile: null, }; function ApplicationForm(props) { var api = props.api; var formState = React.useState(EMPTY_APPLICATION); var form = formState[0]; var setForm = formState[1]; var jdsState = React.useState([]); var jds = jdsState[0]; var setJds = jdsState[1]; var submitState = React.useState({ submitting: false, result: null, error: null }); var submit = submitState[0]; var setSubmit = submitState[1]; React.useEffect(function () { api.listJds() .then(function (r) { setJds(r.jds || []); }) .catch(function () { setJds([]); }); // non-fatal -- the JD dropdown just stays empty/"Not specified" }, []); function setField(name) { return function (e) { var value = e.target.value; setForm(function (f) { var updated = Object.assign({}, f); updated[name] = value; return updated; }); }; } function readFileAsBase64(file) { return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function () { // reader.result is "data:application/pdf;base64,AAAA..." -- strip the prefix var commaIndex = reader.result.indexOf(","); resolve(reader.result.slice(commaIndex + 1)); }; reader.onerror = function () { reject(new Error("Could not read the selected file.")); }; reader.readAsDataURL(file); }); } function handleSubmit(e) { e.preventDefault(); if (!form.resumeFile) { setSubmit({ submitting: false, result: null, error: "Please choose a resume PDF to upload." }); return; } setSubmit({ submitting: true, result: null, error: null }); readFileAsBase64(form.resumeFile) .then(function (resumeBase64) { return api.submitApplication({ fullName: form.fullName.trim(), email: form.email.trim(), phone: form.phone.trim() || null, linkedin: form.linkedin.trim() || null, jdId: form.jdId || null, coverNote: form.coverNote.trim() || null, resumeBase64: resumeBase64, resumeFilename: form.resumeFile.name, }); }) .then(function (result) { setSubmit({ submitting: false, result: result, error: null }); setForm(EMPTY_APPLICATION); }) .catch(function (err) { setSubmit({ submitting: false, result: null, error: err.message }); }); } return (

Submit a new application

Fields marked * are required. Everything you enter here is recorded with your submission (see frontend/README.md) — resume parsing (name, email, skills, experience, etc.) is done fresh from the resume itself, not from what you type below.