tf-checkout-react 1.0.99 → 1.0.100-beta.2

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