Files
baya-monorepo/dev/shared-working-context/reports/ui-phase-4-report.md
T
2026-07-18 09:51:03 +03:30

12 KiB
Raw Blame History

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-040variantDisplayName (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-042nurseGender 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.