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
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
apps/portal/src/pages/_app.tsxRoot 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.
apps/portal/src/server/router/index.tsComposes 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.
apps/portal/src/pages/api/trpc/[trpc].tsNext.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.
apps/portal/src/server/context.tsCreates 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).
apps/portal/src/server/db/client.tsExports the singleton Prisma client instance used across all server-side code.
Start here when understanding database access patterns or troubleshooting Prisma connection issues.
apps/portal/src/utils/trpc.tsConfigures 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.
apps/portal/src/components/global/AppShell.tsxGlobal 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.
apps/portal/src/env/schema.mjsZod schemas defining all required server and client environment variables.
Start here when adding a new environment variable to the application.
apps/portal/next.config.mjsNext.js build and runtime configuration including redirects, rewrites, and module transpilation.
Start here when modifying build behavior, adding redirects, or configuring external packages.
apps/portal/tailwind.config.cjsTailwind 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
apps/portal/src/server/router/questions/questions-question-router.tsOpen 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.
apps/portal/src/server/router/questions/questions-question-router.tsAdd 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.
apps/portal/src/pages/questions/[questionId]/[questionSlug]/index.tsxCall 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:
Add a new feature module (e.g., a 'Salaries' feature)
apps/portal/src/server/router/offers/offers-profile-router.tsStudy 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.
apps/portal/src/server/router/salaries/salaries-router.tsCreate 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(...)...`
apps/portal/src/server/router/index.tsImport 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.
apps/portal/src/pages/salaries/index.tsxCreate 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.
apps/portal/src/components/salaries/SalariesTable.tsxCreate 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.
apps/portal/src/components/global/AppShell.tsxAdd 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:
Add a new shared UI component to the design system
apps/portal/src/ui/Button.tsxStudy 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.
apps/portal/src/ui/NewComponent.tsxCreate 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.
apps/portal/src/ui/index.tsIf 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:
Add a new Next.js page/route
apps/portal/src/pages/offers/index.tsxStudy 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/`.
apps/portal/src/pages/yourfeature/index.tsxCreate 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.
apps/portal/src/components/global/AppShell.tsxAdd 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:
Add a new REST API endpoint (non-tRPC)
apps/portal/src/pages/api/file-storage.tsStudy 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({...})`.
apps/portal/src/pages/api/your-endpoint.tsCreate 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`.
apps/portal/src/pages/api/restricted.tsIf 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:
Add a new shared/cross-feature component (typeahead, picker, etc.)
apps/portal/src/components/shared/CitiesTypeahead.tsxStudy 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.
apps/portal/src/components/shared/YourSharedComponent.tsxCreate 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:
Add a new environment variable
apps/portal/src/env/schema.mjsAdd 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() })`.
apps/portal/src/env/client.mjsIf 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.
apps/portal/.envAdd 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:
Add database seed data
apps/portal/prisma/seed-questions.tsStudy 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.
apps/portal/prisma/seed-your-data.tsCreate 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:
Coding Conventions
Standards and patterns used in this codebase
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/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 layerUse 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 pathsShared 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 projectsMonorepo 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 appShared 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}/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 profilesCentralized 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 directoriesNext.js configuration uses .mjs extension with typed config objects and environment validation
next.config.mjs exports a typed configuration with experimental and build settingsNext.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/)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