tf-checkout-react 1.0.42 → 1.0.46

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 (45) hide show
  1. package/dist/api/index.d.ts +1 -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 +6 -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 +688 -203
  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 +693 -206
  21. package/dist/tf-checkout-react.esm.js.map +1 -1
  22. package/dist/types/billing-info-data.d.ts +2 -1
  23. package/dist/validators/index.d.ts +2 -1
  24. package/package.json +3 -1
  25. package/src/.DS_Store +0 -0
  26. package/src/api/index.ts +5 -3
  27. package/src/components/.DS_Store +0 -0
  28. package/src/components/billing-info-container/index.tsx +239 -63
  29. package/src/components/billing-info-container/style.css +15 -0
  30. package/src/components/billing-info-container/utils.ts +41 -13
  31. package/src/components/common/CheckboxField.tsx +3 -2
  32. package/src/components/common/CustomField.tsx +16 -1
  33. package/src/components/confirmationContainer/index.tsx +8 -3
  34. package/src/components/loginModal/index.tsx +46 -13
  35. package/src/components/paymentContainer/index.tsx +10 -0
  36. package/src/components/registerModal/index.tsx +20 -3
  37. package/src/components/ticketsContainer/TicketRow.tsx +86 -0
  38. package/src/components/ticketsContainer/TicketsSection.tsx +82 -0
  39. package/src/components/ticketsContainer/index.tsx +96 -210
  40. package/src/components/ticketsContainer/utils.ts +11 -0
  41. package/src/components/waitingList/index.tsx +162 -0
  42. package/src/components/waitingList/style.css +18 -0
  43. package/src/index.ts +2 -1
  44. package/src/types/billing-info-data.ts +2 -1
  45. 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 (
@@ -166,6 +34,11 @@ export interface IGetTickets {
166
34
  onAddToCartSuccess: (response: CartSuccess) => void;
167
35
  getTicketsLabel?: string;
168
36
  contentStyle?: React.CSSProperties;
37
+ onAddToCartError: (e: AxiosError) => void;
38
+ onGetTicketsSuccess: (response: any) => void;
39
+ onGetTicketsError: (e: AxiosError) => void;
40
+
41
+ theme?: 'light' | 'dark';
169
42
  }
170
43
 
171
44
  export interface ITicket {
@@ -182,11 +55,16 @@ export const TicketsContainer = ({
182
55
  eventId,
183
56
  onAddToCartSuccess,
184
57
  contentStyle = {},
58
+ onAddToCartError = () => {},
59
+ onGetTicketsSuccess = () => {},
60
+ onGetTicketsError = () => {},
61
+ theme = 'light',
185
62
  }: IGetTickets) => {
186
63
  const [selectedTickets, setSelectedTickets] = useState(
187
64
  {} as ISelectedTickets
188
65
  )
189
66
  const [tickets, setTickets] = useState([] as ITicket[])
67
+ const [showWaitingList, setShowWaitingList] = useState(false)
190
68
  const [isLoading, setIsLoading] = useState(false)
191
69
  const [handleBookIsLoading, setHandleBookIsLoading] = useState(false)
192
70
  const [promoCode, setPromoCode] = useState('')
@@ -216,14 +94,14 @@ export const TicketsContainer = ({
216
94
  setCustomHeader(response)
217
95
  const attributes = _get(response, 'data.data.attributes')
218
96
  setPromoCodeIsApplied(attributes.ValidPromoCode)
219
- const tickets = _filter(
220
- (Object.values(attributes) || []) as ITicket[],
221
- item => _isObject(item)
222
- )
223
- setTickets(tickets)
97
+ setTickets(_get(attributes, 'tickets'))
98
+ setShowWaitingList(attributes.showWaitingList)
99
+ onGetTicketsSuccess(response.data)
224
100
  }
225
101
  } catch (e) {
226
- console.log('e', e)
102
+ if (axios.isAxiosError(e)) {
103
+ onGetTicketsError(e)
104
+ }
227
105
  } finally {
228
106
  setIsLoading(false)
229
107
  }
@@ -283,7 +161,9 @@ export const TicketsContainer = ({
283
161
  })
284
162
  }
285
163
  } catch (e) {
286
- console.log('e', e)
164
+ if (axios.isAxiosError(e)) {
165
+ onAddToCartError(e)
166
+ }
287
167
  } finally {
288
168
  setHandleBookIsLoading(false)
289
169
  }
@@ -295,80 +175,86 @@ export const TicketsContainer = ({
295
175
  )
296
176
 
297
177
  return (
298
- <div className="get-tickets-page" style={contentStyle}>
178
+ <div className={`get-tickets-page ${theme}`} style={contentStyle}>
299
179
  {isLoading ? (
300
180
  <Loader />
301
181
  ) : (
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
- }}
182
+ <>
183
+ {showWaitingList ? (
184
+ <WaitingList tickets={tickets} />
185
+ ) : (
186
+ <div>
187
+ <TicketsSection
188
+ ticketsList={tickets}
189
+ selectedTickets={selectedTickets}
190
+ handleTicketSelect={handleTicketSelect}
191
+ promoCodeIsApplied={promoCodeIsApplied}
326
192
  />
327
- <Button
328
- className="promo-apply-button"
329
- placeholder="Promo Code"
330
- onClick={() => {
331
- setPromoCodeUpdated(promoCode)
332
- }}
333
- >
334
- Apply
335
- </Button>
193
+ {promoCodeIsApplied ? (
194
+ <div className="alert-info">
195
+ Your promo code was applied successfully.
196
+ </div>
197
+ ) : null}
198
+ {showPromoInput && (
199
+ <div className="promo-code-block">
200
+ <input
201
+ placeholder="Promo Code"
202
+ onChange={e => {
203
+ setPromoCode(e.target.value)
204
+ }}
205
+ onKeyPress={event => {
206
+ if (event.key === 'Enter') {
207
+ setPromoCodeUpdated(promoCode)
208
+ }
209
+ }}
210
+ />
211
+ <Button
212
+ className="promo-apply-button"
213
+ placeholder="Promo Code"
214
+ onClick={() => {
215
+ setPromoCodeUpdated(promoCode)
216
+ }}
217
+ >
218
+ Apply
219
+ </Button>
220
+ </div>
221
+ )}
222
+ {!showPromoInput && !isAllTicketsSoldOut ? (
223
+ <Button
224
+ className="promo-code-button"
225
+ placeholder="Promo Codes"
226
+ onClick={() => {
227
+ setShowPromoInput(true)
228
+ }}
229
+ >
230
+ Got a promo code? Click here
231
+ </Button>
232
+ ) : null}
233
+ <div className="test v1.0.19" style={{ display: 'none' }} />
234
+ {!isAllTicketsSoldOut && (
235
+ <Button
236
+ aria-hidden={true}
237
+ className={`book-button ${
238
+ handleBookIsLoading ||
239
+ _isEmpty(selectedTickets) ||
240
+ Object.values(selectedTickets)[0] === 0
241
+ ? 'disabled'
242
+ : ''
243
+ }`}
244
+ onClick={
245
+ !handleBookIsLoading &&
246
+ !_isEmpty(selectedTickets) &&
247
+ Object.values(selectedTickets)[0] > 0
248
+ ? handleBook
249
+ : () => {}
250
+ }
251
+ >
252
+ {getTicketsLabel || 'GET TICKETS'}
253
+ </Button>
254
+ )}
336
255
  </div>
337
256
  )}
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>
257
+ </>
372
258
  )}
373
259
  </div>
374
260
  )
@@ -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'
@@ -9,7 +9,7 @@ export interface IGroupItem {
9
9
  className?: string;
10
10
  required?: boolean;
11
11
  component?: ReactNode | JSX.Element | HTMLElement;
12
- onValidate: (value: any) => void;
12
+ onValidate?: (value: any) => void;
13
13
 
14
14
  // aditional props
15
15
  [key: string]: any;
@@ -22,6 +22,7 @@ export interface IFieldData {
22
22
  groupClassname?: string;
23
23
  groupLabel?: string | JSX.Element | HTMLElement;
24
24
  groupLabelClassName?: string;
25
+ id: number;
25
26
  }
26
27
  export interface IBillingInfoData {
27
28
  id: number | string;
@@ -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
+ }