336 lines
26 KiB
Markdown
336 lines
26 KiB
Markdown
# 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** (B3–B6, 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.
|