Frontend Developer Handbook
Welcome to the team! This document serves as the single source of truth for our frontend development standards, architecture, and workflows for the CoreEnroll App.

1. Core Tech Stack
We use a modern, performance-focused stack:
- Build Tool: Vite 6
- Framework: React 19
- Routing: React Router 7
- Language: TypeScript 5 (Strict mode enabled)
- Styling: Tailwind CSS 4
- State Management:
- Server State: TanStack Query v5
- URL State: Nuqs (Type-safe search params)
- Global/Shared: React Context (for simple cases) or Zustand (for complex state)
- Forms: React Hook Form + Zod
- Linting/Formatting: Biome
- Icons: Lucide React
1.1 State Management: Context vs Zustand
New developers often ask: When should I use Context vs Zustand?
| Feature | React Context | Zustand |
|---|---|---|
| Use Case | Static, low-frequency updates, simple dependency injection. | Complex, high-frequency updates, cross-slice logic. |
| Examples | Theme (Dark/Light), Auth User Object, Toast Notifications. | Shopping Cart, Multi-step Form Data, Dashboard Filters. |
| Performance | Renders all consumers on update. | Selectors allow granular re-renders. |
Rule of Thumb: Start with local state (useState). If you need to share it, try Context. If performance suffers or logic gets messy, upgrade to Zustand.
2. Monorepo Architecture
We operate in a Monorepo. This allows us to share types, UI components, and logic across different applications.
- /apps/: Contains our Vite + React applications.
apps/admin: The Admin Dashboard application.
- /packages/: Shared libraries.
packages/ui: Our shared Shadcn UI library.packages/gen: Generated API clients, types, and TanStack Query options.
2.1 Path Aliases (@/)
To keep our imports clean and maintainable, we use TypeScript Path Aliases.
NEVER use relative imports like ../../../../shared/ui/button.
@/shared->src/shared@/entities->src/entities@/features->src/features@/widgets->src/widgets@/pages->src/pages@/app->src/app
Correct ✅:
import { Button } from "@/shared/ui/button";
import { useUser } from "@/entities/user";
Incorrect ❌:
import { Button } from "../../../shared/ui/button";
3. Feature-Sliced Design (FSD) Deep Dive
We strictly follow the Feature-Sliced Design methodology. It is critical to understand the hierarchy of Layers, Slices, and Segments.
3.1 Layers (The "Where")
Top-level directories that distinguish the scope of functionality.
Rule: A layer can only import from layers below it.
- App (
src/app):- Initializes the app. Global providers, styles, entry points.
- Pages (
src/pages):- Composition layer. A page couples Widgets, Features, and Entities to form a full screen.
- Widgets (
src/widgets):- Large, independent UI blocks (e.g.,
Header,Sidebar,UserList). - Widgets combine Features and Entities.
- Large, independent UI blocks (e.g.,
- Features (
src/features):- User Actions. Any interaction that brings business value.
- Examples:
AuthByEmail,ChangeTheme,AddToCart,FilterPolicy.
- Entities (
src/entities):- Business Data. The core domain models.
- Examples:
User,Policy,Order,Payment. - Contains: Data model (types), UI for that data (cards, rows), and API logic.
- Strict Rule: Entities cannot import other Entities.
- Shared (
src/shared):- Reusable infrastructure code (UI kit, API clients, helpers).
- Agnostic to business logic.
3.2 Slices (The "What")
Inside a Layer (except shared and app), code is partitioned by domain or subject. This directory is called a Slice.
For example, in the Refunding Feature and Order Entity:
src/features/refunding/-> "refunding" is the Slice.src/entities/order/-> "order" is the Slice.
3.3 Segments (The "How")
Inside a Slice, code is organized by technical purpose.
ui/: React components (e.g.,ui/user-card.tsx).model/: Business logic, state (Zustand), and types (e.g.,model/user.store.ts).api/: Data fetching logic (e.g.,api/user.queries.ts).lib/: Helper functions specific to this slice.
Example File Structure
src/
├── entities/ # Layer
│ └── user/ # Slice (Domain: User)
│ ├── ui/ # Segment
│ │ └── user-card.tsx # Component using User data
│ ├── model/ # Segment
│ │ └── types.ts # User type definitions
│ └── api/ # Segment
│ └── user.queries.ts
└── features/ # Layer
└── auth-by-email/ # Slice (Domain: Auth)
├── ui/ # Segment
│ └── login-form.tsx
└── model/ # Segment
└── auth.schema.ts
4. Styling & Theming (CSS Variables)
We use a Theme-First approach with Tailwind CSS v4.
We DO NOT use hardcoded colors like bg-blue-500 or text-gray-600 in our components.
Instead, we use semantic CSS variables defined in src/app/index.css. This ensures that:
- The app is fully themable (Dark Mode, High Contrast, Brand pivots).
- Components are consistent.
4.1 How to Style
Use the semantic tokens provided by Shadcn/Tailwind configuration.
Correct ✅:
<div className="bg-primary text-primary-foreground hover:bg-primary/90">
<span className="text-muted-foreground">Description</span>
<div className="border-border border">Card</div>
</div>
Incorrect ❌:
<div className="bg-blue-600 text-white hover:bg-blue-700">
<span className="text-gray-400">Description</span>
<div className="border-gray-200 border">Card</div>
</div>
4.2 Adding New Variables
If you need a repetitive color that doesn't exist, create a variable in src/app/index.css.
Example: You need a specific "Status Success" color that isn't just green-500.
- Open
src/app/index.css. - Add the variable to
:root(Light mode) and.dark(Dark mode).
@theme {
--color-status-success: hsl(142, 76%, 36%);
/* ... */
}
/* In Dark Mode section */
.dark {
--status-success: 142 76% 36%; /* HSL values */
}
- Use it in your code:
<div className="bg-status-success text-white">...</div>
5. UI Components
We use Shadcn UI as our base component library.
We maintain our UI kit in packages/ui. To add a new component to the Admin app (or update the shared package), you must run the installation command within the packages/ui directory.
Installing a Component
Do not copy-paste code manually unless customizing heavily.
# 1. Go to the UI package
cd packages/ui
# 2. Add the component
pnpm dlx shadcn@latest add accordion
This will verify dependencies and place the component in packages/ui/src/components/ui/accordion.tsx.
6. Getting Started
Prerequisites
- Node.js v20+ (Development on v22.15.0)
- pnpm 9.0.0+
Installation
CRITICAL: You must install dependencies from the root directory to ensure turbo and workspace packages are linked correctly.
# Install pnpm if you don't have it
npm install -g pnpm
# Install dependencies (Must be done from root!)
pnpm install
Running the App
We use specific scripts defined in the root package.json to run commands for specific apps.
# Copy environment variables
cp apps/admin/.env.example apps/admin/.env
# Run the Admin App (Dev Mode)
pnpm run dev:admin
This runs turbo run dev --filter=admin, ensuring only the admin app and its dependencies are started.
Other Commands
| Action | Command | Description |
|---|---|---|
| Build Admin | pnpm run build:admin |
Builds the admin app for production |
| Preview | pnpm run preview:admin |
Preview the production build locally |
| Lint Admin | pnpm run lint:admin |
Runs Biome linting on the admin app |
| Commit | pnpm run cz |
Start interactive commit interface |
7. API & Data Fetching (The "Hey API" Workflow)
We use Hey API to generate our API client from the OpenAPI schema. We use ky as the underlying fetch client.
7.1 Generation
The API types and hooks are generated in @insurance/gen. Do not edit files in the gen package manually. API files are generated automatically when we build Frontend application. In case you want to generate them manually:
// Navigate to api package
cd packages/gen
// Run build command
pnpm run build7.2 Query & Mutation Pattern
Every feature/entity should have an api/ segment containing its query and mutation logic. Use Query Factories to organize keys and options.
Example: entities/admin/api/dashboard.queries.ts
import { useQuery } from "@tanstack/react-query";
import { getAdminDashboardOptions } from "@insurance/gen/api/tanstack/react-query";
// Query Factory
export const dashboardQueries = {
main: () => ({
...getAdminDashboardOptions(),
enabled: true
}),
};
export const useAdminDashboard = () => {
return useQuery(dashboardQueries.main());
};
7.3 Cache Invalidation
To maintain consistency, we use a custom utility invalidateQueryById located in shared/utils/query. This ensures robust cache invalidation even with complex query keys.
Usage Example
Use this inside your mutation onSuccess callback.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { invalidateQueryById } from "@/shared/utils/query";
export const useUpdateUser = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateUser,
onSuccess: (data, variables) => {
// Invalidate the specific user query
invalidateQueryById(queryClient, 'getUserById', {
path: { userId: variables.id }
});
// Invalidate the list of users (if filters match)
invalidateQueryById(queryClient, 'getUsers', {
query: { role: 'admin' }
});
},
});
};
8. How to Add a New Feature (Step-by-Step)
This guide shows you exactly how to add a new feature (e.g., "Assign Policy to User") following FSD.
Step 1: Check the API
Go to packages/gen or check Swagger. Does the API endpoint exist?
- Yes: Good, you can proceed.
- No: Backend needs to deploy first.
Step 2: Create/Update the Entity (The "Data")
Does the data model (e.g., Policy) exist in src/entities/policy?
- No: Create
src/entities/policy.model/types.ts: Define the interface.api/policy.queries.ts: AddusePolicyhooks using@insurance/gen.ui/policy-card.tsx: Create a read-only view of the policy.
- Yes: Extend existing files if needed.
Step 3: Create the Feature (The "Action")
This is where the user interaction happens.
- Create
src/features/assign-policy/. ui/assign-policy-button.tsx: The trigger button.ui/assign-policy-dialog.tsx: The form/modal.model/assign-policy.schema.ts: Zod schema for validation.api/assign-policy.mutation.ts: TheuseMutationhook.
Step 4: Assemble in a Widget (Optional)
If this feature is part of a larger block (e.g., a User Profile), update the Widget.
src/widgets/user-profile/ui/user-profile.tsx: matchUserCard(Entity) withAssignPolicyButton(Feature).
Step 5: Add to Page
- Import the Widget or Feature into
src/pages/user-details/ui/user-details-page.tsx.
9. Summary Checklist for Developers
- [ ] FSD: Did I create a Slice for my domain logic?
- [ ] FSD: Did I separate UI, Model, and API into Segments?
- [ ] Styling: Did I use
bg-primary/text-mutedinstead of hardcoded hex/colors? - [ ] API: Did I use the generated hooks/options from
@insurance/gen? - [ ] Commits: Is my commit message conventional? (Use
pnpm run czif unsure) - [ ] Imports: Did I use
@/aliases instead of relative paths? - [ ] FSD: Did I separate UI, Model, and API into Segments?
- [ ] Styling: Did I use
bg-primary/text-mutedinstead of hardcoded hex/colors? - [ ] API: Did I use the generated hooks/options from
@insurance/gen? - [ ] Commits: Is my commit message conventional? (Use
pnpm run czif unsure)