# Customer · Discover — Logic & Flows

**Scope note:** The demo `Discover.tsx` is a **mobile customer-app** screen. In our
stack the customer app is the **mobile/API client**, NOT the Yii shop (`frontend/`)
or admin (`backend/`) portal. So parity here is assessed against the **API**
(`api/controllers/ShopsController.php`, `AdsController.php`) that the mobile app
consumes. The on-screen UI itself lives in the mobile app repo, out of this repo's
scope — flagged throughout.

---

## Demo source
`/private/tmp/Navagoo_MI_dev/navagoo-app/src/portals/customer/Discover.tsx`
Store: `src/store/store.ts` + `src/store/seed.ts`; types: `src/types.ts`.

## Demo logic / flows

### 1. Two-tab screen: Discover (shops) + Deals
`Discover.tsx:23` `useState<'discover' | 'deals'>`. Local tab state only; no routing.

### 2. Active-shop listing (Discover tab)
- `Discover.tsx:25` — `state.shops.filter(s => s.status === 'active')`. Only `active`
  shops are listed (inactive/onboarding shops hidden).
- Header count `Discover.tsx:72` — `{shops.length} open`.
- No server pagination, no search filtering wired (the search input at
  `Discover.tsx:44` is **decorative** in the demo — no `onChange`/filter).

### 3. ShopCard derived metrics (`Discover.tsx:117-172`)
Per shop, computed client-side from the store:
- **Rating / reviews / distance** — `RATINGS` hard-coded map keyed by shop id
  (`Discover.tsx:9-13`), fallback `{ rating: 4.8, reviews: 0, distance: '—' }`
  (`Discover.tsx:119`). i.e. demo rating/distance are **mock**, not computed.
- **Service count** — `state.services.filter(s => s.shopId === shop.id).length`
  (`Discover.tsx:120`).
- **Min price** — `Math.min(...services.map(s => s.price))` (`Discover.tsx:121-123`).
- **Type label** — `women|men|all → Women|Men|Unisex` (`Discover.tsx:15-19`).
- **Logo tint** — `shop.logoHue` drives an oklch gradient + `ShopTile`
  (`Discover.tsx:129-139`).

### 4. Book now (stub)
`Discover.tsx:78-83` — `toast.info('Shop page coming soon …')`. Booking flow is a
later milestone; Discover only navigates conceptually.

### 5. Deals tab (`Discover.tsx:88-110`)
- `state.deals` rendered directly (seed `deals` array, `seed.ts:1136-1167`).
- Each deal resolves its shop via `deal.shopId` → `state.shops.find` (`Discover.tsx:91`).
- `deal.shopId == null` ⇒ **platform-wide** deal, labelled "Platform-wide · all salons"
  and shown with a `Sparkles` icon instead of `Tag` (`Discover.tsx:97-104`).
- Deal shape (`seed.ts:1136`): `description`, `discountType` (percent|fixed),
  `discountValue`, `usageCap`, `usageCount`, `expiry`. The card shows only
  `description` + shop name — no expiry/usage logic on this screen.

---

## Our implementation (API)

### Shop listing — `api/controllers/ShopsController.php`
- `actionIndex` (`ShopsController.php:52`) and `actionListAll` (`:141`).
- **Active-only filter:** `andWhere(['status' => Shop::STATUS_ACTIVE])`
  (`ShopsController.php:59`, `:148`) — matches demo rule §2.
- **Demo/live partition:** `is_demo` filter keyed off the authenticated user's
  `is_demo` flag (`ShopsController.php:60-64`). No demo equivalent (demo app is
  in-memory seed only).
- **Filters (richer than demo):** `category_id`, `top_rated`, `popular`,
  location (`lat`/`long` Haversine), gender (`type_id`), `service_id`, `name`
  (search), `city_id`, `rate_value` (`ShopsController.php:66-107`). The demo's
  search box and gender personalisation are **not wired** on the Discover screen,
  so the API is ahead here.
- **Pagination:** `ActiveDataProvider`, default 12/page (`ShopsController.php:18-20`).
  Demo has none.

### ShopCard metrics — `api/resources/ShopsResource.php`
- **Rating:** real, `rate` field → `{ rate_average, total_rates }`
  (`ShopsResource.php:23-30`). Demo rating is mock; ours is computed/persisted →
  **ours is ahead**.
- **Distance:** real Haversine `distance` (km) when `lat/long` passed
  (`Shop.php:91-120`, exposed `ShopsResource.php:117` `'distance'`). Demo distance
  is mock → **ours ahead**.
- **Service count / min price:** NOT a dedicated field on `ShopsResource`. Service
  pricing lives in `ShopServiceResource` (`price`, `price_before_discount`,
  `ShopServiceResource.php:14-37`) via the shop-services endpoint. The Discover-style
  "N services · from X SAR" summary is **not pre-aggregated** on the shop list payload
  → partial (mobile app would have to fetch services separately or we add aggregate
  fields).
- **Type/gender label:** `gender` underlies `applyGenderFilter` (`Shop.php:239`); the
  resource does not expose a women/men/unisex display label field → minor gap.
- **Logo/image:** `image` field (`ShopsResource.php:88`). No `logoHue` (demo's
  generated-avatar tint is a UI nicety; N/A server-side).

### Deals — closest match: Ads + service discounts
- The demo "Deals" concept (promotional offers, percent/fixed, optional
  platform-wide) has **no single matching API endpoint**.
- Closest: `AdsController::actionIndex` (`AdsController.php:27`) — promo banners
  scoped to active shops, `AdsResource` exposes only `id`, `shop`, `image`
  (`AdsResource.php:8-19`). No `discountType`/`discountValue`/`expiry`/usage, and
  **no platform-wide (shopId-null) ad** concept.
- Per-service discounts exist as `price_before_discount` vs `price`
  (`ShopServiceResource.php:19-25`) and promo codes in the booking flow
  (`BookingController.php:908-909`), but there is **no curated "Deals for you" feed**.
- Verdict: Deals tab is **partial/missing** server-side — Ads covers the banner case
  only, not a structured deals feed.
