cleanup phases 6
This commit is contained in:
@@ -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 (5–6 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 1–5 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 040–043):
|
||||
- **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.
|
||||
Reference in New Issue
Block a user