tf-checkout-react 1.0.43 → 1.0.47

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 (43) hide show
  1. package/dist/api/index.d.ts +2 -0
  2. package/dist/components/billing-info-container/index.d.ts +22 -3
  3. package/dist/components/billing-info-container/utils.d.ts +2 -2
  4. package/dist/components/common/CheckboxField.d.ts +2 -2
  5. package/dist/components/confirmationContainer/index.d.ts +4 -1
  6. package/dist/components/loginModal/index.d.ts +8 -4
  7. package/dist/components/paymentContainer/index.d.ts +5 -1
  8. package/dist/components/registerModal/index.d.ts +3 -0
  9. package/dist/components/ticketsContainer/TicketRow.d.ts +10 -0
  10. package/dist/components/ticketsContainer/TicketsSection.d.ts +10 -0
  11. package/dist/components/ticketsContainer/index.d.ts +7 -1
  12. package/dist/components/ticketsContainer/utils.d.ts +4 -0
  13. package/dist/components/waitingList/index.d.ts +7 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/tf-checkout-react.cjs.development.css +3 -2
  16. package/dist/tf-checkout-react.cjs.development.js +827 -269
  17. package/dist/tf-checkout-react.cjs.development.js.map +1 -1
  18. package/dist/tf-checkout-react.cjs.production.min.js +1 -1
  19. package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
  20. package/dist/tf-checkout-react.esm.js +832 -272
  21. package/dist/tf-checkout-react.esm.js.map +1 -1
  22. package/dist/types/billing-info-data.d.ts +2 -2
  23. package/dist/validators/index.d.ts +2 -1
  24. package/package.json +4 -1
  25. package/src/api/index.ts +7 -3
  26. package/src/components/billing-info-container/index.tsx +278 -70
  27. package/src/components/billing-info-container/style.css +15 -0
  28. package/src/components/billing-info-container/utils.ts +41 -13
  29. package/src/components/common/CheckboxField.tsx +3 -2
  30. package/src/components/common/CustomField.tsx +16 -1
  31. package/src/components/confirmationContainer/index.tsx +8 -3
  32. package/src/components/loginModal/index.tsx +46 -13
  33. package/src/components/paymentContainer/index.tsx +10 -0
  34. package/src/components/registerModal/index.tsx +20 -3
  35. package/src/components/ticketsContainer/TicketRow.tsx +86 -0
  36. package/src/components/ticketsContainer/TicketsSection.tsx +82 -0
  37. package/src/components/ticketsContainer/index.tsx +98 -210
  38. package/src/components/ticketsContainer/utils.ts +11 -0
  39. package/src/components/waitingList/index.tsx +162 -0
  40. package/src/components/waitingList/style.css +18 -0
  41. package/src/index.ts +2 -1
  42. package/src/types/billing-info-data.ts +2 -2
  43. package/src/validators/index.ts +9 -3
@@ -1,151 +1,19 @@
1
1
  import React, { useState, useEffect } from 'react'
2
+ import axios, { AxiosError } from 'axios'
2
3
  import './style.css'
3
4
 
4
5
  import { getTickets, addToCart, setCustomHeader } from '../../api'
5
- import _sortBy from 'lodash/sortBy'
6
6
  import _get from 'lodash/get'
7
- import _has from 'lodash/has'
8
7
  import _some from 'lodash/some'
9
8
  import _find from 'lodash/find'
10
9
  import _isEmpty from 'lodash/isEmpty'
11
10
  import _filter from 'lodash/filter'
12
11
  import _isObject from 'lodash/isObject'
13
- import Box from '@mui/material/Box'
14
- import FormControl from '@mui/material/FormControl'
15
- import MenuItem from '@mui/material/MenuItem'
16
- import Select from '@mui/material/Select'
17
12
  import CircularProgress from '@mui/material/CircularProgress'
18
13
  import Button from 'react-bootstrap/Button'
19
14
  import jwt_decode from 'jwt-decode'
20
-
21
- const getTicketSelectOptions = (
22
- maxCount: number = 10,
23
- minCount: number = 1,
24
- multiplier: number = 1
25
- ) => {
26
- const options = [{ label: 0, value: 0 }]
27
- for (let i = minCount; i <= Math.min(50, maxCount); i += multiplier) {
28
- options.push({ label: i, value: i })
29
- }
30
- return options
31
- }
32
-
33
- const computeTierStateLabel = (
34
- tier: any,
35
- prevTier: any,
36
- selectedTickets: any,
37
- handleTicketSelect: any
38
- ) => {
39
- const soldOutMessage = tier.soldOutMessage
40
- ? `${tier.soldOutMessage}`.toUpperCase()
41
- : 'SOLD OUT'
42
- const isSalesClosed =
43
- !tier.salesStarted ||
44
- tier.salesEnded ||
45
- !_has(tier, 'maxQuantity') ||
46
- tier.maxQuantity === 0
47
- const options = getTicketSelectOptions(
48
- tier.maxQuantity,
49
- tier.minQuantity,
50
- tier.multiplier
51
- )
52
-
53
- const onSaleContent = isSalesClosed ? null : (
54
- <div className="get-tickets">
55
- <Box className="get-tickets__selectbox">
56
- <FormControl fullWidth>
57
- <Select
58
- value={selectedTickets[tier.id] ? selectedTickets[tier.id] : 0}
59
- onChange={handleTicketSelect}
60
- displayEmpty
61
- inputProps={{ 'aria-label': 'Without label' }}
62
- MenuProps={{
63
- PaperProps: {
64
- sx: { maxHeight: 150 },
65
- className: 'get-tickets-paper',
66
- },
67
- }}
68
- >
69
- {options.map((option, index) => (
70
- <MenuItem key={index} value={option.value}>
71
- {option.value}
72
- </MenuItem>
73
- ))}
74
- </Select>
75
- </FormControl>
76
- </Box>
77
- </div>
78
- )
79
-
80
- if (tier.sold_out || !tier.displayTicket || tier.soldOut)
81
- return soldOutMessage
82
- if (!+tier.cost && !+tier.price) return 'FREE'
83
- if (tier.displayTicket) return onSaleContent
84
- if (prevTier.in_stock) return 'SOON'
85
-
86
- return ''
87
- }
88
-
89
- const renderTiers = (
90
- boxOffice: any,
91
- selectedTickets: any,
92
- handleTicketSelect: any,
93
- promoCodeIsApplied: boolean
94
- ) => {
95
- const sortedBoxOfiice = _sortBy(boxOffice, 'sortOrder')
96
- const primaryTiers = sortedBoxOfiice.map((tier, i, arr) => {
97
- const isSoldOut = tier.sold_out || !tier.displayTicket || tier.soldOut
98
- const ticketSelect = (event: any) => {
99
- const { value } = event.target
100
- handleTicketSelect(tier.id, value)
101
- }
102
-
103
- let ticketIsDiscounted = false
104
- if (
105
- tier.oldPrice &&
106
- promoCodeIsApplied &&
107
- !isSoldOut &&
108
- tier.oldPrice !== tier.price
109
- ) {
110
- ticketIsDiscounted = true
111
- }
112
-
113
- return (
114
- <div
115
- key={tier.id || tier.name}
116
- className={`event-detail__tier ${isSoldOut ? 'disabled' : ''}`}
117
- >
118
- <div className="event-detail__tier-name">
119
- {tier.displayName || tier.name}
120
- </div>
121
- <div className="event-tickets-container">
122
- <div className="event-detail__tier-price">
123
- {ticketIsDiscounted && (
124
- <p className="old-price">$ {(+tier.oldPrice).toFixed(2)}</p>
125
- )}
126
- <p>
127
- {isSoldOut
128
- ? 'SOLD OUT'
129
- : `$ ${(+tier.cost || +tier.price).toFixed(2)}`}
130
- </p>
131
- {!isSoldOut && (
132
- <p className='fees'>{tier.taxesIncluded ? '(incl. Fees)' : '(excl. Fees)'}</p>
133
- )}
134
- </div>
135
- <div className="event-detail__tier-state" style={{ minWidth: 55 }}>
136
- {computeTierStateLabel(
137
- tier,
138
- arr[i - 1],
139
- selectedTickets,
140
- ticketSelect
141
- )}
142
- </div>
143
- </div>
144
- </div>
145
- )
146
- })
147
- return primaryTiers
148
- }
15
+ import { TicketsSection } from './TicketsSection'
16
+ import WaitingList from '../waitingList'
149
17
 
150
18
  function Loader() {
151
19
  return (
@@ -159,6 +27,7 @@ interface CartSuccess {
159
27
  skip_billing_page: boolean;
160
28
  names_required: boolean;
161
29
  age_required: boolean;
30
+ event_id: string;
162
31
  }
163
32
 
164
33
  export interface IGetTickets {
@@ -166,6 +35,11 @@ export interface IGetTickets {
166
35
  onAddToCartSuccess: (response: CartSuccess) => void;
167
36
  getTicketsLabel?: string;
168
37
  contentStyle?: React.CSSProperties;
38
+ onAddToCartError: (e: AxiosError) => void;
39
+ onGetTicketsSuccess: (response: any) => void;
40
+ onGetTicketsError: (e: AxiosError) => void;
41
+
42
+ theme?: 'light' | 'dark';
169
43
  }
170
44
 
171
45
  export interface ITicket {
@@ -182,11 +56,16 @@ export const TicketsContainer = ({
182
56
  eventId,
183
57
  onAddToCartSuccess,
184
58
  contentStyle = {},
59
+ onAddToCartError = () => {},
60
+ onGetTicketsSuccess = () => {},
61
+ onGetTicketsError = () => {},
62
+ theme = 'light',
185
63
  }: IGetTickets) => {
186
64
  const [selectedTickets, setSelectedTickets] = useState(
187
65
  {} as ISelectedTickets
188
66
  )
189
67
  const [tickets, setTickets] = useState([] as ITicket[])
68
+ const [showWaitingList, setShowWaitingList] = useState(false)
190
69
  const [isLoading, setIsLoading] = useState(false)
191
70
  const [handleBookIsLoading, setHandleBookIsLoading] = useState(false)
192
71
  const [promoCode, setPromoCode] = useState('')
@@ -216,14 +95,14 @@ export const TicketsContainer = ({
216
95
  setCustomHeader(response)
217
96
  const attributes = _get(response, 'data.data.attributes')
218
97
  setPromoCodeIsApplied(attributes.ValidPromoCode)
219
- const tickets = _filter(
220
- (Object.values(attributes) || []) as ITicket[],
221
- item => _isObject(item)
222
- )
223
- setTickets(tickets)
98
+ setTickets(_get(attributes, 'tickets'))
99
+ setShowWaitingList(attributes.showWaitingList)
100
+ onGetTicketsSuccess(response.data)
224
101
  }
225
102
  } catch (e) {
226
- console.log('e', e)
103
+ if (axios.isAxiosError(e)) {
104
+ onGetTicketsError(e)
105
+ }
227
106
  } finally {
228
107
  setIsLoading(false)
229
108
  }
@@ -280,10 +159,13 @@ export const TicketsContainer = ({
280
159
  names_required:
281
160
  result?.data?.data?.attributes?.names_required ?? false,
282
161
  age_required: result?.data?.data?.attributes?.age_required ?? false,
162
+ event_id: String(eventId)
283
163
  })
284
164
  }
285
165
  } catch (e) {
286
- console.log('e', e)
166
+ if (axios.isAxiosError(e)) {
167
+ onAddToCartError(e)
168
+ }
287
169
  } finally {
288
170
  setHandleBookIsLoading(false)
289
171
  }
@@ -295,80 +177,86 @@ export const TicketsContainer = ({
295
177
  )
296
178
 
297
179
  return (
298
- <div className="get-tickets-page" style={contentStyle}>
180
+ <div className={`get-tickets-page ${theme}`} style={contentStyle}>
299
181
  {isLoading ? (
300
182
  <Loader />
301
183
  ) : (
302
- <div>
303
- {renderTiers(
304
- tickets,
305
- selectedTickets,
306
- handleTicketSelect,
307
- promoCodeIsApplied
308
- )}
309
- {promoCodeIsApplied ? (
310
- <div className="alert-info">
311
- Your promo code was applied successfully.
312
- </div>
313
- ) : null}
314
- {showPromoInput && (
315
- <div className="promo-code-block">
316
- <input
317
- placeholder="Promo Code"
318
- onChange={e => {
319
- setPromoCode(e.target.value)
320
- }}
321
- onKeyPress={event => {
322
- if (event.key === 'Enter') {
323
- setPromoCodeUpdated(promoCode)
324
- }
325
- }}
184
+ <>
185
+ {showWaitingList ? (
186
+ <WaitingList tickets={tickets} />
187
+ ) : (
188
+ <div>
189
+ <TicketsSection
190
+ ticketsList={tickets}
191
+ selectedTickets={selectedTickets}
192
+ handleTicketSelect={handleTicketSelect}
193
+ promoCodeIsApplied={promoCodeIsApplied}
326
194
  />
327
- <Button
328
- className="promo-apply-button"
329
- placeholder="Promo Code"
330
- onClick={() => {
331
- setPromoCodeUpdated(promoCode)
332
- }}
333
- >
334
- Apply
335
- </Button>
195
+ {promoCodeIsApplied ? (
196
+ <div className="alert-info">
197
+ Your promo code was applied successfully.
198
+ </div>
199
+ ) : null}
200
+ {showPromoInput && (
201
+ <div className="promo-code-block">
202
+ <input
203
+ placeholder="Promo Code"
204
+ onChange={e => {
205
+ setPromoCode(e.target.value)
206
+ }}
207
+ onKeyPress={event => {
208
+ if (event.key === 'Enter') {
209
+ setPromoCodeUpdated(promoCode)
210
+ }
211
+ }}
212
+ />
213
+ <Button
214
+ className="promo-apply-button"
215
+ placeholder="Promo Code"
216
+ onClick={() => {
217
+ setPromoCodeUpdated(promoCode)
218
+ }}
219
+ >
220
+ Apply
221
+ </Button>
222
+ </div>
223
+ )}
224
+ {!showPromoInput && !isAllTicketsSoldOut ? (
225
+ <Button
226
+ className="promo-code-button"
227
+ placeholder="Promo Codes"
228
+ onClick={() => {
229
+ setShowPromoInput(true)
230
+ }}
231
+ >
232
+ Got a promo code? Click here
233
+ </Button>
234
+ ) : null}
235
+ <div className="test v1.0.19" style={{ display: 'none' }} />
236
+ {!isAllTicketsSoldOut && (
237
+ <Button
238
+ aria-hidden={true}
239
+ className={`book-button ${
240
+ handleBookIsLoading ||
241
+ _isEmpty(selectedTickets) ||
242
+ Object.values(selectedTickets)[0] === 0
243
+ ? 'disabled'
244
+ : ''
245
+ }`}
246
+ onClick={
247
+ !handleBookIsLoading &&
248
+ !_isEmpty(selectedTickets) &&
249
+ Object.values(selectedTickets)[0] > 0
250
+ ? handleBook
251
+ : () => {}
252
+ }
253
+ >
254
+ {getTicketsLabel || 'GET TICKETS'}
255
+ </Button>
256
+ )}
336
257
  </div>
337
258
  )}
338
- {!showPromoInput && !isAllTicketsSoldOut ? (
339
- <Button
340
- className="promo-code-button"
341
- placeholder="Promo Codes"
342
- onClick={() => {
343
- setShowPromoInput(true)
344
- }}
345
- >
346
- Got a promo code? Click here
347
- </Button>
348
- ) : null}
349
- <div className="test v1.0.19" style={{ display: 'none' }} />
350
- {!isAllTicketsSoldOut && (
351
- <Button
352
- aria-hidden={true}
353
- className={`book-button ${
354
- handleBookIsLoading ||
355
- _isEmpty(selectedTickets) ||
356
- Object.values(selectedTickets)[0] === 0
357
- ? 'disabled'
358
- : ''
359
- }`}
360
- onClick={
361
- !handleBookIsLoading &&
362
- !_isEmpty(selectedTickets) &&
363
- Object.values(selectedTickets)[0] > 0
364
- ? handleBook
365
- : () => {}
366
- }
367
- >
368
- {getTicketsLabel || 'GET TICKETS'}
369
- </Button>
370
- )}
371
- </div>
259
+ </>
372
260
  )}
373
261
  </div>
374
262
  )
@@ -0,0 +1,11 @@
1
+ export const getTicketSelectOptions = (
2
+ maxCount: number = 10,
3
+ minCount: number = 1,
4
+ multiplier: number = 1
5
+ ) => {
6
+ const options = [{ label: 0, value: 0 }]
7
+ for (let i = minCount; i <= Math.min(50, maxCount); i += multiplier) {
8
+ options.push({ label: i, value: i })
9
+ }
10
+ return options
11
+ }
@@ -0,0 +1,162 @@
1
+ import React, { useState } from 'react'
2
+ import Button from '@mui/material/Button'
3
+ import CircularProgress from '@mui/material/CircularProgress'
4
+ import { Field, Form, Formik } from 'formik'
5
+ import { CustomField } from '../common/CustomField'
6
+ import { addToWaitingList } from '../../api'
7
+ import {
8
+ combineValidators,
9
+ requiredValidator,
10
+ emailValidator,
11
+ } from '../../validators'
12
+ import { ENV } from '../../env'
13
+
14
+ import './style.css'
15
+
16
+ interface WaitingListProps {
17
+ tickets: any;
18
+ }
19
+
20
+ interface WaitingListFields {
21
+ ticketTypeId: string;
22
+ quantity: string;
23
+ firstName: string;
24
+ lastName: string;
25
+ email: string;
26
+ }
27
+
28
+ const generateQuantity = (n: number) => {
29
+ const quantityList = []
30
+ for (let i = 1; i <= n; i++) {
31
+ quantityList.push({ label: i, value: i })
32
+ }
33
+ return quantityList
34
+ }
35
+
36
+ const WaitingList = ({ tickets = {} }: WaitingListProps) => {
37
+ const [showSuccessMessage, setShowSuccessMessage] = useState(false)
38
+ const [loading, setLoading] = useState(false)
39
+ const ticketTypesList = Object.values(tickets).map((d: any) => ({
40
+ label: d.displayName,
41
+ value: d.id,
42
+ }))
43
+
44
+ const handleSubmit = async (values: WaitingListFields) => {
45
+ try {
46
+ setLoading(true)
47
+ const id = ENV.EVENT_ID
48
+ const requestData = {
49
+ data: {
50
+ attributes: values,
51
+ },
52
+ }
53
+ const { data } = await addToWaitingList(id, requestData)
54
+
55
+ if (data.success) {
56
+ setShowSuccessMessage(true)
57
+ }
58
+ } catch (error) {
59
+ console.log(error)
60
+ } finally {
61
+ setLoading(false)
62
+ }
63
+ }
64
+
65
+ return (
66
+ <div className="waiting-list">
67
+ {showSuccessMessage ? (
68
+ <div className="success-message">
69
+ <h3>{`You've been added to the waiting list!`}</h3>
70
+ <p>{`You'll be notified if tickets become available.`}</p>
71
+ </div>
72
+ ) : (
73
+ <>
74
+ <h2>WAITING LIST</h2>
75
+ <Formik
76
+ initialValues={{
77
+ ticketTypeId: '',
78
+ quantity: '',
79
+ firstName: '',
80
+ lastName: '',
81
+ email: '',
82
+ }}
83
+ onSubmit={handleSubmit}
84
+ >
85
+ <Form>
86
+ <div className="field-item">
87
+ <Field
88
+ name="ticketTypeId"
89
+ label="Ticket types"
90
+ type="select"
91
+ component={CustomField}
92
+ selectOptions={[
93
+ { label: 'Type of Ticket', value: '', disabled: true },
94
+ ...ticketTypesList,
95
+ ]}
96
+ />
97
+ </div>
98
+ <div className="field-item">
99
+ <Field
100
+ name="quantity"
101
+ label="Quantity"
102
+ type="select"
103
+ component={CustomField}
104
+ selectOptions={[
105
+ { label: 'Quantity Requested', value: '', disabled: true },
106
+ ...generateQuantity(10),
107
+ ]}
108
+ />
109
+ </div>
110
+ <div className="field-item">
111
+ <Field
112
+ name="firstName"
113
+ label="First name"
114
+ validate={(value: string) =>
115
+ requiredValidator(value, 'Please enter your First name')
116
+ }
117
+ component={CustomField}
118
+ />
119
+ </div>
120
+ <div className="field-item">
121
+ <Field
122
+ name="lastName"
123
+ label="Last name"
124
+ validate={(value: string) =>
125
+ requiredValidator(value, 'Please enter your Last name')
126
+ }
127
+ component={CustomField}
128
+ />
129
+ </div>
130
+ <div className="field-item">
131
+ <Field
132
+ name="email"
133
+ label="Email"
134
+ validate={combineValidators(
135
+ (value: string) =>
136
+ requiredValidator(value, 'Please enter your Email'),
137
+ (value: string) => emailValidator(value)
138
+ )}
139
+ component={CustomField}
140
+ />
141
+ </div>
142
+
143
+ <Button
144
+ type="submit"
145
+ variant="contained"
146
+ className="waiting-list-button"
147
+ >
148
+ {loading ? (
149
+ <CircularProgress size="22px" />
150
+ ) : (
151
+ 'ADD TO WAITING LIST'
152
+ )}
153
+ </Button>
154
+ </Form>
155
+ </Formik>
156
+ </>
157
+ )}
158
+ </div>
159
+ )
160
+ }
161
+
162
+ export default WaitingList
@@ -0,0 +1,18 @@
1
+ .waiting-list {
2
+ padding: 17px 35px 20px;
3
+ }
4
+ .waiting-list .field-item {
5
+ margin-bottom: 30px;
6
+ }
7
+ .waiting-list .waiting-list-button {
8
+ width: 100% !important;
9
+ }
10
+ .waiting-list .waiting-list-button:hover {
11
+ background-color: #505050;
12
+ }
13
+ .waiting-list .success-message h3 {
14
+ margin: 10px 0;
15
+ }
16
+ .waiting-list .success-message p {
17
+ margin: 0;
18
+ }
package/src/index.ts CHANGED
@@ -2,4 +2,5 @@ export { BillingInfoContainer } from './components/billing-info-container/index'
2
2
  export { PaymentContainer } from './components/paymentContainer/index'
3
3
  export { ConfirmationContainer } from './components/confirmationContainer/index'
4
4
  export { TicketsContainer } from './components/ticketsContainer/index'
5
- export { currencyNormalizerCreator, createFixedFloatNormalizer } from './normalizers'
5
+ export { currencyNormalizerCreator, createFixedFloatNormalizer } from './normalizers'
6
+ export { LoginModal } from './components/loginModal'
@@ -2,7 +2,7 @@ import { ReactNode } from 'react'
2
2
 
3
3
  export interface IGroupItem {
4
4
  name: string;
5
- label: string;
5
+ label: string | JSX.Element;
6
6
 
7
7
  // optional
8
8
  type?: string;
@@ -29,6 +29,6 @@ export interface IBillingInfoData {
29
29
  fields: IFieldData[];
30
30
 
31
31
  // optional
32
- label?: string;
32
+ label?: string | JSX.Element;
33
33
  labelClassName?: string;
34
34
  }
@@ -1,14 +1,20 @@
1
+ const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
2
+
1
3
  export const combineValidators = (...validators: any) => (...value: any) => {
2
4
  for (let i = 0; i < validators.length; ++i) {
3
5
  const error_message = validators[i](...value)
4
- if (error_message !== undefined) return error_message
6
+ if (error_message) return error_message
5
7
  }
6
8
  }
7
9
 
8
- export const requiredValidator = (value?: string | number): string => {
10
+ export const requiredValidator = (value?: string | number, message?: string): string => {
9
11
  let errorMessage = ''
10
12
  if (!value) {
11
- errorMessage = 'Required'
13
+ errorMessage = message || 'Required'
12
14
  }
13
15
  return errorMessage
14
16
  }
17
+
18
+ export const emailValidator = (email: string) => {
19
+ return !emailRegex.test(email) ? 'Please enter a valid email address' : ''
20
+ }