# Frontend status log (append-only) One block per completed frontend phase. Newest at the top. Frontend lane writes here; backend reads for awareness. ## refinement-phase-2 — Auth & role-aware navigation ("only customer side" fix) — 2026-07-13 - **Shipped:** the resolved-vs-pending role fix. `useRoleHydration()` (`services/auth`, `loading|error|ready` over `useMe`) + `RoleGuard` (wraps every private shell; tested) + `AuthAccountError`. Shells now: neutral splash while `/me` loads (never the customer shell), explicit `/me`-failed recovery (never a silent customer fallback), and role-mismatch **redirect** to `resolveRoleDestination` + `guard_denied` toast. `(customer)`/ `nurse`/`admin` guard `expected={APP_ROLES.*}`; `partner` is hydration-only (self-gates via useMyPartnerCenter). i18n `auth.guard_denied`/`account_error_*` (en+fa). Backend (a little): 2 phone-OTP admins added to the demo seeder (`09120000020` super_admin / `09120000021` finance) so `/admin` is reachable via phone-OTP + `useAdminCapabilities` gating is demonstrable. - **Consumes:** the real b2 auth (`/me`, `me/select_role`) — no new contract. `USE_AUTH_MOCK` stays false. - **Mocked client-side:** none new. Partner login-routing deferred (`/partner` reachable by direct nav via the existing partnerCenter mock). - **Gate:** npm run check green · RoleGuard.test.tsx 8/8 · en/fa in sync · server build 0 errors · DemoWorldSeederTests 4/4. - **Requests filed:** yes — **REQ-004 resolved** (client owns active-role); **REQ-038 filed** (a `/me` partner-center-admin signal for partner login-routing). ## frontend-phase-15-b15 — Admin backoffice & partner-center consoles — 2026-07-10 — **MVP COMPLETE** - **Shipped:** the internal **operational cockpit** — the role-gated admin backoffice (desktop sidebar shell) + the separately-scoped **partner-center portal**. Two new domains: **`services/admin`** (config / holidays / audit / support-alerts / RBAC — `usePlatformConfigs`/`useUpdatePlatformConfig`/`useConfigChangeHistory`/ `useHolidays`/`useUpsertHoliday`/`useAuditLogs`/`useSupportAlerts`/`useAssignSupportAlert`/ `useResolveSupportAlert`/`useAdminRoles`/`useGrantRole`/`useRevokeRole`) and **`services/partnerCenter`** (admin management + `useMyPartnerCenter`/`useMySponsoredNurses`/`useMySponsoredBookings`/`useMySettlement` portal reads). **Admin-endpoint additions to 5 existing domains** (the staff lens, not new domains): verification (`useVerificationQueue`/`useVerificationCase`/`useVerificationDocumentUrl`/`useDecideStep`/`useApproveVerification`/ `useRejectVerification`), refunds (`useRefundPreview`/`useInitiateRefund`/`useApproveRefund`/`useRejectRefund`), payouts (`usePayoutBatches`/`usePayoutBatchDetail`/`usePreviewPayoutBatch`/`useRunPayoutBatch`/`useRetryPayout`/ `useRecordTransferReference`), reviews (`useModerationQueue`/`useModerateReview`), tickets (`useAdminTickets`/ `useAdminTicket`/`useAdminTicketThread`/`usePostAdminMessage` — the admin types carry `isInternal`, the user types never do). **Screens:** `/admin` overview + verification (queue + `[nurseId]` case w/ signed-URL `DocumentViewer`, structured credential entry, Approve-only-when-all-passed), tickets (queue + `[id]` thread w/ internal-note composer + ticket-linked **RefundPanel**), payouts (dashboard + preview → **idempotency-keyed run** + `[batchId]` detail w/ retry + transfer-ref reconcile), reviews (moderation publish/hide/reject), config (typed inputs + 0–1 validation + audited-save + history drawer), holidays (`is_bank_closed`), alerts (internal triage assign/resolve), audit (read-only filtered diff), partners (list/create + `[id]` verify/activate/edit + roster/assign, IBAN write-then-masked), roles (**DEFERRED-IF-MISSING**, mock-backed). **Partner portal** (`/partner` scope): home (onboarding state + license + MoR), nurses, bookings, and settlement (**MoR-gated** commission/VAT invoices via `PartnerSettlementRow` + signed-URL PDF; non-MoR → "via Balinyaar" state). New shared/tested `@/components/admin`: `AdminPageHeader`/`AdminEmptyState`/`AdminErrorState`/`AdminPager`/`ConfirmDialog`/`AdminDataTable`/`ConfigRow`/ `AuditLogRow`/`SupportAlertCard`/`PartnerSettlementRow`/`DocumentViewer`/`RefundPanel`/`AdminMessageBubble` (13, 36 tests). Role-gating via **`useAdminCapabilities()`** (fine-grained `roleCodes` off `AuthContext`); `admin` + `partner` i18n namespaces (both locales, incl. پروانه تأسیس / مسئول فنی / نماد اعتماد الکترونیکی / سامانه مودیان). - **Consumes:** dev/contracts/domains/messaging-notifications-admin.md (b15) + verification.md (b6) + refunds-invoices.md (b11) + payouts.md (b13) + reviews-records.md (b14) + config-reference.md (b1). - **Mocked client-side:** `services/admin` (`USE_ADMIN_MOCK`), `services/partnerCenter` (`USE_PARTNER_MOCK`), and the admin methods of verification/refunds/payouts/reviews/tickets — all mock-primary (their real `clientApi` maps the live routes and targets proposed slugs for the gaps). See mocks-registry. - **Gate:** npm run check green (0/0) · npm run test:ci green (325 tests, +36) · npm run build green with NEXT_PUBLIC_API_URL set · i18n admin+partner in sync. - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-029…037: config audit columns, rich audit filters, RBAC endpoints, partner portal split reads + activate/suspend, invoice total, verification nurse-queue + on-demand doc URL + whole-verification approve/reject, refund preview + approve/reject, payout single-preview + holidayShifted + record-transfer-reference, moderation `tagCodes`). ## frontend-phase-14-b15 — Messaging (tickets) & notifications — 2026-07-10 - **Shipped:** the social/communication layer for the customer **and** nurse apps (role decides chrome, not the components; admin lens DEFERRED to f15). Two new domains — **`services/tickets`** (`useMyTickets` / `useTicket` = the whole thread in one `detail(id)`, no message pagination / `useTicketThread` = select over detail / `useOpenTicket` / `usePostMessage` **optimistic, draft-preserving**, reconcile by `clientMessageId`) and **`services/notifications`** (`useNotifications` unread-first / `useUnreadCount` the **polled** bell — count-only, stale-while-revalidate / `useMarkNotificationRead` + `useMarkAllRead` optimistic `setQueryData`). Screens shared by both shells: **My Tickets inbox** (`/support/tickets`, `/nurse/support/tickets`) with prominent `referenceCode` + unread indicator + null-safe linked-entity hint + Contact-support dialog (shows the new `referenceCode`); **thread** (`/…/tickets/[id]`) role-aware bubbles + sticky composer; **notification center** (`/notifications`, `/nurse/notifications`) unread-first, mark-read-on-open + mark-all, deep-links via `notificationDeepLink`. **Notification bell** in the customer TopBar + the nurse shell (subscribes to the poll so only it re-renders). **Emergency banner** + **"Get support / Open ticket"** on the f8 booking detail (`BookingSupportEntry`, reuses the cached booking + nurse-gated care query — no refetch); a support icon in the customer TopBar + a Support item in the nurse sidebar. New shared/tested composites: `MessageBubble`, `TicketListCard`, `EmergencyBanner`, `NotificationRow`, `NotificationBellView`, `ContactSupportDialog` (+ screens); `notificationDeepLink`/`parseNotificationData` unit-tested; `support`/`send` icons; `tickets` + `notifications` i18n namespaces + `nav.support` (both locales). - **Consumes:** dev/contracts/domains/messaging-notifications-admin.md (b15 tickets) + config-reference.md (b1 notifications) + openapi/swagger.v1.json. Real & mapped 1:1 by the client APIs: `POST/GET /tickets`, `GET /tickets/{id}`, `POST /tickets/{id}/messages`; `GET notifications/get_notifications`, `get_unread_count`, `POST mark_notification_read` / `mark_all_read`. - **Critical rules honoured:** `is_internal` is **never** modelled in the user-app types — both API mappers DROP any internal message (server-strip mimic), no internal affordance anywhere; the emergency surface is a **post-confirmation `tel:` playbook only** (nurse-gated care contact, no VoIP seam, never a general phone); the unread **count** polls politely (60s interval + 45s staleTime + refetch-on-focus, auth-gated) and the list is never polled; `data_json` is parsed into a typed union and degrades to no-deep-link. - **Mocked client-side:** `services/tickets` (`USE_TICKETS_MOCK`) — b15 is live and `ticketsClientApi` maps it 1:1, but the linked bookings are mock-primary and the summary lacks `unreadCount`/`lastMessageAt` (REQ-028); `services/notifications` (`USE_NOTIFICATIONS_MOCK`) — b1 is live and mapped 1:1, but nothing dispatches notifications client-side yet. Both default `true`; swap is one flag. See mocks-registry. - **Gate:** npm run check green · npm run test:ci green (66 suites / 289 tests, +32) · production build compiles + type-checks clean (a **pre-existing** "Missing .env variable" prerender guard fails on `/en/addresses` + `/en/nurse/verification/identity` only — unrelated to this phase; sibling pages under the same modified shells prerender fine). 6-dimension adversarial review with per-finding verification. - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-028: `unreadCount`+`lastMessageAt` on the ticket summary, a message author label / masked-name confirmation, a by-booking user ticket lookup, and an optimistic `clientMessageId` idempotency field). ## frontend-phase-13-b14 — Reviews & patient care records — 2026-07-10 - **Shipped:** the last feature-domain phase. Two new domains — **`services/reviews`** (`useNurseReviews` infinite published-only aggregate+list / `useReviewEligibility` / `useMyReviewForBooking` / `useCreateReview` — invalidates eligibility+my-review, NEVER the public list) and **`services/patientRecords`** (`usePatientCareRecord` / `useRecordAccess` / `usePatientHistory` / `useUpdateCareRecord` CUSTOMER-only / `useCreateVisitNote` NURSE-only). Screens: `/bookings/[id]/review` (RatingInput + ReviewTagSelector, gated completed+can_review+1:1, under-review state) + `LeaveReviewCta` on the customer booking detail; C3 nurse profile **reviews tab** (published-only aggregate+count+infinite list); E2 **`/patients/[id]/record`** (4 tabs داروها/روتین/سوابق/وظایف + ownership banner + customer edit + non-leaking access-denied); nurse **`NurseVisitNotesPanel`** (append-only task-checklist+note composer BELOW the f8 EVV banner). New shared composites RatingInput/ReviewTagSelector/VisitNoteCard/PatientHeader (each tested; PatientCard now composes PatientHeader + gained `onOpen`); `usePatient(id)`; new icons; `reviews`+`records` i18n namespaces (both locales). Extended the f8 bookings mock: completed booking **5005** + cross-mock read mockGetBookingForReview. - **Consumes:** dev/contracts/domains/reviews-records.md + openapi/swagger.v1.json (backend-phase-14). REAL & mapped 1:1: `POST bookings/{id}/review`, `GET nurses/{id}/reviews`, `GET`/`POST patients/{id}/care_records`. - **Mocked client-side:** `services/reviews` (`USE_REVIEWS_MOCK`) — eligibility + my-review are REQ-026 gaps, moderation is admin-only/f15 (dev `__mockPublishSubmittedReview`). `services/patientRecords` (`USE_PATIENT_RECORDS_MOCK`) — the visit-note history/append are REAL b14; the family-owned record (meds/routine/tasks) + access check have NO backend (REQ-027). Both default `true`; swap is one flag. See mocks-registry. - **Gate:** npm run check green · npm run test:ci green (all suites, +17) · npm run build green with NEXT_PUBLIC_API_URL set. 6-dimension adversarial review with per-finding verification. - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-026 review eligibility + my-review + masked author; REQ-027 family-owned care record + access + structured taskResults — flags the E2 record has no data-model entity). ## frontend-phase-12-b13 — Nurse earnings & payout history — 2026-07-10 - **Shipped:** the **last money-path frontend phase** — the read-only **nurse earnings** surface. A **new `services/payouts` domain** (types/keys/constants/apis[client+mock]/4 read-only hooks + hooks-only barrel) and three nurse screens under the nurse shell: **`/nurse/earnings`** (net payable balance + four-bucket breakdown + a plain-Persian cadence/dispute-window explainer + a state-segmented earnings list), **`/nurse/ earnings/payouts`** (payout history list), and **`/nurse/earnings/payouts/[id]`** (payout/batch reconciliation detail — money decomposition + masked IBAN + transfer ref + the exact bookings covered). Three shared tested composites: **`EarningsBalanceHeader`** (net balance with an explicit **negative "owed back"** state — never a bare minus), **`EarningsRow`** (three-amount `gross − commission = your payout` breakdown via `PriceBreakdown` + one of four visually-distinct state chips + state affordance: pending shows a **display-only** dispute-window countdown reusing `CountdownTimer`), **`PayoutHistoryRow`** (net transferred + status chip + masked IBAN + failure banner). Deep-links to the f8 booking detail (`/nurse/visits/{id}`); the `earnings` nav item + `PaidOutlined` icon + a `payouts` i18n namespace (82 keys, both locales in sync) added. **Strictly read-only** — no transfer/retry/batch/mutation; a failed payout shows its reason with **no retry** (admin action). Money is IRR digit-strings via the f0 money util (`gross = commission + payout`; the BNPL provider commission never appears); eligibility/dates/amounts are server truth (never computed client-side). - **Consumes:** dev/contracts/domains/payouts.md (backend phase b13) — only `GET api/v1/nurse_payouts/history` maps a live nurse route; the summary/earnings-list/nurse-payout-detail are contract gaps (REQ-025). - **Mocked client-side:** `services/payouts` via `payoutsMockApi` (`USE_PAYOUTS_MOCK=true`) — self-contained, money-correct fixtures covering all four earnings states + all four payout statuses + a `failed` payout + a **negative net balance** (`MOCK_SCENARIO='clawback_heavy'`). Swap is one flip once REQ-025 lands. - **Gate:** npm run check green · npm run test:ci green (242 tests, +3 suites) · npm run build green with NEXT_PUBLIC_API_URL set. - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-025). ## frontend-phase-11-b12 — BNPL installment checkout (D1–D5) — 2026-07-10 - **Shipped:** the **alternate checkout branch** off C6 — a **new `services/bnpl` domain** (types/keys/ constants/invalidations/apis[client+mock]/7 hooks + barrel) and the five wireframe screens under the customer shell, terracotta-financial: **D1 روش پرداخت** (provider chooser off `useBnplOptions`, full-card fall-back), **D2 انتخاب طرح** (single-select plan), **D3 اعتبارسنجی** (کد ملی + prefilled موبایل + consent-gated → approved-with-ceiling / declined+ceiling-exceeded → **card fall-back**), **D4 تایید طرح و قرارداد** (served repayment table + ownership note + contract-gated final action → provider handoff), and **D5 پیگیری اقساط** in the **کیف‌پول** tab (provider-reported outstanding balance + due list + early-pay **provider hand-off**). The handoff (gateway harness → return) settles the down-payment and **routes to the reused f9 confirmation** (`?method=bnpl`), so the booking confirms exactly as the card path. Two shared tested composites: `BnplPlanCard`, `InstallmentScheduleRow` (reused D4+D5). C6's «پرداخت اقساطی» is enabled; the `installments` icon + the `bnpl` i18n namespace (87 keys, both locales in sync) added. - **Consumes:** dev/contracts/domains/bnpl.md (backend phase b12) — `eligibility`/`initiate`/order-by-id map 1:1; options/schedule/wallet-status/by-request-order/D3-KYC are contract gaps. - **Mocked client-side:** `services/bnpl` via `bnplMockApi` (`USE_BNPL_MOCK=true`) — the settle bridge reuses the f9 conversion (a settled BNPL order = a card payment net-of-fee) + seeds a provider-reported Wallet plan. See mocks-registry. Money = served IRR digit-strings end-to-end (mock computes with BigInt; components only format). **D5 is provider-reported status, NOT a Balinyaar ledger; early-pay hands off to the provider.** - **Reviewed:** 6-dimension adversarial multi-agent review + verify pass — 5 confirmed findings, all fixed (return-page window-expired copy/CTA, real getBnplOrder order-id keying, a needless poll fetch, dead `col_*` keys + surfacing `ownership_note` at D1, a dark-mode glyph-contrast token). - **Gate:** npm run check green · npm run test:ci green (223 tests, +9) · npm run build green with NEXT_PUBLIC_API_URL set. - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-022 options+schedule, REQ-023 eligibility KYC inputs, REQ-024 wallet status + customer bookingId + by-request order lookup). ## frontend-phase-10-b11 — Cancellation & refund status (customer) — 2026-07-10 - **Shipped:** the trust-first **exit** — a **new `services/refunds` domain** (types/constants/keys/ invalidations/apis[client+mock]/3 hooks + barrel) and two customer screens: the **cancellation flow** `/bookings/[id]/cancel` (discloses the resolved policy tier + **refund % + fee %** + the Toman refund-vs-fee split + the multi-session refundable/locked breakdown **before** confirm, gated behind an explicit acknowledgement → `useCancelBooking` → refund status) and the read-only **refund status** `/bookings/[id]/refund_status` (3-step **submitted → on-its-way → completed** stepper, the honest BNPL **~7–10-business-day** ETA, `failed`=contact-support with **no retry**, empty state). The customer booking detail now hangs a **Cancel** CTA / **refund section** (`CustomerBookingActions`, reuses the cached booking query). Three shared tested composites: `CancellationPolicyDisclosure`, `RefundStatusCard`, `RefundEtaBanner`. New `refunds` i18n namespace (71 keys, both locales). - **Load-bearing rules honored:** refunds are **admin-approved — the customer never self-issues** (no issue/approve/retry control anywhere; failed = contact-support); the fee/refund % is **disclosed + acknowledged before confirm**; money = IRR digit-strings, **BigInt only** (integer parts-per-10000 — refund+fee and the fee-leg split reconcile to the rial, `PriceBreakdown` dev-guarded); **BNPL surfaced honestly** (the ~7–10-day window + `expected_customer_refund_eta`, money flows *through the provider*, never instant, never Balinyaar→customer) — and **suppressed on a failed refund** (no success-framed "money is on its way"); **per-session** — only un-started sessions refundable, completed-and-verified stay locked; labels are i18n keys off the codes, **never raw enum codes**; polling **only while non-terminal**; cancel invalidates booking detail/lists + **primes** the refund cache. - **Consumes:** dev/contracts/domains/refunds-invoices.md (b11 — `refunds/{id}/status` shape + enums; `refund_status`=requested|approved|processing|succeeded|failed|rejected, `refund_channel`=psp_card| bnpl_revert|manual, `expected_customer_refund_eta` is a date). **Not served by the contract (admin-only):** the customer cancel command, the pre-cancel policy preview + per-session flags, refund-by-booking, and the fee-leg decomposition on the customer status → REQ-019/020/021. - **Mocked client-side:** `services/refunds` via `refundsMockApi` (**USE_REFUNDS_MOCK=true, primary**) — it reads the shared f8 bookings store to resolve the tier by lead time + per-session refundability, flips the booking to `cancelled` (`mockMarkBookingCancelled` stamps the b9 snapshot), and drives card-immediate (`succeeded`) / BNPL-`processing`→`succeeded` refunds with a real ETA + a seeded `failed` refund (5004) and the outside-policy `409`. Added bookings-store seeds 5003 (mid-engagement mixed) + 5004 (cancelled) + two non-seam exports. Real `refundsClientApi` maps `refunds/{id}/status` 1:1 and targets the proposed slugs for the gaps; one flag flip when REQ-019/020/021 land. - **Reviewed:** 4-dimension adversarial review + per-finding verify — 3 confirmed findings, all fixed (failed-state ETA/amount suppression = BNPL-honesty; two missing `no_refund_*` i18n keys; a UTC/local `daysUntil` mismatch that flipped the tier in +offset timezones). - **Gate:** npm run check green · npm run test:ci green (**214 tests, +10**). - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-019 cancel command, REQ-020 policy preview + per-session flags + canonical policy codes, REQ-021 refund-by-booking + customer decomposition). ## frontend-phase-9-b10 — Checkout, card payment & invoice — 2026-07-10 - **Shipped:** the money moment — a **new `services/payment` domain** (types/keys/constants/ apis[client+mock]/invalidations/5 hooks + barrel) and the customer checkout flow: **C6 خلاصه و پرداخت** `/bookings/checkout?request_id=` (acceptance badge, served **reconciling** service-cost/کارمزد/مالیات/مبلغ کل breakdown, verbatim **escrow notice**, payment-window countdown, «ادامه پرداخت ←» with an **idempotency-key-per-attempt**, disabled BNPL seam for f11), the **card states** (initiating → redirect → dev **mock-gateway harness** `/bookings/checkout/gateway` → return `/bookings/checkout/return` with a **backoff pending-callback poll** → succeeded/failed/expired), the **confirmation** `/bookings/checkout/confirmation` («مشاهده رزرو» + «دانلود فاکتور»), and the **invoice** `/bookings/[id]/invoice` (VAT-on-commission line, read-only مودیان state, pdfUrl download or print receipt). Three shared tested composites: `PriceBreakdown`, `EscrowNotice`, `PaymentStatusBadge`. New `payment` i18n namespace (53 keys, both locales). - **Load-bearing rules honored:** money = IRR digit-strings, **BigInt only** (integer parts-per-10000 rate math in the mock — zero floats); the breakdown **must reconcile** (`PriceBreakdown` dev-guards it; rows are served, VAT never derived client-side); **VAT on the commission only** (invoice line labelled accordingly); escrow copy **verbatim** in fa (pinned by test against fa.json), info tone never error; **409 = benign convergence** (re-reads the outcome, never an error toast); poll uses **geometric backoff, stops on terminal + bounded attempts**; success flips the booking **by cache invalidation** (exact keys, no refetch storm). - **Consumes:** dev/contracts/domains/payments.md (b10 — initiate route + `Idempotency-Key` header + `InitiatePaymentResult`; status enum `pending|succeeded|failed`) and the invoice slice of refunds-invoices.md (b11 — `GET invoices/{bookingId}`, `InvoiceDto`), casing verified against swagger.v1.json. **Not served by any contract:** a checkout summary, a client transaction read, and the converted request's booking id → REQ-016/017/018. - **Mocked client-side:** `services/payment` via `paymentMockApi` (**USE_PAYMENT_MOCK=true, primary**) — it is the missing **conversion trigger bridging the f7 ↔ f8 mock stores**: capture converts the request (`converted` + client-augmented `bookingId`), inserts a **confirmed** booking into the f8 store, and auto-issues the b11-shaped invoice, so C5 → C6 → gateway → confirmation → booking detail → invoice runs end-to-end in one session. The dev **mock-gateway page is a test harness, not a product feature**. Real `paymentClientApi`: initiate/invoice = published contract; summary targets the REQ-016 proposed slug; outcome maps `booking_requests/get` (REQ-017). - **Gate:** npm run check green · npm run test:ci green (204 tests, +9) · production build green. - **Requests filed:** frontend/requests/for-backend.md — yes (REQ-016 checkout summary, REQ-017 payment outcome + bookingId, REQ-018 customer invoice availability post-capture). ## 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: `` (client type/size validation, progress %, success/ retry, re-upload-on-reject, server-metadata truth + local-capture mode) and `` (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 `/$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).