# Frontend Development — Rules & Guidelines Rules and conventions every frontend developer must follow. Read this before writing your first line of code. --- ## 1. Language & Typing - **TypeScript** is mandatory — no plain JavaScript files (`.js`, `.jsx`) in the codebase. - Use **strict mode** (`"strict": true` in `tsconfig.json`) — the project must compile with zero errors. - Never use `any`. If a type is truly unknown, use `unknown` and narrow it with type guards. - Prefer **interfaces** for object shapes that may be extended, **type aliases** for unions, intersections, and utility types. - Every function must have explicit parameter types and return types — including React components (`React.FC` is discouraged; type props and return `JSX.Element` explicitly). - Use `T | null` or `T | undefined` instead of `Optional` patterns — be explicit about nullability. - Use `as const` for constant literals and enums where it improves type narrowness. - Run `tsc --noEmit` in CI — the codebase must pass with zero type errors. ```tsx // Good interface BanEntry { ip: string; jail: string; bannedAt: string; expiresAt: string | null; } function BanRow({ ban }: { ban: BanEntry }): JSX.Element { return {ban.ip}{ban.jail}; } // Bad — untyped, uses `any` function BanRow({ ban }: any) { return {ban.ip}; } ``` --- ## 2. Reusable Types - All **shared type definitions** live in a dedicated `types/` directory. - Group types by domain: `types/ban.ts`, `types/jail.ts`, `types/auth.ts`, `types/api.ts`, etc. - Import types using the `import type` syntax when the import is only used for type checking — this keeps the runtime bundle clean. - Component-specific prop types may live in the same file as the component, but any type used by **two or more files** must move to `types/`. - Never duplicate a type definition — define it once, import everywhere. - Export API response shapes alongside their domain types so consumers always know what the server returns. ```ts // types/ban.ts export interface Ban { ip: string; jail: string; bannedAt: string; expiresAt: string | null; banCount: number; country: string | null; } export interface BanListResponse { bans: Ban[]; total: number; } ``` ```tsx // components/BanTable.tsx import type { Ban } from "../types/ban"; ``` --- ## 3. Type Safety in API Calls - Every API call must have a **typed request** and **typed response**. - Define response shapes as TypeScript interfaces in `types/` and cast the response through them. - Use a **central API client** (e.g., a thin wrapper around `fetch` or `axios`) that returns typed data — individual components never call `fetch` directly. - Validate or assert the response structure at the boundary when dealing with untrusted data; for critical flows, consider a runtime validation library (e.g., `zod`). - API endpoint paths are **constants** defined in a single file (`api/endpoints.ts`) — never hard-code URLs in components. ```ts // api/client.ts const BASE_URL = import.meta.env.VITE_API_URL ?? "/api"; async function get(path: string): Promise { const response: Response = await fetch(`${BASE_URL}${path}`, { credentials: "include", }); if (!response.ok) { throw new ApiError(response.status, await response.text()); } return (await response.json()) as T; } export const api = { get, post, put, del } as const; ``` ```ts // api/bans.ts import type { BanListResponse } from "../types/ban"; import { api } from "./client"; export async function fetchBans(hours: number): Promise { return api.get(`/bans?hours=${hours}`); } ``` --- ## 4. Code Organization ### Project Structure ``` frontend/ ├── public/ ├── src/ │ ├── api/ # API client, endpoint definitions, per-domain request files │ ├── assets/ # Static images, fonts, icons │ ├── components/ # Reusable UI components (buttons, modals, tables, etc.) │ ├── hooks/ # Custom React hooks │ ├── layouts/ # Page-level layout wrappers (sidebar, header, etc.) │ ├── pages/ # Route-level page components (one per route) │ ├── providers/ # React context providers (auth, theme, etc.) │ ├── theme/ # Fluent UI custom theme, tokens, and overrides │ ├── types/ # Shared TypeScript type definitions │ ├── utils/ # Pure helper functions, constants, formatters │ ├── App.tsx # Root component, FluentProvider + router setup │ ├── main.tsx # Entry point │ └── vite-env.d.ts # Vite type shims ├── .eslintrc.cjs ├── .prettierrc ├── tsconfig.json ├── vite.config.ts └── package.json ``` ### Separation of Concerns - **Pages** handle routing and compose layout + components — they contain no business logic. - **Components** are reusable, receive data via props, and emit changes via callbacks — they never call the API directly. - **Hooks** encapsulate stateful logic, side effects, and API calls so components stay declarative. - **API layer** handles all HTTP communication — components and hooks consume typed functions from `api/`, never raw `fetch`. - **Types** are purely declarative — no runtime code in `types/` files. - **Utils** are pure functions with no side effects and no React dependency. - **Theme** contains exclusively Fluent UI custom token overrides and theme definitions — no component logic. --- ## 5. UI Framework — Fluent UI React (v9) - **Fluent UI React Components v9** (`@fluentui/react-components`) is the only UI component library allowed — do not add alternative component libraries (Material UI, Chakra, Ant Design, etc.). - Install via npm: ```bash npm install @fluentui/react-components @fluentui/react-icons ``` ### FluentProvider - Wrap the entire application in `` at the root — this supplies the theme and design tokens to all Fluent components. - The provider must sit above the router so every page inherits the theme. ```tsx // App.tsx import { FluentProvider, webLightTheme } from "@fluentui/react-components"; import { BrowserRouter } from "react-router-dom"; import AppRoutes from "./AppRoutes"; function App(): JSX.Element { return ( ); } export default App; ``` ### Theming & Design Tokens - Use the built-in themes (`webLightTheme`, `webDarkTheme`) as the base. - Customise design tokens by creating a **custom theme** in `theme/` — never override Fluent styles with raw CSS. - Reference tokens via the `tokens` object from `@fluentui/react-components` when writing `makeStyles` rules. - If light/dark mode is needed, switch the `theme` prop on `FluentProvider` — never duplicate style definitions for each mode. ```ts // theme/customTheme.ts import { createLightTheme, createDarkTheme } from "@fluentui/react-components"; import type { BrandVariants, Theme } from "@fluentui/react-components"; const brandColors: BrandVariants = { 10: "#020305", // ... define brand colour ramp 160: "#e8ebf9", }; export const lightTheme: Theme = createLightTheme(brandColors); export const darkTheme: Theme = createDarkTheme(brandColors); ``` ### Styling with `makeStyles` (Griffel) - All custom styling is done via `makeStyles` from `@fluentui/react-components` — Fluent UI uses **Griffel** (CSS-in-JS with atomic classes) under the hood. - Never use inline `style` props, global CSS, or external CSS frameworks for Fluent components. - Co-locate styles in the same file as the component they belong to, defined above the component function. - Use `mergeClasses` when combining multiple style sets conditionally. - Reference Fluent **design tokens** (`tokens.colorBrandBackground`, `tokens.fontSizeBase300`, etc.) instead of hard-coded values — this ensures consistency and automatic theme support. ```tsx import { makeStyles, tokens, mergeClasses } from "@fluentui/react-components"; const useStyles = makeStyles({ root: { padding: tokens.spacingVerticalM, backgroundColor: tokens.colorNeutralBackground1, }, highlighted: { backgroundColor: tokens.colorPaletteRedBackground2, }, }); function BanCard({ isHighlighted }: BanCardProps): JSX.Element { const styles = useStyles(); return (
{/* ... */}
); } ``` ### Component Usage Rules - **Always** prefer Fluent UI components over plain HTML elements for interactive and presentational UI: `