236 lines
21 KiB
Markdown
236 lines
21 KiB
Markdown
# Frontend status log (append-only)
|
|
|
|
One block per completed frontend phase. Newest at the top. Frontend lane writes here; backend reads
|
|
for awareness.
|
|
|
|
<!-- TEMPLATE — copy for each phase
|
|
## frontend-phase-N-bM — <Title> — <YYYY-MM-DD>
|
|
- **Shipped:** <screens/flows/services/hooks/components in one or two lines>
|
|
- **Consumes:** dev/contracts/domains/<domain>.md (backend phase bM)
|
|
- **Mocked client-side:** <which services are mocked pending which backend phase>
|
|
- **Gate:** npm run check green / tests green
|
|
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
|
|
-->
|
|
|
|
## frontend-phase-8-b9 — Booking detail, sessions & nurse EVV — 2026-07-10
|
|
- **Shipped:** the post-payment engagement — a **new** `services/bookings` domain (the sibling of
|
|
`bookingRequests`, NOT a rename): types/keys/constants/apis[client(1:1 b9)+mock+serverApi]/8 hooks +
|
|
barrel, plus the `evv/locationProvider.ts` **ILocationProvider** GPS seam. Screens: customer **رزروها
|
|
list** `/bookings` + **booking detail** `/bookings/[id]` (BookingDetailView, customer view), nurse
|
|
**ویزیت امروز** `/nurse/visits` (today-sessions EVV feed) + **nurse booking detail** `/nurse/visits/[id]`
|
|
(EVV controls + gated care). Seven shared tested composites under `src/components/booking/`:
|
|
`BookingDetailView`, `BookingStatusTimeline` (server-truth 7-status), `SessionList`→`SessionCard`
|
|
(per-session schedule/status/EVV CTA), `EvvStatusBanner` (advisory in/out-of-range/no-gps), `CareInstructionsCard`,
|
|
`BookingMoneySummary`, + `useEvvController`. i18n `booking` extended (`bstatus_*`/`sstatus_*`/`evv_*`/`care_*`/
|
|
`money_*`/`list_*`) both locales; new icons (check_in/out, gps, clinical, medication, emergency, lock) +
|
|
`--bal-secondary-soft` token.
|
|
- **Load-bearing rules honored:** **two-stage disclosure is a UI gate** — `useCareInstructions` is
|
|
`enabled` only for the assigned-nurse view on a `confirmed`+ booking; the customer NEVER fires it (proven
|
|
by test). **EVV mismatch/GPS-denial is advisory, never a block** — out-of-range check-in still succeeds
|
|
(warning-tokened banner, not error); denial still submits. **Timeline = server truth** (never advanced
|
|
client-side); **money display-only** (gross/commission/payout rendered as sent, never summed/re-split;
|
|
`payoutEligibleAt` never recomputed); single-visit renders one session row through the same card; EVV
|
|
mutations **invalidate** detail+sessionEvv+today+list.
|
|
- **Consumes:** dev/contracts/domains/bookings-evv.md (b9) + swagger `BookingDetailDto`/`BookingSessionSummaryDto`/
|
|
`VisitVerificationDto`/`CareInstructionsDto` — `services/bookings/types.ts` derives from these 1:1.
|
|
- **Mocked client-side:** `services/bookings` via `bookingsMockApi` (**USE_BOOKINGS_MOCK=true, primary**) —
|
|
seeds 2 confirmed bookings (one 3-session, one single-visit) + care + a check-in/out EVV state machine,
|
|
because a real booking only exists after `bookings/convert` runs on a paid request and both upstreams
|
|
(bookingRequests mock, card capture b10) aren't real client-side yet. Real `bookingsClientApi` maps the
|
|
routes 1:1; swap is one flag. Also the **ILocationProvider** GPS seam (`NEXT_PUBLIC_EVV_MOCK_GPS`
|
|
in_range|out_of_range|denied|off) — the first frontend seam recorded in mocks-registry.
|
|
- **Gate:** npm run check green · npm run test:ci green (195 tests, +22). Added a committed
|
|
`NEXT_PUBLIC_API_URL` default in `jest.setup.ts` (first test to render a service-hook component pulled
|
|
`@/config` at import).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-015: confirm the booking/session/EVV enum
|
|
string codes + the `checkInAddressMatch` tri-state semantics the client unions assume).
|
|
|
|
## frontend-phase-7-b8 — Booking request flow (customer request + nurse inbox) — 2026-07-09
|
|
- **Shipped:** the money-free request phase — `services/bookingRequests` (types/keys/constants/apis[client+
|
|
mock]/hooks + barrel) and screens **C4** `/bookings/request` (patient/variant/address/date+time + a
|
|
first-class 3-way caregiver-gender toggle + stage-1 notes; client-side validation gates the CTA; domain
|
|
400s surface as field/form errors; reuses f2 patients, f3 addresses + map preview, f4/search variants),
|
|
**C5** `/bookings/request/[id]` (summary card + 3-step tracker + polled status; response countdown →
|
|
accept flips to a 30-min payment countdown + checkout CTA / reject/expire/cancel/converted terminal
|
|
cards; cancel-with-confirm), the **nurse inbox** `/nurse/requests` (+ nurse nav item) and **detail**
|
|
`/nurse/requests/[id]` (only `customerNotes` + masked city/district; accept / reject-with-reason
|
|
invalidate inbox+detail), and a `/bookings/checkout` f9 stub. Two shared tested composites:
|
|
`CountdownTimer` (owns its 1s tick — only it re-renders; stops at zero), `BookingRequestSummaryCard`.
|
|
i18n `booking` (fully fleshed) + `nav.requests` in both locales.
|
|
- **Load-bearing rules honored:** deadlines are **server-frozen UTC** (client renders, never recomputes);
|
|
`required_caregiver_gender` is explicit + client-blocks a same-gender mismatch; two-stage disclosure (the
|
|
nurse UI never renders address/clinical fields — `get(id,'nurse')` masks); polling **stops** on a
|
|
terminal/`converted` status.
|
|
- **Consumes:** dev/contracts/domains/booking-requests.md (b8) — routes `booking_requests/{create,accept/{id},
|
|
reject/{id},cancel/{id},list,get/{id}}`, wire camelCase, action-style. `services/bookingRequests/types.ts`
|
|
derives from it.
|
|
- **Mocked client-side:** `services/bookingRequests` via `bookingRequestsMockApi` (**USE_BOOKING_REQUESTS_MOCK
|
|
=true, primary**) — a shared in-memory state machine so create → nurse inbox → accept/reject → C5-poll →
|
|
lazy expiry all demo end-to-end (b8 is live, but its inputs — search/patients/addresses — are themselves
|
|
mock-primary; and the DTO omits `variantPrice`, REQ-013). Real `bookingRequestsClientApi` maps the b8
|
|
contract 1:1; swap is one flag. Recorded in the phase report (not mocks-registry — backend DI seams only).
|
|
- **Gate:** npm run check green · npm run test:ci green (173 tests, +8) · npm run build green with
|
|
NEXT_PUBLIC_API_URL set (the only prerender failure without env is the pre-existing "Missing .env
|
|
variable!" — hits the existing `/fa/search` too, unrelated to f7).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-013 variantPrice on the DTO + nurse avatar;
|
|
REQ-014 inbox list-item variantLabel + patient age).
|
|
|
|
## frontend-phase-6-b7 — Search & discovery (find a verified, same-gender nurse) — 2026-07-09
|
|
- **Shipped:** the family discovery slice — `services/search` (types/keys/constants/apis/hooks + a shared
|
|
`filterParams.ts` C1↔C2 URL serializer) and screens **C1** `/search` (reused category grid + f3 region
|
|
picker + prominent same-gender facet + Toman price + live-count CTA; `useSearchFilters` colocated
|
|
controller with debounced price), **C2** `/search/results` (rating-sorted `NurseResultCard` list, all
|
|
four states incl. "relax filters" empty, load-more), **C3** `/search/nurse/[nurseId]` (TrustBadge + نظام
|
|
پرستاری badges, attribute chips, `ServicePriceRow` services, latest review, "درخواست رزرو" handoff).
|
|
Two shared tested components: `NurseResultCard`, `ServicePriceRow`. `/bookings/request` = f7 handoff stub.
|
|
i18n `search` (filled) + `booking` (seeded) in both locales. Reused f4 category grid, f5 TrustBadge, f3
|
|
geo picker, f0 money util — none rebuilt.
|
|
- **Headline caching:** the **filter object IS the query key** — reverting to a prior filter set is a cache
|
|
hit with zero network (keepPreviousData avoids flashing); price input debounced.
|
|
- **Consumes:** dev/contracts/domains/search.md (b7) + b6 trust badge / b5 variant reads.
|
|
- **Mocked client-side:** `services/search` via `searchMockApi` (**USE_SEARCH_MOCK=true, primary**) — b7's
|
|
index row + b5/b6 reads don't yet expose nurse name/avatar/distance or an aggregated profile
|
|
(name/bio/specialties/services list/latest review). Real `searchClientApi` maps what exists; swap is one
|
|
line once REQ-012 lands. Recorded in the phase report (not mocks-registry — that's for backend DI seams).
|
|
- **Gate:** npm run check green · npm run test:ci green (165 tests, +8). `npm run build` compiles + types
|
|
clean; prerender fails only on the pre-existing f5 `/nurse/verification` "Missing .env variable!" (needs
|
|
env set — unrelated to f6).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-012: search row name/avatar/distance +
|
|
`GET nurses/{id}/profile` aggregation).
|
|
|
|
## frontend-phase-5-b6 — Nurse verification flow (trust engine) — 2026-07-09
|
|
- **Shipped:** `services/verification` domain (types/keys/constants/validation/apis[client+mock(primary)+seam]/
|
|
hooks/index) — ONE cached `status()` query drives B3+B6, every mutation invalidates it. The nurse
|
|
verification route subtree `nurse/verification/{page(B3),identity(B4),credentials(B5),review(B6)}` +
|
|
co-located `VerificationChecklist` / `verificationSteps` (data-driven step rendering, synthetic mobile step).
|
|
Two shared components with tests: `<DocumentUpload>` (client type/size validation, progress %, success/
|
|
retry, re-upload-on-reject, server-metadata truth + local-capture mode) and `<TrustBadge>` (verified/
|
|
unverified/expired off `--bal-*`, reused by f6). Publish gate wired on `nurse/services` (`PublishGate` —
|
|
disabled until `approved`); trust badge on the nurse profile (own state from `ownBadgeState`). 6 new AppIcons,
|
|
4 route constants, `verification` i18n namespace (123 keys) in both locales. Honest copy: manual steps
|
|
never claim an automated authority check; a step reads "تاییدشده" only when `passed`.
|
|
- **Consumes:** dev/contracts/domains/verification.md + openapi/swagger.v1.json (backend-phase-6). Wire is
|
|
**camelCase**; action-style routes under `api/v1/nurse_verification/*` + `api/v1/nurses/{id}/trust_badge`.
|
|
- **Mocked client-side:** `services/verification` via `verificationMockApi` behind `USE_VERIFICATION_MOCK`
|
|
(**default true** — runs the whole journey standalone incl. a dev-only admin-decision sim, since b6 isn't
|
|
reachable here). One-line swap to `verificationClientApi`. See mocks-registry.
|
|
- **Gate:** npm run check green · npm run test:ci green (157 tests, +9) · en/fa in sync (123 verification keys).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-011 nurse credential-details endpoint).
|
|
|
|
## frontend-phase-4-b5 — Catalog browse (Home A5) & nurse service builder (B7) — 2026-07-05
|
|
- **Shipped:** `services/catalog` domain (types/keys/constants/apis[client+mock+seam]/hooks/index) — the b5
|
|
catalog skeleton + nurse pricing layer. Hooks: `useServiceCategories`, `useCategoryOptionGroups` (both
|
|
**session-cached reference data**, Infinite `staleTime`), `useMyVariants` (paginated, self-scoped),
|
|
`useCreateVariant`/`useUpdateVariant`/`useSetVariantActive` (mutations invalidate `catalogKeys.myVariantsLists()`;
|
|
update `setQueryData`s the row). Shared composites (each tested): **`CategoryTile`** (data-driven Home tile +
|
|
builder `selected` state), **`PriceDisplay`** (money-util Toman + i18n unit label + unit-aware estimated total),
|
|
**`VariantCard`** (offering card, active/deactivated distinction, no delete). Money util gained `tomanToRial`
|
|
(field-boundary Toman→IRR) + `multiplyIrr` (integer-safe estimate). Screens: **customer Home (A5)** — greeting +
|
|
avatar, search bar (navigates toward f6 `/search`, results deferred), **data-driven category grid**
|
|
(loading/empty/error), patient nudge (reuses cached f2 `usePatients`, no new fetch); **nurse Services & prices
|
|
(B7)** at `/nurse/services` (new sidebar tab) — offerings list (active/inactive, edit, soft deactivate w/ confirm,
|
|
reactivate, empty/skeleton) + **3-step variant builder** (category → required/optional options → price+unit+duration;
|
|
required-group gate; Toman→IRR digit-string submit; live unit-aware total; editable auto `displayName`; inline
|
|
`409` duplicate warning; locked-category edit form). Added `catalog`/`services`/`search` i18n namespaces + `home`
|
|
additions + `nav.services` (both locales); 7 icons (`services`,`category`,`elderly`,`post_surgery`,`infant`,
|
|
`chronic`,`companionship`); routes `SEARCH`,`NURSE_SERVICES`; deferred `/search` placeholder stub (→ f6).
|
|
- **Consumes:** dev/contracts/domains/catalog.md (backend-phase-5). Routes `api/v1/catalog/{categories,option_groups}`,
|
|
`api/v1/nurse_variants/{create,update/{id},set_active/{id},list,get/{id}}`. Wire camelCase; `price_unit` enum;
|
|
IRR-string money; `409` duplicate listing / `400` missing required dimension.
|
|
- **Mocked client-side:** `services/catalog` via `catalogMockApi` behind `USE_CATALOG_MOCK` (default `true`) — seeds
|
|
the 5 real b5 categories + representative option groups (incl. a cross-category one) + the `409`/`400` rules;
|
|
variant store seeded **empty** so the offerings empty-state demos. Real `catalogClientApi` wired for a one-line
|
|
flip. See mocks-registry + the report (note: the mock seeds option groups the fresh backend does not — an admin
|
|
authors them).
|
|
- **Gate:** npm run check green · npm run test:ci green (147 tests, +18 across 4 suites: catalog components + money)
|
|
· npm run build green with NEXT_PUBLIC_API_URL set (routes /nurse/services, /search generated; home prerenders).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-010 confirm the list pagination query-param name
|
|
`pageSize` vs the doc's `page_size`).
|
|
|
|
## frontend-phase-3-b4 — Addresses, map picker & nurse coverage areas — 2026-07-03
|
|
- **Shipped:** three domain services — `services/geography` (cached province→city→district reference lookups;
|
|
**Infinity `staleTime`** + shared `geographyKeys`; `useProvinces`/`useCities`/`useDistricts`; seam+mock+client),
|
|
`services/addresses` (address book CRUD + set-primary; single-primary invariant; every mutation invalidates
|
|
the list; `useAddresses`/`useCreateAddress`/`useUpdateAddress`/`useDeleteAddress`/`useSetPrimaryAddress`),
|
|
`services/serviceAreas` (coverage add/remove; `areaExists` dup-guard; `useServiceAreas`/`useAddServiceArea`/
|
|
`useRemoveServiceArea`). Shared composites (`src/components/geography/`, each tested): `CascadingRegionSelect`
|
|
(drives the cached cascade), `AddressMapPicker` (map-pin **stand-in** emitting real lat/lng), `AddressForm`,
|
|
`AddressCard`. Screens: **customer address book** (`/addresses`, reached from a profile-hub link — cascade +
|
|
map pin dialog, set-primary, delete, empty/skeleton), **nurse coverage editor** (`/nurse/coverage`, new
|
|
sidebar tab — chips, whole-city/specific-district scope toggle, inline duplicate block + 409, "won't appear
|
|
in search" empty warning). Added `geo`/`address`/`coverage` i18n namespaces + `nav.coverage` (both locales);
|
|
`location`/`delete`/`coverage` icons; routes `ADDRESSES`/`NURSE_COVERAGE`.
|
|
- **Consumes:** dev/contracts/domains/geography-addresses.md (backend-phase-4). Routes `api/v1/geo/{provinces,
|
|
cities,districts}`, `api/v1/customer_addresses/{list,create,update,set_primary,delete}`, `api/v1/
|
|
nurse_service_areas/{list,add,remove}`. Wire camelCase; geo query params snake_case; 409 on duplicate coverage.
|
|
- **Mocked client-side:** `services/geography` (`USE_GEOGRAPHY_MOCK`), `services/addresses` (`USE_ADDRESSES_MOCK`),
|
|
`services/serviceAreas` (`USE_SERVICE_AREAS_MOCK`) — all default `true`; real clients wired for a one-line flip.
|
|
The `AddressMapPicker` is a stand-in (no real map tiles). See mocks-registry + the report.
|
|
- **Reviewed:** 5-dimension adversarial review → 3 findings fixed (map marker RTL transform; `page_size`→`pageSize`
|
|
pagination casing on the real list calls; coverage "districts" dead-end on a district-less city).
|
|
- **Gate:** npm run check green · npm run test:ci green (129 tests, +17 across 5 suites) · npm run build green
|
|
with NEXT_PUBLIC_API_URL set (routes /addresses, /nurse/coverage generated).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-008 accept the map pin on address create/update,
|
|
REQ-009 `provinceId` on `CustomerAddressDto` for edit prefill).
|
|
|
|
## 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`) —
|
|
the username/password stub is gone; A1/A2 customer login + B1/B2 nurse switch (one OTP flow parameterised
|
|
by intended role) at `/login`; the **role router** (`RoleRouter` + pure `resolveRoleDestination`) →
|
|
customer→family, nurse→nurse app, no-role→`/select-role`, admin→admin console (splash while `/me` loads,
|
|
no wrong-shell flash); `SelectRole` screen at `/select-role`; **silent token refresh** in the fetch layer
|
|
(single-flight `attemptTokenRefresh`, one retry on 401); widened `AuthState` (roles via `SessionUser`,
|
|
hydrated from `/me` by `useSessionRoleSync` in the private layout); `auth` i18n namespace + `common.brand*`
|
|
in both locales.
|
|
- **Consumes:** dev/contracts/domains/identity-auth.md + openapi/swagger.v1.json (backend-phase-2). Wire is
|
|
**camelCase**; routes `api/v1/auth/{request_otp,verify_otp,refresh,logout}`, `api/v1/me`, `api/v1/me/select_role`.
|
|
- **Mocked client-side:** `services/auth` via `authMockApi` behind `USE_AUTH_MOCK` (**default false** — b2 is
|
|
live; flip true for offline dev, dev code `123456`). See mocks-registry.
|
|
- **Gate:** npm run check green · npm run test:ci green (95 tests, +23) · npm run build green with
|
|
NEXT_PUBLIC_API_URL set. Fixed the jest `@/`→`src` alias (was `<rootDir>/$1`).
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-002 OTP length/expiry, REQ-003 verify
|
|
error codes + lockout retry-after, REQ-004 multi-role `activeRole?`).
|
|
|
|
## frontend-phase-0 — Foundations: app shells, design system & data/contract patterns — 2026-07-02
|
|
- **Shipped:** 3 actor shells (customer bottom-nav / nurse / admin sidebar) + role-aware routing under
|
|
`(private-routes)`; `useActorRole`; the `services/{domain}` reference (`patients`, mocked behind a
|
|
seam) with deliberate Query caching + invalidation; `lib/api/types.ts` (envelope/pagination); money +
|
|
Shamsi-date utils; shared composites `OtpInput`/`PhoneNumberField`/`StepperHeader`/`StatusChip`/
|
|
`PlaceholderScreen` (each tested); i18n `nav`/`common`/`shell`/`patients` in both locales. Removed the
|
|
demo scaffolding; fixed the `BottomBar` pathname bug.
|
|
- **Consumes:** dev/contracts/conventions/* + openapi/swagger.v1.json (b0 = ping only). No feature
|
|
contract consumed yet.
|
|
- **Mocked client-side:** `services/patients` via `patientsMockApi` (USE_PATIENTS_MOCK=true) — template
|
|
for f1+. Swap is one line once real endpoints land.
|
|
- **Gate:** npm run check green · npm run test:ci green (72 tests) · npm run build green with
|
|
NEXT_PUBLIC_API_URL set.
|
|
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-001).
|