diff --git a/events/blocks/events-form/events-form.css b/events/blocks/events-form/events-form.css index 2e79b4c4..b931b5d3 100644 --- a/events/blocks/events-form/events-form.css +++ b/events/blocks/events-form/events-form.css @@ -309,8 +309,26 @@ } .events-form .form-success-msg { + position: relative; text-align: center; padding: 80px 40px; + min-height: 300px; +} + +.events-form .form-success-msg > div:not(.hidden) { + height: 100%; + min-height: 300px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +} + +.events-form .form-success-msg > .second-screen > hgroup { + flex-basis: 56%; + display: flex; + flex-direction: column; + justify-content: space-between; } .events-form .form-success-msg h1, @@ -419,6 +437,10 @@ width: auto; } + .events-form .form-success-msg { + min-width: 720px; + } + .events-form .form-success-msg .post-rsvp-button-wrapper { gap: 1rem; } diff --git a/events/blocks/events-form/events-form.js b/events/blocks/events-form/events-form.js index 3f29aaff..c3c7c85b 100644 --- a/events/blocks/events-form/events-form.js +++ b/events/blocks/events-form/events-form.js @@ -92,7 +92,14 @@ async function submitForm(bp) { return acc; }, {}); - return getAndCreateAndAddAttendee(getMetadata('event-id'), cleanPayload); + let rsvpType = 'registered'; + const eventInfo = BlockMediator.get('eventInfo'); + if (eventInfo) { + const { allowWaitlisting, attendeeLimit, attendeeCount } = eventInfo; + if (allowWaitlisting && attendeeCount >= attendeeLimit) rsvpType = 'waitlisted'; + } + + return getAndCreateAndAddAttendee(getMetadata('event-id'), cleanPayload, rsvpType); } function clearForm(form) { @@ -121,11 +128,26 @@ async function buildErrorMsg(parent, status) { }, 3000); } -function showSuccessMsg(bp) { +function showSuccessMsgFirstScreen(bp) { + const rsvpData = BlockMediator.get('rsvpData'); + + if (!rsvpData) return; + clearForm(bp.form); bp.form.classList.add('hidden'); bp.eventHero.classList.add('hidden'); - bp.successMsg.classList.remove('hidden'); + + const { registrationStatus } = rsvpData; + + if (registrationStatus === 'waitlisted') { + bp.waitlistSuccessScreen?.classList.remove('hidden'); + bp.waitlistSuccessScreen?.querySelector('.first-screen')?.classList.remove('hidden'); + } + + if (registrationStatus === 'registered') { + bp.rsvpSuccessScreen?.classList.remove('hidden'); + bp.rsvpSuccessScreen?.querySelector('.first-screen')?.classList.remove('hidden'); + } } function eventFormSendAnalytics(bp, view) { @@ -150,9 +172,12 @@ function createButton({ type, label }, bp) { button.removeAttribute('disabled'); button.classList.remove('submitting'); if (!respJson) return; - if (respJson.ok !== false && !respJson.error) eventFormSendAnalytics(bp, 'Form Submit'); + BlockMediator.set('rsvpData', respJson.data); - if (respJson.error) { + + if (respJson.ok) { + eventFormSendAnalytics(bp, 'Form Submit'); + } else { let { status } = respJson; // FIXME: temporary fix for ESL 500 on ESP 400 @@ -333,51 +358,95 @@ function addTerms(form, terms) { submit.disabled = none(Array.from(checkboxes), (c) => c.checked); } -function decorateSuccessMsg(form, bp) { - const ctas = bp.successMsg.querySelectorAll('a'); - const hgroup = createTag('hgroup'); - const eyeBrowText = bp.successMsg.querySelector('p:first-child'); - const headings = bp.successMsg.querySelectorAll('h1, h2, h3, h4, h5, h6'); - headings.forEach((h) => { - hgroup.append(h); - }); +function decorateSuccessScreen(screen) { + if (!screen) return; - if (eyeBrowText) { - eyeBrowText.classList.add('eyebrow'); - hgroup.prepend(eyeBrowText); - } + screen.classList.add('form-success-msg'); + const subScreens = screen.querySelectorAll('div'); - bp.successMsg.prepend(hgroup); - ctas.forEach((cta, i) => { - if (i === 0) { - cta.parentElement.classList.add('post-rsvp-button-wrapper'); - cta.classList.add('con-button', 'outline', 'button-l'); - } else if (i === 1) { - cta.classList.add('con-button', 'black', 'button-l'); - } + const [firstScreen, secondScreen] = subScreens; - cta.addEventListener('click', async (e) => { - e.preventDefault(); + subScreens.forEach((ss, i) => { + ss.classList.add('hidden'); + const hgroup = createTag('hgroup'); + const eyeBrowText = ss.querySelector('p:first-child'); + const headings = ss.querySelectorAll('h1, h2, h3, h4, h5, h6'); - cta.classList.add('loading'); + headings.forEach((h) => { + hgroup.append(h); + }); + + if (eyeBrowText) { + eyeBrowText.classList.add('eyebrow'); + hgroup.prepend(eyeBrowText); + } - if (i === 0) { - const resp = await deleteAttendeeFromEvent(getMetadata('event-id')); - cta.classList.remove('loading'); - if (resp?.data?.espProvider?.status !== 204) { - buildErrorMsg(bp.successMsg); - return; + ss.prepend(hgroup); + + if (i === 0) { + ss.classList.add('first-screen'); + const firstScreenCtas = ss.querySelectorAll('a'); + + firstScreenCtas.forEach((cta) => { + const ctaUrl = new URL(cta.href); + if (ctaUrl.hash.startsWith('#cancel')) { + cta.parentElement.classList.add('post-rsvp-button-wrapper'); + cta.classList.add('con-button', 'outline', 'button-l', 'cancel-button'); + } else if (ctaUrl.hash.startsWith('#ok')) { + cta.classList.add('con-button', 'black', 'button-l', 'ok-button'); } - if (resp?.data?.espProvider?.attendeeDeleted) BlockMediator.set('rsvpData', null); - } + cta.addEventListener('click', async (e) => { + e.preventDefault(); - const modal = form.closest('.dialog-modal'); - closeModal(modal); - }); + cta.classList.add('loading'); + + if (cta.classList.contains('cancel-button')) { + const resp = await deleteAttendeeFromEvent(getMetadata('event-id')); + if (!resp.ok) return; + + const { data } = resp; + + cta.classList.remove('loading'); + if (data?.espProvider?.status !== 204) { + buildErrorMsg(screen); + return; + } + + if (data?.espProvider?.attendeeDeleted) BlockMediator.set('rsvpData', null); + + firstScreen.classList.add('hidden'); + secondScreen.classList.remove('hidden'); + cta.classList.remove('loading'); + } + + if (cta.classList.contains('ok-button')) { + const modal = screen.closest('.dialog-modal'); + if (modal) closeModal(modal); + } + }); + }); + } + + if (i === 1) { + ss.classList.add('second-screen'); + const secondScreenCtas = ss.querySelectorAll('a'); + + secondScreenCtas.forEach((cta) => { + const ctaUrl = new URL(cta.href); + if (ctaUrl.hash.startsWith('#ok')) { + cta.classList.add('con-button', 'black', 'button-l'); + cta.addEventListener('click', async (e) => { + e.preventDefault(); + const modal = screen.closest('.dialog-modal'); + if (modal) closeModal(modal); + }); + } + }); + } }); - bp.successMsg.classList.add('hidden'); + screen.classList.add('hidden'); } async function createForm(bp, formData) { @@ -464,7 +533,6 @@ async function createForm(bp, formData) { }); addTerms(formEl, terms); - decorateSuccessMsg(formEl, bp); formEl.addEventListener('input', () => applyRules(formEl, rules)); applyRules(formEl, rules); @@ -494,11 +562,11 @@ function decorateHero(heroEl) { async function buildEventform(bp, formData) { if (!bp.formContainer || !bp.form) return; bp.formContainer.classList.add('form-container'); - bp.successMsg.classList.add('form-success-msg'); - const { formEl, sanitizeList } = await createForm( - bp, - formData, - ); + const { formEl, sanitizeList } = await createForm(bp, formData); + + [bp.rsvpSuccessScreen, bp.waitlistSuccessScreen].forEach((screen) => { + decorateSuccessScreen(screen); + }); if (formEl) { bp.form.replaceWith(formEl); @@ -508,22 +576,25 @@ async function buildEventform(bp, formData) { } function initFormBasedOnRSVPData(bp) { + const validRegistrationStatus = ['registered', 'waitlisted']; const { block } = bp; const profile = BlockMediator.get('imsProfile'); const rsvpData = BlockMediator.get('rsvpData'); - if (rsvpData?.espProvider?.registered || rsvpData?.externalAttendeeId) { - showSuccessMsg(bp); + + if (validRegistrationStatus.includes(rsvpData?.registrationStatus)) { + showSuccessMsgFirstScreen(bp); eventFormSendAnalytics(bp, 'Confirmation Modal View'); } else { personalizeForm(block, profile); } BlockMediator.subscribe('rsvpData', ({ newValue }) => { - if (newValue?.espProvider?.registered || newValue?.externalAttendeeId) { - showSuccessMsg(bp); + if (validRegistrationStatus.includes(newValue?.registrationStatus)) { + showSuccessMsgFirstScreen(bp); eventFormSendAnalytics(bp, 'Confirmation Modal View'); } }); + if (bp.block.querySelector('.form-success-msg.hidden')) { eventFormSendAnalytics(bp, 'Form View'); } @@ -581,7 +652,8 @@ export default async function decorate(block, formData = null) { formContainer: block.querySelector(':scope > div:nth-of-type(2)'), form: block.querySelector(':scope > div:nth-of-type(2) a[href$=".json"]'), terms: block.querySelector(':scope > div:nth-of-type(3)'), - successMsg: block.querySelector(':scope > div:last-of-type > div'), + rsvpSuccessScreen: block.querySelector(':scope > div:nth-of-type(4)'), + waitlistSuccessScreen: block.querySelector(':scope > div:nth-of-type(5)'), }; await onProfile(bp, formData); diff --git a/events/scripts/content-update.js b/events/scripts/content-update.js index bb9d189f..6f29dd6f 100644 --- a/events/scripts/content-update.js +++ b/events/scripts/content-update.js @@ -93,41 +93,77 @@ function createTag(tag, attributes, html, options = {}) { async function updateRSVPButtonState(rsvpBtn, miloLibs, eventInfo) { const rsvpData = BlockMediator.get('rsvpData'); const checkRed = getIcon('check-circle-red'); - const eventFull = +eventInfo.attendeeLimit <= +eventInfo.attendeeCount; + let eventFull = false; + if (eventInfo) eventFull = +eventInfo.attendeeLimit <= +eventInfo.attendeeCount; - if (!rsvpData) { - if (eventFull) { - const eventFullText = await miloReplaceKey(miloLibs, 'event-full-cta-text'); - rsvpBtn.el.setAttribute('tabindex', -1); - rsvpBtn.el.href = ''; - rsvpBtn.el.classList.add('disabled'); - updateAnalyticTag(rsvpBtn.el, eventFullText); - rsvpBtn.el.textContent = eventFullText; - checkRed.remove(); - } else { - rsvpBtn.el.classList.remove('disabled'); - rsvpBtn.el.setAttribute('tabindex', 0); - updateAnalyticTag(rsvpBtn.el, rsvpBtn.originalText); - rsvpBtn.el.textContent = rsvpBtn.originalText; - checkRed.remove(); - } - } else if (rsvpData.espProvider?.registered || rsvpData.externalAttendeeId) { - const registeredText = await miloReplaceKey(miloLibs, 'registered-cta-text'); + const enableBtn = () => { rsvpBtn.el.classList.remove('disabled'); rsvpBtn.el.setAttribute('tabindex', 0); + }; + + const disableBtn = () => { + rsvpBtn.el.setAttribute('tabindex', -1); + rsvpBtn.el.href = ''; + rsvpBtn.el.classList.add('disabled'); + }; + + const closedState = async () => { + const closedText = await miloReplaceKey(miloLibs, 'event-full-cta-text'); + disableBtn(); + updateAnalyticTag(rsvpBtn.el, closedText); + rsvpBtn.el.textContent = closedText; + checkRed.remove(); + }; + + const defaultState = () => { + enableBtn(); + updateAnalyticTag(rsvpBtn.el, rsvpBtn.originalText); + rsvpBtn.el.textContent = rsvpBtn.originalText; + checkRed.remove(); + }; + + const registeredState = async () => { + const registeredText = await miloReplaceKey(miloLibs, 'registered-cta-text'); + enableBtn(); updateAnalyticTag(rsvpBtn.el, registeredText); rsvpBtn.el.textContent = registeredText; rsvpBtn.el.prepend(checkRed); + }; + + const waitlistState = async () => { + const waitlistText = await miloReplaceKey(miloLibs, 'waitlist-cta-text'); + enableBtn(); + updateAnalyticTag(rsvpBtn.el, waitlistText); + rsvpBtn.el.textContent = waitlistText; + checkRed.remove(); + }; + + const waitlistedState = async () => { + const waitlistedText = await miloReplaceKey(miloLibs, 'waitlisted-cta-text'); + disableBtn(); + updateAnalyticTag(rsvpBtn.el, waitlistedText); + rsvpBtn.el.textContent = waitlistedText; + rsvpBtn.el.prepend(checkRed); + }; + + if (!rsvpData) { + if (eventFull) { + if (eventInfo?.allowWaitListing) { + await waitlistState(); + } else { + await closedState(); + } + } else { + defaultState(); + } + } else if (rsvpData.registrationStatus === 'registered') { + await registeredState(); + } else if (rsvpData.registrationStatus === 'waitlisted') { + await waitlistedState(); } else if (!rsvpData.ok) { // FIXME: temporary solution for ESL returning 500 on ESP 400 response if (rsvpData.error?.message === 'Request to ESP failed: Event is full') { - const eventFullText = await miloReplaceKey(miloLibs, 'event-full-cta-text'); - rsvpBtn.el.setAttribute('tabindex', -1); - rsvpBtn.el.href = ''; - rsvpBtn.el.classList.add('disabled'); - updateAnalyticTag(rsvpBtn.el, eventFullText); - rsvpBtn.el.textContent = eventFullText; - checkRed.remove(); + await closedState(); } } } @@ -143,12 +179,9 @@ export function signIn() { async function handleRSVPBtnBasedOnProfile(rsvpBtn, miloLibs, profile) { const resp = await getEvent(getMetadata('event-id')); - - if (!resp) { - return; - } + if (!resp) return; const eventInfo = resp.data; - + BlockMediator.set('eventInfo', eventInfo); if (profile?.noProfile || resp.status === 401) { if (eventInfo && +eventInfo.attendeeLimit <= +eventInfo.attendeeCount) { const eventFullText = await miloReplaceKey(miloLibs, 'event-full-cta-text'); diff --git a/events/scripts/esp-controller.js b/events/scripts/esp-controller.js index dc69f3a3..831e43fc 100644 --- a/events/scripts/esp-controller.js +++ b/events/scripts/esp-controller.js @@ -171,12 +171,12 @@ export async function createAttendee(attendeeData) { } } -export async function addAttendeeToEvent(eventId, attendee) { +export async function addAttendeeToEvent(eventId, attendee, registrationStatus) { if (!eventId || !attendee) return false; const { firstName, lastName, email } = attendee; const { host } = API_CONFIG.esl[getECCEnv()]; - const raw = JSON.stringify({ firstName, lastName, email }); + const raw = JSON.stringify({ firstName, lastName, email, registrationStatus }); const options = await constructRequestOptions('POST', raw); try { @@ -244,7 +244,7 @@ export async function deleteAttendeeFromEvent(eventId) { } // compound helper functions -export async function getAndCreateAndAddAttendee(eventId, attendeeData) { +export async function getAndCreateAndAddAttendee(eventId, attendeeData, registrationStatus) { const attendeeResp = await getAttendee(eventId); let attendee; @@ -258,5 +258,5 @@ export async function getAndCreateAndAddAttendee(eventId, attendeeData) { const newAttendeeData = attendee.data; - return addAttendeeToEvent(eventId, newAttendeeData); + return addAttendeeToEvent(eventId, newAttendeeData, registrationStatus); } diff --git a/events/scripts/profile.js b/events/scripts/profile.js index 7924756f..f122a655 100644 --- a/events/scripts/profile.js +++ b/events/scripts/profile.js @@ -40,12 +40,8 @@ export function lazyCaptureProfile() { BlockMediator.set('imsProfile', profile); if (!profile.noProfile) { - const rsvpData = await getEventAttendee(getMetadata('event-id')); - if (!rsvpData.error) { - BlockMediator.set('rsvpData', rsvpData.data); - } else { - BlockMediator.set('rsvpData', null); - } + const resp = await getEventAttendee(getMetadata('event-id')); + BlockMediator.set('rsvpData', resp.data); } clearInterval(profileRetryer);