docs(leads): document Leads store migration and payment UI fix; note tests update
This commit is contained in:
@@ -138,6 +138,17 @@ Two scan modes (toggled per exhibit):
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
|
||||
@@ -3,27 +3,17 @@ import { onMount, untrack } from 'svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import {
|
||||
events_loc,
|
||||
events_sess,
|
||||
events_slct
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { page } from '$app/state';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { LoaderCircle, Store } from '@lucide/svelte';
|
||||
import Comp_exhibit_search from './ae_comp__exhibit_search.svelte';
|
||||
|
||||
// *** Initialization & Store Guard ***
|
||||
if ($events_loc.leads) {
|
||||
if (typeof $events_loc.leads.search_version === 'undefined')
|
||||
$events_loc.leads.search_version = 0;
|
||||
if (typeof $events_loc.leads.qry__remote_first === 'undefined')
|
||||
$events_loc.leads.qry__remote_first = false;
|
||||
if (typeof $events_loc.leads.qry__search_text === 'undefined')
|
||||
$events_loc.leads.qry__search_text = '';
|
||||
if (typeof $events_loc.leads.qry__sort_order === 'undefined')
|
||||
$events_loc.leads.qry__sort_order = 'name_asc';
|
||||
}
|
||||
// leads_loc is a PersistedState store — defaults are always initialized.
|
||||
|
||||
let exhibit_id_li: Array<string> = $state([]);
|
||||
let search_debounce_timer: any = null;
|
||||
@@ -44,7 +34,7 @@ let lq__event_exhibit_obj_li = $derived.by(() => {
|
||||
}
|
||||
|
||||
// SCENARIO 2: Fallback broad search
|
||||
if (event_id && !$events_loc.leads.qry__search_text) {
|
||||
if (event_id && !leads_loc.current.qry__search_text) {
|
||||
return await db_events.exhibit
|
||||
.where('event_id')
|
||||
.equals(event_id)
|
||||
@@ -57,11 +47,11 @@ let lq__event_exhibit_obj_li = $derived.by(() => {
|
||||
|
||||
// Standardized Reactive Search Pattern
|
||||
let search_params = $derived({
|
||||
v: $events_loc.leads.search_version,
|
||||
str: ($events_loc.leads.qry__search_text ?? '').toLowerCase().trim(),
|
||||
sort: $events_loc.leads.qry__sort_order,
|
||||
v: leads_loc.current.search_version,
|
||||
str: (leads_loc.current.qry__search_text ?? '').toLowerCase().trim(),
|
||||
sort: leads_loc.current.qry__sort_order,
|
||||
event_id: page.params.event_id,
|
||||
remote_first: $events_loc.leads.qry__remote_first
|
||||
remote_first: leads_loc.current.qry__remote_first
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -14,13 +14,11 @@ import {
|
||||
Search
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
|
||||
function handle_search_trigger() {
|
||||
if ($events_loc.leads.search_version === undefined) {
|
||||
$events_loc.leads.search_version = 0;
|
||||
}
|
||||
$events_loc.leads.search_version++;
|
||||
leads_loc.current.search_version++;
|
||||
}
|
||||
|
||||
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
@@ -45,7 +43,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
type="search"
|
||||
placeholder="Exhibitor name or code..."
|
||||
id="exhibit_fulltext_search_qry_str"
|
||||
bind:value={$events_loc.leads.qry__search_text}
|
||||
bind:value={leads_loc.current.qry__search_text}
|
||||
autocomplete="off"
|
||||
data-lpignore="true"
|
||||
class="input grow font-mono text-lg transition-all"
|
||||
@@ -57,7 +55,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
title="Search by name or code. Press Enter." />
|
||||
|
||||
<select
|
||||
bind:value={$events_loc.leads.qry__sort_order}
|
||||
bind:value={leads_loc.current.qry__sort_order}
|
||||
onchange={handle_search_trigger}
|
||||
class="select select-sm max-w-fit px-1 text-xs">
|
||||
<option value="name_asc">Name ASC</option>
|
||||
@@ -82,9 +80,9 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class:hidden={!$events_loc.leads.qry__search_text}
|
||||
class:hidden={!leads_loc.current.qry__search_text}
|
||||
onclick={() => {
|
||||
$events_loc.leads.qry__search_text = '';
|
||||
leads_loc.current.qry__search_text = '';
|
||||
handle_search_trigger();
|
||||
}}
|
||||
class="btn btn-sm preset-outlined-tertiary-100-900 hover:preset-filled-tertiary-100-900 text-xs transition-all"
|
||||
@@ -103,7 +101,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
<span> Remote First </span>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$events_loc.leads.qry__remote_first}
|
||||
bind:checked={leads_loc.current.qry__remote_first}
|
||||
onchange={handle_search_trigger}
|
||||
class="checkbox checkbox-sm" />
|
||||
</label>
|
||||
|
||||
@@ -3,10 +3,10 @@ import { onMount, untrack } from 'svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import {
|
||||
events_loc,
|
||||
events_sess,
|
||||
events_slct
|
||||
} from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { page } from '$app/state';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
@@ -27,36 +27,24 @@ import Tab_start from './ae_tab__start.svelte';
|
||||
import Tab_manage from './ae_tab__manage.svelte';
|
||||
import Comp_exhibit_payment from './ae_comp__exhibit_payment.svelte';
|
||||
|
||||
// *** Initialization & Store Guard ***
|
||||
if ($events_loc.leads) {
|
||||
if (typeof $events_loc.leads.tracking__search_version === 'undefined')
|
||||
$events_loc.leads.tracking__search_version = 0;
|
||||
if (typeof $events_loc.leads.tracking__qry__remote_first === 'undefined')
|
||||
$events_loc.leads.tracking__qry__remote_first = false;
|
||||
if (typeof $events_loc.leads.tracking__qry__search_text === 'undefined')
|
||||
$events_loc.leads.tracking__qry__search_text = '';
|
||||
if (typeof $events_loc.leads.tracking__qry__sort_order === 'undefined')
|
||||
$events_loc.leads.tracking__qry__sort_order = 'created_desc';
|
||||
if (typeof $events_loc.leads.refresh_interval_sec === 'undefined')
|
||||
$events_loc.leads.refresh_interval_sec = 25;
|
||||
if (typeof $events_loc.leads.show_hidden === 'undefined')
|
||||
$events_loc.leads.show_hidden = false;
|
||||
}
|
||||
// leads_loc is a PersistedState store — defaults are always initialized.
|
||||
|
||||
// --- Sign-In State (Derived) ---
|
||||
// 1. Manager Access (Bypass) OR 2. Valid Exhibit Auth entry
|
||||
let is_signed_in = $derived(
|
||||
$ae_loc.manager_access ||
|
||||
!!$events_loc.leads.auth_exhibit_kv?.[page.params.exhibit_id ?? '']
|
||||
!!leads_loc.current.auth_exhibit_kv?.[page.params.exhibit_id ?? '']
|
||||
);
|
||||
|
||||
// --- Tab State (Sticky via Store) ---
|
||||
let active_tab = $derived.by(() => {
|
||||
const exhibit_id = page.params.exhibit_id;
|
||||
if (!exhibit_id) return 'start';
|
||||
const saved_tab = $events_loc.leads.tab?.[exhibit_id] ?? 'list';
|
||||
const saved_tab = leads_loc.current.tab?.[exhibit_id] ?? 'list';
|
||||
// If signed in but stuck on start tab, go to list
|
||||
if (is_signed_in && saved_tab === 'start') return 'list';
|
||||
// If payment tab was saved but payments are no longer required, fall back to list
|
||||
if (saved_tab === 'payment' && !leads_require_payment) return 'list';
|
||||
return saved_tab;
|
||||
});
|
||||
let previous_main_tab = $state('list'); // To remember if we were on 'add' or 'list' before going to 'manage'
|
||||
@@ -64,8 +52,7 @@ let previous_main_tab = $state('list'); // To remember if we were on 'add' or 'l
|
||||
function set_active_tab(new_tab: string) {
|
||||
const exhibit_id = page.params.exhibit_id;
|
||||
if (!exhibit_id) return;
|
||||
if (!$events_loc.leads.tab) $events_loc.leads.tab = {};
|
||||
$events_loc.leads.tab[exhibit_id] = new_tab;
|
||||
leads_loc.current.tab[exhibit_id] = new_tab;
|
||||
}
|
||||
|
||||
let tracking_id_li: Array<string> = $state([]);
|
||||
@@ -99,7 +86,7 @@ let filtered_lead_li = $derived.by(() => {
|
||||
$effect(() => {
|
||||
const ids = tracking_id_li;
|
||||
const exhibit_id = page.params.exhibit_id;
|
||||
const has_search = !!$events_loc.leads.tracking__qry__search_text;
|
||||
const has_search = !!leads_loc.current.tracking__qry__search_text;
|
||||
|
||||
const observable = liveQuery(async () => {
|
||||
// 1. Specific IDs provided (from API Search or Manual Entry)
|
||||
@@ -159,28 +146,28 @@ let stripe_cfg = $derived({
|
||||
|
||||
// Standardized Reactive Search Pattern
|
||||
let search_params = $derived.by(() => {
|
||||
let licensee_email = $events_loc.leads.tracking__qry__licensee_email;
|
||||
let licensee_email = leads_loc.current.tracking__qry__licensee_email;
|
||||
|
||||
// Resolve "My Leads" to the correct identity used when storing leads.
|
||||
// Shared-passcode users store 'shared_passcode' literal (not the passcode string itself).
|
||||
// Licensed users store their email. Aether bypass users store $ae_loc.access_type.
|
||||
if (licensee_email === 'my') {
|
||||
const kv = $events_loc.leads.auth_exhibit_kv?.[page.params.exhibit_id ?? ''];
|
||||
const kv = leads_loc.current.auth_exhibit_kv?.[page.params.exhibit_id ?? ''];
|
||||
licensee_email = kv?.type === 'shared'
|
||||
? 'shared_passcode'
|
||||
: kv?.key || 'all';
|
||||
}
|
||||
|
||||
return {
|
||||
v: $events_loc.leads.tracking__search_version,
|
||||
str: ($events_loc.leads.tracking__qry__search_text ?? '')
|
||||
v: leads_loc.current.tracking__search_version,
|
||||
str: (leads_loc.current.tracking__qry__search_text ?? '')
|
||||
.toLowerCase()
|
||||
.trim(),
|
||||
sort: $events_loc.leads.tracking__qry__sort_order,
|
||||
sort: leads_loc.current.tracking__qry__sort_order,
|
||||
licensee_email: licensee_email,
|
||||
exhibit_id: page.params.exhibit_id,
|
||||
remote_first: $events_loc.leads.tracking__qry__remote_first,
|
||||
show_hidden: $events_loc.leads.show_hidden ?? false
|
||||
remote_first: leads_loc.current.tracking__qry__remote_first,
|
||||
show_hidden: leads_loc.current.show_hidden ?? false
|
||||
};
|
||||
});
|
||||
|
||||
@@ -192,7 +179,7 @@ $effect(() => {
|
||||
handle_search_refresh(params);
|
||||
// Reset countdown on manual search
|
||||
$events_sess.leads.next_refresh_countdown =
|
||||
$events_loc.leads.refresh_interval_sec || 25;
|
||||
leads_loc.current.refresh_interval_sec || 25;
|
||||
});
|
||||
}, 300);
|
||||
return () => {
|
||||
@@ -210,9 +197,9 @@ $effect(() => {
|
||||
$events_sess.leads.next_refresh_countdown--;
|
||||
} else {
|
||||
// Trigger refresh
|
||||
$events_loc.leads.tracking__search_version++;
|
||||
leads_loc.current.tracking__search_version++;
|
||||
$events_sess.leads.next_refresh_countdown =
|
||||
$events_loc.leads.refresh_interval_sec || 25;
|
||||
leads_loc.current.refresh_interval_sec || 25;
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
@@ -502,9 +489,9 @@ function toggle_manage_tab() {
|
||||
</div>
|
||||
{:else if active_tab === 'add'}
|
||||
<Tab_add exhibit_id={page.params.exhibit_id ?? ''} />
|
||||
{:else if active_tab === 'payment'}
|
||||
{:else if active_tab === 'payment' && leads_require_payment}
|
||||
<div class="mx-auto w-full max-w-4xl">
|
||||
<Comp_exhibit_payment exhibit_id={page.params.exhibit_id ?? ''} {...stripe_cfg} />
|
||||
<Comp_exhibit_payment exhibit_id={page.params.exhibit_id ?? ''} {...stripe_cfg} leads_require_payment={leads_require_payment} />
|
||||
</div>
|
||||
{:else if active_tab === 'list'}
|
||||
<div class="flex w-full flex-col space-y-6">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { AlertTriangle, CheckCircle, CreditCard } from '@lucide/svelte';
|
||||
import { AlertTriangle, CheckCircle, CreditCard, LoaderCircle } from '@lucide/svelte';
|
||||
|
||||
interface Props {
|
||||
exhibit_id: string;
|
||||
@@ -30,6 +30,8 @@ interface Props {
|
||||
stripe_btn_3_license?: string | null;
|
||||
stripe_btn_6_license?: string | null;
|
||||
stripe_btn_10_license?: string | null;
|
||||
/** Event-level flag: when false, payment UI is hidden (pre-paid events) */
|
||||
leads_require_payment?: boolean;
|
||||
}
|
||||
let {
|
||||
exhibit_id,
|
||||
@@ -38,6 +40,7 @@ let {
|
||||
stripe_btn_3_license: prop_btn_3 = null,
|
||||
stripe_btn_6_license: prop_btn_6 = null,
|
||||
stripe_btn_10_license: prop_btn_10 = null
|
||||
, leads_require_payment = true
|
||||
}: Props = $props();
|
||||
|
||||
const lq__exhibit_obj = liveQuery(() => {
|
||||
@@ -85,7 +88,13 @@ $effect(() => {
|
||||
</script>
|
||||
|
||||
<div class="exhibit-payment space-y-6">
|
||||
{#if $lq__exhibit_obj?.priority}
|
||||
{#if $lq__exhibit_obj === undefined}
|
||||
<div class="py-6 text-center">
|
||||
<LoaderCircle size="2em" class="mx-auto mb-2 animate-spin" />
|
||||
<div class="text-sm opacity-70">Loading booth status…</div>
|
||||
</div>
|
||||
|
||||
{:else if $lq__exhibit_obj?.priority}
|
||||
<!-- Paid Confirmation — shown when admin has marked this booth as paid (priority = true) -->
|
||||
<div class="card preset-tonal-success border-success-500/30 border p-6">
|
||||
<div class="mb-3 flex items-center gap-3">
|
||||
@@ -109,6 +118,9 @@ $effect(() => {
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{:else if !leads_require_payment}
|
||||
<p class="py-4 text-center text-sm opacity-60">Payment is not required for this event.</p>
|
||||
|
||||
{:else if !is_stripe_configured}
|
||||
<!-- Stripe not configured — show a setup hint only to admins -->
|
||||
{#if $ae_loc.administrator_access}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { page } from '$app/state';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import {
|
||||
ArrowRight,
|
||||
CircleAlert,
|
||||
@@ -101,10 +102,7 @@ function complete_signin(key: string, type: string) {
|
||||
status = 'success';
|
||||
|
||||
// Save to persistent store
|
||||
if (!$events_loc.leads.auth_exhibit_kv)
|
||||
$events_loc.leads.auth_exhibit_kv = {};
|
||||
|
||||
$events_loc.leads.auth_exhibit_kv[exhibit_id] = {
|
||||
leads_loc.current.auth_exhibit_kv[exhibit_id] = {
|
||||
key: key,
|
||||
type: type,
|
||||
updated_on: new Date().toISOString()
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
} from '@lucide/svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import { ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -68,19 +69,16 @@ $effect(() => {
|
||||
|
||||
untrack(() => {
|
||||
if (
|
||||
$events_loc.leads.tracking__qry__licensee_email === 'all' &&
|
||||
leads_loc.current.tracking__qry__licensee_email === 'all' &&
|
||||
!$ae_loc.administrator_access
|
||||
) {
|
||||
$events_loc.leads.tracking__qry__licensee_email = 'my';
|
||||
leads_loc.current.tracking__qry__licensee_email = 'my';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function handle_search_trigger() {
|
||||
if ($events_loc.leads.tracking__search_version === undefined) {
|
||||
$events_loc.leads.tracking__search_version = 0;
|
||||
}
|
||||
$events_loc.leads.tracking__search_version++;
|
||||
leads_loc.current.tracking__search_version++;
|
||||
}
|
||||
|
||||
function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
@@ -105,7 +103,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
type="search"
|
||||
placeholder="Search leads (name, email, notes)..."
|
||||
id="exhibit_tracking_fulltext_search_qry_str"
|
||||
bind:value={$events_loc.leads.tracking__qry__search_text}
|
||||
bind:value={leads_loc.current.tracking__qry__search_text}
|
||||
autocomplete="off"
|
||||
data-lpignore="true"
|
||||
class="input grow font-mono text-lg transition-all"
|
||||
@@ -117,7 +115,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
title="Search by name, email or notes. Press Enter." />
|
||||
|
||||
<select
|
||||
bind:value={$events_loc.leads.tracking__qry__sort_order}
|
||||
bind:value={leads_loc.current.tracking__qry__sort_order}
|
||||
onchange={handle_search_trigger}
|
||||
class="select select-sm max-w-fit px-1 text-xs">
|
||||
<option value="created_desc">Newest First</option>
|
||||
@@ -127,7 +125,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
</select>
|
||||
|
||||
<select
|
||||
bind:value={$events_loc.leads.tracking__qry__licensee_email}
|
||||
bind:value={leads_loc.current.tracking__qry__licensee_email}
|
||||
onchange={handle_search_trigger}
|
||||
class="select select-sm max-w-fit px-1 text-xs">
|
||||
<option value="all">All Leads</option>
|
||||
@@ -154,9 +152,9 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class:hidden={!$events_loc.leads.tracking__qry__search_text}
|
||||
class:hidden={!leads_loc.current.tracking__qry__search_text}
|
||||
onclick={() => {
|
||||
$events_loc.leads.tracking__qry__search_text = '';
|
||||
leads_loc.current.tracking__qry__search_text = '';
|
||||
handle_search_trigger();
|
||||
}}
|
||||
class="btn btn-sm preset-outlined-tertiary-100-900 hover:preset-filled-tertiary-100-900 text-xs transition-all"
|
||||
@@ -173,16 +171,16 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-token flex cursor-pointer items-center gap-1 px-2 py-1 text-xs font-semibold transition-colors"
|
||||
class:preset-tonal-warning={$events_loc.leads.show_hidden}
|
||||
class:preset-tonal-surface={!$events_loc.leads.show_hidden}
|
||||
class:preset-tonal-warning={leads_loc.current.show_hidden}
|
||||
class:preset-tonal-surface={!leads_loc.current.show_hidden}
|
||||
onclick={() => {
|
||||
$events_loc.leads.show_hidden = !$events_loc.leads.show_hidden;
|
||||
leads_loc.current.show_hidden = !leads_loc.current.show_hidden;
|
||||
handle_search_trigger();
|
||||
}}
|
||||
title={$events_loc.leads.show_hidden
|
||||
title={leads_loc.current.show_hidden
|
||||
? 'Showing hidden leads — click to hide them'
|
||||
: 'Hidden leads excluded — click to show all'}>
|
||||
{#if $events_loc.leads.show_hidden}
|
||||
{#if leads_loc.current.show_hidden}
|
||||
<Eye size="1em" />
|
||||
<span>Showing Hidden</span>
|
||||
{:else}
|
||||
@@ -197,7 +195,7 @@ function prevent_default<T extends Event>(fn: (event: T) => void) {
|
||||
<span> Remote First </span>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$events_loc.leads.tracking__qry__remote_first}
|
||||
bind:checked={leads_loc.current.tracking__qry__remote_first}
|
||||
onchange={handle_search_trigger}
|
||||
class="checkbox checkbox-sm" />
|
||||
</label>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { page } from '$app/state';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { Eye, LoaderCircle, Search, ShieldOff, UserPlus } from '@lucide/svelte';
|
||||
import type { ae_EventBadge } from '$lib/types/ae_types';
|
||||
@@ -98,7 +98,7 @@ async function add_as_lead(badge: ae_EventBadge) {
|
||||
adding_id = badge_id;
|
||||
add_error_id = '';
|
||||
|
||||
const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id];
|
||||
const kv = leads_loc.current.auth_exhibit_kv?.[exhibit_id];
|
||||
const user_email =
|
||||
kv?.type === 'licensed' && kv.key
|
||||
? kv.key
|
||||
|
||||
@@ -12,7 +12,7 @@ import { goto } from '$app/navigation';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import Element_qr_scanner from '$lib/elements/element_qr_scanner.svelte';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
@@ -136,7 +136,7 @@ async function confirm_add_lead(dest: 'scan_next' | 'view_lead' = 'scan_next') {
|
||||
// licensed exhibit user → their email (kv.key)
|
||||
// shared passcode → 'shared_passcode' label (don't store the actual passcode)
|
||||
// Aether user (no kv) → access_type string ('trusted', 'manager', 'super', etc.)
|
||||
const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id];
|
||||
const kv = leads_loc.current.auth_exhibit_kv?.[exhibit_id];
|
||||
const user_email =
|
||||
kv?.type === 'licensed' && kv.key
|
||||
? kv.key
|
||||
|
||||
@@ -17,7 +17,7 @@ import { page } from '$app/state';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import type { ae_EventBadge } from '$lib/types/ae_types';
|
||||
@@ -222,7 +222,7 @@ async function add_lead(item: BatchItem) {
|
||||
if (item.status !== 'ready' || !item.badge?.event_badge_id) return;
|
||||
item.status = 'adding';
|
||||
|
||||
const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id];
|
||||
const kv = leads_loc.current.auth_exhibit_kv?.[exhibit_id];
|
||||
const user_email =
|
||||
kv?.type === 'licensed' && kv.key
|
||||
? kv.key
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Bot, ChevronDown, Layers, QrCode, Search, Zap } from '@lucide/svelte';
|
||||
import Comp_lead_qr_scanner from './ae_comp__lead_qr_scanner.svelte';
|
||||
import Comp_lead_qr_scanner_multi from './ae_comp__lead_qr_scanner_multi.svelte';
|
||||
import Comp_lead_manual_search from './ae_comp__lead_manual_search.svelte';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
|
||||
interface Props {
|
||||
exhibit_id: string;
|
||||
@@ -24,31 +24,28 @@ interface Props {
|
||||
let { exhibit_id }: Props = $props();
|
||||
|
||||
// QR vs Manual Search (persisted per exhibit)
|
||||
let mode = $derived($events_loc.leads.tab_add_mode?.[exhibit_id] ?? 'qr');
|
||||
let mode = $derived(leads_loc.current.tab_add_mode?.[exhibit_id] ?? 'qr');
|
||||
|
||||
function set_mode(new_mode: string) {
|
||||
if (!$events_loc.leads.tab_add_mode) $events_loc.leads.tab_add_mode = {};
|
||||
$events_loc.leads.tab_add_mode[exhibit_id] = new_mode;
|
||||
leads_loc.current.tab_add_mode[exhibit_id] = new_mode;
|
||||
}
|
||||
|
||||
// Scan qualify mode (persisted per exhibit)
|
||||
// 'qualify' was merged into 'rapid' — normalize stale localStorage values
|
||||
type ScanQualifyMode = 'rapid' | 'auto' | 'multi';
|
||||
let scan_qualify = $derived.by(() => {
|
||||
const raw = $events_loc.leads.tab_scan_qualify?.[exhibit_id] ?? 'rapid';
|
||||
const raw = leads_loc.current.tab_scan_qualify?.[exhibit_id] ?? 'rapid';
|
||||
// 'qualify' was merged into 'rapid' — normalize stale localStorage values
|
||||
return (raw === 'qualify' ? 'rapid' : raw) as ScanQualifyMode;
|
||||
});
|
||||
|
||||
function set_scan_qualify(new_mode: ScanQualifyMode) {
|
||||
if (!$events_loc.leads.tab_scan_qualify)
|
||||
$events_loc.leads.tab_scan_qualify = {};
|
||||
$events_loc.leads.tab_scan_qualify[exhibit_id] = new_mode;
|
||||
leads_loc.current.tab_scan_qualify[exhibit_id] = new_mode;
|
||||
show_mode_opts = false;
|
||||
}
|
||||
|
||||
function handle_lead_added(badge: any) {
|
||||
$events_loc.leads.tracking__search_version++;
|
||||
leads_loc.current.tracking__search_version++;
|
||||
}
|
||||
|
||||
// Mode selector expand/collapse
|
||||
|
||||
@@ -8,6 +8,7 @@ import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
|
||||
import { events_func } from '$lib/ae_events/ae_events_functions';
|
||||
import Element_ae_obj_field_editor from '$lib/elements/element_ae_obj_field_editor.svelte';
|
||||
import Comp_exhibit_license_list from './ae_comp__exhibit_license_list.svelte';
|
||||
@@ -64,11 +65,10 @@ let show_billing = $state(false);
|
||||
|
||||
function handle_signout() {
|
||||
if (confirm('Sign out from this booth?')) {
|
||||
delete $events_loc.leads.auth_exhibit_kv[exhibit_id];
|
||||
delete leads_loc.current.auth_exhibit_kv[exhibit_id];
|
||||
$events_sess.leads.entered_passcode = null;
|
||||
// Navigate to start tab
|
||||
if (!$events_loc.leads.tab) $events_loc.leads.tab = {};
|
||||
$events_loc.leads.tab[exhibit_id] = 'start';
|
||||
leads_loc.current.tab[exhibit_id] = 'start';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -330,7 +330,7 @@ function handle_signout() {
|
||||
<!-- Licenses — visible to: Aether admins OR someone signed in with the shared exhibit passcode.
|
||||
Spec: "A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode
|
||||
can add/edit/remove licenses." — PROJECT__AE_Events_Exhibitor_Leads_v3.md -->
|
||||
{#if $ae_loc.administrator_access || $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.type === 'shared'}
|
||||
{#if $ae_loc.administrator_access || leads_loc.current.auth_exhibit_kv?.[exhibit_id]?.type === 'shared'}
|
||||
<div class="p-0">
|
||||
<button
|
||||
class="hover:bg-surface-500/5 group flex w-full items-center justify-between p-4 transition-colors"
|
||||
@@ -443,10 +443,10 @@ function handle_signout() {
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if show_billing}
|
||||
{#if show_billing}
|
||||
<div
|
||||
class="bg-surface-500/5 border-surface-500/10 animate-in fade-in slide-in-from-top-2 border-t p-4">
|
||||
<Comp_exhibit_payment {exhibit_id} {...stripe_cfg} />
|
||||
<Comp_exhibit_payment {exhibit_id} {...stripe_cfg} leads_require_payment={leads_require_payment} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -528,7 +528,7 @@ function handle_signout() {
|
||||
class="input border-surface-500/20 w-20 border-b bg-transparent p-1 text-right font-mono"
|
||||
min="1"
|
||||
max="120"
|
||||
bind:value={$events_loc.leads.refresh_interval_sec}
|
||||
bind:value={leads_loc.current.refresh_interval_sec}
|
||||
placeholder="25" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user