tf-checkout-react 1.0.50 → 1.0.54

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.
@@ -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
  }
@@ -0,0 +1,2 @@
1
+ /// <reference types="react" />
2
+ export declare const ErrorFocus: import("react").ComponentType<{}>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.50",
2
+ "version": "1.0.54",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -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,
@@ -199,6 +202,10 @@ export const BillingInfoContainer = ({
199
202
  ? window.localStorage.getItem('access_token') || ''
200
203
  : ''
201
204
 
205
+ const [dataWithUniqueIds, setDataWithUniqueIds] = useState<
206
+ IBillingInfoData[]
207
+ >(data)
208
+
202
209
  const [cartInfoData, setCartInfo] = useState<any>({})
203
210
  const [countries, setCountries] = useState<any>([])
204
211
  const [states, setStates] = useState<any>([])
@@ -234,7 +241,11 @@ export const BillingInfoContainer = ({
234
241
  const showTicketHolderName = getQueryVariable('names_required') === 'true'
235
242
  const eventId = getQueryVariable('event_id')
236
243
  const optedInFieldValue: boolean = _get(cartInfoData, 'optedIn', false)
237
- const hideOptIn: boolean = _get(cartInfoData, 'hide_ttf_opt_in', false)
244
+ const hideTtfOptIn: boolean = _get(cartInfoData, 'hide_ttf_opt_in', true)
245
+
246
+ if (!_get(dataWithUniqueIds, '[0].uniqueId')) {
247
+ setDataWithUniqueIds(assingUniqueIds(data))
248
+ }
238
249
 
239
250
  const getQuantity = (cart: any = []) => {
240
251
  let qty: any = 0
@@ -276,7 +287,9 @@ export const BillingInfoContainer = ({
276
287
  const cartInfo = _get(res, 'data.data.attributes')
277
288
  setCartInfo(cartInfo)
278
289
  const { cart = [] } = cartInfo
279
- setTicketsQuantity(new Array(getQuantity(cart)).fill(null).map(() => nanoid()))
290
+ setTicketsQuantity(
291
+ new Array(getQuantity(cart)).fill(null).map(() => nanoid())
292
+ )
280
293
  onGetCartSuccess(res.data)
281
294
  } catch (e) {
282
295
  if (axios.isAxiosError(e)) {
@@ -314,7 +327,7 @@ export const BillingInfoContainer = ({
314
327
  <>
315
328
  <Formik
316
329
  initialValues={getInitialValues(
317
- data,
330
+ dataWithUniqueIds,
318
331
  {
319
332
  ...initialValues,
320
333
  email: emailLogged,
@@ -347,11 +360,22 @@ export const BillingInfoContainer = ({
347
360
  showDOB
348
361
  )
349
362
  const res = await postOnCheckout(checkoutBody, access_token)
350
- handleSubmit(values, formikHelpers as FormikHelpers<any>, eventId, res)
363
+ handleSubmit(
364
+ values,
365
+ formikHelpers as FormikHelpers<any>,
366
+ eventId,
367
+ res
368
+ )
351
369
  return
352
370
  }
353
371
 
354
- const bodyFormData = createRegisterFormData(values)
372
+ const checkoutBodyForRegistration = createCheckoutDataBody(
373
+ ticketsQuantity.length,
374
+ values,
375
+ { emailLogged, firstNameLogged, lastNameLogged },
376
+ showDOB
377
+ )
378
+ const bodyFormData = createRegisterFormData(values, checkoutBodyForRegistration)
355
379
 
356
380
  let access_token_register = null
357
381
  try {
@@ -416,7 +440,12 @@ export const BillingInfoContainer = ({
416
440
  checkoutBody,
417
441
  access_token_register
418
442
  )
419
- handleSubmit(values, formikHelpers as FormikHelpers<any>, eventId, res)
443
+ handleSubmit(
444
+ values,
445
+ formikHelpers as FormikHelpers<any>,
446
+ eventId,
447
+ res
448
+ )
420
449
  } catch (e) {
421
450
  if (axios.isAxiosError(e)) {
422
451
  if (e.response?.data.error === 'invalid_token') {
@@ -434,6 +463,7 @@ export const BillingInfoContainer = ({
434
463
  >
435
464
  {(props: FormikProps<any>) => (
436
465
  <Form onSubmit={props.handleSubmit}>
466
+ <ErrorFocus />
437
467
  <LogicRunner
438
468
  values={props.values}
439
469
  setStates={setStates}
@@ -467,7 +497,7 @@ export const BillingInfoContainer = ({
467
497
  </div>
468
498
  </div>
469
499
  )}
470
- {_map(data, item => {
500
+ {_map(dataWithUniqueIds, item => {
471
501
  const { label, labelClassName, fields } = item
472
502
  if (
473
503
  label === 'Ticket Holders' &&
@@ -477,7 +507,7 @@ export const BillingInfoContainer = ({
477
507
  return null
478
508
  }
479
509
  return (
480
- <React.Fragment key={nanoid()}>
510
+ <React.Fragment key={item.uniqueId}>
481
511
  <p className={labelClassName}>{label}</p>
482
512
  {_map(fields, group => {
483
513
  const {
@@ -487,7 +517,7 @@ export const BillingInfoContainer = ({
487
517
  groupLabelClassName,
488
518
  } = group
489
519
  return (
490
- <React.Fragment key={nanoid()}>
520
+ <React.Fragment key={group.uniqueId}>
491
521
  {!isLoggedIn && (
492
522
  <div className={groupLabelClassName}>
493
523
  {groupLabel}
@@ -511,7 +541,7 @@ export const BillingInfoContainer = ({
511
541
  if (el.name === 'holderAge' && !showDOB) {
512
542
  return false
513
543
  }
514
- if (el.name === 'brand_opt_in' && hideOptIn) {
544
+ if (el.name === 'ttf_opt_in' && hideTtfOptIn) {
515
545
  return false
516
546
  }
517
547
  return true
@@ -520,10 +550,14 @@ export const BillingInfoContainer = ({
520
550
  ['password', 'confirmPassword'].includes(
521
551
  element.name
522
552
  ) && isLoggedIn ? null : (
523
- <React.Fragment key={nanoid()}>
524
- {element.name === 'email'
525
- ? <div className="email-checking">IMPORTANT: Please double check that your email address is correct. It's where we send your confirmation and e-tickets to!</div>
526
- : null}
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}
527
561
  <div className={element.className}>
528
562
  {element.component ? (
529
563
  element.component
@@ -532,17 +566,15 @@ export const BillingInfoContainer = ({
532
566
  name={element.name}
533
567
  label={element.label}
534
568
  type={element.type}
535
- validate={combineValidators(
536
- element.required
537
- ? requiredValidator
538
- : () => {},
539
- element.onValidate
540
- ? element.onValidate
541
- : () => {}
569
+ validate={getValidateFunctions(
570
+ element,
571
+ states
542
572
  )}
543
573
  component={
544
574
  element.type === 'checkbox'
545
575
  ? CheckboxField
576
+ : element.type === 'select'
577
+ ? SelectField
546
578
  : CustomField
547
579
  }
548
580
  selectOptions={
@@ -567,21 +599,21 @@ export const BillingInfoContainer = ({
567
599
  )
568
600
  })}
569
601
  {showTicketHolderName && (
570
- <React.Fragment key={nanoid()}>
602
+ <React.Fragment>
571
603
  <div className="ticket-holders-fields">
572
604
  <p>{ticketHoldersFields.label}</p>
573
605
  {_map(ticketsQuantity, (_item, index) => (
574
- <div key={nanoid()}>
606
+ <div key={_item}>
575
607
  <h5>Ticket {index + 1}</h5>
576
608
  {_map(ticketHoldersFields.fields, group => {
577
609
  const { groupClassname, groupItems } = group
578
610
  return (
579
- <div key={nanoid()}>
611
+ <div key={group.uniqueId}>
580
612
  <div className={groupClassname}>
581
613
  {_map(groupItems, element => (
582
614
  <div
583
615
  className={element.className}
584
- key={nanoid()}
616
+ key={element.uniqueId}
585
617
  >
586
618
  <Field
587
619
  name={`${element.name}-${index}`}
@@ -613,7 +645,7 @@ export const BillingInfoContainer = ({
613
645
  </div>
614
646
  </React.Fragment>
615
647
  )}
616
- <div className="button-container" key={nanoid()}>
648
+ <div className="button-container">
617
649
  <LoadingButton
618
650
  type="submit"
619
651
  variant="contained"
@@ -99,4 +99,8 @@ button {
99
99
  .billing-info-container .main-header {
100
100
  font-size: 1.5rem;
101
101
  }
102
+ }
103
+
104
+ .email-checking {
105
+ margin-bottom: 15px;
102
106
  }
@@ -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 = (values: IValues = {}): FormData => {
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
+ }
@@ -118,6 +118,7 @@ export const PaymentContainer = ({
118
118
  ? conditionsInfo.map((item: string) => ({
119
119
  id: nanoid(),
120
120
  text: item,
121
+ checked: false
121
122
  }))
122
123
  : []
123
124
  )
@@ -45,11 +45,3 @@
45
45
  padding-top: 20px;
46
46
  text-align: center;
47
47
  }
48
-
49
- .conditions-block {
50
- border: 1px solid #e3e3e3;
51
- border-radius: 4px;
52
- background-color: #f5f5f5;
53
- padding: 19px;
54
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
55
- }
@@ -37,7 +37,6 @@ export interface ICheckoutForm {
37
37
  total: string;
38
38
  currency: string;
39
39
  onSubmit: (error: any) => Promise<any>;
40
-
41
40
  error?: string | null;
42
41
  stripeCardOptions?: StripeCardNumberElementOptions;
43
42
  stripe_client_secret: string;
@@ -70,6 +69,8 @@ const CheckoutForm = ({
70
69
  const elements = useElements()
71
70
  const [postalCode, setPostalCode] = useState<any>('')
72
71
  const [stripeError, setStripeError] = useState<any>(null)
72
+ const [checkboxes, setCheckboxes] = useState<any>([])
73
+ const [allowSubmit, setAllowSubmit] = useState(false)
73
74
 
74
75
  const handleSubmit = async (event: React.SyntheticEvent) => {
75
76
  handleSetLoading(true)
@@ -127,6 +128,18 @@ const CheckoutForm = ({
127
128
  }
128
129
  }
129
130
 
131
+ const handleCheckboxes=(e:any) => {
132
+ const checkbox = e.target
133
+ const updatedCheckedState = checkboxes.map((item:any) =>{
134
+ const value = item.id === checkbox.name ? !item.checked : item.checked
135
+ return {
136
+ ...item,
137
+ checked: value
138
+ }
139
+ })
140
+ setCheckboxes(updatedCheckedState)
141
+ }
142
+
130
143
  const onChangePostalCode = (e: any) => {
131
144
  setPostalCode(e.target.value)
132
145
  }
@@ -141,6 +154,19 @@ const CheckoutForm = ({
141
154
  }
142
155
  }, [])
143
156
 
157
+ useEffect(() => {
158
+ if(conditions.length){
159
+ setCheckboxes(conditions)
160
+ }
161
+ }, [conditions])
162
+
163
+ useEffect(() => {
164
+ if(checkboxes.length){
165
+ const allChecked = checkboxes.every((item:any)=>item?.checked === true)
166
+ setAllowSubmit(allChecked)
167
+ }
168
+ }, [checkboxes])
169
+
144
170
  const buttonIsDiabled = !stripe || !!error || isLoading
145
171
 
146
172
  return (
@@ -178,7 +204,7 @@ const CheckoutForm = ({
178
204
  />
179
205
  </label>
180
206
  </div>
181
- {conditions?.map((checkbox: any) => (
207
+ {checkboxes?.map((checkbox: any) => (
182
208
  <div
183
209
  className={'billing-info-container__singleField'}
184
210
  key={checkbox.id}
@@ -188,13 +214,15 @@ const CheckoutForm = ({
188
214
  name={checkbox.id}
189
215
  label={checkbox.text}
190
216
  required={true}
217
+ onChange={handleCheckboxes}
218
+ checked={checkbox.checked}
191
219
  />
192
220
  </div>
193
221
  </div>
194
222
  ))}
195
223
  <div
196
224
  className={`payment_button ${
197
- buttonIsDiabled ? 'disabled-payment-button' : ''
225
+ buttonIsDiabled || !allowSubmit ? 'disabled-payment-button' : ''
198
226
  }`}
199
227
  >
200
228
  <button disabled={buttonIsDiabled} type="submit">
@@ -2,6 +2,10 @@
2
2
  background: #232323;
3
3
  border-radius: 8px;
4
4
  padding: 15px;
5
+ width: 50%;
6
+ margin: 0 auto;
7
+ min-width: 325px;
8
+ margin-bottom: 20px;
5
9
  }
6
10
  .card_form_inner .card_label_text {
7
11
  color: #fff;
@@ -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)