cleanup phases 6
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user