Files
baya-monorepo/client/CLAUDE.md
T
2026-07-30 02:26:52 +03:30

178 lines
11 KiB
Markdown

# Balinyaar Client
The web frontend of **Balinyaar**, a trust-first home-nursing marketplace in Iran. Families search for,
book, pay for and review home nursing; nurses list configurable services and run their day from the same
app. Four actors share one mobile shell: family, nurse, admin, partner centre.
> Last verified: 2026-07-30 against commit `d3ec723`.
- Repo-wide context and the backend → root [CLAUDE.md](../CLAUDE.md)
- Business rules (what to build) → [`product/`](../product/index.md). **Read the relevant doc before
designing a feature** — don't infer a business rule from code.
- Visual/design work → the **frontend-designer** skill. It is the *design* contract and defers to this file
and [`docs/rules/client/`](../docs/rules/client/) for engineering rules.
---
## Stack
- **Next.js 16** — App Router, Turbopack, React Server Components. **Not a static export.**
- **React 19** + **TypeScript** (`strict`)
- **MUI v9** (`@mui/material`) with **Emotion** underneath; RTL via `stylis-plugin-rtl`
- **Lucide** (`lucide-react`) for icons, behind the `AppIcon` name registry
- **next-intl v4** — locales `fa` (default, RTL) and `en`
- **TanStack Query v5** for server state; a small **AuthContext** (`src/context/auth/`) for session state
- **react-hook-form v7** for every form with more than one field
- **notistack** for toasts; **js-cookie** (wrapped) for client cookies
- **Jest** + **Testing Library**; **ESLint 9** (flat config) + **Prettier**
## Commands
| Task | Command |
| --- | --- |
| Dev server | `npm run dev` |
| Production build | `npm run build` |
| **Type + lint + copy (the gate)** | `npm run check` |
| Type-check only | `npm run type` |
| Lint only / autofix | `npm run lint` · `npm run lint:fix` |
| Persian copy lint | `npm run lint:copy` |
| Format | `npm run format` |
| Test (watch / CI) | `npm test` · `npm run test:ci` |
## Quality gates
```
npm run check # tsc --noEmit → eslint . → scripts/check-copy.mjs
npm run test:ci # also required when you touched a component with a co-located *.test.tsx
```
Both must be green, and `en.json`/`fa.json` must be in sync, before work is done. There is **no
`next lint`** — it was removed in Next 16 and calling it silently does nothing.
---
## Hard rules
1. **Never add a layout above `[locale]`.** `src/app/[locale]/layout.tsx` **is** the root layout — it renders
`<html>`/`<body>`. A layout above it freezes `lang`/`dir`/messages on the default locale for every route.
2. **Respect the server/client boundary.** Never import `next/headers`, `next-intl/server`, or
`@/lib/cookies/server` from a client component; never import `@/lib/cookies/client` from an RSC.
3. **No hard-coded UI strings.** Every user-visible string is a key in **both** `messages/en.json` and
`messages/fa.json`. The one exception is `app/global-error.tsx`, which cannot use next-intl.
4. **Fetch only through `clientFetch`/`serverFetch`** (`@/lib/api`) — never raw `fetch()`. Domain calls live in
`src/services/{domain}/apis/`.
5. **Cookies only through the cookie manager** (`@/lib/cookies/*`) — never `document.cookie`, `js-cookie`
directly, `localStorage`, or `sessionStorage` for app or auth state.
6. **Colors come from `tokens.css` (`var(--bal-*)`) or MUI palette keys** — never a hex or rgb literal in `sx`
or `styled`. Use the pre-built `APP_THEME_LTR`/`APP_THEME_RTL`; **never call `createTheme()` in a
component.**
7. **MUI v9 API only.** `sx={{ mb: 4 }}`, not `mb={4}`. No v5/v6-only props (`useFlexGap`, `flexWrap` on
`Stack`, `storageWindow`, `InitColorSchemeScript`).
8. **RTL-safe.** Never `marginLeft`, `left:`, or `textAlign: 'left'` for layout flow — use logical or
MUI-flipped properties. `fa` is the default locale and it is RTL.
9. **Never add a pre-paint color-scheme script.** The no-flash boot is CSS-only; extend the `tokens.css`
media-query fallback instead.
10. **`prefers-reduced-motion` has exactly one gate**, in `src/app/globals.css`. Never add a second,
component-local branch.
11. **One layout: a phone.** `AppFrame` caps every screen at 480px at every viewport. Never add a `≥md` branch
that widens a shell, restores a sidebar, or goes multi-column. Wide content scrolls inside its own
container.
12. **Navigation is the bottom bar; there is no drawer.** Add a destination by adding a tab or a hub row, never
by forking `MobileShell`. Active state comes from the shared `matchActivePath`, never a hand-rolled
`pathname.startsWith`.
13. **All chrome navigation goes through `@/i18n/navigation`** — never a raw `next/link`, never a manual
`` `/${locale}` `` prefix.
14. **Icons come from the `AppIcon` registry by lowercase name.** `@mui/icons-material` was removed — never
reintroduce it, and never pass a raw icon component where a name is expected.
15. **Any form with more than one field uses react-hook-form**, bound through the
`@/components/common/form` wrappers. Never call `register`/`useController` at a call site.
16. **Never append `ROUTES.HOME` (`'/'`) to `PUBLIC_PATHS`** — it is matched with `startsWith`, so that would
silently make every route public.
17. **The middleware auth check is UX-only, not a security boundary** — it does not verify the JWT signature.
Never gate real authorization on it, on `isTokenAlive`, or on `useAdminCapabilities`.
18. **The client displays money; it never computes it.** IRR digit strings parsed with integer-safe `BigInt`
helpers, never a float. Never compute a rate, an aggregate, a payout date, or a holiday shift. A
server-frozen deadline is rendered, never recomputed. A signed balance is never clamped.
19. **Never leak clinical data.** The customer never fires the care-instructions query; the nurse's care-record
access is append-only; access-denied is gated *before* any clinical fetch; `is_internal` is never modelled
in user-app types. Clinical text is never logged, stored in `localStorage`, or put in a query string.
20. **Every shared component has a co-located `*.test.tsx`** (shared = imported from more than one place).
Don't mock MUI — test the rendered DOM.
21. **No dead code.** `@typescript-eslint/no-unused-vars` is an **error**, so it fails the gate. Delete it;
prefix a deliberately-unused binding with `_`. Comment the *why*, never the *what*.
22. **Every mutation needs an `onError` toast** unless the failure is already surfaced inline. But never toast
401/403/5xx in a hook — `clientFetch` already does.
23. **Magic strings become named constants** (`src/constants/`, or a co-located `constants.ts`).
24. **Don't reintroduce starter scaffolding** or `_TITLE_`/`_DESCRIPTION_` placeholders.
25. **When you change the structure, update "Project structure" below in the same change.**
---
## Project structure
The canonical map of the frontend's architecture. Expanded, with the reasoning, in
[`docs/rules/client/structure.md`](../docs/rules/client/structure.md).
```
client/
├── messages/{en,fa}.json translations — add every key to BOTH
├── middleware.ts i18n routing → guest front door → auth gate
├── next.config.mjs next-intl plugin + reactStrictMode, nothing else
└── src/
├── app/ the App Router tree
│ ├── global-error.tsx above [locale]: renders its own <html>, cannot use next-intl
│ ├── robots.ts · sitemap.ts the public surface, both locales
│ ├── fonts/ Mikhak woff2 (next/font/local resolves relative to the caller)
│ └── [locale]/
│ ├── layout.tsx THE ROOT LAYOUT — <html lang dir>, fonts, setRequestLocale,
│ │ providers, generateMetadata + metadataBase
│ ├── error.tsx · not-found.tsx · [...rest]/page.tsx
│ ├── (private-routes)/ layout mounts useSessionRoleSync
│ │ ├── _chrome/ shared content skeleton (private, not a route)
│ │ ├── select-role/ first-use role picker, in FocusedLayout
│ │ ├── (customer)/ the family app — no URL segment
│ │ ├── (customer-focused)/ chrome-free, same URL space (onboarding)
│ │ ├── nurse/ the nurse app
│ │ ├── admin/ the backoffice (+ _hub/ shared group-root body)
│ │ └── partner/ the partner-centre portal — a SEPARATE authz scope
│ └── (public-routes)/ login · terms · privacy · welcome
├── components/ common/ primitives + one folder per domain composite family
├── constants/ routes.ts · roles.ts · headers.ts · policy.ts
├── context/auth/ AuthContext (provider + reducer + useAuth)
├── hooks/ auth.ts · capabilities.ts · layout.ts · useAdminListState.ts
├── i18n/ routing.ts · request.ts · navigation.ts
├── layout/ AppFrame · MobileShell · the 4 actor layouts · chrome · config.ts
├── lib/ api/ · auth/ · cookies/ · query/ · toast/
├── services/ 22 domain services — the data layer
├── theme/ colors.ts · tokens.css · typography.ts · theme.ts · ThemeProvider.tsx
├── utils/ money · date · number · text · toCsv · navigation
└── config.ts API_URL · SITE_URL · NESHAN_WEB_KEY · IS_DEBUG
```
Route groups (parenthesised) add no URL segment; `_`-prefixed folders are private, not routes. Each private
group's layout is `'use client'` and wraps `RoleGuard` → that actor's layout.
**Every screen is a thin RSC `page.tsx` (exporting `generateMetadata`) plus a co-located `'use client'`
`<PageName>Screen.tsx`.** `page.tsx` never renders `<title>` and never touches `document.title`.
---
## Where to read more
Open **one** of these for the area you are touching.
| Working on… | Read |
| --- | --- |
| Routes, layouts, the RSC boundary, page metadata | [docs/rules/client/structure.md](../docs/rules/client/structure.md) |
| Colors, tokens, dark mode, RTL, fonts, motion | [docs/rules/client/theme.md](../docs/rules/client/theme.md) |
| The `App*` library, shells, navigation, icons, constants | [docs/rules/client/components.md](../docs/rules/client/components.md) |
| Any form | [docs/rules/client/forms.md](../docs/rules/client/forms.md) |
| Copy, translations, Persian orthography | [docs/rules/client/i18n.md](../docs/rules/client/i18n.md) |
| Fetching, TanStack Query, `services/{domain}`, money display, cookies | [docs/rules/client/services.md](../docs/rules/client/services.md) |
| Sessions, refresh, `RoleGuard`, middleware, security posture | [docs/rules/client/auth.md](../docs/rules/client/auth.md) |
| Tests, ESLint, the type gate | [docs/rules/client/testing.md](../docs/rules/client/testing.md) |
| The wire contract — envelope, status codes, enums, pagination | [docs/integration/](../docs/integration/index.md) |
| What is built, what is mocked, what is next | [docs/status/](../docs/status/index.md) |
| Brand, look and feel, turning a design into a screen | the **frontend-designer** skill |
| Cross-project rules — naming, gates, code quality | [docs/rules/shared/](../docs/rules/shared/) |