frontend phase 2: onboarding & profiles — customer/patient, nurse profile & bank

Turns a logged-in user into a usable account, consuming the b3 identity-profiles
contract behind the services/{domain} seam.

Services (mock default true; real HTTP clients wired for a one-line flip):
- services/patients: rewritten to b3 PatientDto + client-augmented relation/conditions;
  full CRUD, optimistic soft-archive, cache-splice on create, age<->birthDate helper.
- services/profiles: customer + nurse profile get/upsert + avatar (404->null mapping).
- services/nurse: payout bank accounts + IBAN(Sheba) util + pending-only polling.

Screens: A3->A4 onboarding wizard, E1 patients list/CRUD, A5 home (first-login gate +
nudge), customer profile (no national-ID), nurse profile bootstrap (unverified
placeholder), nurse bank settings (pending/verified/mismatch + make-primary).

Shared composites (each tested): GenderToggle, ConditionChips, RelationSelect,
PatientForm, PatientCard, BankStatusPanel; reuses f0 StepperHeader/StatusChip/PhoneField.
Adds onboarding/home/profile/nurseProfile/bank i18n namespaces (both locales, in sync),
the --bal-primary-soft token, and nurse sidebar Profile + Bank entries.

Contract gaps filed: REQ-005 (patient relation/conditions), REQ-006 (avatar route),
REQ-007 (customer name/language). Gate: check + 112 tests + build all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamid
2026-07-02 22:04:38 +03:30
parent 82561c4cc6
commit 4b4243c451
70 changed files with 3111 additions and 190 deletions
@@ -12,6 +12,33 @@ for awareness.
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
-->
## frontend-phase-2-b3 — Onboarding & profiles (customer, patient, nurse, bank) — 2026-07-02
- **Shipped:** three domain services — `services/patients` (rewritten to the b3 `PatientDto` + client-augmented
`relation`/`conditions`; full CRUD seam + mock + real client; `usePatients`/`useCreatePatient`/
`useUpdatePatient`/`useArchivePatient` with optimistic soft-archive), `services/profiles` (customer + nurse
profile get/upsert + avatar; `useCustomerProfile`/`useUpsertCustomerProfile`/`useNurseProfile`/
`useUpsertNurseProfile`/`useUploadAvatar`), `services/nurse` (bank accounts; `useNurseBankAccounts` with
pending-only `refetchInterval`, `useAddNurseBankAccount`, `useSetPrimaryBankAccount`; `iban.ts` Sheba
validate/normalize/bank-name). Screens: **A3→A4 onboarding wizard** (`(customer)/onboarding`), **E1 patients
list/CRUD** (add/edit dialog reusing the A4 form, soft-archive confirm, empty + skeleton states), **A5 Home**
(first-login redirect into onboarding when 0 patients + "complete patient record" nudge), **customer profile**
(name + preferred language + emergency contact, no national-ID), **nurse profile bootstrap** (avatar + bio +
years, unverified "not bookable" placeholder → verification), **nurse bank settings** (IBAN form + the three
ownership states pending/verified/mismatch). Shared composites `GenderToggle`/`ConditionChips`/`RelationSelect`/
`PatientForm`/`PatientCard`/`BankStatusPanel` (each tested); reused the f0 `StepperHeader`/`StatusChip`/
`PhoneNumberField`. Added `onboarding`/`home`/`profile`/`nurseProfile`/`bank` i18n namespaces + `patients`
extensions (both locales); `--bal-primary-soft` token (both schemes); nurse sidebar gains Profile + Bank.
- **Consumes:** dev/contracts/domains/identity-profiles.md (backend-phase-3). Routes `api/v1/{customer_profiles,
nurse_profiles}/{me,upsert}`, `api/v1/patients/{list,get,create,update,archive}`, `api/v1/nurse_bank_accounts/
{list,add,set_primary,verify_ownership}`.
- **Mocked client-side:** `services/patients` (`USE_PATIENTS_MOCK`), `services/profiles` (`USE_PROFILES_MOCK`),
`services/nurse` (`USE_NURSE_BANK_MOCK`) — all default `true`; real clients wired for a one-line flip. See
mocks-registry + the report for exactly what/why (relation/conditions, avatar, customer name/language gaps).
- **Gate:** npm run check green · npm run test:ci green (112 tests, +17) · npm run build green with
NEXT_PUBLIC_API_URL set (routes /onboarding, /nurse/profile, /nurse/bank generated).
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-005 patient relation/conditions, REQ-006 avatar
upload route, REQ-007 customer name/preferred-language).
## frontend-phase-1-b2 — Auth: phone-OTP login & role routing — 2026-07-02
- **Shipped:** `services/auth` rewritten for phone-OTP (types/keys/apis[client+mock+seam]/hooks:
`useRequestOtp`/`useVerifyOtp`/`useMe`/`useRefresh`/`useLogout`/`useSelectRole`/`useSessionRoleSync`) —
@@ -52,6 +52,39 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
- **Proposed shape:** `{ isSuccess: false, statusCode: 400, message: "…", code: "otp_locked", data: { retryAfterSeconds: 60 } }`
- **Status:** open
## REQ-005 — Patient `relation` + `conditions` fields — filed by frontend-phase-2-b3 — 2026-07-02
- **Need:** Add two fields to `PatientDto` and the `patients/create` + `patients/update` bodies:
1. `relation` (`parent`|`spouse`|`child`|`self`, nullable) — the care-recipient's relation to the payer.
2. `conditions` (string[] of stable codes, e.g. `elderly`/`post_surgery`/`diabetes`/`mobility`/`dementia`).
- **Why:** The A3 onboarding step captures the relation, and the A4 form + E1 patient cards show condition
chips. Neither field exists on the wire `PatientDto` (only `initialMedicalNotes` free-text). The client
currently augments them behind the `services/patients` seam (the mock persists them; `USE_PATIENTS_MOCK=true`)
and drops them on the real path. Adding the columns lets the client flip the flag to the live endpoints.
- **Proposed shape:** `PatientDto { …, relation: string|null, conditions: string[] }`; same fields accepted on
create/update. Enum for `relation`; `conditions` a stable code list (could also be a normalized child table).
- **Status:** open
## REQ-006 — Avatar / object-storage upload route (nurse & customer) — filed by frontend-phase-2-b3 — 2026-07-02
- **Need:** A multipart image-upload endpoint backed by `IObjectStorage` that returns a stored URL, plus an
`avatarUrl` field on `NurseProfileDto` (and later `CustomerProfileDto`). e.g. `POST api/v1/nurse_profiles/avatar`
(multipart/form-data) → `{ url }`, and persist `avatar_url` on the profile.
- **Why:** The B7 nurse profile bootstrap and the customer profile both take a profile photo. The b3 contract
has no avatar field or upload route, and the client fetch layer is JSON-only (can't send multipart). The
client mocks this behind the `services/profiles` seam (`uploadAvatar` returns an object URL). The real
`profilesClientApi.uploadAvatar` throws `501` until this lands.
- **Status:** open
## REQ-007 — Customer name + preferred-language update — filed by frontend-phase-2-b3 — 2026-07-02
- **Need:** Either add `firstName`/`lastName`/`preferredLanguage` to the `customer_profiles/upsert` body +
`CustomerProfileDto`, or confirm the customer name is only ever set elsewhere (and how). `MeResult` exposes
`firstName`/`lastName` read-only with no update endpoint; `CustomerProfileDto` carries only the emergency
contact.
- **Why:** The customer profile screen edits first/last name + preferred language alongside the emergency
contact. Absent a wire field/endpoint, the client augments name/language behind the `services/profiles` seam
(mock-persisted; the real upsert sends only the emergency contact). Confirm the intended home for these so the
client stops augmenting.
- **Status:** open
## REQ-004 — Confirm multi-role disambiguation (activeRole?) — filed by frontend-phase-1-b2 — 2026-07-02
- **Need:** Confirm whether `MeResult` will gain an `activeRole` (the user's currently-selected actor) for a
user who holds **both** `customer` and `nurse`, or whether the client should keep owning that choice.