Skip to main content

interviews-ui

Purpose

interviews-ui is the web application every user of theinterviews.ai touches: it renders the marketing site, candidate/recruiter/admin dashboards, the AI interview room, the Profile Card, and plan checkout. It is a Next.js 15 / React 19 / TypeScript app, and it is the front door to four backend services — it owns presentation and routing, not business logic.

Architecture

The platform is five services; this repo is the browser-facing one. The golden rule is the three data paths — every network call from the front end takes exactly one of them:

  1. Java core backend (user-management) over REST + JWT — users, meetings, subscriptions, recordings, blog, platform config. Always through the typed clients in src/services/*, never inline fetch in components.
  2. Next.js /api/* serverless routes — OpenAI-facing only (resume ATS scoring, resume match, AI mock-interview Q&A). These exist purely to keep the OpenAI key server-side. User, billing, meeting, and recording data must never flow through them.
  3. LiveKit over WebSocket — real-time audio/video. The Java backend mints a time-limited token (GET /api/meetings/{id}/authorize-video); the browser then connects directly to the LiveKit SFU.

Two backends sit behind the AI interview experience: video-streaming-server (Node — the legacy voice brain: STT, LLM Q&A/evaluation, TTS, avatar session minting) and bot-backend (Python + livekit-agents — the next-generation interview worker, currently in migration/eval under TI-340). The live in-call session room is a separate React repo, smart-interview-ui.

Video meeting flow

The component tree branches on NEXT_PUBLIC_VIDEO_PLATFORM_MODE: INTERNAL_V1 routes to the in-app /meeting/[meetingId] page (LiveKit WebRTC), while LEGACY redirects the user to an external join URL. In the internal path, the page calls the Java backend to authorize the user and fetch a LiveKit token, then connects over WebSocket; recording happens server-side via LiveKit Egress, and an AI agent can join the room silently to transcribe and summarize.

Key components

PathOwns
src/app/App Router pages: / landing, /auth, /signup/*, /customer/* (dashboard, AI mock/bot interviews, resume optimizer, profile, subscription), /meeting/[meetingId], /admin/*, /blog
src/app/api/Serverless routes — OpenAI-only (resume upload/ATS/match, AI mock-interview sessions)
src/services/REST clients per domain (auth, blog, AI interview, …) — the only sanctioned way to call the Java backend
src/store/Redux Toolkit slices + selectors, for cross-cutting state only (auth, user, plan entitlements)
src/features/Feature-scoped modules, e.g. ai-bot-interview (the voice AI interview room, avatar routing, bot service)
src/hooks/Custom hooks, e.g. useLiveKit for room lifecycle management
src/components/Shared UI primitives (video grid, tracks, meeting controls)
src/utils/Utilities — notably planEntitlements.ts (all plan/entitlement checks) and featureFlags.ts (video platform mode resolution)
src/lib/Framework-neutral helpers
docs/In-repo deep dives: SETUP, BACKEND, VIDEO_PLATFORM, AI_AGENT, AMPLIFY_ENV_SETUP, PLAN_LIMITS_AND_TESTING

State management convention: TanStack Query for server data that doesn't need to live in Redux; Redux only for cross-cutting state.

Local development

Prerequisites: Node.js 18+, npm. For the full stack you also need Java 17+ (the user-management backend), Docker (a local LiveKit server), and optionally Python (the bot-backend AI agent) — each has its own setup guide.

npm install

# create your local env file from the template, then fill in values
cp .env.example .env.local

npm run dev:local # dev server on localhost:3000 — reads .env.local directly

Other commands you'll use:

npm run build # production build (runs scripts/check-env.js first)
npm test # vitest run; single file: npx vitest run <file-pattern>
npm run lint # ESLint (next lint)
npx tsc --noEmit # typecheck (no npm script for this)
npm run check-env # validate required env vars
npm run restore:local # restore .env.local after an env-swapped run (see Gotchas)

The UI is usable standalone for marketing pages, but anything behind login needs the Java backend running, and the meeting room additionally needs a LiveKit server (Docker image livekit/livekit-server works locally).

Environment variables

Names only — set real values in .env.local locally, and in the Amplify Console per branch for deployed environments (Amplify does not read .env files).

VariablePurpose
NEXT_PUBLIC_ENVIRONMENTEnvironment identifier (local / development / staging / production); highest-priority input to environment detection
NEXT_PUBLIC_VIDEO_PLATFORM_MODEINTERNAL_V1 (in-app LiveKit meetings) or LEGACY (redirect to external join URL)
NEXT_PUBLIC_LIVEKIT_WS_URLLiveKit WebSocket endpoint, e.g. <LIVEKIT_WS_URL>
NEXT_PUBLIC_API_URLBase URL of the Java backend REST API, e.g. <API_BASE_URL>
NEXT_PUBLIC_APP_URLApplication base URL used by the app, e.g. <API_BASE_URL>
NEXT_PUBLIC_JOIN_URL_BASEBase URL used when generating meeting join links
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYStripe publishable key for checkout (publishable, not secret)
OPENAI_API_KEYOpenAI key — server-side only, consumed by the /api/* serverless routes
OPENAI_MODELOpenAI model name used by the resume/AI-interview routes

Run npm run check-env to confirm everything required is set.

Gotchas

  • .env is a derived, transient file. The env-swapping scripts (dev:dev, dev:production, build:*, start:*, export:*) back up .env.local and copy the target env file over .env. The canonical sources are .env.local, .env.dev, .env.production. Only dev:local reads .env.local directly with no swap. After any swapped run, run npm run restore:local — or your local env is silently wrong.
  • Never route user data through /api/*. Those serverless routes are OpenAI-facing only. User/billing/meeting/recording data goes through src/services/* to the Java backend. Routing it through /api/* by accident sends user data to the wrong place.
  • Inline fetch in components is banned. Every backend call goes through a src/services/<domain>Service client.
  • Plan checks go through src/utils/planEntitlements.ts. Never scatter plan === 'pro' string comparisons across components.
  • Amplify does not read .env files. Deployed env vars are configured per branch in the Amplify Console — a value that works locally can be missing in a deployed build. See docs/AMPLIFY_ENV_SETUP.md in the repo.
  • Environment detection has a priority chain: NEXT_PUBLIC_ENVIRONMENT → Amplify branch name → hostname → NODE_ENV. Know which one is winning before you debug "wrong API URL" issues.
  • NEXT_PUBLIC_VIDEO_PLATFORM_MODE forks real code paths. Components branch on INTERNAL_V1 vs LEGACY — when changing video behavior, exercise both.
  • Tests exist. Vitest + jsdom is configured, with suites under src/features/ai-bot-interview/__tests__/ and src/app/components/__tests__/. (Older docs claimed "no tests" — outdated.)
  • Design constraints are enforced: single accent color #7C7AED, no multi-color gradients, no emojis on AI Interview / Recruiter / Pricing / Profile Card surfaces, no fake stats. npm run build and npm run lint must pass with zero warnings before work is "done".