# UI Phase 5 — Booking lifecycle

> **Mission:** the request→track→booking→cancel→review flow is functionally rich but visually generic, and
> hides real defects: a confirm dialog whose **dismiss button carries the destructive action's own label**,
> required-field validation that is **provably dead code**, and a customer requests inbox
> (`useCustomerRequests`) **exported but wired to nothing** — a pending, money-adjacent request is orphaned
> once the user leaves C5. Make the lifecycle legible, calm, and trustworthy: the family always sees *who*
> they're inviting home, always has a way back to a pending request, and every terminal state offers recovery.
>
> **Track:** frontend · **Depends on:** [Phases 0–2](ui-phase-2-shells-and-navigation.md);
> [Phase 4](ui-phase-4-customer-storefront.md) recommended (funnel order) · **Unlocks:**
> [Phase 6](ui-phase-6-checkout-and-money.md) (checkout follows acceptance)
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md)
> and invoke the frontend-designer skill — both are mandatory.**

## 1. Context — where this sits

The booking lifecycle is the emotional core of the product: a family hands a stranger their patient's details
and home address, then waits on two countdowns (nurse response, then a 30-minute payment window). Every
screen already works — services real or contract-shaped, deadlines server-frozen, money display-only — but
the presentation is a default-MUI form stack that undersells the trust story and ships four verified defects
(all re-confirmed in code, 2026-07-16):

1. **The cancel-request dialog is mislabeled.** In `bookings/request/[id]/page.tsx` lines 223–225 the
   dismiss button renders `t('cancel_request')` («انصراف از درخواست») — the destructive action's own label —
   next to the real destructive `cancel_confirm_yes`; users who want to cancel click the button that keeps
   the request.
2. **C4's inline errors are unreachable.** `setAttempted(true)` runs only inside `handleSubmit` (line 128)
   while submit is `disabled={!requiredChosen || …}` (line 439), so `error={attempted && …}` (line 230 etc.)
   never renders; the only feedback for an incomplete form is a silently disabled button.
3. **A pending request is unreachable once you leave C5.** `useCustomerRequests` is exported from
   `services/bookingRequests` and consumed by **zero** pages (grep: only `index.ts` + its own hook file);
   `bookings/page.tsx` lists only post-payment bookings; the bottom nav has no requests entry.
4. **The family never sees who they're inviting home.** C4 fetches `useNurseProfile` (line 61) but renders no
   nurse name, avatar, rating, or badge — the profile is used only for the gender-mismatch check.

Also verified: the bookings list calls `useBookingList('customer')` with no `page` param though the hook
paginates — booking #21 is unreachable; C4 keeps a native Gregorian `type="date"` (line 336), a
`pointerEvents: 'none'` fake-map preview (line 317), and negative-margin stitching (lines 272/424);
`BookingRequestSummaryCard.tsx` builds `whenLabel` (line 60) with no bidi isolation while
`SessionCard.tsx:99` wraps its identical range in `dir="ltr"`; the review page fires `useMyReviewForBooking`
ungated (line 43) though the hook accepts `{ enabled }`; the cancel page pre-defaults its reason to
`'changed_mind'` (line 58); `BookingDetailView.tsx:92` reuses the `unnamed_nurse` *fallback* key as the
nurse field *label*.

**What already exists (do not rebuild):**

- The full functional flow: C4 form, C5 tracker, bookings list + detail, cancel flow with
  `CancellationPolicyDisclosure`, refund status, review page — and the service layer beneath it:
  `services/bookingRequests` (incl. the unused `useCustomerRequests`), `services/bookings` (paginated
  `useBookingList`, sessions/EVV), `services/refunds`, `services/reviews` (`useReviewEligibility`,
  `useMyReviewForBooking` with an `enabled` option), `services/tickets`.
- Phase 1's primitives: CountdownTimer v2 (progress ring), the vertical `StatusTimeline`, StatusChip v2
  (soft tints), the Jalali date picker, `<Money>`, skeleton twins, EmptyState/ErrorState. **Consume these;
  never fork a local variant** (README ownership rules). Phase 4's trust components (`TrustBadge` patterns,
  NurseResultCard v2, rating display).
- The do-not-regress architecture: server-frozen deadlines, two-stage disclosure gate, honest refund copy,
  advisory EVV, shaped skeletons (§5).

## 2. Required reading (do this first)

- [audit/booking-lifecycle.md](audit/booking-lifecycle.md) — the 19 problems, 10 opportunities, and
  keep-list this phase is built from, with file/line evidence.
- Code — read before touching, under `client/src/app/[locale]/(private-routes)/(customer)/bookings/`:
  `request/page.tsx` (C4), `request/[id]/page.tsx` (C5), `page.tsx` (list), `[id]/page.tsx`,
  `[id]/cancel/page.tsx`, `[id]/review/page.tsx`. Components: `booking/BookingDetailView/`,
  `BookingRequestSummaryCard/`, `booking/SessionCard/` (the `dir="ltr"` precedent), `CountdownTimer/`.
  Services: `services/bookingRequests/hooks/useCustomerRequests.ts`,
  `services/bookings/hooks/useBookingList.ts` (it already paginates).
- Phase 1's report (`dev/shared-working-context/reports/ui-phase-1-report.md`) for the exact APIs of
  CountdownTimer v2, StatusTimeline, StatusChip v2, the Jalali picker.
- `.claude/skills/frontend-designer/SKILL.md` — the design contract (invoke the skill, don't just read it).
- Product rules: [product/business/05-booking-and-scheduling.md](../../../product/business/05-booking-and-scheduling.md)
  (request lifecycle, deadlines, two-stage disclosure),
  [product/business/07-cancellation-and-refunds.md](../../../product/business/07-cancellation-and-refunds.md)
  (policy tiers — the disclosure copy is product-mandated),
  [product/business/11-reviews-trust-and-safety.md](../../../product/business/11-reviews-trust-and-safety.md)
  (moderation-before-publish), [product/business/06-evv-and-service-delivery.md](../../../product/business/06-evv-and-service-delivery.md)
  (EVV is advisory, never blocking).

## 3. Scope — build this

### 3.1 C4 request form — a trust-anchored request, not a form stack

`bookings/request/page.tsx`:

- **Sticky nurse identity summary** at the top: avatar, name, rating + review count, `TrustBadge`, gender —
  all already available from the fetched `useNurseProfile` (line 61). The family must always see who they're
  inviting home. Compose from phase-4's card anatomy; compact and sticky on mobile scroll.
- **«چه اتفاقی می‌افتد؟» strip** — a 3-step visual (درخواست → پاسخ پرستار → پرداخت امن) reinforcing the
  money-free promise already in `form_subtitle`; use phase 1's step primitives, no new one-off stepper.
- **Jalali picker + time-window chips** replacing the native Gregorian `type="date"` (line 336) and free
  time fields: phase 1's Jalali date picker plus tappable window presets (صبح ۸–۱۲ / بعدازظهر ۱۲–۱۶ /
  عصر ۱۶–۲۰) with a «زمان دلخواه» custom option that reveals the time fields — presets kill the end≤start
  error class for most users. Nurse-availability hints on the picker are (DEFERRED → needs a backend
  availability read; file a REQ only if you build the seam now).
- **Fix the dead validation:** switch to touched-on-blur field errors **plus** a disabled-CTA explainer — a
  one-line caption under the disabled submit listing what's missing («برای ادامه: انتخاب بیمار، تاریخ»).
  Delete the unreachable `attempted`-only branches. Keep the gender-mismatch inline block exactly as is.
- **Replace the fake-map preview** (lines 315–327, `pointerEvents: 'none'` around `AddressMapPicker`) with a
  compact address row: icon, title, one-line street text, an «تغییر» affordance back to the select — the
  grid-canvas stand-in communicates nothing and eats vertical space.
- **Remove the negative-margin hacks** (`mt: -1.5` line 272, `mt: -2` line 424) — group price-under-select
  and counter-under-notes with real composed containers (Stack spacing), so helper text can't collide.

### 3.2 C5 tracker — a calm wait with a way out

`bookings/request/[id]/page.tsx`:

- **Countdown as the phase-1 progress ring** with humanized framing: the ring shows the fraction of the
  response window remaining ((deadline − createdAt), server fields only — the client never recomputes the
  deadline, §5); above ~10 minutes remaining render «حدود ۳ ساعت» instead of per-second digits, switching to
  precise digits in the final minutes. Add a one-line «نتیجه را اطلاع می‌دهیم» note so users feel safe leaving.
- **FIX the cancel-request dialog defect** (lines 223–225): the dismiss button must never carry the
  destructive label. Adopt a clear confirm convention — destructive: «بله، انصراف از درخواست» (error,
  contained); dismiss: «نه، نگه دار» (text, neutral) — and apply it to every confirm dialog you touch.
- **Terminal-state recovery:** rejected/expired cards currently funnel to generic search. When the rejection
  reason permits (not gender/coverage), offer «درخواست دوباره با زمان دیگر» — C4 reopened prefilled with the
  same nurse/variant/patient/address (extend the query params C4 already accepts from C3) — plus a
  «پرستاران مشابه» entry (same service + area) into existing search. Recover the intent, not from zero.

### 3.3 Customer requests visibility — /bookings becomes the lifecycle home

`bookings/page.tsx`:

- **Segmented tabs:** «در انتظار پاسخ» / «فعال» / «گذشته». The pending tab finally wires the
  exported-but-unused `useCustomerRequests` — rows show the nurse name, requested Shamsi slot, and a **live
  mini-countdown chip** (compact CountdownTimer v2); accepted-awaiting-payment rows make the payment deadline
  the primary CTA. Rows deep-link to C5.
- **Pagination:** the list renders page 1 only while `useBookingList` already accepts `{ page, pageSize }`
  and returns `total`. Add a pager (or load-more, matching the C2 results pattern) with locale digits.
- **Status-accent rows:** a soft `StatusChip` (phase 1's soft-tint system) plus a status-colored
  `borderInlineStart` accent per row so a page of bookings ranks visually without reading every chip.
- **Rows fully tappable** — the whole row navigates (keyboard-focusable, `role`/`aria` correct), not just
  the small button; give the error branch a retry and the empty state a CTA into search.

### 3.4 Booking detail — a hero that answers where/when/who

`components/booking/BookingDetailView/BookingDetailView.tsx` (+ detail page):

- **Next-upcoming-session headline** («ویزیت ۲ · فردا ۹:۰۰» — derived from the served sessions,
  display-only), the visit address, nurse avatar with a support entry (reuse `BookingSupportEntry`), and
  **add-to-calendar** — a client-side `.ics` download for the next session (no backend; Gregorian UTC in the
  file, Shamsi in the UI).
- **Adopt phase 1's vertical `StatusTimeline`** in place of the horizontal `StepperHeader` usage — per-stage
  icons, timestamps where the server provides them, terracotta marker on the current stage, distinct terminal
  branch. The timeline remains server truth: never advance a step client-side.
- **EVV as a presence state:** elevate the existing advisory EVV data into a headline on the detail («پرستار
  در محل است · ورود ۰۹:۰۲») and a compact indicator on the in-progress list row. Keep the tri-state semantics
  (in-range / out-of-range warning / no-GPS neutral) — never error-toned, never blocking.
- **Session list polish:** align session cards to the phase-1 card anatomy; keep SessionCard's `dir="ltr"`
  isolation. Fix the `unnamed_nurse` key misuse (line 92) — add a proper `bd_nurse_label` key and leave
  `unnamed_nurse` as the fallback value it was written to be.

### 3.5 Cancel flow — off-ramps before the kill switch

`bookings/[id]/cancel/page.tsx`:

- **Keep the policy disclosure untouched** — `CancellationPolicyDisclosure` (tier, refund %/fee %,
  reconciled Toman split, per-session refundable/locked, acknowledgement checkbox) is do-not-regress.
- **Add off-ramps above the disclosure:** «تغییر زمان» — opens a support ticket via the existing
  `ContactSupportDialog`/tickets service, pre-categorized `coordination`; real rescheduling is (DEFERRED →
  product decision + backend) — and «گفتگو با پشتیبانی», plus one human line about nurse impact. The
  destructive path stays fully available — these are exits, not obstacles.
- **Do not pre-default the reason:** replace `useState<CancelReasonCategory>('changed_mind')` (line 58) with
  an empty placeholder state; confirm stays disabled until a reason is chosen. Keeps the analytics honest.

### 3.6 Review flow — context and expectations up front

`bookings/[id]/review/page.tsx` + the bookings-list row:

- **Context recap header:** service name, Shamsi visit date, nurse name/avatar (from the cached booking
  detail — no new fetch) so the user knows exactly what they're reviewing.
- **Moderation expectation note up front:** «نظر شما پس از بررسی منتشر می‌شود» before submit, not only in the
  post-submit state — moderation-before-publish is a product rule the UI should disclose early.
- **Post-completion star-strip CTA on the list row:** a completed booking without a review shows a compact
  RatingInput-styled strip on its /bookings row deep-linking into the review page (the eligibility +
  my-review hooks exist; gate the extra queries to completed rows only).
- **Fix the ungated hook call:** pass `{ enabled }` to `useMyReviewForBooking` on the review page (line 43)
  exactly as the detail page gates it — the hook already accepts the option.

### 3.7 Misc verified defects

- **Bidi-isolate `BookingRequestSummaryCard`'s `whenLabel`** (line 60/123): wrap the time-range segment in a
  `dir="ltr"` span with tabular-nums, exactly matching `SessionCard.tsx` line 99. This is a shared, tested
  component — update `BookingRequestSummaryCard.test.tsx` accordingly.
- The «ادامه پرداخت ←» arrow-in-string CTA (fa/en `continue_payment`) is **phase 12's catalog sweep** — note
  it in your report; removing the arrow on strings you already touch is fine, but don't sweep copy-wide here.

## 4. Mocks & seams in this phase

**None introduced.** Every deliverable is client-side over existing seams: `useCustomerRequests`,
`useBookingList` pagination, and the review hooks already exist; the `.ics` file is generated in the browser;
the «تغییر زمان» off-ramp rides the existing tickets service. `bookingRequests`/`bookings`/`reviews` run
real (de-mocked in refinement-phase-4) — build against the real wire, UI mock-tolerant behind the seams.

**REQ posture:** if a deliverable surfaces a genuine backend gap (a rejection-reason code C5's recovery logic
needs, nurse-availability for the time chips), append a REQ to
[for-backend.md](../../shared-working-context/frontend/requests/for-backend.md) — **REQ-001…038 are taken
(tracker verified); number from REQ-039** — and ship degraded-gracefully. Never edit `server/`.

## 5. Critical rules you must not get wrong

From the audit keep-list (all currently true in code — regressions fail this phase):

1. **Server-truth discipline.** `CountdownTimer` never computes or extends a deadline — the ring is
   presentation over server-frozen instants; timelines never advance a step client-side; money stays
   display-only IRR digit-strings via the money utils — never summed or re-split.
2. **Two-stage disclosure is a hard UI gate.** The customer's care-instructions query **never fires**
   (`useCareInstructions` stays enabled-gated to the nurse on confirmed+); keep the BookingDetailView test
   proving it.
3. **Honest cancel/refund copy stays.** The full pre-confirm disclosure, the acknowledgement checkbox, the
   failed-refund state that suppresses all success framing, and the BNPL 7–10-day ETA are product-mandated.
4. **Gender preference stays first-class.** Never silently defaulted; the inline mismatch block stays; the
   culturally-tuned hint copy is untouched.
5. **EVV stays advisory.** Out-of-range is warning-toned, no-GPS neutral, never blocking — the presence
   headline is a positive reframe, not a new gate.
6. **Skeletons stay shaped like content.** Every redesigned layout updates its skeleton twin in the same
   change — no spinner-only regressions.

Design-contract non-negotiables that bite here: i18n keys in **both** catalogs (this phase adds many);
tokens/palette only, never hexes (soft chips use the phase-0/1 `--bal-*-soft` tokens); RTL logical props
(`borderInlineStart` accents, `dir="ltr"` islands for times/digits); dark mode on every new surface; MUI v9
API only; changed shared components keep/gain co-located tests; fetch/cookies rules untouched.

## 6. Definition of Done

On top of the shared [definition-of-done.md](../../phases/_shared/definition-of-done.md):

- [ ] `npm run check` green; `npm run test:ci` green including updated tests for every touched shared
      component (`BookingRequestSummaryCard`, booking composites, new list-row components);
      `en.json`/`fa.json` in sync.
- [ ] Cancel-request dialog: dismiss reads «نه، نگه دار», destructive reads «بله، انصراف از درخواست» — the
      dismiss button never carries a destructive label anywhere in the flow.
- [ ] C4: nurse identity summary renders; blurred-empty required fields show inline errors; the disabled CTA
      explains what's missing; no Gregorian `type="date"`, fake-map preview, or negative-margin stitching.
- [ ] /bookings: three tabs; pending tab lists live requests with mini-countdowns deep-linking to C5; a list
      of >20 bookings is fully reachable via the pager; rows fully tappable with status accents.
- [ ] Booking detail: next-session hero with address + `.ics` download; vertical StatusTimeline (no
      horizontal stepper); EVV presence headline while checked-in.
- [ ] Cancel page: reason select starts empty (confirm disabled until chosen); both off-ramps present;
      `CancellationPolicyDisclosure` byte-identical in behavior.
- [ ] Review page: context recap + up-front moderation note; `useMyReviewForBooking` gated with `enabled`;
      completed list rows show the star-strip CTA.
- [ ] Visual verification on all four axes — `/fa` + `/en` × light + dark — and mobile + desktop for C4, C5,
      /bookings, booking detail (`/fa` mobile first: the primary user).

## 7. How to test (what a human can verify after this phase)

1. From a nurse profile (C3) tap «درخواست رزرو» → C4 shows the sticky nurse card and the 3-step strip. Blur
   the empty patient select → inline error; the disabled CTA lists the missing fields.
2. Pick a date from the **Jalali** picker and tap the «صبح» chip → times fill; choose «زمان دلخواه» → custom
   time fields appear. The address select shows a compact text row, not a grid canvas.
3. Submit → C5 shows the countdown progress ring with «حدود …» framing. Tap «انصراف از درخواست» → the
   dialog's keep-button reads «نه، نگه دار» and keeps the request; «بله، انصراف از درخواست» cancels it.
4. Leave C5 → /bookings «در انتظار پاسخ» tab shows the pending request with a live mini-countdown; the row
   returns to C5. Reject/expire a request (dev sim) → the terminal card offers «درخواست دوباره با زمان
   دیگر» (C4 opens prefilled) and «پرستاران مشابه».
5. With >20 bookings seeded, page 2 is reachable and booking #21 opens. Rows are tappable end-to-end; each
   carries a soft status chip + matching inline-start accent.
6. Open an active booking → hero shows «ویزیت … · <Shamsi>», the visit address, nurse avatar, support entry,
   a working `.ics` download, and a vertical timeline. Check a nurse in (dev EVV) → detail and list row show
   «پرستار در محل است · ورود …».
7. Start a cancellation → reason select is empty and confirm disabled; the off-ramps open a support ticket /
   support chat; completing the flow shows the unchanged policy disclosure and acknowledgement gate.
8. Open a completed booking's /bookings row → star-strip CTA → review page shows the service/date/nurse recap
   and «نظر شما پس از بررسی منتشر می‌شود» before submit. Repeat 1–8 spot-wise on `/en` (LTR) and in dark
   mode: `BookingRequestSummaryCard` time ranges read start–end both directions; no stock-MUI colors appear.

## 8. Hand off & document (close the phase)

- Update `client/CLAUDE.md` **Project Structure**: the /bookings entry (tabs + wired `useCustomerRequests` +
  pagination), the C4/C5 descriptions, and any new shared components under `components/(booking/)`.
- Write the report at `dev/shared-working-context/reports/ui-phase-5-report.md`: what changed per scope item,
  the defects fixed (dialog labels, dead validation, orphaned inbox, ungated hook, bidi label), REQs filed
  (REQ-039+, appended to [for-backend.md](../../shared-working-context/frontend/requests/for-backend.md) in
  the standard entry shape — or "none"), and the phase-12 note (arrow-in-string CTA sweep).
- Save a memory note per operating-rules §8: the lifecycle redesign decisions (tabs model, recovery paths,
  presence state), the confirm-dialog labeling convention now in force, and what phase 6 (checkout) should
  know about the accepted-request → payment handoff surfaces you touched.
