Files
baya-monorepo/dev/phases/frontend/frontend-phase-2-b3.md
T
2026-06-28 21:59:59 +03:30

26 KiB
Raw Blame History

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 (auth/OTP/roles, AuthContext) + the backend-phase-3 contract (identity-profiles.md) · Unlocks: frontend-phase-3-b4.md (addresses & geo), frontend-phase-7-b8.md (booking request — needs a patient) Before you start, read ../_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: 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: 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, 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 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 (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. The whole nurse verification pipeline (B3B6, identity/Shahkar/license) is (DEFERRED) to 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 and ../_shared/frontend-conventions-checklist.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 — the visual baseline: ../../../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 (the b3 contract you consume — endpoints, request/response shapes, enums, masking, failure cases), plus the cross-cutting conventions you already follow: api-conventions.md (envelope, snake_case routes, pagination, status codes) and 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 (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() (useMutationPUT …/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() (useMutationPOST/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. 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); 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, 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 faen 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 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 — don't invent rules. Note any reusable pattern in client/CLAUDE.md.
  • Contract: consume ../../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 (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; write ../../shared-working-context/reports/frontend-phase-2-report.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 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.