tf-checkout-react 1.0.103 → 1.0.106

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