# 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, ``, 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('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 «ویزیت … · », 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.