Files
baya-monorepo/dev/shared-working-context/reports/frontend-phase-6-report.md
T
2026-07-09 04:09:35 +03:30

6.5 KiB

Frontend Phase 6 — Search & discovery (C1/C2/C3) — report

Date: 2026-07-09 · Track: frontend · Depends on: f4 (catalog/category grid), f5 (TrustBadge), f3 (geo picker), f0 (money util, services pattern) · Consumes: b7 search.md + b6 trust badge / b5 variant reads · Unlocks: f7 booking request.

What was built

A vertical discovery slice — the trust funnel where a family picks a real, verified nurse.

services/search/ (the domain, copies the f0/auth shape)

  • types.tsNurseSearchFilters (the cache key/URL shape), NurseSearchResult (C2 card row), NurseProfile + NurseProfileServiceRow + NurseReviewSnippet (C3), the SearchApi seam. Derived from the b7 contract; the fields b7 doesn't expose are documented inline + filed (REQ-012).
  • keys.tssearchKeys.results(filters) / searchKeys.profile(id) + canonicalizeSearchFilters (stable key order, absent optionals omitted) — the filter-object-as-query-key caching contract.
  • constants.tsUSE_SEARCH_MOCK (true, primary), stale/gc times, page size, debounce ms.
  • filterParams.ts — the single C1↔C2 URL (de)serializer (snake_case, matching b7 params) so the screen that writes the URL and the screen that reads it never drift.
  • apis/mockApi.ts (primary; real-shaped verified fixtures in seed.ts, reproduces b7 filter + whole-city geography + rating-sort semantics), clientApi.ts (real b7/b6 mapping scaffold, gaps left blank), index.ts (seam selection by USE_SEARCH_MOCK).
  • hooks/useNurseSearch (keepPreviousData, enabled on category+city), useNurseProfile (enabled on id), useDebouncedValue (generic, used by the C1 controller). index.ts re-exports hooks.

Screens (all RTL/i18n/dark-mode, under the customer bottom-tab shell)

  • C1 /search — reused f4 category grid (selectable) + f3 CascadingRegionSelect (district optional = whole city) + prominent same-gender toggle (خانم/آقا/فرقی ندارد) with a why-line + intent-only date + Toman price range; a live result count drives the "مشاهده N پرستار" CTA into C2. Fast-changing filter state in the colocated useSearchFilters controller (debounced price).
  • C2 /search/results — result count + rating sort control (one option; other sorts DEFERRED), rating-sorted NurseResultCard list, all four states (skeleton / empty "relax filters" with concrete suggestions / error-retry / populated), load-more. Filters live in the URL.
  • C3 /search/nurse/[nurseId] — avatar/name/rating, ✓ تاییدشده (reused TrustBadge) + نظام پرستاری (rendered only when inoMembership), attribute chips (specialty codes → i18n + years-experience), ServicePriceRow services list, latest-review snippet (+ "no reviews" empty), loading/not-found/error states, and the "درخواست رزرو" CTA that hands off to /bookings/request.

Shared components (tested)

  • NurseResultCard — presentational + memoized; avatar, name, reused verified badge, rating + review count, optional distance chip, "from X تومان/unit" via PriceDisplay.
  • ServicePriceRow — service name + PriceDisplay (money util + i18n unit label); reused by the booking summary in f7+.

Other

  • Added star + tune icons to the AppIcon registry. Routes: SEARCH_RESULTS, SEARCH_NURSE, BOOKING_REQUEST. i18n: search filled + booking seeded, both locales in sync.

Now testable, and exactly how (§7 of the phase)

Run npm run dev (mock is primary — no backend needed; or point NEXT_PUBLIC_API_URL at a b7 server and flip USE_SEARCH_MOCK=false, noting the REQ-012 gaps).

  • Discovery E2E: Home → tap a category (e.g. مراقبت سالمند) → C1 preselects it → set city (تهران), gender (خانم) → CTA shows a real count → tap → C2 lists only verified nurses, rating-sorted, each with photo/initials, name, ✓ تاییدشده, rating + review count, distance, "from X تومان/ساعت".
  • Profile: tap a card → C3 shows badges, attribute chips, services + Persian unit labels, latest review → "درخواست رزرو" → /bookings/request echoing nurse + variant + gender intent.
  • Empty state: search Mashhad/Isfahan/Shiraz (seeded empty) → "relax your filters" with suggestions.
  • Caching (headline): React Query Devtools → filter set A → set B (one fetch) → revert to A → instant, zero new requests. Type in the price field → one debounced request, not one per keystroke.
  • i18n/RTL: flip fa↔en — all labels/badges/units/empty copy translate + mirror; dark mode holds.

Mocked behind the seam (how f-next swaps it)

services/search is mock-primary (USE_SEARCH_MOCK = true) because b7's NurseSearchResultDto row omits the nurse name/avatar/distance, and there is no aggregated public nurse-profile endpoint (only the b6 trust badge + b5 single-variant read). searchMockApi supplies real-shaped fixtures so C1/C2/C3 fully demo. The real searchClientApi already maps everything b7/b6 provide and leaves the missing fields blank; once REQ-012 lands (enrich the search row + add GET nurses/{id}/profile), the swap is flipping one flag — no hook/component change. (This is a client-side mock; it is recorded here, not in mocks-registry.md, which tracks backend DI seams.)

Contract consumed / requests filed

  • Consumed (not edited): dev/contracts/domains/search.md (b7) — services/search/types.ts derives from it; b6 trust badge + b5 variant read for the profile scaffold.
  • Filed: frontend/requests/for-backend.md REQ-012 — search-row nurseName/avatarUrl/ distanceKm + an aggregated GET api/v1/nurses/{id}/profile.

Follow-ups

  • f7 booking: the "درخواست رزرو" handoff carries nurse_id, variant_id, required_gender (the C1 same-gender intent → required_caregiver_gender/b8), city_id, service_category_id, date as query params to /bookings/request (currently a DEFERRED stub). f7 builds the form + captures the gender into the booking request.
  • C3 reviews tab: DEFERRED → f13 (only the latest-review snippet ships now).
  • DEFERRED (per contract): availability-window hard filter, sorts beyond rating, map/radius discovery.

Gate

npm run check green · npm run test:ci green (165 tests, +8). npm run build compiles and type-checks clean; the only prerender failure is the pre-existing f5 /nurse/verification "Missing .env variable!" (needs env set) — unrelated to this phase's routes.