The Talent Accelerator — Frontend Developer Documentation¶
Audience: Frontend developers only. Goal: A complete onboarding + reference guide so a new frontend developer can understand the project, navigate the code, and start contributing without a live walkthrough.
Ground rule: Everything here is derived from the actual source code. Where something cannot be determined from the code, it says "Unable to determine from the available code." Nothing is invented.
ℹ️ Scope note. The frontend is not one app — it is five separate React applications that share one template, one backend, and one login. They live under
Frontend Prod/:sso-cicd(sign-in & admin portal),tom-cicd(TOM — offers & pay data),taref-cicd(JE — job evaluation),live-bm-cicd(LBT — benchmarking),dashboard-cicd(analytics). Unless a section says otherwise, code examples are fromtom-cicd(the richest app); the same patterns apply to all five because they were forked from the same template.
Table of Contents¶
1. Introduction¶
Purpose of the frontend¶
The frontend is the user interface for a compensation-management platform ("The Talent Accelerator", internally "TOM"). Companies use it to manage pay data, build candidate offers, evaluate jobs, benchmark pay, and view analytics. (For the product/business view, see the Product Handbook.)
High-level overview¶
Five single-page applications (SPAs), one per business area, all talking to the same backend REST API:
| App folder | Business area | REACT_APP_APPLICATION |
|---|---|---|
sso-cicd |
Login + company/user administration | SSO |
tom-cicd |
Pay data + offer modelling + AI insights | TOM (also builds as ADMIN_PANEL) |
taref-cicd |
Job evaluation + job libraries | JE |
live-bm-cicd |
Live Pay Benchmarking Tool | LBT |
dashboard-cicd |
Compensation analytics | LBT default (serves the dashboard) |
They share login through a browser cookie on a common domain (see §11).
Technology stack¶
| Concern | Choice | Version (from package.json) |
|---|---|---|
| Language | TypeScript | ^4.1.2 |
| Framework | React | ^17.0.2 (taref-cicd uses React 18) |
| Build tooling | Create React App + CRACO | react-scripts 4.0.3, @craco/craco ^6.2.0 |
| State | Redux Toolkit + RTK Query | @reduxjs/toolkit ^1.6.1 |
| State persistence | redux-persist | ^6.0.0 |
| Routing | React Router | react-router-dom ^5.2.0 (v5) |
| UI library (primary) | Ant Design 4 | antd ^4.16.6 |
| UI library (secondary) | MUI 5 + Emotion | @mui/material ^5.15.14 |
| Utility CSS | Tailwind CSS (postcss7-compat) | via craco.config.js |
| Styling language | LESS | less ^4.1.1, craco-less |
| Charts | Recharts | ^2.7.2 (TOM/JE/Dashboard) |
| Rich text | react-quill | ^2.0.0 |
| HTTP | native fetch (wrapped) |
— |
| JWT decode | jwt-decode | ^4.0.0 |
| Cookies | universal-cookie | ^4.0.4 |
| Dates | moment + date-fns | — |
| Icons | @ant-design/icons + react-icons | — |
| Git hooks | husky + lint-staged | ^7.0.1, ^11.0.0 |
| Formatting | Prettier | .prettierrc present |
Design principles (as observed in the code)¶
- One template, five apps. Every app has an identical
src/layout so a developer who learns one learns all five. - API-first via RTK Query. All backend calls are RTK Query
createApi"services"; components use the generated hooks. - Path aliases everywhere. Imports use
@components,@services,@hooks, etc. (never long relative paths). - Permission-driven UI. Buttons, tabs, routes, and menu items are gated by
checkPermission(...). - Colocation. A page owns its subcomponents in its own folder.
- Shared cookie SSO. No app has a real login except
sso-cicd; the rest read a shared cookie and verify.
2. Project Structure¶
Every app has the same src/ layout. Understand it once, apply it five times.
text
<app>/
├── public/ # index.html, favicon, color.less (runtime theme), /samples/*.csv
├── src/
│ ├── App.tsx # Root component: bootstraps auth from cookie, picks route config, renders Layout
│ ├── index.tsx # ReactDOM entry: Provider + PersistGate + HashRouter
│ ├── assets/ # Images, SVGs, fonts
│ ├── components/ # Reusable UI components (see §7)
│ ├── constants/ # roles, env-derived constants, theme JSON, static data
│ ├── hooks/ # Custom hooks (see §12)
│ ├── pages/ # Screens, split into admin/ and client/ (see §6)
│ ├── router/ # Route configs, paths, permissions, guards (see §4)
│ ├── services/ # RTK Query APIs + REST wrapper + cookies/storage (see §9)
│ ├── store/ # Redux store + per-domain slices (see §8)
│ ├── styles/ # LESS theme + global styles (see §13)
│ ├── types/ # Shared TypeScript types
│ └── utils/ # Pure helper functions (see §12)
├── craco.config.js # Path aliases, LESS, Ant theme plugin, Tailwind
├── tsconfig.json / tsconfig.paths.json
├── tailwind.config.js
├── .env.example # Required env vars (values injected per environment)
├── .prettierrc
└── buildspec.yml / firebase.json # CI/deploy
Folder-by-folder (why each exists)¶
| Folder | Purpose | Interacts with |
|---|---|---|
App.tsx / index.tsx |
App bootstrap & providers. index.tsx wires Redux + persistence + router; App.tsx runs the cookie-based login and renders the shell. |
store, router, services, components/Layout |
components/ |
Reusable UI (tables, modals, buttons, layout shell, stepper, upload). Split between generic and feature-specific. | hooks, services, utils, store |
constants/ |
Roles list, env-derived constants (ssoUrl, cookiesDomain), theme JSON (admin.json/client.json), static data (world.json). |
Imported widely |
hooks/ |
Cross-cutting hooks: typed Redux hooks, useDebounce, useFormValidation, useBreadcrumbs. |
store, services |
pages/ |
The screens. admin/ = internal-staff screens, client/ = company screens. Each page folder colocates its subcomponents. |
components, services, store, router |
router/ |
routeConfig.ts (route → component maps), paths.ts (URL strings), permissions.ts (permission constants), Routes.tsx + RouteWithSubRoutes.tsx (guards). |
pages, hooks, utils |
services/ |
The API layer: one file per backend domain (auth.ts, offers.ts…), restService.ts (the fetch wrapper), cookies.ts, storage.ts. |
store, constants |
store/ |
Redux store config + one folder per domain slice (auth, offers, companies…). |
services, types |
styles/ |
theme.less (design tokens), global.less, fonts.less, utils.less. |
craco.config.js |
types/ |
Shared TS interfaces (globals.ts, index.ts, types.ts). |
Everywhere |
utils/ |
Pure functions: compensation math (calculateStiTotal…), checkPermission, isAdmin, formatters, validateFile. |
Pages, components |
💡 Why
admin/vsclient/underpages/? The same app template serves both internal staff ("admin") and company users ("client"). The split keeps the two audiences' screens separate. Intom-cicdtoday, the app renders the client route config for everyone (see §4 — the admin/client branch is currently collapsed).
3. Frontend Architecture¶
Overall architecture¶
flowchart TB
subgraph Browser
subgraph "React App (one of 5)"
IDX[index.tsx<br/>Provider + PersistGate + HashRouter]
APP[App.tsx<br/>cookie login → verify → Layout]
RT[Router<br/>route config + guards]
PG[Pages]
CM[Components]
HK[Hooks]
end
RTKQ[RTK Query services<br/>generated hooks]
REST[restService.ts<br/>fetch wrapper + token + refresh]
STORE[(Redux store<br/>+ redux-persist)]
COOKIE[(Shared cookie<br/>user_data / selected_company)]
end
API[(Backend REST API)]
IDX --> APP --> RT --> PG
PG --> CM
PG --> HK
PG --> RTKQ
CM --> RTKQ
RTKQ --> REST --> API
RTKQ --> STORE
APP --> COOKIE
REST --> COOKIE
Application lifecycle (from load to rendered page)¶
sequenceDiagram
participant B as Browser
participant IDX as index.tsx
participant PG as PersistGate
participant APP as App.tsx
participant CK as Shared cookie
participant API as Backend
B->>IDX: Load app
IDX->>IDX: Wrap in Provider(store) + HashRouter
IDX->>PG: Rehydrate persisted state (auth)
PG->>APP: Render App
APP->>CK: getTokenFromSharedStorage()
alt Cookie present
APP->>API: verifyUser(application, companyId)
API-->>APP: user + token + claims (role, permissions)
APP->>APP: Decode JWT → set auth slice
APP->>B: Render Layout + Routes
else No cookie
APP->>B: Redirect to SSO login
end
Component architecture¶
- Presentational + container blended. Pages are containers (fetch data via RTK Query hooks, hold local state). Components in
components/are mostly presentational but several read Redux directly (Layout,AISuggestionBot,customTabs). - Colocation. A page folder (e.g.
pages/client/Offers/) contains the page plus its private subcomponents (AddOffer/,OfferModeller/, etc.). - Two UI eras coexist. Older Ant Design–wrapped components (
Button,Modal,Tabs) and newer Tailwind-basedcustom*components (customButton,customModel,customTabs) both exist. See §7 and §21.
Design patterns used¶
| Pattern | Where |
|---|---|
| Provider pattern | Redux Provider, PersistGate in index.tsx |
| Config-driven routing | routeConfig.ts arrays consumed by a generic RouteWithSubRoutes |
| Higher-order guard | RouteWithSubRoutes wraps each route with auth + permission checks |
| Generated-hooks / service layer | RTK Query createApi → useXQuery/useXMutation |
| Typed selector/dispatch hooks | useTypedSelector, useTypedDispatch |
| Slice pattern | One Redux slice per domain in store/ |
| Path aliasing | @components, @services… via CRACO + tsconfig paths |
4. Routing¶
Library & mode¶
- React Router v5 with
HashRouter(index.tsx). URLs are hash-based:https://app/#/client/offers. This is deliberate — hash routing avoids server-side route configuration on static hosting (S3/Firebase).
Route configuration structure¶
Routing is config-driven. Four files in src/router/:
| File | Role |
|---|---|
paths.ts |
The URL strings (nested objects, e.g. paths.client.offers.listing). Params like :company_id. |
permissions.ts |
Permission constant strings (e.g. VIEW_OFFER). |
routeConfig.ts |
Arrays of IRoute objects mapping a path → a component, with flags. Two arrays: admin_routeConfig and client_routeConfig. |
Routes.tsx + RouteWithSubRoutes.tsx |
The renderer and the per-route guard. |
An IRoute (router/types.ts):
ts
export interface IRoute {
path: string;
component: any;
isPrivate: boolean; // requires auth
key: string;
routes?: IRoute[]; // nested routes
exact?: boolean;
breadcrumb?: string;
permission?: string; // required permission
isCompany?: boolean;
}
Route guard logic (RouteWithSubRoutes.tsx)¶
flowchart TB
R([Route requested]) --> AUTH{isPrivate &&<br/>not authenticated?}
AUTH -->|Yes| SSO[Redirect to SSO login]
AUTH -->|No| PERM{Has required<br/>permission?}
PERM -->|No| BACK[Redirect to prev path<br/>or default]
PERM -->|Yes| ROLE{admin path vs<br/>client path matches role?}
ROLE -->|Mismatch| BACK
ROLE -->|OK| RENDER[Render the page component]
The guard:
1. Reads state.auth.user; if the route isPrivate and there's no user → window.open(ssoUrl, "_self").
2. If authenticated but the route's permission fails checkPermission(...), or the admin/client path doesn't match the user's role (isAdmin), it Redirects to the previous path (stored in localStorage.prevPath) or a default.
3. Otherwise it renders route.component, passing nested routes.
Public vs protected routes¶
| Type | isPrivate |
Examples |
|---|---|---|
| Public | false |
ResetPassword (/client/reset-password). In sso-cicd: /login, /forgot-password, /reset-password. |
| Protected | true |
Everything else — all data screens, offers, evaluations, dashboards. |
⚠️ Login routes are commented out in the non-SSO apps (
routeConfig.ts). Onlysso-cicdrenders a real login page. The others send unauthenticated users tossoUrl.
Fallback route (Routes.tsx)¶
The last Route (no path) is the catch-all. In tom-cicd it decides the landing page by role/permission:
tsx
{user?.role === "TOM_MASTER_USER"
? <Route component={Dashboard} />
: hasTOMDashboardPermission
? <Route component={Dashboard} />
: <Route component={GradeSetup} />}
Lazy-loaded routes¶
- None found. Route components are imported eagerly at the top of
routeConfig.ts(staticimport). There is noReact.lazy/Suspensecode-splitting per route. See §16.
5. Module Documentation¶
Each app is a "module." Below is what each contains. (Business-level detail: Product Handbook §3; backend endpoints: Backend Documentation §7.)
5.1 tom-cicd — TOM (Total Offer Management)¶
| Aspect | Detail |
|---|---|
| Purpose | Maintain company pay data; build/send candidate offers; view analytics. |
| Pages (client) | Dashboard, GradeSetup, SalaryRange, CashAllowances, BenefitsPlan, ShortTermIP, LongTermIP, InternalPayrollData, MarketData, Offers (+ AddOffer wizard), EmailTemplate, SubAdmins, CompanyProfile, CreateCompany, Auth/ResetPassword. |
| Pages (admin) | Companies, GradeMapTable, JobFunction, Sectors, User/SubAdmins. |
| Key services | offers, gradeSetup, salaryRange, cashAllowances, benefitsPlan, shortTermIP, longTermIP, internalPayrollData, marketData, dashboard, aiSuggestion, companies, companySubAdmins. |
| State | auth, offers, companies, businessUnit, sectors, grade, countries, subAdmin, email-template, dashboard. |
| Feature highlights | 3-step Offer Builder wizard; AISuggestionBot; CSV upload/download per dataset; email/PDF preview; offer revisions. |
5.2 taref-cicd — JE (Job Evaluation)¶
| Aspect | Detail |
|---|---|
| Purpose | Create & evaluate jobs (factor questionnaire → grade); job libraries. |
| Pages | Dashboard, JobEvaluation (list + create), CreateJobEvaluation (questionnaire), JobGradeDefination, JobLibrary, PayNet, PayNetJobFunctions (custom library), SubAdmins, Company profile. |
| Distinctive | React 18; deployed on Firebase; extra deps xlsx (mass upload), react-slick. Persists auth and paynet. |
| Key state | auth, jobEvaluation (full working evaluation), paynet, plus shared slices. |
5.3 live-bm-cicd — LBT (Live Pay Benchmarking)¶
| Aspect | Detail |
|---|---|
| Purpose | Generate market-pay benchmarking reports. |
| Pages | DynamicReport (list + 5-step report wizard), BenchmarkingByTitle (instant view). |
| Distinctive | Trimmed fork of TOM; most legacy routes commented out; custom SVG percentile-bar charts (no chart lib). |
| Key service | livePayBenchmarking. |
5.4 dashboard-cicd — Dashboard (ADMIN_PANEL)¶
| Aspect | Detail |
|---|---|
| Purpose | Compensation analytics (compa-ratio, pay gap, hiring trends). |
| Pages | Dashboard (charts + cascading filters), plus the benchmarking pages (bundled). |
| Distinctive | Adds recharts + react-select; dashboard service (dashboard-ci endpoints). |
5.5 sso-cicd — SSO / Admin Portal¶
| Aspect | Detail |
|---|---|
| Purpose | The entry point: login, app picker, company picker, and company/user administration. |
| Pages | Auth (Login/Forgot/Reset), Apps (launcher), CompanyList, CompanyProfile, CompanyUsers, CreateCompany, admin taxonomy pages. |
| Distinctive | Only app with a real login (Formik + Yup). Adds formik, yup, react-slick. Persists auth and selected_app. Writes the shared cookies the other apps consume. |
Inter-module relationships¶
flowchart LR
SSO[sso-cicd<br/>login + admin] -->|writes shared cookie<br/>+ launches| TOM[tom-cicd]
SSO --> JE[taref-cicd]
SSO --> LBT[live-bm-cicd]
SSO --> DASH[dashboard-cicd]
TOM -. same backend<br/>+ company context .- JE
TOM -. .- LBT
TOM -. .- DASH
They do not import each other's code — they are separate builds. They share data (same backend) and session (shared cookie), not modules.
6. Pages¶
Pages live in src/pages/{admin,client}/. A page folder colocates its subcomponents. Below is the anatomy every page follows, then the headline pages.
The standard page pattern¶
Almost every list page follows this shape:
flowchart TB
A[Page mounts] --> B[Read company_id from cookie / URL]
B --> C[useXQuery hook fetches list<br/>page, page_size, sort_by, order, search]
C --> D[Render Table component]
D --> E[Toolbar: SearchBox + UploadButton + Download + Versions]
E -->|search change| F[useDebounce 500ms → refetch]
E -->|Upload click| G[Upload modal → onSubmit mutation → refetch]
D -->|Create click| H[Modal form → create mutation → refetch]
Headline pages (from tom-cicd)¶
| Page | Route (hash) | Components | APIs | State | Business logic |
|---|---|---|---|---|---|
| Offers list | /client/offers |
customTabs, Table, SearchBox, Button |
offersApi (fetchOffers, updateStatus, deleteDraft, revise) |
offers, auth |
Three tabs (Active / Past / Drafts); revise only when Placed; delete only Draft |
| Add Offer | /client/offers/offer/:offer_id |
stepper, offer subforms, AISuggestionBot, EmailModal |
offersApi (create/update details, candidate, modeller, auto-populate, comparator, currency, email) |
offers (working offer + stage) |
3-step wizard; auto-populate; AI insights; email/PDF |
| Grade Setup | /client/grade-setup |
Table, UploadButton, Upload, Modal |
gradeSetupApi |
grade |
CRUD + CSV + versions; permission VIEW_JOB_GRADE |
| Salary Range | /client/salary-range |
Table (+ column picker), Upload |
salaryRangeApi |
— | CRUD + CSV + versions; show/hide columns |
| Dashboard | /client or /client/dashboard |
Recharts charts, MultiFilter, date range |
dashboardApi |
dashboard |
Analytics; permission VIEW_TOM_DASHBOARD |
| Company Profile | /client/companies/:company_id |
Tabs, CollapsibleCard, nested BU/Region/Country/Legal-Entity pages |
companiesApi, businessUnitApi |
companies |
Company + org structure management |
| Sub Admins | /client/sub-admins |
Table, forms |
companySubAdmins |
subAdmin |
Company user CRUD; gated by VIEW/CREATE/UPDATE_COMPANY_USER |
For JE's evaluation questionnaire, LBT's report wizard, and SSO's user-provisioning tabs, see the Product Handbook §4–§5 for the user-facing flow.
7. Components¶
Location: src/components/. Full prop-level catalog below. Legend: Generic = reused across pages; Specific = tied to one feature.
7.1 Core / featured¶
Layout/ — the app shell (Generic, singleton)¶
Wraps every authenticated page.
- Props: children only — everything else from Redux/services.
- Renders: collapsible left Sider (nav), top Header (notifications bell, Apps launcher, profile dropdown), breadcrumb bar, scrollable content.
- Key behaviors:
- Sidebar is data-driven from sidebar-config (client_config(jobGrades, company_id)); each item gated by checkPermission.
- Company switcher (only TOM_MASTER_USER/TOM_SUPER_USER): lists all companies, writes selected_company cookie, re-runs verifyUser to re-scope the session.
- Profile dropdown: "Apps" → window.open(appsPageUrl); "Log out" → logout mutation → redirect to ssoUrl.
- Breadcrumbs via useBreadcrumbs.
- ⚠️ The admin-vs-client sidebar branch is currently collapsed — both branches use client_config.
Table/ — generic data table (Generic)¶
Wraps Ant Design Table with a split layout (main columns scrollable; last column rendered as a pinned table for a fixed actions column).
- Key props: data, columns ({title, dataIndex, key, width?}), onRowClick?, pagination?, page?, count?, onChangePage?, onPageSizeChange?, isLoading? (renders Skeleton), scrollY?, onTableChange?(pagination, filters, sorter) (how sorting reaches the parent).
- Notes: rowKey="id"; page sizes 10/20/50/100; no built-in search — the parent owns search/sort/filter.
stepper/ — multi-step wizard header (Generic; used by Offers)¶
Wraps MUI Stepper. Props: steps ({title, icon}[]), currentStep, onStepClick(index). Steps ≤ current are "completed."
Upload/ + UploadButton/ — CSV import (Generic)¶
UploadButtonis the permission gate: renders only ifrole !== "COMPANY_ADMIN" && (checkPermission(create) || checkPermission(update)), elsenull. Props:role,createPermission,updatePermission,onClick,isLoading.Uploadis the modal (built oncustomModel): validates CSV viavalidateFile, has an "Active" checkbox and a per-type "Download Sample File" link (SAMPLE_FILES_MAP). Props:isVisible,setIsVisible,onSubmit,title,file,setFile.
searchInput/ (SearchBox) — (Generic)¶
Controlled search box. Props: placeholder?, value, onChange(value: string) (passes the string, not the event). No debounce built in — pair with useDebounce.
7.2 Modals¶
| Component | Base | Notes |
|---|---|---|
Modal/ |
Ant Modal |
centered, destroyOnClose, mode: "versions" | "standard", width default 1092. Props: title, isVisible, footer, setIsVisible. |
customModel/ (CustomModal) |
Tailwind divs | width is a Tailwind class string; backdrop-click closes. Used by Upload. |
⚠️ There are effectively four modal implementations (
Modal,customModel, plus inline overlays inLayoutandEmailModal). PreferModalfor new work unless you need the Tailwind variant. See §21.
7.3 Form primitives¶
| Component | Base | Key props |
|---|---|---|
customButton/ (CustomButton) |
Tailwind <button> |
btnName, onClick, children (icon), iconPosition, isLoading, disabledProp. The de-facto standard button. |
Button/ |
Ant Button |
variant: download\|add\|upload\|versions\|… (icon chosen from an SVG map), isLoading. Toolbar-action button. |
customInput/ (CustomFormInput) |
HTML input/textarea | title, value, onChange(event), type: text\|textarea, disabled. Default lg:w-[48%] (2-per-row). |
customSelect/ (CustomSelect) |
Ant Select |
title, options: {label,value}[], + all Ant Select props. |
FormError/ |
— | show: boolean, message. Renders red text only when show. |
CustomErrorMessage/ (customErrorMessage) |
Ant message |
A function, not a component. Opens a persistent error toast. Used app-wide. |
7.4 Tabs¶
| Component | Notes |
|---|---|
Tabs/ |
Generic, permission-filtered; route mode (NavLink) or controlled mode. |
customTabs/ |
Offers-specific (hardcoded Active/Past/Draft; hides Draft for some roles). |
7.5 Feature-specific (not for reuse)¶
| Component | Purpose |
|---|---|
AISuggestionBot/ |
Floating AI-insights bot for the open offer. Self-driven from Redux + ~8 RTK Query mutations, debounced 700ms. No props. |
EmailModal/ |
Offer email preview + send. Renders HTML via dangerouslySetInnerHTML. |
CollapsibleCard/ |
Expandable card with Update/Save buttons. |
AuthLandingImg/ |
Marketing panel on auth pages. |
Type-safety status¶
- Typed (
.tsx+ interfaces): Layout, Table, Modal, customModel, searchInput, Button, customButton, Tabs, FormError, AuthLandingImg, Upload, stepper. - Untyped (
.js/.jsx): AISuggestionBot, CollapsibleCard, CustomErrorMessage, EmailModal, customInput, customSelect, customTabs, UploadButton.
8. State Management¶
Approach¶
Redux Toolkit with two kinds of state:
1. Server state → RTK Query API slices (one per backend domain). These cache responses and generate hooks.
2. Client/UI state → hand-written slices (auth, offers, companies, …).
Only auth is persisted (via redux-persist) — see the whitelist. In taref-cicd, paynet is also persisted; in sso-cicd, selected_app is also persisted.
Store structure (store/index.ts)¶
flowchart TB
subgraph "Redux Store"
subgraph "RTK Query API reducers (server state, cached)"
A1[authApi]:::api
A2[offersApi]:::api
A3[companiesApi]:::api
A4[dashboardApi]:::api
A5[...15+ more...]:::api
end
subgraph "Hand-written slices (UI state)"
S1[auth ✱ persisted]:::slice
S2[offers]:::slice
S3[companies]:::slice
S4[businessUnit]:::slice
S5[sectors / grade / countries / subAdmin / dashboard]:::slice
end
end
classDef api fill:#e3f2fd,stroke:#1565c0;
classDef slice fill:#f3e5f5,stroke:#6a1b9a;
Data flow¶
sequenceDiagram
participant C as Component
participant H as RTK Query hook
participant RS as restService
participant API as Backend
participant ST as Redux store
participant SL as Slice (extraReducers)
C->>H: useFetchOffersQuery(args)
H->>ST: check cache
alt cache miss
H->>RS: fetch
RS->>API: HTTPS + Bearer token
API-->>RS: JSON
RS-->>H: normalized data
H->>ST: cache result
H->>SL: matchFulfilled → update slice (e.g. auth)
end
ST-->>C: data + isLoading + error (re-render)
Global vs local state¶
| State kind | Where | Example |
|---|---|---|
| Global — server | RTK Query cache | Offers list, companies, dashboard stats |
| Global — UI | Slices | auth (user/token/permissions), offers (working offer + wizard stage) |
| Local | useState in a page/component |
Modal open/close, form field values, search text |
The auth slice (the most important slice)¶
- State:
{ user, token: { access, refresh }, permissions[], applications[], is_one_time_password }. - How it fills:
authApi.endpoints.verify.matchFulfilleddecodes the JWTclaims(viajwt-decode) to extract role and permissions, merges role intouser, and storesapplicationsfromuser.company.applications. Ifpermissionsis empty it defaults to["all"]. - Logout reset: the root reducer wipes the entire store and clears
localStoragewhen thelogoutmutation fulfills. window.store = storeis exposed so non-React code (restService,checkPermission) can read/dispatch.
Best practices (this codebase)¶
- Use RTK Query hooks for all server data — don't hand-roll fetches in components.
- Use
useTypedSelector/useTypedDispatch(never the untypeduseSelector/useDispatch). - Keep ephemeral UI state local (
useState); only lift to a slice if multiple screens need it.
9. API Layer¶
Architecture¶
flowchart TB
C[Component] -->|useXQuery / useXMutation| SVC[Service: createApi]
SVC -->|baseQuery| REST[tomService<br/>restService.ts]
REST -->|Bearer token| API[(Backend REST API)]
REST -->|401 / invalid token| REF[Refresh token → retry once]
REF -->|refresh fails| SSO[Clear storage → redirect to SSO]
REST -->|text/csv| DL[Auto-download file]
Service layer (src/services/)¶
- One file per backend domain, each an RTK Query
createApi:
ts
export const offersApi = createApi({
reducerPath: "offersApi",
baseQuery: tomService({ baseUrl: `${process.env.REACT_APP_BASE_URL}/company` }),
tagTypes: ["Offers"],
endpoints: builder => ({
fetchOffers: builder.query({ query: ({ company_id, ... }) => ({ url: `/${company_id}/offers/list/?...`, method: "GET" }), providesTags: ["Offers"] }),
createOfferDetails: builder.mutation({ query: ({ company_id, body }) => ({ url: `/${company_id}/offer/`, method: "POST", body }), invalidatesTags: ["Offers"] }),
// ...
}),
});
export const { useFetchOffersQuery, useCreateOfferDetailsMutation, ... } = offersApi;
tagTypes+providesTags/invalidatesTagsgive automatic cache invalidation (a mutation refetches affected queries).- Company-scoped services use
baseUrl = REACT_APP_BASE_URL + "/company"; auth uses the bare base URL.
The REST wrapper (services/restService.ts → tomService)¶
The single fetch wrapper all services use. It:
- Sets
Accept/Content-Type: application/json(unlessformData). - Reads the access token from
localStorage.tokens(loadToken) and attachesAuthorization: Bearer <token>on all non-login, non-third-party calls. - Handles special responses:
text/csv→ downloads a blob automatically (this is how "Download CSV" works).- 204 No Content → returns a success shape.
- Non-JSON → returns a structured error.
- Token refresh: on
code 5000or1002 "Invalid Token", it calls/auth/refresh-token/with the stored refresh token, re-saves tokens, and retries the original request once. If refresh fails → dispatchlogout, clearlocalStorage, redirect tossoUrl. - Normalizes success to
{ data: { data, pagination, message } }so components get a consistent shape.
Request lifecycle¶
sequenceDiagram
participant C as Component
participant HK as useXQuery
participant TS as tomService
participant LS as localStorage
participant API as Backend
C->>HK: call hook
HK->>TS: baseQuery(url, method, body)
TS->>LS: loadToken()
TS->>API: fetch(url, {Authorization: Bearer})
API-->>TS: response
alt token expired (5000/1002)
TS->>API: POST /auth/refresh-token/
API-->>TS: new access token
TS->>LS: saveTokens()
TS->>API: retry original request
end
TS-->>HK: { data } or { error }
HK-->>C: data / isLoading / error
Authentication in the API layer¶
- Tokens live in
localStorage.tokens(storage.ts), and also in the shared cookieuser_datafor cross-app SSO (cookies.ts). saveTokens,loadToken,loadRefreshTokeninstorage.ts.
Response handling & errors¶
See §17. In short: tomService returns either { data } or { error: { success, message, code, error, ... } }; components read error from the hook.
10. Forms & Validation¶
Form handling approaches (there are three)¶
| Approach | Where | Notes |
|---|---|---|
Ant Design Form |
Most create/edit modals (compensation data, company, users) | Uses Ant's Form/Form.Item rules for validation + useForm. |
Controlled useState + useFormValidation |
Simpler custom forms | useFormValidation(values, validate) returns errors, isValid, touch, shouldShowError. Pair with FormError. |
| Formik + Yup | sso-cicd login only |
The only app using Formik/Yup. |
useFormValidation hook¶
ts
const { isValid, errors, touch, shouldShowError } = useFormValidation(values, validate);
// validate: (values) => Record<string, boolean> // true = has error
// shouldShowError(field): show only after the field was touched
Render an error with the FormError component:
tsx
<FormError show={shouldShowError("email")} message="Email is required" />
Validation rules (observed)¶
| Rule | Where |
|---|---|
| Required fields | Ant Form.Item rules={[{ required: true }]} and validate functions |
| File type = CSV | validateFile(file.type) before upload (Upload component) |
| Login email/password | Yup schema (sso-cicd) |
| Business rules (e.g. grade required in user scope) | Enforced by the backend; the UI surfaces the returned validation error |
Most business validation is server-side (see Backend Documentation §10). The frontend does field-level checks and then displays the backend's
is_validation_errormessages.
User feedback¶
- Success: Ant
message.success(...). - Errors:
customErrorMessage(...)(persistent toast) or inlineFormError. - Loading: button
isLoadingspinners;TableSkeleton.
11. Authentication & Authorization¶
The SSO model (how one login serves five apps)¶
flowchart TB
subgraph "sso-cicd"
LOGIN[Login form Formik+Yup] --> POST[POST /v2/auth/login/]
POST --> COOKIE[Write tokens to shared cookie<br/>user_data on cookiesDomain]
COOKIE --> PICK[App picker + Company picker]
PICK --> SETC[Write selected_application<br/>+ selected_company cookies]
end
SETC -->|window.open app URL| APP[TOM / JE / LBT / Dashboard]
subgraph "Any other app (App.tsx)"
APP --> READ[Read shared cookie]
READ --> VER[verifyUser application, companyId]
VER --> DEC[Decode JWT → role + permissions]
DEC --> RENDER[Render app, scoped to this user + app]
end
Login flow (per app, App.tsx::loginUserFromCookies)¶
getTokenFromSharedStorage()reads theuser_datacookie.- If present:
saveTokens(...), readselected_companycookie, callverifyUser({ selected_application, companyId }). - If absent:
window.open(ssoUrl, "_self")→ user logs in on SSO. - On verify rejection: redirect to
ssoUrl.
Token management¶
| Token | Stored where | Lifetime |
|---|---|---|
| Access | localStorage.tokens + shared cookie |
1 day (backend) |
| Refresh | localStorage.tokens + shared cookie |
5 days (backend) |
- Refresh is automatic in
tomService(see §9). - Logout blacklists the refresh token (backend) and the root reducer wipes local state.
Session handling¶
redux-persistkeepsauthacross reloads.PersistGateshows a loading state until rehydration completes.
Protected pages & permission-based rendering¶
Two levels:
- Route level —
RouteWithSubRoutesblocks unauthenticated/unauthorized access (see §4). - UI element level —
checkPermission(permission):
ts
export const checkPermission = (permission?: string | string[]) => {
const permissions = store?.getState()?.auth?.permissions;
if (permissions?.includes("all")) return true; // fallback super-permission
if (!permission) return true;
if (Array.isArray(permission)) return permission.some(p => permissions?.includes(p));
return permissions?.includes(permission);
};
Used to gate buttons (UploadButton), tabs (Tabs), and menu items (Layout). Roles come from constants/roles.ts (admin, company, and special groups like rolesThatCantAccessAiInsights). isAdmin(role) distinguishes internal vs company users.
12. Shared Utilities¶
Custom hooks (src/hooks/)¶
| Hook | Purpose | When to use |
|---|---|---|
useTypedSelector |
Typed useSelector bound to RootState |
Always instead of raw useSelector |
useTypedDispatch |
Typed useDispatch |
Always instead of raw useDispatch |
useDebounce(value, delay=500) |
Debounced value | Search inputs before triggering a query |
useFormValidation(values, validate) |
Lightweight validation state | Custom (non-Ant) forms |
useBreadcrumbs(...) |
Route-driven breadcrumbs | In Layout |
Utility functions (src/utils/)¶
| Group | Files | Purpose |
|---|---|---|
| Compensation math | calculateFixedTotal, calculateStiTotal, getTotalLti, getTotalForBeneFit, calculateDifference, percentageDifference, calculatePercentageAmount, calculatePropose, getGradeRows |
Offer/pay calculations mirrored from backend logic |
| Formatting | formatNumberWithCommas, formatFirstLetterUpperRestLower, round, getCurrencyTotal, currenciesConverter |
Display helpers |
| Dates | disabledDates, generateYears, getDaysOfMonth |
Date-picker helpers |
| Auth/permission | checkPermission, isAdmin |
Access checks (see §11) |
| Files | validateFile, sanitizeFileName |
Upload validation |
| Feedback | showSuccessPopup |
Success UI |
| Misc | sortingFunctions, getDifference, constant |
Sorting + shared constants |
When to use utils: import pure helpers from
@utils. Don't duplicate compensation math in components — reuse theutilsversions (they mirror the backend formula).
Constants (src/constants/)¶
| File | Contents |
|---|---|
roles.ts |
Role groupings (admin, company, rolesThatCantAccessAiInsights, …) |
index.ts |
ssoUrl, appsPageUrl, cookiesDomain, livePayTool, dateFormat, theme vars |
admin.json / client.json |
Ant theme variable sets (runtime theming) |
world.json |
Static country data |
13. Styling System¶
Approach — three layers working together¶
flowchart LR
A[Ant Design 4<br/>component styles] --> D[Final UI]
B[Tailwind CSS<br/>utility classes] --> D
C[LESS theme.less<br/>design tokens] --> A
C --> E[Runtime theming<br/>window.less.modifyVars]
E --> D
- Ant Design 4 — the base component look, themed via LESS variables (the
antd-theme-webpack-pluginincraco.config.js). - Tailwind — utility classes (
bg-themeBlue,text-white,lg:w-[48%]) for layout and custom components. - LESS — design tokens in
src/styles/theme.less.
Design tokens (styles/theme.less)¶
| Token | Value |
|---|---|
@primary-color |
#2f49d1 (brand blue) |
@primary-black |
#0b2540 |
@error-color |
#fa1818 |
@green-light / @blue-light |
#23dbaf / #23a7d0 |
| Fonts | Montserrat (primary), HK Grotesk (secondary) |
| Font sizes | @text-xxl 40px … @text-xs 12px |
Runtime theming (admin vs client)¶
App.tsx calls window.less.modifyVars(adminVars | clientVars) at runtime to switch the theme depending on whether the user is on an admin or client path (adminVars/clientVars come from constants/admin.json/client.json). This lets the same build render two visual themes.
Style files (src/styles/)¶
| File | Purpose |
|---|---|
theme.less |
Design tokens (colors, fonts, sizes, borders, transitions) |
global.less |
Global element styles |
fonts.less |
Font-face declarations |
utils.less |
Utility classes |
index.less |
Aggregator |
Responsive design¶
- Tailwind responsive prefixes (
lg:, etc.) — e.g. form fields default tolg:w-[48%](two per row on large screens). - Ant Design's
Row/Colgrid for page layout.
UI consistency¶
- Reuse the shared components (
Table,Modal,customButton, form primitives) rather than restyling per page. - Use theme tokens/Tailwind theme classes, not hardcoded colors.
14. Assets¶
| Asset type | Location | Notes |
|---|---|---|
| Images / SVGs | src/assets/ |
Illustrations (auth landing), logos, icons; button icons defined in components/Button/config.tsx |
| Icons | @ant-design/icons + react-icons |
No custom icon font |
| Fonts | Declared in styles/fonts.less |
Montserrat, HK Grotesk |
| Static data | src/constants/world.json, JE job-library JSON |
Bundled reference data |
| Sample CSVs | public/samples/*.csv |
Downloadable upload templates (mapped in Upload's SAMPLE_FILES_MAP) |
| Runtime theme | public/color.less |
Used by the Ant theme plugin for live theming |
Exact asset inventory: the specific image filenames are in
src/assets/— browse that folder for the current set. (Full listing: Unable to determine from the available code without enumerating the folder.)
15. Environment Configuration¶
Environment variables (.env.example)¶
All apps read these (values injected per environment, not committed):
| Variable | Purpose |
|---|---|
REACT_APP_BASE_URL |
Backend API base URL (all service baseUrls build on this) |
REACT_APP_SSO_URL |
The SSO login app URL (redirect target when unauthenticated) |
REACT_APP_APPS_PAGE_URL |
The app-launcher page URL ("Apps" menu) |
REACT_APP_COOKIES_DOMAIN |
Shared cookie domain (enables cross-app SSO) |
REACT_APP_APPLICATION |
Which app this build is (TOM/JE/LBT/SSO/ADMIN_PANEL) — set by npm scripts |
sso-cicdand the app launcher also reference per-app target URLs (tomAppUrl,jeAppUrl,rbAppUrl,ciAppUrl) and aREACT_APP_LIST_LIVE_PAY_BENCHMARKINGflag. Exact values are environment-injected — Unable to determine from the available code.
Build configuration¶
| File | Role |
|---|---|
craco.config.js |
Path aliases (@components…), LESS loader, Ant theme plugin, Tailwind/PostCSS |
tsconfig.json + tsconfig.paths.json |
TS compiler + path mapping (mirrors the CRACO aliases) |
tailwind.config.js |
Tailwind theme (custom colors like themeBlue) |
package.json scripts |
start, start:tom, build:tom, build, test (via craco) |
npm scripts (from tom-cicd)¶
jsonc
"start": "craco start",
"start:tom": "set PORT=3002 && cross-env REACT_APP_APPLICATION=TOM craco start",
"build:tom": "cross-env REACT_APP_APPLICATION=TOM craco build",
"build": "craco build",
"test": "craco test"
Deployment¶
| App | CI/Deploy |
|---|---|
Most (tom, live-bm, dashboard) |
AWS CodeBuild (buildspec.yml); env pulled from Secrets Manager (prod/tta/*); artifacts = build/ |
taref (JE), sso |
Firebase Hosting (firebase.json, .firebaserc) — SPA rewrite all routes to /index.html |
Build note:
buildspec.ymlsetsNODE_OPTIONS=--openssl-legacy-provider(needed for Node 17+ with react-scripts 4).
16. Performance Optimizations¶
What exists¶
| Technique | Status |
|---|---|
| RTK Query caching | ✅ Server responses cached; tag-based invalidation avoids redundant refetches |
| Debounced search | ✅ useDebounce (500ms) before firing list queries |
| Server-side pagination | ✅ Table + backend page/page_size (default 10) |
Skeleton loading |
✅ Table renders skeletons while loading |
| Debounced AI calls | ✅ AISuggestionBot debounces 700ms before posting analysis |
What is NOT present (opportunities)¶
| Technique | Status |
|---|---|
Route-level code splitting (React.lazy/Suspense) |
❌ Not used — all route components imported eagerly |
React.memo / useMemo / useCallback |
⚠️ Sparse — not a systematic pattern |
| Bundle analysis / manual chunking | ❌ Not configured (CRA defaults only) |
For new work: consider
React.lazyfor heavy pages (Offer wizard, Dashboard charts) and memoizing expensive derived values. See §22.
17. Error Handling¶
Layers¶
flowchart TB
A[API error] --> B[tomService returns error object]
B --> C[RTK Query hook exposes error]
C --> D{Handle in component}
D -->|show toast| E[customErrorMessage / message.error]
D -->|inline| F[FormError]
B -->|token expired| G[Auto-refresh or redirect to SSO]
H[Render error] --> I[react-error-boundary in JE app]
API errors¶
tomServicereturns{ error: { success, message, code, error, is_validation_error, data } }.- Components read the hook's
errorand showcustomErrorMessage(error.message)or maperror.errorto field-levelFormErrors. - Token/auth errors are handled centrally (refresh → retry → redirect to SSO).
UI / render errors¶
taref-cicdusesreact-error-boundary. Other apps: Unable to determine from the available code whether a global error boundary is mounted (none found in the files reviewed).
Fallback UI¶
Tableshows AntEmptywhen there's no data.PersistGateshows"loading..."during rehydration.
Logging¶
- Client-side logging is via
console.log/console.error(e.g., JWT decode errors in the auth slice). No dedicated error-tracking SDK found — Unable to determine from the available code whether Sentry/etc. is wired.
18. Development Workflow¶
Setting up the project (per app)¶
```bash cd "Frontend Prod/tom-cicd" # pick the app you're working on cp .env.example .env # fill in REACT_APP_* values (ask for the dev values) npm install # installs deps + husky hooks (postinstall) npm start # runs on the CRACO dev server
or an app-specific script:¶
npm run start:tom # sets REACT_APP_APPLICATION=TOM, PORT=3002 ```
⚠️ You typically run one app at a time. To exercise the full SSO flow locally you'd need SSO + the target app running with a shared cookie domain — check with the team for the local SSO setup. (Local multi-app cookie sharing config: Unable to determine from the available code.)
Running the application¶
npm start→ dev server with hot reload.npm run build→ production bundle inbuild/.npm test→ CRA/CRACO test runner.
Add a new page¶
- Create a folder under
src/pages/client/MyPage/withindex.tsx(colocate subcomponents here). - Add the URL to
src/router/paths.ts. - If it needs a permission, add it to
src/router/permissions.ts. - Register it in
src/router/routeConfig.tsas anIRoute(setisPrivate,permission,key). - Add a sidebar entry in
components/Layout'ssidebar-configif it should appear in the nav (gate withpermission).
Add a new component¶
- Create
src/components/MyComponent/index.tsx. - Define a props
interface; prefer.tsx(typed) over.js. - Reuse existing primitives (
customButton,Table,Modal) rather than re-implementing. - Import via the alias:
import MyComponent from "@components/MyComponent".
Add a new route¶
See "Add a new page" (routing is config-driven — steps 2–4).
Connect to an API¶
- Create
src/services/myThing.tswithcreateApi(copy an existing service likeoffers.ts). - Set
baseUrl(add/companyif company-scoped) andtagTypes. - Register the API's
reducerandmiddlewareinstore/index.ts. - Export it from the services barrel (
@services). - Use the generated hook in your page:
const { data, isLoading } = useMyThingQuery(args).
Create a reusable component¶
- Put it in
src/components/, type its props, keep it presentational (take data via props), and avoid reading Redux inside it unless it's genuinely global (likeLayout).
19. Coding Standards¶
Naming conventions (as observed)¶
| Thing | Convention | Example |
|---|---|---|
| Component folders | PascalCase (or custom* for the Tailwind variants) |
Table/, customButton/ |
| Page folders | PascalCase | Offers/, GradeSetup/ |
| Component files | index.tsx (main), colocated subfiles PascalCase |
AddOffer/index.tsx |
| Service files | camelCase, one per domain | offers.ts, cashAllowances.ts |
| Slice folders | kebab-case | business-unit/, email-template/ |
| Hooks | useXxx camelCase |
useDebounce.ts, useTypedSelector.ts |
| Utils | camelCase, verb-first | calculateStiTotal.ts, formatNumberWithCommas.ts |
| Interfaces/types | I-prefixed |
IRoute, IAuthState, ITable |
| RTK Query hooks | use<Name>Query / use<Name>Mutation |
useFetchOffersQuery |
Import organization¶
- Use path aliases, never deep relative paths:
import { useTypedSelector } from "@hooks"— not../../hooks. - Aliases:
@(src),@pages,@assets,@components,@store,@types,@utils,@router,@hooks,@services,@styles,@constants(defined in bothcraco.config.jsandtsconfig.paths.json). - Barrel exports: folders expose an
index.ts(e.g.@services,@hooks,@store).
Code formatting (.prettierrc)¶
jsonc
{ "arrowParens": "avoid", "bracketSpacing": true, "semi": true,
"singleQuote": false, "tabWidth": 2, "trailingComma": "es5", "useTabs": false }
- Double quotes, semicolons, 2-space indent,
es5trailing commas,x => x(no parens on single arrow arg). - husky + lint-staged run on commit (
postinstallinstalls husky). Keep commits passing the hooks.
Best-practice conventions¶
- Prefer
.tsx+ typed props for new components (some legacy files are untyped.js). - Prefer RTK Query hooks over manual fetches.
- Prefer the shared components over new one-off UI.
20. Debugging Guide¶
Tools¶
| Need | Tool |
|---|---|
| State inspection | Redux DevTools (enabled in non-production — store/index.ts). Inspect auth, RTK Query caches, slices. |
| Network / API | Browser DevTools → Network. Watch the Authorization: Bearer header, the ?statuses=&page= query strings, and the response envelope (success, code, data). |
| Component tree | React DevTools. |
| Global store from console | window.store.getState() (exposed intentionally). |
Common issues & where to look¶
| Symptom | Likely cause / where |
|---|---|
| Redirected to SSO unexpectedly | No/invalid token in the shared cookie; check cookies.ts + verifyUser result; token refresh failed in restService. |
| "You don't have permission" / hidden buttons | checkPermission — inspect state.auth.permissions. Empty perms default to ["all"]. |
| Data not refreshing after a create/edit | Missing invalidatesTags/providesTags on the RTK Query endpoint. |
| CSV "download" does nothing | The text/csv branch in restService handles this; check the response Content-Type. |
| Wrong company's data | selected_company cookie / company switcher in Layout. |
| Charts/diagrams blank | (Docs only) view the published docs site, not raw markdown. |
| Styling looks wrong for admin vs client | window.less.modifyVars(adminVars/clientVars) in App.tsx. |
API debugging tips¶
- The standard response envelope is
{ success, message, code, data, error, is_validation_error, pagination }. Asuccess: falsewith acodetells you which handler fired (see Backend Documentation §13). code 5000/1002= token issue → the app should auto-refresh; if it loops, the refresh token is bad.
21. Common Pitfalls¶
| Pitfall | Why it happens | Do this instead |
|---|---|---|
| Picking the wrong Button/Modal/Tabs | Two eras coexist: Ant-based (Button, Modal, Tabs) vs Tailwind custom*. Four modal implementations exist. |
For new work, standardize on one set (prefer the typed Ant Modal/generic Tabs and customButton). Don't invent a fifth modal. |
Editing untyped .js components |
Some components (customInput, customSelect, customTabs, UploadButton, AISuggestionBot, EmailModal) have no types. |
Add types when you touch them; don't propagate any. |
| Assuming the admin config renders | In tom-cicd, App.tsx and Layout currently force the client config for all roles. |
Don't rely on admin_routeConfig/admin_config being active — verify before building admin-only UI. |
| Forgetting cache invalidation | RTK Query won't refetch without invalidatesTags. |
Tag queries with providesTags and mutations with invalidatesTags. |
| Manual fetch in a component | Bypasses caching, token handling, refresh. | Always add an RTK Query endpoint and use its hook. |
| Deep relative imports | ../../../components/... breaks on moves. |
Use @components, @services, etc. |
| Search without debounce | Fires a query per keystroke. | Wrap the search value in useDebounce. |
| Trusting client validation only | Real rules are server-side. | Do field checks for UX, but handle is_validation_error responses. |
Reading raw useSelector/useDispatch |
Loses typing. | Use useTypedSelector/useTypedDispatch. |
dangerouslySetInnerHTML |
EmailModal renders server HTML. |
Only render trusted server HTML; never user input. |
22. Best Practices¶
Components¶
- Type props with an
I-prefixed interface; prefer.tsx. - Keep components presentational; take data via props. Reserve Redux reads for genuinely global components (
Layout). - Reuse
Table,Modal,customButton, and the form primitives.
State management¶
- RTK Query for server state; slices for UI state. Persist only what must survive reload (
auth). - Use
providesTags/invalidatesTagsfor correct cache behavior. - Use typed hooks (
useTypedSelector/useTypedDispatch).
API integration¶
- One
createApiservice per backend domain; company-scoped services use the/companybase. - Let
tomServicehandle tokens, refresh, CSV downloads — don't reimplement.
Performance¶
- Debounce search; paginate server-side.
- Consider
React.lazyfor heavy pages anduseMemo/React.memofor expensive renders (not yet standard here — an improvement area).
Maintainability¶
- Follow the aliases and folder conventions.
- Converge the duplicate component sets over time.
- Mirror compensation math from
@utilsinstead of re-deriving it.
Accessibility¶
- Current state: no systematic a11y patterns found (Unable to determine from the available code beyond Ant Design's built-in accessibility).
- For new work: rely on Ant components' semantics, add
aria-*/labels on custom Tailwind controls (customButton,customInput), and ensure keyboard navigation for modals/wizards.
Code reuse¶
- Put shared logic in
@utils/@hooks; shared UI in@components; shared constants in@constants.
23. Developer Onboarding Checklist¶
Work through this in order. Check each box.
Environment setup¶
- [ ] Clone the repo; open
Frontend Prod/. - [ ] Read this document's §1–§4.
- [ ] Pick an app (start with
tom-cicd). Copy.env.example→.envand get dev values from the team. - [ ]
npm install(installs husky hooks too). - [ ]
npm start(ornpm run start:tom) and load the app.
Understand the project structure¶
- [ ] Skim
src/and match each folder to §2. - [ ] Read
App.tsxandindex.tsx— understand the bootstrap + SSO login (§3, §11). - [ ] Read
store/index.tsand theauthslice (§8).
Run & explore¶
- [ ] Log in via the SSO flow (or the team's local shortcut) and reach a page.
- [ ] Open Redux DevTools; inspect
auth.permissionsand an RTK Query cache. - [ ] Trigger a list page; watch the Network tab for the request/response envelope (§9).
Learn routing & permissions¶
- [ ] Read
router/routeConfig.ts,paths.ts,permissions.ts,RouteWithSubRoutes.tsx(§4). - [ ] Find where a button is gated by
checkPermission(§11).
Learn shared components¶
- [ ] Read
components/Table,components/Modal,components/customButton,components/Upload+UploadButton,components/stepper(§7). - [ ] Note the duplicate component sets (§21).
Make your first change¶
- [ ] Add a harmless UI tweak to a client page (e.g., a column to a
Table, using an existing service field). - [ ] Use an alias import and a typed prop.
- [ ] Verify hot reload; check Prettier/lint pass on commit (husky).
Add something new (stretch)¶
- [ ] Add a new list page:
paths.ts→permissions.ts→routeConfig.ts→ sidebar entry → acreateApiservice (§18).
Submit a pull request¶
- [ ] Ensure
.prettierrcformatting and lint-staged hooks pass. - [ ] Reference the affected app + page in the PR description.
- [ ] Request review per team convention. (Exact PR/branch policy: Unable to determine from the available code — confirm with the team.)
Appendix — "Unable to determine from the available code"¶
- Exact deployed URLs, cookie domain, and per-app target URLs (env-injected).
- Whether a global React error boundary is mounted in apps other than
taref-cicd. - Whether a client-side error-tracking SDK (e.g., Sentry) is configured.
- The exact local multi-app SSO setup for running several apps together.
- The exact asset filenames in
src/assets/(browse the folder). - The team's branch/PR policy.
This document is derived from static analysis of the five apps under Frontend Prod/. Line references reflect the code at analysis time and may drift. For the product/business view see the Product Handbook; for backend/API detail see the Backend Documentation.