tf-checkout-react 1.0.100 → 1.0.104

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 (84) hide show
  1. package/dist/components/billing-info-container/index.d.ts +3 -1
  2. package/dist/components/common/RedirectModal.d.ts +7 -0
  3. package/dist/components/common/SnackbarAlert.d.ts +13 -0
  4. package/dist/components/confirmationContainer/index.d.ts +2 -1
  5. package/dist/components/ticketsContainer/index.d.ts +2 -1
  6. package/dist/env.d.ts +1 -0
  7. package/dist/images/done.svg +3 -3
  8. package/dist/index.d.ts +1 -0
  9. package/dist/tf-checkout-react.cjs.development.js +317 -136
  10. package/dist/tf-checkout-react.cjs.development.js.map +1 -1
  11. package/dist/tf-checkout-react.cjs.production.min.js +1 -1
  12. package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
  13. package/dist/tf-checkout-react.esm.js +318 -138
  14. package/dist/tf-checkout-react.esm.js.map +1 -1
  15. package/dist/tf-checkout-styles.css +1 -1
  16. package/package.json +89 -89
  17. package/src/.DS_Store +0 -0
  18. package/src/.d.ts +2 -2
  19. package/src/api/index.ts +293 -278
  20. package/src/assets/images/done.svg +3 -3
  21. package/src/components/.DS_Store +0 -0
  22. package/src/components/billing-info-container/index.tsx +796 -777
  23. package/src/components/billing-info-container/style.css +105 -105
  24. package/src/components/billing-info-container/utils.ts +224 -224
  25. package/src/components/common/CheckboxField.tsx +41 -41
  26. package/src/components/common/CustomField.tsx +84 -84
  27. package/src/components/common/FormikPhoneNumberField.tsx +51 -51
  28. package/src/components/common/Loader.tsx +9 -9
  29. package/src/components/common/RadioField.tsx +35 -35
  30. package/src/components/common/RedirectModal.tsx +43 -0
  31. package/src/components/common/SelectField.tsx +80 -80
  32. package/src/components/common/SnackbarAlert.tsx +54 -0
  33. package/src/components/common/index.tsx +4 -4
  34. package/src/components/confirmModal/index.tsx +51 -51
  35. package/src/components/confirmModal/style.css +21 -21
  36. package/src/components/confirmationContainer/config.ts +72 -72
  37. package/src/components/confirmationContainer/index.tsx +197 -194
  38. package/src/components/confirmationContainer/social-buttons.tsx +94 -94
  39. package/src/components/confirmationContainer/style.css +202 -202
  40. package/src/components/countdown/index.tsx +89 -89
  41. package/src/components/countdown/style.css +9 -9
  42. package/src/components/index.ts +7 -7
  43. package/src/components/loginModal/index.tsx +209 -209
  44. package/src/components/loginModal/style.css +71 -71
  45. package/src/components/myTicketsContainer/index.tsx +196 -137
  46. package/src/components/myTicketsContainer/row.tsx +41 -41
  47. package/src/components/myTicketsContainer/style.css +39 -39
  48. package/src/components/myTicketsContainer/tableConfig.tsx +34 -34
  49. package/src/components/orderDetailsContainer/index.tsx +249 -249
  50. package/src/components/orderDetailsContainer/style.css +72 -72
  51. package/src/components/orderDetailsContainer/ticketsTable.tsx +124 -124
  52. package/src/components/paymentContainer/index.tsx +284 -284
  53. package/src/components/registerModal/index.tsx +190 -190
  54. package/src/components/stripePayment/index.tsx +253 -253
  55. package/src/components/stripePayment/style.css +59 -59
  56. package/src/components/ticketResale/index.tsx +56 -56
  57. package/src/components/ticketResaleModal/index.tsx +210 -210
  58. package/src/components/ticketResaleModal/style.css +28 -28
  59. package/src/components/ticketsContainer/PromoCodeSection.tsx +99 -99
  60. package/src/components/ticketsContainer/ReferralLogic.tsx +33 -33
  61. package/src/components/ticketsContainer/TicketRow.tsx +83 -83
  62. package/src/components/ticketsContainer/TicketsSection.tsx +81 -81
  63. package/src/components/ticketsContainer/index.tsx +427 -409
  64. package/src/components/ticketsContainer/style.css +181 -181
  65. package/src/components/ticketsContainer/utils.ts +11 -11
  66. package/src/components/timerWidget/index.tsx +70 -70
  67. package/src/components/timerWidget/style.css +26 -26
  68. package/src/components/waitingList/index.tsx +178 -178
  69. package/src/components/waitingList/style.css +26 -26
  70. package/src/env.ts +20 -19
  71. package/src/index.ts +14 -13
  72. package/src/normalizers/index.ts +45 -45
  73. package/src/types/billing-info-data.ts +37 -37
  74. package/src/types/payment-field.ts +7 -7
  75. package/src/types/referral-promotion.ts +7 -7
  76. package/src/utils/createCheckoutDataBodyWithDefaultHolder.ts +59 -59
  77. package/src/utils/downloadPDF.tsx +30 -30
  78. package/src/utils/formikErrorFocus.ts +24 -24
  79. package/src/utils/getImage.ts +14 -14
  80. package/src/utils/getQueryVariable.ts +13 -13
  81. package/src/utils/index.ts +5 -5
  82. package/src/utils/setConfigs.ts +26 -26
  83. package/src/utils/showZero.tsx +10 -10
  84. package/src/validators/index.ts +20 -20
@@ -1,409 +1,427 @@
1
- import React, { useState, useEffect } from 'react'
2
- import axios, { AxiosError } from 'axios'
3
- import { Loader } from '../common/index'
4
- import './style.css'
5
-
6
- import {
7
- getTickets,
8
- getEvent,
9
- addToCart,
10
- setCustomHeader,
11
- postOnCheckout,
12
- } from '../../api'
13
- import _get from 'lodash/get'
14
- import _some from 'lodash/some'
15
- import _every from 'lodash/every'
16
- import _find from 'lodash/find'
17
- import _isEmpty from 'lodash/isEmpty'
18
- import Button from 'react-bootstrap/Button'
19
- import jwt_decode from 'jwt-decode'
20
- import { TicketsSection } from './TicketsSection'
21
- import WaitingList from '../waitingList'
22
- import { PromoCodeSection } from './PromoCodeSection'
23
- import { LoginModal } from '../loginModal'
24
- import Countdown from '../countdown'
25
- import { createCheckoutDataBodyWithDefaultHolder, getQueryVariable } from '../../utils'
26
- import { ThemeProvider } from '@mui/private-theming'
27
- import { createTheme, ThemeOptions } from '@mui/material'
28
- import { CSSProperties } from '@mui/styles'
29
- import { ReferralLogic } from './ReferralLogic'
30
-
31
- interface CartSuccess {
32
- skip_billing_page: boolean;
33
- names_required: boolean;
34
- age_required: boolean;
35
- phone_required: boolean;
36
- event_id: string;
37
- hash?: string;
38
- }
39
-
40
- export interface IGetTickets {
41
- eventId: number;
42
- onAddToCartSuccess: (response: CartSuccess) => void;
43
- getTicketsLabel?: string;
44
- contentStyle?: React.CSSProperties;
45
- onAddToCartError: (e: AxiosError) => void;
46
- onGetTicketsSuccess: (response: any) => void;
47
- onGetTicketsError: (e: AxiosError) => void;
48
- onLoginSuccess: () => void;
49
-
50
- theme?: 'light' | 'dark';
51
- queryPromoCode?: string;
52
- isPromotionsEnabled?: boolean;
53
- themeOptions?: ThemeOptions & { input?: CSSProperties; checkbox?: CSSProperties }
54
- isAccessCodeEnabled?: boolean;
55
- hideSessionButtons?: boolean;
56
- hideWaitingList?: boolean;
57
- }
58
-
59
- export interface ITicket {
60
- id: string | number;
61
- [key: string]: string | number;
62
- }
63
-
64
- export interface ISelectedTickets {
65
- [key: string]: string | number;
66
- }
67
-
68
- export const TicketsContainer = ({
69
- onLoginSuccess,
70
- getTicketsLabel,
71
- eventId,
72
- onAddToCartSuccess,
73
- contentStyle = {},
74
- onAddToCartError = () => {},
75
- onGetTicketsSuccess = () => {},
76
- onGetTicketsError = () => {},
77
- theme = 'light',
78
- queryPromoCode = '',
79
- isPromotionsEnabled = true,
80
- themeOptions,
81
- isAccessCodeEnabled = false,
82
- hideSessionButtons = false,
83
- hideWaitingList = false
84
- }: IGetTickets) => {
85
- const [selectedTickets, setSelectedTickets] = useState(
86
- {} as ISelectedTickets
87
- )
88
- const isWindowDefined = typeof window !== 'undefined'
89
- const [isLogged, setIsLogged] = useState(
90
- isWindowDefined ? !!window.localStorage.getItem('access_token') : false
91
- )
92
- const [showLoginModal, setShowLoginModal] = React.useState(false)
93
- const [tickets, setTickets] = useState([] as ITicket[])
94
- const [event, setEvent] = useState<any>(null)
95
- const [showWaitingList, setShowWaitingList] = useState(false)
96
- const [isLoading, setIsLoading] = useState(false)
97
- const [isPromoLoading, setIsPromoLoading] = useState(false)
98
- const [handleBookIsLoading, setHandleBookIsLoading] = useState(false)
99
- const [promoCode, setPromoCode] = useState('')
100
- const [promoCodeUpdated, setPromoCodeUpdated] = useState(getQueryVariable('r') || queryPromoCode)
101
- const [showPromoInput, setShowPromoInput] = useState(false)
102
- const [promoCodeIsApplied, setPromoCodeIsApplied] = useState(false)
103
-
104
- useEffect(() => {
105
- if (typeof window !== 'undefined') {
106
- const access_token = window.localStorage.getItem('access_token')
107
- if (access_token) {
108
- const decoded = jwt_decode<{ exp: number }>(access_token)
109
- if (decoded && decoded.exp < Date.now() / 1000) {
110
- window.localStorage.removeItem('access_token')
111
- window.localStorage.removeItem('user_data')
112
- }
113
- }
114
- }
115
- }, [])
116
-
117
- useEffect(() => {
118
- !!eventId && getTicketsApi(!!promoCodeUpdated)
119
- }, [eventId, promoCodeUpdated])
120
-
121
- const handleLogout = () => {
122
- if (isWindowDefined) {
123
- window.localStorage.removeItem('access_token')
124
- window.localStorage.removeItem('user_data')
125
- setIsLogged(false)
126
- const event = new window.CustomEvent('tf-logout')
127
- window.document.dispatchEvent(event)
128
- }
129
- }
130
-
131
- const handleExternalLogin = () => {
132
- setIsLogged(true)
133
- }
134
- const handleOnClose = () => {
135
- setShowLoginModal(false)
136
- }
137
- const handleOnLogin = () => {
138
- setShowLoginModal(false)
139
- setIsLogged(true)
140
- if (onLoginSuccess) {
141
- onLoginSuccess()
142
- }
143
- }
144
-
145
- async function getTicketsApi(isUpdateingPromoCode?: boolean) {
146
- try {
147
- !isUpdateingPromoCode && setIsLoading(true)
148
- setIsPromoLoading(true)
149
- const response = await getTickets(eventId, promoCodeUpdated)
150
- const eventResponse = await getEvent(eventId)
151
- if (response.data.success) {
152
- setCustomHeader(response)
153
- const attributes = _get(response, 'data.data.attributes')
154
- setPromoCodeIsApplied(attributes.ValidPromoCode)
155
- setTickets(_get(attributes, 'tickets'))
156
- setShowWaitingList(attributes.showWaitingList)
157
- onGetTicketsSuccess(response.data)
158
- setPromoCode('')
159
- }
160
- if (eventResponse.data.success) {
161
- const event = _get(eventResponse, 'data.data.attributes')
162
- setEvent(event)
163
- }
164
- } catch (e) {
165
- if (axios.isAxiosError(e)) {
166
- onGetTicketsError(e)
167
- }
168
- } finally {
169
- setIsLoading(false)
170
- setIsPromoLoading(false)
171
- }
172
- }
173
-
174
- const handleTicketSelect = (key: string, value: number | string) => {
175
- setSelectedTickets(prevState => {
176
- if (Object.keys(prevState)[0] !== key && !value) {
177
- return prevState
178
- }
179
- return {
180
- [key]: value,
181
- }
182
- })
183
- }
184
-
185
- const handleOrdersClick = () => {
186
- if (isWindowDefined) {
187
- window.location.href = '/orders'
188
- }
189
- }
190
-
191
- const handleBook = async () => {
192
- setHandleBookIsLoading(true)
193
- const ticket =
194
- _find(tickets, item => selectedTickets[item.id] > 0) || ({} as ITicket)
195
- const optionName = _get(ticket, 'optionName')
196
- const ticketId = _get(ticket, 'id')
197
- const ticketQuantity = +selectedTickets[ticket.id]
198
-
199
- const data = {
200
- attributes: {
201
- alternative_view_id: null,
202
- product_cart_quantity: ticketQuantity,
203
- product_options: {
204
- [optionName]: ticketId,
205
- },
206
- product_id: eventId,
207
- ticket_types: {
208
- [ticketId]: {
209
- product_options: {
210
- [optionName]: ticketId,
211
- ticket_price: ticket.price,
212
- },
213
- quantity: ticketQuantity,
214
- },
215
- },
216
- },
217
- }
218
-
219
- try {
220
- const result = await addToCart(eventId, data)
221
- if (result.status === 200) {
222
- setCustomHeader(result)
223
-
224
- const skipBillingPage =
225
- result?.data?.data?.attributes?.skip_billing_page ?? false
226
- const nameIsRequired =
227
- result?.data?.data?.attributes?.names_required ?? false
228
- const ageIsRequired =
229
- result?.data?.data?.attributes?.age_required ?? false
230
- const phoneIsRequired =
231
- result?.data?.data?.attributes?.phone_required ?? false
232
-
233
- let hash = ''
234
-
235
- if (skipBillingPage) {
236
- // Get user data for checkout data
237
- const isWindowDefined = typeof window !== 'undefined'
238
- const userData =
239
- isWindowDefined && window.localStorage.getItem('user_data')
240
- ? JSON.parse(window.localStorage.getItem('user_data') || '')
241
- : {}
242
-
243
- const access_token =
244
- isWindowDefined && window.localStorage.getItem('access_token')
245
- ? window.localStorage.getItem('access_token') || ''
246
- : ''
247
-
248
- const checkoutBody = createCheckoutDataBodyWithDefaultHolder(
249
- ticketQuantity,
250
- userData
251
- )
252
-
253
- const checkoutResult = await postOnCheckout(
254
- checkoutBody,
255
- access_token
256
- )
257
-
258
- hash = _get(checkoutResult, 'data.data.attributes.hash')
259
- }
260
-
261
- onAddToCartSuccess({
262
- skip_billing_page: skipBillingPage,
263
- names_required: nameIsRequired,
264
- phone_required: phoneIsRequired,
265
- age_required: ageIsRequired,
266
- event_id: String(eventId),
267
- hash,
268
- })
269
- }
270
- } catch (e) {
271
- if (axios.isAxiosError(e)) {
272
- onAddToCartError(e)
273
- }
274
- } finally {
275
- setHandleBookIsLoading(false)
276
- }
277
- }
278
-
279
- const updateTickets = () => {
280
- getTicketsApi()
281
- }
282
-
283
- const isAllTicketsSoldOut = _every(
284
- tickets,
285
- item => (item.sold_out || item.soldOut)
286
- )
287
-
288
- const isTicketOnSale = _some(
289
- tickets,
290
- item => (item.salesStarted && !item.salesEnded && !item.soldOut)
291
- )
292
-
293
- const themeMui = createTheme(themeOptions)
294
-
295
- useEffect(() => {
296
- isWindowDefined &&
297
- window.document.addEventListener('custom-logout', handleLogout)
298
- return () => {
299
- isWindowDefined &&
300
- window.document.removeEventListener('custom-logout', handleLogout)
301
- }
302
- }, [])
303
-
304
- useEffect(() => {
305
- isWindowDefined &&
306
- window.document.addEventListener('custom-login', handleExternalLogin)
307
- return () => {
308
- isWindowDefined &&
309
- window.document.removeEventListener(
310
- 'custom-login',
311
- handleExternalLogin
312
- )
313
- }
314
- }, [])
315
-
316
- return (
317
- <ThemeProvider theme={themeMui}>
318
- <ReferralLogic eventId={eventId} />
319
- <div className={`get-tickets-page ${theme}`} style={contentStyle}>
320
- {isLoading ? (
321
- <Loader />
322
- ) : (
323
- <div>
324
- <TicketsSection
325
- ticketsList={tickets}
326
- selectedTickets={selectedTickets}
327
- handleTicketSelect={handleTicketSelect}
328
- promoCodeIsApplied={promoCodeIsApplied}
329
- />
330
- {event?.salesEnded ? (
331
- <p className='event-closed-message'>Sales for this event are closed.</p>
332
- ) : !event?.salesStarted && event?.salesStart ? (
333
- <Countdown
334
- startDate={event.salesStart}
335
- timezone={event.timezone}
336
- title="Sales start in:"
337
- message="No tickets are currently available for this event."
338
- callback={updateTickets}
339
- />
340
- ) : null}
341
- {showWaitingList && event.salesStarted && !hideWaitingList && (
342
- <WaitingList tickets={tickets} eventId={eventId} />
343
- )}
344
- <PromoCodeSection
345
- promoCode={promoCode}
346
- promoCodeIsApplied={promoCodeIsApplied}
347
- showPromoInput={showPromoInput}
348
- setPromoCode={setPromoCode}
349
- setPromoCodeUpdated={setPromoCodeUpdated}
350
- setShowPromoInput={setShowPromoInput}
351
- isPromotionsEnabled={isPromotionsEnabled}
352
- isAccessCodeEnabled={isAccessCodeEnabled}
353
- isAllTicketsSoldOut={isAllTicketsSoldOut}
354
- isPromoLoading={isPromoLoading}
355
- />
356
- {(isTicketOnSale || !event?.salesEnded) && (
357
- <Button
358
- aria-hidden={true}
359
- className={`book-button ${
360
- handleBookIsLoading ||
361
- _isEmpty(selectedTickets) ||
362
- Object.values(selectedTickets)[0] === 0
363
- ? 'disabled'
364
- : ''
365
- }`}
366
- onClick={
367
- !handleBookIsLoading &&
368
- !_isEmpty(selectedTickets) &&
369
- Object.values(selectedTickets)[0] > 0
370
- ? handleBook
371
- : () => {}
372
- }
373
- >
374
- {getTicketsLabel || 'GET TICKETS'}
375
- </Button>
376
- )}
377
- {isLogged && !hideSessionButtons ? (
378
- <div className="session-wrapper">
379
- <span className="session-container">
380
- <Button
381
- variant="outline-light"
382
- className="session-buttons"
383
- onClick={handleOrdersClick}
384
- >
385
- My Orders
386
- </Button>
387
- </span>
388
- <span className="session-container">
389
- <Button
390
- variant="outline-light"
391
- className="session-buttons"
392
- onClick={handleLogout}
393
- >
394
- Log out
395
- </Button>
396
- </span>
397
- </div>
398
- ) : (
399
- ''
400
- )}
401
- </div>
402
- )}
403
- {showLoginModal ? (
404
- <LoginModal onClose={handleOnClose} onLogin={handleOnLogin} />
405
- ) : null}
406
- </div>
407
- </ThemeProvider>
408
- )
409
- }
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import axios, { AxiosError } from 'axios'
3
+ import { Loader } from '../common/index'
4
+ import './style.css'
5
+
6
+ import {
7
+ getTickets,
8
+ getEvent,
9
+ addToCart,
10
+ postOnCheckout,
11
+ } from '../../api'
12
+ import _get from 'lodash/get'
13
+ import _some from 'lodash/some'
14
+ import _every from 'lodash/every'
15
+ import _find from 'lodash/find'
16
+ import _isEmpty from 'lodash/isEmpty'
17
+ import Button from 'react-bootstrap/Button'
18
+ import jwt_decode from 'jwt-decode'
19
+ import { TicketsSection } from './TicketsSection'
20
+ import WaitingList from '../waitingList'
21
+ import { PromoCodeSection } from './PromoCodeSection'
22
+ import { LoginModal } from '../loginModal'
23
+ import Countdown from '../countdown'
24
+ import { createCheckoutDataBodyWithDefaultHolder, getQueryVariable } from '../../utils'
25
+ import { ThemeProvider } from '@mui/private-theming'
26
+ import { createTheme, ThemeOptions } from '@mui/material'
27
+ import { CSSProperties } from '@mui/styles'
28
+ import { ReferralLogic } from './ReferralLogic'
29
+
30
+ interface CartSuccess {
31
+ skip_billing_page: boolean;
32
+ names_required: boolean;
33
+ age_required: boolean;
34
+ phone_required: boolean;
35
+ event_id: string;
36
+ hash?: string;
37
+ }
38
+
39
+ export interface IGetTickets {
40
+ eventId: number;
41
+ onAddToCartSuccess: (response: CartSuccess) => void;
42
+ getTicketsLabel?: string;
43
+ contentStyle?: React.CSSProperties;
44
+ onAddToCartError: (e: AxiosError) => void;
45
+ onGetTicketsSuccess: (response: any) => void;
46
+ onGetTicketsError: (e: AxiosError) => void;
47
+ onLoginSuccess: () => void;
48
+
49
+ theme?: 'light' | 'dark';
50
+ queryPromoCode?: string;
51
+ isPromotionsEnabled?: boolean;
52
+ themeOptions?: ThemeOptions & { input?: CSSProperties; checkbox?: CSSProperties }
53
+ isAccessCodeEnabled?: boolean;
54
+ hideSessionButtons?: boolean;
55
+ hideWaitingList?: boolean;
56
+ isButtonScrollable?: boolean;
57
+ }
58
+
59
+ export interface ITicket {
60
+ id: string | number;
61
+ [key: string]: string | number;
62
+ }
63
+
64
+ export interface ISelectedTickets {
65
+ [key: string]: string | number;
66
+ }
67
+
68
+ export const TicketsContainer = ({
69
+ onLoginSuccess,
70
+ getTicketsLabel,
71
+ eventId,
72
+ onAddToCartSuccess,
73
+ contentStyle = {},
74
+ onAddToCartError = () => {},
75
+ onGetTicketsSuccess = () => {},
76
+ onGetTicketsError = () => {},
77
+ theme = 'light',
78
+ queryPromoCode = '',
79
+ isPromotionsEnabled = true,
80
+ themeOptions,
81
+ isAccessCodeEnabled = false,
82
+ hideSessionButtons = false,
83
+ hideWaitingList = false,
84
+ isButtonScrollable = false
85
+ }: IGetTickets) => {
86
+ const [selectedTickets, setSelectedTickets] = useState(
87
+ {} as ISelectedTickets
88
+ )
89
+ const isWindowDefined = typeof window !== 'undefined'
90
+ const [isLogged, setIsLogged] = useState(
91
+ isWindowDefined ? !!window.localStorage.getItem('access_token') : false
92
+ )
93
+ const [showLoginModal, setShowLoginModal] = useState(false)
94
+ const [tickets, setTickets] = useState([] as ITicket[])
95
+ const [event, setEvent] = useState<any>(null)
96
+ const [showWaitingList, setShowWaitingList] = useState(false)
97
+ const [isLoading, setIsLoading] = useState(true)
98
+ const [isPromoLoading, setIsPromoLoading] = useState(false)
99
+ const [handleBookIsLoading, setHandleBookIsLoading] = useState(false)
100
+ const [promoCode, setPromoCode] = useState('')
101
+ const [promoCodeUpdated, setPromoCodeUpdated] = useState(getQueryVariable('r') || queryPromoCode)
102
+ const [showPromoInput, setShowPromoInput] = useState(false)
103
+ const [promoCodeIsApplied, setPromoCodeIsApplied] = useState(false)
104
+
105
+ const ticketsContainerRef = useRef<HTMLDivElement>(null)
106
+
107
+ useEffect(() => {
108
+ if (typeof window !== 'undefined') {
109
+ const access_token = window.localStorage.getItem('access_token')
110
+ if (access_token) {
111
+ const decoded = jwt_decode<{ exp: number }>(access_token)
112
+ if (decoded && decoded.exp < Date.now() / 1000) {
113
+ window.localStorage.removeItem('access_token')
114
+ window.localStorage.removeItem('user_data')
115
+ }
116
+ }
117
+ }
118
+ }, [])
119
+
120
+ useEffect(() => {
121
+ !!eventId && getTicketsApi(!!promoCodeUpdated)
122
+ }, [eventId, promoCodeUpdated])
123
+
124
+ const handleLogout = () => {
125
+ if (isWindowDefined) {
126
+ window.localStorage.removeItem('access_token')
127
+ window.localStorage.removeItem('user_data')
128
+ setIsLogged(false)
129
+ const event = new window.CustomEvent('tf-logout')
130
+ window.document.dispatchEvent(event)
131
+ }
132
+ }
133
+
134
+ useEffect(() => {
135
+ try {
136
+ if (typeof window !== 'undefined') {
137
+ const userData = window.localStorage.getItem('user_data')
138
+ ? JSON.parse(window.localStorage.getItem('user_data') || '{}')
139
+ : {}
140
+ if (userData.country === '') {
141
+ handleLogout()
142
+ window.open("https://www.ticketfairy.com/account/change_information?need_country=true");
143
+ }
144
+ }
145
+ } catch(e) {}
146
+ }, [])
147
+
148
+ const handleExternalLogin = () => {
149
+ setIsLogged(true)
150
+ }
151
+ const handleOnClose = () => {
152
+ setShowLoginModal(false)
153
+ }
154
+ const handleOnLogin = () => {
155
+ setShowLoginModal(false)
156
+ setIsLogged(true)
157
+ if (onLoginSuccess) {
158
+ onLoginSuccess()
159
+ }
160
+ }
161
+
162
+ async function getTicketsApi(isUpdateingPromoCode?: boolean) {
163
+ try {
164
+ !isUpdateingPromoCode && setIsLoading(true)
165
+ setIsPromoLoading(true)
166
+ const response = await getTickets(eventId, promoCodeUpdated)
167
+ const eventResponse = await getEvent(eventId)
168
+ if (response.data.success) {
169
+ const attributes = _get(response, 'data.data.attributes')
170
+ setPromoCodeIsApplied(attributes.ValidPromoCode)
171
+ setTickets(_get(attributes, 'tickets'))
172
+ setShowWaitingList(attributes.showWaitingList)
173
+ onGetTicketsSuccess(response.data)
174
+ setPromoCode('')
175
+ }
176
+ if (eventResponse.data.success) {
177
+ const event = _get(eventResponse, 'data.data.attributes')
178
+ setEvent(event)
179
+ }
180
+ } catch (e) {
181
+ if (axios.isAxiosError(e)) {
182
+ onGetTicketsError(e)
183
+ }
184
+ } finally {
185
+ setIsLoading(false)
186
+ setIsPromoLoading(false)
187
+ }
188
+ }
189
+
190
+ const handleTicketSelect = (key: string, value: number | string) => {
191
+ setSelectedTickets(prevState => {
192
+ if (Object.keys(prevState)[0] !== key && !value) {
193
+ return prevState
194
+ }
195
+ return {
196
+ [key]: value,
197
+ }
198
+ })
199
+ }
200
+
201
+ const handleOrdersClick = () => {
202
+ if (isWindowDefined) {
203
+ window.location.href = '/orders'
204
+ }
205
+ }
206
+
207
+ const handleBook = async () => {
208
+ setHandleBookIsLoading(true)
209
+ const ticket =
210
+ _find(tickets, item => selectedTickets[item.id] > 0) || ({} as ITicket)
211
+ const optionName = _get(ticket, 'optionName')
212
+ const ticketId = _get(ticket, 'id')
213
+ const ticketQuantity = +selectedTickets[ticket.id]
214
+
215
+ const data = {
216
+ attributes: {
217
+ alternative_view_id: null,
218
+ product_cart_quantity: ticketQuantity,
219
+ product_options: {
220
+ [optionName]: ticketId,
221
+ },
222
+ product_id: eventId,
223
+ ticket_types: {
224
+ [ticketId]: {
225
+ product_options: {
226
+ [optionName]: ticketId,
227
+ ticket_price: ticket.price,
228
+ },
229
+ quantity: ticketQuantity,
230
+ },
231
+ },
232
+ },
233
+ }
234
+
235
+ try {
236
+ const result = await addToCart(eventId, data)
237
+ if (result.status === 200) {
238
+
239
+ const skipBillingPage =
240
+ result?.data?.data?.attributes?.skip_billing_page ?? false
241
+ const nameIsRequired =
242
+ result?.data?.data?.attributes?.names_required ?? false
243
+ const ageIsRequired =
244
+ result?.data?.data?.attributes?.age_required ?? false
245
+ const phoneIsRequired =
246
+ result?.data?.data?.attributes?.phone_required ?? false
247
+
248
+ let hash = ''
249
+
250
+ if (skipBillingPage) {
251
+ // Get user data for checkout data
252
+ const isWindowDefined = typeof window !== 'undefined'
253
+ const userData =
254
+ isWindowDefined && window.localStorage.getItem('user_data')
255
+ ? JSON.parse(window.localStorage.getItem('user_data') || '')
256
+ : {}
257
+
258
+ const access_token =
259
+ isWindowDefined && window.localStorage.getItem('access_token')
260
+ ? window.localStorage.getItem('access_token') || ''
261
+ : ''
262
+
263
+ const checkoutBody = createCheckoutDataBodyWithDefaultHolder(
264
+ ticketQuantity,
265
+ userData
266
+ )
267
+
268
+ const checkoutResult = await postOnCheckout(
269
+ checkoutBody,
270
+ access_token
271
+ )
272
+
273
+ hash = _get(checkoutResult, 'data.data.attributes.hash')
274
+ }
275
+
276
+ onAddToCartSuccess({
277
+ skip_billing_page: skipBillingPage,
278
+ names_required: nameIsRequired,
279
+ phone_required: phoneIsRequired,
280
+ age_required: ageIsRequired,
281
+ event_id: String(eventId),
282
+ hash,
283
+ })
284
+ }
285
+ } catch (e) {
286
+ if (axios.isAxiosError(e)) {
287
+ onAddToCartError(e)
288
+ }
289
+ } finally {
290
+ setHandleBookIsLoading(false)
291
+ }
292
+ }
293
+
294
+ const updateTickets = () => {
295
+ getTicketsApi()
296
+ }
297
+
298
+ const isAllTicketsSoldOut = _every(
299
+ tickets,
300
+ item => (item.sold_out || item.soldOut)
301
+ )
302
+
303
+ const isTicketOnSale = _some(
304
+ tickets,
305
+ item => (item.salesStarted && !item.salesEnded && !item.soldOut)
306
+ )
307
+
308
+ const themeMui = createTheme(themeOptions)
309
+
310
+ useEffect(() => {
311
+ isWindowDefined &&
312
+ window.document.addEventListener('custom-logout', handleLogout)
313
+ return () => {
314
+ isWindowDefined &&
315
+ window.document.removeEventListener('custom-logout', handleLogout)
316
+ }
317
+ }, [])
318
+
319
+ useEffect(() => {
320
+ isWindowDefined &&
321
+ window.document.addEventListener('custom-login', handleExternalLogin)
322
+ return () => {
323
+ isWindowDefined &&
324
+ window.document.removeEventListener(
325
+ 'custom-login',
326
+ handleExternalLogin
327
+ )
328
+ }
329
+ }, [])
330
+
331
+ const handleGetTicketClick = () => {
332
+ if (!handleBookIsLoading && !_isEmpty(selectedTickets) && Object.values(selectedTickets)[0] > 0) {
333
+ handleBook()
334
+ } else {
335
+ if (isButtonScrollable && ticketsContainerRef && ticketsContainerRef.current) {
336
+ ticketsContainerRef.current.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' })
337
+ }
338
+ }
339
+ }
340
+
341
+ const bookButtonIsDisabled = handleBookIsLoading || _isEmpty(selectedTickets) || Object.values(selectedTickets)[0] === 0
342
+
343
+ return (
344
+ <ThemeProvider theme={themeMui}>
345
+ {!isLoading && <ReferralLogic eventId={eventId} />}
346
+ <div className={`get-tickets-page ${theme}`} style={contentStyle}>
347
+ {isLoading ? (
348
+ <Loader />
349
+ ) : (
350
+ <div ref={ticketsContainerRef}>
351
+ <TicketsSection
352
+ ticketsList={tickets}
353
+ selectedTickets={selectedTickets}
354
+ handleTicketSelect={handleTicketSelect}
355
+ promoCodeIsApplied={promoCodeIsApplied}
356
+ />
357
+ {event?.salesEnded ? (
358
+ <p className='event-closed-message'>Sales for this event are closed.</p>
359
+ ) : !event?.salesStarted && event?.salesStart ? (
360
+ <Countdown
361
+ startDate={event.salesStart}
362
+ timezone={event.timezone}
363
+ title="Sales start in:"
364
+ message="No tickets are currently available for this event."
365
+ callback={updateTickets}
366
+ />
367
+ ) : null}
368
+ {showWaitingList && event.salesStarted && !hideWaitingList && (
369
+ <WaitingList tickets={tickets} eventId={eventId} />
370
+ )}
371
+ <PromoCodeSection
372
+ promoCode={promoCode}
373
+ promoCodeIsApplied={promoCodeIsApplied}
374
+ showPromoInput={showPromoInput}
375
+ setPromoCode={setPromoCode}
376
+ setPromoCodeUpdated={setPromoCodeUpdated}
377
+ setShowPromoInput={setShowPromoInput}
378
+ isPromotionsEnabled={isPromotionsEnabled}
379
+ isAccessCodeEnabled={isAccessCodeEnabled}
380
+ isAllTicketsSoldOut={isAllTicketsSoldOut}
381
+ isPromoLoading={isPromoLoading}
382
+ />
383
+ {(isTicketOnSale || !event?.salesEnded) && (
384
+ <Button
385
+ aria-hidden={true}
386
+ className={`book-button
387
+ ${bookButtonIsDisabled ? 'disabled' : ''}
388
+ ${isButtonScrollable ? 'is-scrollable' : ''}
389
+ `}
390
+ onClick={handleGetTicketClick}
391
+ >
392
+ {getTicketsLabel || 'GET TICKETS'}
393
+ </Button>
394
+ )}
395
+ {isLogged && !hideSessionButtons ? (
396
+ <div className="session-wrapper">
397
+ <span className="session-container">
398
+ <Button
399
+ variant="outline-light"
400
+ className="session-buttons"
401
+ onClick={handleOrdersClick}
402
+ >
403
+ My Orders
404
+ </Button>
405
+ </span>
406
+ <span className="session-container">
407
+ <Button
408
+ variant="outline-light"
409
+ className="session-buttons"
410
+ onClick={handleLogout}
411
+ >
412
+ Log out
413
+ </Button>
414
+ </span>
415
+ </div>
416
+ ) : (
417
+ ''
418
+ )}
419
+ </div>
420
+ )}
421
+ {showLoginModal ? (
422
+ <LoginModal onClose={handleOnClose} onLogin={handleOnLogin} />
423
+ ) : null}
424
+ </div>
425
+ </ThemeProvider>
426
+ )
427
+ }