tf-checkout-react 1.0.75 → 1.0.79

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 (42) hide show
  1. package/dist/api/index.d.ts +1 -0
  2. package/dist/components/billing-info-container/index.d.ts +7 -2
  3. package/dist/components/billing-info-container/utils.d.ts +1 -0
  4. package/dist/components/common/FormikPhoneNumberField.d.ts +1 -1
  5. package/dist/components/countdown/index.d.ts +11 -0
  6. package/dist/components/orderDetailsContainer/ticketsTable.d.ts +13 -1
  7. package/dist/components/paymentContainer/index.d.ts +9 -2
  8. package/dist/components/ticketsContainer/index.d.ts +9 -1
  9. package/dist/components/waitingList/index.d.ts +1 -2
  10. package/dist/tf-checkout-react.cjs.development.js +505 -177
  11. package/dist/tf-checkout-react.cjs.development.js.map +1 -1
  12. package/dist/tf-checkout-react.cjs.production.min.js +1 -1
  13. package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
  14. package/dist/tf-checkout-react.esm.js +498 -170
  15. package/dist/tf-checkout-react.esm.js.map +1 -1
  16. package/dist/tf-checkout-styles.css +1 -1
  17. package/dist/utils/createCheckoutDataBodyWithDefaultHolder.d.ts +7 -0
  18. package/dist/utils/downloadPDF.d.ts +1 -0
  19. package/dist/utils/index.d.ts +2 -0
  20. package/package.json +2 -1
  21. package/src/api/index.ts +9 -0
  22. package/src/components/billing-info-container/index.tsx +25 -27
  23. package/src/components/billing-info-container/utils.ts +2 -1
  24. package/src/components/common/CheckboxField.tsx +19 -8
  25. package/src/components/common/CustomField.tsx +7 -0
  26. package/src/components/common/FormikPhoneNumberField.tsx +11 -1
  27. package/src/components/common/SelectField.tsx +5 -1
  28. package/src/components/countdown/index.tsx +89 -0
  29. package/src/components/countdown/style.css +10 -0
  30. package/src/components/loginModal/index.tsx +2 -0
  31. package/src/components/orderDetailsContainer/ticketsTable.tsx +66 -60
  32. package/src/components/paymentContainer/index.tsx +9 -11
  33. package/src/components/stripePayment/index.tsx +4 -4
  34. package/src/components/ticketsContainer/PromoCodeSection.tsx +3 -2
  35. package/src/components/ticketsContainer/TicketsSection.tsx +10 -6
  36. package/src/components/ticketsContainer/index.tsx +236 -76
  37. package/src/components/ticketsContainer/style.css +7 -0
  38. package/src/components/waitingList/index.tsx +3 -9
  39. package/src/components/waitingList/style.css +4 -2
  40. package/src/utils/createCheckoutDataBodyWithDefaultHolder.ts +51 -0
  41. package/src/utils/downloadPDF.tsx +22 -0
  42. package/src/utils/index.ts +2 -0
@@ -2,7 +2,13 @@ import React, { useState, useEffect } from 'react'
2
2
  import axios, { AxiosError } from 'axios'
3
3
  import './style.css'
4
4
 
5
- import { getTickets, addToCart, setCustomHeader } from '../../api'
5
+ import {
6
+ getTickets,
7
+ getEvent,
8
+ addToCart,
9
+ setCustomHeader,
10
+ postOnCheckout,
11
+ } from '../../api'
6
12
  import _get from 'lodash/get'
7
13
  import _some from 'lodash/some'
8
14
  import _find from 'lodash/find'
@@ -13,6 +19,12 @@ import jwt_decode from 'jwt-decode'
13
19
  import { TicketsSection } from './TicketsSection'
14
20
  import WaitingList from '../waitingList'
15
21
  import { PromoCodeSection } from './PromoCodeSection'
22
+ import { LoginModal } from '../loginModal'
23
+ import Countdown from '../countdown'
24
+ import { createCheckoutDataBodyWithDefaultHolder } from '../../utils'
25
+ import { ThemeProvider } from '@mui/private-theming'
26
+ import { createTheme, ThemeOptions } from '@mui/material'
27
+ import { CSSProperties } from '@mui/styles'
16
28
 
17
29
  function Loader() {
18
30
  return (
@@ -27,6 +39,7 @@ interface CartSuccess {
27
39
  names_required: boolean;
28
40
  age_required: boolean;
29
41
  event_id: string;
42
+ hash?: string;
30
43
  }
31
44
 
32
45
  export interface IGetTickets {
@@ -37,10 +50,12 @@ export interface IGetTickets {
37
50
  onAddToCartError: (e: AxiosError) => void;
38
51
  onGetTicketsSuccess: (response: any) => void;
39
52
  onGetTicketsError: (e: AxiosError) => void;
53
+ onLoginSuccess: () => void;
40
54
 
41
55
  theme?: 'light' | 'dark';
42
56
  queryPromoCode?: string;
43
57
  isPromotionsEnabled?: boolean;
58
+ themeOptions?: ThemeOptions & { input?: CSSProperties; checkbox?: CSSProperties }
44
59
  }
45
60
 
46
61
  export interface ITicket {
@@ -53,21 +68,29 @@ export interface ISelectedTickets {
53
68
  }
54
69
 
55
70
  export const TicketsContainer = ({
71
+ onLoginSuccess,
56
72
  getTicketsLabel,
57
73
  eventId,
58
74
  onAddToCartSuccess,
59
75
  contentStyle = {},
60
- onAddToCartError = () => { },
61
- onGetTicketsSuccess = () => { },
62
- onGetTicketsError = () => { },
76
+ onAddToCartError = () => {},
77
+ onGetTicketsSuccess = () => {},
78
+ onGetTicketsError = () => {},
63
79
  theme = 'light',
64
80
  queryPromoCode = '',
65
81
  isPromotionsEnabled = true,
82
+ themeOptions,
66
83
  }: IGetTickets) => {
67
84
  const [selectedTickets, setSelectedTickets] = useState(
68
85
  {} as ISelectedTickets
69
86
  )
87
+ const isWindowDefined = typeof window !== 'undefined'
88
+ const [isLogged, setIsLogged] = useState(
89
+ isWindowDefined ? !!window.localStorage.getItem('access_token') : false
90
+ )
91
+ const [showLoginModal, setShowLoginModal] = React.useState(false)
70
92
  const [tickets, setTickets] = useState([] as ITicket[])
93
+ const [event, setEvent] = useState<any>(null)
71
94
  const [showWaitingList, setShowWaitingList] = useState(false)
72
95
  const [isLoading, setIsLoading] = useState(false)
73
96
  const [handleBookIsLoading, setHandleBookIsLoading] = useState(false)
@@ -90,29 +113,59 @@ export const TicketsContainer = ({
90
113
  }, [])
91
114
 
92
115
  useEffect(() => {
93
- async function getTicketsApi() {
94
- try {
95
- setIsLoading(true)
96
- const response = await getTickets(eventId, promoCodeUpdated)
97
- if (response.data.success) {
98
- setCustomHeader(response)
99
- const attributes = _get(response, 'data.data.attributes')
100
- setPromoCodeIsApplied(attributes.ValidPromoCode)
101
- setTickets(_get(attributes, 'tickets'))
102
- setShowWaitingList(attributes.showWaitingList)
103
- onGetTicketsSuccess(response.data)
104
- }
105
- } catch (e) {
106
- if (axios.isAxiosError(e)) {
107
- onGetTicketsError(e)
108
- }
109
- } finally {
110
- setIsLoading(false)
111
- }
112
- }
113
116
  getTicketsApi()
114
117
  }, [eventId, promoCodeUpdated])
115
118
 
119
+ const handleLogout = () => {
120
+ if (isWindowDefined) {
121
+ window.localStorage.removeItem('access_token')
122
+ window.localStorage.removeItem('user_data')
123
+ setIsLogged(false)
124
+ const event = new window.CustomEvent('tf-logout')
125
+ window.document.dispatchEvent(event)
126
+ }
127
+ }
128
+
129
+ const handleExternalLogin = () => {
130
+ setIsLogged(true)
131
+ }
132
+ const handleOnClose = () => {
133
+ setShowLoginModal(false)
134
+ }
135
+ const handleOnLogin = () => {
136
+ setShowLoginModal(false)
137
+ setIsLogged(true)
138
+ if (onLoginSuccess) {
139
+ onLoginSuccess()
140
+ }
141
+ }
142
+
143
+ async function getTicketsApi() {
144
+ try {
145
+ setIsLoading(true)
146
+ const response = await getTickets(eventId, promoCodeUpdated)
147
+ const eventResponse = await getEvent(eventId)
148
+ if (response.data.success) {
149
+ setCustomHeader(response)
150
+ const attributes = _get(response, 'data.data.attributes')
151
+ setPromoCodeIsApplied(attributes.ValidPromoCode)
152
+ setTickets(_get(attributes, 'tickets'))
153
+ setShowWaitingList(attributes.showWaitingList)
154
+ onGetTicketsSuccess(response.data)
155
+ }
156
+ if (eventResponse.data.success) {
157
+ const event = _get(eventResponse, 'data.data.attributes')
158
+ setEvent(event)
159
+ }
160
+ } catch (e) {
161
+ if (axios.isAxiosError(e)) {
162
+ onGetTicketsError(e)
163
+ }
164
+ } finally {
165
+ setIsLoading(false)
166
+ }
167
+ }
168
+
116
169
  const handleTicketSelect = (key: string, value: number | string) => {
117
170
  setSelectedTickets(prevState => {
118
171
  if (Object.keys(prevState)[0] !== key && !value) {
@@ -124,6 +177,12 @@ export const TicketsContainer = ({
124
177
  })
125
178
  }
126
179
 
180
+ const handleOrdersClick = () => {
181
+ if (isWindowDefined) {
182
+ window.location.href = '/orders'
183
+ }
184
+ }
185
+
127
186
  const handleBook = async () => {
128
187
  setHandleBookIsLoading(true)
129
188
  const ticket =
@@ -156,13 +215,50 @@ export const TicketsContainer = ({
156
215
  const result = await addToCart(eventId, data)
157
216
  if (result.status === 200) {
158
217
  setCustomHeader(result)
218
+
219
+ const skipBillingPage =
220
+ result?.data?.data?.attributes?.skip_billing_page ?? false
221
+ const nameIsRequired =
222
+ result?.data?.data?.attributes?.names_required ?? false
223
+ const ageIsRequired =
224
+ result?.data?.data?.attributes?.age_required ?? false
225
+
226
+ let hash = ''
227
+
228
+ if (skipBillingPage) {
229
+ // Get user data for checkout data
230
+ const isWindowDefined = typeof window !== 'undefined'
231
+ const userData =
232
+ isWindowDefined && window.localStorage.getItem('user_data')
233
+ ? JSON.parse(window.localStorage.getItem('user_data') || '')
234
+ : {}
235
+
236
+ const access_token =
237
+ isWindowDefined && window.localStorage.getItem('access_token')
238
+ ? window.localStorage.getItem('access_token') || ''
239
+ : ''
240
+
241
+ const ticketsQuantity = Object.keys(selectedTickets).length
242
+
243
+ const checkoutBody = createCheckoutDataBodyWithDefaultHolder(
244
+ ticketsQuantity,
245
+ userData
246
+ )
247
+
248
+ const checkoutResult = await postOnCheckout(
249
+ checkoutBody,
250
+ access_token
251
+ )
252
+
253
+ hash = _get(checkoutResult, 'data.data.attributes.hash')
254
+ }
255
+
159
256
  onAddToCartSuccess({
160
- skip_billing_page:
161
- result?.data?.data?.attributes?.skip_billing_page ?? false,
162
- names_required:
163
- result?.data?.data?.attributes?.names_required ?? false,
164
- age_required: result?.data?.data?.attributes?.age_required ?? false,
165
- event_id: String(eventId)
257
+ skip_billing_page: skipBillingPage,
258
+ names_required: nameIsRequired,
259
+ age_required: ageIsRequired,
260
+ event_id: String(eventId),
261
+ hash,
166
262
  })
167
263
  }
168
264
  } catch (e) {
@@ -174,62 +270,126 @@ export const TicketsContainer = ({
174
270
  }
175
271
  }
176
272
 
273
+ const updateTickets = () => {
274
+ getTicketsApi()
275
+ }
276
+
177
277
  const isAllTicketsSoldOut = !_some(
178
278
  tickets,
179
279
  item => !(item.sold_out || item.soldOut)
180
280
  )
181
281
 
282
+ const themeMui = createTheme(themeOptions)
283
+
284
+ useEffect(() => {
285
+ isWindowDefined &&
286
+ window.document.addEventListener('custom-logout', handleLogout)
287
+ return () => {
288
+ isWindowDefined &&
289
+ window.document.removeEventListener('custom-logout', handleLogout)
290
+ }
291
+ }, [])
292
+
293
+ useEffect(() => {
294
+ isWindowDefined &&
295
+ window.document.addEventListener('custom-login', handleExternalLogin)
296
+ return () => {
297
+ isWindowDefined &&
298
+ window.document.removeEventListener(
299
+ 'custom-login',
300
+ handleExternalLogin
301
+ )
302
+ }
303
+ }, [])
304
+
182
305
  return (
183
- <div className={`get-tickets-page ${theme}`} style={contentStyle}>
184
- {isLoading ? (
185
- <Loader />
186
- ) : (
187
- <div>
188
- <TicketsSection
189
- ticketsList={tickets}
190
- selectedTickets={selectedTickets}
191
- handleTicketSelect={handleTicketSelect}
192
- promoCodeIsApplied={promoCodeIsApplied}
193
- />
194
- {showWaitingList && (
195
- <WaitingList
196
- tickets={tickets}
197
- eventId={eventId}
306
+ <ThemeProvider theme={themeMui}>
307
+ <div className={`get-tickets-page ${theme}`} style={contentStyle}>
308
+ {isLoading ? (
309
+ <Loader />
310
+ ) : (
311
+ <div>
312
+ <TicketsSection
313
+ ticketsList={tickets}
314
+ selectedTickets={selectedTickets}
315
+ handleTicketSelect={handleTicketSelect}
316
+ promoCodeIsApplied={promoCodeIsApplied}
317
+ />
318
+ {event?.salesEnded ? (
319
+ <p>Sales for this event are closed.</p>
320
+ ) : event?.salesStart ? (
321
+ <Countdown
322
+ startDate={event.salesStart}
323
+ timezone={event.timezone}
324
+ title="Sales start in:"
325
+ message="No tickets are currently available for this event."
326
+ callback={updateTickets}
327
+ />
328
+ ) : null}
329
+ {showWaitingList && event.salesStarted && (
330
+ <WaitingList tickets={tickets} eventId={eventId} />
331
+ )}
332
+ <PromoCodeSection
333
+ promoCode={promoCode}
334
+ promoCodeIsApplied={promoCodeIsApplied}
335
+ showPromoInput={showPromoInput}
336
+ setPromoCode={setPromoCode}
337
+ setPromoCodeUpdated={setPromoCodeUpdated}
338
+ setShowPromoInput={setShowPromoInput}
198
339
  isPromotionsEnabled={isPromotionsEnabled}
340
+ isAllTicketsSoldOut={isAllTicketsSoldOut}
199
341
  />
200
- )}
201
- <PromoCodeSection
202
- promoCode={promoCode}
203
- promoCodeIsApplied={promoCodeIsApplied}
204
- showPromoInput={showPromoInput}
205
- setPromoCode={setPromoCode}
206
- setPromoCodeUpdated={setPromoCodeUpdated}
207
- setShowPromoInput={setShowPromoInput}
208
- isPromotionsEnabled={isPromotionsEnabled}
209
- isAllTicketsSoldOut={isAllTicketsSoldOut}
210
- />
211
- {!isAllTicketsSoldOut && (
212
- <Button
213
- aria-hidden={true}
214
- className={`book-button ${handleBookIsLoading ||
215
- _isEmpty(selectedTickets) ||
216
- Object.values(selectedTickets)[0] === 0
217
- ? 'disabled'
218
- : ''
342
+ {!isAllTicketsSoldOut && (
343
+ <Button
344
+ aria-hidden={true}
345
+ className={`book-button ${
346
+ handleBookIsLoading ||
347
+ _isEmpty(selectedTickets) ||
348
+ Object.values(selectedTickets)[0] === 0
349
+ ? 'disabled'
350
+ : ''
219
351
  }`}
220
- onClick={
221
- !handleBookIsLoading &&
352
+ onClick={
353
+ !handleBookIsLoading &&
222
354
  !_isEmpty(selectedTickets) &&
223
355
  Object.values(selectedTickets)[0] > 0
224
- ? handleBook
225
- : () => { }
226
- }
227
- >
228
- {getTicketsLabel || 'GET TICKETS'}
229
- </Button>
230
- )}
231
- </div>
232
- )}
233
- </div>
356
+ ? handleBook
357
+ : () => {}
358
+ }
359
+ >
360
+ {getTicketsLabel || 'GET TICKETS'}
361
+ </Button>
362
+ )}
363
+ {isLogged ? (
364
+ <div className="session-wrapper">
365
+ <span className="session-container">
366
+ <Button
367
+ variant="outline-light"
368
+ className="session-buttons"
369
+ onClick={handleOrdersClick}
370
+ >
371
+ My Orders
372
+ </Button>
373
+ </span>
374
+ <span className="session-container">
375
+ <Button
376
+ variant="outline-light"
377
+ className="session-buttons"
378
+ onClick={handleLogout}
379
+ >
380
+ Log out
381
+ </Button>
382
+ </span>
383
+ </div>
384
+ ) : (
385
+ ''
386
+ )}
387
+ </div>
388
+ )}
389
+ {showLoginModal ? (
390
+ <LoginModal onClose={handleOnClose} onLogin={handleOnLogin} />
391
+ ) : null}
392
+ </div>
393
+ </ThemeProvider>
234
394
  )
235
395
  }
@@ -108,6 +108,13 @@ body {
108
108
  text-transform: uppercase;
109
109
  }
110
110
 
111
+ .event-detail__tier-state .ticket-not-started-message {
112
+ color: #000000;
113
+ text-transform: initial;
114
+ width: 50px;
115
+ text-align: left;
116
+ }
117
+
111
118
  .promo-code-block input {
112
119
  font-size: 14px;
113
120
  padding: 1px 8px;
@@ -16,7 +16,6 @@ import './style.css'
16
16
  interface WaitingListProps {
17
17
  tickets: any;
18
18
  eventId: number;
19
- isPromotionsEnabled: boolean;
20
19
  }
21
20
 
22
21
  interface WaitingListFields {
@@ -35,7 +34,7 @@ const generateQuantity = (n: number) => {
35
34
  return quantityList
36
35
  }
37
36
 
38
- const WaitingList = ({ tickets = {}, eventId, isPromotionsEnabled }: WaitingListProps) => {
37
+ const WaitingList = ({ tickets = {}, eventId }: WaitingListProps) => {
39
38
  const [showSuccessMessage, setShowSuccessMessage] = useState(false)
40
39
  const [loading, setLoading] = useState(false)
41
40
  const ticketTypesList = Object.values(tickets).map((d: any) => ({
@@ -68,16 +67,11 @@ const WaitingList = ({ tickets = {}, eventId, isPromotionsEnabled }: WaitingList
68
67
  <div className="waiting-list">
69
68
  {showSuccessMessage ? (
70
69
  <div className="success-message">
71
- <p className="added-success-message">{`You've been added to the waiting list!`}</p>
72
- <p>{`You'll be notified if tickets become available.`}</p>
70
+ <p className="added-success-message">You've been added to the waiting list!</p>
71
+ <p>You'll be notified if tickets become available.</p>
73
72
  </div>
74
73
  ) : (
75
74
  <>
76
- {!isPromotionsEnabled && (
77
- <p className="no-tickets-text">
78
- No tickets are currently available for this event.
79
- </p>
80
- )}
81
75
  <h2>WAITING LIST</h2>
82
76
  <Formik
83
77
  initialValues={{
@@ -10,8 +10,9 @@
10
10
  .waiting-list .waiting-list-button:hover {
11
11
  background-color: #505050;
12
12
  }
13
- .waiting-list .success-message h3 {
14
- margin: 10px 0;
13
+ .waiting-list .success-message {
14
+ text-align: center;
15
+ margin-bottom: 15px;
15
16
  }
16
17
  .waiting-list .success-message p {
17
18
  margin: 0;
@@ -22,4 +23,5 @@
22
23
  }
23
24
  .waiting-list .added-success-message {
24
25
  font-size: 22px;
26
+ margin-bottom: 10px;
25
27
  }
@@ -0,0 +1,51 @@
1
+ import _get from 'lodash/get'
2
+
3
+ interface ICheckoutBody {
4
+ attributes: {
5
+ [key: string]: any;
6
+ };
7
+ }
8
+
9
+ interface IticketHolder {
10
+ first_name?: string;
11
+ last_name?: string;
12
+ phone?: string;
13
+ email?: string;
14
+ }
15
+
16
+ export const createCheckoutDataBodyWithDefaultHolder = (
17
+ ticketsQuantity: number,
18
+ logedInValues: {}
19
+ ): ICheckoutBody => {
20
+ const ticket_holders: IticketHolder[] = []
21
+
22
+ const first_name =
23
+ _get(logedInValues, 'firstName') || _get(logedInValues, 'first_name') || ''
24
+ const last_name =
25
+ _get(logedInValues, 'lastName', '') ||
26
+ _get(logedInValues, 'last_name') ||
27
+ ''
28
+ const phone = _get(logedInValues, 'phone', '')
29
+ const email = _get(logedInValues, 'email', '')
30
+
31
+ for (let i = 0; i <= ticketsQuantity - 1; i++) {
32
+ const individualHolder = i
33
+ ? { first_name: '', last_name: '', phone: '', email: '' }
34
+ : { first_name, last_name, phone, email }
35
+
36
+ ticket_holders.push(individualHolder)
37
+ }
38
+
39
+ const body: ICheckoutBody = {
40
+ attributes: {
41
+ ...logedInValues,
42
+ email,
43
+ confirm_email: email,
44
+ first_name,
45
+ last_name,
46
+ ticket_holders,
47
+ },
48
+ }
49
+
50
+ return body
51
+ }
@@ -0,0 +1,22 @@
1
+ export const downloadPDF = (pdfUrl: string) => {
2
+ if (typeof window === 'undefined') return
3
+
4
+ const accessToken: string | null = localStorage.getItem('access_token')
5
+
6
+ if (!accessToken) return
7
+
8
+ fetch(pdfUrl, {
9
+ headers: {
10
+ Authorization: `Bearer ${accessToken}`,
11
+ },
12
+ })
13
+ .then(async response => {
14
+ const blobValue = await response.blob()
15
+ return blobValue
16
+ })
17
+ .then(blobValue => {
18
+ const file = new Blob([blobValue], { type: 'application/pdf' })
19
+ const fileURL = URL.createObjectURL(file)
20
+ window.open(fileURL)
21
+ })
22
+ }
@@ -1,3 +1,5 @@
1
1
  export { setConfigs, CONFIGS } from './setConfigs'
2
2
  export { getQueryVariable } from './getQueryVariable'
3
3
  export { ErrorFocus } from './formikErrorFocus'
4
+ export { downloadPDF } from './downloadPDF'
5
+ export { createCheckoutDataBodyWithDefaultHolder } from './createCheckoutDataBodyWithDefaultHolder'