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.ts—RequiredCaregiverGender, the fullBookingRequestStatusunion +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 theBookingRequestsApiseam. Derived from the b8 contract;variantPriceis documented client-augmented (REQ-013).keys.ts—bookingRequestKeys.list(role,status,page)/.nurseInbox(...)/.detail(id).constants.ts—USE_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/convertedvia arefetchIntervalguard;roledrives 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'sservicesfrom the f6 profile) + aPriceDisplayof 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-stepStepperHeadertracker + a status-driven body: pending shows aCountdownTimertonurseResponseDeadlineAt; accepted swaps the tracker, shows a ✓ badge + a terracotta 30-minCountdownTimertopaymentDeadlineAt+ 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, acustomerNotespreview, and a per-requestCountdownTimer. Empty state + light polling. New nurse-shell nav item. - Nurse detail
/nurse/requests/[id]— the summary + onlycustomerNotes(masked coarse city/district, a disclosure note) + accept / reject-with-reason (a dialog capturing the reason). A stale409surfaces 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 + firesonElapsedonce, 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 (PriceDisplaywhen 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:bookingnamespace fully fleshed +nav.requests, both locales in sync. The prior/bookings/requestf6-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):
- 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).
- 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). - 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). - Reject: on another request, reject with a reason → C5 shows the rejected terminal card + reason.
- 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.
- 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 arebooking_requests/{create,accept/{id},reject/{id},cancel/{id},list,get/{id}}(the phase file's illustrativecreate_booking_request-style names were superseded by the contract), and the wire usesrequestedDate+requestedTimeStart/End(not a singlescheduled_start_at). - Filed:
frontend/requests/for-backend.md— REQ-013 (variantPrice+nurseAvatarUrlonBookingRequestDto) and REQ-014 (variantLabel+patientAgeonBookingRequestListItemDto).
Follow-ups for later phases
- f8 (booking detail): the C5
convertedstate routes to/bookingstoday — wire it to the real booking-detail route when f8 lands. f8 should reuseBookingRequestSummaryCard. - 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=falseonce 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.