feat: use email as identity anchor for presenters without Person records
For events like LCI where ~75% of presenters are not in iMIS and have no Person record, person_id on the presenter is null. Email (from the spreadsheet import) is now the fallback identity throughout the sign-in flow: - expand_auth_for_person: detects email via '@', routes Dexie and API lookups to presenter.email field instead of person_id. Keys auth__kv.presenter by BOTH event_presenter_id and email so any sibling presenter record with the same email auto-unlocks without per-ID lookups. - Copy Access Link button: now visible for any presenter with person_id OR email. URL uses person_id ?? email and person_passcode ?? passcode as fallbacks. - Email Access Link button: now visible for person_primary_email OR email. Sends to the best available address; passes email as person_id when no Person record. - presenter_agree_enabled: checks auth__kv.presenter[email] as a second key so the Agreed button unlocks across all sessions for the same email identity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -244,8 +244,9 @@ let presenter_is_authed = $derived(
|
||||
|
||||
<span
|
||||
class="flex flex-row flex-wrap items-center justify-center gap-0.25">
|
||||
{#if $lq__event_presenter_obj.person_id && $ae_loc.trusted_access}
|
||||
{#if ($lq__event_presenter_obj.person_id || $lq__event_presenter_obj.email) && $ae_loc.trusted_access}
|
||||
<!-- A button to copy the access link to the clipboard. -->
|
||||
<!-- person_id falls back to presenter.email for presenters without a Person record. -->
|
||||
<!-- Example: /events/CHs3F44Xq76/session/Wh8UnJlbIA0?person_id=fV1dl_IJ0yY&person_pass=abc123 -->
|
||||
|
||||
<!-- {#snippet btn_text()}
|
||||
@@ -254,7 +255,7 @@ let presenter_is_authed = $derived(
|
||||
{/snippet} -->
|
||||
<MyClipboard
|
||||
value={encodeURI(
|
||||
`${$ae_loc.url_origin}/events/${$lq__event_presenter_obj.event_id}/session/${$lq__event_presenter_obj.event_session_id}?person_id=${$lq__event_presenter_obj.person_id}&person_pass=${$lq__event_presenter_obj.person_passcode}&presentation_id=${$lq__event_presenter_obj?.event_presentation_id}&presenter_id=${$lq__event_presenter_obj?.event_presenter_id}`
|
||||
`${$ae_loc.url_origin}/events/${$lq__event_presenter_obj.event_id}/session/${$lq__event_presenter_obj.event_session_id}?person_id=${$lq__event_presenter_obj.person_id ?? $lq__event_presenter_obj.email}&person_pass=${$lq__event_presenter_obj.person_passcode ?? $lq__event_presenter_obj.passcode}&presentation_id=${$lq__event_presenter_obj?.event_presentation_id}&presenter_id=${$lq__event_presenter_obj?.event_presenter_id}`
|
||||
)}
|
||||
btn_text="Copy Access Link"
|
||||
btn_title="Copy the presenter access link to the clipboard."
|
||||
@@ -262,45 +263,35 @@ let presenter_is_authed = $derived(
|
||||
></MyClipboard>
|
||||
{/if}
|
||||
|
||||
{#if pres_mgmt_loc.current.show__email_access_link && $lq__event_presenter_obj?.person_primary_email}
|
||||
{#if pres_mgmt_loc.current.show__email_access_link && ($lq__event_presenter_obj?.person_primary_email || $lq__event_presenter_obj?.email)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
console.log('Email the access link');
|
||||
if (
|
||||
!$lq__event_presenter_obj.person_primary_email
|
||||
) {
|
||||
alert(
|
||||
'No email address found for this presenter.'
|
||||
);
|
||||
// WHY: prefer the Person-record email (person_primary_email) when
|
||||
// available — it is the verified/canonical address. Fall back to
|
||||
// presenter.email (from the spreadsheet import) for the ~75% of
|
||||
// LCI presenters who have no Person record in iMIS.
|
||||
const use_email = $lq__event_presenter_obj.person_primary_email ?? $lq__event_presenter_obj.email;
|
||||
const use_person_id = $lq__event_presenter_obj.person_id ?? $lq__event_presenter_obj.email;
|
||||
const use_passcode = $lq__event_presenter_obj.person_passcode ?? $lq__event_presenter_obj.passcode;
|
||||
|
||||
if (!use_email) {
|
||||
alert('No email address found for this presenter.');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
confirm(
|
||||
`This will send the sign in email to ${$lq__event_presenter_obj.person_primary_email}`
|
||||
)
|
||||
) {
|
||||
console.log(
|
||||
'Send the email to the presenter.'
|
||||
);
|
||||
} else {
|
||||
console.log('Cancelled sending the email.');
|
||||
return false;
|
||||
if (!confirm(`This will send the sign in email to ${use_email}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
events_func.email_sign_in__event_presenter({
|
||||
api_cfg: $ae_api,
|
||||
to_email:
|
||||
$lq__event_presenter_obj?.person_primary_email,
|
||||
to_email: use_email,
|
||||
to_name:
|
||||
$lq__event_presenter_obj?.full_name ??
|
||||
'-- not set --',
|
||||
base_url: $ae_loc.url_origin,
|
||||
person_id:
|
||||
$lq__event_presenter_obj?.person_id,
|
||||
person_passcode:
|
||||
$lq__event_presenter_obj?.person_passcode ??
|
||||
'-- not set --',
|
||||
person_id: use_person_id,
|
||||
person_passcode: use_passcode ?? '-- not set --',
|
||||
event_id:
|
||||
$lq__event_presenter_obj?.event_id,
|
||||
event_session_id:
|
||||
|
||||
@@ -24,13 +24,15 @@ import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';
|
||||
let show_modal = $state(false);
|
||||
let show_help = $state(false);
|
||||
|
||||
// WHY: auth__kv.presenter[id] is only set when a sign-in link is clicked for that
|
||||
// specific presenter record. A person presenting in multiple sessions would need to
|
||||
// click every link. The person_id match covers them across all their presenter records
|
||||
// once they've signed in via any one link.
|
||||
// WHY: auth__kv.presenter is keyed by both event_presenter_id AND email.
|
||||
// A single sign-in link grants auth for one presenter_id, then expand_auth_for_person
|
||||
// finds all sibling records by person_id (iMIS users) or email (no Person record) and
|
||||
// sets both keys. This covers multi-session presenters and the ~75% of LCI presenters
|
||||
// who are not in iMIS and use email as their identity anchor.
|
||||
let presenter_agree_enabled = $derived(
|
||||
$ae_loc.trusted_access ||
|
||||
!!events_auth_loc.current.auth__kv.presenter[$lq__event_presenter_obj?.event_presenter_id ?? ''] ||
|
||||
!!events_auth_loc.current.auth__kv.presenter[$lq__event_presenter_obj?.email ?? ''] ||
|
||||
(!!events_auth_loc.current.auth__person.id &&
|
||||
events_auth_loc.current.auth__person.id === $lq__event_presenter_obj?.person_id)
|
||||
);
|
||||
|
||||
@@ -106,14 +106,28 @@ onMount(() => {
|
||||
// WHY: a single sign-in link only grants auth for one presenter_id/session_id. This
|
||||
// queries both Dexie (fast, warm cache) AND the API (reliable, cold cache) in parallel,
|
||||
// merges the results, and pre-populates auth__kv for all roles in one shot.
|
||||
//
|
||||
// person_id may be a real Person UUID (when a Person record exists, e.g. from iMIS)
|
||||
// OR an email address (the common case when no Person record exists — for LCI roughly
|
||||
// 75% of presenters are not in iMIS). Email is detected via '@' and the lookup is
|
||||
// routed to presenter.email instead of presenter.person_id.
|
||||
//
|
||||
// auth__kv.presenter is keyed by BOTH event_presenter_id AND email so that
|
||||
// presenter_agree_enabled works across all sessions without per-ID lookups.
|
||||
async function expand_auth_for_person(person_id: string, event_id: string) {
|
||||
try {
|
||||
const is_email = person_id.includes('@');
|
||||
// Route the presenter lookup to the right field based on what we received
|
||||
const presenter_field = is_email ? 'email' : 'person_id';
|
||||
|
||||
const [dexie_presenters, dexie_sessions, api_presenters, api_sessions] = await Promise.all([
|
||||
db_events.presenter
|
||||
.where('person_id').equals(person_id)
|
||||
.where(presenter_field).equals(person_id)
|
||||
.filter(p => p.event_id === event_id)
|
||||
.toArray(),
|
||||
db_events.session
|
||||
// POC sessions always link via poc_person_id (a real Person UUID).
|
||||
// Skip when signing in with email — email won't match poc_person_id.
|
||||
is_email ? Promise.resolve([] as Session[]) : db_events.session
|
||||
.where('poc_person_id').equals(person_id)
|
||||
.filter(s => s.event_id === event_id)
|
||||
.toArray(),
|
||||
@@ -122,11 +136,11 @@ async function expand_auth_for_person(person_id: string, event_id: string) {
|
||||
obj_type: 'event_presenter',
|
||||
search_query: { and: [
|
||||
{ field: 'event_id', op: 'eq', value: event_id },
|
||||
{ field: 'person_id', op: 'eq', value: person_id }
|
||||
{ field: presenter_field, op: 'eq', value: person_id }
|
||||
]},
|
||||
limit: 200
|
||||
}).catch(() => null),
|
||||
api.search_ae_obj({
|
||||
is_email ? Promise.resolve(null) : api.search_ae_obj({
|
||||
api_cfg: $ae_api,
|
||||
obj_type: 'event_session',
|
||||
search_query: { and: [
|
||||
@@ -151,6 +165,9 @@ async function expand_auth_for_person(person_id: string, event_id: string) {
|
||||
|
||||
for (const p of Object.values(presenter_kv)) {
|
||||
events_auth_loc.current.auth__kv.presenter[p.event_presenter_id] = true;
|
||||
// Also key by email — presenter_agree_enabled checks this so any record
|
||||
// sharing the same email auto-unlocks without needing the exact ID.
|
||||
if (p.email) events_auth_loc.current.auth__kv.presenter[p.email] = true;
|
||||
if (p.event_presentation_id) events_auth_loc.current.auth__kv.presentation[p.event_presentation_id] = true;
|
||||
// 'read' matches presenter_sign_in — truthy, signals read-only session access for presenters
|
||||
if (p.event_session_id) events_auth_loc.current.auth__kv.session[p.event_session_id] = 'read';
|
||||
@@ -159,7 +176,7 @@ async function expand_auth_for_person(person_id: string, event_id: string) {
|
||||
events_auth_loc.current.auth__kv.session[s.event_session_id] = true;
|
||||
}
|
||||
|
||||
console.log(`expand_auth_for_person: ${Object.keys(presenter_kv).length} presenter(s), ${Object.keys(session_kv).length} POC session(s) auto-granted`);
|
||||
console.log(`expand_auth_for_person: ${Object.keys(presenter_kv).length} presenter(s) [by ${presenter_field}], ${Object.keys(session_kv).length} POC session(s) auto-granted`);
|
||||
} catch (e) {
|
||||
console.warn('expand_auth_for_person: lookup failed', e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user