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>
6.6 KiB
Frontend phase 2 (f2-b3) — Onboarding & profiles — report
Track: frontend · Consumes: dev/contracts/domains/identity-profiles.md (backend-phase-3) · Date: 2026-07-02
What was built
Domain services (f0 services/{domain} pattern: types ← contract, keys factory, apis[client+mock+seam], one-hook-per-file, hooks-only barrel)
services/patients— rewritten from the f0 reference stub to the b3PatientDto. Full CRUD seam (list/get/create/update/archive) + realpatientsClientApi(action routespatients/{list,get,create, update,archive}) +patientsMockApi. Hooks:usePatients(staleTime),useCreatePatient,useUpdatePatient(both invalidate lists),useArchivePatient(optimistic remove + rollback + settle-invalidate). Helpersage.ts(age↔birthDate) and the client-augmentedrelation/conditions(REQ-005, mock-persisted).services/profiles— customer + nurse profile. SeamgetCustomerProfile/upsertCustomerProfile/getNurseProfile/upsertNurseProfile/uploadAvatar; real client maps 404→null(no profile yet). HooksuseCustomerProfile/useNurseProfile(queries) anduseUpsertCustomerProfile/useUpsertNurseProfile(setQueryData + invalidate/mefor profile-completion) anduseUploadAvatar.services/nurse— payout bank accounts (kept separate). Seamlist/add/setPrimary/verifyOwnership;iban.ts(ShebaIR+24 validate/normalize + bank-name from the 3-digit code). HooksuseNurseBankAccounts(poll viarefetchIntervalonly while any account is pending),useAddNurseBankAccount,useSetPrimaryBankAccount.deriveBankStatusmapsmatchedNationalId(null→pending, false→mismatch, true→verified).
Shared composites (src/components/…, each with a co-located *.test.tsx)
GenderToggle (required male/female, never defaulted, can't deselect), ConditionChips (multi-select codes),
RelationSelect (radio cards), PatientForm (the A4 form — name/age/gender/conditions/relation, reused
create+edit), PatientCard (E1 card), BankStatusPanel (the three ownership states, masked IBAN, non-accusatory
mismatch). Reused the f0 StepperHeader/StatusChip/PhoneNumberField (not re-implemented). Added --bal-primary-soft
token (both schemes) and 5 AppIcon registry names (edit/archive/bank/camera/warning).
Screens
- A3→A4 onboarding (
(customer)/onboarding/page.tsx) — 2-step stepper: relation → patient form; creates the first patient and lands on Home. - A5 Home (
(customer)/page.tsx, now a client component) — first-login gate: a customer with 0 patients is redirected into onboarding (waits for a settled list so a post-create refetch never bounces back); otherwise the "complete patient record" nudge (+ a profile nudge untilhasCustomerProfile). - E1 patients (
(customer)/patients/page.tsx) — cached list, skeleton + empty states, add/edit dialog (A4 form), soft-archive with confirm. - Customer profile (
(customer)/profile/page.tsx) — name + preferred language + emergency contact (reused phone field). No national-ID field. - Nurse profile (
nurse/profile/page.tsx) — avatar upload + bio + years; unverified/not-bookable placeholder → verification; services/availability correctly deferred (a caption, not a stub). - Nurse bank (
nurse/bank/page.tsx) — IBAN + holder form; renders each account viaBankStatusPanelin its pending/verified/mismatch state; mismatch offers re-enter. - NurseLayout sidebar gains Profile + Bank.
What is now testable and exactly how (npm run dev, mocks default on — no backend needed)
- Onboarding: log in as customer → land on A3 (step 1) → pick a relation → A4; submit without gender → blocked + "gender required"; fill it + submit → lands on Home (A5) with the nudge; the flow doesn't re-trigger (a patient now exists).
- Patients (E1): Patients tab shows the new patient card (relation/name/age·gender/conditions). "+ Add patient"
→ dialog (A4 form) → appears without a full reload (invalidate). Edit → persists. Archive (confirm) → card
disappears (soft,
isActive=false), not hard-deleted. Fresh session → empty state with add CTA. React Query Devtools shows the list cached + invalidated on mutation. - Customer profile: edit name + emergency contact → save → Home profile nudge clears. No national-ID field.
- Nurse profile + bank: log in as nurse → bootstrap profile (avatar + bio) → saves, shows unverified /
not-bookable. Bank settings → enter an IBAN → pending "در حال استعلام" panel → (after ~1 poll) verified
green with masked IBAN (last-4). Enter
IR000000000000000000000000→ mismatch with re-enter CTA. - i18n / RTL: toggle locale → strings flip fa↔en,
dirflips, gender toggle / chips / stepper mirror.
What is mocked client-side + how to make it real
All three services default to USE_*_MOCK = true (real HTTP clients fully wired for a one-line flip). See the
mock registry rows for PatientsApi, ProfilesApi, NurseBankAccountsApi. The b3
endpoints are live; the mocks stay on because of the three filed gaps:
- REQ-005 —
PatientDto.relation+conditions(client-augmented meanwhile). - REQ-006 — avatar/object-storage upload route +
avatarUrl(realuploadAvatarthrows501). - REQ-007 — customer
firstName/lastName/preferredLanguagehome (name is/me-only, read-only today). Once each lands, flip the corresponding flag — no hook/component/call-site change.
Contracts consumed
identity-profiles.md (b3) as the type source: NurseProfileDto, CustomerProfileDto, PatientDto,
NurseBankAccountDto, the enums (gender load-bearing), IBAN masking (last-4), guarded isVerified, tenancy-404.
Gaps filed in requests/for-backend.md (REQ-005/006/007) — no shapes guessed; augmented fields are clearly marked.
Follow-ups for later phases
- f3 (addresses & geo): reuse this profile shell + the
services/{domain}pattern; the A4/E1 sibling address book slots in. - f4 (catalog & service builder): the nurse services-and-prices builder and available-days picker slot onto the B7 profile (both deferred here; a caption marks them).
- f5 (verification): replaces the neutral unverified placeholder on the nurse profile with the real
"not bookable until verified" banner; flips
isVerifiedinside the backend transaction. - f7 (booking): consumes the patient (needs a known
gender) created here. - When REQ-005/006/007 land, flip the three mock flags.