Help
RSS
API
Feed
Maltego
Contact
Domain > forms.billsstadiumexperience.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2023-06-06
172.67.12.72
(
ClassC
)
2026-02-10
172.66.43.100
(
ClassC
)
Port 443
HTTP/1.1 200 OKDate: Tue, 10 Feb 2026 19:00:07 GMTContent-Type: text/html; charsetutf-8Content-Length: 193326Connection: keep-aliveServer: cloudflareX-Content-Type-Options: nosniffSet-Cookie: access_typestandalone; ExpiresTue, 10 Feb 2026 20:00:07 GMT; Path/Set-Cookie: tbits26ae2ebb-a3a5-4333-9939-2868ca548f75; ExpiresSun, 09 Aug 2026 19:00:07 GMT; Secure; HttpOnly; Path/; SameSiteNoneCache-Control: no-cache, no-store, max-age0, must-revalidate, proxy-revalidateP3P: CPNOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEMPermissions-Policy: browsing-topics()Referrer-Policy: strict-origin-when-cross-originAccept-Ranges: bytesPragma: no-cacheExpires: 0X-TBits-Container-ID: 69e9e4f51b36Strict-Transport-Security: max-age31536000; includeSubDomains; preloadcf-cache-status: DYNAMICCF-RAY: 9cbdd8f74809eedd-PDXalt-svc: h3:443; ma86400 !DOCTYPE html>html langen>head> meta charsetUTF-8> meta contentwidthdevice-width,initial-scale1.0,minimum-scale1.0 nameviewport> meta contentyes namemobile-web-app-capable/> meta contenthttps://tradablebits.com itempropauthor> meta contentNew Buffalo Bills Stadium Interest Form itempropname> meta contenthttps://tradablebits.com/static/icons/tradablebits-icon256x256.png itempropimage> meta content itempropdescription> meta content namedescription> meta contenthttps://forms.billsstadiumexperience.com/tb_app/487396 propertyog:url> meta contentwebsite propertyog:type> meta contentNew Buffalo Bills Stadium Interest Form propertyog:site_name> meta contentNew Buffalo Bills Stadium Interest Form propertyog:title> meta contenthttps://tradablebits.com/static/icons/tradablebits-icon256x256.png propertyog:image> meta content propertyog:description> meta contentsummary_large_image nametwitter:card> meta content nametwitter:site> meta content nametwitter:creator> meta contentNew Buffalo Bills Stadium Interest Form nametwitter:title> meta content nametwitter:description> meta contenthttps://tradablebits.com/static/icons/tradablebits-icon256x256.png nametwitter:image> link hrefhttps://forms.billsstadiumexperience.com/tb_app/487396 relcanonical/> link hrefhttps://static.tradablebits.com/static/icons/favicon.ico relicon typeimage/x-icon> title>New Buffalo Bills Stadium Interest Form/title> link hrefhttps://static.tradablebits.com/static/bootstrap/css/bootstrap-reboot.min.css relstylesheet> link hrefhttps://fonts.googleapis.com/css2?familyLato:wght@300;400;700&displayswap relstylesheet> link hrefhttps://static.tradablebits.com/static/js/select2/select2.min.css relstylesheet> link hrefhttps://static.tradablebits.com/static/css/tbits-icons.css relstylesheet> link relstylesheet hrefhttps://static.tradablebits.com/static/bootstrap/css/bootstrap.min.css> link relstylesheet hrefhttps://static.tradablebits.com/static/jquery/ui/jquery-ui.min.css/> link relstylesheet hrefhttps://tradablebits.com/static/css/campaign_buttons.css> script typemodule> const styleProps {background_attachment: fixed, button_border_radius: 4px, colour_background: #ffffff, colour_content_background: #ffffff, colour_content_section_background: #ffffff, colour_correct_button: #008561, colour_correct_button_text: #ffffff, colour_entry_field: #ffffff, colour_entry_field_text: #121212, colour_error_modal_background: #ffffff, colour_error_modal_text: #000000, colour_error_modal_title: #E36F70, colour_fan_wallet_background: #ffffff, colour_fan_wallet_text: #000000, colour_font: #03202f, colour_hyperlink_text: #4A4A4B, colour_inactive_button: #757575, colour_inactive_button_text: #ffffff, colour_incorrect_button: #D22124, colour_incorrect_button_text: #ffffff, colour_legal_text: #000000, colour_primary_button: #0c2e82, colour_primary_button_border: transparent, colour_primary_button_text: #ffffff, colour_secondary_button: #757575, colour_secondary_button_border: transparent, colour_secondary_button_text: #ebebeb, colour_selected_button: #d50a0a, colour_selected_button_text: #ffffff, colour_share_button: #000000, colour_timer_background: #dfdfdf, colour_timer_font: #000000, colour_timer_foreground: #00ba95, colour_timer_well_background: #FAFAFA, colour_timer_well_border: #D6D6D6, colour_timer_well_text: #000000, cookie_colour_background: #292929, cookie_colour_font: #f0f0f0, cookie_colour_link: #33CCA3, cookie_colour_primary_button: #33CCA3, cookie_colour_primary_button_text: #292929, cookie_colour_secondary_button: #33CCA3, opacity_content_background: 0, repeat_background: no-repeat, selected_font: default, size_background: auto, social_media_icons_style: standard, weight_button_border: 1}; // create CSS custom properties for style props, called `--style_prop-name` for (const key, value of Object.entries(styleProps)) { document.documentElement.style.setProperty(`--style_prop_${key}`, value); } if (styleProps.selected_font && styleProps.selected_font ! default) { let customFont new FontFace(custom_font, `url(https://tradablebits.com/fb_media/${styleProps.selected_font})`) document.fonts.add(customFont) } /script> !-- custom html --> !-- end --> style> html, body { height: 100%; margin: 0; padding: 0; font-weight: normal; font-size: 14px; line-height: 1.5; font-family: custom_font, Lato, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; } #tbits-campaign { color: var(--style_prop_colour_font); background-color: var(--style_prop_colour_background); background-size: var(--style_prop_size_background); background-repeat: var(--style_prop_repeat_background); background-attachment: var(--style_prop_background_attachment); background-position: center; } img { max-width: 100%; } #main { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; } /* Small screens */ #content, .header-section .campaign-content, .content-width { width: min(480px, 100% - 10px); } .campaign-content { padding: 10px; } /* Medium Screens */ @media screen and (min-width: 600px) { #content, .header-section .campaign-content, .content-width { width: min(600px, 100% - 30px); } .campaign-content { padding: 30px; } } /* Large Screens */ @media screen and (min-width: 800px) { #content, .header-section .campaign-content, .content-width { width: min(800px, 100% - 30px); } .campaign-content { padding: 30px; } } #header { width: 100%; } #content, .header-section { display: flex; flex-direction: column; align-items: center; justify-content: center; } .header-image { width: min(800px, 100%); } .header-image img { width: 100%; object-fit: cover; } .campaign-section { width: 100%; } .campaign-content { background-color: rgb(from var(--style_prop_colour_content_background) r g b / var(--style_prop_opacity_content_background)); } .content-width { margin-left: auto; margin-right: auto; } a { color: var(--style_prop_colour_hyperlink_text) } a:focus, a:focus-visible { color: color-mix(in srgb, var(--style_prop_colour_hyperlink_text) 88%, #000000); } a:hover { color: color-mix(in srgb, var(--style_prop_colour_hyperlink_text) 88%, #000000); } #preloader { background-color: var(--style_prop_colour_background); height: 100vh; width: 100vw; position: fixed; z-index: 2; left: 0; top: 0; display: none; } .preloader-inner { height: inherit; display: flex; flex-direction: column; align-items: center; justify-content: center; } #white-wall { position: fixed; top: 0; left: 0; overflow: hidden; height: 4000px; width: 100%; background: var(--bg-color, #fff); opacity: .75; display: none; z-index: 2000; } #white-wall img { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2001; } img.tbits-throbber { width: 80px; height: 80px; animation: tbits-throbber-spin 1s ease-in-out infinite; user-select: none; } img.tbits-throbber.nospin { animation: none; } img.media-manager-throbber { width: 60px; height: 50px; animation: tbits-throbber-spin 1s ease-in-out infinite; user-select: none; } @keyframes tbits-throbber-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(-360deg); } } #background-video { position: fixed; top: 0; bottom: 0; min-width: 100%; min-height: 100%; z-index: -1; display: block; } .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .error-modal-title { color: var(--style_prop_colour_error_modal_title, #E36F70) } .error-modal .modal-content, .show-success-modal .modal-content { background-color: var(--style_prop_colour_error_modal_background, #ffffff); } .error-modal-body, .show-success-modal .modal-body { padding: 1.5rem 10%; text-align: center; color: var(--style_prop_colour_error_modal_text, #000000); } .show-success-modal { .modal-body.show-success-message { text-align: center; margin: 0; padding: 50px 15px; font-size: 40px; } .tbits-icon { margin-right: 20px; font-size: 30px; } } .error-modal-error { text-align: center; margin-bottom: 1.5rem; font-weight: bold; } .error-modal-trace-wrapper { color: rgb(122, 122, 122); margin-bottom: 0; word-break: break-all; } .error-modal-trace { font-family: monospace; font-size: 11px; } .error-modal-footer { text-align: left; } .campaign-table { border-collapse: collapse; width: 100%; } .campaign-table th, .campaign-table td { border: 1px solid #808080; padding: 8px 12px; text-align: left; font-weight: 400; } .campaign-table th { background-color: #f0f0f0; font-weight: 700; } .campaign-table th p, .campaign-table td p { font-weight: unset; margin: 0; } inputtyperange { accent-color: var(--style_prop_colour_selected_button); } .slider-container { display: flex; justify-content: center; flex-direction: column; width: 100%; } .slider-container .range-overlay-container { display: flex; justify-content: center; width: 100%; } .slider-container .range-overlay { font-size: small; position: relative; bottom: 4px; left: 0; justify-content: center; border-radius: 4px; padding: 4px 8px; min-width: 30px; text-align: center; background-color: var(--style_prop_colour_selected_button); color: var(--style_prop_colour_selected_button_text); } .slider-container label { width: 100%; margin-bottom: 0; align-self: center; } .slider-labels { display: flex; justify-content: space-between; margin-top: 5px; } .slider-labels h4 { text-wrap: nowrap; margin-top: 0; font-weight: bold; font-size: inherit; } .pre-campaign-failure-alert { border-radius: 4px; border: 1px solid #D22124; background: #FFF5F4; text-align: center; color: #811012; font-size: 14px; font-weight: bold; min-height: 57px; padding: 0 24px; display: flex; align-items: center; } #pre-content-container { display: none; width: 100%; min-height: 100dvh; position: fixed; inset: 0; backdrop-filter: blur(200px); -webkit-backdrop-filter: blur(200px); z-index: 1; flex-direction: column; align-items: center; } #pre-content { flex: 1; width: 100%; display: flex; align-items: center; padding: 24px; } #pre-content > .campaign-section { display: flex; justify-content: center; } #pre-content-footer { width: 100%; } .pre-campaign-section { display: flex; flex-direction: column; align-items: center; gap: 40px; width: 100%; max-width: 528px; } .pre-campaign-section-text { font-weight: bold; font-size: 20px; } .pre-campaign-section-icon { font-size: 80px; } /style> /head>body idtbits-campaign classtbits-campaign> main idcampaign>/main>div idpreloader> div classpreloader-inner> img altLoading... height64 srchttps://tradablebits.com/static/icons/defaultthrobber.svg width64> /div>/div>div idwhite-wall> img classtbits-throbber nospin srchttps://tradablebits.com/static/icons/defaultthrobber.svg>/div>script srchttps://static.tradablebits.com/static/jquery/jquery-3.6.4.min.js>/script>script srchttps://static.tradablebits.com/static/jquery/ui/jquery-ui.min.js>/script>script srchttps://static.tradablebits.com/static/jquery/jquery.ui.touch-punch.min.js>/script>script srchttps://static.tradablebits.com/static/js/select2/select2.min.js>/script>script srchttps://static.tradablebits.com/static/js/libs/underscore.js>/script>script srchttps://static.tradablebits.com/static/js/tbits-1.2.js>/script>script defer srchttps://tradablebits.com/pixels/7177377/sdk.js>/script>script srchttps://static.tradablebits.com/static/bootstrap/js/bootstrap.min.js>/script>script srchttps://static.tradablebits.com/static/js/libs/luxon.min.js>/script>script srchttps://tradablebits.com/static/third-party-tracking.js>/script>script idstandard-layout-template typetext/template> main idmain> div idpre-content-container> div idpre-content>/div> div idpre-content-footer>/div> /div> header idheader>/header> div idcontent>/div> footer idfooter>/footer> /main> style> .cookie-popup { background-color: var(--style_prop_cookie_colour_background, rgb(41, 41, 41)); border-radius: 4px; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.7); margin: 20px auto 30px auto; color: var(--style_prop_cookie_colour_font, #f0f0f0); position: fixed; bottom: 0; left: 30px; width: 350px; z-index: 999; } .cookie-pop-title { font-size: 18px; font-weight: 600; margin-top: 0; } .cookie-popup-header { padding: 20px 20px 10px 25px; } .cookie-popup-body { padding: 0 15px 15px 15px; } .cookie-msg { padding: 0 10px 16px 10px; } @media (max-width: 767px) { .cookie-popup { width: auto; left: 0; margin: 20px auto 0 auto; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .cookie-pop-title { font-size: 14px; } .cookie-popup-header { padding: 18px 20px 0 25px; } .cookie-popup-body { max-height: 80%; } .cookie-msg { font-size: 13px; padding: 0 10px 10px 10px; } } .cookie-btn-group { display: flex; justify-content: space-evenly; gap: 10px; margin: 5px; } .cookie-btn { display: inline-grid; grid-auto-flow: column; column-gap: 0.375em; justify-content: center; align-items: center; font-weight: bold; font-size: 14px; line-height: 1.428571429; vertical-align: middle; user-select: none; background-color: transparent; border: 1px solid transparent; border-radius: 4px; padding: 6px 12px; text-decoration: none; cursor: pointer; touch-action: manipulation; flex-grow: 1; } .cookie-btn-primary, .cookie-btn-primary:hover, .cookie-btn-primary:focus { background-color: var(--style_prop_cookie_colour_primary_button, rgb(51, 204, 163)); color: var(--style_prop_cookie_colour_primary_button_text, rgb(41, 41, 41)); } .cookie-btn:hover, .cookie-btn:focus { cursor: pointer; filter: brightness(85%); } .cookie-btn-secondary, .cookie-btn-secondary:hover, .cookie-btn-secondary:focus { background-color: rgb(41, 41, 41); border-color: var(--style_prop_cookie_colour_secondary_button, rgb(51, 204, 163)); color: var(--style_prop_cookie_colour_secondary_button, rgb(51, 204, 163)); } .cookie-link { color: var(--style_prop_cookie_colour_link, rgb(51, 204, 163)); text-decoration: underline; } .cookie-link:hover, .cookie-link:focus { color: var(--style_prop_cookie_colour_link, rgb(0, 173, 127)); }/style>% if (tab.props.disable_cookie_optout) { %> % if (!tab.props.disable_cookie_warning) { %> div classcookie-popup styledisplay: none> div classcookie-popup-header> a classclose close-cookie-popup-btn stylecolor: #f0f0f0>×/a> div classcookie-pop-title> %- tab.locale_props.legal_text_privacy %> /div> /div> div classcookie-popup-body> div classcookie-msg> span>%- tab.locale_props.legal_text_cookie_policy_overall %> a href%- tab.home_url %>/terms target_blank aria-labelRead more about the Tradable Bits terms of use, opens new tab> %- tab.locale_props.legal_text_terms?.toLowerCase() %> /a> & a href%- tab.home_url %>/privacy target_blank aria-labelRead more about the Tradable Bits privacy policy, opens new tab> %- tab.locale_props.legal_text_privacy?.toLowerCase() %> /a>. /span> /div> /div> /div> % } %>% } else { %> div classcookie-popup styledisplay: none> div classcookie-popup-header> div classcookie-pop-title> %- tab.locale_props.legal_text_privacy %> /div> /div> div classcookie-popup-body> div classcookie-msg stylepadding-bottom: 10px> span>%- tab.locale_props.legal_text_cookie_policy_overall %> a classcookie-link href%- tab.home_url %>/terms target_blank aria-labelRead more about the Tradable Bits terms of use, opens new tab> %- tab.locale_props.legal_text_terms?.toLowerCase() %> /a> & % if (tab.props.cookie_warning_custom_policy_url && tab.props.cookie_warning_business_name) { %> %- tab.props.cookie_warning_business_name %> a classcookie-link href%- tab.props.cookie_warning_custom_policy_url %> target_blank aria-labelRead more about our privacy policy, opens new tab> %- tab.locale_props.legal_text_privacy?.toLowerCase() %> /a>. % } else { %> a classcookie-link href%- tab.home_url %>/privacy target_blank aria-labelRead more about the Tradable Bits privacy policy, opens new tab> %- tab.locale_props.legal_text_privacy?.toLowerCase() %> /a>. % } %> /span> /div> hr styleborder: 0; border-top: 1px solid rgba(238, 238, 238, 0.3)> div classcookie-msg> span>%- tab.locale_props.legal_text_cookie_policy_marketing %> /span> /div> div classcookie-btn-group> button typebutton classcookie-btn cookie-btn-primary accept-cookies-btn> %- tab.locale_props.cookies_accept %> /button> button typebutton classcookie-btn cookie-btn-secondary decline-cookies-btn> %- tab.locale_props.cookies_decline %> /button> /div> /div> /div>% } %>/script>script idsingle-page-campaign-script> const PAGE_TAB_ID 487396; const ACCESS_TYPE getCookie(access_type) native ? native : standalone; const IS_EMBEDDED window.self ! window.top; async function loadPageTab() { const params new URLSearchParams(document.location.search) const {body: tab} await tbitsFetch(`/tb_app/${PAGE_TAB_ID}/page_tab`, {params}) // Set up steps that require loadPageTab if (tab.media_map.video_background_url) { const backgroundVideo document.createElement(video); backgroundVideo.id background-video; backgroundVideo.autoplay true; backgroundVideo.muted true; backgroundVideo.loop true; backgroundVideo.src tab.media_map.video_background_url1; document.querySelector(#tbits-campaign).prepend(backgroundVideo) } else if (tab.media_map.image_background) { const backgroundImageUrl `${tab.home_url}/fb_media/${tab.media_map.image_background1}` document.querySelector(#tbits-campaign).style.setProperty(background-image, `url(${backgroundImageUrl})`) } return tab; } function conversionTrack(activityId) { ThirdPartyTracking.sendRegistration({content_name: Interest Deposit Form - Tickets}); if (window.trackEvent) { trackEvent(page_tab, null, null, PAGE_TAB_ID, activityId, null); } } function getCookie(name) { const value document.cookie .split(; ) .find(row > row.startsWith(name + )) ?.split()1; return value ? decodeURIComponent(value) : null; } function updateRangeOverlay(element) { const currentValue parseInt(element.value); const minRange, maxRange parseFloat(element.min), parseFloat(element.max); const sliderOverlay element.previousElementSibling.querySelector(.range-overlay); const sliderWidth element.clientWidth; sliderOverlay.innerText currentValue; const newVal Number(((currentValue - minRange) * 100) / (maxRange - minRange)); sliderOverlay.style.left `calc(${Number((newVal / 100 * sliderWidth) - (sliderWidth / 2))}px + (${Number(8 - newVal * 0.15)}px))`; window.addEventListener(resize, () > { updateRangeOverlay(element); }, {once: true}); } pingServer(PAGE_TAB_ID) class Campaign { /** * Current state object * @type {CampaignState | null} **/ currentState null; /** @type {CampaignState} */ stateHistory ; /** @type {object} */ tab; /** @type {Mapstring, CampaignState>} */ states new Map(); /** @type {string | null } */ layoutTemplateName #standard-layout-template updateInterval null; constructor(tab) { if (typeof(customCallback) function) { customCallback(); } this.tab tab; this.tab.startDateTime luxon.DateTime.fromISO(this.tab.start_timestamp, {zone: this.tab.time_zone}) this.tab.startTimestampUnix this.tab.startDateTime.toUnixInteger() this.tab.endDateTime luxon.DateTime.fromISO(this.tab.end_timestamp, {zone: this.tab.time_zone}) this.tab.endTimestampUnix this.tab.endDateTime.toUnixInteger() this.initialize() this.setupCampaignTracking(); } reloadCurrentState() { this.transitionTo(this.currentState, ...this.currentArgs); } initialize() { // General Campaign this.ageGateState new AgeGateState(this.tab) this.ageGateFailedState new AgeGateFailedState(this.tab) this.regionGateState new RegionGateState(this.tab) this.regionGateFailedState new RegionGateFailedState(this.tab) this.notStartedState new NotStartedState(this.tab) this.contestOverState new ContestOverState(this.tab) this.authLoginState new AuthLoginState(this.tab) AgeGateSection.ageGateCompleteEvent.on(() > { this.start() }) AgeGateSection.ageGateFailedEvent.on(() > { this.transitionTo(this.ageGateFailedState) }) RegionGateState.regionGatePassed.on(() > { this.start() }) RegionGateState.regionGateFailed.on((ev) > { const {detail} ev; this.transitionTo(this.regionGateFailedState, detail) }) EmailLogoutSection.fanLogoutEvent.on(() > { this.authLoginState.logOut() this.start() }) StartAgainSection.startAgainLogoutEvent.on(() > { this.authLoginState.logOut() window.location.reload() }) ClockDateCountdownSection.clockDateCountdownOver.on(() > { this.start() }) ConnectFormSection.fanLoginEvent.on(() > { this.start() }) ChangeLanguageSection.changeCampaignLanguageEvent.on(({detail}) > { this.tab.localeLanguage detail.setLanguage; this.reloadCurrentState(); }) AuthLoginState.reloadStateEvent.on((() > { this.reloadCurrentState(); })) } setupCampaignTracking() { this.appTrackingInitialized false; const trackingTags this.tab.tracking_tags || ; ThirdPartyTracking.setTrackingTags(trackingTags); ThirdPartyTracking.setLogData({page_tab_id: this.tab.page_tab_id}); } start() { throw new Error(start not implemented); } setCookiePopupEventHandlers() { const cookiePopupElem document.querySelector(.cookie-popup); const cookiePopupCloseBtn document.querySelector(.close-cookie-popup-btn); const cookiePopupAcceptBtn document.querySelector(.accept-cookies-btn); const cookiePopupDeclineBtn document.querySelector(.decline-cookies-btn); const closeCookiePopup () > { cookiePopupElem.style.display none; createCookie(tbits_campaign_privacy, seen, 21); if (this.tab.props.disable_cookie_optout) { ThirdPartyTracking.load(); } } if (cookiePopupCloseBtn) { cookiePopupCloseBtn.addEventListener(click, () > closeCookiePopup()); } if (cookiePopupAcceptBtn) { cookiePopupAcceptBtn.addEventListener(click, () > { createCookie(tbits_campaign_privacy, accept, 21); ThirdPartyTracking.load(); cookiePopupElem.style.display none; }); } if (cookiePopupDeclineBtn) { cookiePopupDeclineBtn.addEventListener(click, () > { createCookie(tbits_campaign_privacy, decline, 21); cookiePopupElem.style.display none; }); } } initAppTracking() { const cookiePopupElem document.querySelector(.cookie-popup); this.setCookiePopupEventHandlers(); if (IS_EMBEDDED) { // Embedded: it is expected that the parent window handles tracking consent. // We fire tracking events regardless, and the parent window can decide to ignore them. ThirdPartyTracking.load(); } else { const privacyCookie readCookie(tbits_campaign_privacy) if (!privacyCookie) { cookiePopupElem.style.display ; } else if (privacyCookie seen) { if (this.tab.props.disable_cookie_optout) { ThirdPartyTracking.load(); } else { cookiePopupElem.style.display ; } } else if (privacyCookie accept) { ThirdPartyTracking.load(); } } } handleCookiePopup() { const cookiePopupElem document.querySelector(.cookie-popup); if (!this.tab.props.disable_cookie_warning && ACCESS_TYPE standalone) { if (!this.appTrackingInitialized) { this.appTrackingInitialized true; this.initAppTracking(this.tab); // dont show if embedded } else if (!IS_EMBEDDED) { this.setCookiePopupEventHandlers(); const privacyCookie readCookie(tbits_campaign_privacy) if (!privacyCookie) { cookiePopupElem.style.display ; } else if (privacyCookie seen && !this.tab.props.disable_cookie_optout) { cookiePopupElem.style.display ; } } } } /** * Record the current state in the state history, then enter the new state (calling its `enter` method). * @param nextState {CampaignState} * @param args */ transitionTo(nextState, ...args) { $(#preloader).show() if (this.currentState) { this.stateHistory.push({state: this.currentState, args: this.currentArgs}); } this.currentState nextState; this.currentArgs args; this.setLocaleProps(this.tab.localeLanguage) let layoutTemplate _.template($(this.layoutTemplateName).html())({tab: this.tab}); $(#campaign).html(layoutTemplate) nextState.enter(...args) this.updateEmbeddedAttributes() this.handleCookiePopup() triggerTbitsEvent(tbits.stateChange, {stateName: this.currentState.id}) $(#preloader).hide() } setLocaleProps(setLanguage) { const propLanguages Object.keys(this.tab.full_locale_props).map(l > l.toLowerCase()) if (setLanguage && propLanguages.includes(setLanguage)) { this.tab.localeLanguage setLanguage; } else { this.tab.localeLanguage this.selectPriorityLanguage() } this.tab.locale_props this.tab.full_locale_propsthis.tab.localeLanguage.props this.tab.language this.tab.full_locale_propsthis.tab.localeLanguage.language } selectPriorityLanguage() { const urlParams new URLSearchParams(window.location.search); const langParam urlParams.get(lang); const propLanguages Object.keys(this.tab.full_locale_props).map(l > l.toLowerCase()) // If theres only one available language, use it. if (propLanguages.length 1) { return propLanguages0 } // if a language is forced via the lang param then we respect it if its supported if (propLanguages.includes(langParam)) { return langParam } // next we take the default language if one is set let values Object.values(this.tab.full_locale_props) let pageTabDefault values.find(l > l.page_tab_default true); if (pageTabDefault) { return pageTabDefault.language_code } const browserLanguages navigator.languages.map(l > l.toLowerCase()); // Match browser languages to available prop languages for (const browserLang of browserLanguages) { if (propLanguages.includes(browserLang)) { return browserLang } // If browser languages didnt match exactly, try the prefix. Ei this will match fr-ca to fr-fr for (const propLang of propLanguages) { if (browserLang.split(-)0 propLang.split(-)0) { return propLang; } } } //last option: first prop language return propLanguages0 } updateEmbeddedAttributes() { if (IS_EMBEDDED) { this.sendUpdatedHeight(); this.sendEmbeddedDetails() setTimeout(() > this.sendUpdatedHeight(), 200); // DK: not sure if its good to have it here. might need to be moved to global code if (this.updateInterval ! null) { clearInterval(this.updateInterval); } this.updateInterval setInterval(() > this.sendUpdatedHeight(), 1000); window.addEventListener(resize, () > { this.sendUpdatedHeight() }); } } sendUpdatedHeight() { const height $(#campaign).height() + 100; window.parent.postMessage({ id: PAGE_TAB_ID, type: height, data: {height}, }, *); } sendEmbeddedDetails() { let title this.tab.name; window.parent.postMessage({ id: PAGE_TAB_ID, type: details, data: {title}, }, *); } async getFanResults() { const params new URLSearchParams(document.location.search) return await tbitsFetch(urlWithCookie(`/tb_app/${this.tab.page_tab_id}/fan_results`), {params}) .then((res) > res.body) .catch((error) > { console.log(error); return null; }) } } /** * Abstract base class for campaign states * @abstract */ class CampaignState { /** * Unique identifier for the state, e.g. entry_form. * Used for tracking state history. * @type {string} **/ id; constructor(tab) { if (this.constructor CampaignState) { throw new TypeError(Cannot instantiate abstract class); } this.id this.constructor.name; this.tab tab; } /** * Enter the state. Usually used to render the state. */ enter() { throw new Error(enter not implemented); } /** * Render the state and initialize any event listeners. */ render() { throw new Error(render not implemented); } } /** * @template T void */ class CampaignEvent extends EventTarget { /** * @param eventName {string} The name of the event */ constructor(eventName) { super(); this.eventName eventName; this.eventCallbacks ; } /** * Add event listener * @param listener {(event: CustomEventT>) > void} */ on(listener) { this.addEventListener(this.eventName, listener); this.eventCallbacks.push(listener); } /** * Remove event listener * @param listener {(event: CustomEventT>) > void} */ off(listener) { this.removeEventListener(this.eventName, listener); } /** * Remove all event listeners */ clear() { for (const listener of this.eventCallbacks) { this.off(listener); } this.eventCallbacks ; } /** * Trigger the event. * @param detail {T} */ trigger(detail undefined) { this.dispatchEvent(new CustomEvent(this.eventName, {detail})); } } // Custom elements stored in internal registry cache, delay the insertion so they can be modified before adding. customElements.pendingElements {}; customElements.getPendingElement function(name) { return this.pendingElementsname; }; customElements.defineNow customElements.define; customElements.define function(name, constructor) { if (constructor.prototype instanceof Section) { this.pendingElementsname constructor; } else { this.defineNow(name, constructor); } }; class Section extends HTMLElement { tab null; static create(element, data) { const elementDefined !!customElements.get(element); if (!elementDefined) { const constructorClass customElements.getPendingElement(element); if (!constructorClass) { throw new Error(`Unable to find section class for ${element}`); } customElements.defineNow(element, constructorClass); } const section document.createElement(element); section.className campaign-section; Object.assign(section, data) return section } }/script>div classmodal fade idmodal-dialog> div classmodal-dialog> div classmodal-content> div classmodal-header>/div> div classmodal-body>/div> div classmodal-footer>/div> /div> /div>/div>style> .age-gate-section { width: 100%; } .age-gate-fields { display: flex; flex-direction: column; gap: 10px; } .age_date_error { display: none; font-weight: bold; font-size: 14px; margin: 5px; color: #811012; } .age-gate-section-header-image img, .age-gate-failed-section-header-image img { width: 100%; }/style>script idage-gate-section-template typetext/template> div> div classpre-campaign-section> span classtbits-icon tbits-icon-lock-icon pre-campaign-section-icon>/span> div classpre-campaign-section-text>%- tab.locale_props.age_gate_header_text %>/div> div classage-gate-section> div classage-gate-fields>/div> div classage_date_error>%- tab.locale_props.age_date_error %>/div> div classbase-btn-container> button classbase-btn primary idage-confirm>/button> /div> /div> /div> /div>/script>script idage-gate-failed-section-template typetext/template> div classpre-campaign-section> span classtbits-icon tbits-icon-lock-icon pre-campaign-section-icon>/span> div classpre-campaign-failure-alert> % if (tab.props.check_age drinking) { %> %- tab.locale_props.drink_warning %> % } else if (tab.props.check_age coppa_compliant) { %> %- tab.locale_props.coppa_warning %> % } else if (tab.props.check_age custom) { %> %- tab.locale_props.custom_age_gate %> % } %> /div> /div>/script>script idage-gate-states> class AgeGateState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { $(#pre-content-container).css(display, flex) const $preContent $(#pre-content); const $preContentFooter $(#pre-content-footer) const $header $(#header) $header.append(Section.create(welcome-header-section, {tab: this.tab})); $header.append(Section.create(change-language-section, {tab: this.tab})) $preContent.prepend(Section.create(age-gate-section, {tab: this.tab})) $preContentFooter.append(Section.create(share-links-section, {tab: this.tab})) $preContentFooter.append(Section.create(legal-section, {tab: this.tab})) } isPassed() { const checked readCookie(`age_checked_${this.tab.page_tab_id}`) return !!checked; } } class AgeGateFailedState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { const $header $(#header); $header.append(Section.create(change-language-section, {tab: this.tab})) $header.append(Section.create(welcome-header-section, {tab: this.tab})); $(#pre-content-container).css(display, flex) const $preContent $(#pre-content); const $preContentFooter $(#pre-content-footer) $preContent.append(Section.create(age-gate-failed-section, {tab: this.tab})) $preContentFooter.append(Section.create(share-links-section, {tab: this.tab})) $preContentFooter.append(Section.create(legal-section, {tab: this.tab})) } }/script>script idage-age-failed-section-script> class AgeGateFailedSection extends Section { template _.template($(#age-gate-failed-section-template).html()) connectedCallback() { this.innerHTML this.template({tab: this.tab}) } } customElements.define(age-gate-failed-section, AgeGateFailedSection)/script>script idage-gate-section-script> class AgeGateSection extends Section { static ageGateCompleteEvent new CampaignEvent(age-gate-complete) static ageGateFailedEvent new CampaignEvent(age-gate-failed) template _.template($(#age-gate-section-template).html()) minimum_age; async connectedCallback() { if (this.ageCheckRequired()){ let $template $(this.template({tab: this.tab})) await this.renderForm($template) this.innerHTML $template.html() $(#country_minimum_age).select2({width: 100%}) $(this).find(#age-confirm).on(click, () > { this.confirmAge() }) } } ageCheckRequired () { const checked readCookie(`age_checked_${this.tab.page_tab_id}`) const blocked readCookie(`age_block_${this.tab.page_tab_id}`); if (checked) { AgeGateSection.ageGateCompleteEvent.trigger() return false } if (blocked) { AgeGateSection.ageGateFailedEvent.trigger() return false } return true } async renderForm($template) { const $ageConfirmBtn $template.find(#age-confirm) if (this.tab.props.check_age drinking) { $ageConfirmBtn.text(this.tab.locale_props.drink_warning_submit); this.minimum_age 18; //This is a fallback value await this.setupDrinkingAgeCountry($template) } else if (this.tab.props.check_age coppa_compliant) { $ageConfirmBtn.text(this.tab.locale_props.coppa_warning_submit); this.minimum_age 13; } else if (this.tab.props.check_age custom) { $ageConfirmBtn.text(this.tab.locale_props.custom_age_gate_submit); this.minimum_age this.tab.props.check_age_custom_value } let fieldObj { label: this.tab.locale_props.birth_date_text, isMandatory: true } const dateFormat this.tab.props.date_format; $template.find(.age-gate-fields).append(renderBirthday(fieldObj, dateFormat)) } async setupDrinkingAgeCountry($template) { let res await this.getLocationDrinkingAge() let drinkingAges res.countries; let options for (const country of drinkingAges) { if (country.province) { options.push({ value: country.province_drinking_age, label: `${country.country_name} / ${country.province}` }) } else { options.push({ value: country.country_drinking_age, label: country.country_name }) } } let countryFieldObj { id: country_minimum_age, name: country_minimum_age, label: this.tab.locale_props.country_text, placeholder: this.tab.locale_props.select_country, isMandatory: true, options, } $template.find(.age-gate-fields).append(renderDropdown(countryFieldObj)) } async getLocationDrinkingAge(country_code, province) { const {body} await tbitsFetch(/application/location_drinking_age, {params: {country_code, province}}) return body } confirmAge() { let $countryMinimumAge $(#country_minimum_age); if ($countryMinimumAge.length && $countryMinimumAge.val().length 0) { $countryMinimumAge.addClass(required); return; } $countryMinimumAge.removeClass(required); if ($countryMinimumAge.length) { this.minimum_age $countryMinimumAge.val(); } let day parseInt($(#age_day).val()); let month parseInt($(#age_month).val()); let year parseInt($(#age_year).val()); let birthday new Date(year, month - 1, day); let today new Date() let minAge new Date(today.getFullYear() - this.minimum_age, today.getMonth(), today.getDay()); if (isNaN(birthday.getDate()) || year 1900 || year > today.getFullYear()) { $(#age_day, #age_month, #age_year).addClass(required); $(.age_date_error).show() return false; } $(.age_date_error).hide() $(#age_day, #age_month, #age_year).removeClass(required); if (minAge birthday) { createCookie(`age_block_${this.tab.page_tab_id}`, true, 1); AgeGateSection.ageGateFailedEvent.trigger() return false; } createCookie(`age_checked_${this.tab.page_tab_id}`, true, 30); AgeGateSection.ageGateCompleteEvent.trigger() return true; } } customElements.define(age-gate-section, AgeGateSection)/script>style> .region-gate-failed-text { font-weight: 600; padding: 20px; text-align: center; } .region-gate-section { width: 100%; } .region-gate-section .entry-form-section-field input, .region-gate-section .entry-form-section-field .select2-container--default .select2-selection--single.entry-form-section-field, .select2-container--default .select2-dropdown, .select2-container--default .select2-search--dropdown .select2-search__field{ background-color: var(--style_prop_colour_entry_field); color: var(--style_prop_colour_entry_field_text); } .region-gate-fields { display: flex; flex-direction: column; gap: 10px; }/style>script idregion-gate-failed-section-template typetext/template> div classpre-campaign-section> span classtbits-icon tbits-icon-lock-icon pre-campaign-section-icon>/span> div classpre-campaign-failure-alert> %- tab.locale_props.location_gate %> /div> /div>/script>script idregion-gate-form-section-template typetext/template> div> div classpre-campaign-section> span classtbits-icon tbits-icon-lock-icon pre-campaign-section-icon>/span> div classpre-campaign-section-text>%- tab.locale_props.region_gate_header_text %>/div> div classregion-gate-section> div classregion-gate-fields>/div> div classbase-btn-container> button classbase-btn primary idregion-confirm typebutton> %- tab.locale_props.submit %> /button> /div> /div> /div> /div>/script>script idregion-age-gate-script> class RegionGateState extends CampaignState { static regionGatePassed new CampaignEvent(region-gate-passed) static regionGateFailed new CampaignEvent(region-gate-failed) passed false; constructor(tab) { super(tab); RegionGateFormSection.regionFormPassedEvent.on(() > { this.passed true RegionGateState.regionGatePassed.trigger() }) RegionGateFormSection.regionFormFailedEvent.on(() > { RegionGateState.regionGateFailed.trigger() }) } async enter(data) { let status await RegionGateState.getLocationStatus(this.tab.page_tab_id) if (status pass) { this.passed true; return RegionGateState.regionGatePassed.trigger() } else if (status fail) { return RegionGateState.regionGateFailed.trigger() } await this.render(status) } async render(status) { const $header $(#header); $header.append(Section.create(change-language-section, {tab: this.tab})) $header.append(Section.create(welcome-header-section, {tab: this.tab})) if (status region_requested) { $(#pre-content-container).css(display, flex) $(#pre-content).append(Section.create(region-gate-form-section, { tab: this.tab, country_code: this.country_code })) const $preContentFooter $(#pre-content-footer) $preContentFooter.append(Section.create(share-links-section, {tab: this.tab})) $preContentFooter.append(Section.create(legal-section, {tab: this.tab})) } else if (status latlong_requested) { await this.latLongCheck() } } isPassed() { return this.passed; } async latLongCheck() { let position await this.getCurrentPosition() if (position) { createCookie(latlong_check_cookie, `${position.coords.latitude},${position.coords.longitude}`, 1) } let status await RegionGateState.getLocationStatus(this.tab.page_tab_id) if (status pass) { this.passed true; RegionGateState.regionGatePassed.trigger() } else { RegionGateState.regionGateFailed.trigger() } } getCurrentPosition() { return new Promise((resolve, reject) > { navigator.geolocation.getCurrentPosition((position) > { resolve(position); }, (error) > { resolve(null) }); }) } static async getLocationStatus(pageTabId, options {}) { const {body} await tbitsFetch(`/application/${pageTabId}/fan_location_check`, {params: options}) return body.status; } } class RegionGateFailedState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { const $header $(#header); $header.append(Section.create(change-language-section, {tab: this.tab})) $header.append(Section.create(welcome-header-section, {tab: this.tab})) $(#pre-content-container).css(display, flex) const $preContent $(#pre-content) $preContent.html(Section.create(region-gate-failed-section, {tab: this.tab})) const $preContentFooter $(#pre-content-footer) $preContentFooter.append(Section.create(share-links-section, {tab: this.tab})) $preContentFooter.append(Section.create(legal-section, {tab: this.tab})) } } class RegionGateFailedSection extends Section { template _.template($(#region-gate-failed-section-template).html()) connectedCallback() { this.innerHTML this.template({tab: this.tab}) } } customElements.define(region-gate-failed-section, RegionGateFailedSection) class RegionGateFormSection extends Section { static regionFormPassedEvent new CampaignEvent(region-form-passed) static regionFormFailedEvent new CampaignEvent(region-form-failed) template _.template($(#region-gate-form-section-template).html()) async connectedCallback() { let $template $(this.template({tab: this.tab})) let countryFieldObj { id: country, name: country, label: this.tab.locale_props.country_text, placeholder: this.tab.locale_props.select_country, isMandatory: true, options: await this.getCountries() } $template.find(.region-gate-fields).append(renderDropdown(countryFieldObj)) let provinceFieldObj { id: province, name: province, label: this.tab.locale_props.province_text, placeholder: this.tab.locale_props.select_province, isMandatory: true, options: } $template.find(.region-gate-fields).append(renderDropdown(provinceFieldObj)) this.innerHTML $template.html() const $country $(#country) $country.select2() await this.initProvinceDropdown($(#province), $country) $(#region-confirm).on(click, async () > await this.submitRegion()) } async submitRegion() { let country_code $(#country).val() let province $(#province).val() if (country_code.length 0 || province.length 0) { $(#country, #province).addClass(required); return } $(#country, #province).removeClass(required) createCookie(region_cookie, `${country_code},${province}`) let status await RegionGateState.getLocationStatus(this.tab.page_tab_id) if (status pass) { this.passed true RegionGateFormSection.regionFormPassedEvent.trigger() } else { RegionGateFormSection.regionFormFailedEvent.trigger() } } async getCountries() { const {body} await tbitsFetch(/public/ajax/countries, {params: {language: this.tab.language }}) return body.countries.map(c > ({value: c.country_code, label: c.country_name})) } async getProvinces(data) { const {body} await tbitsFetch(/public/ajax/provinces, { params: { language: data.language, country_code: data.country_code, query: data.query } }) return {results: body.provinces.map(p > ({id: p.province, text: p.alias_name}))} } async initProvinceDropdown($province, $country) { let language this.tab.language; let section this; $province.select2({ width: 100%, ajax: { delay: 250, transport: async function (params, success, failure) { let data { query: params.data.term, country_code: $country.val(), language: language } let provinces; try { provinces await section.getProvinces(data); } catch { return failure() } success(provinces); } }, }); } } customElements.define(region-gate-form-section, RegionGateFormSection)/script>style> .auth-header-description { padding: 20px; } .tbits-icon.connect-social-icon { font-size: 18px; margin: 0 5px; color: var(--style_prop_colour_primary_button_text); } .connect-btn.connect-google { background-color: #131314; } .connect-btn.connect-spotify { background-color: #1DB954; } .connect-btn.connect-euroleague { background-color: #fa5601; } .connect-btn.connect-amazon { background-color: #59626c; } .connect-btn.connect-afg { background-color: #04156C; }/style>script idconnect-form-section-template typetext/template> div classbase-btn-container stacked> % if (tab.auth_network email) { %> button classbase-btn primary connect-btn typebutton>%- tab.locale_props.connect %>/button> % } else { %> button classbase-btn primary connect-btn connect-%-tab.auth_network %> typebutton> %- tab.locale_props.connect_social %> %- tab.auth_network_name %> span classtbits-icon tbits-icon-%- tab.auth_network %>-icon connect-social-icon>/span> /button> % if (this.tab.show_connect_skip_button) { %> button classbase-btn secondary skip-btn typebutton>%- tab.locale_props.enter_with_email %>/button> % } %> % } %> /div>/script>script idauth-gate-header-template typetext/template> % if (tab.media_map.auth_gate_image?.1) { %> div classauth-gate-section-header-image header-image> img alt%- tab.props.auth_header_img_alt || Auth %> src%- tab.home_url %>/fb_media/%- tab.media_map.auth_gate_image1 %>/> /div> % } %> % if (tab.locale_props.auth_description || tab.props.raw_auth_gate_text) { %> div classauth-header-description campaign-content> % tab.locale_props.auth_description %> % tab.props.raw_auth_gate_text %> /div> % } %>/script>script idauth-login-state> class AuthLoginState extends CampaignState { static reloadStateEvent new CampaignEvent(reload-state-event); loggedIn false; constructor(tab) { super(tab); ConnectFormSection.fanLoginEvent.on(() > { this.loggedIn true; }); } async enter() { const fanSession await this.getFanSession(); if (fanSession) { // If a fan session already exists, skip auth regardless of auth network. ConnectFormSection.fanLoginEvent.trigger(); return; } this.render(); } render() { const $header $(#header); const $content $(#content); const $footer $(#footer); $header.append(Section.create(change-language-section, {tab: this.tab})) $header.append(Section.create(auth-login-header-section, {tab: this.tab})) $content.append(Section.create(clock-date-countdown-section, {tab: this.tab})) $content.append(Section.create(connect-form-section, {tab: this.tab})) $footer.append(Section.create(footer-section, {tab: this.tab})) } isLoggedIn() { return this.loggedIn } logOut() { this.loggedIn false; } async getFanSession() { try { const {body} await tbitsFetch(`/application/${this.tab.page_tab_id}/fan_session`) return body.fan_session; } catch (e) { return null } } } class ConnectFormSection extends Section { static fanLoginEvent new CampaignEvent(fan-login-event); template _.template($(#connect-form-section-template).html()) async connectedCallback() { this.innerHTML this.template({tab: this.tab}) $(this).find(.connect-btn).on(click, () > this.goToAuth()) $(this).find(.skip-btn).on(click, () > this.skipAuth()) } async goToAuth() { const campaignEndpoint ACCESS_TYPE native ? tb_native : tb_app; const params new URLSearchParams(window.location.search); const {body} await tbitsFetch(`/${campaignEndpoint}/${this.tab.page_tab_id}`, {params, method: POST}) if (body.redirect_url) { window.location.href body.redirect_url; } else { AuthLoginState.reloadStateEvent.trigger(); } } async skipAuth() { try { await tbitsFetch(`/tb_app/${this.tab.page_tab_id}?skiptrue`, {method: POST}) ConnectFormSection.fanLoginEvent.trigger() } catch (e) { showError(e) } } } customElements.define(connect-form-section, ConnectFormSection) class AuthLoginHeaderSection extends Section { template _.template($(#auth-gate-header-template).html()) connectedCallback() { this.innerHTML this.template({tab: this.tab}) this.className header-section } } customElements.define(auth-login-header-section, AuthLoginHeaderSection)/script>script idnot-started-section-template typetext/template> % if (tab.media_map.not_started_image?.1) { %> div classnot-started-section-header-image header-image> img alt%- tab.props.not_started_header_img_alt || Campaign Not Started %> src%- tab.home_url %>/fb_media/%- tab.media_map.not_started_image1 %>/> /div> % } %> % if (tab.locale_props.not_started_description || tab.props.raw_not_started_description) { %> div classnot-started-section-description campaign-content> % tab.locale_props.not_started_description %> % tab.props.raw_not_started_description %> /div> % } %>/script>script idnot-started-script> class NotStartedState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { const $header $(#header); $header.append(Section.create(change-language-section, {tab: this.tab})); $header.append(Section.create(not-started-section, {tab: this.tab})); $(#content).append(Section.create(clock-date-countdown-section, {tab: this.tab})); $(#footer).html(Section.create(footer-section, {tab: this.tab})); } } class NotStartedSection extends Section { template _.template($(#not-started-section-template).html()); connectedCallback() { this.innerHTML this.template({tab: this.tab}); this.className header-section } } customElements.define(not-started-section, NotStartedSection)/script>style> .contest-over-header-section-description { padding: 20px; }/style>script idcontest-over-header-section-template typetext/template> % if (tab.media_map.finished_image?.1) { %> div classcontest-over-header-section-header-image header-image> img alt%- tab.props.finished_header_img_alt || Campaign Over %> src%- tab.home_url %>/fb_media/%- tab.media_map.finished_image1 %>/> /div> % } %> % if (tab.locale_props.contest_over_description || tab.props.raw_finished_description) { %> div classcontest-over-header-section-description campaign-content> % tab.locale_props.contest_over_description %> % tab.props.raw_finished_description %> /div> % } %>/script>script idcontest-over-state-script> class ContestOverState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { const $header $(#header); $header.append(Section.create(change-language-section, {tab: this.tab})); $header.append(Section.create(contest-over-header-section, {tab: this.tab})); $(#footer).append(Section.create(footer-section, {tab: this.tab})); } }/script>script idcontest-over-header-section-script> class ContestOverHeaderSection extends Section { template _.template($(#contest-over-header-section-template).html()); connectedCallback() { this.innerHTML this.template({tab: this.tab}); this.className header-section } } customElements.define(contest-over-header-section, ContestOverHeaderSection)/script>style> .countdown-section { width: 100%; text-align: center; } .countdown-flex { max-width: 480px; display: flex; flex-direction: column; gap: 16px; justify-content: center; margin-left: auto; margin-right: auto; } .countdown-header { font-size: 22px; font-weight: 700; } .countdown-well { max-width: 480px; display: flex; padding: 16px 0; align-items: center; align-self: stretch; justify-content: space-evenly; border-radius: 8px; border: 1px solid var(--style_prop_colour_timer_well_border); background: var(--style_prop_colour_timer_well_background); color: var(--style_prop_colour_timer_well_text); } .countdown-well > div { flex: 1 0 0; } .countdown-well > div:not(:last-child) { border-right: 1px solid #D6D6D6; } .countdown-value { font-size: 18px; font-weight: 700; } .countdown-sub-title { font-size: 16px; font-weight: 400; }/style>script typetext/template idclock-date-countdown-section-template> div classcampaign-content countdown-section> div classcountdown-flex> div classcountdown-header> div classtime-till-start styledisplay: none> %- tab.locale_props.time_till_launch %> /div> div classtime-till-end styledisplay: none> %- tab.locale_props.time_remaining %> /div> /div> div classcountdown-well> div classcountdown-days> div classcountdown-value days>12/div> div classcountdown-sub-title> %- tab.locale_props.days_text %> /div> /div> div classcountdown-hours> div classcountdown-value hours>12/div> div classcountdown-sub-title> %- tab.locale_props.hours_text %> /div> /div> div classcountdown-minutes> div classcountdown-value minutes>12/div> div classcountdown-sub-title> %- tab.locale_props.minutes_text %> /div> /div> div classcountdown-seconds> div classcountdown-value seconds>12/div> div classcountdown-sub-title> %- tab.locale_props.seconds_text %> /div> /div> /div> /div> /div>/script>script idclock-date-countdown-script> class ClockDateCountdownSection extends Section { template _.template($(#clock-date-countdown-section-template).html()) static clockDateCountdownOver new CampaignEvent(clockDateCountdownOver) connectedCallback() { const currentTimeUnix Math.floor(Date.now() / 1000); let timeToStart true; if (currentTimeUnix this.tab.startTimestampUnix){ this.countdownTime this.tab.startTimestampUnix; } else if (currentTimeUnix this.tab.endTimestampUnix) { this.countdownTime this.tab.endTimestampUnix; timeToStart false; } else { return } if (this.tab.props.timer_enabled) { this.innerHTML this.template({tab: this.tab}); } if (timeToStart){ $(this).find(.time-till-start).show(); } else { $(this).find(.time-till-end).show() } this.$days $(this).find(.countdown-value.days); this.$hours $(this).find(.countdown-value.hours) this.$minutes $(this).find(.countdown-value.minutes) this.$seconds $(this).find(.countdown-value.seconds) this.timerIterator() this.interval window.setInterval(() > this.timerIterator(), 1000) } disconnectedCallback(){ clearInterval(this.interval) } timerIterator() { let now Date.now() / 1000; let diff this.countdownTime - now; if (diff 0) { clearInterval(this.interval) this.innerHTML ClockDateCountdownSection.clockDateCountdownOver.trigger() } let seconds Math.floor(diff % 60) || 0; let minutes Math.floor(diff / 60 % 60) || 0; let hours Math.floor(diff / 3600 % 24) || 0; let days Math.floor(diff / 86400) || 0; if (days 0){ $(.countdown-days).hide() } this.$days.text(days) this.$hours.text(hours) this.$minutes.text(minutes) this.$seconds.text(seconds) } } customElements.define(clock-date-countdown-section, ClockDateCountdownSection)/script>script idstart-again-template typetext/template> div idstart-again classstart-again-section campaign-content> div classbase-btn-container> button typebutton classbase-btn primary start-again> %- tab.locale_props.start_again %> /button> /div> /div>/script>script idstart-again-script> class StartAgainSection extends Section { static startAgainLogoutEvent new CampaignEvent(start-again-logout) template _.template($(#start-again-template).html()); connectedCallback() { if (this.tab.props.kiosk_mode_enabled) { this.innerHTML this.template({tab: this.tab}) $(this).find(.start-again).on(click, async () > await this.startAgain()) } } async startAgain() { try { await tbitsFetch(`/application/fan_logout?page_tab_id${this.tab.page_tab_id}`) deleteCookie(`age_checked_${this.tab.page_tab_id}`) StartAgainSection.startAgainLogoutEvent.trigger() } catch (error) { showError(error) } } } customElements.define(start-again-section, StartAgainSection)/script>script idemail-logout-section-template typetext/template> div classemail-logout-section campaign-content> div idstart-again classbase-btn-container> button typebutton classbase-btn secondary email-logout> %- tab.locale_props.email_logout %> /button> /div> /div>/script>script idemail-logout-section-script> class EmailLogoutSection extends Section { template _.template($(#email-logout-section-template).html()) static fanLogoutEvent new CampaignEvent(fan-logout) async connectedCallback() { if (await this.getFanSession()) { this.innerHTML this.template({tab: this.tab}) $(this).find(.email-logout).on(click, () > this.emailLogout()) } } async getFanSession() { try { const {body} await tbitsFetch(`/application/${this.tab.page_tab_id}/fan_session`) return body.fan_session } catch (e) { return null } } async emailLogout() { try { await tbitsFetch(`/application/fan_logout?page_tab_id${this.tab.page_tab_id}`) EmailLogoutSection.fanLogoutEvent.trigger() } catch (e) { showError(e) } } } customElements.define(email-logout-section, EmailLogoutSection)/script>style> :root { --grey-200: #EBEBEB; --swiper-pagination-color: var(--style_prop_colour_primary_button) } .legal-links .legal-toggle { background: none; border: none; padding: 0; font-weight: 700; } .legal-links .legal-toggle:not(:first-child) { margin-left: 4px; } #entry-form-fields { display: flex; flex-direction: column; gap: 10px; } #select2-phone_code-container { padding-right: 8px; text-overflow: unset; } .select2-container--default .select2-dropdown, .select2-container--default .select2-search--dropdown .select2-search__field { background-color: var(--style_prop_colour_entry_field); color: var(--style_prop_colour_entry_field_text) } .entry-form-section-phone-inputs { display: flex; flex-direction: row; gap: 10px; flex-wrap: wrap; } .swiper { height: auto; min-height: 100px; width: 100%; } .swiper-slide-child { height: 100%; width: 100%; display: flex; justify-content: center; align-items: center; padding: 2px; } .swiper .swiper-slide.entry-form-field { display: flex; justify-content: center; align-items: center; } .swiper-area { position: relative; } .swiper-pagination { position: relative; }/style>script idform-legal-agree-terms-template typetext/template> div classlegal-agree-container entry-form-section-field> label forlegal-agree> input typecheckbox idlegal-agree namelegal_agree required> span> % tab.locale_props.legal_text_agree %> span classlegal-links>/span> span classred>*/span> /span> /label> /div>/script>script idform-legal-agree-view-template typetext/template> div classlegal-agree-container> span classlegal-links>/span> /div>/script>script idform-legal-policy-copy-template typetext/template> button typebutton classlegal-toggle %- type %>>%- prop %>/button>/script>script idform-legal-url-template typetext/template> a href%- url %> target_blank classlegal-toggle %- type %>>%- prop %>/a>/script>script identry-form-validate-template typetext/template> div classentry-form-section-field field-%- id %>> label for%- field.crm_field_key %>> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> input typetext name%- name %> id%- field.crm_field_key %> class data-field-typevalidation data-crm_field_key%- field.crm_field_key %> data-page_tab_id%- field.page_tab_id %> data-business-id%- field.business_id %> %- isMandatory ? required : %> > /div>/script>script identry-form-section-template typetext/template> form identry-form> div classentry-form-section campaign-content> % if (!tab.is_pii_disabled && tab.locale_props.form_title) { %> div classconfirm-title> span>%- tab.locale_props.form_title %>/span> % if (tab.locale_props.required) { %> small> - %- tab.locale_props.required %>/small> % } %> /div> % } %> div identry-form-fields>/div> div classbase-btn-container> button typebutton idsubmit-btn classbase-btn primary> %- tab.locale_props.submit %> /button> /div> /div> /form>/script>script identry-form-swiper-template typetext/template> form identry-form classcampaign-content> % if (!tab.is_pii_disabled && tab.locale_props.form_title) { %> div classconfirm-title> span>%- tab.locale_props.form_title %>/span> % if (tab.locale_props.required) { %> small> - %- tab.locale_props.required %>/small> % } %> /div> % } %> div classswiper-area> div classswiper mySwiper> div classswiper-wrapper>/div> /div> div classswiper-pagination>/div> /div> div classbase-btn-container> button typebutton idswiper-back-btn classbase-btn secondary> %- tab.locale_props.back %> /button> button typebutton idswiper-next-btn classbase-btn primary> %- tab.locale_props.next %> /button> button typebutton idswiper-submit-btn classbase-btn primary> %- tab.locale_props.submit %> /button> /div> /form>/script>script idverify-pii-modal-template typetext/template> div classmodal-dialog stylemargin-top: 100px; max-width: 400px; min-width: 332px;> div classmodal-content stylemin-height: 420px;> div classmodal-header styleborder-bottom: none;> modal-close-button>/modal-close-button> /div> form classmodal-body styledisplay:flex;flex-direction:column;align-items:center;padding: 20px 20px;> div classfont-bold stylefont-size: 18px;>%- tab.locale_props.campaign_verification_code %>/div> div stylemargin-bottom: 40px;> %- tab.locale_props.campaign_verification_code_sent_to %> b>%- verification_method email ? email: phone %>/b> /div> div stylemargin-bottom: 40px;> input nameverification-code classverification-code required> input nameverification-code classverification-code required> input nameverification-code classverification-code required> input nameverification-code classverification-code required> input nameverification-code classverification-code required> input nameverification-code classverification-code required> /div> div classalert alert-danger idverify-pii-error-container styledisplay: none;>/div> input typehidden namerequest-uid value%- request_uid %>> div idverify-pii-original-state stylemargin-bottom: 20px;> div>%- tab.locale_props.campaign_verification_code_expiry %>/div> div>%- tab.locale_props.campaign_verification_code_not_received %> a href# idverify-pii-resend-btn styletext-decoration: none;> %- tab.locale_props.campaign_verification_code_resend %> /a> /div> /div> div idverify-pii-resend-state styledisplay: none;margin-bottom: 20px;> div>%- tab.locale_props.campaign_verification_code_resent %>/div> div>%- tab.locale_props.campaign_verification_send_code_again_countdown %> span idverify-pii-resend-value>/span> /div> /div> button typesubmit idverify-pii-btn classbase-btn primary stylemargin-bottom: 20px; width: fit-content; padding-top: 10px; padding-bottom: 10px;> %- tab.locale_props.campaign_verification_verify_button %> /button> /form> /div> /div>/script>script srchttps://tradablebits.com/static/swiper/swiper-bundle.min.js>/script>link hrefhttps://tradablebits.com/static/swiper/swiper-bundle.min.css relstylesheet>script> class EntryFormSection extends Section { static submitEntryFormEvent new CampaignEvent(submit-entry-form) static triggerSubmitEntryForm new CampaignEvent(trigger-submit-entry-from) template _.template($(#entry-form-section-template).html()); swiperTemplate _.template($(#entry-form-swiper-template).html()) legalAgreeTermsTemplate _.template($(#form-legal-agree-terms-template).html()); legalAgreeViewTemplate _.template($(#form-legal-agree-view-template).html()); validateFieldTemplate _.template($(#entry-form-validate-template).html()); async connectedCallback() { this.activityStartTime Date.now(); this.fanValues await this.getFanValues() if (this.tab.is_pii_disabled) { if ($(#entry-form-fields).length 0) { this.innerHTML this.template({tab: this.tab}) } } else { let fieldArray this.renderCrmFields() let legalAndOptinFields await this.renderLegalAndOptins() fieldArray.push(...legalAndOptinFields) if (this.tab.props?.scroll_type) { this.innerHTML this.swiperTemplate({tab: this.tab}) this.setupFormSwiper(this.tab.props.scroll_type, fieldArray) } else { let $fields; if ($(#entry-form-fields).length ! 0) { $fields $(#entry-form-fields) } else { this.innerHTML this.template({tab: this.tab}) $fields $(this).find(#entry-form-fields) } fieldArray.forEach((i) > { $fields.append(i) }) } let $entryForm $(#entry-form) await this.setFanValues($entryForm) if (this.tab.props?.scroll_type) { // Make select2s within swiper clickable $(.select2).addClass(swiper-no-swiping) } } $(select:not(.select2-hidden-accessible)).select2({ width: 100%, }) let formSignature document.createElement(input); Object.assign(formSignature, { id: my_form_signature, name: my_form_signature, type: hidden, value: this.fanValues.my_form_signature }) $(#entry-form).append(formSignature) $(this).find(#submit-btn, #swiper-submit-btn).on(click, () > this.submitForm()) EntryFormSection.triggerSubmitEntryForm.on(() > { this.submitForm() }) triggerTbitsEvent(tbits.formLoad) } async submitForm() { showThrobber(); let campaignData this.campaignData; if (campaignData && this.activityStartTime) { campaignDataactivity_duration_ms Date.now() - this.activityStartTime; } try { let res await this.submitEntryForm(campaignData) if (res) { EntryFormSection.submitEntryFormEvent.trigger(res) } } catch (e) { showError(e) } hideThrobber() } setupFormSwiper(scrollType, fieldArray) { fieldArray.forEach((i) > { let slide document.createElement(div) slide.classList.add(swiper-slide, entry-form-field) let slideChild document.createElement(div) slideChild.className swiper-slide-child $(slideChild).html(i) slide.appendChild(slideChild) $(.swiper-wrapper).append(slide) }) $(.swiper).css(height, $(this).height()) let dynamic false; let bulletCount 3; if (scrollType vertical) { if (fieldArray.length > 5) { dynamic true; } $(.swiper-slide).css(padding-right, 24px) } else if (scrollType horizontal) { if (fieldArray.length > 9) { dynamic true bulletCount 7; } $(.swiper-slide).css(padding-bottom, 24px) } let swiper new Swiper(.mySwiper, { pagination: { el: .swiper-pagination, clickable: true, dynamicBullets: dynamic, dynamicMainBullets: bulletCount, }, direction: scrollType, keyboard: { enabled: true }, }); function showHideButtons() { $(#swiper-next-btn, #swiper-back-btn).css(visibility, visible).show(); $(#swiper-submit-btn).hide() if (swiper.isBeginning) { $(#swiper-back-btn).css(visibility, hidden) } else if (swiper.isEnd) { $(#swiper-next-btn).hide() $(#swiper-submit-btn).show() } } $(#swiper-next-btn).on(click, () > { swiper.slideNext(); showHideButtons() }); $(#swiper-back-btn).on(click, () > { swiper.slidePrev(); showHideButtons() }); swiper.on(slideChange, () > { showHideButtons() }) showHideButtons() } renderCrmFields() { let fieldArray ; for (const field of this.tab.page_tab_fields) { let $field; switch (this.getFieldType(field)) { case text: $field this.renderCrmFieldInput(field) break; case email: $field this.renderCrmFieldEmail(field); break; case checkbox: $field this.renderCrmFieldCheckbox(field); break; case radio: $field this.renderCrmFieldRadio(field); break; case textarea: $field this.renderCrmFieldTextArea(field); break; case date: $field this.renderCrmFieldDate(field); break case phone: $field this.renderCrmFieldPhone(field); break; case select: $field this.renderCrmFieldDropdown(field); break; case birthday: $field this.renderCrmFieldBirthday(field); break; case gender: $field this.renderCrmFieldGender(field); break; case validation: $field this.renderValidate(field); break; } fieldArray.push($field) } return fieldArray; } async renderLegalAndOptins() { let fieldArray let $legalAgree await this.renderLegalAgree(this.tab.legal) if ($legalAgree) { fieldArray.push($legalAgree) } let $businessOptin this.renderBusinessOptin() if ($businessOptin) { fieldArray.push($businessOptin) } return fieldArray } async setFanValues($fields) { for (const field in this.fanValues.fields) { if (this.fanValues.fieldsfield) { const $el $fields.find(`#${field}`); $el.val(this.fanValues.fieldsfield).trigger(change); if (this.fanValues.readonly && !this.fanValues.allow_update.includes(field)) { $el.attr(readonly, true); // checkboxes should be disabled instead of readonly $el.filter(:checkbox).prop(disabled, true); } } } await populateDropdowns(this.fanValues.fields.phone_country_code, $fields, this.tab.language) await triggerTbitsEvent(tbits.populateFieldValues) } getFieldLabel(field) { let key; if (field.is_core) { if (field.crm_field_key network_connection) { return `${this.tab.props.network_connection} ${this.tab.locale_propsnetwork_connection_title}` } key { //get locale_prop used for each field first_name: first_name_text, last_name: last_name_text, display_name: display_name_text, email: email_text, phone: phone_text, is_subscribed: subscribe_email, is_phone_subscribed: subscribe_phone, city: city_text, postal_code: postal_code_text, province: province_text, select_province: select_province, country_code: country_text, birth_year: birth_year_text, birth_date: birth_date_text, gender: gender_text, spotify_uids: spotify_uids_text }field.crm_field_key; } else { key field.crm_field_key; } const val this.tab.locale_propskey if (!val || key val) { return toTitleCase(key) } return val } getFieldType(field) { if (field.is_core) { return { email: email, phone: phone, first_name: text, last_name: text, display_name: text, is_subscribed: checkbox, is_phone_subscribed: checkbox, network_connection: text, city: text, postal_code: text, province: select, country_code: select, birth_year: birthday, birth_date: birthday, gender: gender, spotify_uids: select }field.crm_field_key; } else { return field.field_type; } } getName(field) { let name; if (field.is_core) { name field.crm_field_key; } else { name `field_${field.crm_field_key}` } return name } formatOptions(field) { let options for (let option of field.field_options || ) { let label this.tab.locale_propsoption || option; options.push({value: option, label: label}) } return options } renderCrmFieldInput(field) { let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory, options: this.formatOptions(field), } return renderInput(fieldObj); } renderCrmFieldEmail(field) { let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory, inputType: email } let $field renderInput(fieldObj); if (field.is_core && field.crm_field_key email && this.tab.props.show_email_confirm) { let fieldObj2 { label: this.tab.locale_propsemail_confirm_text, name: email_confirm, isMandatory: field.is_mandatory, inputType: email } let $confirmEmail renderInput(fieldObj2) $field.append($confirmEmail) } return $field } renderCrmFieldCheckbox(field) { let checked false; if (field.is_core) { if (field.crm_field_key is_subscribed && this.tab.props.is_subscribed_default) { checked true; } else if (field.crm_field_key is_phone_subscribed && this.tab.props.is_phone_subscribed_default) { checked true; } } let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory, checked, linkUrl: field?.crm_field?.link_url }; return renderCheckbox(fieldObj) } renderCrmFieldRadio(field) { let fieldObj { id: field.crm_field_key, options: this.formatOptions(field), label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory } return renderRadio(fieldObj) } renderCrmFieldTextArea(field) { let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory } return renderTextArea(fieldObj) } renderCrmFieldDropdown(field) { const placeholder { province: select_province, country_code: select_country, spotify_uids: select_spotify_uids }field.crm_field_key let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), options: this.formatOptions(field), isMandatory: field.is_mandatory, placeholder: this.tab.locale_propsplaceholder } return renderDropdown(fieldObj) } renderCrmFieldDate(field) { let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory, inputType: date } return renderInput(fieldObj) } renderCrmFieldPhone(field) { let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory, countryCodeLabel: this.tab.locale_props.phone_country_code, } return renderPhone(fieldObj) } renderCrmFieldBirthday(field) { let fieldObj { id: field.crm_field_key, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory, date_format: this.tab.props.date_format, } const dateFormat this.tab.props.date_format; const yearOnly (field.crm_field_key birth_year); return renderBirthday(fieldObj, dateFormat, yearOnly) } renderCrmFieldGender(field) { let genderOptions this.tab.gender_options.map(option > ({ value: option, label: this.tab.locale_props`gender_${option}` })); let fieldObj { id: gender, label: this.getFieldLabel(field), name: this.getName(field), placeholder: this.tab.locale_propsselect_gender, options: genderOptions, isMandatory: field.is_mandatory } return renderDropdown(fieldObj) } renderValidate(field) { return $(this.validateFieldTemplate({ id: field.crm_field_key, field, label: this.getFieldLabel(field), name: this.getName(field), isMandatory: field.is_mandatory })); } renderBusinessOptin() { if (this.tab.props.show_business_optin) { let fieldObj { id: is_business_subscribed, label: this.tab.locale_props.business_optin, name: is_business_subscribed, isMandatory: false, checked: this.tab.props.default_check_business_optin }; return renderCheckbox(fieldObj) } } async renderLegalAgree(legal) { if (!Object.values(this.tab.legal).map(type > Object.values(type)).flat().some(value > value)) { return; } const copyTemplate _.template($(#form-legal-policy-copy-template).html()) const policyTemplate _.template($(#form-legal-url-template).html()) let links for (let type in legal) { let terms legaltype; let prop this.tab.locale_props`legal_text_${type}`; if (terms.policy_copy) { let $copySection $(copyTemplate({type, prop})) $copySection.on(click, () > this.toggleLegalSection(.legal-display, type)) links.push($copySection) } else if (terms.policy_url) { links.push(policyTemplate({type, prop, url: terms.policy_url})) } } let $legalSection if (this.fanValues.terms_required ! false) { $legalSection $(this.legalAgreeTermsTemplate({tab: this.tab})) let $legalArea $legalSection.find(.legal-links) for (let i, link of links.entries()) { if (links.length 2 && i 1) { $legalArea.append(this.tab.locale_props.legal_text_agree_and) } else if (links.length > 2) { if (i links.length - 1) { $legalArea.append(this.tab.locale_props.legal_text_agree_comma_and) } else if (i ! 0) { $legalArea.append(, ) } } $legalArea.append(link) } } else { $legalSection $(this.legalAgreeViewTemplate({tab: this.tab})) $legalSection.find(.legal-links).append(links) } return $legalSection } toggleLegalSection(container, section) { let $container $(container); let $activeContainer $container.find(.legal-container. + section).toggle(); let $activeToggle $container.find(.legal-toggle. + section).toggleClass(active); $(html, body).animate({ scrollTop: $(.legal-container. + section).offset().top }, 200); $container.find(.legal-container).not($activeContainer).hide(); $container.find(.legal-toggle).not($activeToggle).removeClass(active); } async getFanValues() { try { const {body} await tbitsFetch( urlWithCookie(/application/fan_values), { method: POST, form: {page_tab_id: this.tab.page_tab_id} }) return body } catch (e) { return null } } /** * Validates and submits entry form along with campaign data. Throws error if invalid form or response status is bad. * @param campaignData {Object} campaign-specific data to add to request body * validateEntryForm function called as part of this function * @returns result {Object} */ async submitEntryForm(campaignData {}) { triggerTbitsEvent(tbits.submit); const entryFormData new FormData(document.querySelector(#entry-form)); entryFormData.set(page_tab_id, this.tab.page_tab_id); try { await this.validateEntryForm(#entry-form, this.tab.locale_props); } catch (e) { throw new Error(e) } if (!this.checkCampaignActive()) { throw new Error(this.tab.locale_props.entry_form_error) } let verificationRequest null; try { verificationRequest await this.maybeCreateVerificationRequest(entryFormData); } catch (e) { throw new Error(e.body) } if (verificationRequest ! null) { const requestUid await this.openVerifyPiiModal(verificationRequest, entryFormData); if (requestUid) { entryFormData.set(request_uid, requestUid); } else { // verification window was closed, all other errors are shown in the verification modal return false } } const url urlWithCookie(/application/submit_entry_form); Object.entries(campaignData).forEach((key, value) > { entryFormData.set(key, JSON.stringify(value)); }); try { const {body} await tbitsFetch(url, {method: POST, form: entryFormData}) this.tab.reached_limit body?.reached_limit; if (body.activity_id) { this.conversionTrack(body.activity_id); } triggerTbitsEvent(tbits.submitSuccess, {fan_id: body?.fan_id, activity_id: body?.activity_id}); this.postPoints() return body; } catch (error) { throw error; } finally { hideThrobber(); } } checkCampaignActive() { const currentTimeUnix Math.floor(Date.now() / 1000); return this.tab.startTimestampUnix currentTimeUnix && currentTimeUnix this.tab.endTimestampUnix; } postPoints() { if (this.tab.point_uid) { if (window.self ! window.top) { window.parent.postMessage({ id: this.tab.page_tab_id, type: tb_crm_fan_points, data: { score: this.tab.point_count || 1, } }, *) } } } conversionTrack(activityId) { ThirdPartyTracking.sendRegistration({content_name: this.tab.name}); if (window.trackEvent) { trackEvent(page_tab, null, null, this.tab.page_tab_id, activityId, null); } } openVerifyPiiModal(data, entryFormData) { hideThrobber(); const verifyPiiModalTemplate _.template($(#verify-pii-modal-template).html()); let $modal $(#modal-dialog); $modal.data(original-html, $modal.html()); let templateData {...data, tab: this.tab} $modal.html(verifyPiiModalTemplate(templateData)); $modal.modal({show: true, background: static}); const $errorContainer $modal.find(#verify-pii-error-container); const $resendBtn $modal.find(#verify-pii-resend-btn); const $originalState $modal.find(#verify-pii-original-state); const $resendState $modal.find(#verify-pii-resend-state); const $resendValue $modal.find(#verify-pii-resend-value); $resendBtn.on(click, async (e) > { // Creates a new verification request when resend is clicked, and updates the form state try { const {body} await tbitsFetch(/application/verification_requests, { method: POST, form: entryFormData }) $modal.find(input).val(); $modal.find(inputnamerequest-uid).val(body.request_uid); $originalState.hide(); $resendState.show(); let countDown 60; $resendValue.text(countDown); let interval setInterval(() > { countDown--; $resendValue.text(countDown); if (countDown 0) { clearInterval(interval); $originalState.show(); $resendState.hide(); } }, 1000); } catch (e) { showError(e) } }) // The following block is slightly confusing logic for handling the fancy verification code inputs. let inputs ; $modal.find(inputnameverification-code).each(function () { inputs.push($(this)); }); inputs.forEach((input, i) > { // This is to prevent the user from clicking any input, // it should automatically focus the first non-filled input instead, // Or the last input if all are filled. input.on(mousedown, (e) > { e.preventDefault(); let j 0; while (j inputs.length && inputsj++.val() ! ) ; inputsj - 1.trigger(focus); }) // Pressing backspace on an empty input will clear and focus previous input. input.on(keydown, (e) > { const BACKSPACE 8; if (e.which BACKSPACE && e.target.value ) { let prevInputIdx Math.max(0, i - 1); inputsprevInputIdx.val(); inputsprevInputIdx.trigger(focus); } }) // When we enter a character, move to the next input. input.on(input, (e) > { $errorContainer.hide(); $modal.find(inputnameverification-code).removeClass(verification-code-error-state); if (e.target.value && e.target.value.length 1) { const nextInputIdx Math.min(i + 1, inputs.length - 1) inputsnextInputIdx.trigger(focus); } // Prevent multiple characters being written to one input. if (e.target.value && e.target.value.length > 1) e.target.value e.target.value0; }) // If the user pastes, then write the pasted data to the inputs in order and focus the last element. input.on(paste, (e) > { e.preventDefault(); const pastedData e.originalEvent.clipboardData.getData(text); if (pastedData) { let j 0; let y i; while (j pastedData.length && y inputs.length) { inputsy++.val(pastedDataj++); } const idxToFocus Math.min(inputs.length - 1, y) inputsidxToFocus.trigger(focus) } }) }) return new Promise((resolve) > { let requestUid; $modal.find(form).on(submit, async (e) > { e.preventDefault(); if (validateFormNatively($modal.find(form))) { let verificationCode ; $modal.find(inputnameverification-code).each(function () { verificationCode + $(this).val(); }) const formRequestUid $modal.find(inputnamerequest-uid).val(); const data { verification_code: verificationCode, }; showThrobber(); try { const {body} await tbitsFetch(`/application/verification_requests/${formRequestUid}`, { method: POST, form: data }) hideThrobber(); if (body.success) { requestUid formRequestUid; $modal.modal(hide) } else { $modal.find(inputnameverification-code).addClass(verification-code-error-state); $errorContainer.text(body.error_message); $errorContainer.show(); } } catch (e) { showError(e) } } }); $modal.on(hidden.bs.modal, () > { resolve(requestUid); }) }) } async maybeCreateVerificationRequest(payload) { const {body} await tbitsFetch(/application/verification_requests, {method: POST, form: payload}) return body } async validateEntryForm(id #entry-form, locale_props {}) { const $form $(id); let allValid true; let emptyForm false; let invalidNameFormat false; let dateValid true; let invalidEmailFormat false; let invalidConfirmEmail false; const validationFieldPromises ; const $validationFields $(inputdata-field-typevalidation); $validationFields.each(function () { const value this.value.trim(); if (value ) { $(this).data(valid, !this.required); if (this.required) emptyForm true; return; } validationFieldPromises.push(new Promise(async (resolve, reject) > { showThrobber(); const {page_tab_id, business_id, crm_field_key} $(this).data(); const data {page_tab_id, business_id, crm_field_key, value}; try { const {body} await tbitsFetch(/application/check_validation_field, { method: POST, form: data, }) let valid body.valid $(`#field_error_${crm_field_key}`).toggle(!valid); if (!valid) { allValid false; } $(this).data({valid}); resolve(); } catch (e) { showError(e) reject() } })); }); // Wait until all checks are done before highlighting fields await Promise.all(validationFieldPromises); const containsInvalid /\d@>$&\\/ const containsEmptyField /^\s*$/ $validationFields.each(function () { if ($(this).data(valid)) { $(this).removeClass(required).addClass(required-filled); } else { $(this).removeClass(required-filled).addClass(required); $(this).one(focusin, function () { $(this).removeClass(required); }); } }); $form.find(inputtypetextdata-field-type!validation, inputtypedate, inputtypenumber, textarea, select).each(function () { if (this.required && !this.value.trim().length) { emptyForm true; $(this).removeClass(required-filled).addClass(required); $(this).one(focusin, function () { $(this).removeClass(required) }); $(this).on(change, function () { $(this).removeClass(required) }) } else if ((this.name first_name || this.name last_name)) { if (containsInvalid.test(this.value)) { $(this).removeClass(required-filled).addClass(required); $(this).one(focusin, function () { $(this).removeClass(required) }); invalidNameFormat true; } if (!invalidNameFormat) { $(this).removeClass(required).addClass(required-filled); } } else { $(this).removeClass(required).addClass(required-filled); } }); const $email $form.find(inputtypeemail); $email.each(function () { const value this.value.trim().toLowerCase(); if (this.required && (!value || !regExesemail.test(value)) || !this.checkValidity()) { if (containsEmptyField.test(value)) { emptyForm true; } else { invalidEmailFormat true; } $(this).addClass(required).removeClass(required-filled); $(this).one(focusin, function () { $(this).removeClass(required) }); } else { $(this).removeClass(required).addClass(required-filled); } }); $form.find(input#email_confirm).each(function () { if (this.value ! $(#email).val()) { invalidConfirmEmail true; $(this).addClass(required).removeClass(required-filled); $(this).one(focusin, function () { $(this).removeClass(required); }); } else { $(this).removeClass(required).addClass(required-filled); } }); $form.find(inputtypecheckbox).each(function () { if (this.required && !this.checked) { allValid false; $(this).addClass(required); $(this).one(change, function () { $(this).removeClass(required); }); } else { $(this).removeClass(required); } }); $form.find(inputtyperadio).each(function () { const $multipleGroup $(this).closest(.multiple-group); if (this.required && $multipleGroup.find(inputtyperadio:checked).length 0) { allValid false; $(this).addClass(required); $(this).one(change, function () { $multipleGroup.find(inputtyperadio).removeClass(required); }); } else { $(this).removeClass(required).addClass(required-filled); } }); $form.find(input#phone).each(function () { const value this.value.trim(); if (this.required && !regExesphone.test(value)) { allValid false; $(this).removeClass(required-filled).addClass(required); $(this).one(focusin, function () { $(this).removeClass(required); }); } else if (this.required && !value.length) { emptyForm true; $(this).removeClass(required-filled).addClass(required); $(this).one(focusin, function () { $(this).removeClass(required); }); } else { $(this).removeClass(required).addClass(required-filled); } }); $form.find(#age_year, #age_month, #age_day).each(function () { let valid $(this).is(:valid); if ($(this).attr(id) age_year) { let n new Date() let y n.getFullYear() if ($(this).val() > y) { valid false; } } if (!valid) { dateValid false; $(this).removeClass(required-filled).addClass(required); $(this).one(focusin, function () { $(this).removeClass(required); }); } else { $(this).removeClass(required).addClass(required-filled); } }); const $mediaUploads $form.find(media-uploader); $mediaUploads.each(function () { if (!this.checkValidity()) { allValid false; $(this).addClass(required).removeClass(required-filled); $(this).one(focusin, function () { $(this).removeClass(required); }); } else { $(this).removeClass(required).addClass(required-filled); } }); if (emptyForm) { hideThrobber(); return Promise.reject(locale_propsentry_form_error_incomplete_form_fields ?? Incomplete form fields); } else if (invalidNameFormat) { hideThrobber(); return Promise.reject(locale_propsentry_form_error_invalid_name_format ?? Invalid name format); } else if (!dateValid) { hideThrobber(); return Promise.reject(locale_propsentry_form_error_invalid_date ?? Invalid date); } else if (invalidEmailFormat) { hideThrobber(); return Promise.reject(locale_propsentry_form_error_invalid_email_format ?? Invalid email format); } else if (invalidConfirmEmail) { hideThrobber(); return Promise.reject(locale_propsentry_form_error_email_confirmation_not_match ?? Email confirmation does not match email); } else if (!allValid) { hideThrobber(); return Promise.reject(locale_propsentry_form_error_invalid_input ?? Invalid input); } return Promise.resolve(true); } } customElements.define(entry-form-section, EntryFormSection)/script>style> .entry-form-section-field, .birth-date-field { flex: 1 0 0; display: flex; flex-direction: column; justify-content: flex-end; } .entry-form-section-field label { font-weight: bold; } .entry-form-section-field input, .entry-form-section-field select, .entry-form-section-field textarea { border: 1px solid rgb(204, 204, 204); border-radius: 4px; background-color: var(--style_prop_colour_entry_field); color: var(--style_prop_colour_entry_field_text); padding: 10px } .entry-form-section-field inputreadonly, .entry-form-section-field selectreadonly, .entry-form-section-field textareareadonly { background-color: var(--grey-200); color: rgb(85, 85, 85); cursor: text; } .entry-form-section-field input, .entry-form-section-field select { height: 48px; } .entry-form-section-field label:has(inputtypecheckbox, inputtyperadio) { display: flex; align-items: center; gap: 4px; } .entry-form-section-field :is(inputtypecheckbox, inputtyperadio) { width: 18px; height: 18px; background-color: #ffffff; background-position: center; background-size: contain; background-repeat: no-repeat; -moz-appearance: none; appearance: none; color-adjust: exact; position: relative; cursor: pointer; padding: 0; flex-shrink: 0; flex-grow: 0; } .entry-form-section-field inputtypecheckbox:checked { background-image: url(data:image/svg+xml,%3csvg xmlnshttp://www.w3.org/2000/svg viewBox1 1 18 18%3e%3cpath fillnone stroke%23fff stroke-linecapround stroke-linejoinround stroke-width3 dM6 10l3 3l6-6/%3e%3c/svg%3e); } .entry-form-section-field inputtypecheckbox:indeterminate { background-image: url(data:image/svg+xml,%3csvg xmlnshttp://www.w3.org/2000/svg viewBox0 0 20 20%3e%3cpath fillnone stroke%23fff stroke-linecapround stroke-linejoinround stroke-width3 dM6 10h8/%3e%3c/svg%3e); } .entry-form-section-field :is(inputtypecheckbox, inputtyperadio):disabled, .entry-form-section-field :is(inputtypecheckbox, inputtyperadio)readonly { background-color: rgb(214, 214, 214); border-color: rgb(224, 224, 224); } .entry-form-section-field :is(inputtypecheckbox, inputtyperadio):checked, .entry-form-section-field inputtypecheckbox:indeterminate { border-color: var(--style_prop_colour_selected_button); background-color: var(--style_prop_colour_selected_button); } .entry-form-section-field :is(inputtypecheckbox, inputtyperadio):checkedreadonly { filter: saturate(0.3); } .entry-form-section-field .multiple-group inputtyperadio { margin-top: 2px; margin-bottom: 2px; } .entry-form-section-field inputtyperadio { border-radius: 50%; } .entry-form-section-field inputtyperadio:checked { background-image: url(data:image/svg+xml,%3csvg xmlnshttp://www.w3.org/2000/svg viewBox-4 -4 8 8%3e%3ccircle r2 fill%23fff/%3e%3c/svg%3e); } .entry-form-section-field .select2-container--default .select2-selection--single { border-radius: 4px; border: 1px solid rgb(204, 204, 204); height: 47px; padding: 9px 12px; background: var(--style_prop_colour_entry_field) url(/static/icons/arrow-dropdown.png) no-repeat 96.5%; } .entry-form-section-field .select2-container--default .select2-selection--single .select2-selection__arrow { display: none; } .entry-form-section-field.birth-date-row { display: flex; gap: 10px; flex-wrap: wrap; flex-direction: row; } .entry-form-section-field.birth-date-row > div { flex: 1 0 0; } .entry-form-section-field.birth-date-row .birth-date-field:not(:first-of-type) label { display: none; } .red { color: rgb(227, 111, 112); font-weight: 600; } .entry-form-section-field input.required, .entry-form-section-field textarea.required, media-uploader.required .media-uploader, .required { background-color: #FFECEB; border: 2px solid #B94A48; } .entry-form-section-field input.required-filled, .entry-form-section-field textarea.required-filled, media-uploader.required-filled .media-uploader, .required-filled { border: 1px solid #00ba95; } .entry-form-section-field .required + .select2-container--default .select2-selection--single { background-color: #FFECEB; border: 2px solid #B94A48; } .entry-form-section-field .required-filled + .select2-container--default .select2-selection--single { border: 1px solid #00ba95; } .phone_number_input { flex-grow: 1; } .verification-code { display: inline-block; width: 40px; height: 40px; text-align: center; font-size: 18px; } .verification-code-error-state { border: red 1px solid; } .entry-form-section-field readonly + .select2-container .select2-selection { pointer-events: none; background-color: var(--grey-200); cursor: default; }/style>script identry-form-input-template typetext/template> div classentry-form-section-field field-%- id %>> % if (inputType ! hidden) { %> label for%- id %>> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> % } %> input type%- inputType %> name%- name %> id%- id %> value%- value %> % if (options.length) { %> listtext_options-%- id %> % } %> %- isMandatory ? required : %>> /div> % if (options.length) { %> datalist idtext_options-%- id %>> % for (let option of options) { %> option value%- option.value %>>%- option.label %>/option> % } %> /datalist> % } %>/script>script identry-form-checkbox-template typetext/template> div classentry-form-section-field field-%- id %>> label for%- id %>> input typecheckbox id%- id %> name%- name %> %- checked ? checked : %> %- isMandatory ? required : %>> span> % if (linkUrl) { %> a href%- linkUrl %>>% label %>/a> % } else { %> % label %> % if (isMandatory) { %> span classred>*/span> % } %> % } %> /span> /label> /div>/script>script identry-form-radio-template typetext/template> div classentry-form-section-field field-%- id %>> label> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> div classmultiple-group> % for (let option of options) { %> label> input class typeradio value%- option.value %> id%- id %>_%- slugifyId(option.value) %> namefield_%- id %> %- isMandatory ? required : %>> % option.label %> /label> % } %> /div> /div>/script>script identry-form-text-area-template typetext/template> div classentry-form-section-field field-%- id %>> label for%- id %>> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> textarea id%- id %> name%- name %> class rows5 cols60 maxlength1024 %- isMandatory ? required : %> >/textarea> /div>/script>script identry-form-dropdown-template typetext/template> div classentry-form-section-field field-%- id %>> label for%- id %>> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> select id%- id %> name%- name %> class tbits-dropdown data-placeholder%- placeholder %> %- isMandatory ? required : %> > option>!-- Empty option required for placeholder -->/option> % for (let option of options) { %> option value%- option.value %>>%- option.label %>/option> % } %> /select> /div>/script>script identry-form-phone-template typetext/template> div classentry-form-section-field field-phone> label> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> div classentry-form-section-phone-inputs> select namephone_country_code idphone_country_code data-label%- countryCodeLabel %> %- isMandatory ? required : %>> /select> input classphone_number_input typetel namephone idphone onkeydownpreventNonPhoneCharacters(event) %- isMandatory ? required : %> > /div> /div>/script>script identry-form-birthday-row-template typetext/template> div classentry-form-section-field birth-date-row field-birthday>/div>/script>script identry-form-birth-year-template typetext/template> div classbirth-date-field field-birth_year> label for%-name %>> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> input typenumber name%-name %> id%-name %> min1910 placeholderYYYY class minlength4 maxlength4 oninputadvanceField(this) %- isMandatory ? required : %> > /div>/script>script identry-form-birth-month-template typetext/template> div classbirth-date-field field-birth_month> label forage_month> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> input typenumber nameage_month idage_month min1 max12 placeholderMM class minlength2 maxlength2 oninputadvanceField(this) %- isMandatory ? required : %> > /div>/script>script identry-form-birth-day-template typetext/template> div classbirth-date-field field-birth_day> label forage_day> % label %> % if (isMandatory) { %> span classred>*/span> % } %> /label> input typenumber nameage_day idage_day min1 max31 placeholderDD class minlength2 maxlength2 oninputadvanceField(this) %- isMandatory ? required : %> > /div>/script>script> const inputTemplate _.template($(#entry-form-input-template).html()); const checkboxFieldTemplate _.template($(#entry-form-checkbox-template).html()); const radioFieldTemplate _.template($(#entry-form-radio-template).html()); const textAreaFieldTemplate _.template($(#entry-form-text-area-template).html()); const dropdownFieldTemplate _.template($(#entry-form-dropdown-template).html()); const phoneFieldTemplate _.template($(#entry-form-phone-template).html()); const birthdayRowFieldTemplate _.template($(#entry-form-birthday-row-template).html()); const birthYearTemplate _.template($(#entry-form-birth-year-template).html()); const birthMonthTemplate _.template($(#entry-form-birth-month-template).html()); const birthDayTemplate _.template($(#entry-form-birth-day-template).html()); function slugifyId(value) { value value.replaceAll(/^\w\s-/g, ) value value.replaceAll( , -) return value } function renderInput(fieldObj) { return $(inputTemplate({ id: fieldObj.id || fieldObj.name, label: fieldObj.label, name: fieldObj.name, isMandatory: fieldObj.isMandatory || false, options: fieldObj.options || , inputType: fieldObj.inputType || text, value: fieldObj.value })) } function renderCheckbox(fieldObj) { return $(checkboxFieldTemplate({ id: fieldObj.id || fieldObj.name, label: fieldObj.label, name: fieldObj.name, isMandatory: fieldObj.isMandatory, checked: fieldObj.checked || false, linkUrl: fieldObj.linkUrl || null })); } function renderRadio(fieldObj) { return $(radioFieldTemplate({ id: fieldObj.id || fieldObj.name, label: fieldObj.label, name: fieldObj.name, isMandatory: fieldObj.isMandatory, checked: fieldObj.checked || false, options: fieldObj.options })) } function renderTextArea(fieldObj) { return $(textAreaFieldTemplate({ id: fieldObj.id || fieldObj.name, label: fieldObj.label, name: fieldObj.name, isMandatory: fieldObj.isMandatory, })) } function renderDropdown(fieldObj) { return $(dropdownFieldTemplate({ id: fieldObj.id || fieldObj.name, label: fieldObj.label, name: fieldObj.name, isMandatory: fieldObj.isMandatory, options: fieldObj.options, placeholder: fieldObj.placeholder || })) } function renderPhone(fieldObj) { return $(phoneFieldTemplate({ id: fieldObj.id || fieldObj.name, label: fieldObj.label, name: fieldObj.name, isMandatory: fieldObj.isMandatory, countryCodeLabel: fieldObj.countryCodeLabel || })) } function renderBirthday(fieldObj, dateFormat MM/DD/YYYY, yearOnly false) { let $field $(birthdayRowFieldTemplate({})) let details { id: fieldObj.id || fieldObj.name, label: fieldObj.label, isMandatory: fieldObj.isMandatory, } if (yearOnly) { detailsname birth_year $field.append(birthYearTemplate(details)) } else { detailsname age_year if (dateFormat YYYY/MM/DD) { $field.append(birthYearTemplate(details)) $field.append(birthMonthTemplate(details)) $field.append(birthDayTemplate(details)) } else if (dateFormat DD/MM/YYYY) { $field.append(birthDayTemplate(details)) $field.append(birthMonthTemplate(details)) $field.append(birthYearTemplate(details)) } else { $field.append(birthMonthTemplate(details)) $field.append(birthDayTemplate(details)) $field.append(birthYearTemplate(details)) } } return $field }/script>style> .change-language { display: flex; flex-direction: row-reverse; margin-right: 25px; margin-top: 5px; } .language-change-container { display: flex; gap: 10px; align-items: center; justify-content: center; cursor: pointer; margin-bottom: 3px; } .language-change-container > span.tbits-icon-chevron-down-icon.rotate { transform: rotate(180deg); } .language-options-container { display: flex; flex-direction: column; gap: 2px; position: absolute; inset-inline-end: 25px; z-index: 1000; width: fit-content; max-width: 250px; background-color: rgba(255, 255, 255, 0.5); } .language-select-option { padding: 5px; cursor: pointer; text-align: right; font-weight: bold; } .language-select-option:hover { background-color: #F5F5F5; color: inherit; } .change-language-section { width: 100%; }/style>script idchange-language-section-template typetext/template>div classchange-language-section> div classchange-language style%- show ? : display: none; %>> div styledisplay: flex; flex-direction: column;> div classlanguage-change-container> span classtbits-icon tbits-icon-world-icon global-icon stylefont-size: 20px;>/span> div stylefont-weight: bold>%- tab.locale_props.change_language %>/div> span idcontainer-open-arrow classtbits-icon tbits-icon-chevron-down-icon>/span> /div> div idlanguage-dropdown styledisplay: none;> div classlanguage-options-container> % for (const language in languageSelectionMap) { %> span id%- languageSelectionMaplanguage %> classlanguage-select-option> %- toTitleCase(language) %> /span> % } %> /div> /div> /div> /div>/div>/script>script> class ChangeLanguageSection extends Section { static changeCampaignLanguageEvent new CampaignEvent(change-campaign-language-event); changeLanguageTemplate _.template($(#change-language-section-template).html()); connectedCallback() { const languageSelectionMap {}; for (const lang_code in this.tab.full_locale_props) { const language this.tab.full_locale_propslang_code.language; languageSelectionMaplanguage lang_code; } const show Object.keys(languageSelectionMap).length > 1; this.languageSelectionMap languageSelectionMap; this.innerHTML this.changeLanguageTemplate({tab: this.tab, languageSelectionMap: languageSelectionMap, show: show}); this.setupLanguageChangeListeners(); this.setupLanguageOptionsToggle(); } setupLanguageOptionsToggle() { const $changeLanguageContainer $(.language-change-container); $changeLanguageContainer.off(); $changeLanguageContainer.on(click, () > this.toggleLanguageOptions()); } setupLanguageChangeListeners() { for (const language in this.languageSelectionMap) { const languageCode this.languageSelectionMaplanguage; const languageCodeElem $(`#${languageCode}`); languageCodeElem.off(); languageCodeElem.on(click, () > this.changeLanguage(languageCode)); } } async changeLanguage(languageCode) { await tbitsFetch(`/application/set_campaign_language/${this.tab.page_tab_id}`, {method: POST, form: {lang: languageCode}}); const detail {setLanguage: languageCode}; ChangeLanguageSection.changeCampaignLanguageEvent.trigger(detail); } toggleLanguageOptions() { $(#language-dropdown).toggle(); $(#container-open-arrow).toggleClass(rotate); } } customElements.define(change-language-section, ChangeLanguageSection)/script>style> navigation-header-section .campaign-content { display: flex; justify-content: center; } navigation-header-section .navigation-container { display: flex; flex-direction: row; align-items: center; gap: 24px; height: 100px; text-align: center; width: 100%; } navigation-header-section .navigation-header-title { margin: auto 0; font-size: 26px; font-weight: bold; }/style>script idnavigation-header-section-template typetext/template> div classheader-section> div classcampaign-content> div classnavigation-container> button classbase-btn primary icon-btn back-btn> span classtbits-icon tbits-icon-chevron-left-icon>/span> /button> div classnavigation-header-title>%- title %>/div> /div> /div> /div>/script>script idnavigation-header-section-script> class NavigationHeaderSection extends Section { template _.template($(#navigation-header-section-template).html()); async connectedCallback() { if (!this.title || this.title null || this.title undefined) { this.title this.tab.locale_props.back; } this.innerHTML this.template({tab: this.tab, title: this.title}); this.initNavButtons(); } initNavButtons() { $(this).find(.back-btn).on(click, () > { this.onBack(); }) } } customElements.define(navigation-header-section, NavigationHeaderSection)/script>script idwelcome-header-section-template typetext/template> a classsr-only sr-only-focusable href#content aria-labelSkip Navigation> Skip Navigation /a> % if (tab.media_map.welcome_image?.1) { %> div classwelcome-header-section-header-image header-image> img alt%- tab.props.welcome_header_img_alt || Welcome %> src%- tab.home_url %>/fb_media/%- tab.media_map.welcome_image1 %>/> /div> % } %> % if (tab.locale_props.welcome_description || tab.props.raw_description) { %> div classwelcome-header-section-description campaign-content> % tab.locale_props.welcome_description %> % tab.props.raw_description %> /div> % } %>/script>script idwelcome-header-section-script> class WelcomeHeaderSection extends ChangeLanguageSection { template _.template($(#welcome-header-section-template).html()); connectedCallback() { super.connectedCallback(); this.innerHTML + this.template({tab: this.tab}); super.setupLanguageChangeListeners(); super.setupLanguageOptionsToggle(); this.className header-section; } } customElements.define(welcome-header-section, WelcomeHeaderSection);/script>script idthank-you-header-section-template typetext/template> a classsr-only sr-only-focusable href#main-content aria-labelSkip Navigation Button> Skip Navigation /a> % if (tab.media_map?.thank_you_image) { %> div classheader-image> % if (tab.links_map?.thankyou_image) { %> a classthank-you-image href%- tab.links_map.thankyou_image %> target_top> img alt%- tab.props.thankyou_header_img_alt || Thank You %> src%- tab.home_url %>/fb_media/%- tab.media_map.thank_you_image1 %>/> /a> % } else { %> img classthank-you-image alt%- tab.props.thankyou_header_img_alt || Thank You %> src%- tab.home_url %>/fb_media/%- tab.media_map.thank_you_image1 %>/> % } %> /div> % } %> % if ((tab.links_map?.thankyou_image && tab.props.thankyou_image_url_as_button && tab.props.thankyou_image_url_button_text ) || (tab.locale_props.thank_you_description || tab.props.raw_thankyou_description) ) { %> div classcampaign-content> % if (tab.links_map?.thankyou_image && tab.props.thankyou_image_url_as_button && tab.props.thankyou_image_url_button_text ) { %> div classbase-btn-container> a href%- tab.links_map.thankyou_image %> target_top classbase-btn primary>%- tab.props.thankyou_image_url_button_text %>/a> /div> % } %> % if (tab.locale_props.thank_you_description || tab.props.raw_thankyou_description) { %> div classthankyou-message description> % tab.locale_props.thank_you_description %> % tab.props.raw_thankyou_description %> /div> % } %> /div> % } %>/script>script idthank-you-section-script> class ThankYouHeaderSection extends ChangeLanguageSection { template _.template($(#thank-you-header-section-template).html()); connectedCallback() { super.connectedCallback(); this.innerHTML + this.template({tab: this.tab}); super.setupLanguageChangeListeners(); super.setupLanguageOptionsToggle(); this.className header-section } } customElements.define(thank-you-header-section, ThankYouHeaderSection)/script>style> #footer { width: 100%; margin-top: 20px; }/style>script idshare-links-section-template typetext/template> style> .share-links-section { border-top: 1px solid #E3E3E3; display: flex; justify-content: center; align-items: center; } .share-icons-container { display: flex; flex-flow: row wrap; max-width: 480px; padding: 32px 12px; justify-content: center; align-items: center; gap: 32px; } #copy-share-link-btn .share-link-icon { color: var(--style_prop_colour_share_button); } .share-link-icon, .share-link-icon:hover { color: var(--style_prop_social_media_icons_style); width: 22px; font-size: 22px; flex-shrink: 0; text-decoration: unset; cursor: pointer; } .campaign-share-links:hover { transform: scale(1.15); } .campaign-share-links, .campaign-share-links:hover{ color: black; transition: all 0.3s ease; display: flex; justify-content: center; align-items: center; text-decoration: unset; } /style> div classshare-links-section> div classshare-icons-container> % if (tab.props.instagram_handle) { %> a classcampaign-share-links aria-labelFollow %- tab.props.instagram_handle %> on Instagram, opens in new tab hrefhttps://instagram.com/%- tab.props.instagram_handle %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/instagram-icon-color.svg altinstagram logo> % } else { %> span classshare-link-icon tbits-icon tbits-icon-instagram-icon>/span> % } %> /a> % } %> % if (tab.props.twitter_handle) { %> a classcampaign-share-links idtwitter-share aria-labelFollow %- tab.props.twitter_handle %> on X, opens in new tab hrefhttps://twitter.com/%- tab.props.twitter_handle %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/x-icon.svg altX logo> % } else { %> span classshare-link-icon tbits-icon tbits-icon-x-network-icon>/span> % } %> /a> % } %> % if (tab.links_map.meta_share) { %> a classcampaign-share-links idfacebook-share aria-labelHead to %- tab.links_map.meta_share %>, opens in new tab href%- tab.links_map.meta_share %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/facebook-round-icon.svg altFacebook logo> % } else { %> span classshare-link-icon tbits-icon tbits-icon-facebook-icon>/span> % } %> /a> % } %> % if (tab.props.tiktok_handle) { %> a classcampaign-share-links idtiktok-share aria-labelFollow %- tab.props.tiktok_handle %> on TikTok, opens in new tab hrefhttps://tiktok.com/@%- tab.props.tiktok_handle %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/tiktok-icon.svg altTikTok logo> % } else { %> span classshare-link-icon tbits-icon tbits-icon-tiktok-icon>/span> % } %> /a> % } %> % if (tab.links_map.youtube_share) { %> a classcampaign-share-links idyoutube-share aria-labelCheck out our YouTube, opens in new tab href%- tab.links_map.youtube_share %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon stylewidth: 24px; src%- tab.home_url %>/static/icons/youtube-icon-color.svg altYoutube logo> % } else { %> span classshare-link-icon tbits-icon tbits-icon-youtube-icon>/span> % } %> /a> % } %> % if (tab.links_map.spotify_share) { %> a classcampaign-share-links idspotify-share aria-labelCheck out our Spotify, opens in new tab href%- tab.links_map.spotify_share %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/spotify-icon.svg> % } else { %> span classshare-link-icon tbits-icon tbits-icon-spotify-icon>/span> % } %> /a> % } %> % if (tab.links_map.apple_music_share) { %> a classcampaign-share-links idapple-music-share aria-labelCheck out our Apple Music, opens in new tab href%- tab.links_map.apple_music_share %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/apple-music-icon.svg> % } else { %> span classshare-link-icon tbits-icon tbits-icon-apple-music-icon>/span> % } %> /a> % } %> % if (tab.links_map.discord_share) { %> a classcampaign-share-links iddiscord-share aria-labelCheck out our Discord, opens in new tab href%- tab.links_map.discord_share %> target_blank> % if (tab.style_props.social_media_icons_style standard) { %> img classshare-link-icon src%- tab.home_url %>/static/icons/discord-icon.svg> % } else { %> span classshare-link-icon tbits-icon tbits-icon-discord-icon stylefont-size: 19px>/span> % } %> /a> % } %> % if (tab.links_map.website_share) { %> a classcampaign-share-links idwebsite-share aria-labelCheck out our website, opens in new tab href%- tab.links_map.website_share %> target_blank> span classshare-link-icon tbits-icon tbits-icon-world-icon>/span> /a> % } %> % if (!tab.props.hide_share_button) { %> a classcampaign-share-links idcopy-share-link-btn aria-labelShare this campaign with a friend> span classshare-link-icon tbits-icon tbits-icon-share-3-icon>/span> /a> % } %> /div> div idcopy_link_container styledisplay: none> span>Please Copy: /span> span stylecolor: var(--blue-500) idandroid_webview_copy_link>/span> /div> /div>/script>script idshare-links-section-script> class ShareLinksSection extends Section { template _.template($(#share-links-section-template).html()) connectedCallback() { if (!this.tab.props.hide_share_button || this.tab.props.instagram_handle || this.tab.props.twitter_handle || this.tab.props.meta_share || this.tab.props.tiktok_handle || this.tab.links_map.website_share || this.tab.links_map.youtube_share || this.tab.links_map.spotify_share || this.tab.links_map.apple_music_share || this.tab.links_map.discord_share) { this.innerHTML this.template({tab: this.tab}) } if(!this.tab.props.hide_share_button) { $(this).find(#copy-share-link-btn).on(click, async () > { await this.copyShareLink() }) } } async copyShareLink() { const inviteURL await this.buildTrackerLink(this.tab.page_tab_id); const payload {title: Share Contest, url: inviteURL}; if (window.AndroidShareHandler && window.AndroidShareHandler.share) { // yinzcam app await window.AndroidShareHandler.share(inviteURL); } else if (navigator.canShare && navigator.canShare(payload)) { await navigator.share(payload).catch(function () { }); await navigator.clipboard.writeText(inviteURL).catch(function () { }); } else { const userAgent window.navigator.userAgent.toLowerCase(); if (userAgent.includes(wv)) { // android webview doesnt support navigator.share $(#copy_link_container).show(); $(#android_webview_copy_link).text(inviteURL); } else { await navigator.clipboard.writeText(inviteURL); await showSuccess(this.tab.locale_props.copied); } } trackShareClick(this.tab.page_tab_id, link); return false; } buildTrackerLink(pageTabId) { return $.ajax({ url: urlWithCookie(/crm/ajax/build_tracker_link), data: {page_tab_id: pageTabId, share_url: this.tab.share_url} }).then(function (res) { return res.share_url; }).catch(function (res) { showError(res); }); } } customElements.define(share-links-section, ShareLinksSection)/script>style> .tbits-tradablebits-terms { font-weight: normal; font-size: 9px; color: #7F7F7F; width: 100%; margin: auto; text-align: center; padding: 5px; }/style>script idopt-out-section-template typetext/template> div classtbits-tradablebits-terms> span classpowered-by> %- tab.locale_props.care_privacy %> a href%- tab.home_url %>/optout/%- tab.account_id %>?actionoptout target_blank>%- tab.locale_props.opt_out_targeting %>/a> | a href%- tab.home_url %>/optout/%- tab.account_id %>?actionerase target_blank>%- tab.locale_props.erase_data %>/a> /span> /div>/script>script idopt-out-section-script> class OptOutSection extends Section { template _.template($(#opt-out-section-template).html()); connectedCallback() { if (this.tab.props.show_optout_links) { this.innerHTML this.template({tab: this.tab}) } } } customElements.define(opt-out-section, OptOutSection)/script>style> .footer-image { height: 100%; width: 100%; object-fit: cover; }/style>script idfooter-image-section-template typetext/template> div classfooter-image-container> img classfooter-image altfooter image src%- tab.home_url %>/fb_media/%- tab.media_map.footer_image1 %>/> /div>/script>script idfooter-image-section-script> class FooterImageSection extends Section { template _.template($(#footer-image-section-template).html()) connectedCallback() { if (this.tab.media_map.footer_image) { this.innerHTML this.template({tab: this.tab}) this.className footer-image-section } } } customElements.define(footer-image-section, FooterImageSection)/script>style> .app-legal { overflow: hidden; position: relative; word-wrap: break-word; } .app-legal .legal-header { display: flex; justify-content: center; margin-bottom: 20px; } .app-legal .legal-header .legal-toggle { color: var(--style_prop_colour_legal_text); text-align: center; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; padding: 0 20px; } .app-legal .legal-header button.legal-toggle { background: none; } .app-legal .legal-header .legal-toggle:not(:last-child) { } .app-legal .legal-header .legal-toggle:hover, .app-legal .legal-header .legal-toggle:focus, .app-legal .legal-header .legal-toggle.active { text-decoration: underline; color: color-mix(in srgb, var(--style_prop_colour_hyperlink_text) 88%, #000000);; } .legal-container { color: var(--style_prop_colour_legal_text); margin: 10px; font-size: 12px; padding: 15px 20px; border-radius: 4px; border: 1px solid rgb(214, 214, 214); background-color: var(--style_prop_colour_content_section_background); } .legal-container .legal-container-header { margin: 0 0 10px 0; font-weight: bold; font-size: 14px; } .legal-container .close-legal { float: right; cursor: pointer; font-size: 24px; } .legal-container .close-legal:hover, .legal-container .close-legal:focus { color: rgb(214, 214, 214); } .legal-display { padding-bottom: 20px; display: flex; justify-content: center; align-items: center; }/style>script idlegal-section-template typetext/template> div classapp-legal idlegal-section> div classlegal-header legal-policies>/div> div classlegal-display>/div> /div>/script>script idlegal-policy-copy-template typetext/template> button typebutton classlegal-toggle %- type %>>%- prop %>/button>/script>script idlegal-url-template typetext/template> a href%- url %> target_blank classlegal-toggle %- type %>>%- prop %>/a>/script>script idlegal-display-template typetext/template> div classlegal-container %- type %> content-width styledisplay: none> span classclose-legal tbits-icon tbits-icon-circle-x-icon>/span> p classlegal-container-header>%- prop %>/p> % policy %> /div>/script>script idlegal-section-script> class LegalSection extends Section { template _.template($(#legal-section-template).html()) connectedCallback() { // checks if theres any copy or urls to show in the legal section. (ie legal.terms.policy_copy) if (Object.values(this.tab.legal).map(type > Object.values(type)).flat().some(value > value)) { this.innerHTML this.template({tab: this.tab}) let $area $(this).find(.legal-policies) const copyTemplate _.template($(#legal-policy-copy-template).html()) const policyTemplate _.template($(#legal-url-template).html()) const displayTemplate _.template($(#legal-display-template).html()) for (let type in this.tab.legal) { let terms this.tab.legaltype; let prop this.tab.locale_props`legal_text_${type}`; if (terms.policy_copy) { let $copySection $(copyTemplate({type, prop})) $copySection.on(click, () > this.toggleLegalSection(.legal-display, type)) $area.append($copySection) let $displaySection $(displayTemplate({type, prop, policy: terms.policy_copy})) $displaySection.find(.close-legal).on(click, (ev) > this.closeLegal(ev.currentTarget, true)) $(this).find(.legal-display).append($displaySection) } else if (terms.policy_url) { $area.append(policyTemplate({type, prop, url: terms.policy_url})) } } } } toggleLegalSection(container, section) { let $container $(container); let $activeContainer $container.find(.legal-container. + section).toggle(); let $activeToggle $container.find(.legal-toggle. + section).toggleClass(active); $container.find(.legal-container).not($activeContainer).hide(); $container.find(.legal-toggle).not($activeToggle).removeClass(active); } closeLegal(el, deactiveToggle) { $(el).closest(.legal-container).hide(); if (deactiveToggle) { $(.legal-toggle).removeClass(active); } } } customElements.define(legal-section, LegalSection)/script>script idfooter-section-script> class FooterSection extends Section { connectedCallback() { let $section $(this); $section.append(Section.create(share-links-section, {tab: this.tab})) $section.append(Section.create(legal-section, {tab: this.tab})) $section.append(Section.create(opt-out-section, {tab: this.tab})) $section.append(Section.create(footer-image-section, {tab: this.tab})) this.className footer-section } } customElements.define(footer-section, FooterSection)/script>style> .sweepstakes-title { margin: 20px; text-align: center; } .sweepstake-option { padding: 16px 24px; margin-bottom: 8px; border-radius: 16px; border: none; display: flex; flex-wrap: wrap; align-items: center; gap: 24px; background-color: var(--style_prop_colour_content_section_background); } .sweepstake-option-icon { flex-basis: 24px; flex-shrink: 0; } .sweepstake-option-icon > .tbits-icon { font-size: 24px; } .sweepstake-option-body { flex-basis: 400px; flex-grow: 999999999; display: flex; flex-direction: column; gap: 4px; } .sweepstake-option-entry { flex-basis: 120px; flex-grow: 1; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; } .sweepstake-option-title { font-weight: bold; font-size: 16px; } .sweepstake-option-win-amount { text-align: center; border: 1px solid var(--style_prop_colour_font); padding: 4px 8px; border-radius: 16px; } .sweepstake-option-separator { border-top: 1px solid var(--style_prop_entry_form_text); opacity: 20%; margin: 0; } .entry-form-section-field .sweepstake-option-opt-in-description { font-weight: normal; } #player { margin-top: 2rem; width: 100%; }/style>script> function getSweepstakeOptionIcon(option_type) { let map { link: tbits-icon-link-icon, youtube: tbits-icon-youtube-icon, email_optin: tbits-icon-checkbox-icon, phone_optin: tbits-icon-checkbox-icon, } return mapoption_type || tbits-icon-star-icon }/script>script idsweepstakes-section-template typetext/template> div classsweepstakes campaign-content> h3 classsweepstakes-title>%- tab.locale_props.enter_sweepstakes %>/h3> div idsweepstakes-area>/div> /div>/script>script idsweepstake-option-template typetext/template> div classsweepstake-option id%- option.option_uid %>> div classsweepstake-option-icon> span classtbits-icon %- getSweepstakeOptionIcon(option.option_type) %>>/span> /div> div classsweepstake-option-body> div classsweepstake-option-title>%- option.option_name %>/div> % if(option.description) { %> span classsweepstake-option-description>%- option.description %>/span> % } %> % if (email_optin, phone_optin.includes(option.option_type)) { %> hr classsweepstake-option-separator /> form classsweepstake-option-opt-in-form> div classentry-form-section-field> label classsweepstake-option-opt-in-description> input typecheckbox classtbits-checkbox sweepstake-option-opt-in-checkbox required> % option.opt_in_description %> span classred>*/span> /label> /div> /form> % } %> /div> div classsweepstake-option-entry> div classsweepstake-option-win-amount> +%- option.win_amount %> %- tab.locale_props.sweepstakes_bonus %> %- pluralize(option.win_amount, tab.locale_props.sweepstakes_entry, tab.locale_props.sweepstakes_entries) %> /div> % if (youtube, email_optin, phone_optin.includes(option.option_type)) { %> button typebutton classbase-btn primary sweepstake-option-btn %- email_optin, phone_optin.includes(option.option_type) ? disabled : %>> %- option.button_text %> /button> % } else { %> a href%- option.tracker_url || option.option_url %> target_blank classbase-btn primary sweepstake-option-btn> %- option.button_text %> /a> % } %> /div> /div>/script>script idyoutube-modal-template typetext/template> div classmodal-dialog> div classmodal-content> div classmodal-body> modal-close-button>/modal-close-button> div idplayer>/div> /div> /div> /div>/script>script idsweepstakes-section-scripts> class SweepstakesSection extends Section { template _.template($(#sweepstakes-section-template).html()); async connectedCallback() { this.sweepstakes await this.getFanSweepstakes() if (this.sweepstakes?.options.length ! 0) { this.innerHTML this.template({tab: this.tab}) this.renderSweepstakeOptions($(this), this.sweepstakes.options, this.sweepstakes.entries) } } async getFanSweepstakes() { try { const {body} await tbitsFetch(`/application/${this.tab.page_tab_id}/fan_sweepstakes`) return body } catch (e) { return null } } renderSweepstakeOptions($container, sweepstake_options, sweepstake_entries) { const $sweepstakesArea $container.find(#sweepstakes-area); const optionTemplate _.template($(#sweepstake-option-template).html()); sweepstake_options.forEach(option > { const $option $(optionTemplate({option, tab: this.tab})); $option.find(.sweepstake-option-btn).on(click, async () > { if (email_optin, phone_optin.includes(option.option_type)) { if (!validateFormNatively($option.find(.sweepstake-option-opt-in-form))) { return } } else if (option.option_type youtube) { const videoWasWatched await this.playYouTubeVideo(option.youtube_video_id); if (!videoWasWatched) { return; } } this.addSweepstakeEntry(option.option_uid) }) // disable options that already have entries const hasEntry sweepstake_entries && sweepstake_entries.some(entry > entry.option_uid option.option_uid); if (hasEntry) { $option.find(`.sweepstake-option-btn`).attr(disabled, true).toggleClass(disabled, true); $option.find(.sweepstake-option-opt-in-checkbox).attr({disabled: true, checked: true}); } else { // enable button only if checkbox is checked $option.find(.sweepstake-option-opt-in-checkbox).on(change, (event) > { $option.find(.sweepstake-option-btn).attr(disabled, !event.target.checked || hasEntry); }) } $sweepstakesArea.append($option); }) } addSweepstakeEntry(option_uid) { showThrobber(); $.ajax({ url: urlWithCookie(`/application/${PAGE_TAB_ID}/sweepstakes/${option_uid}/entries`), type: POST, success() { $(`#${option_uid} .sweepstake-option-btn`).attr(disabled, true).addClass(disabled); $(`#${option_uid} .sweepstake-option-opt-in-checkbox`).attr(disabled, true); showSuccess(); }, error(xhr) { showError(xhr); }, complete() { hideThrobber(); } }) } loadYoutubeIframeAPI() { if (window.YT) { return Promise.resolve(); } return new Promise((resolve) > { window.onYouTubeIframeAPIReady resolve; // Copied from https://developers.google.com/youtube/iframe_api_reference const tag document.createElement(script); tag.src https://www.youtube.com/iframe_api; const firstScriptTag document.getElementsByTagName(script)0; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); }) } async playYouTubeVideo(youtube_video_id) { showThrobber(); await this.loadYoutubeIframeAPI(); hideThrobber(); const $modal $(#modal-dialog); $modal.html($(#youtube-modal-template).html()).modal({ backdrop: static, keyboard: false }); return new Promise((resolve) > { const onModalClosed () > { player.stopVideo(); resolve(false); } $modal.one(hide.bs.modal, onModalClosed); const player new YT.Player(player, { height: 390, width: 640, videoId: youtube_video_id, playerVars: { playsinline: 1, controls: 0, disablekb: 1, // disable keyboard controls rel: 0, // disable related videos }, events: { onReady: (event) > event.target.playVideo(), onStateChange: (event) > { if (event.data YT.PlayerState.ENDED) { $modal.off(hide.bs.modal, onModalClosed); resolve(true); } } } }) }) } } customElements.define(sweepstakes-section, SweepstakesSection)/script>script typetext/javascript> class EntryFormCampaign extends Campaign { initialize() { super.initialize(); //Entry Form Specific this.entryFormContentState new EntryFormContentState(this.tab); this.entryAppThankYouState new EntryAppThankYouState(this.tab); EntryFormSection.submitEntryFormEvent.on(() > { this.transitionTo(this.entryAppThankYouState) }) } async start() { if (this.tab.props.check_age) { if (!this.ageGateState.isPassed()) { this.transitionTo(this.ageGateState) return } } if (this.tab.has_region_gate) { if (!this.regionGateState.isPassed()) { this.transitionTo(this.regionGateState) return } } const currentTimeUnix Math.floor(Date.now() / 1000); if (currentTimeUnix this.tab.startTimestampUnix) { this.transitionTo(this.notStartedState) } else if (currentTimeUnix this.tab.endTimestampUnix) { if (this.tab.auth_network ! null && !this.authLoginState.isLoggedIn()) { this.transitionTo(this.authLoginState) return } let fanResults await this.getFanResults() if (!this.tab.props.kiosk_mode_enabled && this.authLoginState.isLoggedIn() && fanResults.activity_id) { this.transitionTo(this.entryAppThankYouState) return } if (this.tab.campaign_reached_entry_limit || fanResults.reached_limit) { if (fanResults.activity_id) { this.transitionTo(this.entryAppThankYouState) } else { this.transitionTo(this.contestOverState) } return } this.transitionTo(this.entryFormContentState) } else { this.transitionTo(this.contestOverState) } } } class EntryFormContentState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { $(#header).append(Section.create(welcome-header-section, {tab: this.tab})); const $content $(#content); $content.append(Section.create(clock-date-countdown-section, {tab: this.tab})); $content.append(Section.create(entry-form-section, {tab: this.tab})); $(#footer).append(Section.create(footer-section, {tab: this.tab})) } } class EntryAppThankYouState extends CampaignState { constructor(tab) { super(tab); } enter() { this.render() } render() { $(#header).html(Section.create(thank-you-header-section, {tab: this.tab})) const $content $(#content); $content.append(Section.create(sweepstakes-section, {tab: this.tab})) $content.append(Section.create(start-again-section, {tab: this.tab})) $content.append(Section.create(email-logout-section, {tab: this.tab})) $(#footer).html(Section.create(footer-section, {tab: this.tab})) } } let campaign null; loadPageTab() .then((tab) > { campaign new EntryFormCampaign(tab) campaign.start() })/script>!-- custom css --> style> .error-modal-footer button { background-color: #00338D !important} /style> style> .tbits-restart-promo { display:none;}.thankyou-btn-container { margin: 10px auto; max-width: 382px !important;}.thankyou-btn-container .campaign-btn { display: inline-block; margin: auto; border-radius: 4px; border: none; padding: 15px 20px; max-width: 100%; cursor: pointer; font-size: 18px !important; line-height: 39px !important;}@media only screen and (max-width: 768px) { .tbits-thankyou #container{ height: 100% } .tbits-thankyou #container .thankyou-container{ height: 100%; display: flex; align-items: center; }} /style> !-- end -->!-- custom js --> script> function checkUnder18() { const dateCutoff new Date(); dateCutoff.setFullYear(dateCutoff.getFullYear() - 18); const day parseInt($(#age_day).val()); const month parseInt($(#age_month).val()); const year parseInt($(#age_year).val()); const userDob Date.parse(year, month + 1, day); return userDob > dateCutoff;}const buffaloTnc a target_blank hrefhttps://www.buffalobills.com/about-us/terms-of-use>Buffalo Bills Terms/a>const buffaloPp a target_blank hrefhttps://www.buffalobills.com/about-us/privacy-policy>Buffalo Bills Privacy Policy/a>const textTos `div idcustom-tos>By clicking submit, I agree to the ${buffaloTnc}, and understand that personal information will be used as described in the ${buffaloPp}. Once registered, you will be able to access our preference center to control content, offers, and more that you receive from the Buffalo Bills, NFL, and other member clubs./div>`const nonUSTextTos `div idnon-us-custom-tos>By clicking submit, I agree to the ${buffaloTnc}, and understand that personal information will be used as described in the ${buffaloPp}.`const ngMxTextTos I agree that the Buffalo Bills, NFL, and other member Clubs may send me content, offers, and more.$(document).on(tbits.populateFieldValues, function() { if ($(#country_code).val() us) { $(#is_subscribed).attr(checked, true); $(.field-is_subscribed).hide(); $(#non-us-custom-tos).hide(); if ($(#custom-tos).length 0) { $(.entry-form-section-field).last().after(textTos); } } else { $(#is_subscribed).attr(checked, true); $(.field-is_subscribed).show(); $(#non-us-custom-tos).show(); $(#custom-tos).hide(); if ($(#non-us-custom-tos).length 0) { $(.entry-form-section-field).last().after(nonUSTextTos); } if ($(#country_code).val() ng || $(#country_code).val() mx) { $(.field-is_subscribed span).html(ngMxTextTos); } else { $(.field-is_subscribed span).html(I agree that the Buffalo Bills may send me content, offers, and more.); } } $(#country_code).on(change, function() { if ($(this).val() us) { $(#is_subscribed).attr(checked, true); $(.field-is_subscribed).hide(); $(#non-us-custom-tos).hide(); if ($(#custom-tos).length 0) { $(.entry-form-section-field).last().after(textTos); } $(#custom-tos).show(); return; } else { $(#is_subscribed).attr(checked, false); $(.field-is_subscribed).show(); $(#custom-tos).hide(); if ($(#non-us-custom-tos).length 0) { $(.entry-form-section-field).last().after(nonUSTextTos); } $(#non-us-custom-tos).show(); if ($(#country_code).val() ng || $(#country_code).val() mx) { $(.field-is_subscribed span).html(ngMxTextTos); } else { $(.field-is_subscribed span).html(I agree that the Buffalo Bills may send me content, offers, and more.); } } });});// SPA/*$(document).on(tbits.populateFieldValues, function() { if ($(#country_code).val() us) { $(#is_subscribed).attr(checked, true).hide(); $(.field-is_subscribed).hide(); if (!$(#custom-tos)) { $(.entry-form-section-field:last-of-type).after(textTos) } $(#custom-tos).show(); } $(#country_code).on(change, function() { if ($(this).val() us) { $(#is_subscribed).attr(checked, true); $(labelforis_subscribed).hide(); if ($(#custom-tos).length 0) { $(.form-group).last().before(textTos) } $(#custom-tos).show(); } else { $(#is_subscribed).attr(checked, false); $(labelforis_subscribed).show(); $(#custom-tos).hide() } })});*//*async function submitEntryForm(campaignData {}) { console.log(async) triggerTbitsEvent(tbits.submit); const entryFormData $(#entry-form).serializeObject(); if (!locale_props) { locale_props {}; } try { await validateEntryForm(#entry-form, locale_props); // will reject if invalid or error } catch (e) { showError(e) return reject(e); } if (checkUnder18()) { const err new Error(Youre too young to participate, come back soon!) showError(err) return reject(err); } let verificationRequest null; try { verificationRequest await maybeCreateVerificationRequest(entryFormData); } catch (e) { showError(e); return reject(e); } if (verificationRequest ! null) { const requestUid await openVerifyPiiModal(verificationRequest, entryFormData); if (requestUid) { entryFormData.request_uid requestUid; } else { return reject(Verification failed.); } } const url urlWithCookie(/application/submit_entry_form); const data Object.assign(entryFormData, campaignData); return await tbitsFetch(url, { method: POST, form: data}) .then(async (res) > { triggerTbitsEvent(tbits.submitSuccess, {fan_id: res.body.fan_id, activity_id: res.body.activity_id}); if (res.body?.activity_id) { await conversionTrack(res.body.activity_id); } return res.body }).catch((error) > { showError(error); throw error }).finally(() > { hideThrobber() })}*/EntryFormSection.prototype.submitEntryForm async function(campaignData {}) { debugger triggerTbitsEvent(tbits.submit); const entryFormData new FormData(document.querySelector(#entry-form)); entryFormData.set(page_tab_id, this.tab.page_tab_id); try { await this.validateEntryForm(#entry-form, this.tab.locale_props); } catch (e) { throw new Error(e) } if (checkUnder18()) { showError(You are too young to participate, come back soon!); return } if (!this.checkCampaignActive()){ throw new Error(this.tab.locale_props.entry_form_error) } let verificationRequest null; try { verificationRequest await this.maybeCreateVerificationRequest(entryFormData); } catch (e) { throw new Error(e.body) } if (verificationRequest ! null) { const requestUid await this.openVerifyPiiModal(verificationRequest, entryFormData); if (requestUid) { entryFormData.set(request_uid, requestUid); } else { // verification window was closed, all other errors are shown in the verification modal return false } } const url urlWithCookie(/application/submit_entry_form); Object.entries(campaignData).forEach((key, value) > { entryFormData.set(key, JSON.stringify(value)); }); try { const {body} await tbitsFetch(url, {method: POST, form: entryFormData}) this.tab.reached_limit body?.reached_limit; if (body.activity_id) { await this.conversionTrack(body.activity_id); } triggerTbitsEvent(tbits.submitSuccess, {fan_id: body?.fan_id, activity_id: body?.activity_id}); this.postPoints() return body; } catch (error) { throw error; } finally { hideThrobber(); }}; $(document).on(tbits.stateChange, () > { $(.legal-toggle).click();})$(document).on(tbits.formLoad, function() { $(#phone_country_code).val(us).trigger(change);}); /script>!-- end -->/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]