Files
OSIT-AE-App-Svelte/documentation/MODULE__AE_Events_Exhibitor_Leads.md
Scott Idem 14c2635df4 docs + fix: Leads module doc, badges onsite doc, license access fix, QR log cleanup
- Add MODULE__AE_Events_Exhibitor_Leads.md — full module reference (auth model, tabs,
  data model, routes, offline/PWA notes, OSIT admin notes)
- Add MODULE__AE_Events_Badges_Onsite.md — onsite printing guide (browser settings,
  CUPS/Linux setup, Zebra ZC10L section with test results, Epson stub, troubleshooting)
- Archive PROJECT__AE_Events_Exhibitor_Leads_v3*.md + Zebra test day doc → history/
- Fix leads license management access: ae_tab__manage.svelte license section now visible
  to administrator_access OR shared-passcode sign-in (type === 'shared'); matches spec
- Add type field to LeadsLocState.auth_exhibit_kv interface (was set at runtime but missing from type)
- Silence QR code console noise: entry log gated at log_lvl >= 2, data URL log removed
- vite.config.ts: exclude documentation/ and tests/ from Vite HMR file watcher

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:58:57 -04:00

9.7 KiB
Raw Blame History

Aether Events — Exhibitor Leads Module (v3)

Status: Implemented and ready for demo. Core lead capture flow works end-to-end. Platform: PWA only — mobile-first, offline-capable. Target users: Conference exhibitors scanning attendee badges at their booths.


What It Does

The Exhibitor Leads module lets conference exhibitors capture and manage attendee leads directly from their booth. Exhibitors scan or search attendee badges and build a list of contacts they met. All data is cached locally (IndexedDB / Dexie.js) for spotty or offline venue Wi-Fi, with background SWR revalidation against the API when the network is available.

Key capabilities:

  • Badge scanning — QR scan or text search (name, email, affiliations, badge ID)
  • Lead list — filterable/sortable, per-exhibitor or per-staff-member view
  • Lead detail — custom question responses, notes (rich text), priority flag, hide/unhide
  • Export — CSV/XLSX download of all leads for an exhibit
  • License management — assign staff accounts (email + passcode) per max license count
  • Custom questions — configurable per-exhibit follow-up questions (ratings, dropdowns, text)
  • Offline-first — IndexedDB cache survives network drops; syncs on reconnect
  • PWA install — Chrome/Android native install prompt; iOS Safari "Add to Home Screen" nudge

Access Levels

Three sign-in levels are supported within this module:

Level How to sign in What they can do
Aether Platform Auth Standard Aether login (manager/trusted access) Full admin bypass; all exhibit data
Shared Exhibit Passcode Enter booth's staff_passcode Manage licenses, view/add leads
Licensed User Email + individual passcode from license_li_json Add and manage leads for this booth

Auth state is persisted in $events_loc.leads.auth_exhibit_kv[exhibit_id] (localStorage-backed).

A booth only shows in the landing page search to non-admins if it is marked priority = true (i.e. paid).

allow_tracking Opt-In

Attendees must have allow_tracking = true on their badge record to be added as a lead. Attendees without this flag are blocked at both the QR scanner and the manual search:

  • QR scan shows a "Tracking Blocked" warning card (ShieldOff icon)
  • Manual search shows an "Opt-Out" badge per result row; the "Add as Lead" button is suppressed

Route Structure

/events/[event_id]/leads/
    → Exhibit search / landing page — find your booth

/events/[event_id]/leads/exhibit/[exhibit_id]/
    → Main exhibitor view — all 4 tabs

/events/[event_id]/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/
    → Lead detail view — edit notes, custom responses, flags

Module Tabs

Tab 1 — Start / Sign In

The only tab visible when not signed in as a licensed leads user.

  • Sign in with shared passcode — grants booth management access (license management, passcode change)
  • Sign in as licensed user — grants lead capture access (email + passcode)
  • PWA install prompt — Chrome/Android native install button; iOS "Share → Add to Home Screen" instructions
  • License list — shown when signed in via shared passcode or Aether admin; add/edit/remove staff slots

Tab 2 — Add Leads

Visible only when signed in (licensed user or Aether auth).

  • Text search — search by name, email, affiliations, badge ID
  • QR scan — two modes:
    • Rapid — auto-resets after 2 seconds for the next scan (high volume)
    • Qualify — navigates immediately to lead detail to capture notes per scan
  • Results show "Add as Lead" or "View Lead" depending on whether already captured
  • All new leads are linked to the capturing user's email (external_person_id)

Tab 3 — Leads List

The main lead management view.

  • Search — full-text across name, email, notes (local IDB fast path + API revalidation)
  • Sort — Newest first, Oldest first, Name A→Z, Name Z→A
  • Filter by staff member — "All Leads" or filter by individual licensed user
  • Show/hide hidden records — toggles hide filter on IDB and API results
  • Export — downloads CSV/XLSX for the exhibit (leads_api_access required)

Tab 4 — Manage / Config

Exhibit configuration and app settings.

Admin Tools (manager_access only):

  • Payment status toggle (priority field)
  • Max licenses, small/large device counts

Booth Profile (all signed-in users):

  • Exhibitor name, booth description (rich text)

Access & Security:

  • View/change shared staff passcode
  • Sign out button

Lead Retrieval Config:

  • Exhibit Leads Licensees — manage staff accounts (administrator_access only; gap: should also allow shared-passcode users — see Known Gaps)
  • Qualifiers & Questions — custom question config
  • Licenses & Billing — stub (Stripe not yet implemented)

App Settings:

  • Auto-hide header/footer toggle
  • Show Payment Tab toggle
  • Show Extra Details toggle
  • Refresh interval (1120 seconds, default 25s), countdown timer, last-refresh timestamp
  • Reload App, Clear IDB, Hard Reset (clears localStorage)

Data Model

event_exhibit

One exhibitor's presence at an event.

Field Purpose
event_exhibit_id / event_exhibit_id_random Primary / URL-safe ID
name Exhibitor display name
code Booth number
staff_passcode Shared sign-in code
priority 1 = paid/active
license_max Max licensed staff slots
license_li_json Array of { full_name, email, passcode }
leads_custom_questions_json Array of question definitions
leads_device_sm_qty / leads_device_lg_qty Device count tracking

event_exhibit_tracking

One captured lead — links an exhibit to a badge.

Field Purpose
event_exhibit_tracking_id Primary key
event_exhibit_id Parent exhibit
event_badge_id Captured attendee's badge
external_person_id Capturing staff's email (from license)
exhibitor_notes Rich text notes (HTML via TipTap)
responses_json { [question_code]: { response: value } }
priority Star/flag for high-priority leads
hide Soft-delete / hide from list
Denormalized badge fields event_badge_full_name, event_badge_email, event_badge_affiliations, event_badge_professional_title

Key Files

Routes

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

Components

File Role
ae_tab__start.svelte Tab 1 — welcome, sign-in, license list
ae_tab__add.svelte Tab 2 — QR scan + text search toggle
ae_tab__manage.svelte Tab 4 — admin tools, booth config, app settings
ae_comp__exhibit_signin.svelte Sign-in UI (shared passcode + licensed user)
ae_comp__lead_qr_scanner.svelte QR scanner (rapid / qualify mode)
ae_comp__lead_manual_search.svelte Manual badge search + add
ae_comp__exhibit_tracking_search.svelte Lead list search/filter/sort bar
ae_comp__exhibit_tracking_obj_li.svelte Lead list item renderer
ae_comp__exhibit_license_list.svelte License slot manager
ae_comp__exhibit_custom_questions.svelte Custom question config editor
ae_comp__exhibit_payment.svelte STUB — Stripe placeholder
ae_comp__exhibit_search.svelte Exhibit search on the landing page
lead/ae_comp__lead_detail_form.svelte Custom question response editor

Lib Functions

File Role
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 aggregated into events_func via src/lib/ae_events/ae_events_functions.ts.


Offline / PWA Notes

  • All data is stored in db_events (Dexie.js) — exhibit and exhibit_tracking tables
  • SWR pattern: IDB cache returned immediately; background API fetch updates IDB and triggers UI refresh
  • Search: local IDB first pass (fast), then API revalidation via search__exhibit_tracking
  • beforeinstallprompt event captured at module load time (src/lib/pwa/pwa_install.svelte.ts) — fires within ~1 second of page load, before any Svelte $effect runs
  • iOS Safari: no native install prompt; shows "Share → Add to Home Screen" instructions instead

Known Gaps

Payment / Stripe

ae_comp__exhibit_payment.svelte is a stub. The Stripe integration is not implemented. The payment tab can be hidden via "Show Payment Tab" in App Settings.

License Management — Shared Passcode Access

Implemented. The license section in the Manage tab is visible to Aether admins and to anyone signed in via the shared exhibit passcode (auth_exhibit_kv[exhibit_id].type === 'shared'). Guard in ae_tab__manage.svelte:

{#if $ae_loc.administrator_access || $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.type === 'shared'}

OSIT Admin Notes

  • Mark priority = 1 on an exhibit to make it visible in public search and to enable lead capture
  • license_max controls how many licensed staff slots an exhibit can have
  • Export endpoint: GET /v3/action/event_exhibit/{id}/tracking_export — requires leads_api_access
  • Custom questions are stored per-exhibit in leads_custom_questions_json (not global)
  • The exhibitor landing page link format: /events/[event_id]/leads/exhibit/[exhibit_exhibit_id_random]/