/* eslint-disable @typescript-eslint/no-explicit-any */ import './style.css' import { ThemeOptions } from '@mui/material' import Alert from '@mui/material/Alert' import { createTheme, ThemeProvider } from '@mui/material/styles' import { CSSProperties } from '@mui/styles' import axios, { AxiosError } from 'axios' import _filter from 'lodash/filter' import _find from 'lodash/find' import _flatten from 'lodash/flatten' import _forEach from 'lodash/forEach' import _get from 'lodash/get' import _identity from 'lodash/identity' import _includes from 'lodash/includes' import _isEmpty from 'lodash/isEmpty' import _map from 'lodash/map' import _some from 'lodash/some' import moment from 'moment-timezone' import React, { ReactNode, useEffect, useRef, useState } from 'react' import Button from 'react-bootstrap/Button' import { addToCart, getCheckoutPageConfigs, getEvent, getProfileData, getTickets, postOnCheckout, } from '../../api' import { getTimeSlotsByDate } from '../../api/common' import { getPreRegistrationInfluencers } from '../../api/preRegistrationComplete' import { X_TF_ECOMMERCE } from '../../constants' import { useCookieListener } from '../../hooks/useCookieListener' import { useLocalStorageListener } from '../../hooks/useLocalStorageListener' import { usePixel } from '../../hooks/usePixel' import { createCheckoutDataBodyWithDefaultHolder, getCookieByName, getQueryVariable, isBrowser, setLoggedUserData, } from '../../utils' import { logoutUser } from '../../utils/auth' import { Loader } from '../common/index' import { PoweredBy } from '../common/PoweredBy' import ConfirmModal from '../confirmModal' import Countdown from '../countdown' import { VerificationPendingModal } from '../idVerificationContainer/VerificationPendingModal' import { LoginModal } from '../loginModal' import { PreRegistration } from '../preRegistration' import WaitingList from '../waitingList' import { AccessCodeSection } from './AccessCodeSection' import { PromoCodeSection } from './PromoCodeSection' import { ReferralLogic } from './ReferralLogic' import { TicketsSection } from './TicketsSection' import TimeSlotsSection from './TimeSlotsSection' const checkUserPreregistration = (preregisteredUsers: IInfluencerData[]) => { const isWindowDefined = typeof window !== 'undefined' const userDataString = isWindowDefined ? window.localStorage.getItem('user_data') : '' let isPreregisteredUser = false if (userDataString) { const user = JSON.parse(userDataString) isPreregisteredUser = _some(preregisteredUsers, item => item.email === user?.email) } return isPreregisteredUser } interface TimeSlotsAttributes { ValidPromoCode: boolean; is_access_code: boolean; isPromotionsEnabled: boolean; } interface CartSuccess { skip_billing_page: boolean; event_id: string; hash?: string | number; total?: string | number; hasAddOn?: boolean; eventSlug?: string; cart?: ICart[]; currencySymbol?: string; } export interface IGetTickets { eventId: number; onAddToCartSuccess: (response: CartSuccess) => void; getTicketsLabel?: string; contentStyle?: React.CSSProperties; onAddToCartError: (e: AxiosError) => void; onGetTicketsSuccess: (response: { data: { attributes: { tickets: ITicketData[] } }; }) => void; onGetTicketsPress: () => void; onGetTicketsError: (e: AxiosError) => void; onLogoutSuccess: () => void; onLogoutError: (e: AxiosError) => void; onGetProfileDataSuccess: (response: { data: { attributes: { [key: string]: string | number | boolean | null } }; }) => void; onGetProfileDataError: (e: AxiosError) => void; onLoginSuccess: () => void; handleNotInvitedModalClose: () => void; handleInvalidLinkModalClose: () => void; theme?: 'light' | 'dark'; queryPromoCode?: string; isPromotionsEnabled?: boolean; themeOptions?: ThemeOptions & { input?: CSSProperties; checkbox?: CSSProperties; }; isAccessCodeEnabled?: boolean; hideSessionButtons?: boolean; hideWaitingList?: boolean; enableBillingInfoAutoCreate?: boolean; isButtonScrollable?: boolean; sortBySoldOut?: boolean; disableCountdownLeadingZero?: boolean; isLoggedIn?: boolean; actionsSectionComponent?: ReactNode; ticketsHeaderComponent?: ReactNode; hideTicketsHeader?: boolean; tableTicketsHeaderComponent?: ReactNode; hideTableTicketsHeader?: boolean; enableInfluencersSection?: boolean; enableAddOns?: boolean; ordersPath?: string; showPoweredByImage?: boolean; promoText?: string; showGroupNameBlock?: boolean; currencySymbol?: string; onReserveButtonClick?: () => void; onPendingVerification?: () => void; showAlertIcons?: boolean; submitMode?: 'auto' | 'click'; onPreRegistrationSuccess?: (response: any) => void; onPreRegistrationError?: (e: AxiosError) => void; onPreRegistrationGetCountriesSuccess?: (res: any) => void; onPreRegistrationGetCountriesError?: (e: AxiosError) => void; onForgotPasswordSuccess?: (res: any) => void; onForgotPasswordError?: (e: AxiosError) => void; showForgotPasswordButton?: boolean; logo?: string; } export interface ITicket { id: string | number; [key: string]: string | number; } interface IInfluencer { [key: string]: string | undefined; } export interface ISelectedTickets { isTable: boolean; [key: string]: string | number | boolean; } export const TicketsContainer = ({ onLoginSuccess, getTicketsLabel, eventId, onAddToCartSuccess, contentStyle = {}, onAddToCartError = _identity, onGetTicketsSuccess = _identity, onGetTicketsError = _identity, onLogoutSuccess = _identity, onLogoutError = _identity, onGetProfileDataSuccess = _identity, onGetProfileDataError = _identity, onGetTicketsPress = _identity, theme = 'light', queryPromoCode = '', isPromotionsEnabled = true, themeOptions, isAccessCodeEnabled = false, hideSessionButtons = false, hideWaitingList = false, enableBillingInfoAutoCreate = true, isButtonScrollable = false, sortBySoldOut = false, disableCountdownLeadingZero = false, isLoggedIn = false, actionsSectionComponent: ActionsSectionComponent, ticketsHeaderComponent, hideTicketsHeader = false, tableTicketsHeaderComponent, hideTableTicketsHeader = false, enableInfluencersSection = true, enableAddOns = true, handleNotInvitedModalClose = _identity, handleInvalidLinkModalClose = _identity, ordersPath, showPoweredByImage = false, promoText, showGroupNameBlock = false, currencySymbol = '', onReserveButtonClick = _identity, onPendingVerification = _identity, showAlertIcons = true, onPreRegistrationSuccess = _identity, onPreRegistrationError = _identity, onPreRegistrationGetCountriesSuccess = _identity, onPreRegistrationGetCountriesError = _identity, onForgotPasswordSuccess = _identity, onForgotPasswordError = _identity, showForgotPasswordButton = false, logo, }: IGetTickets) => { const [selectedTickets, setSelectedTickets] = useState({} as ISelectedTickets) const [isLogged, setIsLogged] = useState(Boolean(getCookieByName(X_TF_ECOMMERCE))) const [showLoginModal, setShowLoginModal] = useState(false) const [tickets, setTickets] = useState([] as ITicketData[]) const [event, setEvent] = useState(null) const [showWaitingList, setShowWaitingList] = useState(false) const [isLoading, setIsLoading] = useState(true) const [codeIsLoading, setCodeIsLoading] = useState(false) const [handleBookIsLoading, setHandleBookIsLoading] = useState(false) const [code, setCode] = useState(getQueryVariable('r') || queryPromoCode) const [showPromoInput, setShowPromoInput] = useState(false) const [codeIsApplied, setCodeIsApplied] = useState(false) const [codeIsInvalid, setCodeIsInvalid] = useState(false) const [showAccessCodeSection, setShowAccessCodeSection] = useState(isAccessCodeEnabled) const [showPromoCodeSection, setShowPromoCodeSection] = useState(isPromotionsEnabled) const [error, setError] = useState(null) const [isNotInvitedError, setIsNotInvitedError] = useState('') const [isInvalidLinkError, setIsInvalidLinkError] = useState('') const [pendingVerificationMessage, setPendingVerificationMessage] = useState() const [preregisteredUsers, setPreregisteredUsers] = useState( [] as Array ) const [ticketsNotAvailableModalOpen, setTicketsNotAvailableModalOpen] = useState(false) const [isPreregistered, setIsPreregistered] = useState( checkUserPreregistration(preregisteredUsers) ) const ticketsContainerRef = useRef(null) const pageUrl = isBrowser ? window.location.href.split('?')[0] : '' const isTimeSlotEvent = event?.isTimeSlotEvent const [availableDates, setAvailableDates] = useState([]) const [selectedDate, setSelectedDate] = useState(null) const [timeSlotGroups, setTimeSlotGroups] = useState<{ [key: string]: any[] }>({}) useCookieListener(X_TF_ECOMMERCE, value => setIsLogged(Boolean(value))) useLocalStorageListener('user_data', (user: any) => { const isPreregisteredUser = _some( preregisteredUsers, item => item.email === user?.email ) setIsPreregistered(isPreregisteredUser) }) usePixel(eventId, { pageUrl }) useEffect(() => { window.localStorage.removeItem('extraData') }, []) useEffect(() => { if (eventId) { getTicketsApi() fetchPreregisteredData() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [eventId]) useEffect(() => { setIsPreregistered(checkUserPreregistration(preregisteredUsers)) }, [preregisteredUsers]) useEffect(() => { if (isLogged) { fetchUserData() .then(res => { window.localStorage.setItem('user_data', JSON.stringify(res)) onGetProfileDataSuccess({ data: { attributes: res } }) }) .catch(e => { if (axios.isAxiosError(e)) { onGetProfileDataError(e) } }) } }, [isLogged]) const handleLogout = () => { logoutUser({ onLogoutError, onLogoutSuccess, setIsLogged }) } const handleExternalLogin = () => { setIsLogged(true) } const handleOnClose = () => { setShowLoginModal(false) } const handleOnLogin = () => { setShowLoginModal(false) setIsLogged(true) if (onLoginSuccess) { onLoginSuccess() } } const getTimeSlots = async (date: string | null, type?: string): Promise => { if (!date) { setShowPromoCodeSection(false) return } try { const timeSlotsResponse = await getTimeSlotsByDate( eventId, moment(date).format('YYYY-MM-DD'), code ) const groupedTickets: any = {} _map(timeSlotsResponse.data.attributes.timeslots, (timeslots, date) => { const dateKeyChunks = date.split('-') const dateKey = [dateKeyChunks[0], dateKeyChunks[1], dateKeyChunks[2]].join('-') _forEach(timeslots, ticketList => { if (!groupedTickets[dateKey]) { groupedTickets[dateKey] = [] } groupedTickets[dateKey] = groupedTickets[dateKey].concat(ticketList) }) }) setTimeSlotGroups(groupedTickets) setShowPromoCodeSection(true) const attributes: TimeSlotsAttributes = timeSlotsResponse?.data?.attributes if (type === 'promo') { setCodeIsApplied(attributes.ValidPromoCode) setCodeIsInvalid(!attributes.ValidPromoCode) setCode('') window.localStorage.setItem('appliedPromoCode', code) setShowAccessCodeSection(attributes.is_access_code) setShowPromoCodeSection(attributes.isPromotionsEnabled) } } catch (error) {} } async function getTicketsApi(isUpdatingCode?: boolean, type?: string) { try { if (isUpdatingCode) { setCodeIsLoading(true) } else { setIsLoading(true) } const previewKey = getQueryVariable('pk') || undefined const eventResponse = await getEvent(eventId, previewKey) if (eventResponse.success) { const event = eventResponse.data.attributes setEvent(event) if (event.country && isBrowser) { window.localStorage.setItem('eventCountry', event.country) } if (event.isTimeSlotEvent) { getTimeSlots(selectedDate, code) // For timeslots we shouldn't fetch all tickets at once setAvailableDates(event?.timeslotSettings?.availableDates ?? []) return } } const response = await getTickets(eventId, code, previewKey) if (response.success) { const attributes = response?.data?.attributes type === 'promo' && setCodeIsApplied(attributes.ValidPromoCode) type === 'promo' && setCodeIsInvalid(!attributes.ValidPromoCode) setTickets(attributes.tickets) setShowWaitingList(attributes.showWaitingList) onGetTicketsSuccess({ data: { attributes: { tickets: response.data.attributes.tickets } }, }) setCode('') window.localStorage.setItem('appliedPromoCode', code) setShowAccessCodeSection(attributes.is_access_code) setShowPromoCodeSection(attributes.isPromotionsEnabled) } } catch (e) { if (axios.isAxiosError(e)) { onGetTicketsError(e) setError(_get(e, 'response.data.message', '')) window.localStorage.removeItem('appliedPromoCode') } } finally { setIsLoading(false) setCodeIsLoading(false) } } async function fetchPreregisteredData() { try { const preregistrationData = await getPreRegistrationInfluencers({ eventId, }) setPreregisteredUsers(preregistrationData.data.attributes.influencers) } catch (error) { if (axios.isAxiosError(error)) { setError(_get(error, 'response.data.message', '')) } } } const handleTicketSelect = (key: string, value: number | string, isTable = false) => { localStorage.setItem('selectedTicketsQuantity', value.toString()) setSelectedTickets(prevState => { // Allow multiple ticket types to be selected simultaneously when flag is enabled if (event?.allowMultipleTicketTypePurchases === true) { // Check if we're switching between tables and regular tickets const hasExistingSelection = Object.keys(prevState).some(k => k !== 'isTable') const switchingTicketType = hasExistingSelection && prevState.isTable !== isTable // If switching from tables to regular tickets or vice versa, clear all selections if (switchingTicketType && Number(value) > 0) { return { [key]: value, isTable, } } // If value is 0, remove this ticket from selection if (!value || Number(value) === 0) { const newState = { ...prevState } delete newState[key] // If no ticket keys remain (only isTable left), return empty state const ticketKeys = Object.keys(newState).filter(k => k !== 'isTable') if (ticketKeys.length === 0) { return { isTable: false } as ISelectedTickets } return { ...newState, isTable: prevState.isTable } } // If value > 0, add or update this ticket while keeping others of the same type selected return { ...prevState, [key]: value, isTable, } } // Default behavior: only one ticket type at a time if (Object.keys(prevState)[0] !== key && !value) { return prevState } return { [key]: value, isTable, } }) } const handleOrdersClick = () => { if (isBrowser) { window.location.href = ordersPath ?? '/orders' } } const onErrorClose = () => { setError(null) } const handleBook = async () => { const timeSlotTickets = _flatten(_map(timeSlotGroups, slots => slots)) setHandleBookIsLoading(true) // Unified flow: works for both single and multiple ticket types const ticketsList = event?.isTimeSlotEvent ? timeSlotTickets : tickets // Get all selected ticket IDs with quantity > 0 (excluding 'isTable' key) const selectedTicketIds = Object.keys(selectedTickets).filter( key => key !== 'isTable' && Number(selectedTickets[key]) > 0 ) // Build ticket_types object with all selected tickets (works for 1 or N tickets) const ticketTypesData: any = {} let totalProductCartQuantity = 0 let firstTicket: ITicket | null = null selectedTicketIds.forEach(ticketId => { const ticket = _find(ticketsList || [], item => String(item.id) === ticketId) as ITicket if (ticket) { if (!firstTicket) firstTicket = ticket const optionName = _get(ticket, 'optionName') const quantity = +selectedTickets[ticketId] totalProductCartQuantity += quantity ticketTypesData[ticketId] = { product_options: { [optionName]: ticketId, ticket_price: ticket.price, }, quantity, } } }) if (!firstTicket) { setHandleBookIsLoading(false) return } const firstOptionName = _get(firstTicket, 'optionName') const firstTicketId = _get(firstTicket, 'id') const data: ICartRequestData = { attributes: { alternative_view_id: null, product_cart_quantity: totalProductCartQuantity, product_options: { [firstOptionName]: firstTicketId, }, product_id: eventId, ticket_types: ticketTypesData, }, } try { onGetTicketsPress() const result = await addToCart(eventId, data) const pageConfigsDataResponse = enableAddOns ? await getCheckoutPageConfigs() : { status: 200, data: { attributes: _get(result, 'data.attributes') }, } if (pageConfigsDataResponse.status === 200) { const pageConfigsData: any = _get(pageConfigsDataResponse, 'data.attributes') || {} const skipBillingPage = pageConfigsData.skip_billing_page ?? false const hasAddOn = pageConfigsData.has_add_on ?? false const freeTicket = pageConfigsData.free_ticket ?? false let hash: string | number | undefined = '' let total: string | number | undefined = '' isBrowser && window.localStorage.removeItem('add_ons') isBrowser && window.localStorage.removeItem('checkoutAdditionalConfigs') if (skipBillingPage && !hasAddOn) { // Get user data for checkout data const userData = isBrowser && window.localStorage.getItem('user_data') ? JSON.parse(window.localStorage.getItem('user_data') || '{}') : {} const checkoutBody = createCheckoutDataBodyWithDefaultHolder( totalProductCartQuantity, userData ) const checkoutResponse = enableBillingInfoAutoCreate ? await postOnCheckout(checkoutBody, freeTicket) : null hash = checkoutResponse?.data?.attributes?.hash || '' total = checkoutResponse?.data?.attributes?.total || '' } onAddToCartSuccess({ skip_billing_page: skipBillingPage, event_id: String(eventId), hash, total, hasAddOn, eventSlug: event?.slug, cart: pageConfigsDataResponse.data.attributes.cart, currencySymbol: event?.currency.symbol, }) } } catch (e) { const errorResponse: any = _get(e, 'response', {}) if (errorResponse?.data?.data?.hasUnverifiedOrder) { setPendingVerificationMessage(errorResponse?.data?.message) } else if (axios.isAxiosError(e)) { onAddToCartError(e) const message = _get(e, 'response.data.message', '') const isInvalidLinkError = _includes( message, 'No more of this ticket type are available right now' ) const isNotInvitedError = _includes( message, 'You must have been invited to this event to attend' ) if (isInvalidLinkError) { setIsInvalidLinkError(message) } else if (isNotInvitedError) { setIsNotInvitedError(message) } else { setError(message) } } } finally { setHandleBookIsLoading(false) } } const updateTickets = (isUpdatingCode?: boolean, type?: string) => { getTicketsApi(isUpdatingCode, type) } const fetchUserData = async () => { const userDataResponse = await getProfileData() const profileData = _get(userDataResponse, 'data') const profileDataObj = setLoggedUserData(profileData) return profileDataObj } const isTicketOnSale = event?.isTimeSlotEvent ? true : _some( tickets, item => (item.salesStarted || event?.presalesStarted) && !item.salesEnded && !item.soldOut ) const eventHasTickets = !_isEmpty(tickets) const isSalesClosed = event?.salesEnded const themeMui = createTheme(themeOptions) useEffect(() => { window.document.addEventListener('custom-login', handleExternalLogin) window.document.addEventListener('custom-logout', handleLogout) return () => { window.document.removeEventListener('custom-login', handleExternalLogin) window.document.removeEventListener('custom-logout', handleLogout) } }, []) const handleGetTicketClick = () => { if ( !handleBookIsLoading && !_isEmpty(selectedTickets) && Number(Object.values(selectedTickets)[0]) > 0 ) { handleBook() } else { if (isButtonScrollable && ticketsContainerRef && ticketsContainerRef.current) { ticketsContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest', }) } } } const handleNotAvailableTicketsClick = () => { if (!_isEmpty(selectedTickets)) { setTicketsNotAvailableModalOpen(true) } } const bookButtonIsDisabled = (handleBookIsLoading || _isEmpty(selectedTickets) || Object.values(selectedTickets)[0] === 0) && !event?.flagSeatMapAllowed const isTicketAvailable = _some( tickets, ticket => ticket.displayTicket && !ticket.soldOut && (ticket.salesStarted || (event?.presalesStarted && !event?.presalesEnded)) ) const wrappedActionsSectionComponent = React.isValidElement(ActionsSectionComponent) ? React.cloneElement(ActionsSectionComponent as React.ReactElement, { handleGetTicketClick, isTicketOnSale, isTicketAvailable, }) : null const externalUrl = event?.redirectUrl const eventSaleIsNotStarted = !event?.salesStarted && event?.salesStart && !isTicketAvailable const influencers = event?.referralsEnabled ? event?.referrals : [] const canShowGetTicketBtn = () => { if ( !wrappedActionsSectionComponent && !eventSaleIsNotStarted && isTicketOnSale && !event?.salesEnded && !externalUrl ) { return true } return false } const onClose = (value: string) => { if (value === 'notInvited') { handleNotInvitedModalClose() } else if (value === 'invalidLink') { handleInvalidLinkModalClose() } setIsNotInvitedError('') setIsInvalidLinkError('') } const hideTopInfluencers = event?.hideTopInfluencers const isSeatMapAllowed = _get(event, 'seatMapAllowed', false) const isTableMapEnabled = _get(event, 'tableMapEnabled', false) const notAvailableTicketsMessage = `Tickets for this event are not available until ${moment( event?.salesStart ).format( 'dddd, DD MMMM YYYY' )}. \n If you pre-registered, log into your account first to buy your pre-sale ticket.` const tableTickets = _filter(tickets, (ticket: any) => ticket.isTable) const ordinaryTickets = {} as ITicket _forEach(tickets, (ticket: any, key: string) => { if (!ticket.isTable) { ordinaryTickets[key] = ticket } }) // Check if event is in pre-registration mode const isPreRegistrationMode = event?.preregEnabled && event?.preregStarted && !event?.preregEnded return ( {!isLoading && }
{isInvalidLinkError && ( onClose('invalidLink')} onConfirm={() => onClose('invalidLink')} /> )} {isNotInvitedError && ( onClose('notInvited')} onConfirm={() => onClose('notInvited')} // loading={removeFromResaleLoading} /> )} {error && ( {error} )} {ticketsNotAvailableModalOpen && ( onClose('invalidLink')} onConfirm={() => setTicketsNotAvailableModalOpen(false)} /> )} {isLoading ? ( ) : isPreRegistrationMode ? ( {}} onLogin={onLoginSuccess} onLoginButtonClick={() => {}} onLoginSuccess={onLoginSuccess} onConfirmationSuccess={onPreRegistrationSuccess} onConfirmationError={onPreRegistrationError} onGetCountriesSuccess={onPreRegistrationGetCountriesSuccess} onGetCountriesError={onPreRegistrationGetCountriesError} onForgotPasswordSuccess={onForgotPasswordSuccess} onForgotPasswordError={onForgotPasswordError} themeOptions={themeOptions} /> ) : (
{isTimeSlotEvent && ( )} {!isSalesClosed && !isTimeSlotEvent && ( )} {externalUrl ? null : isSalesClosed ? (

Sales for this event are closed.

) : eventSaleIsNotStarted ? ( ) : null} {event?.enableWaitingList && showWaitingList && event.salesStarted && !hideWaitingList && ( )} {codeIsLoading ? ( ) : isSalesClosed ? null : showAccessCodeSection ? ( ) : showPromoCodeSection ? ( ) : null} {wrappedActionsSectionComponent} {canShowGetTicketBtn() && ( )} {isSeatMapAllowed && !event?.salesEnded && isTicketAvailable && ( )} {isLogged && !hideSessionButtons ? (
) : ( '' )}
)} {showLoginModal ? ( ) : null}
{showPoweredByImage ? : null} {enableInfluencersSection && !hideTopInfluencers && influencers.length ? (

TOP INFLUENCERS

    {influencers.map((influencer: IInfluencer, i: number) => (
  1. {`${influencer.firstName} ${influencer.lastName?.charAt(0)}`}{' '}
  2. ))}
) : null} { onPendingVerification() }} />
) }