diff --git a/src/lib/ae_events/ae_events__event_presenter.ts b/src/lib/ae_events/ae_events__event_presenter.ts index 6c91a78e..140ea832 100644 --- a/src/lib/ae_events/ae_events__event_presenter.ts +++ b/src/lib/ae_events/ae_events__event_presenter.ts @@ -595,7 +595,6 @@ export async function email_sign_in__event_presenter({ }) { if ( !to_email || - !person_id || !person_passcode || !event_id || !event_presenter_id @@ -609,10 +608,14 @@ export async function email_sign_in__event_presenter({ // Routes to the session page (which has the sign-in handler mounted) not /presenter/[id] // which has no sign-in handler. Includes presenter_id + presentation_id so the handler // can grant presenter-level auth (not just session read access). - // Per-param encodeURIComponent is required for query values — encodeURI() does not - // encode '+', which URLSearchParams.get() then decodes as a space, breaking email - // addresses that contain '+' (e.g. test+alias@example.com). - const sign_in_url = `${base_url}/events/${event_id}/session/${event_session_id}?person_id=${encodeURIComponent(person_id ?? '')}&person_pass=${encodeURIComponent(person_passcode ?? '')}&presenter_id=${encodeURIComponent(event_presenter_id ?? '')}&presentation_id=${encodeURIComponent(event_presentation_id ?? '')}`; + // person_id is omitted for email-only presenters (no Person record) — the sign-in handler + // falls back to presenter_id and resolves their identity via Dexie lookup. + const sign_in_parts: string[] = []; + if (person_id) sign_in_parts.push(`person_id=${encodeURIComponent(person_id)}`); + sign_in_parts.push(`person_pass=${encodeURIComponent(person_passcode ?? '')}`); + sign_in_parts.push(`presenter_id=${encodeURIComponent(event_presenter_id ?? '')}`); + if (event_presentation_id) sign_in_parts.push(`presentation_id=${encodeURIComponent(event_presentation_id)}`); + const sign_in_url = `${base_url}/events/${event_id}/session/${event_session_id}?${sign_in_parts.join('&')}`; const body_html = `
${to_name},

Your sign-in link for ${presentation_name ?? 'Presentation'} (Session: ${session_name ?? 'Session'}): ${sign_in_url}

This link takes you to the session page — your presentation and file upload sections will be available after you sign in.

`; return await api.send_email({ api_cfg, diff --git a/src/routes/events/[event_id]/(pres_mgmt)/presenter/ae_comp__event_presenter_obj_li.svelte b/src/routes/events/[event_id]/(pres_mgmt)/presenter/ae_comp__event_presenter_obj_li.svelte index 3e659b33..bbe50bde 100644 --- a/src/routes/events/[event_id]/(pres_mgmt)/presenter/ae_comp__event_presenter_obj_li.svelte +++ b/src/routes/events/[event_id]/(pres_mgmt)/presenter/ae_comp__event_presenter_obj_li.svelte @@ -193,7 +193,8 @@ let ae_tmp: key_val = $state({}); // Prefer Person-record email; fall back to presenter.email for // presenters without a Person record (common for LCI/iMIS gaps). const use_email = event_presenter_obj.person_primary_email ?? event_presenter_obj.email; - const use_person_id = event_presenter_obj.person_id ?? event_presenter_obj.event_presenter_id; + // null for email-only presenters — email function omits person_id from URL; sign-in derives identity from presenter_id + const use_person_id = event_presenter_obj.person_id; const use_passcode = event_presenter_obj.person_passcode ?? event_presenter_obj.passcode; if (!use_email) { diff --git a/src/routes/events/[event_id]/sign_in_out.svelte b/src/routes/events/[event_id]/sign_in_out.svelte index b5861453..607cd93a 100644 --- a/src/routes/events/[event_id]/sign_in_out.svelte +++ b/src/routes/events/[event_id]/sign_in_out.svelte @@ -57,11 +57,14 @@ onMount(() => { let url_presenter_id = data.url.searchParams.get('presenter_id'); let url_session_id = data.url.searchParams.get('session_id'); - // Gate on having a sign-in target (presenter or session), not on the passcode value. - // WHY: passcode may be empty/null for presenter records without one configured; - // the old gate `if (url_person_pass)` broke when passcode was empty string. - // The presenter_id / session_id in the URL is the real sign-in signal. - if (url_person_id !== null && (url_presenter_id || url_session_id)) { + // Gate on having a sign-in target (presenter or session) — person_id is optional. + // WHY: email-only presenters (no Person record) have no separate person_id to put + // in the URL. expand_auth_for_person resolves their identity via presenter_id_hint. + // For session POC links, person_id is always present (it's poc_person_id). + if (url_presenter_id || url_session_id) { + // person_id is absent for email-only presenter links — fall back to presenter_id. + // POC links always include person_id, so this branch is only hit for presenters. + if (!url_person_id) url_person_id = url_presenter_id ?? url_session_id; console.log( `ae_events_pres_mgmt session [slug] +page.svelte: event_session_id=${$events_slct.event_session_id}; person_id=${url_person_id}; person_pass=${url_person_pass}; presentation_id=${url_presentation_id}; presenter_id=${url_presenter_id}` );