Skip to content

Phase 2 — Candidate Dashboard

Goal: Candidates can log in via Firebase, see all their pipeline history across organizations, and view their own submitted answers for past sessions. Privacy boundary is enforced — no internal scores or recruiter notes leak.


2.1 Backend — Candidate Auth Middleware

src/middleware/candidateAuthMiddleware.ts

typescript
// 1. Verify Firebase idToken → get uid
// 2. Participant.findOne({ authId: uid }) → attach req.participant
// 3. If not found → onCandidateFirstLogin(uid, email from token)

onCandidateFirstLogin(uid, email):

typescript
const existing = await Participant.findOne({ email: email.toLowerCase() });
if (existing && !existing.authId) {
  await Participant.findByIdAndUpdate(existing._id, { authId: uid });
  return existing;
}
if (!existing) {
  return await Participant.create({
    email,
    authId: uid,
    preferences: { emailNotifications: true },
    stats: { totalPipelines: 0, totalInterviews: 0, noShowCount: 0 }
  });
}
return existing;

2.2 Backend — candidateProjection.ts

src/utils/candidateProjection.ts

Whitelist projection applied to every candidate API response. See Data Ownership docs for the complete field list.


2.3 Backend — Candidate Endpoints

New router: src/routes/candidateRoutes.ts — all routes use candidateAuthMiddleware

EndpointLogic
GET /v1/candidate/dashboardCandidatePipeline.find({ participantId }).sort({ lastActivityAt: -1 })candidateProjection()
GET /v1/candidate/pipeline/:idVerify pipeline.participantId === req.participant._id → return projected pipeline
GET /v1/candidate/interviews/:idVerify interview.participantId === req.participant._idtoCandidateProjection()
PATCH /v1/candidate/interviews/:id/rsvpAccept or decline live invite → set participantRsvp
PATCH /v1/candidate/preferencesUpdate Participant.preferences

2.4 Frontend — Candidate Dashboard

New pages:

  • src/pages/candidate/Dashboard.tsx — all pipelines with jobSnapshot, candidateFacingStatus, stage dots
  • src/pages/candidate/PipelineDetail.tsx/candidate/pipeline/:id — stage timeline, per-stage candidateStatus
  • src/pages/candidate/PastSession.tsx/candidate/interviews/:id — own answers, Q&A transcript (no scores)

New service: src/services/candidateDashboard.service.ts


2.5 Frontend — RSVP for Live Interviews

From PipelineDetail.tsx, when candidateStatus === 'scheduled' and stage is live_1on1:

  • Show "Accept" / "Decline" buttons
  • Call PATCH /v1/candidate/interviews/:id/rsvp

Phase 2 — Acceptance Criteria

  • [ ] Candidate logs in with Firebase (same email as recruiter's invite) → Participant.authId is set
  • [ ] GET /v1/candidate/dashboard returns pipelines with candidateFacingStatus and stageProgression[].candidateStatus only
  • [ ] No feedbacks, aiReport, aiScore, result, notes fields in any candidate API response
  • [ ] Candidate can view their own screening answers from a past session
  • [ ] Candidate can RSVP accept/decline for a live 1-on-1 invite
  • [ ] Dashboard shows correct candidateFacingStatus after recruiter shortlists/rejects