cleanup phases 6

This commit is contained in:
hamid
2026-08-02 18:48:32 +03:30
parent e2db97392a
commit 51e86a1e5f
239 changed files with 118 additions and 70 deletions
@@ -0,0 +1,152 @@
# UI Phase 4 — Customer Storefront — Report (2026-07-18)
## What was built
**Home (A5)**`HomeScreen.tsx`:
- The dead free-text search bar (`?q=` silently discarded by C1) replaced with a tappable faux-input
(`ButtonBase`) that routes straight to C1. Decision: the search index has no text column, variant names
aren't client-queryable, and the only matchable dataset client-side (56 cached category names) is
already better served by the category grid directly below — a half-working text field over-promises
where trust matters most. The upgrade path is documented in the component's JSDoc and filed as REQ-041.
- A compact one-line `TrustStrip` (escrow · verified nurses · support) under the greeting.
- The patient-record `NudgeCard` is now gated on a derived completeness signal (a patient with no
recorded conditions) instead of rendering forever, and is dismissible for the session (a module-scoped
flag — survives client-side navigation, resets on a hard reload; deliberately not a cookie/localStorage
write, since it's ephemeral UI state, not app/auth state).
- A `RebookRow` — up to 2 "رزرو دوباره با …" cards sourced from `useBookingList('customer')`, deduplicated
by nurse, each resolving its `nurseId` via a per-card `useBookingDetail` call (the list row doesn't carry
`nurseId`) and deep-linking to the nurse's C3 profile. Renders nothing when there's no booking history.
- The pre-existing `isError` retry branch on `usePatients()` was found **already correct** in the current
code (the audit's finding predates it) — verified, not re-fixed.
**Search (C1)**`SearchScreen.tsx` + `useSearchFilters.ts`:
- The native `<input type="date">` replaced by a Jalali day-chip strip (`JalaliDatePicker` `chips` variant,
extended with optional `todayLabel`/`tomorrowLabel` props for «امروز»/«فردا») plus a calendar-icon entry
into the full Jalali grid (a `Popover`) for later dates — intent-only semantics unchanged.
- The inline `ToggleButtonGroup` gender facet replaced by the shared `GenderToggle`, extended with an
opt-in `allowAny` mode (a discriminated-union prop shape — the default booking-context contract is
byte-for-byte unchanged for every other caller).
- The live-count CTA is now a `StickyActionBar` (new shared primitive) — never a tappable "مشاهده ۰
پرستار": at zero results it shows a non-CTA message with a relaxation hint instead of the button.
- `useSearchFilters` now hydrates **every** field from the initial URL (`searchParamsToFilters`), not just
`category_id` — a full filter set carried back from a C2 recap chip rehydrates C1 completely. A
client-only `province_id` query param (not part of `NurseSearchFilters`/the search cache key) carries the
province id so `CascadingRegionSelect`'s city dropdown can prefill without a server round trip.
**Results (C2)**`results/page.tsx`:
- A tappable filter-recap chip row (category · region · gender · price) under the count — every chip
performs the same navigation: back to C1 with the **entire current query string** (C2's URL is already a
superset of everything C1 set, including `province_id`/`date`), so hydration is exact.
- The dead single-option sort `<TextField select>` replaced with a static "مرتب‌شده بر اساس امتیاز" caption.
- Skeleton twins (`NurseResultCard.Skeleton`) were already wired in the current code; updated to match the
v2 card anatomy.
**`NurseResultCard` v2** (this phase owns it) — rebuilt as the four-second decision unit:
- Added: a service/variant label (falls back to the category name via a page-supplied `serviceLabel` prop
until REQ-040's `variantDisplayName` lands), a quiet nurse-gender chip, the completed-visits count
(«N ویزیت موفق», already-served data that was fetched and never rendered), an optional one-line top-review
tag (`topReviewTag`, REQ-040), and a tappable `TrustBadge` (now passes `nurseId`).
- `NurseResultCardSkeleton` updated to the new anatomy (an extra meta-row).
**Nurse profile (C3)**`nurse/[nurseId]/page.tsx`:
- Header now shows the completed-visits count (served, previously unrendered). No gender chip — the
public profile DTO doesn't serve `nurseGender` (the client's `'female'` stub is explicitly a
placeholder); filed as REQ-042 rather than rendering it.
- New `VerificationSection` renders the shared `VerificationPanel`, fed by `useNurseTrustBadge(nurseId)`.
- `TrustBadge` now passes `nurseId`, making the badge tappable everywhere it appears (C2 cards + C3).
- The primary CTA is now a `StickyActionBar` (price-from beside "درخواست رزرو") — survives the infinite
reviews list.
- An optional latest-review snippet renders on the services tab when `profile.latestReview` is present.
- The reviews tab's fractional `RatingInput` (no `Math.round`) was found **already correct** in the current
code — verified, not re-fixed (phase 1's RatingInput v2 already replaced the rounding).
**New shared components** (both co-located-tested, both reused by phase 8 per the phase's cross-reference):
- `VerificationPanel` (`src/components/VerificationPanel/`) — "what Balinyaar verified": one row per
`TrustBadge.credentialTypes[]` (i18n off the `verification` namespace's `step_*` codes, never a raw wire
value) + the approval date. Renders only what is served — no invented steps, no fake dates.
- `TrustBadge`'s new opt-in `nurseId` prop — tappable, opens a bottom-sheet (mobile)/dialog (desktop)
rendering `VerificationPanel`, fed by a **lazily-enabled** `useNurseTrustBadge` fetch (only queries once
the explainer is actually opened). Implemented as an inner `InteractiveTrustBadge` subcomponent so the
**default** (no `nurseId`) badge — used everywhere else in the app (`ProfileSummary`, etc.) — calls no
query hook at all and needs no `QueryClientProvider` in its callers' tests.
- `StickyActionBar` (`src/components/common/StickyActionBar/`) — the bottom-pinned action-bar shell shared
by C1's live-count CTA and C3's booking CTA. Composes with the shell's existing `BottomBar` safe-area
handling (a flex sibling below the scrolling `main`) rather than reimplementing
`env(safe-area-inset-bottom)`.
**Copy**: `search.empty_suggest_city` («شهر نزدیک دیگری مانند مشهد، اصفهان یا شیراز») deleted from both
`en.json`/`fa.json`, replaced with `empty_suggest_date` ("try a different date" — an honest relaxation the
family can actually act on). Reused in the C1 zero-count sticky-bar hint too.
## What is now testable (and exactly how)
1. **Home**: log in as the seeded customer (`0912000000x`). Tap the search field on Home → lands on
`/search` with no `?q=` in the URL. Stop the API and reload → an error card with «تلاش مجدد», not an
eternal spinner. Restart → home recovers; the trust strip shows under the greeting; a customer with a
completed booking sees a «رزرو دوباره با …» card that opens the nurse's profile.
2. **C1**: `/search` — the date filter shows «امروز»/«فردا» + Shamsi day chips (no native browser
calendar anywhere); tapping the calendar icon opens the full Jalali grid in a popover. Pick a category +
city → the count CTA is pinned at the bottom while scrolling the page. Pick filters matching nothing
(e.g. an unusually high min price) → the sticky bar shows the non-CTA "no matches" message, not a
tappable button.
3. **C2**: run a search → recap chips show category/region/gender/price; tap any chip → C1 opens with
every filter pre-filled (category, region incl. province prefill, gender, price, date); browser-back
returns to identical results with zero network (cache hit, unchanged from before this phase). The
header reads «مرتب‌شده بر اساس امتیاز» as static text, not a dropdown.
4. **Cards**: a nurse with multiple variants shows distinguishable cards (category/variant label + price);
every card shows the gender chip and «N ویزیت موفق»; tapping the ✓ badge opens the verification
bottom-sheet/dialog (fetches `nurses/{id}/trust_badge` lazily on open).
5. **C3**: open a profile → header shows completed visits + rating; the `VerificationPanel` section lists
the served credential types + approval date (Shamsi); open the reviews tab and scroll deep → «درخواست
رزرو» stays pinned at the bottom; a 4.5 average renders as a fractional star row. Tap the CTA → the C4
request form receives the same nurse/variant/gender/date params as before.
6. Repeat 15 on `/en` (LTR) and in dark mode; on a mobile viewport confirm both sticky bars sit above the
`BottomBar` and the home-indicator safe area (the `BottomBar` already owns
`env(safe-area-inset-bottom)`; `StickyActionBar` is a separate flex sibling above it, inside the
scrolling `main`, so it never overlaps).
## What is mocked / waiting on a real service
Nothing new mocked this phase — search stays real (`USE_SEARCH_MOCK = false`, unchanged) and verification
stays mock-primary (`USE_VERIFICATION_MOCK`, unchanged) exactly as before; this phase only consumes the
existing `useNurseTrustBadge` hook (previously built, never wired into a page) and adds no new fetch code
outside existing service hooks. No mock registry entries added/changed.
## Contracts
- Consumed: `services/search` (b7 + REQ-012, unchanged), `services/verification`'s public trust-badge read
(b6, unchanged — this phase is the first to actually call `useNurseTrustBadge` from a page/component).
- Requested (`dev/shared-working-context/frontend/requests/for-backend.md`, next free numbers 040043):
- **REQ-040** — `variantDisplayName` (required) + optional `topReviewTag` on `NurseSearchResultDto`.
- **REQ-041** — free-text `q` search over nurse/variant/category names on `GET search/nurses` (the
Home search bar's upgrade path).
- **REQ-042** — `nurseGender` on `NursePublicProfileDto` (the C3 header gender chip, currently omitted).
- **REQ-043** — public per-step verification detail (step codes + decision dates) so `VerificationPanel`
can list identity/Shahkar/license individually instead of folding to `credentialTypes[]`.
## Docs updated
- `client/CLAUDE.md` "Project Structure": the C1/C2/C3 line items (Jalali chips, sticky CTA, recap chips,
dossier layout), the new `VerificationPanel`/`StickyActionBar` entries, and updated `TrustBadge`/
`GenderToggle`/`NurseResultCard`/`JalaliDatePicker` lines noting their new modes.
## Follow-ups for later phases
- **Multi-variant collapse.** Collapsing a nurse's several variant rows into one card with a price range +
"N خدمت" disclosure needs the `variantDisplayName` REQ (040) served first — noted as a design follow-up,
not built this phase (the category-name fallback keeps rows distinguishable meanwhile).
- **Public/guest storefront and landing page** — deferred to phase 13 (unchanged from the phase brief).
- **Save/favorite/share nurses** — post-MVP product decision (unchanged from the phase brief).
- **Full ICU zero-case copy sweep** (ratings/counts pluralization polish across the whole app) — deferred
to phase 12 per the phase brief; this phase only ensured the C1 zero-count state is never a tappable CTA.
- Phase 8's public-profile preview should reuse `VerificationPanel` and `TrustBadge`'s `nurseId` explainer
mode unchanged, per the phase's explicit cross-reference.
## Memory
Saved a `project`-type memory (`ui_phase_4_customer_storefront.md`, indexed in `MEMORY.md`) covering: the
`NurseResultCard`/`TrustBadge` ownership split with phase 8, the `VerificationPanel` shared-component
contract, the `TrustBadge` split-component gotcha (query hooks must live in a conditionally-**mounted**
subcomponent, not a conditionally-**enabled** hook call, to avoid forcing `QueryClientProvider` on every
caller's tests), the C3 profile DTO's missing `nurseGender` (REQ-042, never render the placeholder stub),
and the `StickyActionBar`/`province_id`-carry patterns for future sticky-CTA or region-prefill needs.