tf-checkout-react 1.7.2 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/api/auth.d.ts +22 -0
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/components/confirmationContainer/index.d.ts +5 -1
  4. package/dist/components/loginForm/index.d.ts +1 -0
  5. package/dist/components/loginModal/SignUpForm.d.ts +10 -0
  6. package/dist/components/loginModal/constants.d.ts +39 -0
  7. package/dist/components/loginModal/index.d.ts +1 -0
  8. package/dist/components/seatMapContainer/addToCart.d.ts +2 -2
  9. package/dist/components/ticketsContainer/TimeSlotTicketRow.d.ts +19 -0
  10. package/dist/tf-checkout-react.cjs.development.js +1654 -698
  11. package/dist/tf-checkout-react.cjs.development.js.map +1 -1
  12. package/dist/tf-checkout-react.cjs.production.min.js +1 -1
  13. package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
  14. package/dist/tf-checkout-react.esm.js +1655 -699
  15. package/dist/tf-checkout-react.esm.js.map +1 -1
  16. package/dist/tf-checkout-styles.css +1 -1
  17. package/dist/validators/index.d.ts +4 -0
  18. package/package.json +2 -2
  19. package/src/api/auth.ts +49 -0
  20. package/src/api/index.ts +1 -1
  21. package/src/api/publicRequest.ts +1 -0
  22. package/src/components/billing-info-container/index.tsx +128 -32
  23. package/src/components/billing-info-container/style.css +46 -2
  24. package/src/components/confirmationContainer/index.tsx +24 -3
  25. package/src/components/loginForm/index.tsx +19 -3
  26. package/src/components/loginModal/SignUpForm.tsx +329 -0
  27. package/src/components/loginModal/constants.ts +46 -0
  28. package/src/components/loginModal/index.tsx +86 -9
  29. package/src/components/loginModal/style.css +44 -2
  30. package/src/components/preRegistration/constants.tsx +6 -4
  31. package/src/components/preRegistration/index.tsx +3 -3
  32. package/src/components/preRegistration/utils.ts +9 -1
  33. package/src/components/ticketsContainer/TimeSlotTicketRow.tsx +224 -0
  34. package/src/components/ticketsContainer/TimeSlotsSection.tsx +98 -24
  35. package/src/components/ticketsContainer/index.tsx +79 -21
  36. package/src/types/api/common.d.ts +1 -0
  37. package/src/types/api/payment.d.ts +2 -0
  38. package/src/types/formFields.d.ts +1 -1
  39. package/src/validators/index.ts +22 -1
@@ -0,0 +1,224 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import './style.css'
3
+
4
+ import Box from '@mui/material/Box'
5
+ import FormControl from '@mui/material/FormControl'
6
+ import MenuItem from '@mui/material/MenuItem'
7
+ import Select from '@mui/material/Select'
8
+ import moment from 'moment-timezone'
9
+ import React, { useState } from 'react'
10
+ import { Tooltip } from 'react-tooltip'
11
+
12
+ import { FEES_STYLES } from '../../constants'
13
+ import { CONFIGS } from '../../utils'
14
+ import InfoIcon from './InfoIcon'
15
+ import { getTicketSelectOptions } from './utils'
16
+
17
+ function decodeHTML(html: string): string {
18
+ const textArea = document.createElement('textarea')
19
+ textArea.innerHTML = html
20
+ return textArea.value
21
+ }
22
+
23
+ interface TimeSlotOption {
24
+ timeKey: string;
25
+ ticketInstance: any;
26
+ }
27
+
28
+ interface ITimeSlotTicketRowProps {
29
+ ticketKey: string;
30
+ ticket: any;
31
+ availableTimeSlots: TimeSlotOption[];
32
+ selectedTickets: any;
33
+ selectedTimeSlots: any;
34
+ handleTicketSelect: (ticketKey: string, quantity: number, ticketInstance: any) => void;
35
+ handleTimeSlotSelect: (ticketKey: string, timeKey: string, ticketInstance: any) => void;
36
+ priceSymbol: string;
37
+ isSoldOut: boolean;
38
+ }
39
+
40
+ export const TimeSlotTicketRow = ({
41
+ ticketKey,
42
+ ticket,
43
+ availableTimeSlots,
44
+ selectedTickets,
45
+ selectedTimeSlots,
46
+ handleTicketSelect,
47
+ handleTimeSlotSelect,
48
+ priceSymbol,
49
+ isSoldOut,
50
+ }: ITimeSlotTicketRowProps) => {
51
+ const [visibleDescription, setVisibleDescription] = useState<boolean>(false)
52
+
53
+ const currentSelectedTimeKey = selectedTimeSlots[ticketKey] || ''
54
+ const currentTicketInstance = availableTimeSlots.find(slot => slot.timeKey === currentSelectedTimeKey)?.ticketInstance
55
+ const currentSelectedQuantity = currentTicketInstance ? (selectedTickets[currentTicketInstance.id] || 0) : 0
56
+
57
+ const maxCount = ticket.maxQuantity
58
+ const minCount = ticket.minQuantity
59
+ const { multiplier } = ticket
60
+ const options = getTicketSelectOptions(maxCount, minCount, multiplier)
61
+
62
+ const ticketPriceWithoutFees = `${priceSymbol} ${(+ticket.cost).toFixed(2)}`
63
+ const ticketPriceWithFees = `${priceSymbol} ${(+ticket.basePrice).toFixed(2)}`
64
+ const ticketOldPriceWithFees = `${priceSymbol} ${(+ticket.oldBasePrice).toFixed(2)}`
65
+ const ticketOldPriceWithoutFees = `${priceSymbol} ${(+ticket.oldCost).toFixed(2)}`
66
+
67
+ let ticketIsDiscounted = false
68
+ if (ticket.oldPrice && !isSoldOut && ticket.oldPrice !== ticket.price) {
69
+ ticketIsDiscounted = true
70
+ }
71
+
72
+ const ticketIsFree = +ticket.price === 0
73
+ const discountTicketPriceElem =
74
+ CONFIGS.FEES_STYLE === FEES_STYLES.DISPLAY_BOTH || !ticket.feeIncluded
75
+ ? ticketOldPriceWithoutFees
76
+ : ticketOldPriceWithFees
77
+ const ticketPriceElem = isSoldOut
78
+ ? 'SOLD OUT'
79
+ : ticketIsFree
80
+ ? 'FREE'
81
+ : CONFIGS.FEES_STYLE === FEES_STYLES.DISPLAY_BOTH || !ticket.feeIncluded
82
+ ? ticketPriceWithoutFees
83
+ : ticketPriceWithFees
84
+
85
+ const handleTimeChange = (event: any) => {
86
+ const selectedTimeKey = event.target.value
87
+ const selectedOption = availableTimeSlots.find(slot => slot.timeKey === selectedTimeKey)
88
+ if (selectedOption) {
89
+ handleTimeSlotSelect(ticketKey, selectedOption.timeKey, selectedOption.ticketInstance)
90
+ }
91
+ }
92
+
93
+ const handleQuantityChange = (event: any) => {
94
+ const { value } = event.target
95
+ // Only allow quantity selection if a time slot is selected
96
+ if (!currentSelectedTimeKey && value > 0) {
97
+ return
98
+ }
99
+ if (currentTicketInstance) {
100
+ handleTicketSelect(ticketKey, value, currentTicketInstance)
101
+ }
102
+ }
103
+
104
+ const handleDescriptionToggle = () => {
105
+ setVisibleDescription(current => !current)
106
+ }
107
+
108
+ return (
109
+ <>
110
+ <div className={`event-detail__tier ${isSoldOut ? 'disabled' : ''}`} id={ticketKey}>
111
+ <div className="event-detail__tier-name">
112
+ {ticket.displayName || ticket.name}
113
+ {ticket.descriptionRich && (
114
+ <>
115
+ <span
116
+ aria-hidden
117
+ className="info-icon"
118
+ onClick={handleDescriptionToggle}
119
+ data-tooltip-id={`tooltip-${ticketKey}`}
120
+ data-tooltip-content="View ticket info"
121
+ style={{
122
+ marginLeft: 8,
123
+ cursor: 'pointer',
124
+ display: 'flex',
125
+ }}
126
+ >
127
+ <InfoIcon />
128
+ </span>
129
+
130
+ <Tooltip id={`tooltip-${ticketKey}`} place="top">
131
+ {ticket.description || 'No description available'}
132
+ </Tooltip>
133
+ </>
134
+ )}
135
+ </div>
136
+ <div className="event-tickets-container">
137
+ <div className="event-detail__tier-price">
138
+ {ticketIsDiscounted && <p className="old-price">{discountTicketPriceElem}</p>}
139
+ <p className={isSoldOut ? 'sold-out' : ''}>{ticketPriceElem}</p>
140
+ {!isSoldOut && !ticketIsFree && (
141
+ <p className="fees">
142
+ {CONFIGS.FEES_STYLE === FEES_STYLES.TRADITIONAL &&
143
+ (ticket.feeIncluded ? '(incl. Fees)' : '(excl. Fees)')}
144
+ {CONFIGS.FEES_STYLE === FEES_STYLES.DISPLAY_BOTH &&
145
+ `(${ticketPriceWithFees} with fees)`}
146
+ </p>
147
+ )}
148
+ </div>
149
+ {!isSoldOut && (
150
+ <div className="event-detail__tier-state time-slot-selectors-container">
151
+ {/* Time Slot Selector */}
152
+ <Box
153
+ sx={{
154
+ display: 'flex',
155
+ flexDirection: 'row',
156
+ alignItems: 'center',
157
+ justifyContent: 'space-between',
158
+ gap: 2,
159
+ marginTop: 2,
160
+ }}
161
+ >
162
+ <FormControl>
163
+ <Select
164
+ sx={{ borderRadius: 0, minWidth: 120 }}
165
+ value={currentSelectedTimeKey}
166
+ onChange={handleTimeChange}
167
+ displayEmpty
168
+ inputProps={{ 'aria-label': 'Select time slot' }}
169
+ MenuProps={{
170
+ PaperProps: {
171
+ className: 'get-tickets-paper',
172
+ },
173
+ }}
174
+ >
175
+ <MenuItem value="" disabled>
176
+ Time
177
+ </MenuItem>
178
+ {availableTimeSlots.map((slot, index) => (
179
+ <MenuItem key={index} value={slot.timeKey}>
180
+ {moment(slot.timeKey).format('hh:mm A')}
181
+ </MenuItem>
182
+ ))}
183
+ </Select>
184
+ </FormControl>
185
+ {/* Quantity Selector */}
186
+ <FormControl>
187
+ <Select
188
+ sx={{ borderRadius: 0, minWidth: 60 }}
189
+ value={currentSelectedQuantity}
190
+ onChange={handleQuantityChange}
191
+ displayEmpty
192
+ disabled={!currentSelectedTimeKey}
193
+ inputProps={{ 'aria-label': 'Select quantity' }}
194
+ MenuProps={{
195
+ PaperProps: {
196
+ sx: { maxHeight: 150 },
197
+ className: 'get-tickets-paper',
198
+ },
199
+ }}
200
+ >
201
+ {options.map((option, index) => (
202
+ <MenuItem key={index} value={option.value}>
203
+ {option.value}
204
+ </MenuItem>
205
+ ))}
206
+ </Select>
207
+ </FormControl>
208
+ </Box>
209
+ </div>
210
+ )}
211
+ </div>
212
+ {visibleDescription && ticket.descriptionRich && (
213
+ <div className="ticket-description">
214
+ <div
215
+ dangerouslySetInnerHTML={{
216
+ __html: decodeHTML(ticket.descriptionRich),
217
+ }}
218
+ />
219
+ </div>
220
+ )}
221
+ </div>
222
+ </>
223
+ )
224
+ }
@@ -2,12 +2,13 @@
2
2
  import { Box, CircularProgress, TextField } from '@mui/material'
3
3
  import { LocalizationProvider, StaticDatePicker as DatePicker } from '@mui/x-date-pickers'
4
4
  import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment'
5
- import _isEmpty from 'lodash/isEmpty'
5
+ import _get from 'lodash/get'
6
6
  import _map from 'lodash/map'
7
+ import _sortBy from 'lodash/sortBy'
7
8
  import moment from 'moment-timezone'
8
- import React, { ReactNode, useState } from 'react'
9
+ import React, { ReactNode, useMemo, useState } from 'react'
9
10
 
10
- import { TicketsSection } from './TicketsSection'
11
+ import { TimeSlotTicketRow } from './TimeSlotTicketRow'
11
12
 
12
13
  interface Props {
13
14
  event: any;
@@ -29,6 +30,16 @@ interface Props {
29
30
  isSeatMapAllowed?: boolean;
30
31
  }
31
32
 
33
+ interface TimeSlotOption {
34
+ timeKey: string;
35
+ ticketInstance: any;
36
+ }
37
+
38
+ interface UniqueTicket {
39
+ ticket: any;
40
+ availableTimeSlots: TimeSlotOption[];
41
+ }
42
+
32
43
  const TimeSlotsSection: React.FC<Props> = ({
33
44
  event,
34
45
  availableDates,
@@ -41,14 +52,14 @@ const TimeSlotsSection: React.FC<Props> = ({
41
52
  sortBySoldOut,
42
53
  hideTicketsHeader,
43
54
  ticketsHeaderComponent,
44
- showGroupNameBlock,
45
55
  currencySymbol,
46
- isSeatMapAllowed,
47
56
  }) => {
48
57
  const [loading, setLoading] = useState(false)
58
+ const [selectedTimeSlots, setSelectedTimeSlots] = useState<{ [key: string]: string }>({})
49
59
 
50
60
  const handleDateChange = async (date: string | null) => {
51
61
  setSelectedDate(date)
62
+ setSelectedTimeSlots({}) // Reset time slot selections when date changes
52
63
  if (date) {
53
64
  setLoading(true)
54
65
  try {
@@ -66,6 +77,67 @@ const TimeSlotsSection: React.FC<Props> = ({
66
77
  return !availableDates.includes(formattedDate)
67
78
  }
68
79
 
80
+ // Group tickets by unique ticket type using displayName + price
81
+ const uniqueTickets = useMemo(() => {
82
+ const ticketMap: { [key: string]: UniqueTicket } = {}
83
+
84
+ // Iterate through all time slots
85
+ _map(timeSlotGroups, (tickets, timeKey) => {
86
+ tickets.forEach((ticket: any) => {
87
+ // Use displayName + price as the unique identifier for ticket types
88
+ // This handles cases where multiple ticket types share the same optionName
89
+ const ticketKey = `${ticket.displayName || ticket.name}_${ticket.price}`
90
+
91
+ if (!ticketMap[ticketKey]) {
92
+ ticketMap[ticketKey] = {
93
+ ticket: { ...ticket },
94
+ availableTimeSlots: [],
95
+ }
96
+ }
97
+
98
+ // Store both the time key and the ticket instance for this slot
99
+ if (!ticketMap[ticketKey].availableTimeSlots.find((slot: any) => slot.timeKey === timeKey)) {
100
+ ticketMap[ticketKey].availableTimeSlots.push({
101
+ timeKey,
102
+ ticketInstance: ticket,
103
+ })
104
+ }
105
+ })
106
+ })
107
+
108
+ // Convert to array and sort
109
+ const ticketsArray = Object.values(ticketMap)
110
+ return sortBySoldOut
111
+ ? _sortBy(_sortBy(ticketsArray, t => t.ticket.sortOrder), t => t.ticket.soldOut)
112
+ : _sortBy(ticketsArray, t => t.ticket.sortOrder)
113
+ }, [timeSlotGroups, sortBySoldOut])
114
+
115
+ const handleTimeSlotSelect = (ticketKey: string, timeKey: string, ticketInstance: any) => {
116
+ setSelectedTimeSlots(prev => ({
117
+ ...prev,
118
+ [ticketKey]: timeKey,
119
+ }))
120
+
121
+ // Reset quantity when time slot changes
122
+ if (selectedTickets[ticketInstance.id]) {
123
+ handleTicketSelect(ticketInstance.id, 0)
124
+ }
125
+ }
126
+
127
+ const handleTicketSelectWithTimeSlot = (ticketKey: string, quantity: number, ticketInstance: any) => {
128
+ const timeSlot = selectedTimeSlots[ticketKey]
129
+ if (!timeSlot && quantity > 0) {
130
+ return // Don't allow quantity selection without time slot
131
+ }
132
+
133
+ if (ticketInstance) {
134
+ handleTicketSelect(ticketInstance.id, quantity)
135
+ }
136
+ }
137
+
138
+ const symbol = _get(event, 'currency.symbol')
139
+ const priceSymbol = currencySymbol || symbol
140
+
69
141
  return (
70
142
  <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
71
143
  <LocalizationProvider dateAdapter={AdapterMoment}>
@@ -88,29 +160,31 @@ const TimeSlotsSection: React.FC<Props> = ({
88
160
  width: '100%',
89
161
  display: 'flex',
90
162
  flexDirection: 'column',
91
- gap: 2,
92
163
  }}
93
164
  >
94
- {_map(Object.keys(timeSlotGroups).sort(), timeKey => (
95
- <div key={timeKey} className="time-slot-block">
96
- <div className="time-slot-date">{moment(timeKey).format('hh:mm A')}</div>
97
- <TicketsSection
98
- event={event}
99
- ticketsList={timeSlotGroups[timeKey]}
100
- tableTickets={[]}
165
+ {!hideTicketsHeader && ticketsHeaderComponent}
166
+ {uniqueTickets.map(({ ticket, availableTimeSlots }) => {
167
+ const ticketKey = `${ticket.displayName || ticket.name}_${ticket.price}`
168
+ const isSoldOut =
169
+ ticket.sold_out ||
170
+ !(ticket.displayTicket || ticket.slotGroupId) ||
171
+ ticket.soldOut
172
+
173
+ return (
174
+ <TimeSlotTicketRow
175
+ key={ticketKey}
176
+ ticketKey={ticketKey}
177
+ ticket={ticket}
178
+ availableTimeSlots={availableTimeSlots}
101
179
  selectedTickets={selectedTickets}
102
- handleTicketSelect={handleTicketSelect}
103
- sortBySoldOut={sortBySoldOut}
104
- ticketsHeaderComponent={ticketsHeaderComponent}
105
- tableTicketsHeaderComponent={null}
106
- hideTableTicketsHeader={true}
107
- hideTicketsHeader={hideTicketsHeader || _isEmpty(timeSlotGroups[timeKey])}
108
- showGroupNameBlock={showGroupNameBlock}
109
- currencySymbol={currencySymbol}
110
- isSeatMapAllowed={isSeatMapAllowed}
180
+ selectedTimeSlots={selectedTimeSlots}
181
+ handleTicketSelect={handleTicketSelectWithTimeSlot}
182
+ handleTimeSlotSelect={handleTimeSlotSelect}
183
+ priceSymbol={priceSymbol}
184
+ isSoldOut={isSoldOut}
111
185
  />
112
- </div>
113
- ))}
186
+ )
187
+ })}
114
188
  </Box>
115
189
  )}
116
190
  </Box>
@@ -48,8 +48,8 @@ import ConfirmModal from '../confirmModal'
48
48
  import Countdown from '../countdown'
49
49
  import { VerificationPendingModal } from '../idVerificationContainer/VerificationPendingModal'
50
50
  import { LoginModal } from '../loginModal'
51
- import WaitingList from '../waitingList'
52
51
  import { PreRegistration } from '../preRegistration'
52
+ import WaitingList from '../waitingList'
53
53
  import { AccessCodeSection } from './AccessCodeSection'
54
54
  import { PromoCodeSection } from './PromoCodeSection'
55
55
  import { ReferralLogic } from './ReferralLogic'
@@ -417,6 +417,41 @@ export const TicketsContainer = ({
417
417
  const handleTicketSelect = (key: string, value: number | string, isTable = false) => {
418
418
  localStorage.setItem('selectedTicketsQuantity', value.toString())
419
419
  setSelectedTickets(prevState => {
420
+ // Allow multiple ticket types to be selected simultaneously when flag is enabled
421
+ if (event?.allowMultipleTicketTypePurchases === true) {
422
+ // Check if we're switching between tables and regular tickets
423
+ const hasExistingSelection = Object.keys(prevState).some(k => k !== 'isTable')
424
+ const switchingTicketType = hasExistingSelection && prevState.isTable !== isTable
425
+
426
+ // If switching from tables to regular tickets or vice versa, clear all selections
427
+ if (switchingTicketType && Number(value) > 0) {
428
+ return {
429
+ [key]: value,
430
+ isTable,
431
+ }
432
+ }
433
+
434
+ // If value is 0, remove this ticket from selection
435
+ if (!value || Number(value) === 0) {
436
+ const newState = { ...prevState }
437
+ delete newState[key]
438
+ // If no ticket keys remain (only isTable left), return empty state
439
+ const ticketKeys = Object.keys(newState).filter(k => k !== 'isTable')
440
+ if (ticketKeys.length === 0) {
441
+ return { isTable: false } as ISelectedTickets
442
+ }
443
+ return { ...newState, isTable: prevState.isTable }
444
+ }
445
+
446
+ // If value > 0, add or update this ticket while keeping others of the same type selected
447
+ return {
448
+ ...prevState,
449
+ [key]: value,
450
+ isTable,
451
+ }
452
+ }
453
+
454
+ // Default behavior: only one ticket type at a time
420
455
  if (Object.keys(prevState)[0] !== key && !value) {
421
456
  return prevState
422
457
  }
@@ -441,32 +476,55 @@ export const TicketsContainer = ({
441
476
  const timeSlotTickets = _flatten(_map(timeSlotGroups, slots => slots))
442
477
 
443
478
  setHandleBookIsLoading(true)
444
- const ticket = event?.isTimeSlotEvent
445
- ? _find(timeSlotTickets || [], item => Number(selectedTickets[item.id]) > 0) ||
446
- ({} as ITicket)
447
- : _find(tickets, item => Number(selectedTickets[item.id]) > 0) || ({} as ITicket)
448
- const optionName = _get(ticket, 'optionName')
449
- const ticketId = _get(ticket, 'id')
450
- const productCartQuantity = +selectedTickets[ticket.id]
451
- const ticketQuantity = +selectedTickets[ticket.id]
479
+
480
+ // Unified flow: works for both single and multiple ticket types
481
+ const ticketsList = event?.isTimeSlotEvent ? timeSlotTickets : tickets
482
+
483
+ // Get all selected ticket IDs with quantity > 0 (excluding 'isTable' key)
484
+ const selectedTicketIds = Object.keys(selectedTickets).filter(
485
+ key => key !== 'isTable' && Number(selectedTickets[key]) > 0
486
+ )
487
+
488
+ // Build ticket_types object with all selected tickets (works for 1 or N tickets)
489
+ const ticketTypesData: any = {}
490
+ let totalProductCartQuantity = 0
491
+ let firstTicket: ITicket | null = null
492
+
493
+ selectedTicketIds.forEach(ticketId => {
494
+ const ticket = _find(ticketsList || [], item => String(item.id) === ticketId) as ITicket
495
+ if (ticket) {
496
+ if (!firstTicket) firstTicket = ticket
497
+ const optionName = _get(ticket, 'optionName')
498
+ const quantity = +selectedTickets[ticketId]
499
+ totalProductCartQuantity += quantity
500
+
501
+ ticketTypesData[ticketId] = {
502
+ product_options: {
503
+ [optionName]: ticketId,
504
+ ticket_price: ticket.price,
505
+ },
506
+ quantity,
507
+ }
508
+ }
509
+ })
510
+
511
+ if (!firstTicket) {
512
+ setHandleBookIsLoading(false)
513
+ return
514
+ }
515
+
516
+ const firstOptionName = _get(firstTicket, 'optionName')
517
+ const firstTicketId = _get(firstTicket, 'id')
452
518
 
453
519
  const data: ICartRequestData = {
454
520
  attributes: {
455
521
  alternative_view_id: null,
456
- product_cart_quantity: productCartQuantity,
522
+ product_cart_quantity: totalProductCartQuantity,
457
523
  product_options: {
458
- [optionName]: ticketId,
524
+ [firstOptionName]: firstTicketId,
459
525
  },
460
526
  product_id: eventId,
461
- ticket_types: {
462
- [ticketId]: {
463
- product_options: {
464
- [optionName]: ticketId,
465
- ticket_price: ticket.price,
466
- },
467
- quantity: ticketQuantity,
468
- },
469
- },
527
+ ticket_types: ticketTypesData,
470
528
  },
471
529
  }
472
530
 
@@ -502,7 +560,7 @@ export const TicketsContainer = ({
502
560
  : {}
503
561
 
504
562
  const checkoutBody = createCheckoutDataBodyWithDefaultHolder(
505
- ticketQuantity,
563
+ totalProductCartQuantity,
506
564
  userData
507
565
  )
508
566
 
@@ -18,6 +18,7 @@ interface IEventResponseData {
18
18
  preregEnabled: boolean;
19
19
  preregStarted: boolean;
20
20
  preregEnded: boolean;
21
+ allowMultipleTicketTypePurchases: boolean | null;
21
22
  }
22
23
 
23
24
  interface ITimeslotSettings {
@@ -151,6 +151,8 @@ interface ICheckoutCompleteData {
151
151
  event_date: string;
152
152
  event_description: string | null;
153
153
  order_total: number;
154
+ order_id: string | number;
155
+ order_hash: string;
154
156
  currency: {
155
157
  currency: string;
156
158
  decimalPlaces: number;
@@ -7,7 +7,7 @@ interface IFormField {
7
7
  required?: boolean;
8
8
  className?: string;
9
9
  options?: { [key: string]: string | number }[];
10
- onValidate?: (() => void) | null;
10
+ onValidate?: ((value: any) => string) | null;
11
11
  disableDropdown?: boolean;
12
12
  }
13
13
 
@@ -1,4 +1,4 @@
1
- const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
1
+ export const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
2
2
 
3
3
  export const combineValidators = (...validators: any) => (...value: any) => {
4
4
  for (let i = 0; i < validators.length; ++i) {
@@ -38,3 +38,24 @@ export const requiredValidator = (
38
38
 
39
39
  export const emailValidator = (email: string) =>
40
40
  !emailRegex.test(email) ? 'Please enter a valid email address' : ''
41
+
42
+ export const passwordValidator = (password: string): string => {
43
+ if (!password || password.length < 6) {
44
+ return 'The password must be at least 6 characters.'
45
+ }
46
+ return ''
47
+ }
48
+
49
+ export const confirmPasswordValidator = (confirmPassword: string, password: string): string => {
50
+ if (confirmPassword !== password) {
51
+ return 'Passwords do not match.'
52
+ }
53
+ return ''
54
+ }
55
+
56
+ export const confirmEmailValidator = (confirmEmail: string, email: string): string => {
57
+ if (confirmEmail !== email) {
58
+ return 'Emails do not match.'
59
+ }
60
+ return ''
61
+ }