add build development phases
This commit is contained in:
@@ -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** (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.
|
||||
Reference in New Issue
Block a user