Files
baya-monorepo/dev/post-phase/ui/ui-phase-5-booking-lifecycle.md
T
2026-07-17 13:22:04 +03:30

281 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 02](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 223225 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 315327, `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 223225): 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 710-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 18 spot-wise on `/en` (LTR) and in dark
mode: `BookingRequestSummaryCard` time ranges read startend 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.