Public Wiki137,569View on GitHub
Want yangshun/tech-interview-handbook knowledge in your AI?

React

Tech Interview Handbook (TIH) — a monorepo containing a Docusaurus documentation website with interview preparation content (including Grind75 coding questions) and a Next.js portal application where users can submit and compare tech compensation offers, browse and discuss interview questions, and get resume reviews from the community.

Tech Stack

ReactNext.jsPrismatRPCTailwind CSSZod

Key Features

  • Offers Submission & Analysis
  • Interview Questions Repository
  • Resume Review Platform
  • Grind75 Study Plan
  • Tech Interview Handbook Documentation
  • Shared UI Component Library

Entry Points

Start here when working with this codebase

Next.js App Entry
apps/portal/src/pages/_app.tsx

Root Next.js application component that wraps all pages with providers (tRPC, auth session, global shell). Configures the tRPC client with batch endpoint at /api/trpc.

Start here when adding a new global provider, modifying app-wide layout, or understanding how tRPC client connects to the server.

tRPC Root Router
apps/portal/src/server/router/index.ts

Composes all feature routers (offers, questions, resumes, todos, companies, locations, user, auth) into the root appRouter. This is where every tRPC namespace is registered.

Start here when adding a new tRPC router or understanding which routers exist and how they are namespaced.

tRPC API Handler
apps/portal/src/pages/api/trpc/[trpc].ts

Next.js API route that handles all tRPC requests. Connects the root appRouter to the HTTP layer.

Start here when debugging tRPC request handling or modifying server-side tRPC configuration.

tRPC Context & Auth
apps/portal/src/server/context.ts

Creates the tRPC context object containing the Prisma client and user session. Every tRPC procedure receives this context.

Start here when modifying what data is available to tRPC procedures (e.g., adding a new service to context).

Prisma Client Singleton
apps/portal/src/server/db/client.ts

Exports the singleton Prisma client instance used across all server-side code.

Start here when understanding database access patterns or troubleshooting Prisma connection issues.

tRPC Client Setup
apps/portal/src/utils/trpc.ts

Configures the tRPC client hooks for React components. Exports the typed `trpc` object used in all frontend data fetching.

Start here when modifying client-side tRPC configuration or understanding how frontend calls reach the server.

App Shell & Navigation
apps/portal/src/components/global/AppShell.tsx

Global application shell with navigation bar, sidebar links, and admin detection. Wraps all page content.

Start here when adding a new top-level navigation item or modifying the global layout.

Environment Schema
apps/portal/src/env/schema.mjs

Zod schemas defining all required server and client environment variables.

Start here when adding a new environment variable to the application.

Next.js Config
apps/portal/next.config.mjs

Next.js build and runtime configuration including redirects, rewrites, and module transpilation.

Start here when modifying build behavior, adding redirects, or configuring external packages.

Tailwind Config
apps/portal/tailwind.config.cjs

Tailwind CSS configuration with content paths for purging and custom theme extensions.

Start here when adding custom Tailwind utilities, colors, or modifying the design system tokens.

How to Make Changes

Common modification patterns for this codebase

Add a new tRPC API endpoint to an existing feature

1
apps/portal/src/server/router/questions/questions-question-router.ts

Open an existing router file to understand the pattern. Each router uses `createRouter()` and chains `.query()` or `.mutation()` calls with Zod input validation and Prisma operations in the `resolve` function.

Pattern: See how `questions-question-router.ts` defines queries with `.query('getQuestionsByFilter', { input: z.object({...}), async resolve({ctx, input}) { ... } })` — replicate this exact structure.

2
apps/portal/src/server/router/questions/questions-question-router.ts

Add your new `.query()` or `.mutation()` call to the router chain. Use `ctx.prisma` for database access and `ctx.session` for auth checks. For protected endpoints, use `createProtectedRouter()` instead of `createRouter()`.

Pattern: See how `questions-answer-router.ts` uses `createProtectedRouter()` for mutations that require authentication, versus `createRouter()` for public queries.

3
apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx

Call the new endpoint from a page or component using `trpc.useQuery(['questions.questions.yourNewEndpoint', { ...params }])` for queries or `trpc.useMutation('questions.questions.yourNewEndpoint')` for mutations.

Pattern: See how `apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx` calls `trpc.useQuery` with the full dot-notation path matching the router namespace.

Conventions to follow:

All tRPC input must be validated with Zod schemas — never access raw input without validationUse `createProtectedRouter()` for any endpoint that requires authentication; use `createRouter()` for public endpointsRouter namespaces follow dot notation: 'feature.subrouter.endpointName' (e.g., 'questions.questions.getQuestionsByFilter')Always use `ctx.prisma` for database access — never import the Prisma client directly in router filesInvalidate related queries after mutations using `utils.invalidateQueries()` on the client side

Add a new feature module (e.g., a 'Salaries' feature)

1
apps/portal/src/server/router/offers/offers-profile-router.ts

Study this file as the reference pattern for creating a new tRPC router. Note how it exports a `createRouter()` chain with queries and mutations, uses Zod for input validation, and accesses Prisma through `ctx.prisma`.

Pattern: Copy the structure of `offers-profile-router.ts`: import createRouter, define Zod input schemas, chain .query() and .mutation() calls, export the router.

2
apps/portal/src/server/router/salaries/salaries-router.ts

Create a new directory `apps/portal/src/server/router/salaries/` and add `salaries-router.ts`. Define your router with queries and mutations following the pattern from step 1.

Pattern: Match the export pattern in `apps/portal/src/server/router/offers/offers-profile-router.ts`: `export const salariesRouter = createRouter().query(...)...`

3
apps/portal/src/server/router/index.ts

Import the new router and merge it into the `appRouter` using `.merge('salaries.', salariesRouter)`. This registers the namespace so the client can call `trpc.useQuery(['salaries.yourEndpoint'])`.

Pattern: See how `apps/portal/src/server/router/index.ts` merges existing routers: `.merge('offers.', offersRouter)` — add your new router in the same pattern.

4
apps/portal/src/pages/salaries/index.tsx

Create a new Next.js page at `apps/portal/src/pages/salaries/index.tsx`. This is the main page for the feature.

Pattern: Follow the page structure in `apps/portal/src/pages/offers/index.tsx`: import trpc hooks, use `trpc.useQuery` for data fetching, wrap content in the page layout.

5
apps/portal/src/components/salaries/SalariesTable.tsx

Create a new directory `apps/portal/src/components/salaries/` and add feature-specific components. Each component should be a focused React component that receives data via props or fetches via tRPC hooks.

Pattern: Follow the component organization in `apps/portal/src/components/offers/` — separate table components, form components, and display components into individual files.

6
apps/portal/src/components/global/AppShell.tsx

Add a navigation link for the new feature in the AppShell navigation array. Add an entry with the feature name, href, and icon.

Pattern: See how existing navigation items are defined in `AppShell.tsx` — each has a `name`, `href`, and `icon` property in the navigation array.

Conventions to follow:

Feature directories follow the pattern: server router in `src/server/router/<feature>/`, components in `src/components/<feature>/`, pages in `src/pages/<feature>/`Each feature router is merged into the root router in `apps/portal/src/server/router/index.ts` with a namespace prefixPages use Next.js file-based routing — the file path IS the URL pathComponents should use Tailwind CSS classes for styling, referencing the design tokens in `apps/portal/tailwind.config.cjs`Use UI components from `apps/portal/src/ui/` for buttons, inputs, selects, etc. — do not create custom base components

Add a new shared UI component to the design system

1
apps/portal/src/ui/Button.tsx

Study an existing UI component to understand the pattern. UI components are generic, reusable, and styled with Tailwind CSS. They accept props for variants, sizes, and standard HTML attributes.

Pattern: See how `apps/portal/src/ui/Button.tsx` defines variant props, uses `clsx` or template literals for conditional Tailwind classes, and forwards refs.

2
apps/portal/src/ui/NewComponent.tsx

Create the new component file in `apps/portal/src/ui/`. Export a named React component with TypeScript props interface. Use Tailwind classes for all styling.

Pattern: Match the export and typing pattern from `apps/portal/src/ui/Button.tsx`: named export, Props type, forwardRef if needed.

3
apps/portal/src/ui/index.ts

If a barrel export file exists, add the new component export. Otherwise, import directly from the component file in consuming code.

Pattern: Check if `apps/portal/src/ui/index.ts` exists; if so, add `export { default as NewComponent } from './NewComponent';`

Conventions to follow:

All UI components live in `apps/portal/src/ui/` — this is the design system layerUse Tailwind CSS classes exclusively for styling — no CSS modules or styled-componentsComponents must be generic and feature-agnostic — they should not import from `src/components/<feature>/`TypeScript props interfaces should be exported for consumers to useFollow the Tailwind config in `apps/portal/tailwind.config.cjs` for available theme tokens

Add a new Next.js page/route

1
apps/portal/src/pages/offers/index.tsx

Study this page as a reference. Pages import tRPC hooks, fetch data with `trpc.useQuery`, handle loading/error states, and compose feature components.

Pattern: See how `apps/portal/src/pages/offers/index.tsx` uses `trpc.useQuery` for data fetching, renders loading spinners, and composes components from `src/components/offers/`.

2
apps/portal/src/pages/yourfeature/index.tsx

Create the new page file. Next.js file-based routing means `src/pages/yourfeature/index.tsx` maps to `/yourfeature`. For dynamic routes, use `[paramName].tsx` syntax.

Pattern: For dynamic routes, see `apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsx` — it uses `useRouter().query` to extract URL parameters.

3
apps/portal/src/components/global/AppShell.tsx

Add navigation entry for the new page if it should appear in the main navigation bar.

Pattern: See the navigation array in `AppShell.tsx` — add an object with `name`, `href`, and `icon` matching the existing entries.

Conventions to follow:

Pages are thin orchestration layers — business logic belongs in server routers or utility functions, not in page componentsUse `trpc.useQuery` and `trpc.useMutation` for all server communication — never use raw `fetch` for tRPC endpointsDynamic route parameters use Next.js bracket syntax: `[id].tsx` for `/feature/:id`Pages should handle loading, error, and empty states explicitlyImport feature components from `src/components/<feature>/` — do not inline large component trees in page files

Add a new REST API endpoint (non-tRPC)

1
apps/portal/src/pages/api/file-storage.ts

Study this file as the reference pattern for non-tRPC API routes. It exports a default handler function that checks `req.method` for GET/POST/DELETE and returns JSON responses with appropriate status codes.

Pattern: See how `file-storage.ts` handles multiple HTTP methods with a switch/if on `req.method`, validates input, performs operations, and returns `res.status(200).json({...})`.

2
apps/portal/src/pages/api/your-endpoint.ts

Create a new file in `apps/portal/src/pages/api/`. Export a default `NextApiHandler` function. Use `req.method` to handle different HTTP methods.

Pattern: Match the handler signature and error handling pattern from `apps/portal/src/pages/api/file-storage.ts`.

3
apps/portal/src/pages/api/restricted.ts

If the endpoint requires authentication, reference this file to see how to check `getServerAuthSession` and return 401 for unauthenticated requests.

Pattern: See how `restricted.ts` calls `getServerAuthSession({req, res})` and checks for a valid session before proceeding.

Conventions to follow:

Non-tRPC API routes live in `apps/portal/src/pages/api/` — the file path maps to the URL (e.g., `api/file-storage.ts` → `/api/file-storage`)Prefer tRPC for most endpoints — use raw API routes only for file uploads, webhooks, or third-party integrationsAlways validate request body and query parameters before processingUse `getServerAuthSession` from `apps/portal/src/server/context.ts` for authentication checksReturn appropriate HTTP status codes: 200 for success, 400 for bad input, 401 for unauthorized, 500 for server errors

Add a new shared/cross-feature component (typeahead, picker, etc.)

1
apps/portal/src/components/shared/CitiesTypeahead.tsx

Study this file as the reference pattern for shared components. It wraps a generic UI component with feature-specific data fetching (tRPC query for cities) and exposes a clean props interface.

Pattern: See how `CitiesTypeahead.tsx` combines a UI typeahead component with `trpc.useQuery` for data, debounced search input, and a callback prop for selection.

2
apps/portal/src/components/shared/YourSharedComponent.tsx

Create the new shared component in `apps/portal/src/components/shared/`. If it needs server data, use tRPC hooks. If it's purely presentational, compose from `src/ui/` components.

Pattern: Match the pattern from `apps/portal/src/components/shared/CitiesTypeahead.tsx`: typed props interface, tRPC data fetching, composition of UI primitives.

Conventions to follow:

Shared components live in `apps/portal/src/components/shared/` — they can be used by any featureShared components CAN use tRPC hooks for data fetching (unlike pure UI components in `src/ui/`)Shared components should NOT import from feature-specific directories like `src/components/offers/`Use TypeScript generics for reusable components that work with different data types

Add a new environment variable

1
apps/portal/src/env/schema.mjs

Add the new variable to either `serverSchema` (server-only) or `clientSchema` (exposed to browser). Use Zod validators like `z.string()`, `z.string().url()`, etc.

Pattern: See how existing variables are defined in `schema.mjs`: `serverSchema` uses `z.object({ DATABASE_URL: z.string() })` and `clientSchema` uses `z.object({ NEXT_PUBLIC_...: z.string() })`.

2
apps/portal/src/env/client.mjs

If adding a client-side variable (NEXT_PUBLIC_ prefix), map it in the `clientEnv` object so it gets validated and exposed.

Pattern: See how `client.mjs` maps `NEXT_PUBLIC_` variables from `process.env` into the validated client env object.

3
apps/portal/.env

Add the actual value to the `.env` file (or `.env.example` for documentation). Server variables can have any name; client variables MUST start with `NEXT_PUBLIC_`.

Pattern: Follow the naming convention of existing variables in the `.env` file.

Conventions to follow:

All environment variables MUST be declared in `apps/portal/src/env/schema.mjs` with Zod validation — never use `process.env.X` directlyClient-side variables MUST have the `NEXT_PUBLIC_` prefix per Next.js conventionAccess server env via `import { env } from 'src/env/server.mjs'` and client env via `import { env } from 'src/env/client.mjs'`The app will fail to start if a required env variable is missing — this is intentional

Add database seed data

1
apps/portal/prisma/seed-questions.ts

Study this file as the reference pattern for seed scripts. It uses the Prisma client to create records, handles upserts to avoid duplicates, and reads from data files.

Pattern: See how `seed-questions.ts` imports PrismaClient, creates records with `prisma.questionsQuestion.create()`, and handles relationships between models.

2
apps/portal/prisma/seed-your-data.ts

Create a new seed file in `apps/portal/prisma/`. Import PrismaClient, instantiate it, and use `prisma.yourModel.create()` or `prisma.yourModel.upsert()` to insert data.

Pattern: Match the structure from `apps/portal/prisma/seed-companies.ts`: read source data (CSV/JSON), iterate and create records, handle errors gracefully.

Conventions to follow:

Seed files live in `apps/portal/prisma/` alongside the schemaUse `upsert` instead of `create` when seed data might already exist to make scripts idempotentSeed scripts should be runnable independently — import their own PrismaClient instanceReference `apps/portal/prisma/seed-companies.ts` for CSV-based seeding and `apps/portal/prisma/seed-questions.ts` for programmatic seeding

Coding Conventions

Standards and patterns used in this codebase

Architecture

Feature-based folder organization with co-located components, pages, server routers, and utils per feature domain (offers, questions, resumes)

Each feature has its own components/, pages/, server/router/, and utils/ directories: src/components/offers/, src/pages/offers/, src/server/router/offers/, src/utils/offers/
apps/portal/src/components/offersapps/portal/src/pages/offersapps/portal/src/server/router/offersapps/portal/src/utils/offers
API Layer

Use tRPC for type-safe API routing with domain-specific routers composed into a root router

Each feature has dedicated tRPC routers (e.g., offers CRUD, analysis, comments) that are composed together in the server infrastructure layer
apps/portal/src/server/router/offersapps/portal/src/server/router/questionsapps/portal/src/server/router/resumes
Styling

Use Tailwind CSS with a shared base configuration extended per-app, including custom colors and typography from a shared package

Portal app extends shared tailwind-config package: require('@tih/tailwind-config/tailwind.config.js') and adds UI package content paths
packages/tailwind-config/tailwind.config.jsapps/portal/tailwind.config.cjs
Linting

Shared ESLint configuration package (eslint-config-tih) enforces consistent TypeScript/React/Next.js linting rules across the monorepo

Centralized ESLint config in packages/eslint-config-tih/index.js defines rules for TypeScript and React projects
packages/eslint-config-tih/index.js
Architecture

Monorepo structure with shared packages (eslint-config, tailwind-config) consumed by multiple apps (portal, website)

Shared packages like @tih/tailwind-config and @tih/eslint-config are referenced by both the portal Next.js app and the website Docusaurus app
packages/eslint-config-tih/index.jspackages/tailwind-config/tailwind.config.jsapps/portal/tailwind.config.cjs
Components

Shared UI components and cross-feature components are separated from feature-specific components, with a dedicated ui/ directory for the design system and shared/ for cross-feature components

Design system primitives live in src/ui/, cross-feature components like typeaheads and job title pickers live in src/components/shared/, and feature-specific components live in src/components/{feature}/
apps/portal/src/uiapps/portal/src/components/sharedapps/portal/src/components/offers
Database

Use Prisma as the ORM with dedicated seed scripts per data domain, each in separate TypeScript files under prisma/

Separate seed scripts for different data types: seed-companies.ts reads from CSV, seed-salaries.ts reads from Excel, seed-questions.ts creates sample data, seed-analysis.ts generates analysis for existing profiles
apps/portal/prisma/seed-analysis.tsapps/portal/prisma/seed-companies.tsapps/portal/prisma/seed-questions.tsapps/portal/prisma/seed-salaries.ts
TypeScript

Centralized type declarations in a dedicated types/ directory for domain models and auth types

Domain model types and auth-related type declarations are co-located in src/types/ rather than scattered across feature directories
apps/portal/src/types
Configuration

Next.js configuration uses .mjs extension with typed config objects and environment validation

next.config.mjs exports a typed configuration with experimental and build settings
apps/portal/next.config.mjs
Routing

Next.js file-based routing with feature-namespaced page directories following Next.js conventions

Pages are organized under src/pages/{feature}/ matching the feature domain structure (src/pages/offers/, src/pages/questions/, src/pages/resumes/)
apps/portal/src/pages/offersapps/portal/src/pages/questionsapps/portal/src/pages/resumes
Infrastructure

Next.js middleware is used for request-level concerns like geolocation extraction, setting data via cookies

Middleware extracts geolocation data from the request and sets it as a cookie for downstream consumption
apps/portal/src/middleware.ts