# 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.ts`** — `NurseSearchFilters` (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.ts`** — `searchKeys.results(filters)` / `searchKeys.profile(id)` + `canonicalizeSearchFilters`
  (stable key order, absent optionals omitted) — the filter-object-as-query-key caching contract.
- **`constants.ts`** — `USE_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.
