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

9.5 KiB

Frontend Phase 7 — Booking request flow (C4 + C5 + nurse inbox) — report

Date: 2026-07-09 · Track: frontend · Depends on: f6 (search/nurse profile → the CTA + the nurse's variants), f3 (addresses + map preview), f2 (patients), f0 (money/date utils, services pattern, stepper/status composites) · Consumes: b8 booking-requests.md · Unlocks: f8 (booking detail → the converted handoff) + f9 (checkout → the payment CTA).

What was built

The money-free request phase of the engagement lifecycle — a customer turns a nurse profile into a sent request and both sides close the loop, with server-frozen deadlines and two-stage clinical disclosure made visible.

services/bookingRequests/ (the domain, copies the f0/auth shape)

  • types.tsRequiredCaregiverGender, the full BookingRequestStatus union + TERMINAL_* set + isTerminalBookingRequestStatus, BookingRequestDto (customer/admin full-address vs nurse masked view), BookingRequestListItem, CreateBookingRequestPayload, RejectBookingRequestPayload, the paginated list params, BookingRequestDisplayContext (mock-only display aid), and the BookingRequestsApi seam. Derived from the b8 contract; variantPrice is documented client-augmented (REQ-013).
  • keys.tsbookingRequestKeys.list(role,status,page) / .nurseInbox(...) / .detail(id).
  • constants.tsUSE_BOOKING_REQUESTS_MOCK (true, primary), poll/stale/gc times, page size, the notes/reason limits, and the mock-only deadline windows.
  • apis/mockApi.ts (primary; a shared in-memory state machine, see below), clientApi.ts (real b8 1:1 mapping — action-style snake_case routes, camelCase bodies, ids from the route), index.ts (seam selection by the flag).
  • hooks/useCreateBookingRequest (mutation → seeds the detail cache + invalidates the customer list), useBookingRequest(id, role) (query; polls while non-terminal, stops at terminal/converted via a refetchInterval guard; role drives the mock's nurse-view masking), useNurseRequestInbox (query; light polling), useCustomerRequests (query; for f8 reuse + create/cancel invalidation), useAccept/useReject/useCancel (mutations → invalidate inbox list + detail). Barrel re-exports hooks.

Screens (all RTL/i18n/dark-mode)

  • C4 /bookings/request (customer) — the request form. Patient dropdown (f2; empty → link to /patients), service-variant dropdown (the chosen nurse's services from the f6 profile) + a PriceDisplay of the picked rate, address dropdown (f3) + a read-only map-pin preview of the saved address's coordinates, native date + time-window fields, a first-class 3-way caregiver-gender toggle (خانم/آقا/فرقی ندارد) with a why-line, and a stage-1 notes field with a counter + honesty copy. Defaults are derived during render (no setState-in-effect): the variant to the carried/first, the address to the primary/first. The CTA is gated on all required fields + a client-side same-gender-mismatch block; domain 400 codes map to field/form errors. On success → C5.
  • C5 /bookings/request/[id] (customer) — the awaiting screen. BookingRequestSummaryCard + the 3-step StepperHeader tracker + a status-driven body: pending shows a CountdownTimer to nurseResponseDeadlineAt; accepted swaps the tracker, shows a ✓ badge + a terracotta 30-min CountdownTimer to paymentDeadlineAt + the ادامه پرداخت CTA → /bookings/checkout; rejected / expired / payment-expired / cancelled are terminal cards with a re-request CTA into search; converted routes to booking (f8). Cancel-with-confirm while pending/accepted. Polls until terminal.
  • Nurse inbox /nurse/requests — pending requests, each a card with patient name, Shamsi time, the required-gender chip, a customerNotes preview, and a per-request CountdownTimer. Empty state + light polling. New nurse-shell nav item.
  • Nurse detail /nurse/requests/[id] — the summary + only customerNotes (masked coarse city/district, a disclosure note) + accept / reject-with-reason (a dialog capturing the reason). A stale 409 surfaces a warning + refetch. Both actions invalidate the inbox + detail.
  • /bookings/checkout — an f9 DEFERRED stub (PlaceholderScreen) so the accept CTA doesn't dead-end.

Shared composites (tested)

  • CountdownTimer — a pure countdown to a server-frozen UTC instant; owns its own 1-second tick so only it re-renders (never the page), stops at zero and shows an elapsed label + fires onElapsed once, renders locale digits forced LTR. 4 tests (MM:SS / HH:MM:SS / tick / elapsed+onElapsed via fake timers).
  • BookingRequestSummaryCard — nurse identity + rating, patient, priced service (PriceDisplay when a price is present), address label, Shamsi date/time. Reused by C5 + nurse detail; f8 reuses it too. 4 tests.

Other

  • Routes: BOOKING_REQUEST (now the C4 form), BOOKING_REQUEST_STATUS, CHECKOUT, NURSE_REQUESTS. Icons: requests, payment. i18n: booking namespace fully fleshed + nav.requests, both locales in sync. The prior /bookings/request f6-handoff stub was replaced by the real C4 form.

Now testable, and exactly how (§7 of the phase)

Run npm run dev (mock is primary — no backend needed). Within one browser tab (the mock store is a shared module singleton):

  1. Submit: from a nurse profile (C3) tap درخواست رزرو → C4 pick patient/variant/address/date/time + a gender → ارسال درخواست → land on C5 (tracker step 2 active, response countdown ticking).
  2. Nurse sees it: open /nurse/requests → the new request appears with the gender chip + notes + countdown; open the detail → only the notes (no address/clinical fields).
  3. Accept: tap accept → it leaves the pending inbox (invalidated); navigate back to C5 → step 2 done, step 3 active, ✓ پرستار تایید کرد, the 30-min payment countdown, and ادامه پرداخت → (routes to the checkout stub with request_id).
  4. Reject: on another request, reject with a reason → C5 shows the rejected terminal card + reason.
  5. Expiry: the mock shortens the response window (see below) — once it lapses, a C5 poll (or reopening) shows expired_no_response; after accept, the 30-min window lapsing shows payment_deadline_expired.
  6. Quality: flip fa↔en (dir + strings), dark mode holds; Devtools shows the inbox/detail invalidating on accept/reject and the C5 poll stopping at a terminal status.

What is mocked (and how it swaps to real)

services/bookingRequests is mock-primary (USE_BOOKING_REQUESTS_MOCK = true). b8 is live server-side, but this is a client-side mock for two reasons: (a) every input id — the nurse (f6 search), the patient (f2), the address (f3) — comes from a mock-primary upstream domain, so a real booking_requests/create would reference ids that don't exist in a real DB; (b) the contract DTO omits the variant price the summary renders (REQ-013). The mock is a shared in-memory state machine: one module-level store drives create → the nurse inbox → accept/reject/cancel → the customer C5 poll, with a lazy expiry sweep on every read (the client stand-in for the server's background job), the nurse-view address masking, and the forward-only status/deadline rules. Mock-only deadline windows: the payment window is contract-accurate (30 min); the response window is shortened to 30 min (a stand-in for the server's 24h) so a session can observe the expiry path. The real bookingRequestsClientApi maps the b8 contract 1:1 (the context/role args are mock-only and ignored). Swapping to real is a one-line flag flip once the upstream domains are live and REQ-013 lands — no hook/component change. (Client-side mock; recorded here, not in mocks-registry.md, which tracks backend DI seams.)

Contracts consumed / requests filed

  • Consumed (not edited): dev/contracts/domains/booking-requests.md (b8) — types derive from it. Note: the actual routes are booking_requests/{create,accept/{id},reject/{id},cancel/{id},list,get/{id}} (the phase file's illustrative create_booking_request-style names were superseded by the contract), and the wire uses requestedDate + requestedTimeStart/End (not a single scheduled_start_at).
  • Filed: frontend/requests/for-backend.mdREQ-013 (variantPrice + nurseAvatarUrl on BookingRequestDto) and REQ-014 (variantLabel + patientAge on BookingRequestListItemDto).

Follow-ups for later phases

  • f8 (booking detail): the C5 converted state routes to /bookings today — wire it to the real booking-detail route when f8 lands. f8 should reuse BookingRequestSummaryCard.
  • f9 (checkout): the C5 accept CTA → /bookings/checkout?request_id=… (a stub). f9 builds the C6 summary + escrow + card/BNPL and consumes the accepted request id.
  • Swap to real b8: flip USE_BOOKING_REQUESTS_MOCK=false once search/patients/addresses are live and REQ-013/014 land; verify the nurse-view masking + deadline freezing against the server.

Gate

npm run check green · npm run test:ci green (173 tests, +8) · npm run build green with NEXT_PUBLIC_API_URL set (all five new routes compile + prerender). Without the env var the build fails on the pre-existing "Missing .env variable!" from @/config — it hits the existing /fa/search route identically, so it is environmental, not an f7 defect.