tf-checkout-react 1.0.49 → 1.0.53
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/components/billing-info-container/utils.d.ts +11 -1
- package/dist/components/common/CustomField.d.ts +1 -1
- package/dist/components/common/SelectField.d.ts +20 -0
- package/dist/tf-checkout-react.cjs.development.css +3 -3
- package/dist/tf-checkout-react.cjs.development.js +284 -131
- 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 +292 -139
- package/dist/tf-checkout-react.esm.js.map +1 -1
- package/dist/types/billing-info-data.d.ts +3 -0
- package/dist/utils/formikErrorFocus.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/billing-info-container/index.tsx +63 -34
- package/src/components/billing-info-container/style.css +4 -0
- package/src/components/billing-info-container/utils.ts +53 -1
- package/src/components/common/CustomField.tsx +5 -4
- package/src/components/common/SelectField.tsx +89 -0
- package/src/components/paymentContainer/style.css +0 -8
- package/src/components/stripePayment/style.css +4 -0
- package/src/components/waitingList/index.tsx +2 -1
- package/src/types/billing-info-data.ts +3 -0
- package/src/utils/formikErrorFocus.ts +24 -0
|
@@ -7,6 +7,7 @@ export interface IGroupItem {
|
|
|
7
7
|
required?: boolean;
|
|
8
8
|
component?: ReactNode | JSX.Element | HTMLElement;
|
|
9
9
|
onValidate?: (value: any) => void;
|
|
10
|
+
uniqueId?: string;
|
|
10
11
|
[key: string]: any;
|
|
11
12
|
}
|
|
12
13
|
export interface IFieldData {
|
|
@@ -15,10 +16,12 @@ export interface IFieldData {
|
|
|
15
16
|
groupLabel?: string | JSX.Element | HTMLElement;
|
|
16
17
|
groupLabelClassName?: string;
|
|
17
18
|
id: number;
|
|
19
|
+
uniqueId?: string;
|
|
18
20
|
}
|
|
19
21
|
export interface IBillingInfoData {
|
|
20
22
|
id: number | string;
|
|
21
23
|
fields: IFieldData[];
|
|
22
24
|
label?: string | JSX.Element;
|
|
23
25
|
labelClassName?: string;
|
|
26
|
+
uniqueId?: string;
|
|
24
27
|
}
|
package/package.json
CHANGED
|
@@ -30,9 +30,11 @@ import {
|
|
|
30
30
|
import { LoginModal } from '../loginModal'
|
|
31
31
|
import { RegisterModal } from '../registerModal'
|
|
32
32
|
import {
|
|
33
|
+
assingUniqueIds,
|
|
33
34
|
createCheckoutDataBody,
|
|
34
35
|
createRegisterFormData,
|
|
35
36
|
getInitialValues,
|
|
37
|
+
getValidateFunctions,
|
|
36
38
|
setLoggedUserData,
|
|
37
39
|
} from './utils'
|
|
38
40
|
import { CustomField } from '../common/CustomField'
|
|
@@ -41,10 +43,11 @@ import { CheckboxField } from '../common/CheckboxField'
|
|
|
41
43
|
import { CircularProgress } from '@mui/material'
|
|
42
44
|
import { nanoid } from 'nanoid'
|
|
43
45
|
import { getQueryVariable } from '../../utils/getQueryVariable'
|
|
46
|
+
import { SelectField } from '../common/SelectField'
|
|
47
|
+
import { ErrorFocus } from '../../utils/formikErrorFocus'
|
|
44
48
|
|
|
45
49
|
// const TTFLOGO = require('./logo-ttf.png')
|
|
46
50
|
|
|
47
|
-
|
|
48
51
|
export interface IBillingInfoPage {
|
|
49
52
|
data?: IBillingInfoData[];
|
|
50
53
|
ticketHoldersFields?: IBillingInfoData;
|
|
@@ -52,7 +55,7 @@ export interface IBillingInfoPage {
|
|
|
52
55
|
values: FormikValues,
|
|
53
56
|
formikHelpers: FormikHelpers<FormikValues>,
|
|
54
57
|
eventId: any,
|
|
55
|
-
res: any
|
|
58
|
+
res: any
|
|
56
59
|
) => void;
|
|
57
60
|
|
|
58
61
|
onRegisterSuccess?: (value: {
|
|
@@ -143,7 +146,7 @@ const LogicRunner: FC<{
|
|
|
143
146
|
lastName: parsedData?.last_name,
|
|
144
147
|
email: parsedData?.email || '',
|
|
145
148
|
phone: parsedData?.phone || '',
|
|
146
|
-
confirmEmail: '',
|
|
149
|
+
confirmEmail: parsedData?.email || '',
|
|
147
150
|
state: parsedData?.state || '',
|
|
148
151
|
street_address: parsedData?.street_address || '',
|
|
149
152
|
country: parsedData?.country || 1,
|
|
@@ -165,8 +168,6 @@ const LogicRunner: FC<{
|
|
|
165
168
|
return null
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
const SectionContainer: FC = ({ children }) => <>{children}</>
|
|
169
|
-
|
|
170
171
|
export const BillingInfoContainer = ({
|
|
171
172
|
data = [],
|
|
172
173
|
ticketHoldersFields = {
|
|
@@ -201,6 +202,10 @@ export const BillingInfoContainer = ({
|
|
|
201
202
|
? window.localStorage.getItem('access_token') || ''
|
|
202
203
|
: ''
|
|
203
204
|
|
|
205
|
+
const [dataWithUniqueIds, setDataWithUniqueIds] = useState<
|
|
206
|
+
IBillingInfoData[]
|
|
207
|
+
>(data)
|
|
208
|
+
|
|
204
209
|
const [cartInfoData, setCartInfo] = useState<any>({})
|
|
205
210
|
const [countries, setCountries] = useState<any>([])
|
|
206
211
|
const [states, setStates] = useState<any>([])
|
|
@@ -236,7 +241,11 @@ export const BillingInfoContainer = ({
|
|
|
236
241
|
const showTicketHolderName = getQueryVariable('names_required') === 'true'
|
|
237
242
|
const eventId = getQueryVariable('event_id')
|
|
238
243
|
const optedInFieldValue: boolean = _get(cartInfoData, 'optedIn', false)
|
|
239
|
-
const
|
|
244
|
+
const hideTtfOptIn: boolean = _get(cartInfoData, 'hide_ttf_opt_in', true)
|
|
245
|
+
|
|
246
|
+
if (!_get(dataWithUniqueIds, '[0].uniqueId')) {
|
|
247
|
+
setDataWithUniqueIds(assingUniqueIds(data))
|
|
248
|
+
}
|
|
240
249
|
|
|
241
250
|
const getQuantity = (cart: any = []) => {
|
|
242
251
|
let qty: any = 0
|
|
@@ -278,7 +287,9 @@ export const BillingInfoContainer = ({
|
|
|
278
287
|
const cartInfo = _get(res, 'data.data.attributes')
|
|
279
288
|
setCartInfo(cartInfo)
|
|
280
289
|
const { cart = [] } = cartInfo
|
|
281
|
-
setTicketsQuantity(
|
|
290
|
+
setTicketsQuantity(
|
|
291
|
+
new Array(getQuantity(cart)).fill(null).map(() => nanoid())
|
|
292
|
+
)
|
|
282
293
|
onGetCartSuccess(res.data)
|
|
283
294
|
} catch (e) {
|
|
284
295
|
if (axios.isAxiosError(e)) {
|
|
@@ -316,7 +327,7 @@ export const BillingInfoContainer = ({
|
|
|
316
327
|
<>
|
|
317
328
|
<Formik
|
|
318
329
|
initialValues={getInitialValues(
|
|
319
|
-
|
|
330
|
+
dataWithUniqueIds,
|
|
320
331
|
{
|
|
321
332
|
...initialValues,
|
|
322
333
|
email: emailLogged,
|
|
@@ -349,11 +360,22 @@ export const BillingInfoContainer = ({
|
|
|
349
360
|
showDOB
|
|
350
361
|
)
|
|
351
362
|
const res = await postOnCheckout(checkoutBody, access_token)
|
|
352
|
-
handleSubmit(
|
|
363
|
+
handleSubmit(
|
|
364
|
+
values,
|
|
365
|
+
formikHelpers as FormikHelpers<any>,
|
|
366
|
+
eventId,
|
|
367
|
+
res
|
|
368
|
+
)
|
|
353
369
|
return
|
|
354
370
|
}
|
|
355
371
|
|
|
356
|
-
const
|
|
372
|
+
const checkoutBodyForRegistration = createCheckoutDataBody(
|
|
373
|
+
ticketsQuantity.length,
|
|
374
|
+
values,
|
|
375
|
+
{ emailLogged, firstNameLogged, lastNameLogged },
|
|
376
|
+
showDOB
|
|
377
|
+
)
|
|
378
|
+
const bodyFormData = createRegisterFormData(values, checkoutBodyForRegistration)
|
|
357
379
|
|
|
358
380
|
let access_token_register = null
|
|
359
381
|
try {
|
|
@@ -418,7 +440,12 @@ export const BillingInfoContainer = ({
|
|
|
418
440
|
checkoutBody,
|
|
419
441
|
access_token_register
|
|
420
442
|
)
|
|
421
|
-
handleSubmit(
|
|
443
|
+
handleSubmit(
|
|
444
|
+
values,
|
|
445
|
+
formikHelpers as FormikHelpers<any>,
|
|
446
|
+
eventId,
|
|
447
|
+
res
|
|
448
|
+
)
|
|
422
449
|
} catch (e) {
|
|
423
450
|
if (axios.isAxiosError(e)) {
|
|
424
451
|
if (e.response?.data.error === 'invalid_token') {
|
|
@@ -436,6 +463,7 @@ export const BillingInfoContainer = ({
|
|
|
436
463
|
>
|
|
437
464
|
{(props: FormikProps<any>) => (
|
|
438
465
|
<Form onSubmit={props.handleSubmit}>
|
|
466
|
+
<ErrorFocus />
|
|
439
467
|
<LogicRunner
|
|
440
468
|
values={props.values}
|
|
441
469
|
setStates={setStates}
|
|
@@ -469,8 +497,8 @@ export const BillingInfoContainer = ({
|
|
|
469
497
|
</div>
|
|
470
498
|
</div>
|
|
471
499
|
)}
|
|
472
|
-
{_map(
|
|
473
|
-
const {
|
|
500
|
+
{_map(dataWithUniqueIds, item => {
|
|
501
|
+
const { label, labelClassName, fields } = item
|
|
474
502
|
if (
|
|
475
503
|
label === 'Ticket Holders' &&
|
|
476
504
|
!showTicketHolderName &&
|
|
@@ -479,7 +507,7 @@ export const BillingInfoContainer = ({
|
|
|
479
507
|
return null
|
|
480
508
|
}
|
|
481
509
|
return (
|
|
482
|
-
<
|
|
510
|
+
<React.Fragment key={item.uniqueId}>
|
|
483
511
|
<p className={labelClassName}>{label}</p>
|
|
484
512
|
{_map(fields, group => {
|
|
485
513
|
const {
|
|
@@ -489,7 +517,7 @@ export const BillingInfoContainer = ({
|
|
|
489
517
|
groupLabelClassName,
|
|
490
518
|
} = group
|
|
491
519
|
return (
|
|
492
|
-
<
|
|
520
|
+
<React.Fragment key={group.uniqueId}>
|
|
493
521
|
{!isLoggedIn && (
|
|
494
522
|
<div className={groupLabelClassName}>
|
|
495
523
|
{groupLabel}
|
|
@@ -513,7 +541,7 @@ export const BillingInfoContainer = ({
|
|
|
513
541
|
if (el.name === 'holderAge' && !showDOB) {
|
|
514
542
|
return false
|
|
515
543
|
}
|
|
516
|
-
if (el.name === '
|
|
544
|
+
if (el.name === 'ttf_opt_in' && hideTtfOptIn) {
|
|
517
545
|
return false
|
|
518
546
|
}
|
|
519
547
|
return true
|
|
@@ -522,10 +550,14 @@ export const BillingInfoContainer = ({
|
|
|
522
550
|
['password', 'confirmPassword'].includes(
|
|
523
551
|
element.name
|
|
524
552
|
) && isLoggedIn ? null : (
|
|
525
|
-
<React.Fragment key={element.
|
|
526
|
-
{element.name === 'email'
|
|
527
|
-
|
|
528
|
-
|
|
553
|
+
<React.Fragment key={element.uniqueId}>
|
|
554
|
+
{element.name === 'email' ? (
|
|
555
|
+
<div className="email-checking">
|
|
556
|
+
{`IMPORTANT: Please double check that your
|
|
557
|
+
email address is correct. It's where we
|
|
558
|
+
send your confirmation and e-tickets to!`}
|
|
559
|
+
</div>
|
|
560
|
+
) : null}
|
|
529
561
|
<div className={element.className}>
|
|
530
562
|
{element.component ? (
|
|
531
563
|
element.component
|
|
@@ -534,17 +566,15 @@ export const BillingInfoContainer = ({
|
|
|
534
566
|
name={element.name}
|
|
535
567
|
label={element.label}
|
|
536
568
|
type={element.type}
|
|
537
|
-
validate={
|
|
538
|
-
element
|
|
539
|
-
|
|
540
|
-
: () => {},
|
|
541
|
-
element.onValidate
|
|
542
|
-
? element.onValidate
|
|
543
|
-
: () => {}
|
|
569
|
+
validate={getValidateFunctions(
|
|
570
|
+
element,
|
|
571
|
+
states
|
|
544
572
|
)}
|
|
545
573
|
component={
|
|
546
574
|
element.type === 'checkbox'
|
|
547
575
|
? CheckboxField
|
|
576
|
+
: element.type === 'select'
|
|
577
|
+
? SelectField
|
|
548
578
|
: CustomField
|
|
549
579
|
}
|
|
550
580
|
selectOptions={
|
|
@@ -562,14 +592,14 @@ export const BillingInfoContainer = ({
|
|
|
562
592
|
)
|
|
563
593
|
)}
|
|
564
594
|
</div>
|
|
565
|
-
</
|
|
595
|
+
</React.Fragment>
|
|
566
596
|
)
|
|
567
597
|
})}
|
|
568
|
-
</
|
|
598
|
+
</React.Fragment>
|
|
569
599
|
)
|
|
570
600
|
})}
|
|
571
601
|
{showTicketHolderName && (
|
|
572
|
-
<
|
|
602
|
+
<React.Fragment>
|
|
573
603
|
<div className="ticket-holders-fields">
|
|
574
604
|
<p>{ticketHoldersFields.label}</p>
|
|
575
605
|
{_map(ticketsQuantity, (_item, index) => (
|
|
@@ -578,16 +608,15 @@ export const BillingInfoContainer = ({
|
|
|
578
608
|
{_map(ticketHoldersFields.fields, group => {
|
|
579
609
|
const { groupClassname, groupItems } = group
|
|
580
610
|
return (
|
|
581
|
-
<div key={group.
|
|
611
|
+
<div key={group.uniqueId}>
|
|
582
612
|
<div className={groupClassname}>
|
|
583
613
|
{_map(groupItems, element => (
|
|
584
614
|
<div
|
|
585
615
|
className={element.className}
|
|
586
|
-
key={
|
|
616
|
+
key={element.uniqueId}
|
|
587
617
|
>
|
|
588
618
|
<Field
|
|
589
619
|
name={`${element.name}-${index}`}
|
|
590
|
-
key={`${element.name}-${index}`}
|
|
591
620
|
label={element.label}
|
|
592
621
|
type={element.type}
|
|
593
622
|
required={true}
|
|
@@ -614,7 +643,7 @@ export const BillingInfoContainer = ({
|
|
|
614
643
|
</div>
|
|
615
644
|
))}
|
|
616
645
|
</div>
|
|
617
|
-
</
|
|
646
|
+
</React.Fragment>
|
|
618
647
|
)}
|
|
619
648
|
<div className="button-container">
|
|
620
649
|
<LoadingButton
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import _map from 'lodash/map'
|
|
2
|
+
import _get from 'lodash/get'
|
|
2
3
|
import _forEach from 'lodash/forEach'
|
|
3
4
|
import _flatMapDeep from 'lodash/flatMapDeep'
|
|
5
|
+
import _isArray from 'lodash/isArray'
|
|
4
6
|
|
|
5
7
|
import { ENV } from '../../env'
|
|
8
|
+
import { IGroupItem } from '../../types'
|
|
9
|
+
import { combineValidators, requiredValidator } from '../../validators'
|
|
10
|
+
import { nanoid } from 'nanoid'
|
|
6
11
|
|
|
7
12
|
export interface ILoggedInValues {
|
|
8
13
|
emailLogged?: string;
|
|
@@ -31,7 +36,10 @@ export const getInitialValues = (
|
|
|
31
36
|
return initialValues
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
export const createRegisterFormData = (
|
|
39
|
+
export const createRegisterFormData = (
|
|
40
|
+
values: IValues = {},
|
|
41
|
+
checkoutBody: { attributes: { [key: string]: any } }
|
|
42
|
+
): FormData => {
|
|
35
43
|
const bodyFormData = new FormData()
|
|
36
44
|
bodyFormData.append('first_name', values.firstName)
|
|
37
45
|
bodyFormData.append('last_name', values.lastName)
|
|
@@ -46,6 +54,11 @@ export const createRegisterFormData = (values: IValues = {}): FormData => {
|
|
|
46
54
|
'client_secret',
|
|
47
55
|
ENV.CLIENT_SECRET || 'b89c191eff22fdcf84ac9bfd88d005355a151ec2c83b26b9'
|
|
48
56
|
)
|
|
57
|
+
|
|
58
|
+
_forEach(checkoutBody.attributes, (item: any, key: string) => {
|
|
59
|
+
bodyFormData.append(key, item)
|
|
60
|
+
})
|
|
61
|
+
|
|
49
62
|
return bodyFormData
|
|
50
63
|
}
|
|
51
64
|
|
|
@@ -81,6 +94,7 @@ export const setLoggedUserData = (data: IUserData) => ({
|
|
|
81
94
|
first_name: data.firstName,
|
|
82
95
|
last_name: data.lastName,
|
|
83
96
|
email: data.email,
|
|
97
|
+
confirmEmail: data.email,
|
|
84
98
|
city: data?.city || '',
|
|
85
99
|
country: data?.country || '',
|
|
86
100
|
phone: data?.phone || '',
|
|
@@ -150,3 +164,41 @@ export const createCheckoutDataBody = (
|
|
|
150
164
|
}
|
|
151
165
|
return body
|
|
152
166
|
}
|
|
167
|
+
|
|
168
|
+
export const getValidateFunctions = (
|
|
169
|
+
element: IGroupItem,
|
|
170
|
+
states: Array<{ [key: string]: any }>
|
|
171
|
+
) => {
|
|
172
|
+
const validationFunctions: any[] = []
|
|
173
|
+
|
|
174
|
+
if (element.required) {
|
|
175
|
+
if (
|
|
176
|
+
element.name !== 'state' ||
|
|
177
|
+
(element.name === 'state' && states.length)
|
|
178
|
+
) {
|
|
179
|
+
validationFunctions.push(requiredValidator)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (element.onValidate) {
|
|
184
|
+
validationFunctions.push(element.onValidate)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return combineValidators(...validationFunctions)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const assingUniqueIds = (data: any): any => {
|
|
191
|
+
if (_get(data[0], 'uniqueId')) {
|
|
192
|
+
return data
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return _map(data, (item: any) => {
|
|
196
|
+
_forEach(item, (itemValue: string, key) => {
|
|
197
|
+
if (_isArray(itemValue)) {
|
|
198
|
+
item[key] = assingUniqueIds(itemValue)
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
return { ...item, uniqueId: nanoid() }
|
|
203
|
+
})
|
|
204
|
+
}
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import TextField from '@mui/material/TextField'
|
|
3
3
|
import _map from 'lodash/map'
|
|
4
4
|
import _get from 'lodash/get'
|
|
5
|
+
import _includes from 'lodash/includes'
|
|
5
6
|
|
|
6
7
|
import { FieldInputProps, FormikProps } from 'formik'
|
|
7
8
|
import { makeStyles } from '@mui/styles'
|
|
@@ -41,13 +42,14 @@ export const CustomField = ({
|
|
|
41
42
|
type = 'text',
|
|
42
43
|
field,
|
|
43
44
|
selectOptions = [] as ISelectOption[],
|
|
44
|
-
form: { touched, errors },
|
|
45
|
+
form: { touched, errors, submitCount },
|
|
45
46
|
theme,
|
|
46
47
|
}: ICustomField & IOtherProps) => {
|
|
47
48
|
const isSelectField = type === 'select'
|
|
48
|
-
const isShrink = Boolean(field.value) || type === 'date' || type === 'select'
|
|
49
|
-
const isTouched = Boolean(_get(touched, field.name))
|
|
50
49
|
const error = _get(errors, field.name)
|
|
50
|
+
const isTouched =
|
|
51
|
+
Boolean(_get(touched, field.name)) ||
|
|
52
|
+
(_includes(field.name, 'holder') && !!error && !!submitCount)
|
|
51
53
|
const classes = useStyles()
|
|
52
54
|
|
|
53
55
|
return (
|
|
@@ -59,7 +61,6 @@ export const CustomField = ({
|
|
|
59
61
|
fullWidth={true}
|
|
60
62
|
error={!!error && isTouched}
|
|
61
63
|
helperText={isTouched && error}
|
|
62
|
-
InputLabelProps={{ shrink: isShrink }}
|
|
63
64
|
InputProps={{
|
|
64
65
|
classes: {
|
|
65
66
|
input: classes.input,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Select from '@mui/material/Select'
|
|
3
|
+
import _map from 'lodash/map'
|
|
4
|
+
import _get from 'lodash/get'
|
|
5
|
+
|
|
6
|
+
import { FieldInputProps, FormikProps } from 'formik'
|
|
7
|
+
import { makeStyles } from '@mui/styles'
|
|
8
|
+
import { FormControl, InputLabel, FormHelperText } from '@mui/material'
|
|
9
|
+
|
|
10
|
+
export interface ISelectOption {
|
|
11
|
+
label: string | number;
|
|
12
|
+
value?: string | number;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ISelectField {
|
|
17
|
+
label: string;
|
|
18
|
+
|
|
19
|
+
field: FieldInputProps<any>;
|
|
20
|
+
form: FormikProps<any>;
|
|
21
|
+
theme: 'dark' | 'light';
|
|
22
|
+
|
|
23
|
+
// optional
|
|
24
|
+
type?: string;
|
|
25
|
+
selectOptions?: ISelectOption[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface IOtherProps {
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const useStyles = makeStyles({
|
|
33
|
+
input: {
|
|
34
|
+
'&::placeholder': {
|
|
35
|
+
color: 'gray',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const SelectField = ({
|
|
41
|
+
label,
|
|
42
|
+
type = 'text',
|
|
43
|
+
field,
|
|
44
|
+
selectOptions = [] as ISelectOption[],
|
|
45
|
+
form: { touched, errors },
|
|
46
|
+
theme,
|
|
47
|
+
}: ISelectField & IOtherProps) => {
|
|
48
|
+
const isTouched = Boolean(_get(touched, field.name))
|
|
49
|
+
const error = _get(errors, field.name)
|
|
50
|
+
const classes = useStyles()
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<FormControl fullWidth={true}>
|
|
54
|
+
<InputLabel htmlFor={field.name} error={!!error && isTouched} shrink={true}>
|
|
55
|
+
{label}
|
|
56
|
+
</InputLabel>
|
|
57
|
+
<Select
|
|
58
|
+
id={field.name}
|
|
59
|
+
label={label}
|
|
60
|
+
type={type}
|
|
61
|
+
fullWidth={true}
|
|
62
|
+
error={!!error && isTouched}
|
|
63
|
+
inputProps={{
|
|
64
|
+
id: field.name,
|
|
65
|
+
classes: {
|
|
66
|
+
input: classes.input,
|
|
67
|
+
},
|
|
68
|
+
}}
|
|
69
|
+
native={true}
|
|
70
|
+
className={theme}
|
|
71
|
+
MenuProps={{ className: theme }}
|
|
72
|
+
{...field}
|
|
73
|
+
>
|
|
74
|
+
{_map(selectOptions, option => (
|
|
75
|
+
<option
|
|
76
|
+
key={option.value}
|
|
77
|
+
value={option.value}
|
|
78
|
+
disabled={option.disabled}
|
|
79
|
+
>
|
|
80
|
+
{option.label}
|
|
81
|
+
</option>
|
|
82
|
+
))}
|
|
83
|
+
</Select>
|
|
84
|
+
{isTouched && error ? (
|
|
85
|
+
<FormHelperText error={!!error && isTouched}>{error}</FormHelperText>
|
|
86
|
+
) : null}
|
|
87
|
+
</FormControl>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
emailValidator,
|
|
11
11
|
} from '../../validators'
|
|
12
12
|
import { ENV } from '../../env'
|
|
13
|
+
import { ErrorFocus } from '../../utils/formikErrorFocus'
|
|
13
14
|
|
|
14
15
|
import './style.css'
|
|
15
16
|
|
|
@@ -58,7 +59,6 @@ const WaitingList = ({ tickets = {} }: WaitingListProps) => {
|
|
|
58
59
|
setShowSuccessMessage(true)
|
|
59
60
|
}
|
|
60
61
|
} catch (error) {
|
|
61
|
-
console.log(error)
|
|
62
62
|
} finally {
|
|
63
63
|
setLoading(false)
|
|
64
64
|
}
|
|
@@ -85,6 +85,7 @@ const WaitingList = ({ tickets = {} }: WaitingListProps) => {
|
|
|
85
85
|
onSubmit={handleSubmit}
|
|
86
86
|
>
|
|
87
87
|
<Form>
|
|
88
|
+
<ErrorFocus />
|
|
88
89
|
{showTicketsField && (
|
|
89
90
|
<>
|
|
90
91
|
<div className="field-item">
|
|
@@ -10,6 +10,7 @@ export interface IGroupItem {
|
|
|
10
10
|
required?: boolean;
|
|
11
11
|
component?: ReactNode | JSX.Element | HTMLElement;
|
|
12
12
|
onValidate?: (value: any) => void;
|
|
13
|
+
uniqueId?: string;
|
|
13
14
|
|
|
14
15
|
// aditional props
|
|
15
16
|
[key: string]: any;
|
|
@@ -23,6 +24,7 @@ export interface IFieldData {
|
|
|
23
24
|
groupLabel?: string | JSX.Element | HTMLElement;
|
|
24
25
|
groupLabelClassName?: string;
|
|
25
26
|
id: number;
|
|
27
|
+
uniqueId?: string;
|
|
26
28
|
}
|
|
27
29
|
export interface IBillingInfoData {
|
|
28
30
|
id: number | string;
|
|
@@ -31,4 +33,5 @@ export interface IBillingInfoData {
|
|
|
31
33
|
// optional
|
|
32
34
|
label?: string | JSX.Element;
|
|
33
35
|
labelClassName?: string;
|
|
36
|
+
uniqueId?: string;
|
|
34
37
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { connect, FormikContextType } from 'formik'
|
|
2
|
+
import { Component } from 'react'
|
|
3
|
+
|
|
4
|
+
interface IProps {
|
|
5
|
+
formik: FormikContextType<any>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class ErrorFocusInternal extends Component<IProps> {
|
|
9
|
+
public componentDidUpdate(prevProps: IProps) {
|
|
10
|
+
const { isSubmitting, isValidating, errors } = prevProps.formik
|
|
11
|
+
const keys = Object.keys(errors)
|
|
12
|
+
if (keys.length > 0 && isSubmitting && !isValidating) {
|
|
13
|
+
const selector = `[name="${keys[0]}"]`
|
|
14
|
+
const errorElement = document.querySelector(selector) as HTMLElement
|
|
15
|
+
if (errorElement) {
|
|
16
|
+
errorElement.focus()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public render = () => null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const ErrorFocus = connect<{}>(ErrorFocusInternal)
|