# Aether Exhibitor Leads Module (v3)

**Status:** Substantially implemented. Core lead capture flow works end-to-end.
**PWA only** — no Electron involvement. The Electron app is exclusively for the Launcher.

Spec docs:

- `documentation/PROJECT__AE_Events_Exhibitor_Leads_v3.md` — overview
- `documentation/PROJECT__AE_Events_Exhibitor_Leads_v3_detail.md` — tab-level detail

---

## Overview

Exhibitors sign in at their booth page, then scan or search attendee badges to capture leads.
Leads are stored as `event_exhibit_tracking` records linked to the exhibit and the badge.
All data is cached in IndexedDB (Dexie.js) for offline use, with background API revalidation (SWR).

---

## Route Structure

```text
/events/[event_id]/leads/                         — Exhibit search / landing
/events/[event_id]/leads/exhibit/[exhibit_id]/    — Exhibit detail (4 tabs)
/events/[event_id]/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/  — Lead detail
```

---

## Key Files

### Routes

| File                                                                 | Role                                        |
| -------------------------------------------------------------------- | ------------------------------------------- |
| `leads/+page.svelte`                                                 | Exhibit search/landing — find your booth    |
| `leads/+page.ts`                                                     | Layout data load                            |
| `leads/exhibit/[exhibit_id]/+page.svelte`                            | Main exhibitor view — orchestrates all tabs |
| `leads/exhibit/[exhibit_id]/+layout.svelte` / `+layout.ts`           | Exhibit layout / data load                  |
| `leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.svelte` | Lead detail view/edit                       |
| `leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts`     | Lead data load                              |

### Components (within `exhibit/[exhibit_id]/`)

| File                                       | Role                                            |
| ------------------------------------------ | ----------------------------------------------- |
| `ae_tab__start.svelte`                     | Tab 1 — welcome + sign-in                       |
| `ae_tab__add.svelte`                       | Tab 2 — QR/search toggle + scan mode toggle     |
| `ae_tab__manage.svelte`                    | Tab 4 — admin tools, booth config, app settings |
| `ae_comp__exhibit_signin.svelte`           | Sign-in: shared passcode + licensed user        |
| `ae_comp__lead_qr_scanner.svelte`          | QR scanner (rapid vs. qualify mode)             |
| `ae_comp__lead_manual_search.svelte`       | Manual badge search + add                       |
| `ae_comp__exhibit_tracking_search.svelte`  | Lead list search/filter bar                     |
| `ae_comp__exhibit_tracking_obj_li.svelte`  | Lead list item renderer                         |
| `ae_comp__exhibit_license_list.svelte`     | License slot manager (admin)                    |
| `ae_comp__exhibit_custom_questions.svelte` | Custom question config editor (admin)           |
| `ae_comp__exhibit_payment.svelte`          | Stripe payment component (fully implemented)    |
| `ae_comp__exhibit_search.svelte`           | Exhibit search input on the landing page        |

### Lead detail components (within `lead/[exhibit_tracking_id]/`)

| File                               | Role                            |
| ---------------------------------- | ------------------------------- |
| `ae_comp__lead_detail_form.svelte` | Custom question response editor |

---

## Data Model

### `event_exhibit`

Represents one exhibitor's presence at an event.
Key fields: `event_exhibit_id`, `name`, `code` (booth #), `staff_passcode`, `priority` (paid flag),
`license_max`, `license_li_json` (array of `{full_name, email, passcode}`),
`leads_custom_questions_json` (array of question defs), `leads_device_sm_qty`, `leads_device_lg_qty`.

### `event_exhibit_tracking`

One captured lead — links an exhibit to a badge.
Key fields: `event_exhibit_tracking_id`, `event_exhibit_id`, `event_badge_id`,
`external_person_id` (capturer's email), `exhibitor_notes` (HTML),
`responses_json` (`{ [question_code]: { response: value } }`),
`priority`, `enable`, `hide`.
Denormalized badge fields: `event_badge_full_name`, `event_badge_email`,
`event_badge_affiliations`, `event_badge_professional_title`.

---

## Sign-In Model

Three auth levels in this module:

1. **Aether platform auth** (manager_access / trusted_access) — full admin bypass
2. **Shared exhibit passcode** (`event_exhibit.staff_passcode`) — grants booth management access
3. **Licensed user** (email + passcode from `license_li_json`) — grants lead capture access

Auth state stored in `$events_loc.leads.auth_exhibit_kv[exhibit_id]` (persisted to localStorage).

---

## QR Scan Flow

Badge QR encodes: `{ type: 'event_badge', id: '<id_random>' }`
Scanner reads this, checks for duplicate in IDB, loads badge info, then creates an
`event_exhibit_tracking` record via `events_func.create_ae_obj__exhibit_tracking`.

Two scan modes (toggled per exhibit):

- **Rapid** — auto-resets after 2 seconds to scan the next person
- **Qualify** — navigates to lead detail immediately to fill in notes/responses

---

## Known Gaps / Not Yet Implemented

- None currently. See `TODO__Agents.md` for the remaining smoke test checklist.

## Implemented (previously listed as gaps)

- **Payment / Stripe** — `ae_comp__exhibit_payment.svelte` is fully implemented. Three states:
  paid (`priority=true` green card), Stripe not configured (admin hint), payment form.
  Visibility is controlled event-wide by `event.mod_exhibits_json.leads_require_payment`.
  Set to `true` in event settings JSON to enable Stripe for an event; default `false` hides
  all billing UI (header CreditCard button + Manage tab "Licenses & Billing" accordion).

- **`allow_tracking` gate** — enforced in both `ae_comp__lead_qr_scanner.svelte` and
  `ae_comp__lead_manual_search.svelte`. Badges without `allow_tracking = true` are blocked.
- **Show/hide hidden records** — toggle in `ae_comp__exhibit_tracking_search.svelte`; filters
  local IDB search, API search (`hidden` param), and `filtered_lead_li` in `+page.svelte`.
- **Export endpoint** — `download_export__event_exhibit_tracking` uses V3 action endpoint
  `/v3/action/event_exhibit/{id}/tracking_export`. Gated on `leads_api_access === true`.
- **PWA install prompt** — `element_pwa_install_prompt.svelte` placed on the Start tab.
  Handles Chrome/Android native install and iOS Safari manual instructions.
- **License management access for shared-passcode users** — `ae_tab__manage.svelte` license
  section now visible to `administrator_access` OR `auth_exhibit_kv[id].type === 'shared'`.
  Also added `type` field to `LeadsLocState.auth_exhibit_kv` interface.

---

### Recent Changes (2026-04-03)

- Migrated Leads persisted state to Svelte‑5 PersistedState: new `leads_loc` store implemented at `src/lib/stores/ae_events_stores__leads.svelte.ts` and the store version constant `AE_LEADS_LOC_VERSION` added to `src/lib/stores/store_versions.ts`.

- Payment UI updates:
  - `ae_comp__exhibit_payment.svelte` now accepts a `leads_require_payment` prop and honors the event-level `event.mod_exhibits_json.leads_require_payment` flag to hide billing UI when payment is not required for the event.
  - Added a loading guard so the payment component shows a loader until the exhibit record resolves from IndexedDB (Dexie `liveQuery`), preventing the payment form from appearing prematurely.

- Tests: some test seeds need updating — update `tests/_helpers/leads_helpers.ts` to seed `leads_loc` defaults and the expected `__version` where tests rely on pre-seeded localStorage.


## Lib Functions

`src/lib/ae_events/ae_events__exhibit.ts` — exhibit load, search, create, update
`src/lib/ae_events/ae_events__exhibit_tracking.ts` — tracking load, search, create, update, export
Both are aggregated into `events_func` via `src/lib/ae_events/ae_events_functions.ts`.
