tf-checkout-react 1.7.31 → 2.0.0-beta.1
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/LICENSE +1 -1
- package/README.md +141 -1033
- package/dist/checkout.css +17 -0
- package/dist/index.d.mts +3946 -0
- package/dist/index.d.ts +3946 -30
- package/dist/index.esm.js +9176 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +9330 -6
- package/dist/index.js.map +1 -0
- package/dist/styles/addons.css +75 -0
- package/dist/styles/auth.css +294 -0
- package/dist/styles/billing.css +98 -0
- package/dist/styles/confirmation.css +51 -0
- package/dist/styles/foundation.css +146 -0
- package/dist/styles/payment.css +179 -0
- package/dist/styles/primitives.css +356 -0
- package/dist/styles/seat-map.css +46 -0
- package/dist/styles/tickets.css +285 -0
- package/dist/styles/verification-confirmation.css +258 -0
- package/package.json +93 -78
- package/src/.d.ts +1 -1
- package/src/CardInput.tsx +374 -0
- package/src/EmbeddedCheckout.tsx +400 -0
- package/src/ResumedCheckout.tsx +132 -0
- package/src/auth/AuthModal.tsx +238 -0
- package/src/auth/ForgotPasswordForm.tsx +93 -0
- package/src/auth/LoginForm.tsx +104 -0
- package/src/auth/PasswordInput.tsx +33 -0
- package/src/auth/RegisterForm.tsx +333 -0
- package/src/auth/ResetPasswordForm.tsx +112 -0
- package/src/auth/index.ts +5 -0
- package/src/checkout.css +17 -0
- package/src/configure.ts +29 -0
- package/src/confirmer.ts +439 -0
- package/src/index.ts +41 -30
- package/src/instance.ts +21 -0
- package/src/sections/AddOns.tsx +241 -0
- package/src/sections/BillingInfo.tsx +790 -0
- package/src/sections/Confirmation.tsx +146 -0
- package/src/sections/IDVerification.tsx +76 -0
- package/src/sections/OrderConfirmation.tsx +277 -0
- package/src/sections/OrderIDVerification.tsx +117 -0
- package/src/sections/OrderSummary.tsx +84 -0
- package/src/sections/Payment.tsx +557 -0
- package/src/sections/SeatMap.tsx +512 -0
- package/src/sections/Tickets.tsx +573 -0
- package/src/sections/billing/BillingAddressFields.tsx +138 -0
- package/src/sections/billing/BuyerField.tsx +75 -0
- package/src/sections/billing/TicketHoldersCard.tsx +124 -0
- package/src/sections/billing/fieldRules.ts +43 -0
- package/src/sections/billing/types.ts +10 -0
- package/src/sections/index.ts +15 -0
- package/src/sections/seatMap.utils.ts +105 -0
- package/src/styles/addons.css +75 -0
- package/src/styles/auth.css +294 -0
- package/src/styles/billing.css +98 -0
- package/src/styles/confirmation.css +51 -0
- package/src/styles/foundation.css +146 -0
- package/src/styles/payment.css +179 -0
- package/src/styles/primitives.css +356 -0
- package/src/styles/seat-map.css +46 -0
- package/src/styles/tickets.css +285 -0
- package/src/styles/verification-confirmation.css +258 -0
- package/src/typings.d.ts +2 -2
- package/src/ui/Button.tsx +38 -0
- package/src/ui/Card.tsx +54 -0
- package/src/ui/CheckoutProvider.tsx +172 -0
- package/src/ui/CustomFields.tsx +195 -0
- package/src/ui/Field.tsx +114 -0
- package/src/ui/I18nProvider.tsx +36 -0
- package/src/ui/InstallmentPicker.tsx +69 -0
- package/src/ui/Loader.tsx +12 -0
- package/src/ui/PaymentMethods.tsx +133 -0
- package/src/ui/PriceLine.tsx +24 -0
- package/src/ui/QuantitySelect.tsx +50 -0
- package/src/ui/Stepper.tsx +36 -0
- package/src/ui/Timer.tsx +34 -0
- package/src/ui/TimerBanner.tsx +55 -0
- package/src/ui/cx.ts +3 -0
- package/src/ui/index.ts +54 -0
- package/src/ui/theme.ts +83 -0
- package/src/useCheckout.ts +35 -0
- package/src/xendit.ts +438 -0
- package/dist/adapters/customFields.d.ts +0 -12
- package/dist/adapters/index.d.ts +0 -1
- package/dist/api/auth.d.ts +0 -27
- package/dist/api/cart.d.ts +0 -2
- package/dist/api/checkout.d.ts +0 -3
- package/dist/api/common.d.ts +0 -11
- package/dist/api/guestTicketDelegation.d.ts +0 -47
- package/dist/api/index.d.ts +0 -82
- package/dist/api/interceptors.d.ts +0 -1
- package/dist/api/orders.d.ts +0 -2
- package/dist/api/payment.d.ts +0 -5
- package/dist/api/preRegistrationComplete.d.ts +0 -3
- package/dist/api/publicRequest.d.ts +0 -9
- package/dist/api/resale.d.ts +0 -5
- package/dist/components/account-settings/index.d.ts +0 -3
- package/dist/components/addonsContainer/AddonComponent.d.ts +0 -18
- package/dist/components/addonsContainer/SimpleAddonsContainer.d.ts +0 -20
- package/dist/components/addonsContainer/adapters/index.d.ts +0 -12
- package/dist/components/addonsContainer/index.d.ts +0 -22
- package/dist/components/addonsContainer/normalizers/index.d.ts +0 -1
- package/dist/components/addonsContainer/useAddons.d.ts +0 -23
- package/dist/components/addonsContainer/utils/index.d.ts +0 -16
- package/dist/components/billing-info-container/hooks/index.d.ts +0 -3
- package/dist/components/billing-info-container/hooks/usePaymentContext.d.ts +0 -5
- package/dist/components/billing-info-container/hooks/usePaymentRedirect.d.ts +0 -14
- package/dist/components/billing-info-container/hooks/useStripePayment.d.ts +0 -18
- package/dist/components/billing-info-container/index.d.ts +0 -69
- package/dist/components/billing-info-container/utils.d.ts +0 -59
- package/dist/components/common/Checkbox.d.ts +0 -11
- package/dist/components/common/CheckboxField/index.d.ts +0 -9
- package/dist/components/common/CopyField.d.ts +0 -12
- package/dist/components/common/CopyMessageModal.d.ts +0 -7
- package/dist/components/common/CustomField.d.ts +0 -26
- package/dist/components/common/DatePickerField.d.ts +0 -20
- package/dist/components/common/FieldSection/index.d.ts +0 -19
- package/dist/components/common/FieldSection/utils/index.d.ts +0 -8
- package/dist/components/common/FormikPhoneNumberField.d.ts +0 -11
- package/dist/components/common/Loader.d.ts +0 -2
- package/dist/components/common/ModalComponent/index.d.ts +0 -16
- package/dist/components/common/NativeSelectFeild/index.d.ts +0 -21
- package/dist/components/common/PhoneNumberField.d.ts +0 -14
- package/dist/components/common/PoweredBy.d.ts +0 -2
- package/dist/components/common/RadioField.d.ts +0 -11
- package/dist/components/common/RadioGroupField/index.d.ts +0 -17
- package/dist/components/common/RedirectModal.d.ts +0 -7
- package/dist/components/common/SelectField/index.d.ts +0 -19
- package/dist/components/common/SnackbarAlert.d.ts +0 -13
- package/dist/components/common/index.d.ts +0 -11
- package/dist/components/common/socials.d.ts +0 -10
- package/dist/components/confirmModal/index.d.ts +0 -10
- package/dist/components/confirmationContainer/config.d.ts +0 -1
- package/dist/components/confirmationContainer/index.d.ts +0 -46
- package/dist/components/confirmationContainer/social-buttons.d.ts +0 -16
- package/dist/components/countdown/index.d.ts +0 -14
- package/dist/components/delegationsContainer/IssueComponent.d.ts +0 -10
- package/dist/components/delegationsContainer/IssueTicketForm.d.ts +0 -9
- package/dist/components/delegationsContainer/TicketsAssignedTable.d.ts +0 -11
- package/dist/components/delegationsContainer/TicketsAvailableTable.d.ts +0 -11
- package/dist/components/delegationsContainer/index.d.ts +0 -10
- package/dist/components/forgotPasswordModal/index.d.ts +0 -12
- package/dist/components/idVerificationContainer/VerificationPendingModal.d.ts +0 -10
- package/dist/components/idVerificationContainer/constants.d.ts +0 -21
- package/dist/components/idVerificationContainer/index.d.ts +0 -16
- package/dist/components/index.d.ts +0 -12
- package/dist/components/loginForm/index.d.ts +0 -31
- package/dist/components/loginModal/SignUpForm.d.ts +0 -10
- package/dist/components/loginModal/constants.d.ts +0 -39
- package/dist/components/loginModal/index.d.ts +0 -22
- package/dist/components/myTicketsContainer/index.d.ts +0 -16
- package/dist/components/myTicketsContainer/row.d.ts +0 -3
- package/dist/components/myTicketsContainer/tableConfig.d.ts +0 -5
- package/dist/components/orderDetailsContainer/CustomFieldsForm.d.ts +0 -12
- package/dist/components/orderDetailsContainer/TicketHolderCustomFields.d.ts +0 -10
- package/dist/components/orderDetailsContainer/index.d.ts +0 -66
- package/dist/components/orderDetailsContainer/ticketsTable.d.ts +0 -46
- package/dist/components/orderDetailsContainer/utils/index.d.ts +0 -6
- package/dist/components/paymentContainer/OrderDetails.d.ts +0 -9
- package/dist/components/paymentContainer/PaymentPlanSection.d.ts +0 -10
- package/dist/components/paymentContainer/handlePayment.d.ts +0 -15
- package/dist/components/paymentContainer/index.d.ts +0 -39
- package/dist/components/preRegistration/FieldsSection.d.ts +0 -19
- package/dist/components/preRegistration/Influancers.d.ts +0 -2
- package/dist/components/preRegistration/PreRegistrationComplete.d.ts +0 -13
- package/dist/components/preRegistration/PreRegistrationInformations.d.ts +0 -2
- package/dist/components/preRegistration/Prewards.d.ts +0 -2
- package/dist/components/preRegistration/ShareOptions.d.ts +0 -2
- package/dist/components/preRegistration/constants.d.ts +0 -2
- package/dist/components/preRegistration/index.d.ts +0 -23
- package/dist/components/preRegistration/utils.d.ts +0 -12
- package/dist/components/registerForm/adapters/index.d.ts +0 -4
- package/dist/components/registerForm/constants.d.ts +0 -1
- package/dist/components/registerForm/index.d.ts +0 -16
- package/dist/components/registerModal/index.d.ts +0 -12
- package/dist/components/resetPasswordContainer/index.d.ts +0 -10
- package/dist/components/rsvpContainer/index.d.ts +0 -7
- package/dist/components/seatMapContainer/SeatMapComponent.d.ts +0 -8
- package/dist/components/seatMapContainer/TicketsSection.d.ts +0 -9
- package/dist/components/seatMapContainer/addToCart.d.ts +0 -14
- package/dist/components/seatMapContainer/index.d.ts +0 -2
- package/dist/components/seatMapContainer/utils.d.ts +0 -6
- package/dist/components/signupModal/index.d.ts +0 -12
- package/dist/components/ticketResale/index.d.ts +0 -12
- package/dist/components/ticketResaleModal/index.d.ts +0 -19
- package/dist/components/ticketsContainer/AccessCodeSection.d.ts +0 -7
- package/dist/components/ticketsContainer/InfoIcon.d.ts +0 -5
- package/dist/components/ticketsContainer/PromoCodeSection.d.ts +0 -15
- package/dist/components/ticketsContainer/ReferralLogic.d.ts +0 -5
- package/dist/components/ticketsContainer/TicketRow.d.ts +0 -13
- package/dist/components/ticketsContainer/TicketsSection.d.ts +0 -20
- package/dist/components/ticketsContainer/TimeSlotTicketRow.d.ts +0 -19
- package/dist/components/ticketsContainer/TimeSlotsSection.d.ts +0 -25
- package/dist/components/ticketsContainer/index.d.ts +0 -93
- package/dist/components/ticketsContainer/utils.d.ts +0 -13
- package/dist/components/timerWidget/index.d.ts +0 -16
- package/dist/components/waitingList/index.d.ts +0 -9
- package/dist/constants/index.d.ts +0 -7
- package/dist/env.d.ts +0 -16
- package/dist/hoc/CustomFields/index.d.ts +0 -3
- package/dist/hoc/index.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/useCookieListener.d.ts +0 -1
- package/dist/hooks/useEventListener.d.ts +0 -1
- package/dist/hooks/useLocalStorageListener.d.ts +0 -1
- package/dist/hooks/useOnline.d.ts +0 -1
- package/dist/hooks/usePixel.d.ts +0 -6
- package/dist/images/Polygon.png +0 -0
- package/dist/images/cross.svg +0 -44
- package/dist/images/done.svg +0 -3
- package/dist/images/edit.svg +0 -10
- package/dist/images/email.svg +0 -1
- package/dist/images/instagram.svg +0 -5
- package/dist/images/logo-ttf.png +0 -0
- package/dist/images/spotify.svg +0 -6
- package/dist/images/user.svg +0 -1
- package/dist/images/xmark.svg +0 -92
- package/dist/normalizers/index.d.ts +0 -4
- package/dist/tf-checkout-react.cjs.development.js +0 -16149
- package/dist/tf-checkout-react.cjs.development.js.map +0 -1
- package/dist/tf-checkout-react.cjs.production.min.js +0 -2
- package/dist/tf-checkout-react.cjs.production.min.js.map +0 -1
- package/dist/tf-checkout-react.esm.js +0 -16116
- package/dist/tf-checkout-react.esm.js.map +0 -1
- package/dist/tf-checkout-styles.css +0 -1
- package/dist/types/add_on.d.ts +0 -8
- package/dist/types/billing-info-data.d.ts +0 -27
- package/dist/types/checkoutPageConfigs.d.ts +0 -13
- package/dist/types/index.d.ts +0 -6
- package/dist/types/order-data.d.ts +0 -15
- package/dist/types/payment-field.d.ts +0 -6
- package/dist/types/payment-plan-configuration.d.ts +0 -18
- package/dist/types/referral-promotion.d.ts +0 -6
- package/dist/types/verification.d.ts +0 -12
- package/dist/utils/adaptStripeErrors.d.ts +0 -8
- package/dist/utils/auth.d.ts +0 -21
- package/dist/utils/cookies.d.ts +0 -3
- package/dist/utils/createCheckoutDataBodyWithDefaultHolder.d.ts +0 -23
- package/dist/utils/createMarkup.d.ts +0 -3
- package/dist/utils/customFields.d.ts +0 -35
- package/dist/utils/downloadPDF.d.ts +0 -1
- package/dist/utils/form.d.ts +0 -3
- package/dist/utils/formikErrorFocus.d.ts +0 -2
- package/dist/utils/getDomain.d.ts +0 -1
- package/dist/utils/getImage.d.ts +0 -1
- package/dist/utils/getQueryVariable.d.ts +0 -1
- package/dist/utils/htmlNodeFromString.d.ts +0 -1
- package/dist/utils/index.d.ts +0 -15
- package/dist/utils/isBrowser.d.ts +0 -1
- package/dist/utils/isValidNumber.d.ts +0 -1
- package/dist/utils/jsonUtils.d.ts +0 -1
- package/dist/utils/replaceVarInString.d.ts +0 -1
- package/dist/utils/setConfigs.d.ts +0 -11
- package/dist/utils/showZero.d.ts +0 -1
- package/dist/validators/index.d.ts +0 -8
- package/src/adapters/customFields.ts +0 -85
- package/src/adapters/index.ts +0 -1
- package/src/api/auth.ts +0 -88
- package/src/api/cart.ts +0 -28
- package/src/api/checkout.ts +0 -28
- package/src/api/common.ts +0 -190
- package/src/api/guestTicketDelegation.ts +0 -79
- package/src/api/index.ts +0 -346
- package/src/api/interceptors.ts +0 -77
- package/src/api/orders.ts +0 -30
- package/src/api/payment.ts +0 -77
- package/src/api/preRegistrationComplete.ts +0 -37
- package/src/api/publicRequest.ts +0 -47
- package/src/api/resale.ts +0 -28
- package/src/assets/images/Polygon.png +0 -0
- package/src/assets/images/cross.svg +0 -44
- package/src/assets/images/done.svg +0 -3
- package/src/assets/images/edit.svg +0 -10
- package/src/assets/images/email.svg +0 -1
- package/src/assets/images/instagram.svg +0 -5
- package/src/assets/images/logo-ttf.png +0 -0
- package/src/assets/images/spotify.svg +0 -6
- package/src/assets/images/user.svg +0 -1
- package/src/assets/images/xmark.svg +0 -92
- package/src/components/account-settings/index.tsx +0 -161
- package/src/components/account-settings/style.css +0 -200
- package/src/components/addonsContainer/AddonComponent.tsx +0 -204
- package/src/components/addonsContainer/SimpleAddonsContainer.tsx +0 -459
- package/src/components/addonsContainer/adapters/index.tsx +0 -62
- package/src/components/addonsContainer/index.tsx +0 -353
- package/src/components/addonsContainer/normalizers/index.ts +0 -5
- package/src/components/addonsContainer/useAddons.ts +0 -248
- package/src/components/addonsContainer/utils/index.tsx +0 -231
- package/src/components/billing-info-container/hooks/index.ts +0 -3
- package/src/components/billing-info-container/hooks/usePaymentContext.ts +0 -22
- package/src/components/billing-info-container/hooks/usePaymentRedirect.ts +0 -145
- package/src/components/billing-info-container/hooks/useStripePayment.ts +0 -119
- package/src/components/billing-info-container/index.tsx +0 -1695
- package/src/components/billing-info-container/style.css +0 -150
- package/src/components/billing-info-container/utils.tsx +0 -413
- package/src/components/common/Checkbox.tsx +0 -51
- package/src/components/common/CheckboxField/index.tsx +0 -41
- package/src/components/common/CopyField.tsx +0 -53
- package/src/components/common/CopyMessageModal.tsx +0 -34
- package/src/components/common/CustomField.tsx +0 -168
- package/src/components/common/DatePickerField.tsx +0 -126
- package/src/components/common/FieldSection/index.tsx +0 -141
- package/src/components/common/FieldSection/utils/index.tsx +0 -92
- package/src/components/common/FormikPhoneNumberField.tsx +0 -51
- package/src/components/common/Loader.tsx +0 -8
- package/src/components/common/ModalComponent/index.tsx +0 -68
- package/src/components/common/NativeSelectFeild/index.tsx +0 -86
- package/src/components/common/PhoneNumberField.tsx +0 -117
- package/src/components/common/PoweredBy.tsx +0 -15
- package/src/components/common/RadioField.tsx +0 -36
- package/src/components/common/RadioGroupField/index.tsx +0 -76
- package/src/components/common/RedirectModal.tsx +0 -43
- package/src/components/common/SelectField/index.tsx +0 -104
- package/src/components/common/SnackbarAlert.tsx +0 -52
- package/src/components/common/index.tsx +0 -11
- package/src/components/common/socials.tsx +0 -33
- package/src/components/confirmModal/index.tsx +0 -60
- package/src/components/confirmationContainer/config.ts +0 -79
- package/src/components/confirmationContainer/index.tsx +0 -349
- package/src/components/confirmationContainer/social-buttons.tsx +0 -149
- package/src/components/confirmationContainer/style.css +0 -206
- package/src/components/countdown/index.tsx +0 -100
- package/src/components/countdown/style.css +0 -10
- package/src/components/delegationsContainer/IssueComponent.tsx +0 -156
- package/src/components/delegationsContainer/IssueTicketForm.tsx +0 -109
- package/src/components/delegationsContainer/TicketsAssignedTable.tsx +0 -55
- package/src/components/delegationsContainer/TicketsAvailableTable.tsx +0 -54
- package/src/components/delegationsContainer/index.tsx +0 -83
- package/src/components/forgotPasswordModal/index.tsx +0 -138
- package/src/components/forgotPasswordModal/style.css +0 -47
- package/src/components/idVerificationContainer/VerificationPendingModal.tsx +0 -47
- package/src/components/idVerificationContainer/constants.ts +0 -27
- package/src/components/idVerificationContainer/index.tsx +0 -323
- package/src/components/index.ts +0 -12
- package/src/components/loginForm/index.tsx +0 -202
- package/src/components/loginModal/SignUpForm.tsx +0 -329
- package/src/components/loginModal/constants.ts +0 -46
- package/src/components/loginModal/index.tsx +0 -263
- package/src/components/loginModal/style.css +0 -116
- package/src/components/myTicketsContainer/index.tsx +0 -228
- package/src/components/myTicketsContainer/row.tsx +0 -46
- package/src/components/myTicketsContainer/style.css +0 -40
- package/src/components/myTicketsContainer/tableConfig.tsx +0 -80
- package/src/components/myTicketsContainer/types.d.ts +0 -41
- package/src/components/orderDetailsContainer/CustomFieldsForm.tsx +0 -75
- package/src/components/orderDetailsContainer/TicketHolderCustomFields.tsx +0 -100
- package/src/components/orderDetailsContainer/index.tsx +0 -586
- package/src/components/orderDetailsContainer/style.css +0 -77
- package/src/components/orderDetailsContainer/ticketsTable.tsx +0 -246
- package/src/components/orderDetailsContainer/utils/index.tsx +0 -55
- package/src/components/paymentContainer/OrderDetails.tsx +0 -277
- package/src/components/paymentContainer/PaymentPlanSection.tsx +0 -169
- package/src/components/paymentContainer/handlePayment.ts +0 -86
- package/src/components/paymentContainer/index.tsx +0 -643
- package/src/components/paymentContainer/style.css +0 -196
- package/src/components/preRegistration/FieldsSection.tsx +0 -150
- package/src/components/preRegistration/Influancers.tsx +0 -34
- package/src/components/preRegistration/PreRegistrationComplete.tsx +0 -186
- package/src/components/preRegistration/PreRegistrationInformations.tsx +0 -60
- package/src/components/preRegistration/Prewards.tsx +0 -41
- package/src/components/preRegistration/ShareOptions.tsx +0 -77
- package/src/components/preRegistration/constants.tsx +0 -169
- package/src/components/preRegistration/index.tsx +0 -330
- package/src/components/preRegistration/style.css +0 -3
- package/src/components/preRegistration/utils.ts +0 -163
- package/src/components/registerForm/adapters/index.tsx +0 -10
- package/src/components/registerForm/constants.tsx +0 -98
- package/src/components/registerForm/index.tsx +0 -188
- package/src/components/registerModal/index.tsx +0 -154
- package/src/components/registerModal/style.css +0 -18
- package/src/components/resetPasswordContainer/index.tsx +0 -102
- package/src/components/resetPasswordContainer/style.css +0 -36
- package/src/components/rsvpContainer/index.tsx +0 -126
- package/src/components/seatMapContainer/SeatMapComponent.tsx +0 -77
- package/src/components/seatMapContainer/TicketsSection.tsx +0 -286
- package/src/components/seatMapContainer/addToCart.ts +0 -66
- package/src/components/seatMapContainer/index.tsx +0 -443
- package/src/components/seatMapContainer/utils.ts +0 -133
- package/src/components/signupModal/index.tsx +0 -178
- package/src/components/signupModal/style.css +0 -58
- package/src/components/ticketResale/index.tsx +0 -86
- package/src/components/ticketResaleModal/index.tsx +0 -224
- package/src/components/ticketResaleModal/style.css +0 -28
- package/src/components/ticketsContainer/AccessCodeSection.tsx +0 -50
- package/src/components/ticketsContainer/InfoIcon.tsx +0 -35
- package/src/components/ticketsContainer/PromoCodeSection.tsx +0 -116
- package/src/components/ticketsContainer/ReferralLogic.tsx +0 -33
- package/src/components/ticketsContainer/TicketRow.tsx +0 -107
- package/src/components/ticketsContainer/TicketsSection.tsx +0 -350
- package/src/components/ticketsContainer/TimeSlotTicketRow.tsx +0 -243
- package/src/components/ticketsContainer/TimeSlotsSection.tsx +0 -194
- package/src/components/ticketsContainer/index.tsx +0 -986
- package/src/components/ticketsContainer/style.css +0 -178
- package/src/components/ticketsContainer/utils.ts +0 -35
- package/src/components/timerWidget/index.tsx +0 -97
- package/src/components/timerWidget/style.css +0 -35
- package/src/components/waitingList/index.tsx +0 -190
- package/src/components/waitingList/style.css +0 -27
- package/src/constants/index.ts +0 -3
- package/src/env.ts +0 -29
- package/src/hoc/CustomFields/index.tsx +0 -85
- package/src/hoc/index.ts +0 -1
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useCookieListener.ts +0 -32
- package/src/hooks/useEventListener.ts +0 -32
- package/src/hooks/useLocalStorageListener.ts +0 -32
- package/src/hooks/useOnline.ts +0 -21
- package/src/hooks/usePixel.ts +0 -88
- package/src/normalizers/index.ts +0 -49
- package/src/types/add_on.ts +0 -8
- package/src/types/api/auth.d.ts +0 -63
- package/src/types/api/axiosResponse.d.ts +0 -6
- package/src/types/api/cart.d.ts +0 -73
- package/src/types/api/checkout.d.ts +0 -92
- package/src/types/api/common.d.ts +0 -153
- package/src/types/api/guestTicketDelegation.d.ts +0 -18
- package/src/types/api/orders.d.ts +0 -116
- package/src/types/api/payment.d.ts +0 -180
- package/src/types/api/preRegistration.d.ts +0 -11
- package/src/types/api/preRegistrationComplete.d.ts +0 -95
- package/src/types/api/ticketResale.d.ts +0 -13
- package/src/types/billing-info-data.ts +0 -37
- package/src/types/checkoutPageConfigs.ts +0 -13
- package/src/types/formFields.d.ts +0 -29
- package/src/types/index.ts +0 -6
- package/src/types/order-data.ts +0 -16
- package/src/types/payment-field.ts +0 -7
- package/src/types/payment-plan-configuration.ts +0 -19
- package/src/types/pre-registration-complete.d.ts +0 -47
- package/src/types/referral-promotion.ts +0 -7
- package/src/types/seatMap.d.ts +0 -159
- package/src/types/verification.ts +0 -12
- package/src/utils/adaptStripeErrors.ts +0 -26
- package/src/utils/auth.ts +0 -45
- package/src/utils/cookies.ts +0 -73
- package/src/utils/createCheckoutDataBodyWithDefaultHolder.ts +0 -81
- package/src/utils/createMarkup.ts +0 -1
- package/src/utils/customFields.ts +0 -80
- package/src/utils/downloadPDF.tsx +0 -42
- package/src/utils/form.ts +0 -34
- package/src/utils/formikErrorFocus.ts +0 -24
- package/src/utils/getDomain.ts +0 -21
- package/src/utils/getImage.ts +0 -14
- package/src/utils/getQueryVariable.ts +0 -13
- package/src/utils/htmlNodeFromString.ts +0 -6
- package/src/utils/index.ts +0 -15
- package/src/utils/isBrowser.ts +0 -2
- package/src/utils/isValidNumber.ts +0 -8
- package/src/utils/jsonUtils.ts +0 -8
- package/src/utils/replaceVarInString.ts +0 -9
- package/src/utils/setConfigs.ts +0 -24
- package/src/utils/showZero.tsx +0 -10
- package/src/validators/index.ts +0 -61
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<BillingInfo/>` — the buyer + attendee details step. Collects: the buyer's
|
|
3
|
+
* name/email + event-gated buyer fields (Instagram/nationality/gender), any
|
|
4
|
+
* ORDER-level custom fields, PER-HOLDER name + ticket-level custom fields (when
|
|
5
|
+
* the event requires holder names or has ticket-scoped fields), and marketing
|
|
6
|
+
* opt-ins. Writes it all into the engine (`setBilling` with `dataCapture`,
|
|
7
|
+
* `setHolders` with `ticketDataCapture`, `setConsent`) so `<Payment/>`'s
|
|
8
|
+
* pre-flight passes. Use it alongside `<Payment/>`, or set those yourself.
|
|
9
|
+
*/
|
|
10
|
+
import React, {
|
|
11
|
+
Fragment,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useState,
|
|
16
|
+
type ChangeEvent,
|
|
17
|
+
type FC,
|
|
18
|
+
type ReactNode,
|
|
19
|
+
} from 'react'
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
hooks,
|
|
23
|
+
mapProfileToBilling,
|
|
24
|
+
missingCheckoutDetails,
|
|
25
|
+
isEmail,
|
|
26
|
+
ticketFieldsFor,
|
|
27
|
+
type CheckoutBilling,
|
|
28
|
+
type CheckoutHolder,
|
|
29
|
+
type CustomFieldValues,
|
|
30
|
+
type EventCustomFieldGroup,
|
|
31
|
+
} from 'tf-checkout-shared'
|
|
32
|
+
import { PhoneInput, type CountryIso2 } from 'react-international-phone'
|
|
33
|
+
|
|
34
|
+
import { Button } from '../ui/Button'
|
|
35
|
+
import { Card, SectionHeader } from '../ui/Card'
|
|
36
|
+
import { useCheckoutContext, useCheckoutValidation } from '../ui/CheckoutProvider'
|
|
37
|
+
import { CustomFieldInput } from '../ui/CustomFields'
|
|
38
|
+
import { Checkbox, Field, FieldRow, Input } from '../ui/Field'
|
|
39
|
+
import { BillingAddressFields } from './billing/BillingAddressFields'
|
|
40
|
+
import { BuyerField } from './billing/BuyerField'
|
|
41
|
+
import { DOB_MIN, isBlankPhone, toDateInputValue, todayISO } from './billing/fieldRules'
|
|
42
|
+
import { TicketHoldersCard } from './billing/TicketHoldersCard'
|
|
43
|
+
import type { BillingDraft, HolderDraft } from './billing/types'
|
|
44
|
+
|
|
45
|
+
export interface BillingInfoProps {
|
|
46
|
+
/** Event context is used to default the country and phone dial code. */
|
|
47
|
+
eventId?: string
|
|
48
|
+
/** Fired once billing is saved to the engine — e.g. reveal `<Payment/>`. */
|
|
49
|
+
onContinue?: () => void
|
|
50
|
+
/** Section heading. Old checkout uses "Billing Information". */
|
|
51
|
+
heading?: string
|
|
52
|
+
/** CTA label. */
|
|
53
|
+
buttonLabel?: string
|
|
54
|
+
/** When provided and the buyer isn't signed in, shows a "Login & skip ahead"
|
|
55
|
+
* row. The current email is passed when login follows an account match. */
|
|
56
|
+
onLogin?: (email?: string) => void
|
|
57
|
+
/** Hide this card only after a signed-in profile satisfies every ordinary
|
|
58
|
+
* billing requirement and the event needs no extra buyer/holder answers. */
|
|
59
|
+
skipWhenProfileComplete?: boolean
|
|
60
|
+
/** Ask guests to repeat their email and require an exact match. */
|
|
61
|
+
requireEmailConfirmation?: boolean
|
|
62
|
+
/** Always collect one named holder per selected ticket. EmbeddedCheckout uses
|
|
63
|
+
* this to match tf-sports; standalone sections default to backend gates. */
|
|
64
|
+
collectTicketHolders?: boolean
|
|
65
|
+
/** When holder details are enabled, also collect each holder's email and phone. */
|
|
66
|
+
collectHolderContact?: boolean
|
|
67
|
+
/** Marketing opt-in labels. Render a checkbox for each provided (unchecked). */
|
|
68
|
+
ttfOptInLabel?: string
|
|
69
|
+
brandOptInLabel?: string
|
|
70
|
+
/** Required host privacy acknowledgement. The URL is supplied by the consumer. */
|
|
71
|
+
privacyPolicy?: CheckoutPrivacyPolicy
|
|
72
|
+
/** Show the legacy inline "Required" error after a failed payment attempt. */
|
|
73
|
+
showPrivacyError?: boolean
|
|
74
|
+
/** Optional numbered badge (old checkout shows none — omitted by default). */
|
|
75
|
+
step?: ReactNode
|
|
76
|
+
className?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CheckoutPrivacyPolicy {
|
|
80
|
+
url: string
|
|
81
|
+
brandName: string
|
|
82
|
+
linkLabel?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const BillingInfo: FC<BillingInfoProps> = ({
|
|
86
|
+
eventId,
|
|
87
|
+
onContinue,
|
|
88
|
+
heading = 'Billing Information',
|
|
89
|
+
buttonLabel = 'Continue to payment',
|
|
90
|
+
onLogin,
|
|
91
|
+
skipWhenProfileComplete = false,
|
|
92
|
+
requireEmailConfirmation = false,
|
|
93
|
+
collectTicketHolders = false,
|
|
94
|
+
collectHolderContact = false,
|
|
95
|
+
ttfOptInLabel = 'Keep me updated about future events from The Ticket Fairy',
|
|
96
|
+
brandOptInLabel,
|
|
97
|
+
privacyPolicy,
|
|
98
|
+
showPrivacyError = false,
|
|
99
|
+
step,
|
|
100
|
+
className,
|
|
101
|
+
}) => {
|
|
102
|
+
const checkout = useCheckoutContext()
|
|
103
|
+
const validation = useCheckoutValidation()
|
|
104
|
+
const profile = hooks.useProfile({ retry: false })
|
|
105
|
+
const event = hooks.useEvent(eventId)
|
|
106
|
+
// No onContinue ⇒ this is one combined form (inside <EmbeddedCheckout/>): mirror the
|
|
107
|
+
// fields into the engine live and drop the "continue" button, so the buyer
|
|
108
|
+
// fills billing + pays with ONE button — the way the old checkout flowed.
|
|
109
|
+
const combined = !onContinue
|
|
110
|
+
|
|
111
|
+
const [billing, setBilling] = useState<BillingDraft>({
|
|
112
|
+
firstName: checkout.billing?.firstName ?? '',
|
|
113
|
+
lastName: checkout.billing?.lastName ?? '',
|
|
114
|
+
email: checkout.billing?.email ?? '',
|
|
115
|
+
confirmEmail: checkout.billing?.confirmEmail ?? '',
|
|
116
|
+
phone: checkout.billing?.phone,
|
|
117
|
+
streetAddress: checkout.billing?.streetAddress,
|
|
118
|
+
city: checkout.billing?.city,
|
|
119
|
+
zip: checkout.billing?.zip,
|
|
120
|
+
countryId: checkout.billing?.countryId,
|
|
121
|
+
stateId: checkout.billing?.stateId,
|
|
122
|
+
countryCode: checkout.billing?.countryCode,
|
|
123
|
+
stateCode: checkout.billing?.stateCode,
|
|
124
|
+
nationality: checkout.billing?.nationality,
|
|
125
|
+
gender: checkout.billing?.gender,
|
|
126
|
+
instagramHandle: checkout.billing?.instagramHandle,
|
|
127
|
+
})
|
|
128
|
+
const [dataCapture, setDataCapture] = useState<CustomFieldValues>(
|
|
129
|
+
checkout.billing?.dataCapture ?? {}
|
|
130
|
+
)
|
|
131
|
+
const [holders, setHolders] = useState<HolderDraft[]>(() =>
|
|
132
|
+
checkout.holders.map((holder) => ({
|
|
133
|
+
firstName: holder.firstName,
|
|
134
|
+
lastName: holder.lastName,
|
|
135
|
+
email: holder.email,
|
|
136
|
+
phone: holder.phone ?? '',
|
|
137
|
+
ticketTypeId: holder.ticketTypeId,
|
|
138
|
+
ticketDataCapture: holder.ticketDataCapture ?? {},
|
|
139
|
+
}))
|
|
140
|
+
)
|
|
141
|
+
const [consent, setConsent] = useState({ ttfOptIn: false, brandOptIn: false })
|
|
142
|
+
const [dob, setDob] = useState(() => {
|
|
143
|
+
const b = checkout.billing
|
|
144
|
+
if (!b?.dobDay || !b.dobMonth || !b.dobYear) return ''
|
|
145
|
+
return `${b.dobYear}-${String(b.dobMonth).padStart(2, '0')}-${String(
|
|
146
|
+
b.dobDay
|
|
147
|
+
).padStart(2, '0')}`
|
|
148
|
+
}) // ISO yyyy-mm-dd
|
|
149
|
+
const restoredHolders = useRef(checkout.holders)
|
|
150
|
+
const profileSeeded = useRef(false)
|
|
151
|
+
const countryDefaultApplied = useRef(Boolean(checkout.billing?.countryId))
|
|
152
|
+
|
|
153
|
+
const gates = checkout.fieldGates
|
|
154
|
+
|
|
155
|
+
// Custom-field groups by scope.
|
|
156
|
+
const orderGroups = useMemo(
|
|
157
|
+
() => checkout.customFields.filter((g) => g.scope === 'order'),
|
|
158
|
+
[checkout.customFields]
|
|
159
|
+
)
|
|
160
|
+
const ticketGroups = useMemo(
|
|
161
|
+
() => checkout.customFields.filter((g) => g.scope === 'ticket'),
|
|
162
|
+
[checkout.customFields]
|
|
163
|
+
)
|
|
164
|
+
const ticketFields = useMemo(
|
|
165
|
+
() => ticketGroups.flatMap((g) => g.fields),
|
|
166
|
+
[ticketGroups]
|
|
167
|
+
)
|
|
168
|
+
const holderNamesRequired = checkout.holderNamesRequired || collectTicketHolders
|
|
169
|
+
const showHolders = holderNamesRequired || ticketFields.length > 0
|
|
170
|
+
|
|
171
|
+
// One holder slot per ticket, tagged with its ticket type (drives ticket-field
|
|
172
|
+
// scoping + the engine's per-holder validation).
|
|
173
|
+
const slots = useMemo(() => {
|
|
174
|
+
const out: { ticketTypeId?: string; ticketName?: string }[] = []
|
|
175
|
+
const lines = checkout.breakdown?.tickets ?? []
|
|
176
|
+
for (const line of lines)
|
|
177
|
+
for (let i = 0; i < line.quantity; i++)
|
|
178
|
+
out.push({ ticketTypeId: line.ticketTypeId, ticketName: line.name })
|
|
179
|
+
if (!out.length) {
|
|
180
|
+
const total = checkout.selections.reduce((n, s) => n + s.quantity, 0)
|
|
181
|
+
for (let i = 0; i < total; i++) out.push({})
|
|
182
|
+
}
|
|
183
|
+
return out
|
|
184
|
+
}, [checkout.breakdown, checkout.selections])
|
|
185
|
+
|
|
186
|
+
// The initial state above restores anything already on the engine. A profile
|
|
187
|
+
// that arrives later may fill blanks ONCE, but must never be consulted again
|
|
188
|
+
// while the buyer edits — otherwise clearing the final character restores the
|
|
189
|
+
// previous engine value on the next live commit.
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const p = profile.data
|
|
192
|
+
if (!p || profileSeeded.current) return
|
|
193
|
+
profileSeeded.current = true
|
|
194
|
+
const profileBilling = mapProfileToBilling(p)
|
|
195
|
+
setBilling((prev) => {
|
|
196
|
+
const next = {
|
|
197
|
+
...prev,
|
|
198
|
+
firstName: prev.firstName || profileBilling.firstName || '',
|
|
199
|
+
lastName: prev.lastName || profileBilling.lastName || '',
|
|
200
|
+
email: prev.email || profileBilling.email || '',
|
|
201
|
+
confirmEmail:
|
|
202
|
+
prev.confirmEmail || (checkout.isLoggedIn ? profileBilling.email : '') || '',
|
|
203
|
+
phone:
|
|
204
|
+
(isBlankPhone(prev.phone) ? undefined : prev.phone) || profileBilling.phone,
|
|
205
|
+
streetAddress: prev.streetAddress || profileBilling.streetAddress,
|
|
206
|
+
city: prev.city || profileBilling.city,
|
|
207
|
+
zip: prev.zip || profileBilling.zip,
|
|
208
|
+
countryId: prev.countryId || profileBilling.countryId,
|
|
209
|
+
stateId: prev.stateId || profileBilling.stateId,
|
|
210
|
+
}
|
|
211
|
+
const keys = Object.keys(next) as (keyof BillingDraft)[]
|
|
212
|
+
return keys.every((k) => next[k] === prev[k]) ? prev : next
|
|
213
|
+
})
|
|
214
|
+
// Date of birth lives outside `billing`, so it has to be seeded here too —
|
|
215
|
+
// and it has to be here rather than in its own effect or in the useState
|
|
216
|
+
// initialiser: the initialiser runs before the profile resolves, and a
|
|
217
|
+
// separate effect would re-fire and overwrite the buyer, since the live
|
|
218
|
+
// commit below depends on `dob`. This effect already runs exactly once.
|
|
219
|
+
setDob((prev) => {
|
|
220
|
+
if (prev) return prev
|
|
221
|
+
const { dobYear, dobMonth, dobDay } = profileBilling
|
|
222
|
+
return toDateInputValue(dobYear, dobMonth, dobDay) ?? prev
|
|
223
|
+
})
|
|
224
|
+
}, [profile.data, checkout.isLoggedIn])
|
|
225
|
+
|
|
226
|
+
// Resize holders to the slot count, preserving typed values; tag ticket type.
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
setHolders((prev) =>
|
|
229
|
+
slots.map((s, i) => ({
|
|
230
|
+
firstName: prev[i]?.firstName ?? restoredHolders.current[i]?.firstName ?? '',
|
|
231
|
+
lastName: prev[i]?.lastName ?? restoredHolders.current[i]?.lastName ?? '',
|
|
232
|
+
email: prev[i]?.email ?? restoredHolders.current[i]?.email ?? '',
|
|
233
|
+
phone: prev[i]?.phone ?? restoredHolders.current[i]?.phone ?? '',
|
|
234
|
+
ticketDataCapture:
|
|
235
|
+
prev[i]?.ticketDataCapture ??
|
|
236
|
+
restoredHolders.current[i]?.ticketDataCapture ??
|
|
237
|
+
{},
|
|
238
|
+
ticketTypeId: s.ticketTypeId,
|
|
239
|
+
ticketName: s.ticketName,
|
|
240
|
+
}))
|
|
241
|
+
)
|
|
242
|
+
}, [slots])
|
|
243
|
+
|
|
244
|
+
// Match the storefront: ticket 1 silently starts as the buyer. The legacy
|
|
245
|
+
// checkout has no extra "use buyer details" control.
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (!showHolders) return
|
|
248
|
+
setHolders((prev) =>
|
|
249
|
+
prev.map((holder, index) =>
|
|
250
|
+
index === 0
|
|
251
|
+
? {
|
|
252
|
+
...holder,
|
|
253
|
+
firstName: billing.firstName,
|
|
254
|
+
lastName: billing.lastName,
|
|
255
|
+
email: billing.email,
|
|
256
|
+
phone: billing.phone ?? '',
|
|
257
|
+
}
|
|
258
|
+
: holder
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
}, [showHolders, billing.firstName, billing.lastName, billing.email, billing.phone])
|
|
262
|
+
|
|
263
|
+
// Debounced "this email already has an account" check → nudges the buyer to
|
|
264
|
+
// sign in (autofill + avoid duplicate accounts). Skips signed-in buyers.
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
const email = billing.email.trim()
|
|
267
|
+
if (checkout.isLoggedIn || !isEmail(email)) return
|
|
268
|
+
const t = setTimeout(
|
|
269
|
+
() => checkout.checkBuyerEmail(email).catch(() => undefined),
|
|
270
|
+
600
|
|
271
|
+
)
|
|
272
|
+
return () => clearTimeout(t)
|
|
273
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
274
|
+
}, [billing.email, checkout.isLoggedIn])
|
|
275
|
+
const emailTaken =
|
|
276
|
+
!checkout.isLoggedIn && checkout.existingAccountEmail === billing.email.trim()
|
|
277
|
+
|
|
278
|
+
const needsNationality = checkout.buyerFields.some((f) => f.kind === 'nationality')
|
|
279
|
+
const countriesQuery = hooks.useCountries({
|
|
280
|
+
enabled: needsNationality || gates.showAddress,
|
|
281
|
+
})
|
|
282
|
+
const countries = useMemo(() => countriesQuery.data ?? [], [countriesQuery.data])
|
|
283
|
+
const statesQuery = hooks.useStates(billing.countryId, {
|
|
284
|
+
enabled: Boolean(billing.countryId),
|
|
285
|
+
})
|
|
286
|
+
const states = useMemo(() => statesQuery.data ?? [], [statesQuery.data])
|
|
287
|
+
|
|
288
|
+
// Event country → signed-in profile → US, matching the hosted checkout's
|
|
289
|
+
// country preference order. Also retain the ISO code for Stripe + phone UI.
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (
|
|
292
|
+
countryDefaultApplied.current ||
|
|
293
|
+
billing.countryId ||
|
|
294
|
+
countries.length === 0 ||
|
|
295
|
+
profile.isLoading ||
|
|
296
|
+
(eventId && event.isLoading)
|
|
297
|
+
) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const eventCode = event.data?.countryCode?.toLowerCase()
|
|
301
|
+
const preferred =
|
|
302
|
+
countries.find((country) => country.code?.toLowerCase() === eventCode) ??
|
|
303
|
+
countries.find(
|
|
304
|
+
(country) =>
|
|
305
|
+
profile.data?.countryId !== null &&
|
|
306
|
+
profile.data?.countryId !== undefined &&
|
|
307
|
+
String(country.id) === String(profile.data.countryId)
|
|
308
|
+
) ??
|
|
309
|
+
countries.find((country) => country.code?.toLowerCase() === 'us')
|
|
310
|
+
if (!preferred) return
|
|
311
|
+
countryDefaultApplied.current = true
|
|
312
|
+
setBilling((current) => {
|
|
313
|
+
// The profile-seed effect above can run in this same commit, so the
|
|
314
|
+
// `billing` this effect closed over is stale — it still shows no country
|
|
315
|
+
// even though the seed just supplied one (with its state). Re-check the
|
|
316
|
+
// live value here: without it the default overwrote the seeded country
|
|
317
|
+
// and blanked `stateId`, which is why a returning buyer's State/County
|
|
318
|
+
// came back empty while every other saved field prefilled.
|
|
319
|
+
if (current.countryId) return current
|
|
320
|
+
return {
|
|
321
|
+
...current,
|
|
322
|
+
countryId: String(preferred.id),
|
|
323
|
+
countryCode: preferred.code?.toUpperCase(),
|
|
324
|
+
stateId: undefined,
|
|
325
|
+
stateCode: undefined,
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
}, [
|
|
329
|
+
billing.countryId,
|
|
330
|
+
countries,
|
|
331
|
+
profile.isLoading,
|
|
332
|
+
profile.data?.countryId,
|
|
333
|
+
eventId,
|
|
334
|
+
event.isLoading,
|
|
335
|
+
event.data?.countryCode,
|
|
336
|
+
])
|
|
337
|
+
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
if (!billing.countryId) return
|
|
340
|
+
const country = countries.find(
|
|
341
|
+
(candidate) => String(candidate.id) === String(billing.countryId)
|
|
342
|
+
)
|
|
343
|
+
const countryCode = country?.code?.toUpperCase()
|
|
344
|
+
if (countryCode && countryCode !== billing.countryCode) {
|
|
345
|
+
setBilling((current) => ({ ...current, countryCode }))
|
|
346
|
+
}
|
|
347
|
+
}, [billing.countryId, billing.countryCode, countries])
|
|
348
|
+
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (!billing.stateId) return
|
|
351
|
+
const state = states.find(
|
|
352
|
+
(candidate) => String(candidate.id) === String(billing.stateId)
|
|
353
|
+
)
|
|
354
|
+
const stateCode = state?.code
|
|
355
|
+
if (stateCode && stateCode !== billing.stateCode) {
|
|
356
|
+
setBilling((current) => ({ ...current, stateCode }))
|
|
357
|
+
}
|
|
358
|
+
}, [billing.stateId, billing.stateCode, states])
|
|
359
|
+
|
|
360
|
+
const setB = (k: keyof BillingDraft) => (e: ChangeEvent<HTMLInputElement>) =>
|
|
361
|
+
setBilling((b) => ({ ...b, [k]: e.target.value }))
|
|
362
|
+
|
|
363
|
+
const patchHolder = (i: number, patch: Partial<HolderDraft>) =>
|
|
364
|
+
setHolders((hs) => hs.map((h, j) => (j === i ? { ...h, ...patch } : h)))
|
|
365
|
+
|
|
366
|
+
const dobMax = useMemo(todayISO, [])
|
|
367
|
+
const [dobYear, dobMonth, dobDay] = dob ? dob.split('-').map(Number) : []
|
|
368
|
+
// A dial code on its own is not a phone number — see `isBlankPhone`.
|
|
369
|
+
const buyerPhone = isBlankPhone(billing.phone) ? undefined : billing.phone
|
|
370
|
+
const committedBilling: CheckoutBilling = {
|
|
371
|
+
...checkout.billing,
|
|
372
|
+
...billing,
|
|
373
|
+
phone: buyerPhone,
|
|
374
|
+
stateRequired: gates.showAddress && Boolean(billing.countryId) && states.length > 0,
|
|
375
|
+
dataCapture,
|
|
376
|
+
dobDay,
|
|
377
|
+
dobMonth,
|
|
378
|
+
dobYear,
|
|
379
|
+
}
|
|
380
|
+
const committedHolders: CheckoutHolder[] = holders.map((holder) => {
|
|
381
|
+
const holderPhone = isBlankPhone(holder.phone) ? '' : holder.phone
|
|
382
|
+
return {
|
|
383
|
+
firstName: holderNamesRequired
|
|
384
|
+
? holder.firstName
|
|
385
|
+
: holder.firstName || billing.firstName,
|
|
386
|
+
lastName: holderNamesRequired
|
|
387
|
+
? holder.lastName
|
|
388
|
+
: holder.lastName || billing.lastName,
|
|
389
|
+
email:
|
|
390
|
+
showHolders && collectHolderContact
|
|
391
|
+
? holder.email
|
|
392
|
+
: holder.email || billing.email,
|
|
393
|
+
phone:
|
|
394
|
+
showHolders && collectHolderContact
|
|
395
|
+
? holderPhone
|
|
396
|
+
: holderPhone || buyerPhone || '',
|
|
397
|
+
ticketTypeId: holder.ticketTypeId,
|
|
398
|
+
ticketDataCapture: holder.ticketDataCapture,
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
const missingDetails = missingCheckoutDetails(
|
|
402
|
+
{
|
|
403
|
+
billing: committedBilling,
|
|
404
|
+
holders: committedHolders,
|
|
405
|
+
selections: checkout.selections,
|
|
406
|
+
breakdown: checkout.breakdown,
|
|
407
|
+
holderNamesRequired: checkout.holderNamesRequired,
|
|
408
|
+
fieldGates: checkout.fieldGates,
|
|
409
|
+
customFields: checkout.customFields,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
requireEmailConfirmation,
|
|
413
|
+
collectTicketHolders,
|
|
414
|
+
requireHolderContact: collectHolderContact,
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
const complete = missingDetails.length === 0
|
|
418
|
+
const showValidation = validation.attempt > 0
|
|
419
|
+
const missingSet = new Set(missingDetails)
|
|
420
|
+
const fieldError = (key: string, message = 'This field is required.') =>
|
|
421
|
+
showValidation && missingSet.has(key) ? message : undefined
|
|
422
|
+
const dobError = showValidation
|
|
423
|
+
? missingDetails.find(
|
|
424
|
+
(detail) => detail === 'Date of birth' || detail.startsWith('Age ')
|
|
425
|
+
)
|
|
426
|
+
: undefined
|
|
427
|
+
const phoneCountry = (
|
|
428
|
+
billing.countryCode ??
|
|
429
|
+
event.data?.countryCode ??
|
|
430
|
+
'US'
|
|
431
|
+
)?.toLowerCase() as CountryIso2
|
|
432
|
+
|
|
433
|
+
const commit = () => {
|
|
434
|
+
checkout.setBilling(committedBilling)
|
|
435
|
+
checkout.setHolders(committedHolders)
|
|
436
|
+
if (ttfOptInLabel || brandOptInLabel) checkout.setConsent(consent)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Combined mode: keep the engine in sync as the form is filled, so <Payment/>
|
|
440
|
+
// is always chargeable without a separate "continue" click.
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
if (combined) commit()
|
|
443
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
444
|
+
}, [combined, billing, holders, consent, dob, dataCapture])
|
|
445
|
+
|
|
446
|
+
const renderGroupFields = (
|
|
447
|
+
group: EventCustomFieldGroup,
|
|
448
|
+
values: CustomFieldValues,
|
|
449
|
+
onChange: (name: string, v: string | string[]) => void,
|
|
450
|
+
ticketTypeId?: string,
|
|
451
|
+
ticketIndex?: number
|
|
452
|
+
) =>
|
|
453
|
+
ticketFieldsFor(group.fields, ticketTypeId).map((f) => {
|
|
454
|
+
const label = f.label || f.name
|
|
455
|
+
const missingKey =
|
|
456
|
+
ticketIndex === undefined ? label : `${label} (ticket ${ticketIndex + 1})`
|
|
457
|
+
return (
|
|
458
|
+
<CustomFieldInput
|
|
459
|
+
key={f.id}
|
|
460
|
+
field={f}
|
|
461
|
+
value={values[f.name]}
|
|
462
|
+
error={fieldError(missingKey)}
|
|
463
|
+
onChange={(v) => onChange(f.name, v)}
|
|
464
|
+
/>
|
|
465
|
+
)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
const hasEventSpecificDetails =
|
|
469
|
+
checkout.buyerFields.length > 0 ||
|
|
470
|
+
checkout.customFields.some((group) => group.fields.length > 0) ||
|
|
471
|
+
showHolders ||
|
|
472
|
+
gates.showDob
|
|
473
|
+
const skipBilling =
|
|
474
|
+
skipWhenProfileComplete &&
|
|
475
|
+
checkout.isLoggedIn &&
|
|
476
|
+
profile.isSuccess &&
|
|
477
|
+
complete &&
|
|
478
|
+
!hasEventSpecificDetails
|
|
479
|
+
|
|
480
|
+
// This is the safe EmbeddedCheckout interpretation of legacy
|
|
481
|
+
// `skip_billing_page`: payment remains visible; only a fully satisfied,
|
|
482
|
+
// ordinary billing card disappears.
|
|
483
|
+
if (skipBilling) return null
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<>
|
|
487
|
+
<Card className={className}>
|
|
488
|
+
<SectionHeader step={step} title={heading} />
|
|
489
|
+
|
|
490
|
+
{onLogin && !checkout.isLoggedIn && (
|
|
491
|
+
<div className="tf-checkout-billing__login">
|
|
492
|
+
<span>Login & skip ahead:</span>
|
|
493
|
+
<button
|
|
494
|
+
type="button"
|
|
495
|
+
className="tf-checkout-billing__login-btn"
|
|
496
|
+
onClick={() => onLogin()}
|
|
497
|
+
>
|
|
498
|
+
Login
|
|
499
|
+
</button>
|
|
500
|
+
</div>
|
|
501
|
+
)}
|
|
502
|
+
|
|
503
|
+
<FieldRow>
|
|
504
|
+
<Field
|
|
505
|
+
error={fieldError('First name', 'First name is required.')}
|
|
506
|
+
invalid={Boolean(fieldError('First name'))}
|
|
507
|
+
>
|
|
508
|
+
<Input
|
|
509
|
+
placeholder="First name *"
|
|
510
|
+
value={billing.firstName}
|
|
511
|
+
required
|
|
512
|
+
invalid={Boolean(fieldError('First name'))}
|
|
513
|
+
aria-invalid={Boolean(fieldError('First name'))}
|
|
514
|
+
onChange={setB('firstName')}
|
|
515
|
+
/>
|
|
516
|
+
</Field>
|
|
517
|
+
<Field
|
|
518
|
+
error={fieldError('Last name', 'Last name is required.')}
|
|
519
|
+
invalid={Boolean(fieldError('Last name'))}
|
|
520
|
+
>
|
|
521
|
+
<Input
|
|
522
|
+
placeholder="Last name *"
|
|
523
|
+
value={billing.lastName}
|
|
524
|
+
required
|
|
525
|
+
invalid={Boolean(fieldError('Last name'))}
|
|
526
|
+
aria-invalid={Boolean(fieldError('Last name'))}
|
|
527
|
+
onChange={setB('lastName')}
|
|
528
|
+
/>
|
|
529
|
+
</Field>
|
|
530
|
+
</FieldRow>
|
|
531
|
+
<FieldRow>
|
|
532
|
+
<Field
|
|
533
|
+
error={fieldError('Valid email address', 'Enter a valid email address.')}
|
|
534
|
+
invalid={Boolean(fieldError('Valid email address'))}
|
|
535
|
+
>
|
|
536
|
+
<Input
|
|
537
|
+
type="email"
|
|
538
|
+
autoComplete="email"
|
|
539
|
+
placeholder="Email *"
|
|
540
|
+
value={billing.email}
|
|
541
|
+
required
|
|
542
|
+
invalid={Boolean(fieldError('Valid email address'))}
|
|
543
|
+
aria-invalid={Boolean(fieldError('Valid email address'))}
|
|
544
|
+
onChange={setB('email')}
|
|
545
|
+
/>
|
|
546
|
+
</Field>
|
|
547
|
+
{requireEmailConfirmation && !checkout.isLoggedIn && (
|
|
548
|
+
<Field
|
|
549
|
+
error={fieldError(
|
|
550
|
+
'Matching email confirmation',
|
|
551
|
+
'Email addresses must match.'
|
|
552
|
+
)}
|
|
553
|
+
invalid={Boolean(fieldError('Matching email confirmation'))}
|
|
554
|
+
>
|
|
555
|
+
<Input
|
|
556
|
+
type="email"
|
|
557
|
+
autoComplete="email"
|
|
558
|
+
placeholder="Confirm email *"
|
|
559
|
+
value={billing.confirmEmail ?? ''}
|
|
560
|
+
required
|
|
561
|
+
invalid={Boolean(fieldError('Matching email confirmation'))}
|
|
562
|
+
aria-invalid={Boolean(fieldError('Matching email confirmation'))}
|
|
563
|
+
onChange={setB('confirmEmail')}
|
|
564
|
+
/>
|
|
565
|
+
</Field>
|
|
566
|
+
)}
|
|
567
|
+
</FieldRow>
|
|
568
|
+
{!showValidation &&
|
|
569
|
+
requireEmailConfirmation &&
|
|
570
|
+
billing.confirmEmail &&
|
|
571
|
+
billing.confirmEmail.trim().toLowerCase() !==
|
|
572
|
+
billing.email.trim().toLowerCase() && (
|
|
573
|
+
<p className="tf-checkout-error-text">The email addresses do not match.</p>
|
|
574
|
+
)}
|
|
575
|
+
{emailTaken && (
|
|
576
|
+
<p className="tf-checkout-note tf-checkout-billing__email-taken">
|
|
577
|
+
This email is already registered.{' '}
|
|
578
|
+
{onLogin ? (
|
|
579
|
+
<button
|
|
580
|
+
type="button"
|
|
581
|
+
className="tf-checkout-billing__login-link"
|
|
582
|
+
onClick={() => onLogin(billing.email)}
|
|
583
|
+
>
|
|
584
|
+
Log in to autofill your details
|
|
585
|
+
</button>
|
|
586
|
+
) : (
|
|
587
|
+
'Log in to autofill your details.'
|
|
588
|
+
)}
|
|
589
|
+
</p>
|
|
590
|
+
)}
|
|
591
|
+
|
|
592
|
+
{checkout.buyerFields.map((f) => (
|
|
593
|
+
<BuyerField
|
|
594
|
+
key={f.key}
|
|
595
|
+
field={f}
|
|
596
|
+
value={String(billing[f.key] ?? '')}
|
|
597
|
+
onChange={(v) => setBilling((b) => ({ ...b, [f.key]: v }))}
|
|
598
|
+
countries={countries}
|
|
599
|
+
error={fieldError(
|
|
600
|
+
f.kind === 'nationality'
|
|
601
|
+
? 'Nationality'
|
|
602
|
+
: f.kind === 'gender'
|
|
603
|
+
? 'Gender'
|
|
604
|
+
: 'Instagram handle'
|
|
605
|
+
)}
|
|
606
|
+
/>
|
|
607
|
+
))}
|
|
608
|
+
|
|
609
|
+
{gates.showPhone && (
|
|
610
|
+
<Field
|
|
611
|
+
label={`Phone${gates.phoneRequired ? ' *' : ''}`}
|
|
612
|
+
error={fieldError('Valid phone number', 'Enter a valid phone number.')}
|
|
613
|
+
invalid={Boolean(fieldError('Valid phone number'))}
|
|
614
|
+
>
|
|
615
|
+
<PhoneInput
|
|
616
|
+
className="tf-checkout-phone"
|
|
617
|
+
inputClassName={`tf-checkout-input tf-checkout-phone__input${
|
|
618
|
+
fieldError('Valid phone number') ? ' tf-checkout-input--invalid' : ''
|
|
619
|
+
}`}
|
|
620
|
+
defaultCountry={phoneCountry}
|
|
621
|
+
// The flag owns the country; the input holds the national number
|
|
622
|
+
// only. Without `disableDialCodeAndPrefix` the empty field still
|
|
623
|
+
// expects a full international number, so a Mexican buyer typing
|
|
624
|
+
// "5512345678" has it read as a dial code and the country guess
|
|
625
|
+
// flips them to Brazil (+55) — submitting the wrong code.
|
|
626
|
+
disableDialCodeAndPrefix
|
|
627
|
+
showDisabledDialCodeAndPrefix
|
|
628
|
+
disableDialCodePrefill
|
|
629
|
+
value={billing.phone ?? ''}
|
|
630
|
+
required={gates.phoneRequired}
|
|
631
|
+
placeholder="Phone number"
|
|
632
|
+
inputProps={{
|
|
633
|
+
autoComplete: 'tel',
|
|
634
|
+
'aria-label': 'Phone',
|
|
635
|
+
'aria-invalid': Boolean(fieldError('Valid phone number')),
|
|
636
|
+
}}
|
|
637
|
+
onChange={(phone) => setBilling((current) => ({ ...current, phone }))}
|
|
638
|
+
/>
|
|
639
|
+
</Field>
|
|
640
|
+
)}
|
|
641
|
+
|
|
642
|
+
{gates.showDob && (
|
|
643
|
+
<Field
|
|
644
|
+
label={`Date of birth *${
|
|
645
|
+
gates.minimumAge ? ` (must be ${gates.minimumAge}+)` : ''
|
|
646
|
+
}`}
|
|
647
|
+
error={
|
|
648
|
+
dobError === 'Date of birth'
|
|
649
|
+
? 'Date of birth is required.'
|
|
650
|
+
: dobError
|
|
651
|
+
? `You must be ${gates.minimumAge} or older.`
|
|
652
|
+
: undefined
|
|
653
|
+
}
|
|
654
|
+
invalid={Boolean(dobError)}
|
|
655
|
+
>
|
|
656
|
+
<Input
|
|
657
|
+
type="date"
|
|
658
|
+
value={dob}
|
|
659
|
+
required
|
|
660
|
+
// A date input with no bounds lets the browser's year segment run
|
|
661
|
+
// to six digits ("202555") and accept dates in the future. The
|
|
662
|
+
// range both caps the year at four digits and keeps the value a
|
|
663
|
+
// date someone could actually have been born on.
|
|
664
|
+
min={DOB_MIN}
|
|
665
|
+
max={dobMax}
|
|
666
|
+
invalid={Boolean(dobError)}
|
|
667
|
+
aria-invalid={Boolean(dobError)}
|
|
668
|
+
onChange={(e) => setDob(e.target.value)}
|
|
669
|
+
/>
|
|
670
|
+
</Field>
|
|
671
|
+
)}
|
|
672
|
+
|
|
673
|
+
{gates.showAddress && (
|
|
674
|
+
<BillingAddressFields
|
|
675
|
+
billing={billing}
|
|
676
|
+
countries={countries}
|
|
677
|
+
states={states}
|
|
678
|
+
fieldError={fieldError}
|
|
679
|
+
setBilling={setBilling}
|
|
680
|
+
/>
|
|
681
|
+
)}
|
|
682
|
+
|
|
683
|
+
{/* Order-level custom fields */}
|
|
684
|
+
{orderGroups.map((g) => (
|
|
685
|
+
<Fragment key={g.id}>
|
|
686
|
+
{g.label && (
|
|
687
|
+
<div className="tf-checkout-billing__section-label">{g.label}</div>
|
|
688
|
+
)}
|
|
689
|
+
{renderGroupFields(g, dataCapture, (name, v) =>
|
|
690
|
+
setDataCapture((d) => ({ ...d, [name]: v }))
|
|
691
|
+
)}
|
|
692
|
+
</Fragment>
|
|
693
|
+
))}
|
|
694
|
+
|
|
695
|
+
{/* Marketing opt-ins (optional) */}
|
|
696
|
+
{brandOptInLabel && (
|
|
697
|
+
<Checkbox
|
|
698
|
+
checked={consent.brandOptIn}
|
|
699
|
+
onChange={(e) => setConsent((c) => ({ ...c, brandOptIn: e.target.checked }))}
|
|
700
|
+
className="tf-checkout-checkbox--consent"
|
|
701
|
+
label={brandOptInLabel}
|
|
702
|
+
/>
|
|
703
|
+
)}
|
|
704
|
+
{privacyPolicy ? (
|
|
705
|
+
<div
|
|
706
|
+
className={
|
|
707
|
+
(showPrivacyError || showValidation) && !consent.ttfOptIn
|
|
708
|
+
? 'tf-checkout-consent--error'
|
|
709
|
+
: ''
|
|
710
|
+
}
|
|
711
|
+
data-checkout-invalid={
|
|
712
|
+
((showPrivacyError || showValidation) && !consent.ttfOptIn) || undefined
|
|
713
|
+
}
|
|
714
|
+
>
|
|
715
|
+
<Checkbox
|
|
716
|
+
required
|
|
717
|
+
checked={consent.ttfOptIn}
|
|
718
|
+
aria-invalid={(showPrivacyError || showValidation) && !consent.ttfOptIn}
|
|
719
|
+
onChange={(e) => setConsent((c) => ({ ...c, ttfOptIn: e.target.checked }))}
|
|
720
|
+
className="tf-checkout-checkbox--consent"
|
|
721
|
+
label={
|
|
722
|
+
<>
|
|
723
|
+
I have read and agree to {privacyPolicy.brandName}{' '}
|
|
724
|
+
<a href={privacyPolicy.url} target="_blank" rel="noreferrer">
|
|
725
|
+
{privacyPolicy.linkLabel || 'Privacy Policy.'}
|
|
726
|
+
</a>
|
|
727
|
+
{' *'}
|
|
728
|
+
</>
|
|
729
|
+
}
|
|
730
|
+
/>
|
|
731
|
+
{(showPrivacyError || showValidation) && !consent.ttfOptIn && (
|
|
732
|
+
<div className="tf-checkout-consent__error">Required</div>
|
|
733
|
+
)}
|
|
734
|
+
</div>
|
|
735
|
+
) : ttfOptInLabel ? (
|
|
736
|
+
<Checkbox
|
|
737
|
+
checked={consent.ttfOptIn}
|
|
738
|
+
onChange={(e) => setConsent((c) => ({ ...c, ttfOptIn: e.target.checked }))}
|
|
739
|
+
className="tf-checkout-checkbox--consent"
|
|
740
|
+
label={ttfOptInLabel}
|
|
741
|
+
/>
|
|
742
|
+
) : null}
|
|
743
|
+
{!combined && (
|
|
744
|
+
<>
|
|
745
|
+
<Button
|
|
746
|
+
block
|
|
747
|
+
onClick={() => {
|
|
748
|
+
if (!complete) {
|
|
749
|
+
validation.requestValidation()
|
|
750
|
+
return
|
|
751
|
+
}
|
|
752
|
+
commit()
|
|
753
|
+
onContinue?.()
|
|
754
|
+
}}
|
|
755
|
+
>
|
|
756
|
+
{buttonLabel}
|
|
757
|
+
</Button>
|
|
758
|
+
</>
|
|
759
|
+
)}
|
|
760
|
+
</Card>
|
|
761
|
+
{showHolders && (
|
|
762
|
+
<TicketHoldersCard
|
|
763
|
+
holders={holders}
|
|
764
|
+
holderNamesRequired={holderNamesRequired}
|
|
765
|
+
collectHolderContact={collectHolderContact}
|
|
766
|
+
phoneCountry={phoneCountry}
|
|
767
|
+
fieldError={fieldError}
|
|
768
|
+
patchHolder={patchHolder}
|
|
769
|
+
renderCustomFields={(holder, index) =>
|
|
770
|
+
ticketGroups.map((group) =>
|
|
771
|
+
renderGroupFields(
|
|
772
|
+
group,
|
|
773
|
+
holder.ticketDataCapture,
|
|
774
|
+
(name, value) =>
|
|
775
|
+
patchHolder(index, {
|
|
776
|
+
ticketDataCapture: {
|
|
777
|
+
...holder.ticketDataCapture,
|
|
778
|
+
[name]: value,
|
|
779
|
+
},
|
|
780
|
+
}),
|
|
781
|
+
holder.ticketTypeId,
|
|
782
|
+
index
|
|
783
|
+
)
|
|
784
|
+
)
|
|
785
|
+
}
|
|
786
|
+
/>
|
|
787
|
+
)}
|
|
788
|
+
</>
|
|
789
|
+
)
|
|
790
|
+
}
|