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.
- package/dist/api/index.d.ts +2 -0
- package/dist/components/billing-info-container/index.d.ts +22 -3
- package/dist/components/billing-info-container/utils.d.ts +2 -2
- package/dist/components/common/CheckboxField.d.ts +2 -2
- package/dist/components/confirmationContainer/index.d.ts +4 -1
- package/dist/components/loginModal/index.d.ts +8 -4
- package/dist/components/paymentContainer/index.d.ts +5 -1
- package/dist/components/registerModal/index.d.ts +3 -0
- package/dist/components/ticketsContainer/TicketRow.d.ts +10 -0
- package/dist/components/ticketsContainer/TicketsSection.d.ts +10 -0
- package/dist/components/ticketsContainer/index.d.ts +7 -1
- package/dist/components/ticketsContainer/utils.d.ts +4 -0
- package/dist/components/waitingList/index.d.ts +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/tf-checkout-react.cjs.development.css +3 -2
- package/dist/tf-checkout-react.cjs.development.js +827 -269
- package/dist/tf-checkout-react.cjs.development.js.map +1 -1
- package/dist/tf-checkout-react.cjs.production.min.js +1 -1
- package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
- package/dist/tf-checkout-react.esm.js +832 -272
- package/dist/tf-checkout-react.esm.js.map +1 -1
- package/dist/types/billing-info-data.d.ts +2 -2
- package/dist/validators/index.d.ts +2 -1
- package/package.json +4 -1
- package/src/api/index.ts +7 -3
- package/src/components/billing-info-container/index.tsx +278 -70
- package/src/components/billing-info-container/style.css +15 -0
- package/src/components/billing-info-container/utils.ts +41 -13
- package/src/components/common/CheckboxField.tsx +3 -2
- package/src/components/common/CustomField.tsx +16 -1
- package/src/components/confirmationContainer/index.tsx +8 -3
- package/src/components/loginModal/index.tsx +46 -13
- package/src/components/paymentContainer/index.tsx +10 -0
- package/src/components/registerModal/index.tsx +20 -3
- package/src/components/ticketsContainer/TicketRow.tsx +86 -0
- package/src/components/ticketsContainer/TicketsSection.tsx +82 -0
- package/src/components/ticketsContainer/index.tsx +98 -210
- package/src/components/ticketsContainer/utils.ts +11 -0
- package/src/components/waitingList/index.tsx +162 -0
- package/src/components/waitingList/style.css +18 -0
- package/src/index.ts +2 -1
- package/src/types/billing-info-data.ts +2 -2
- 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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
223
|
-
setTickets(tickets)
|
|
98
|
+
setTickets(_get(attributes, 'tickets'))
|
|
99
|
+
setShowWaitingList(attributes.showWaitingList)
|
|
100
|
+
onGetTicketsSuccess(response.data)
|
|
224
101
|
}
|
|
225
102
|
} catch (e) {
|
|
226
|
-
|
|
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
|
-
|
|
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=
|
|
180
|
+
<div className={`get-tickets-page ${theme}`} style={contentStyle}>
|
|
299
181
|
{isLoading ? (
|
|
300
182
|
<Loader />
|
|
301
183
|
) : (
|
|
302
|
-
|
|
303
|
-
{
|
|
304
|
-
tickets
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
328
|
-
className="
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/validators/index.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|