import { Alert } from '@mui/material' import axios from 'axios' import _find from 'lodash/find' import _forEach from 'lodash/forEach' import _get from 'lodash/get' import _identity from 'lodash/identity' import _isEmpty from 'lodash/isEmpty' import _isEqual from 'lodash/isEqual' import _keys from 'lodash/keys' import _map from 'lodash/map' import _values from 'lodash/values' import moment from 'moment' import React, { useCallback, useEffect, useRef, useState } from 'react' import Countdown, { CountdownRenderProps } from 'react-countdown' import { getSeatMapData, getSeatMapStatuses, removeSeatReserve, reserveSeat, } from '../../api' import { useOnline } from '../../hooks/useOnline' import { showZero } from '../../utils/showZero' import { ReferralLogic } from '../ticketsContainer/ReferralLogic' import { addToCartFunc } from './addToCart' import { SeatMapComponent } from './SeatMapComponent' import { TicketsSection } from './TicketsSection' import { getAddToCartRequestData, getOwnReservationsBasedOnStatuses, getTicketDropdownData, getTierIdBasedOnSeatId, } from './utils' const OFFLINE_MESSAGE = 'Offline.' interface IGuestCounts { [key: string]: number; } export const SeatMapContainer = (props: IMapContainerProps) => { const { event: { id: eventId, currency: { symbol }, tableMapEnabled, country, }, mapContainerId, timerMessage = '', onAddToCartSuccess = _identity, onCountdownFinish = _identity, ticketDeleteButtonContent, ticketInfoContent, } = props const [seatMapData, setSeatMapData] = useState({ seatMap: '', } as { seatMap: string; seatReservationTime: number; seatMapType: string | null; tierPrices: any; predefinedSeats: any; }) const eventSeatsRef = useRef([] as EventSeat[]) const ticketTypeTierRelationsRef = useRef({} as TicketTypeTierRelations) const [selectedTickets, setSelectedTickets] = useState<{ [seatId: string]: string; }>({}) const [seatMapStatuses, setSeatMapStatuses] = useState('') const [reservedSeats, setReservedSeats] = useState>([]) const [isLoadingSeatMapData, setIsLoadingSeatMapData] = useState(true) const [isLoadingStatuses, setIsLoadingStatuses] = useState(false) const [isReserving, setIsReserving] = useState(false) const [isAddingToCart, setIsAddingToCart] = useState(false) const [error, setError] = useState(null) const [showTimer, setShowTimer] = useState( Date.now() <= Number(localStorage.getItem(`reservationStart-${eventId}`)) ) const [guestCounts, setGuestCounts] = useState({} as IGuestCounts) const [isOnline, setIsOnline] = useState(true) const isGuestCountsSet = useRef(false) useOnline(setIsOnline) const updateGuestCounts = (data: any) => { _forEach(data, (item: any) => { const tierTickets = ticketTypeTierRelationsRef.current[item.tierId] const seatTicketsArray = _values(tierTickets) setGuestCounts((prevState: any) => ({ [item.seatId]: Number(seatTicketsArray[0].ticket_type_min_number_of_guests), ...prevState, })) }) } const fetchSeatMapData = useCallback(async () => { try { const seatMapDataResponse = await getSeatMapData(eventId) const { data: { attributes: { seatData, ticketTypeTierRelations, eventSeats, seatReservationTime, seatMapType, tierPrices, predefinedSeats, }, }, } = seatMapDataResponse eventSeatsRef.current = eventSeats ticketTypeTierRelationsRef.current = ticketTypeTierRelations setSeatMapData({ seatMap: JSON.parse(seatData), seatReservationTime, seatMapType, tierPrices, predefinedSeats, }) } catch (error) { setError('Something went wrong') } finally { setIsLoadingSeatMapData(false) } }, [eventId]) const fetchSeatMapReservations = useCallback(async () => { if (isOnline) { try { const statusesResponse = await getSeatMapStatuses(eventId) const statuses = _get(statusesResponse, 'data.attributes') || {} const reservationData: Array = [] const ownReservations: string[] = getOwnReservationsBasedOnStatuses(statuses) _forEach(ownReservations, reservation => { const tierIdOfReservation = getTierIdBasedOnSeatId( reservation, eventSeatsRef.current ) as string reservationData.push({ seatId: reservation, tierId: tierIdOfReservation, type: 'reserve', }) }) localStorage.setItem('reservationData', JSON.stringify(reservationData)) if (!_isEqual(seatMapStatuses, statusesResponse.data.attributes)) { setSeatMapStatuses(statusesResponse.data.attributes) } if (!_isEqual(reservationData, reservedSeats)) { setReservedSeats(reservationData) } // automatically set ticket/table type if it's the only one if ( ticketTypeTierRelationsRef.current && !_isEqual(reservationData, reservedSeats) ) { if (!isGuestCountsSet.current) { updateGuestCounts(reservationData) isGuestCountsSet.current = true } _forEach(reservationData, (item: any) => { const tierTickets = ticketTypeTierRelationsRef.current[item.tierId] const seatTicketsArray = _values(tierTickets) setSelectedTickets((prevState: any) => ({ ...prevState, [item.seatId]: seatTicketsArray.length === 1 ? seatTicketsArray[0].ticket_type_id : '', })) }) if (_isEmpty(reservationData)) { setGuestCounts({}) setSelectedTickets({}) } } } catch (error) { setError('Something went wrong') } } }, [eventId, seatMapStatuses, reservedSeats, isOnline]) const startTimer = useCallback( (duration: number) => { setShowTimer(true) if (!localStorage.getItem(`reservationStart-${eventId}`)) { localStorage.setItem(`reservationStart-${eventId}`, String(Date.now() + duration)) } }, [eventId] ) const endTimer = useCallback(() => { localStorage.removeItem(`reservationStart-${eventId}`) setShowTimer(false) }, [eventId]) const handleSeatReservation = async ( eventId: string, tierId: string, seatId: string ) => { setIsReserving(true) try { await reserveSeat(eventId, tierId, seatId) await fetchSeatMapReservations() startTimer(seatMapData.seatReservationTime * 60000) const reservationData = JSON.parse(localStorage.getItem('reservationData') || '') setReservedSeats(reservationData) updateGuestCounts(reservationData) // automatically set ticket/table type if it's the only one const relations = _keys(ticketTypeTierRelationsRef.current[tierId]) const [firstItem] = _keys(ticketTypeTierRelationsRef.current[tierId]) handleTicketSelect(relations.length === 1 ? firstItem : '', seatId) } catch (error) { setError( error?.response?.data?.message === 'Selected seat is not available' ? // eslint-disable-next-line max-len 'No more of this ticket type are available right now - they’re either sold out or in people’s shopping carts. Try refreshing the page!' : 'Something went wrong' ) } finally { setIsReserving(false) } } const handleCancelSeatReservtion = async ( eventId: string, tierId: string, seatId: string ) => { setIsReserving(true) try { await removeSeatReserve(eventId, tierId, [seatId]) await fetchSeatMapReservations() if (_isEmpty(JSON.parse(localStorage.getItem('reservationData') as string))) { endTimer() } const reservationData = JSON.parse(localStorage.getItem('reservationData') || '') const currentSelectedTickets = { ...selectedTickets } delete currentSelectedTickets[seatId] setReservedSeats(reservationData) setSelectedTickets(currentSelectedTickets) } catch (error) { setError('Something went wrong') } finally { setIsReserving(false) } } const onSeatClick = async (seatInfo: { seat: { tierId: string; seatId: string; status: string; }; }) => { const { seat: { tierId, seatId, status }, } = seatInfo const reservationData = JSON.parse(localStorage.getItem('reservationData') || '[]') if (status === 'B' || status === 'R' || status === 'BLOCKED' || status === 'RE') return if (_find(reservationData, data => data.seatId === seatId)) { handleCancelSeatReservtion(eventId, tierId, seatId) } else if (reservationData.length >= 10) { setError('Limit exceeded') } else { handleSeatReservation(eventId, tierId, seatId) } } const handleTicketSelect = (ticketId: string, seatId: string) => { const currentSelectedTickets = { ...selectedTickets } currentSelectedTickets[seatId] = ticketId setSelectedTickets(currentSelectedTickets) } const handleGetTicketBtnClick = async () => { setIsAddingToCart(true) const ticketsDropdownsData = getTicketDropdownData( reservedSeats, ticketTypeTierRelationsRef.current ) let selectedSeats = {} _forEach(ticketsDropdownsData, ticketData => { selectedSeats = { ...selectedSeats, [ticketData.seatId]: ticketData.ticketsData, } }) const addToCartData = getAddToCartRequestData({ eventId, reservations: _map(reservedSeats, seat => seat.seatId), selectedSeats, selectedTickets, guestCounts, }) try { const response = await addToCartFunc({ eventId, data: addToCartData, ticketQuantity: addToCartData?.attributes?.product_cart_quantity, }) localStorage.removeItem('reservationData') onAddToCartSuccess(response) } catch (error) { if (axios.isAxiosError(error)) { const message = _get(error, 'response.data.message', '') setError(message) } } finally { setIsAddingToCart(false) } } useEffect(() => { if (Date.now() > Number(localStorage.getItem(`reservationStart-${eventId}`))) { setSelectedTickets({}) setReservedSeats([]) } const makeRequests = async () => { try { setIsLoadingStatuses(true) await fetchSeatMapData() await fetchSeatMapReservations() } catch (error) { setError('Something went wrong') } finally { setIsLoadingStatuses(false) } } makeRequests() if (country) { localStorage.setItem('eventCountry', country) } }, [country, eventId, fetchSeatMapData]) useEffect(() => { const intervalId = setInterval(() => { fetchSeatMapReservations() }, 3000) return () => { clearInterval(intervalId) } }, [fetchSeatMapReservations]) useEffect(() => { if (!isOnline) { setError(OFFLINE_MESSAGE) } else if (isOnline && error === OFFLINE_MESSAGE) { setError(null) } }, [error, isOnline]) return ( <> {error && ( { setError(null) }} variant="filled" style={{ width: '350px' }} > {error} )} {showTimer && ( (
{timerMessage} {showZero(props.minutes)}:{showZero(props.seconds)}
)} onComplete={() => { endTimer() setSelectedTickets({}) setReservedSeats([]) if (onCountdownFinish) { onCountdownFinish() } }} /> )} {!isLoadingSeatMapData && !isLoadingStatuses && } handleCancelSeatReservtion(eventId, tireId, seatId) } handleGetTicketClick={handleGetTicketBtnClick} isAddingToCart={isAddingToCart} reservedSeats={reservedSeats} ticketDeleteButtonContent={ticketDeleteButtonContent} ticketInfoContent={ticketInfoContent} ticketTypeTierRelations={ticketTypeTierRelationsRef.current} currencySymbol={symbol} tableMapEnabled={Boolean(tableMapEnabled)} guestCounts={guestCounts} setGuestCounts={setGuestCounts} /> ) }