# 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.