SalesforceInteractions.init({ consents: new Promise(resolve => { const { OptIn, OptOut } = SalesforceInteractions.ConsentStatus const purpose = SalesforceInteractions.ConsentPurpose.Tracking const provider = 'Pronto' // Use event delegation to listen for the consent button even before it's rendered document.addEventListener('click', (event) => { if (event.target.matches('.consent-accept-button')) { resolve([{ purpose, provider, status: OptIn }]) } }, { once: true }) }) }).then(() => { // Set the log level during sitemap development to see potential problems SalesforceInteractions.setLoggingLevel('debug'); const { cashDom, listener, resolvers, sendEvent, util, CartInteractionName, CatalogObjectInteractionName, OrderInteractionName, } = SalesforceInteractions const global = { listeners: [ // Capture email address when a user signs up listener('submit', '.user-signup-form', (actionEvent) => { const emailAddress = cashDom("#user_email").val() if (emailAddress) { sendEvent({ interaction: { name: 'Email Sign Up' }, user: { attributes: { email: emailAddress, eventType: 'contactPointEmail' } } }) } }), // Section Click Tracking listener('click', '[data-section]', (actionEvent) => { const section = actionEvent.target; console.log('Section Click event fired'); const sectionId = section.getAttribute('data-section'); const sectionName = section.getAttribute('data-section-name') || section.textContent?.trim().substring(0, 50) || 'Unknown Section'; const sectionType = section.getAttribute('data-section-type') || 'general'; // Deduplication check const eventKey = `${sectionId}_${Date.now()}`; if (isDuplicateEvent('sectionClick', eventKey)) return; const engagementDescription = `User clicked on ${sectionName} section`; sendEvent({ interaction: { name: sectionName, eventType: 'sectionClick', category: 'Navigation', catalogObject: { type: 'PageEngagement', id: sectionId, attributes: { engagementType: 'Section Click', sectionName: sectionName, sectionType: sectionType, engagementDescription: engagementDescription, contentType: 'section', sourceUrl: window.location.href, page: window.location.pathname } } } }); }), ], // Attach optional data to every actionEvent that is sent out onActionEvent: (actionEvent) => { const email = window && window._userInfo && window._userInfo.email if (email) { actionEvent.user = actionEvent.user || {} actionEvent.user.attributes = actionEvent.user.attributes || {} actionEvent.user.attributes.emailAddress = email } return actionEvent } } // Restaurant Detail page const restaurantDetailPage = { name: 'Restaurant Detail', isMatch: () => /^\/restaurants\/[a-zA-Z0-9-]+\/?$/.test(window.location.pathname), interaction: { name: CatalogObjectInteractionName.ViewCatalogObject, catalogObject: { type: 'Restaurant', id: () => window.location.pathname.split('/').pop().replace('/', ''), attributes: { name: resolvers.fromSelector('h1.text-3xl.font-bold.mb-2.text-gray-900'), cuisine: resolvers.fromSelector('.px-2.bg-orange-100'), city: resolvers.fromSelector('.text-md.text-gray-500.mb-2 span'), url: resolvers.fromHref() } } }, listeners: [ // Capture when the user adds a menu item to their cart listener('click', '.add-to-cart-btn', () => { const element = cashDom('.add-to-cart-btn') const id = element.attr('data-id') const name = element.attr('data-name') const price = parseFloat(element.attr('data-price')) || 0 if (id && name) { sendEvent({ interaction: { name: CartInteractionName.AddToCart, lineItem: { catalogObjectType: 'MenuItem', catalogObjectId: id, price: price, quantity: 1 } } }) } }) ] } // Cart page const cartPage = { name: 'Cart', isMatch: () => /^\/cart$/.test(window.location.pathname), listeners: [ // Capture when a user removes an item from their cart listener('click', '.remove-from-cart', (event) => { const cartItem = cashDom(event.target).parents('.cart-item').first() const itemId = cartItem.attr('data-id') const quantity = parseInt(cartItem.find('.quantity').text().trim(), 10) if (itemId) { sendEvent({ interaction: { name: CartInteractionName.RemoveFromCart, lineItem: { catalogObjectType: 'MenuItem', catalogObjectId: itemId, quantity: quantity } } }) } }) ] } // Order Confirmation page const orderConfirmationPage = { name: 'Order Confirmation', isMatch: () => /^\/order-confirmation$/.test(window.location.pathname), interaction: { name: OrderInteractionName.Purchase, order: { id: resolvers.fromSelectorAttribute('.order-id', 'data-id'), totalValue: () => { const total = cashDom('.order-total') return parseFloat(total.attr('data-total')) || 0 }, lineItems: () => cashDom('.order .line-items').map((index, el) => { const lineItem = cashDom(el) return { catalogObjectType: 'MenuItem', catalogObjectId: lineItem.attr('data-id'), quantity: parseInt(lineItem.find('.quantity').text().trim(), 10) } }) } } } // Default page type const pageTypeDefault = { name: 'default', listeners: [ // Capture general section clicks listener('click', '[data-section]', (event) => { const element = cashDom(event.target) const sectionId = element.attr('data-section') const sectionName = element.attr('data-section-name') || element.text().trim().substring(0, 50) sendEvent({ interaction: { name: 'Section Click', catalogObject: { type: 'PageSection', id: sectionId, attributes: { name: sectionName, sectionType: element.attr('data-section-type') || 'general' } } } }) }) ] } // Lead Form Submission Tracking (via custom event) document.addEventListener('leadFormSubmitted', (event) => { const leadData = event.detail; console.log('Lead Form Submission event fired'); // Set identity FIRST before sending lead form event if (leadData.email) { console.log('[SITEMAP] Setting identity for lead form submission:', leadData.email); sendEvent({ interaction: { name: 'Identity Event', eventType: 'identity', category: 'Profile' }, user: { attributes: { eventType: 'identity', firstName: leadData.firstName, lastName: leadData.lastName, email: leadData.email, phoneNumber: leadData.phone, isAnonymous: '0' } } }); console.log('[SITEMAP] Identity set successfully'); } sendEvent({ interaction: { name: 'Partner Sign Up', eventType: 'partnerSignup', category: 'Engagement', firstName: leadData.firstName, lastName: leadData.lastName, email: leadData.email, phoneNumber: leadData.phone, businessName: leadData.businessName || '', address: leadData.address, isAnonymous: '0' }, user: { attributes: { eventType: 'identity', firstName: leadData.firstName, lastName: leadData.lastName, email: leadData.email, phoneNumber: leadData.phone, isAnonymous: '0' } } }); }); function sendPageViewEvent() { const payload = { interaction: { name: 'Page View', eventType: 'pageView', category: 'Engagement', page: window.location.pathname, url: window.location.href, referrer: document.referrer, } }; sendEvent(payload); } document.addEventListener('play', function(event) { const video = event.target; if (video.tagName === 'VIDEO') { const videoId = video.getAttribute('data-video-id') || video.src || 'unknown'; const videoTitle = video.getAttribute('data-video-title') || video.title || 'Untitled Video'; console.log('Video View event fired'); const videoDuration = video.getAttribute('data-video-duration') || video.duration || null; const videoContext = video.getAttribute('data-video-context') || 'general'; // Generate engagement description for video const generateVideoEngagementDescription = (title, context, duration) => { const contextText = context !== 'general' ? ` on ${context} page` : ''; const durationText = duration ? ` (${Math.round(duration)}s duration)` : ''; return `User started watching ${title}${contextText}${durationText}`; }; const engagementDescription = video.getAttribute('data-engagement-description') || generateVideoEngagementDescription(videoTitle, videoContext, videoDuration); const payload = { interaction: { name: videoTitle, eventType: 'pageEngagement', category: 'Engagement', engagementType: 'Video View', tierLevel: '', ctaText: '', engagementDescription: engagementDescription, contentType: 'video', sourceUrl: window.location.href, sourcePageType: window.location.pathname.split('/')[1] || 'home', page: window.location.pathname, url: window.location.href, } }; sendEvent(payload); } }, true); // Use capture phase for media events // Fire on initial load sendPageViewEvent(); // Fire on client-side navigation (Next.js/SPA) window.addEventListener('popstate', sendPageViewEvent); window.addEventListener('pushstate', sendPageViewEvent); window.addEventListener('replacestate', sendPageViewEvent); // Monkey-patch pushState/replaceState to emit events (function(history) { ['pushState', 'replaceState'].forEach(function(type) { const orig = history[type]; history[type] = function() { const rv = orig.apply(this, arguments); const event = new Event(type.toLowerCase()); window.dispatchEvent(event); return rv; }; }); })(window.history); SalesforceInteractions.initSitemap({ global, pageTypeDefault, pageTypes: [restaurantDetailPage, cartPage, orderConfirmationPage] }) console.log('Pronto sitemap registered successfully') })