feat(leads): allow_tracking gate + icon bug fix + docs update
- QR scanner: after badge loads, blocks add with 'Tracking Opt-Out' warning card if allow_tracking !== true; replaced deprecated CheckCircle → CircleCheck - Manual search: shows ShieldOff 'Opt-Out' label per row for blocked badges; add_as_lead() also guards against programmatic bypass - Fix: ae_comp__exhibit_tracking_obj_li — Loader2 from wrong package @lucide/svelte → LoaderCircle from lucide-svelte - ae_types.ts: added allow_tracking and agree_to_tc to ae_EventBadge interface - README.md (leads): full rewrite reflecting actual current state and known gaps - TODO__Agents.md: updated Leads entry from stale 'NEXT MAJOR FEATURE' to accurate in-progress status with remaining checklist Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,14 +5,12 @@
|
||||
|
||||
## 🚧 Upcoming High Priority
|
||||
|
||||
### [Stores] Refactor — ae_stores.ts and ae_events_stores.ts cleanup
|
||||
Both files have grown organically and are messy. Refactor goals:
|
||||
- Split into focused files per domain (core, user/auth, files, module-specific)
|
||||
- Remove dead/commented-out code and stale `ver`/`ver_idb` constants from data structs (replaced by `__version` in store_versions.ts)
|
||||
- Standardize field naming conventions
|
||||
- Move sponsorships/stripe Stripe button IDs out of session store and into config
|
||||
- Keep `ae_stores.ts` and `ae_events_stores.ts` as barrel re-exports for backwards compatibility
|
||||
Related: `src/lib/stores/store_versions.ts` is the new home for version constants.
|
||||
### [Stores] Refactor — Phase 2c (deferred)
|
||||
Phases 1, 2a, 2b are complete (see ✅ Completed below). One phase remaining:
|
||||
|
||||
- [ ] **Phase 2c — Actual separate stores (`ae_auth`, `ae_app`):** Requires touching ~471
|
||||
`$ae_loc.*` auth-field read sites across 150+ files. Deferred until a Svelte runes migration
|
||||
of the store layer itself (touching every component anyway makes the callsite sweep cheap).
|
||||
|
||||
### [Backend] Join event_location_id onto event_presenter API view
|
||||
The `event_presenter` object currently has `event_session_id` but not `event_location_id`.
|
||||
@@ -57,11 +55,34 @@ for the full checklist and prep plan.
|
||||
- [ ] **Test data set:** include edge cases — very long name, HTML markup in name/affiliations,
|
||||
badge with no affiliations, badge with all ticket/option codes set.
|
||||
|
||||
### [Leads] Exhibitor Lead Scanning — NEXT MAJOR FEATURE
|
||||
QR code scan at exhibitor booth → capture attendee badge data. Gated by `allow_tracking` on
|
||||
the badge. Check if `documentation/MODULE__AE_Events_Leads.md` exists for full spec.
|
||||
Key questions before starting: which routes, does the Electron app scan, what does the
|
||||
lead record look like in the DB?
|
||||
### [Leads] Exhibitor Lead Scanning — IN PROGRESS (demo-ready prep)
|
||||
Module is substantially built as a PWA (no Electron). Core flow works end-to-end.
|
||||
Spec: `documentation/PROJECT__AE_Events_Exhibitor_Leads_v3.md` and `_detail.md`.
|
||||
Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_events__exhibit*.ts`.
|
||||
|
||||
**What's working:**
|
||||
- Exhibit search/landing (`/leads/`) — SWR, local + API search, sort
|
||||
- Exhibit detail page — 4-tab layout, sticky header with Add/List toggle, auto-refresh timer
|
||||
- Tab 1 (Start): sign-in via shared passcode OR licensed user (email + passcode)
|
||||
- Tab 2 (Add): QR scan (rapid vs. qualify mode) + manual badge search; duplicate detection on both
|
||||
- Tab 3 (List): SWR lead list, licensee filter (All / My Leads), sort options, export button
|
||||
- Tab 4 (Manage): admin tools, booth profile edit, passcode, license mgmt, custom questions config, app settings (refresh interval, clear IDB/localStorage, reload)
|
||||
- Lead detail page: view/edit custom question responses, exhibitor notes (TipTap), priority/enable flags
|
||||
- Export wired to legacy `/event/exhibit/{id}/tracking/export` endpoint (CSV; confirm backend is live)
|
||||
|
||||
**Remaining before demo:**
|
||||
- [ ] **Verify export endpoint** — `download_export__event_exhibit_tracking` calls legacy endpoint
|
||||
`/event/exhibit/{exhibit_id}/tracking/export` — confirm it's live on the backend
|
||||
- [x] **`allow_tracking` gate** — implemented (2026-03-16). QR scanner shows a warning card and
|
||||
blocks the add. Manual search shows a ShieldOff "Opt-Out" badge per row and guards `add_as_lead`.
|
||||
Opt-in model: `allow_tracking` must be explicitly `true` on the badge. Also added `allow_tracking`
|
||||
and `agree_to_tc` to `ae_EventBadge` in `ae_types.ts`.
|
||||
**Demo note:** ensure test badges have `allow_tracking = true` or no one can be added.
|
||||
- [ ] **Payment component** — `ae_comp__exhibit_payment.svelte` is a stub (Stripe placeholder only);
|
||||
omit from demo or hide the payment tab via "Show Payment Tab" toggle in Manage settings
|
||||
- [ ] **End-to-end smoke test** — sign in with shared passcode, scan/search a badge, add a lead,
|
||||
view detail, add notes/responses, export CSV; verify on mobile (Chrome/Safari PWA)
|
||||
- [ ] **Install prompt** — spec calls for a PWA install nudge on the Start tab; not yet added
|
||||
|
||||
### [DevOps] Remaining deployment items
|
||||
- [x] **Wire AE_APP_REPLICAS:** `docker-compose.yml` line 147 already has `scale: ${AE_APP_REPLICAS:-1}`. (verified 2026-03-11)
|
||||
@@ -75,6 +96,9 @@ lead record look like in the DB?
|
||||
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
|
||||
|
||||
## ✅ Completed (2026-03)
|
||||
- [x] **[Stores] Phase 1 — Dead code cleanup** (`ae_stores.ts`, `ae_events_stores.ts`, `ae_idaa_stores.ts`): removed `ver_idb`, stale comments, `console.log` lines, Stripe button block (zero consumers), personal Novi UUIDs, dead alternatives. Net: −202 lines across 3 files. svelte-check: 0 errors. (2026-03-16)
|
||||
- [x] **[Stores] Phase 2a — Split defaults into domain sub-files**: `ae_stores__auth_loc_defaults.ts`; `ae_events_stores__badges/launcher/leads/pres_mgmt_defaults.ts`. Spread-merged back into store structs — zero consumer changes. (2026-03-16)
|
||||
- [x] **[Stores] Phase 2b — TypeScript interfaces for defaults sub-files**: `SiteCfgJson`, `AePerson`, `AeUser`, `AccessType`, `AuthLocState`; `BadgesLocState/SessState`; `SectionState`, `LauncherLocState/SessState`; `LeadsLocState/SessState`, `TmpLicense`; `PresMgmtLocState/SessState`. svelte-check: 0 errors. (2026-03-16)
|
||||
- [x] **[UI]** Style Review Phase 1 & 2 complete — all non-frozen, non-IDAA routes migrated: FA→Lucide (events, pres_mgmt, core, badges, leads, hosted_files), `variant-*`→`preset-*` (all modules), `code_to_html` badge dict refactored to Lucide component map, FA CDN scoped to IDAA layout, global `svg.lucide { display: inline }` CSS rule added to fix icon inline flow. See `documentation/PROJECT__AE_Style_Review.md`. (2026-03-16)
|
||||
- [x] **[UI]** Pres Mgmt Phase 3 — FA→Lucide icon migration across all 24 pres_mgmt files. (2026-03-16)
|
||||
- [x] **[IDAA]** `ae_idaa_comp__event_obj_id_edit.svelte` — inlined Tailwind utilities, removed `<style>` block; eliminated all 23 `@apply`/`@reference` svelte-check warnings. (2026-03-16)
|
||||
|
||||
@@ -502,6 +502,10 @@ export interface ae_EventBadge extends ae_BaseObj {
|
||||
print_first_datetime?: string | Date | null;
|
||||
print_last_datetime?: string | Date | null;
|
||||
|
||||
// Exhibitor lead capture opt-in. Must be true to allow adding as a lead.
|
||||
allow_tracking?: boolean | null;
|
||||
agree_to_tc?: boolean | null;
|
||||
|
||||
ticket_list?: any[] | null;
|
||||
data_json?: any;
|
||||
default_qry_str?: string | null;
|
||||
|
||||
@@ -1,71 +1,126 @@
|
||||
# Aether (AE) Event Lead Retrieval Module (v3)
|
||||
# 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
|
||||
|
||||
This is a major work in progress. It still needs the overall directory and file structure planned, documented, and created.
|
||||
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).
|
||||
|
||||
This module provides a comprehensive solution for event exhibitors to capture and manage leads. It supports exhibitor login, badge scanning for lead capture, manual lead entry, lead qualification (ranking/priority), and data export functionalities. It integrates with the Aether API for data synchronization and utilizes local caching for performance and offline capabilities.
|
||||
---
|
||||
|
||||
## Key Features
|
||||
## Route Structure
|
||||
|
||||
- **Exhibitor Login:** Secure access for exhibitors using a shared passcode or individual license keys (email-based).
|
||||
- **Lead Capture:** Efficiently add new leads by scanning attendee badge QR codes or through manual data entry.
|
||||
- **Lead Management:** A dedicated interface for exhibitors to view, sort, and manage their collected leads.
|
||||
- **Lead Qualification:** Tools to rank and prioritize leads (e.g., sort, star/favorite) for follow-up.
|
||||
- **Data Export:** Functionality to export collected lead data, typically to an Excel-compatible format.
|
||||
- **Real-time Synchronization:** Data is synchronized with the backend Aether API.
|
||||
- **Local Caching:** Utilizes IndexedDB (Dexie.js) for robust local data storage, enabling offline use and faster load times.
|
||||
```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
|
||||
```
|
||||
|
||||
## Data Model
|
||||
---
|
||||
|
||||
The core data structures managed by this module are `Exhibit` and `Exhibit_tracking`. The actual DB table name is event_person_tracking. It was originally intended for generalized "tracking" of a person at an event. This would include other things like session attendance tracking, in addition to exhibitor lead retrieval. However, the initial implementation is focused solely on the lead retrieval use case for exhibitors.
|
||||
|
||||
### Exhibit
|
||||
|
||||
Represents an exhibitor's presence at an event. It contains configuration for lead retrieval, such as:
|
||||
|
||||
- `event_exhibit_id`: Unique identifier for the exhibit (primary key).
|
||||
- `code`, `name`, `description`: Basic information about the exhibit.
|
||||
- `staff_passcode`: For staff login.
|
||||
- `leads_api_access`: Boolean indicating if lead retrieval API access is enabled.
|
||||
- `leads_custom_questions_json`: Configuration for custom questions asked during lead capture.
|
||||
- `license_max`, `license_li_json`: Details regarding lead retrieval licenses.
|
||||
|
||||
### Exhibit_tracking
|
||||
|
||||
Represents a single lead captured by an exhibitor. It links an exhibitor to an attendee (badge) and stores details about the interaction:
|
||||
|
||||
- `event_exhibit_tracking_id`: Unique identifier for the captured lead (primary key).
|
||||
- `event_exhibit_id`: Links to the `Exhibit` that captured the lead.
|
||||
- `event_badge_id`: Links to the attendee's `Badge` information.
|
||||
- `exhibitor_notes`: Notes added by the exhibitor about the lead.
|
||||
- `responses_json`: Responses to custom questions.
|
||||
- Contains duplicated badge information for convenience (e.g., `event_badge_full_name`, `event_badge_email`, `event_badge_professional_title`, `event_badge_affiliations`) to denormalize data for faster display.
|
||||
|
||||
## Routing and Components
|
||||
## Key Files
|
||||
|
||||
### Routes
|
||||
|
||||
- `/events/[event_id]/(leads)`: The main entry point for the Leads module within a specific event, typically displays a list of available exhibits.
|
||||
- `+page.svelte`: Renders the list of exhibits.
|
||||
- `+page.ts`: Loads the data for available exhibits using `events_func.load_ae_obj_li__event_exhibit`.
|
||||
- `+layout.svelte`/`+layout.ts`: Provides a common layout and data for the module, including a submenu.
|
||||
- `/events/[event_id]/(leads)/exhibit/[slug]`: Dynamic route for managing leads for a specific exhibitor within an event. The `[slug]` corresponds to `event_exhibit_id`.
|
||||
- `+page.svelte`: The primary interface for an exhibitor, orchestrating lead capture and management components.
|
||||
- `+page.ts`: Loads specific `Exhibit` data and associated `Exhibit_tracking` (leads) using `events_func.load_ae_obj_id__event_exhibit` and `events_func.load_ae_obj_li__event_exhibit_tracking`.
|
||||
| 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 |
|
||||
|
||||
### Core Components (within `src/routes/events/[event_id]/(leads)/exhibit/[slug]/`)
|
||||
### Components (within `exhibit/[exhibit_id]/`)
|
||||
|
||||
- `leads_add_scan.svelte`: Handles the process of adding new leads, either by scanning QR codes (badge) or manual entry.
|
||||
- `leads_list.svelte`: Displays a sortable and filterable list of captured leads for the current exhibitor.
|
||||
- `leads_manage.svelte`: Provides functionalities for managing individual leads, potentially including editing notes or qualification status.
|
||||
- `leads_payment.svelte`: (If applicable) Component related to handling payment or license activation for lead retrieval features.
|
||||
- `leads_view_lead.svelte`: Displays detailed information for a selected lead.
|
||||
| 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` | **STUB** — Stripe placeholder, not functional |
|
||||
| `ae_comp__exhibit_search.svelte` | Exhibit search input on the landing page |
|
||||
|
||||
## Technical Implementation
|
||||
### Lead detail components (within `lead/[exhibit_tracking_id]/`)
|
||||
|
||||
- **Svelte v5 with Runes:** The module adheres to Svelte v5's reactivity model, utilizing `$state` and `$derived` for reactive state management.
|
||||
- **ID Convention (`id`):** All frontend operations, including routing and data fetching, consistently use the string-based `event_exhibit_id` and `event_exhibit_tracking_id` as primary identifiers, rather than numeric `id` values.
|
||||
- **API Interaction:** Data is fetched and synchronized with the backend FastAPI application through functions exposed in `src/lib/ae_events_functions.ts`.
|
||||
- **Local Database (Dexie.js):** Data for `Exhibit` and `Exhibit_tracking` is cached in the browser's IndexedDB using Dexie.js, defined in `src/lib/ae_events/db_events.ts`. This ensures data persistence and fast retrieval, especially for offline scenarios.
|
||||
- **Styling:** The UI is primarily styled using Tailwind CSS, having migrated from Skeleton UI to resolve previous rendering issues.
|
||||
| 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
|
||||
|
||||
- **`allow_tracking` gate** — spec says badge must have `allow_tracking = true` before a lead can
|
||||
be added. Neither the QR scanner nor manual search enforce this yet.
|
||||
- **Payment / Stripe** — `ae_comp__exhibit_payment.svelte` is a stub. The payment tab can be
|
||||
hidden via the "Show Payment Tab" toggle in the Manage tab's App Settings.
|
||||
- **Export endpoint** — `download_export__event_exhibit_tracking` calls a legacy V1 endpoint
|
||||
(`/event/exhibit/{id}/tracking/export`). Verify it's live on the backend before demoing export.
|
||||
- **PWA install prompt** — spec calls for nudging exhibitors to install as a PWA on the Start tab.
|
||||
|
||||
---
|
||||
|
||||
## 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_functions.ts`.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Loader2 } from '@lucide/svelte';
|
||||
|
||||
interface Props {
|
||||
lq__event_exhibit_tracking_obj_li: any;
|
||||
log_lvl?: number;
|
||||
@@ -14,7 +12,8 @@
|
||||
MapPin,
|
||||
Clock,
|
||||
FileText,
|
||||
ChevronRight
|
||||
ChevronRight,
|
||||
LoaderCircle
|
||||
} from 'lucide-svelte';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { page } from '$app/state';
|
||||
@@ -66,7 +65,7 @@
|
||||
<div class="ae_comp__exhibit_tracking_obj_li w-full px-2 sm:px-4">
|
||||
{#if !lq__event_exhibit_tracking_obj_li}
|
||||
<div class="flex justify-center p-10">
|
||||
<Loader2 size="2rem" class="animate-spin opacity-20" aria-hidden="true" />
|
||||
<LoaderCircle size="2rem" class="animate-spin opacity-20" aria-hidden="true" />
|
||||
</div>
|
||||
{:else if lq__event_exhibit_tracking_obj_li.length === 0}
|
||||
<div class="card p-8 text-center preset-tonal-surface">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { ae_api } from '$lib/stores/ae_stores';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events_functions';
|
||||
import { Search, UserPlus, CheckCircle, LoaderCircle, Eye } from 'lucide-svelte';
|
||||
import { Search, UserPlus, LoaderCircle, Eye, ShieldOff } from 'lucide-svelte';
|
||||
import type { ae_EventBadge } from '$lib/types/ae_types';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
|
||||
@@ -73,6 +73,15 @@
|
||||
console.warn('[add_as_lead] badge missing event_badge_id_random and event_badge_id', badge);
|
||||
return;
|
||||
}
|
||||
|
||||
// Gate: attendee must have opted in to lead tracking (allow_tracking must be explicitly true).
|
||||
// Defensive guard — the UI already hides the Add button for blocked badges,
|
||||
// but this prevents any direct/programmatic calls from bypassing the check.
|
||||
if (badge.allow_tracking !== true) {
|
||||
console.warn('[add_as_lead] blocked — allow_tracking is not true for badge', badge_id);
|
||||
return;
|
||||
}
|
||||
|
||||
adding_id = badge_id;
|
||||
add_error_id = '';
|
||||
|
||||
@@ -156,6 +165,12 @@
|
||||
<Eye size="1em" class="mr-1" />
|
||||
View
|
||||
</a>
|
||||
{:else if badge.allow_tracking !== true}
|
||||
<!-- Attendee has not opted in to tracking — cannot add as lead -->
|
||||
<span class="flex items-center gap-1 text-xs text-warning-500 font-bold opacity-70" title="This attendee has not opted in to exhibitor lead tracking.">
|
||||
<ShieldOff size="1em" />
|
||||
Opt-Out
|
||||
</span>
|
||||
{:else if add_error_id === badge_id}
|
||||
<span class="text-xs text-error-500 font-bold">Add failed — retry?
|
||||
<button type="button" class="btn btn-sm preset-outlined-error ml-1" onclick={() => add_as_lead(badge)}>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { events_func } from '$lib/ae_events_functions';
|
||||
import Element_qr_scanner_v3 from '$lib/element_qr_scanner_v3.svelte';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { LoaderCircle, UserPlus, CheckCircle, CircleAlert, Eye } from 'lucide-svelte';
|
||||
import { LoaderCircle, UserPlus, CircleCheck, CircleAlert, Eye, ShieldOff } from 'lucide-svelte';
|
||||
import type { ae_EventBadge } from '$lib/types/ae_types';
|
||||
|
||||
interface Props {
|
||||
@@ -45,7 +45,7 @@
|
||||
);
|
||||
|
||||
let start_qr_scanner = $state(true);
|
||||
let scanning_status = $state('idle'); // idle, scanning, found, adding, success, error, already_added
|
||||
let scanning_status = $state('idle'); // idle, scanning, found, adding, success, error, already_added, tracking_blocked
|
||||
let found_badge: ae_EventBadge | null = $state(null);
|
||||
let existing_tracking_id = $state('');
|
||||
let new_tracking_id = $state(''); // ID of the lead just created — used for "View Details" link
|
||||
@@ -66,13 +66,20 @@
|
||||
scanning_status = 'found';
|
||||
}
|
||||
|
||||
// Load full badge info
|
||||
// Load full badge info (needed for allow_tracking check and display)
|
||||
try {
|
||||
found_badge = await events_func.load_ae_obj_id__event_badge({
|
||||
api_cfg: $ae_api,
|
||||
event_badge_id: obj.id,
|
||||
log_lvl: 1
|
||||
});
|
||||
|
||||
// Gate: attendee must have opted in to lead tracking.
|
||||
// allow_tracking must be explicitly true — default on badges is false (opt-in model).
|
||||
// Only applies to the 'found' state; already-captured badges are left as-is.
|
||||
if (scanning_status === 'found' && found_badge?.allow_tracking !== true) {
|
||||
scanning_status = 'tracking_blocked';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load badge info', e);
|
||||
}
|
||||
@@ -139,10 +146,30 @@
|
||||
</div>
|
||||
<p class="text-center opacity-70 italic text-sm">Point camera at the badge QR code</p>
|
||||
|
||||
{:else if scanning_status === 'tracking_blocked'}
|
||||
<div class="card p-6 w-full max-w-md space-y-4 preset-tonal-warning shadow-xl border-2 border-warning-500 animate-in zoom-in">
|
||||
<div class="text-center space-y-2">
|
||||
<ShieldOff size="3em" class="mx-auto text-warning-500" />
|
||||
<h3 class="h3 font-bold">Tracking Opt-Out</h3>
|
||||
<p class="text-xl font-bold">{found_badge?.full_name || 'Attendee'}</p>
|
||||
<p class="opacity-70 text-sm">
|
||||
This attendee has not opted in to exhibitor lead tracking
|
||||
(<code>allow_tracking</code> is not set on their badge).
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn w-full preset-filled-warning font-bold"
|
||||
onclick={reset_scanner}
|
||||
>
|
||||
Scan Next
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{:else if scanning_status === 'already_added'}
|
||||
<div class="card p-6 w-full max-w-md space-y-4 preset-tonal-secondary shadow-xl border-2 border-secondary-500 animate-in zoom-in">
|
||||
<div class="text-center space-y-2">
|
||||
<CheckCircle size="3em" class="mx-auto text-secondary-500" />
|
||||
<CircleCheck size="3em" class="mx-auto text-secondary-500" />
|
||||
<h3 class="h3 font-bold">Already Captured</h3>
|
||||
<p class="text-xl font-bold">{found_badge?.full_name || 'Attendee'}</p>
|
||||
<p class="opacity-70 text-sm">This attendee is already in your leads list.</p>
|
||||
@@ -199,7 +226,7 @@
|
||||
|
||||
{:else if scanning_status === 'success'}
|
||||
<div class="card p-10 w-full max-w-md flex flex-col items-center space-y-4 preset-tonal-success shadow-xl">
|
||||
<CheckCircle size="4em" class="text-success-500 animate-bounce" />
|
||||
<CircleCheck size="4em" class="text-success-500 animate-bounce" />
|
||||
<div class="text-center">
|
||||
<h3 class="h4 font-bold">Lead Added!</h3>
|
||||
<p class="text-xl font-bold">{found_badge?.full_name}</p>
|
||||
|
||||
Reference in New Issue
Block a user