Skip to content

Component System


Job Components

JobForm.tsx

Located: src/components/jobs/JobForm.tsx

Handles job creation and editing. Phase 0 changes:

  • On mount: calls GET /v1/stage-types → caches response
  • Stage type dropdown populated from API (never hardcoded STAGE_TYPES array)
  • For each stage added, renders correct config panel based on stageTypeConfig.questionBankConfig.questionType:
    • 'screening' → renders <ScreeningQuestionsConfig />
    • 'dsa' → renders <DsaProblemPicker /> (Phase 3)
    • 'behavioral' → renders <InterviewQuestionPicker /> (Phase 1C)
    • null → no question bank panel
  • Date/time fields hidden when stageTypeConfig.schedulingConfig.type === 'async'

ScreeningQuestionsConfig.tsx

Located: src/components/jobs/ScreeningQuestionsConfig.tsx

Already fetches from API on mount. Reads system questions from GET /v1/screening-questions?source=system grouped by category.


Interview Components

ScheduleInterviewForm.tsx

Located: src/components/interviews/ScheduleInterviewForm.tsx

Phase 0 changes: Remove all hardcoded STAGE_FIELD_CONFIG. All conditional field rendering reads from StageTypeConfig:

StageTypeConfig fieldWhat it controls
schedulingConfig.requiresStartTimeShow date + time pickers
schedulingConfig.requiresEndTimeShow end time picker
schedulingConfig.hasExpiryDeadlineShow expiry deadline field
automationConfig.hasSendLinkToggleShow "send automated link" toggle

Phase 1A note: "Customize for this candidate" modal — deferred, not implemented. The stageOverrides field exists on the Interview model for forward compatibility.


Interview Components (Phase 1B)

FeedbackForm.tsx

src/components/interviews/FeedbackForm.tsx — shared component used by both the post-call page and the late-submission dialog.

typescript
interface FeedbackFormProps {
  interviewId: string;
  role: 'recruiter' | 'candidate';
  onSuccess: () => void;
  onSkip?: () => void;   // shows "Submit later" text link if provided
}

Fields:

  • Rating: 1–10 segmented button grid
  • Recommendation: 4 pill buttons — recruiter sees "Strong Yes / Yes / No / Strong No"; candidate sees "Highly Recommend / Recommend / Not Recommend / Strong No"
  • Traits: multi-select chip grid from the appropriate hardcoded list
  • Comment: required for recruiter (min 5 chars, z.string().min(5)), optional for candidate

Calls interviewApi.submitFeedback() (recruiter) or interviewApi.submitCandidateFeedback() (candidate).

Exports:

typescript
export const RECRUITER_TRAITS: string[];  // 10 recruiter-facing traits
export const CANDIDATE_TRAITS: string[];  // 10 candidate-facing traits

Feedback.tsx (reworked Phase 1B)

src/pages/Feedback.tsx — route: /feedback/:roomId

  • roomId from useParams() = interview document _id (since meetingLink = /room/:interviewId)
  • Calls interviewApi.getOne(roomId) to load interview title for context
  • Role detected from dbUser.roles.includes('recruiter')
  • If lastCallStats exists (navigated from Room): shows compact call stats card
  • If no lastCallStats (direct URL / page refresh): shows form without stats — no longer redirects to home
  • Renders <FeedbackForm interviewId={roomId} role={role} onSuccess={...} onSkip={...} />
  • Recruiter onSuccess: shows success message → auto-navigates to /dashboard
  • Candidate onSuccess: shows inline thank-you screen (no redirect)
  • onSkip: navigates to /dashboard (recruiter) or /candidate/dashboard (candidate)

Room Components

Room.tsx

The live interview room. Existing WebRTC + Socket.io integration.

On call end: navigates to /feedback/:roomId (roomId = interviewId for pipeline-aware live interviews, or the nanoid lobby ID for legacy).

Phase 1C additions (deferred):

  • If isAuthenticated && interview.stageTypeKey === 'live_1on1': show "My Questions" panel
  • Call GET /v1/interviews/:id/private-questions
  • Each question has a "Push to candidate" button → socket event push_question
  • Candidate's screen shows pushed question in a visible card (but never sees interviewerHints or expectedAnswerGuide)

Candidate Components (Phase 2+)

Dashboard.tsx

src/pages/candidate/Dashboard.tsx

  • Lists all pipelines sorted by lastActivityAt
  • Shows jobSnapshot, candidateFacingStatus, stage status dots
  • No internal data

PipelineDetail.tsx

src/pages/candidate/PipelineDetail.tsx

  • Stage timeline with candidateStatus per stage
  • Accept/Decline RSVP buttons for status === 'scheduled' live stages
  • candidateAggregateScore badge on completed stages

PastSession.tsx

src/pages/candidate/PastSession.tsx

  • Renders own screening answers, DSA code, AI conversation transcript
  • Zero AI scores, feedback, or internal results

Public Session Pages (No Auth)

Screening.tsx

src/pages/candidate/Screening.tsx — route: /candidate/screening?token=TOKEN

  1. Extract token from query string
  2. GET /v1/screening/:token → load questions and session metadata
  3. Multi-step text Q&A — one question per screen, progress indicator
  4. POST /v1/screening/:token/start on first answer
  5. POST /v1/screening/:token/submit on final submission
  6. Thank-you / completion screen

DeclineConfirmation.tsx

src/pages/candidate/DeclineConfirmation.tsx — route: /candidate/decline/:token

Loaded from the email decline link. Before the candidate confirms:

  • Optional free-text reason (max 1 000 chars)
  • Multi-select reason chips (pre-set options)
  • "Confirm Decline" CTA → POST /v1/interviews/decline/:declineToken with { reason?, tags? }
  • Success/thank-you screen after confirmation

DsaSession.tsx (Phase 3)

src/pages/DsaSession.tsx — route: /dsa/:token

  • Monaco editor with language switcher
  • Problem statement panel
  • "Run" (visible test cases) + "Submit" (all test cases)
  • Timer countdown

AiInterviewSession.tsx (Phase 4)

src/pages/AiInterviewSession.tsx — route: /ai-session/:token

  • Text or voice interface — determined by StageTypeConfig.toolsConfig.voiceInterface
  • Optional whiteboard/IDE enabled from StageTypeConfig.toolsConfig (not hardcoded)

Recruiter Dashboard Pages

CandidatePipelineTab.tsx (Phase 1A)

src/components/jobs/CandidatePipelineTab.tsxembedded as the "Candidates" tab inside JobDetails.tsx, not a standalone page.

  • GET /v1/pipeline?jobId=:jobId on mount; all filtering is client-side
  • Client-side search (debounced, name/email) + stage filter dropdown
  • Pagination: PAGE_SIZE = 10, Prev/Next controls
  • Each row: avatar initial, name + email, current stage, {completed}/{total} count, status badge
  • onSelectCandidate(pipelineId) callback → opens CandidateDetailPanel in a ResponsiveSheet

CandidateDetailPanel.tsx (Phase 1A + 1B)

src/components/jobs/CandidateDetailPanel.tsxembedded as a ResponsiveSheet modal inside JobDetails.tsx, not a standalone page. Prop-driven: pipelineId: string.

  • GET /v1/pipeline/:id → stage timeline with status badges and timestamps
  • AI report viewer: expanding a completed automated_screening stage shows screeningAiReport (score, summary, recommendation, strengths, concerns) + Q&A pairs — recruiter only
  • Decline data: candidate's optional reason text and selected chips displayed in the stage card
  • Live stage feedback (Phase 1B):
    • Expanding a live_1on1 or culture_fit_hr stage card fetches interview data via interviewApi.getOne(interviewId)
    • If no feedback yet: shows amber "Feedback pending" badge + "Submit Feedback" button
    • If feedback exists: shows summary card — rating badge, recommendation chip (color-coded), trait pills, comment excerpt, timestamp
    • Stage header shows an amber "Feedback" button for stages with an interview but no feedback (not yet completed)
    • Button opens a Dialog containing <FeedbackForm role="recruiter"> — on success: closes dialog + re-fetches pipeline
  • Unlock stage: "Unlock Next Stage" button; shows warning dialog if preceding stages are incomplete. If a preceding live stage lacks feedback, the API returns 400 requiresFeedback: true even with force: true
  • Schedule CTA: for an unlocked stage with no interview, a "Schedule" button closes the candidate modal and opens the schedule modal with participantName, participantEmail, round pre-filled
  • Notes: recruiter notes panel with POST /v1/pipeline/:id/notes
  • Global status selector: PATCH /v1/pipeline/:id/status

src/pages/recruiter/CandidateDetail.tsx is a thin route wrapper reusing CandidateDetailPanel for the legacy full-page route (/dashboard/jobs/:jobId/candidates/:pipelineId). This route exists in App.tsx but is not linked from the main UI.

CandidatePool.tsx (Phase 1A)

src/pages/recruiter/CandidatePool.tsx — route: /dashboard/pool

Top-level page that owns selectedParticipantId state and wires the split-pane layout. No URL param changes on selection — matches Pipeline.tsx pattern. Mobile responsive: left pane hides when profile is open; back button in profile restores list.

CandidatePoolList.tsx (Phase 1A)

src/components/pool/CandidatePoolList.tsx — left pane.

  • Three compact stat cards at top: Total / Active / Hired from summary in the pool response
  • Search input (400 ms debounce, min 2 chars, name + email)
  • Status filter dropdown + Sort dropdown
  • Paginated candidate rows: avatar initials, name, email, status badge, job count, last activity date
  • Calls pipelineApi.getPool() on mount and on any param change

CandidatePoolProfile.tsx (Phase 1A)

src/components/pool/CandidatePoolProfile.tsx — right pane.

  • Participant header: large avatar, name, email, stats badges (jobs, interviews, stages completed)
  • Tags: union of all pipeline.tags across jobs
  • Jobs tab: one card per pipeline with title, status badge, mini stage progress bar (completed/total), last activity. Click → ResponsiveSheetCandidateDetailPanel (full stage timeline, AI report, notes, status selector)
  • Activity tab: chronological timeline built client-side from stageProgression timestamps across all pipelines. No extra API call.
  • Calls pipelineApi.getPoolProfile() on participantId change

InterviewQuestionBank.tsx (Phase 1C)

Route: /interview-questions

  • List + create private questions
  • Filter by type, tags
  • Preview interviewerHints

Analytics.tsx (Phase 6)

Route: /analytics

  • Pipeline funnel chart
  • Time-to-hire breakdown
  • Gated by featureSnapshot.advancedAnalytics (upgrade prompt if false)