Skip to content

Frontend Guide (PeerZoom)

Stack: React + TypeScript, Vite, Axios, shadcn/ui, React Router v6, React Query (or React Context)


Directory Structure

src/
├── components/
│   ├── ui/           — Generic UI elements (shadcn/ui based)
│   ├── layout/       — Page wrappers (DashboardLayout, AuthLayout)
│   ├── jobs/         — Job CRUD components (JobForm, ScreeningQuestionsConfig, ...)
│   ├── interviews/   — Interview scheduling (ScheduleInterviewForm, ...)
│   └── room/         — Live room components (VideoGrid, CodeEditor, ...)
├── pages/
│   ├── recruiter/    — Recruiter dashboard pages
│   ├── candidate/    — Candidate dashboard pages (Phase 2+)
│   └── public/       — Token-gated public pages (ScreeningSession, DsaSession, ...)
├── services/         — API layer (Axios instances + typed wrappers)
├── contexts/         — Auth, Media, Sidebar global state
└── routes/           — React Router definitions + ProtectedRoute

API Base

The Axios instance in src/services/auth.service.ts uses VITE_SERVER_URL + '/v1' as the base URL. All service files use this instance.

typescript
// src/services/auth.service.ts
const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_SERVER_URL + '/v1',
});

// Attach Firebase idToken to every request
axiosInstance.interceptors.request.use(async (config) => {
  const token = await auth.currentUser?.getIdToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

Routing

React Router v6. Key route groups:

RouteComponentAuth
/Landing or dashboard redirectPublic
/loginLogin pagePublic
/jobsRecruiter job listProtectedRoute (recruiter)
/jobs/newCreate jobProtectedRoute
/interviews/:id/feedbackFeedback form (Phase 1B)ProtectedRoute
/interview-questionsQuestion bank page (Phase 1B)ProtectedRoute
/analyticsAnalytics dashboard (Phase 6)ProtectedRoute
/settings/organizationOrg settings (Phase 5)ProtectedRoute (admin)
/settings/billingBilling (Phase 5)ProtectedRoute (admin)
/candidate/dashboardCandidate dashboard (Phase 2)ProtectedRoute (candidate)
/candidate/pipeline/:idApplication detail (Phase 2)ProtectedRoute (candidate)
/candidate/interviews/:idPast session view (Phase 2)ProtectedRoute (candidate)
/candidate/screeningScreening session (?token=TOKEN)Public (token in query param)
/candidate/decline/:tokenDecline confirmation pagePublic (token in path)
/dsa/:tokenDSA session (Phase 3)Public (token-gated)
/ai-session/:tokenAI session (Phase 4)Public (token-gated)
/lobby/:roomIdLive interview lobbyPublic (meeting link)
/room/:roomIdLive interview roomPublic (meeting link)

The candidate pipeline list and candidate detail view are not standalone routes. They are embedded inside JobDetails.tsx as a tab (CandidatePipelineTab) and a ResponsiveSheet modal (CandidateDetailPanel). The legacy routes /dashboard/jobs/:jobId/candidates and /dashboard/jobs/:jobId/candidates/:pipelineId exist in App.tsx but are not linked from the main UI.


Contexts

ContextPurpose
AuthContextFirebase auth state, current user/role
MediaContextLocal camera/mic streams and permissions
SidebarContextResponsive layout sidebar state
StageTypeContext (Phase 0)Cached GET /v1/stage-types response for the session

The "No Hardcoding" Rule

The frontend must never hardcode:

  • Stage type names, keys, or display strings
  • Which form fields appear for each stage type
  • Which tools are enabled in the room
  • Min/max question counts
  • Whether questions are interviewer-only
  • Whether feedback is required
  • Plan names or feature comparisons

All of this comes from GET /v1/stage-types and GET /v1/plans. See the full table in Architecture docs.


Key Services

FilePurpose
src/services/auth.service.tsAxios instance + auth interceptor
src/services/stageType.service.tsstageTypeApi.getAll()
src/services/job.service.tsJob CRUD
src/services/interview.service.tsInterview scheduling + feedback
src/services/question.service.tsScreening question CRUD (targets /screening-questions)
src/services/pipeline.service.tsPipeline management (pipelineApi)
src/services/screening.service.tsPublic screening session API — uses a separate no-auth Axios instance
src/services/candidateDashboard.service.tsCandidate-facing API (Phase 2)