diff --git a/ecc/blocks/attendee-management-table/attendee-management-table.js b/ecc/blocks/attendee-management-table/attendee-management-table.js index fa4abc1b..c7513140 100644 --- a/ecc/blocks/attendee-management-table/attendee-management-table.js +++ b/ecc/blocks/attendee-management-table/attendee-management-table.js @@ -1,17 +1,17 @@ /* eslint-disable max-len */ import { getAllEventAttendees, getEvents } from '../../scripts/esp-controller.js'; -import { ALLOWED_ACCOUNT_TYPES } from '../../constants/constants.js'; import { LIBS } from '../../scripts/scripts.js'; import { getIcon, buildNoAccessScreen, camelToSentenceCase, readBlockConfig, + signIn, getECCEnv, } from '../../scripts/utils.js'; -import BlockMediator from '../../scripts/deps/block-mediator.min.js'; import { SearchablePicker } from '../../components/searchable-picker/searchable-picker.js'; import { FilterMenu } from '../../components/filter-menu/filter-menu.js'; +import { initProfileLogicTree } from '../../scripts/event-apis.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); @@ -565,27 +565,15 @@ export default async function init(el) { return; } - const profile = BlockMediator.get('imsProfile'); - - if (profile) { - if (profile.noProfile || !ALLOWED_ACCOUNT_TYPES.includes(profile.account_type)) { + initProfileLogicTree({ + noProfile: () => { + signIn(); + }, + noAccessProfile: () => { buildNoAccessScreen(el); - } else { + }, + validProfile: () => { buildDashboard(el, config); - } - - return; - } - - if (!profile) { - const unsubscribe = BlockMediator.subscribe('imsProfile', ({ newValue }) => { - if (newValue?.noProfile || !ALLOWED_ACCOUNT_TYPES.includes(newValue.account_type)) { - buildNoAccessScreen(el); - } else { - buildDashboard(el, config); - } - - unsubscribe(); - }); - } + }, + }); } diff --git a/ecc/blocks/ecc-dashboard/ecc-dashboard.js b/ecc/blocks/ecc-dashboard/ecc-dashboard.js index 101868c0..8dffe28d 100644 --- a/ecc/blocks/ecc-dashboard/ecc-dashboard.js +++ b/ecc/blocks/ecc-dashboard/ecc-dashboard.js @@ -6,11 +6,17 @@ import { publishEvent, unpublishEvent, } from '../../scripts/esp-controller.js'; -import { ALLOWED_ACCOUNT_TYPES } from '../../constants/constants.js'; import { LIBS } from '../../scripts/scripts.js'; -import { getIcon, buildNoAccessScreen, getEventPageHost, readBlockConfig, getECCEnv } from '../../scripts/utils.js'; +import { + getIcon, + buildNoAccessScreen, + getEventPageHost, + readBlockConfig, + signIn, + getECCEnv, +} from '../../scripts/utils.js'; import { quickFilter } from '../form-handler/data-handler.js'; -import BlockMediator from '../../scripts/deps/block-mediator.min.js'; +import { initProfileLogicTree } from '../../scripts/event-apis.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); @@ -713,27 +719,15 @@ export default async function init(el) { return; } - const profile = BlockMediator.get('imsProfile'); - - if (profile) { - if (profile.noProfile || !ALLOWED_ACCOUNT_TYPES.includes(profile.account_type)) { + initProfileLogicTree({ + noProfile: () => { + signIn(); + }, + noAccessProfile: () => { buildNoAccessScreen(el); - } else { + }, + validProfile: () => { buildDashboard(el, config); - } - - return; - } - - if (!profile) { - const unsubscribe = BlockMediator.subscribe('imsProfile', ({ newValue }) => { - if (newValue?.noProfile || !ALLOWED_ACCOUNT_TYPES.includes(newValue.account_type)) { - buildNoAccessScreen(el); - } else { - buildDashboard(el, config); - } - - unsubscribe(); - }); - } + }, + }); } diff --git a/ecc/blocks/form-handler/controllers/event-community-link-component-controller.js b/ecc/blocks/form-handler/controllers/event-community-link-component-controller.js index ac54ce5b..2156bc87 100644 --- a/ecc/blocks/form-handler/controllers/event-community-link-component-controller.js +++ b/ecc/blocks/form-handler/controllers/event-community-link-component-controller.js @@ -7,7 +7,7 @@ export function onSubmit(component, props) { const checkbox = component.querySelector('#checkbox-community'); if (checkbox.checked) { - const communityTopicUrl = component.querySelector('#community-url-details').value; + const communityTopicUrl = component.querySelector('#community-url-details')?.value?.trim(); props.payload = { ...props.payload, communityTopicUrl }; } else { const tempPayload = { ...props.payload }; diff --git a/ecc/blocks/form-handler/controllers/registration-details-component-controller.js b/ecc/blocks/form-handler/controllers/registration-details-component-controller.js index cde7c625..90d12af3 100644 --- a/ecc/blocks/form-handler/controllers/registration-details-component-controller.js +++ b/ecc/blocks/form-handler/controllers/registration-details-component-controller.js @@ -41,10 +41,10 @@ function prefillFields(component, props) { export function onSubmit(component, props) { if (component.closest('.fragment')?.classList.contains('hidden')) return; - const attendeeLimitVal = component.querySelector('#attendee-count-input')?.value; + const attendeeLimitVal = component.querySelector('#attendee-count-input')?.value?.trim(); const allowWaitlisting = component.querySelector('#registration-allow-waitlist')?.checked; const contactHost = component.querySelector('#registration-contact-host')?.checked; - const hostEmail = component.querySelector('#event-host-email-input')?.value; + const hostEmail = component.querySelector('#event-host-email-input')?.value?.trim(); const rsvpDescription = component.querySelector('#rsvp-form-detail-description')?.value; const attendeeLimit = Number.isNaN(+attendeeLimitVal) ? null : +attendeeLimitVal; diff --git a/ecc/blocks/form-handler/controllers/venue-info-component-controller.js b/ecc/blocks/form-handler/controllers/venue-info-component-controller.js index 4b3a894b..74bf0ed1 100644 --- a/ecc/blocks/form-handler/controllers/venue-info-component-controller.js +++ b/ecc/blocks/form-handler/controllers/venue-info-component-controller.js @@ -4,15 +4,17 @@ import BlockMediator from '../../../scripts/deps/block-mediator.min.js'; import { changeInputValue, getECCEnv, getSecret } from '../../../scripts/utils.js'; import { buildErrorMessage } from '../form-handler.js'; -function togglePrefillableFieldsHiddenState(component, showPrefilledFields) { - const addressInput = component.querySelector('#venue-info-venue-address'); - const cityInput = component.querySelector('#location-city'); - const stateInput = component.querySelector('#location-state'); - const postalCodeInput = component.querySelector('#location-zip-code'); - const countryInput = component.querySelector('#location-country'); +function togglePrefillableFieldsHiddenState(component) { + const address = component.querySelector('#venue-info-venue-address'); + const city = component.querySelector('#location-city'); + const state = component.querySelector('#location-state'); + const postal = component.querySelector('#location-zip-code'); + const county = component.querySelector('#location-country'); - [addressInput, cityInput, stateInput, postalCodeInput, countryInput].forEach((input) => { - input.classList.toggle('hidden', showPrefilledFields); + const hasUnfilledFields = [address, city, state, postal, county].some((input) => !input.value); + + [address, city, state, postal, county].forEach((input) => { + input.classList.toggle('hidden', hasUnfilledFields); }); } @@ -29,6 +31,109 @@ async function loadGoogleMapsAPI(callback) { document.head.appendChild(script); } +function resetAllFields(component) { + const venueNameInput = component.querySelector('#venue-info-venue-name'); + const addressInput = component.querySelector('#venue-info-venue-address'); + const cityInput = component.querySelector('#location-city'); + const stateInput = component.querySelector('#location-state'); + const stateCodeInput = component.querySelector('#location-state-code'); + const postalCodeInput = component.querySelector('#location-zip-code'); + const countryInput = component.querySelector('#location-country'); + const placeLatInput = component.querySelector('#google-place-lat'); + const placeLngInput = component.querySelector('#google-place-lng'); + const placeIdInput = component.querySelector('#google-place-id'); + const mapUrlInput = component.querySelector('#google-map-url'); + const gmtoffsetInput = component.querySelector('#google-place-gmt-offset'); + + venueNameInput.value = ''; + changeInputValue(addressInput, 'value', ''); + changeInputValue(cityInput, 'value', ''); + changeInputValue(stateInput, 'value', ''); + changeInputValue(stateCodeInput, 'value', ''); + changeInputValue(postalCodeInput, 'value', ''); + changeInputValue(countryInput, 'value', ''); + changeInputValue(placeLatInput, 'value', ''); + changeInputValue(placeLngInput, 'value', ''); + changeInputValue(placeIdInput, 'value', ''); + changeInputValue(mapUrlInput, 'value', ''); + changeInputValue(gmtoffsetInput, 'value', ''); +} + +function updateAllFields(venueData, component) { + const venueNameInput = component.querySelector('#venue-info-venue-name'); + const addressInput = component.querySelector('#venue-info-venue-address'); + const cityInput = component.querySelector('#location-city'); + const stateInput = component.querySelector('#location-state'); + const stateCodeInput = component.querySelector('#location-state-code'); + const postalCodeInput = component.querySelector('#location-zip-code'); + const countryInput = component.querySelector('#location-country'); + const placeLatInput = component.querySelector('#google-place-lat'); + const placeLngInput = component.querySelector('#google-place-lng'); + const placeIdInput = component.querySelector('#google-place-id'); + const mapUrlInput = component.querySelector('#google-map-url'); + const gmtoffsetInput = component.querySelector('#google-place-gmt-offset'); + + changeInputValue(venueNameInput, 'value', venueData.venueName); + changeInputValue(addressInput, 'value', venueData.address); + changeInputValue(cityInput, 'value', venueData.city); + changeInputValue(stateInput, 'value', venueData.state); + changeInputValue(stateCodeInput, 'value', venueData.statecode); + changeInputValue(postalCodeInput, 'value', venueData.postalCode); + changeInputValue(countryInput, 'value', venueData.country); + changeInputValue(placeLatInput, 'value', venueData.coordinates?.lat); + changeInputValue(placeLngInput, 'value', venueData.coordinates?.lon); + changeInputValue(placeIdInput, 'value', venueData.placeId); + changeInputValue(mapUrlInput, 'value', venueData.mapUrl); + changeInputValue(gmtoffsetInput, 'value', venueData.gmtOffset); +} + +function getVenueDataInForm(component) { + const venueNameInput = component.querySelector('#venue-info-venue-name'); + const addressInput = component.querySelector('#venue-info-venue-address'); + const cityInput = component.querySelector('#location-city'); + const stateInput = component.querySelector('#location-state'); + const stateCodeInput = component.querySelector('#location-state-code'); + const postalCodeInput = component.querySelector('#location-zip-code'); + const countryInput = component.querySelector('#location-country'); + const placeLatInput = component.querySelector('#google-place-lat'); + const placeLngInput = component.querySelector('#google-place-lng'); + const placeIdInput = component.querySelector('#google-place-id'); + const mapUrlInput = component.querySelector('#google-map-url'); + const gmtoffsetInput = component.querySelector('#google-place-gmt-offset'); + + const venueName = venueNameInput.value; + const address = addressInput.value; + const city = cityInput.value; + const state = stateInput.value; + const stateCode = stateCodeInput.value; + const postalCode = postalCodeInput.value; + const country = countryInput.value; + const placeId = placeIdInput.value; + const mapUrl = mapUrlInput.value; + const lat = +placeLatInput.value; + const lon = +placeLngInput.value; + const gmtOffset = +gmtoffsetInput.value; + + const venueData = { + venueName, + address, + city, + state, + stateCode, + postalCode, + country, + placeId, + mapUrl, + coordinates: { + lat, + lon, + }, + gmtOffset, + }; + + return venueData; +} + function initAutocomplete(el, props) { const venueName = el.querySelector('#venue-info-venue-name'); // eslint-disable-next-line no-undef @@ -86,6 +191,13 @@ function initAutocomplete(el, props) { } }); + if (Object.values(addressInfo).some((v) => !v)) { + el.dispatchEvent(new CustomEvent('show-error-toast', { detail: { error: { message: 'The selection is not a valid venue.' } }, bubbles: true, composed: true })); + resetAllFields(el); + togglePrefillableFieldsHiddenState(el); + return; + } + if (place.name) changeInputValue(venueName, 'value', place.name); changeInputValue(address, 'value', addressInfo.address); changeInputValue(city, 'value', addressInfo.city); @@ -96,7 +208,7 @@ function initAutocomplete(el, props) { changeInputValue(placeId, 'value', place.place_id); changeInputValue(mapUrl, 'value', place.url); - togglePrefillableFieldsHiddenState(el, false); + togglePrefillableFieldsHiddenState(el); BlockMediator.set('eventDupMetrics', { ...BlockMediator.get('eventDupMetrics'), city: addressInfo.city }); } @@ -128,66 +240,23 @@ export default async function init(component, props) { const { venue, showVenuePostEvent } = eventData; const venueNameInput = component.querySelector('#venue-info-venue-name'); - const addressInput = component.querySelector('#venue-info-venue-address'); - const cityInput = component.querySelector('#location-city'); - const stateInput = component.querySelector('#location-state'); - const stateCodeInput = component.querySelector('#location-state-code'); - const postalCodeInput = component.querySelector('#location-zip-code'); - const countryInput = component.querySelector('#location-country'); - const placeLatInput = component.querySelector('#google-place-lat'); - const placeLngInput = component.querySelector('#google-place-lng'); - const placeIdInput = component.querySelector('#google-place-id'); - const mapUrlInput = component.querySelector('#google-map-url'); - const gmtoffsetInput = component.querySelector('#google-place-gmt-offset'); - togglePrefillableFieldsHiddenState(component, true); + togglePrefillableFieldsHiddenState(component); venueNameInput.addEventListener('change', () => { if (!venueNameInput.value) { - changeInputValue(addressInput, 'value', ''); - changeInputValue(cityInput, 'value', ''); - changeInputValue(stateInput, 'value', ''); - changeInputValue(stateCodeInput, 'value', ''); - changeInputValue(postalCodeInput, 'value', ''); - changeInputValue(countryInput, 'value', ''); - changeInputValue(placeLatInput, 'value', ''); - changeInputValue(placeLngInput, 'value', ''); - changeInputValue(placeIdInput, 'value', ''); - changeInputValue(mapUrlInput, 'value', ''); - changeInputValue(gmtoffsetInput, 'value', ''); + resetAllFields(component); + togglePrefillableFieldsHiddenState(component, true); } }); if (venue) { - const { - venueName, - address, - city, - state, - statecode, - postalCode, - country, - placeId, - mapUrl, - } = venue; - - changeInputValue(venueNameInput, 'value', venueName); - changeInputValue(addressInput, 'value', address); - changeInputValue(cityInput, 'value', city); - changeInputValue(stateInput, 'value', state); - changeInputValue(stateCodeInput, 'value', statecode); - changeInputValue(postalCodeInput, 'value', postalCode); - changeInputValue(countryInput, 'value', country); - changeInputValue(placeLatInput, 'value', venue.coordinates?.lat); - changeInputValue(placeLngInput, 'value', venue.coordinates?.lon); - changeInputValue(placeIdInput, 'value', placeId); - changeInputValue(mapUrlInput, 'value', mapUrl); - changeInputValue(gmtoffsetInput, 'value', venue.gmtOffset); - BlockMediator.set('eventDupMetrics', { ...BlockMediator.get('eventDupMetrics'), city }); - - if (venueName) { + updateAllFields(venue, component); + BlockMediator.set('eventDupMetrics', { ...BlockMediator.get('eventDupMetrics'), city: venue.city }); + + if (venue.venueName) { component.classList.add('prefilled'); - togglePrefillableFieldsHiddenState(component, false); + togglePrefillableFieldsHiddenState(component); } } @@ -196,53 +265,6 @@ export default async function init(component, props) { } } -const getVenueDataInForm = (component) => { - const venueNameInput = component.querySelector('#venue-info-venue-name'); - const addressInput = component.querySelector('#venue-info-venue-address'); - const cityInput = component.querySelector('#location-city'); - const stateInput = component.querySelector('#location-state'); - const stateCodeInput = component.querySelector('#location-state-code'); - const postalCodeInput = component.querySelector('#location-zip-code'); - const countryInput = component.querySelector('#location-country'); - const placeLatInput = component.querySelector('#google-place-lat'); - const placeLngInput = component.querySelector('#google-place-lng'); - const placeIdInput = component.querySelector('#google-place-id'); - const mapUrlInput = component.querySelector('#google-map-url'); - const gmtoffsetInput = component.querySelector('#google-place-gmt-offset'); - - const venueName = venueNameInput.value; - const address = addressInput.value; - const city = cityInput.value; - const state = stateInput.value; - const stateCode = stateCodeInput.value; - const postalCode = postalCodeInput.value; - const country = countryInput.value; - const placeId = placeIdInput.value; - const mapUrl = mapUrlInput.value; - const lat = +placeLatInput.value; - const lon = +placeLngInput.value; - const gmtOffset = +gmtoffsetInput.value; - - const venueData = { - venueName, - address, - city, - state, - stateCode, - postalCode, - country, - placeId, - mapUrl, - coordinates: { - lat, - lon, - }, - gmtOffset, - }; - - return venueData; -}; - export async function onEventUpdate(component, props) { if (component.closest('.fragment')?.classList.contains('hidden')) return; diff --git a/ecc/blocks/form-handler/form-handler.js b/ecc/blocks/form-handler/form-handler.js index ee73e6bd..af2a2f6d 100644 --- a/ecc/blocks/form-handler/form-handler.js +++ b/ecc/blocks/form-handler/form-handler.js @@ -1,4 +1,3 @@ -import { ALLOWED_ACCOUNT_TYPES } from '../../constants/constants.js'; import { LIBS } from '../../scripts/scripts.js'; import { getIcon, @@ -6,6 +5,7 @@ import { generateToolTip, camelToSentenceCase, getEventPageHost, + signIn, getECCEnv, } from '../../scripts/utils.js'; import { @@ -26,8 +26,8 @@ import ProductSelectorGroup from '../../components/product-selector-group/produc import PartnerSelector from '../../components/partner-selector/partner-selector.js'; import PartnerSelectorGroup from '../../components/partner-selector-group/partner-selector-group.js'; import getJoinedData, { getFilteredCachedResponse, quickFilter, setPayloadCache, setResponseCache } from './data-handler.js'; -import BlockMediator from '../../scripts/deps/block-mediator.min.js'; import { CustomSearch } from '../../components/custom-search/custom-search.js'; +import { initProfileLogicTree } from '../../scripts/event-apis.js'; const { createTag } = await import(`${LIBS}/utils/utils.js`); const { decorateButtons } = await import(`${LIBS}/utils/decorate.js`); @@ -104,8 +104,8 @@ export function buildErrorMessage(props, resp) { }); }); } else if (errorMessage) { - if (resp.status === 409) { - const toast = createTag('sp-toast', { open: true, variant: 'negative' }, errorMessage, { parent: toastArea }); + if (resp.status === 409 || resp.error.message === 'Request to ESP failed: {"message":"Event update invalid, event has been modified since last fetch"}') { + const toast = createTag('sp-toast', { open: true, variant: 'negative' }, 'The event has been updated by a different session since your last save.', { parent: toastArea }); const url = new URL(window.location.href); url.searchParams.set('eventId', getFilteredCachedResponse().eventId); @@ -113,7 +113,7 @@ export function buildErrorMessage(props, resp) { slot: 'action', variant: 'overBackground', href: `${url.toString()}`, - }, 'See the latest version.', { parent: toast }); + }, 'See the latest version', { parent: toast }); toast.addEventListener('close', () => { toast.remove(); @@ -867,33 +867,18 @@ export default async function init(el) { return; } - const profile = BlockMediator.get('imsProfile'); - - if (profile) { - if (profile.noProfile || !ALLOWED_ACCOUNT_TYPES.includes(profile.account_type)) { + initProfileLogicTree({ + noProfile: () => { + signIn(); + }, + noAccessProfile: () => { buildNoAccessScreen(el); el.classList.remove('loading'); - } else { + }, + validProfile: () => { buildECCForm(el).then(() => { el.classList.remove('loading'); }); - } - - return; - } - - if (!profile) { - const unsubscribe = BlockMediator.subscribe('imsProfile', ({ newValue }) => { - if (newValue?.noProfile || !ALLOWED_ACCOUNT_TYPES.includes(newValue.account_type)) { - buildNoAccessScreen(el); - el.classList.remove('loading'); - unsubscribe(); - } else { - buildECCForm(el).then(() => { - el.classList.remove('loading'); - unsubscribe(); - }); - } - }); - } + }, + }); } diff --git a/ecc/components/image-dropzone/image-dropzone.js b/ecc/components/image-dropzone/image-dropzone.js index 197bc8ce..42a571f1 100644 --- a/ecc/components/image-dropzone/image-dropzone.js +++ b/ecc/components/image-dropzone/image-dropzone.js @@ -1,5 +1,6 @@ /* eslint-disable import/prefer-default-export */ /* eslint-disable class-methods-use-this */ +import { isImageTypeValid, isImageSizeValid } from '../../scripts/image-validator.js'; import { LIBS } from '../../scripts/scripts.js'; import { style } from './image-dropzone.css.js'; @@ -22,16 +23,21 @@ export class ImageDropzone extends LitElement { this.handleDelete = this.handleDelete || null; } - setFile(files) { + async setFile(files) { const [file] = files; - if (file.size > 26214400) { + + if (!isImageSizeValid(file, 26214400)) { this.dispatchEvent(new CustomEvent('show-error-toast', { detail: { error: { message: 'File size should be less than 25MB' } }, bubbles: true, composed: true })); return; } - if (file.type.startsWith('image/')) { + + const isValid = await isImageTypeValid(file); + if (isValid) { this.file = file; this.file.url = URL.createObjectURL(file); this.requestUpdate(); + } else { + this.dispatchEvent(new CustomEvent('show-error-toast', { detail: { error: { message: 'Invalid file type. The image file should be in one of the following format: .jpeg, .jpg, .png, .svg' } }, bubbles: true, composed: true })); } } @@ -39,22 +45,22 @@ export class ImageDropzone extends LitElement { return this.file; } - handleImageDrop(e) { + async handleImageDrop(e) { e.preventDefault(); e.stopPropagation(); const { files } = e.dataTransfer; if (files.length > 0) { - this.setFile(files); + await this.setFile(files); this.handleImage(); } } - onImageChange(e) { + async onImageChange(e) { const { files } = e.currentTarget; if (files.length > 0) { - this.setFile(files); + await this.setFile(files); this.handleImage(); } diff --git a/ecc/components/partner-selector/partner-selector.js b/ecc/components/partner-selector/partner-selector.js index fe1227bf..d0829f6a 100644 --- a/ecc/components/partner-selector/partner-selector.js +++ b/ecc/components/partner-selector/partner-selector.js @@ -149,13 +149,13 @@ export default class PartnerSelector extends LitElement {