Files
baya-monorepo/client/CLAUDE.md
T
2026-08-02 20:01:31 +03:30

11 KiB

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
  • Current product truth (what to test, what's blocking, what's missing) → mvp/
  • Business rules in depth (archived reference, not actively maintained) → archive/product/. 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 archive/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.

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
Colors, tokens, dark mode, RTL, fonts, motion docs/rules/client/theme.md
The App* library, shells, navigation, icons, constants docs/rules/client/components.md
Any form docs/rules/client/forms.md
Copy, translations, Persian orthography docs/rules/client/i18n.md
Fetching, TanStack Query, services/{domain}, money display, cookies docs/rules/client/services.md
Sessions, refresh, RoleGuard, middleware, security posture docs/rules/client/auth.md
Tests, ESLint, the type gate docs/rules/client/testing.md
The wire contract — envelope, status codes, enums, pagination docs/integration/
What is built, what is mocked, what is next docs/status/
Brand, look and feel, turning a design into a screen the frontend-designer skill
Cross-project rules — naming, gates, code quality docs/rules/shared/