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.
@@ -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.49",
2
+ "version": "1.0.53",
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,
@@ -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 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
+ }
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(new Array(getQuantity(cart)).fill(null).map(() => nanoid()))
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
- data,
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(values, formikHelpers as FormikHelpers<any>, eventId, res)
363
+ handleSubmit(
364
+ values,
365
+ formikHelpers as FormikHelpers<any>,
366
+ eventId,
367
+ res
368
+ )
353
369
  return
354
370
  }
355
371
 
356
- 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)
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(values, formikHelpers as FormikHelpers<any>, eventId, res)
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(data, item => {
473
- const { id, label, labelClassName, fields } = item
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
- <SectionContainer key={id}>
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
- <SectionContainer key={group.id}>
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 === 'brand_opt_in' && hideOptIn) {
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.name}>
526
- {element.name === 'email'
527
- ? <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>
528
- : 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}
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={combineValidators(
538
- element.required
539
- ? requiredValidator
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
- </SectionContainer>
595
+ </React.Fragment>
566
596
  )
567
597
  })}
568
- </SectionContainer>
598
+ </React.Fragment>
569
599
  )
570
600
  })}
571
601
  {showTicketHolderName && (
572
- <SectionContainer>
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.id}>
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={`${element.name}-${index}`}
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
- </SectionContainer>
646
+ </React.Fragment>
618
647
  )}
619
648
  <div className="button-container">
620
649
  <LoadingButton
@@ -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
+ }
@@ -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
- }
@@ -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)