Files
baya-monorepo/archive/build-chain/working-context/frontend/STATUS.md
T
2026-08-02 18:48:32 +03:30

45 KiB
Raw Blame History

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 + 01 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 (D1D5) — 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 ~710-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 ~710-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-processingsucceeded 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), SessionListSessionCard (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 gateuseCareInstructions 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/CareInstructionsDtoservices/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 setQueryDatas 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_sizepageSize 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).