add build development phases

This commit is contained in:
hamid
2026-06-28 21:59:59 +03:30
parent 1df3cd9f64
commit 53a40dc51d
52 changed files with 12379 additions and 0 deletions
+335
View File
@@ -0,0 +1,335 @@
# Frontend Phase 2 — Onboarding & profiles (customer, patient, nurse, bank)
> **Mission:** with auth and roles in place, turn a freshly-logged-in user into a *usable account*.
> A family completes the "who is care for?" onboarding and registers their first **patient** (the
> care recipient, who is not the payer); a nurse bootstraps a public profile and adds the payout
> **bank account** that the verification pipeline and payouts later depend on. This phase implements
> the wireframe onboarding screens (A3, A4, E1) plus the customer profile and the nurse profile /
> bank settings, and stands up the `services/profiles`, `services/patients`, and `services/nurse`
> domains following the f0 data pattern. It is the gate that makes addresses (f3) and booking (f7)
> possible — a booking needs a patient with a known gender, and a payout needs a verified bank account.
>
> **Track:** frontend · **Depends on:** [`frontend-phase-1-b2.md`](./frontend-phase-1-b2.md) (auth/OTP/roles, `AuthContext`) + the **backend-phase-3** contract ([`identity-profiles.md`](../../contracts/domains/identity-profiles.md)) · **Unlocks:** [`frontend-phase-3-b4.md`](./frontend-phase-3-b4.md) (addresses & geo), [`frontend-phase-7-b8.md`](./frontend-phase-7-b8.md) (booking request — needs a patient)
> **Before you start, read [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md).** It is not optional.
---
## 1. Context — where this sits
The user can now log in by phone-OTP and the app knows their role (f1-b2). What they *can't* yet do is
say **who they are caring for**. Balinyaar's whole model rests on the **customer ≠ patient** split: the
payer (an adult child, a spouse) is almost never the care recipient (an elderly parent, an infant, a
post-surgical adult). This phase captures that split for the customer side and, for the nurse side, the
two pieces of profile state that everything downstream hangs off — a public-facing profile and a payout
destination. No search, no booking, no money yet — just the identity/profile surface those later phases
read from.
**What already exists (do not rebuild):**
- **From [`frontend-phase-0.md`](./frontend-phase-0.md):** the three actor app shells + role-scoped
route groups under `[locale]`; the **customer 5-tab bottom nav** (خانه/Home · رزروها/Bookings ·
بیماران/Patients · کیف‌پول/Wallet · پروفایل/Profile); the `services/{domain}` reference pattern
(`types.ts` / `keys.ts` / `apis/clientApi.ts` / `hooks/use*.ts` / `index.ts`) modelled on
`src/services/auth/*`; the TanStack-Query caching rules (per-domain `keys` factory, deliberate
`staleTime`/`gcTime`, **invalidate on mutation**); the money/format util in `src/utils/`; the
contracts→types pattern; the i18n namespace plan; and the shared composite components — the
**stepper / progress header**, the **status chip** (verified/pending/…), and the **phone-number
field** — which you **reuse here, not re-create**.
- **From [`frontend-phase-1-b2.md`](./frontend-phase-1-b2.md):** phone login (A1), OTP (A2), the
customer↔nurse login switch, the role router, and roles surfaced in `AuthContext` (read the current
user + role from there; don't re-fetch `/me` ad-hoc). The OTP-input and phone-field composites are
already wired — reuse them.
- **From the backend ([`identity-profiles.md`](../../contracts/domains/identity-profiles.md), b3):** the
live endpoints for customer profile, patients CRUD, nurse profile bootstrap, and nurse bank accounts
(with the IBAN-ownership inquiry). **Consume the contract; do not guess shapes.** If a shape you need
is missing or unclear, append it to
[`../../shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)
and mock behind the `services/{domain}` seam meanwhile (operating-rules §6).
> **Scope fence:** this phase builds the **B7 *profile* part only** — avatar + short bio. The nurse
> **services-and-prices builder** on B7 (the "+ افزودن خدمت" list) is **(DEFERRED)** to
> [`frontend-phase-4-b5.md`](./frontend-phase-4-b5.md) (catalog & service builder). The nurse
> **available-days picker** on B7 is **(DEFERRED)** with catalog/availability. **Addresses** (the A4/E1
> sibling "address book") are **(DEFERRED)** to [`frontend-phase-3-b4.md`](./frontend-phase-3-b4.md).
> The whole **nurse verification pipeline** (B3B6, identity/Shahkar/license) is
> **(DEFERRED)** to [`frontend-phase-5-b6.md`](./frontend-phase-5-b6.md) — here the nurse only gets an
> *unverified* profile and a bank account; surfacing the "not bookable until verified" banner is part of
> f5, not this phase (a simple placeholder is acceptable).
## 2. Required reading (do this first)
- [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md) and
[`../_shared/frontend-conventions-checklist.md`](../_shared/frontend-conventions-checklist.md).
- [`client/CLAUDE.md`](../../../client/CLAUDE.md) — the RSC/client boundary, layouts, i18n, theme,
cookies, the `services/{domain}` fetch pattern, anti-patterns. Re-confirm the f0 *Project Structure*
additions so you place new folders correctly.
- **Invoke the `frontend-designer` skill** before any visual work — it is the design/brand contract
(teal `#1d4a40` / terracotta `#d98c6a` palette, `tokens.css`, typography, the `App*` library, the
layout shells, the hard UI rules). The onboarding stepper, gender toggle, condition chips, patient
cards, empty states, and the bank-status panel are all visual deliverables and **must** go through it.
- **Product — the business rules (the source of truth, not the code):**
- [`../../../product/business/01-actors-and-onboarding.md`](../../../product/business/01-actors-and-onboarding.md)
— the customer/patient split, role-staged KYC, and what is MVP vs DEFERRED (customer national-ID
KYC is deferred; do **not** add it to any form).
- [`../../../product/data-model/01-identity-and-access.md`](../../../product/data-model/01-identity-and-access.md)
— the exact tables and columns behind these screens (`customer_profiles`, `patients`,
`nurse_profiles`, `nurse_bank_accounts`) and their constraints (single-primary bank account;
`iban_hash` uniqueness; `matched_national_id`; the guarded `is_verified`).
- **Product — the visual baseline:**
[`../../../product/wireframes/index.html`](../../../product/wireframes/index.html) — read screens **A3**
(برای چه کسی؟ — 2-step progress bar, single-select), **A4** (ثبت بیمار — name, age, gender toggle مرد/زن,
condition chips), **A5** (Home — the "complete patient record" nudge you land on), **E1** (لیست بیماران —
patient cards + "+ افزودن بیمار", Patients tab active), and the **B7** profile header (photo + short
bio). Match the RTL Persian layout, the brand colours, and the status legend (green = verified, amber =
pending, grey = manual/later).
- **Contracts:** [`../../contracts/domains/identity-profiles.md`](../../contracts/domains/identity-profiles.md)
(the b3 contract you consume — endpoints, request/response shapes, enums, masking, failure cases),
plus the cross-cutting conventions you already follow:
[`api-conventions.md`](../../contracts/conventions/api-conventions.md) (envelope, snake_case routes,
pagination, status codes) and [`money-and-types.md`](../../contracts/conventions/money-and-types.md)
(enums-as-codes, **`gender` = `male`/`female` is load-bearing**, masked PII like last-4 of an IBAN).
- **Code to mirror:** `src/services/auth/*` (the exact service skeleton every new domain copies) and the
three shared composites from f0 (`stepper/progress header`, `status chip`, `phone-number field`) — read
their props before reusing them.
- **The handoff you're handed:**
[`../../shared-working-context/backend/handoff/after-backend-phase-3.md`](../../shared-working-context/backend/handoff/after-backend-phase-3.md)
(what b3 shipped, which endpoints are live, what's mocked behind a seam — e.g. the IBAN-ownership
inquiry).
## 3. Scope — build this
Three new domain services, the customer onboarding flow, patient CRUD, the customer profile, and the
nurse profile + bank settings. Every user-visible string is an i18n key in **both** `en.json` and
`fa.json` (RTL-first); every list is cached and invalidated per the f0 pattern; every screen is built
through the **frontend-designer** skill.
### 3.1 The domain services (copy the f0 pattern)
Three services under `src/services/`, each with `types.ts` (from the b3 contract — never guessed),
`keys.ts` (a query-key factory), `apis/clientApi.ts` (wrapping `clientFetch`; `serverApi.ts` only if an
RSC needs prefetch), `hooks/use*.ts` (one hook per file), and `index.ts`:
- **`services/profiles`** — the customer & nurse *profile* domain.
- `useCustomerProfile()` (`useQuery`) → `GET …/me/customer_profile` (or the b3 route).
- `useUpsertCustomerProfile()` (`useMutation``PUT …/me/customer_profile`) — name, contact,
`default_emergency_contact_name`/`default_emergency_contact_phone`; **invalidates** the customer-profile
query and patches `AuthContext`'s cached `/me` if profile-completion changes.
- `useNurseProfile()` (`useQuery`) → the nurse's own profile.
- `useCreateNurseProfile()` / `useUpdateNurseProfile()` (`useMutation``POST/PUT …/me/nurse_profile`)
— bootstrap (`is_verified` stays `false`, server-owned — never sent by the client), then edit
`bio`/`avatar_url`/`years_experience`. Invalidate the nurse-profile query on success.
- **`services/patients`** — the care-recipient domain (customer-scoped).
- `usePatients()` (`useQuery`, paginated per `api-conventions.md`) → `GET …/patients``{ items, total }`.
- `useCreatePatient()` (`POST …/patients`), `useUpdatePatient()` (`PUT …/patients/{id}`),
`useArchivePatient()` (the archive/soft-delete route — sets `is_active=false`, **not** a hard delete).
- All three mutations **invalidate `patientKeys.list()`** (or `setQueryData` to splice the row) so the
E1 list never refetches needlessly; archive optimistically removes/greys the card then reconciles.
- **`services/nurse`** — the nurse payout **bank account** sub-domain (kept separate from the profile
because verification/payouts read it independently).
- `useNurseBankAccounts()` (`useQuery`) → list (usually one primary).
- `useAddNurseBankAccount()` (`POST …/me/nurse_bank_accounts`) — submit IBAN (Sheba) + account-holder
name; the server kicks off the ownership inquiry (mocked behind `IBankAccountOwnershipVerifier` in
b3) and returns the account in a **pending** state.
- `useSetPrimaryBankAccount()` (where the contract exposes it) — single-primary enforcement is
server-side; reflect it in cache.
- All mutations invalidate the bank-accounts query so the pending→verified/mismatch transition shows
on the next read (poll/refetch — see §3.5).
> Where a b3 endpoint isn't live when you build, ship a **mock `clientApi`** behind the same seam (a
> fixed in-memory patient list; a bank account that flips pending→verified after one refetch; a
> mismatch account for a known test IBAN) and record it in your report + the mock registry, so it swaps
> cleanly once the real endpoint lands.
### 3.2 Customer onboarding — A3 → A4 (the "who is care for" flow)
A two-step wizard, mounted in the customer route group, run once after first login (and re-enterable
from the patient list). **Reuse the f0 stepper/progress header** for the 2-step bar.
- **A3 · برای چه کسی؟ (Who is care for?)** — a single-select radio list of relations: **پدر/مادر**
(parent), **همسر** (spouse), **فرزند** (child), **خودم** (self). Selecting a relation carries forward
to pre-shape A4 (e.g. "خودم" pre-fills the patient as the customer). Primary CTA **ادامه** advances the
stepper; back is allowed. The relation is a stable enum code (`parent`/`spouse`/`child`/`self`), an
i18n-labelled chip — never a hardcoded Persian string in logic.
- **A4 · ثبت بیمار (Add patient)** — the patient form: **full name**, **age** (or birth date per the
contract — map to `birth_date`), **gender toggle (مرد/زن → `male`/`female`)**, and **condition chips**
(multi-select: سالمند/elderly, پس از جراحی/post-surgery, دیابت/diabetes, + بیشتر). On submit it calls
`useCreatePatient()`; the chosen relation is stored with the patient. CTA **ذخیره و ادامه** creates the
patient, invalidates the list, and **routes to Home (A5)** where the "complete patient record" nudge is
already shown.
Validation: name required; **gender required** (it drives same-gender matching downstream — see §5);
age/birth-date validated; conditions optional. Surface 400 field errors from the envelope inline.
### 3.3 Patient list & CRUD — E1
The **Patients** bottom-nav tab. Reuse the f0 cards/empty-state primitives where they exist.
- **E1 · لیست بیماران (Patients list)** — patient cards showing relation + name, age/gender, and condition
chips, with a prominent **+ افزودن بیمار** CTA. States to build: **loading skeleton**, **empty state**
(no patients → the A4 add flow as the prominent CTA), and the populated list. Patients tab active in the
bottom nav.
- **Add / Edit** — the same A4 form, reused for create and edit (`useCreatePatient` / `useUpdatePatient`).
Edit pre-fills from the cached row.
- **Archive** — a confirm dialog ("آرشیو بیمار؟") then `useArchivePatient()`; the card is removed/greyed.
**Never a hard delete** — archive only (`is_active=false`); a patient referenced by a past booking must
survive.
> The full **patient record viewer** (E2 — medications/routine/history/tasks tabs) and **nurse visit
> notes** (E3) are **(DEFERRED)** to [`frontend-phase-13-b14.md`](./frontend-phase-13-b14.md). E1 here is
> the list/CRUD shell only.
### 3.4 Customer profile + emergency contact
A profile screen under the customer **پروفایل/Profile** tab: editable `first_name`/`last_name`,
`preferred_language`, optional `avatar_url`, and the **emergency contact** (`default_emergency_contact_name`
+ `_phone`, the phone via the **reused f0 phone-field**). Saves through `useUpsertCustomerProfile()`,
invalidates the profile query, and reflects profile-completion back into the Home nudge. Do **not** add a
national-ID field — customer KYC is **(DEFERRED)** and the column stays unused at launch.
### 3.5 Nurse profile bootstrap + bank settings (the B7 *profile* part)
Mounted in the nurse route group.
- **Nurse profile bootstrap (B7 header)** — **avatar/profile photo** (upload via the contract's
avatar/object-storage route; if not live, mock behind the seam) + **short bio** (+ `years_experience`
if the contract carries it). `useCreateNurseProfile()` on first entry, then `useUpdateNurseProfile()`.
The nurse profile is created **unverified** (`is_verified=false`, server-owned) and **not bookable**
show a neutral "تکمیل احراز هویت برای فعال‌سازی" placeholder pointing at verification (the real banner is
f5). The **services-and-prices builder** and **available-days picker** on B7 are **(DEFERRED)** to f4.
- **Nurse bank-account settings (payout IBAN)** — an **IBAN (شبا) entry** form (Sheba format validation
client-side: `IR` + 24 digits) + account-holder name, submitted via `useAddNurseBankAccount()`. Render
the three ownership-inquiry states off the contract's status field, each a distinct UI state built with
the **reused f0 status chip**:
- **pending** (`matched_national_id` null / inquiry in flight) — amber "در حال استعلام مالکیت حساب" panel;
poll/refetch (`refetchInterval` while pending, then stop) so the transition appears without a manual
reload.
- **verified** (`matched_national_id=true`, `is_verified=true`) — green "حساب تاییدشد" + the **masked**
IBAN (last 4) per the money-and-types masking rule.
- **mismatch** (`matched_national_id=false`) — a clear, **non-accusatory** error state: "حساب باید به نام
خودتان باشد" with a re-enter CTA. Ownership mismatch is gated server-side; surface it as a friendly
domain error, never a raw 4xx toast.
## 4. Mocks & seams in this phase
This is a **frontend** phase; it owns no backend seam. It **consumes** the b3 contract and, where an
endpoint isn't live yet, mocks **behind the `services/{domain}` seam** (a mock `clientApi`) per
operating-rules §6 — the same pattern f0 established. Specifically you may need to mock:
- **Patients CRUD** — an in-memory list seeded with one patient, supporting create/edit/archive.
- **The IBAN-ownership inquiry result** — the real check is the backend's
`IBankAccountOwnershipVerifier` seam (introduced in **b3**, recorded in the
[mock registry](../../shared-working-context/reports/mocks-registry.md)); on the client just drive the
**pending → verified** transition (and a **mismatch** for a known test IBAN) so the three UI states are
demonstrable end-to-end.
- **Avatar/photo upload** — if the contract's storage route isn't live, accept the file and echo a fake
URL behind the seam.
Record every client-side mock in your phase report and in the mock registry with **how f-later (or the
b3 merge) swaps it out** — the swap must be implementation-only, no call-site changes.
## 5. Critical rules you must not get wrong
- **Customer ≠ patient — never collapse them.** The payer and the care recipient are distinct rows; a
patient is created under the signed-in customer and is **tenancy-scoped server-side**. Don't assume the
logged-in user is the patient (except the explicit "خودم" relation, which still creates a patient row).
- **Patient `gender` is REQUIRED.** It is load-bearing for **same-gender caregiver matching** (a near-hard
requirement) used by search (f6) and booking (f7). The gender toggle (`male`/`female`) must be a required
field — never default it, never let the form submit without it.
- **Tenancy is enforced server-side — surface friendly errors.** A `403`/`404` from acting on someone
else's patient/profile is the fetch layer's concern (it already toasts auth errors); your hooks add only
the **domain-specific** message ("این بیمار در دسترس شما نیست"). Never try to enforce tenancy on the
client or expose another customer's data.
- **No customer national-ID KYC.** It is DEFERRED; the column is unused at launch. Do not add a national-ID
field to the customer profile or gate browsing/booking on it.
- **`is_verified` is server-owned and guarded.** The client **never** sends or sets it; a freshly
bootstrapped nurse profile is unverified and not bookable. Reflect that read-only state; the flip happens
only inside the backend verification transaction (f5).
- **Bank account: three states, money-safe.** Render **pending-verification**, **ownership-mismatch**, and
**verified** distinctly; the IBAN is **masked** (last 4) once stored; one primary account per nurse is
server-enforced. **First payout is gated on `matched_national_id=true`** — never present a mismatched or
pending account as ready to pay. The mismatch copy must be **non-accusatory**.
- **Archive, don't delete.** Patient removal is soft (`is_active=false`) so historical bookings stay intact.
- **Caching is a feature.** Patient/profile/bank queries use deliberate `queryKey`/`staleTime`, and every
create/edit/archive **invalidates** (or `setQueryData`) — never re-fetch data already in cache. Keep the
bank `refetchInterval` only while pending; stop it once resolved. Minimise re-renders (colocate form
state, stable callbacks).
- **RSC/client boundary, RTL, both locales, tokens.** Forms and lists are client components (no
`next-intl/server`/`next/headers` in them); `fa` is default and **RTL** — design RTL-first and verify the
gender toggle, chips, and stepper mirror correctly; every string in **both** `en.json`/`fa.json`; colours
from `tokens.css`; MUI v9 API + the pre-built themes only. **MUI primitives stay MUI**; the stepper /
status-chip / phone-field are the **f0 shared composites — reuse, don't re-implement.** Any genuinely new
shareable composite (e.g. a `PatientCard`, a `GenderToggle`, a `ConditionChips`, a `BankStatusPanel`)
lives at the shared `src/components/…` level with a co-located `*.test.tsx`.
## 6. Definition of Done
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus phase-specific:
- [ ] **A3 → A4** runs end-to-end: a new customer picks a relation, fills the patient form (name, age,
**required** gender, optional conditions), and **lands on Home (A5)** with one patient created.
- [ ] **E1** patient list works: empty state with add CTA; create/edit reuse the A4 form; archive
(soft) with confirm; the list is cached and invalidated on every mutation (no needless refetch).
- [ ] **Customer profile + emergency contact** saves and reflects profile-completion; no national-ID field.
- [ ] **Nurse profile bootstrap** (avatar + bio) creates an unverified, not-bookable profile; the
services builder + availability picker are correctly **deferred** (not stubbed as working).
- [ ] **Nurse bank account** submits an IBAN and shows all three states — **pending → verified** (mock
transition) and **ownership-mismatch** — with a masked IBAN on verify and non-accusatory mismatch copy.
- [ ] `services/profiles`, `services/patients`, `services/nurse` follow the f0 pattern (keys factory,
one-hook-per-file, invalidation); types derive from the b3 contract (or a gap is filed in
`requests/for-backend.md` and mocked behind the seam).
- [ ] New shared composites each have a co-located test; the **f0 stepper/status-chip/phone-field are
reused** (not duplicated).
- [ ] `npm run check` green; `npm run test:ci` green for the shared components added; `en.json`/`fa.json`
in sync; `client/CLAUDE.md` *Project Structure* updated for the new services/route folders.
## 7. How to test (what a human can verify after this phase)
Run `npm run dev` (and the b3 server, or the seam mock).
- **Customer onboarding:** log in as a customer → land on **A3**, the 2-step bar shows step 1; pick a
relation → **A4**; try to submit without gender → blocked with a required-field error; fill it and
submit → you land on **Home (A5)** and the "complete patient record" nudge is present. *Expected:* one
patient exists and the flow doesn't re-trigger on next login.
- **Patient CRUD (E1):** open the **Patients** tab → see the patient as a card (relation, name,
age/gender, condition chips). Add a second patient → it appears without a full reload (cache spliced/
invalidated). Edit it → changes persist. Archive it (confirm) → the card disappears; it is **not**
hard-deleted. Open Patients on a fresh account → the **empty state** with the add CTA. Inspect React
Query Devtools: the list query is cached and mutations invalidate it.
- **Customer profile:** edit name + emergency contact → save → the Home nudge reflects completion. Confirm
there is **no** national-ID field.
- **Nurse profile + bank:** log in as a nurse → bootstrap the profile (set avatar + a short bio) → it
saves and shows an **unverified / not-bookable** state. Open bank settings → enter an IBAN → see the
**pending** "در حال استعلام" panel, then (after the mock resolves / a refetch) the **verified** green
state with a **masked** IBAN. Enter the known **mismatch** test IBAN → see the **ownership-mismatch**
error with re-enter CTA. *Expected:* the three states are visually distinct and the verified account
shows last-4 only.
- **i18n / RTL:** switch locale → strings flip `fa``en` and `dir` flips; the gender toggle, chips, and
stepper mirror correctly. `npm run check` and `npm run test:ci` pass.
## 8. Hand off & document (close the phase)
- **Docs:** update the **Project Structure** tree in [`client/CLAUDE.md`](../../../client/CLAUDE.md) for
the new `services/profiles`, `services/patients`, `services/nurse` domains, the new shared composites
(`PatientCard`, `GenderToggle`, `ConditionChips`, `BankStatusPanel`), and any new route segments under
the customer/nurse groups. If you discover/confirm a business rule the product docs don't capture
(e.g. a relation-enum decision), record it in
[`../../../product/business/01-actors-and-onboarding.md`](../../../product/business/01-actors-and-onboarding.md)
— don't invent rules. Note any reusable pattern in `client/CLAUDE.md`.
- **Contract:** **consume** [`../../contracts/domains/identity-profiles.md`](../../contracts/domains/identity-profiles.md)
(b3) as the type source — do not guess shapes. Any gap (a missing field, an unclear enum, the
bank-status field, the avatar route) goes to
[`../../shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)
(append only — never edit backend files); mock behind the `services/{domain}` seam until b3 delivers it.
- **Handoff & report:** append your phase summary to
[`../../shared-working-context/frontend/STATUS.md`](../../shared-working-context/frontend/STATUS.md);
write [`../../shared-working-context/reports/frontend-phase-2-report.md`](../../shared-working-context/reports/README.md)
covering what was built, **what is now testable and exactly how** (the A3→A4→Home flow, patient CRUD,
the bank state transitions), what is **mocked client-side** (patients list, IBAN-inquiry transition,
avatar upload) and exactly how each swaps to the real b3 endpoint, and follow-ups for f3 (addresses
reuse this profile shell), f4 (the nurse services builder slots onto the B7 profile), and f5
(verification banner replaces the placeholder). Add/extend rows in
[`../../shared-working-context/reports/mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md)
for every client-side mock.
- **Memory:** save a `project` memory note for the non-obvious decisions this phase fixes (the
relation-enum + customer/patient split on the client, the three bank-account UI states and the
`matched_national_id` gating, the patients caching/invalidation strategy), with a one-line pointer in
`MEMORY.md`. Don't record what the code/contract already make obvious.