frontend phase 0: app shells, design system & data/contract patterns
Turn the starter into the Balinyaar foundation for the three actor
experiences and lock in the patterns later phases copy.
- Cleanup: remove toastDemo namespace, placeholder home page, and the two
dead icons; fix BottomBar to use usePathname (locale-aware active tab).
- Three actor shells under (private-routes), no layout above [locale]:
customer (customer) group with the 5-tab bottom nav; nurse (/nurse) and
admin (/admin) on the shared sidebar engine. Role model via constants/roles
+ useActorRole (defaults to customer until roles land in f1-b2).
- services/{domain} reference (patients) with a mock behind a config seam,
hierarchical query keys, deliberate staleTime, and mutation invalidation;
shared ApiEnvelope/Paginated wire types + unwrap() in lib/api/types.
- Money (integer-safe IRR/Toman) + Shamsi-date utils; toEnglishDigits helper.
- Shared composites, each tested: OtpInput, PhoneNumberField, StepperHeader,
StatusChip, PlaceholderScreen.
- i18n: seed nav/common/shell/patients in both locales; document namespace
conventions. Update client/CLAUDE.md Project Structure + fix ColorSchemeScript
doc drift. Add phase report, STATUS, and REQ-001 (envelope/casing/pagination).
Gate: npm run check + test:ci green (72 tests); build green with NEXT_PUBLIC_API_URL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,4 +12,17 @@ for awareness.
|
||||
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
|
||||
-->
|
||||
|
||||
_(no phases completed yet)_
|
||||
## frontend-phase-0 — Foundations: app shells, design system & data/contract patterns — 2026-07-02
|
||||
- **Shipped:** 3 actor shells (customer bottom-nav / nurse / admin sidebar) + role-aware routing under
|
||||
`(private-routes)`; `useActorRole`; the `services/{domain}` reference (`patients`, mocked behind a
|
||||
seam) with deliberate Query caching + invalidation; `lib/api/types.ts` (envelope/pagination); money +
|
||||
Shamsi-date utils; shared composites `OtpInput`/`PhoneNumberField`/`StepperHeader`/`StatusChip`/
|
||||
`PlaceholderScreen` (each tested); i18n `nav`/`common`/`shell`/`patients` in both locales. Removed the
|
||||
demo scaffolding; fixed the `BottomBar` pathname bug.
|
||||
- **Consumes:** dev/contracts/conventions/* + openapi/swagger.v1.json (b0 = ping only). No feature
|
||||
contract consumed yet.
|
||||
- **Mocked client-side:** `services/patients` via `patientsMockApi` (USE_PATIENTS_MOCK=true) — template
|
||||
for f1+. Swap is one line once real endpoints land.
|
||||
- **Gate:** npm run check green · npm run test:ci green (72 tests) · npm run build green with
|
||||
NEXT_PUBLIC_API_URL set.
|
||||
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-001).
|
||||
|
||||
@@ -12,4 +12,21 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
|
||||
- **Status:** open | delivered in backend-phase-K
|
||||
-->
|
||||
|
||||
_(no requests yet)_
|
||||
## REQ-001 — Confirm response envelope, wire casing & pagination shape — filed by frontend-phase-0 — 2026-07-02
|
||||
- **Need:** Authoritative confirmation of three things the frontend types depend on:
|
||||
1. **Envelope unwrapping.** The b0 swagger shows every response wrapped in `ApiResult`
|
||||
(`{ isSuccess, statusCode, message, requestId, data }`). The frontend's `clientFetch` currently
|
||||
returns the **raw body**, so domain `clientApi`s read the payload via `unwrap()` (`data`). Confirm this
|
||||
is the intended shape for all endpoints (i.e. payload always under `data`), so the pattern is correct
|
||||
before f1+ copy it.
|
||||
2. **Wire casing.** Observed swagger properties are **camelCase** (`isSuccess`, `serverTimeUtc`) — not the
|
||||
snake_case `api-conventions.md` implies for URL segments. Please confirm JSON body casing is camelCase
|
||||
(and, if so, we can note it in the convention doc), or state where it differs.
|
||||
3. **Pagination payload.** `api-conventions.md` says lists return `items` + `total` (+ `page`/`page_size`).
|
||||
Confirm the exact field names/casing on the wire (we've typed `Paginated<T>` as
|
||||
`{ items, total, page, pageSize }` in `client/src/lib/api/types.ts`).
|
||||
- **Why:** These fix the shared `ApiEnvelope<T>`/`Paginated<T>` types and the `services/{domain}` reference
|
||||
pattern every later frontend phase inherits.
|
||||
- **Proposed shape:** `{ isSuccess: boolean, statusCode: number, message?: string, requestId?: string, data?: T }`
|
||||
and `data: { items: T[], total: number, page: number, pageSize: number }` for lists.
|
||||
- **Status:** open
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Frontend Phase 0 — Foundations: app shells, design system & data/contract patterns — Report (2026-07-02)
|
||||
|
||||
## What was built
|
||||
|
||||
**Cleanup (3.1)**
|
||||
- Removed the `toastDemo` i18n namespace (both locales) and the placeholder home page.
|
||||
- Deleted the two dead icons (`AppIcon/icons/CurrencyIcon.tsx`, `YellowPlanIcon.tsx`).
|
||||
- Fixed `BottomBar` to read the route via `usePathname()` (was the global `location`) and made it
|
||||
locale-aware (highlights the active tab, pushes locale-prefixed routes).
|
||||
- Audit note "AppLoading missing from the `@/components` barrel" — verified it is already exported
|
||||
(`components/common/index.tsx`); no change needed.
|
||||
|
||||
**Actor shells + routing (3.2)** — three role-scoped experiences under `(private-routes)`, no layout
|
||||
added above `[locale]`:
|
||||
- **Customer (family)** — `(customer)` route group (no URL segment) at `/`, `/bookings`, `/patients`,
|
||||
`/wallet`, `/profile`; `CustomerLayout` = slim TopBar + the 5-tab `BottomBar` from the wireframe.
|
||||
- **Nurse** — `/nurse`, `/nurse/verification`, `/nurse/visits`; `NurseLayout` on the shared
|
||||
`TopBarAndSideBarLayout` engine.
|
||||
- **Admin** — `/admin`, `/admin/users`, `/admin/notifications`; `AdminLayout`, persistent desktop sidebar.
|
||||
- Role model: `constants/roles.ts` (`AppRole`), optional `User.roles`, and `useActorRole()` (defaults to
|
||||
`customer` until the server seeds roles in f1-b2). Nav is built per shell from `useTranslations('nav')`.
|
||||
|
||||
**Data pattern + utils (3.3)**
|
||||
- Reference domain `services/patients/` mirroring `auth`: `types.ts` (+ the `PatientsApi` seam interface),
|
||||
`keys.ts` (hierarchical factory), `constants.ts` (mock toggle + staleTime), `apis/` (`clientApi` real,
|
||||
`mockApi` in-memory, `index` selects by config), `hooks/` (`usePatients` with `staleTime`,
|
||||
`useAddPatient` invalidates the list), barrel exporting hooks only.
|
||||
- Shared wire types `lib/api/types.ts`: `ApiEnvelope<T>` + `unwrap()`, `Paginated<T>`, `PageParams`.
|
||||
- Money/date utils in `@/utils`: `parseIrr`/`rialToToman`/`formatIrr`/`formatIrrToToman` (integer-safe
|
||||
BigInt) and `formatShamsiDate`/`formatShamsiDateTime` (Intl Persian calendar — no date lib). Plus
|
||||
`toEnglishDigits`/`digitsOnly` in `utils/text.ts`.
|
||||
|
||||
**Shared composites (3.4)** — each in `src/components/<Name>/` with a co-located `.test.tsx`, composed
|
||||
from MUI/`App*` primitives, i18n-agnostic (labels passed by caller):
|
||||
- `OtpInput` (auto-advance, backspace-to-previous, paste distribution, digit-normalizing, LTR-in-RTL),
|
||||
- `PhoneNumberField` (Iranian mobile, normalizes Persian/Arabic digits, caps at 11, `isIranianMobile`),
|
||||
- `StepperHeader` (MUI Stepper, RTL-aware), `StatusChip` (verified/pending/rejected/… off `--bal-*` tokens),
|
||||
- `PlaceholderScreen` (empty-state used by every not-yet-built screen).
|
||||
- Nurse/result card and price-breakdown were **deferred** to their feature phases (per the phase's "your call").
|
||||
|
||||
**i18n (3.5)** — seeded `nav`, `common`, `shell`, `patients` in both `en.json`/`fa.json` (in sync,
|
||||
RTL-first). Documented the future namespace conventions in `client/CLAUDE.md`.
|
||||
|
||||
## What is now testable (and exactly how)
|
||||
|
||||
1. `cd client && npm run dev` → open `http://localhost:3000` (redirects to `/fa`).
|
||||
- Customer shell: mobile 5-tab bottom nav (خانه/رزروها/بیماران/کیفپول/پروفایل); tapping switches routes
|
||||
and highlights the active tab.
|
||||
- Nurse shell: `/fa/nurse` — TopBar + sidebar (داشبورد/احراز هویت/ویزیتها).
|
||||
- Admin shell: `/fa/admin` — persistent sidebar on desktop (نمای کلی/کاربران/اعلانها).
|
||||
- Switch locale to `/en` → `dir` flips to LTR and all strings translate; dark-mode toggle still works.
|
||||
2. **Reference data pattern:** `/fa/patients` shows the mocked list (~400 ms latency), an add form
|
||||
(name + gender). Submitting adds the patient and the list updates **without a refetch** — open React
|
||||
Query Devtools to watch `['patients','list',…]` cache + the invalidation on mutation success.
|
||||
3. `npm run check` (type + lint) and `npm run test:ci` (72 tests, 12 suites) both pass. `npm run build`
|
||||
passes when `NEXT_PUBLIC_API_URL` is set (see Follow-ups).
|
||||
|
||||
## What is mocked / waiting on a real service
|
||||
|
||||
- **Patients domain — client-side mock.** `services/patients/apis/mockApi.ts` (`patientsMockApi`)
|
||||
implements the `PatientsApi` interface (`services/patients/types.ts`) in memory. Selected by
|
||||
`USE_PATIENTS_MOCK = true` in `services/patients/constants.ts`. The real `clientApi.ts` is written
|
||||
against `/patients` (GET list + POST create) and already unwraps the `ApiEnvelope`. **To make real:**
|
||||
publish the `patients` contract + endpoints, set `USE_PATIENTS_MOCK = false` — no hook/component change.
|
||||
(This is a frontend client-side mock, not a backend DI seam, so it is recorded here rather than in the
|
||||
backend-owned `mocks-registry.md`.)
|
||||
- This is the template f1+ copy for any domain whose backend phase hasn't merged.
|
||||
|
||||
## Contracts
|
||||
|
||||
- **Produced:** none (frontend consumes).
|
||||
- **Consumed:** `dev/contracts/conventions/{api-conventions,money-and-types}.md` and the b0
|
||||
`openapi/swagger.v1.json` (only `ping` endpoints exist yet). Types-from-contract step is wired for the
|
||||
`patients` reference (shapes mirror the intended wire; `ApiEnvelope`/`Paginated` in `lib/api/types.ts`).
|
||||
- **Request filed:** `frontend/requests/for-backend.md` REQ-001 (confirm envelope unwrapping, wire casing,
|
||||
pagination payload shape).
|
||||
|
||||
## Docs updated
|
||||
|
||||
- `client/CLAUDE.md`: *Project Structure* tree (new route groups, actor layouts, shared composites,
|
||||
`services/patients`, `lib/api/types.ts`, `constants/roles.ts`, money/date utils); i18n namespaces +
|
||||
future-namespace conventions; a new *services/{domain} reference pattern* subsection (caching, mock
|
||||
seam, envelope, money/dates). Corrected the `ColorSchemeScript` doc drift in the two structure lines
|
||||
that named it (it is neither exported from `@/theme` nor rendered).
|
||||
|
||||
## Follow-ups for later phases
|
||||
|
||||
- **Envelope unwrapping (REQ-001):** `clientFetch`/`serverFetch` currently return the raw body, so domain
|
||||
`clientApi`s call `unwrap()`. If the team prefers central unwrapping, that touches the auth plumbing —
|
||||
coordinate before changing. Wire casing observed is **camelCase**, not the snake_case api-conventions
|
||||
implies; confirm and update the convention doc.
|
||||
- **Role guards:** shells read `useActorRole()` but do not yet *guard* cross-actor access (any authed user
|
||||
can open `/nurse`, `/admin`). Add real guards once roles land in **f1-b2**.
|
||||
- **Login is username/password** today; phone-OTP arrives in **f1-b2** (use `OtpInput`/`PhoneNumberField`).
|
||||
- **`npm run build` needs `NEXT_PUBLIC_API_URL`:** `@/config` uses `envRequired`, which throws at import.
|
||||
Dev works via the committed `.env.development`; a production build must supply the var (as it always would
|
||||
once any page imports the fetch layer). Not a code defect — an env expectation to note in CI.
|
||||
Reference in New Issue
Block a user