tf-checkout-react 1.7.1 → 1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/auth.d.ts +22 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/components/billing-info-container/utils.d.ts +1 -0
- package/dist/components/common/PhoneNumberField.d.ts +1 -1
- package/dist/components/loginForm/index.d.ts +1 -0
- package/dist/components/loginModal/index.d.ts +1 -0
- package/dist/components/preRegistration/PreRegistrationComplete.d.ts +2 -0
- package/dist/tf-checkout-react.cjs.development.js +2166 -1864
- package/dist/tf-checkout-react.cjs.development.js.map +1 -1
- package/dist/tf-checkout-react.cjs.production.min.js +1 -1
- package/dist/tf-checkout-react.cjs.production.min.js.map +1 -1
- package/dist/tf-checkout-react.esm.js +2166 -1864
- package/dist/tf-checkout-react.esm.js.map +1 -1
- package/dist/tf-checkout-styles.css +1 -1
- package/dist/utils/createCheckoutDataBodyWithDefaultHolder.d.ts +1 -0
- package/dist/validators/index.d.ts +4 -0
- package/package.json +2 -2
- package/src/api/auth.ts +49 -0
- package/src/api/index.ts +1 -1
- package/src/components/billing-info-container/index.tsx +228 -112
- package/src/components/billing-info-container/style.css +46 -2
- package/src/components/billing-info-container/utils.tsx +5 -1
- package/src/components/common/CustomField.tsx +1 -1
- package/src/components/common/PhoneNumberField.tsx +4 -2
- package/src/components/confirmationContainer/index.tsx +4 -0
- package/src/components/loginForm/index.tsx +19 -3
- package/src/components/loginModal/index.tsx +19 -3
- package/src/components/loginModal/style.css +6 -2
- package/src/components/preRegistration/PreRegistrationComplete.tsx +12 -2
- package/src/components/preRegistration/constants.tsx +6 -4
- package/src/components/preRegistration/index.tsx +144 -110
- package/src/components/preRegistration/style.css +3 -0
- package/src/components/preRegistration/utils.ts +9 -1
- package/src/components/ticketsContainer/index.tsx +79 -21
- package/src/components/timerWidget/style.css +2 -1
- package/src/types/api/common.d.ts +1 -0
- package/src/types/api/payment.d.ts +2 -0
- package/src/types/formFields.d.ts +1 -1
- package/src/utils/createCheckoutDataBodyWithDefaultHolder.ts +3 -1
- package/src/validators/index.ts +22 -1
|
@@ -26,6 +26,7 @@ export interface ILoginFormProps {
|
|
|
26
26
|
showForgotPasswordButton?: boolean;
|
|
27
27
|
showSignUpButton?: boolean;
|
|
28
28
|
showPoweredByImage?: boolean;
|
|
29
|
+
registerUrl?: string;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// interface IUserData {
|
|
@@ -75,6 +76,7 @@ export const LoginForm: FC<ILoginFormProps> = ({
|
|
|
75
76
|
showForgotPasswordButton = false,
|
|
76
77
|
showSignUpButton = false,
|
|
77
78
|
showPoweredByImage = false,
|
|
79
|
+
registerUrl = 'https://www.ticketfairy.com/register',
|
|
78
80
|
}) => {
|
|
79
81
|
const [error, setError] = useState('')
|
|
80
82
|
return (
|
|
@@ -171,9 +173,23 @@ export const LoginForm: FC<ILoginFormProps> = ({
|
|
|
171
173
|
)}
|
|
172
174
|
{showSignUpButton && (
|
|
173
175
|
<div className="forgot-password">
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
{onSignupButtonClick !== _identity ? (
|
|
177
|
+
<span
|
|
178
|
+
aria-hidden="true"
|
|
179
|
+
onClick={onSignupButtonClick}
|
|
180
|
+
style={{ cursor: 'pointer' }}
|
|
181
|
+
>
|
|
182
|
+
Sign up
|
|
183
|
+
</span>
|
|
184
|
+
) : (
|
|
185
|
+
<a
|
|
186
|
+
href={registerUrl}
|
|
187
|
+
target="_blank"
|
|
188
|
+
rel="noopener noreferrer"
|
|
189
|
+
>
|
|
190
|
+
Sign up
|
|
191
|
+
</a>
|
|
192
|
+
)}
|
|
177
193
|
</div>
|
|
178
194
|
)}
|
|
179
195
|
{showPoweredByImage ? <PoweredBy /> : null}
|
|
@@ -30,6 +30,7 @@ export interface Props {
|
|
|
30
30
|
showForgotPasswordButton?: boolean;
|
|
31
31
|
showSignUpButton?: boolean;
|
|
32
32
|
showPoweredByImage?: boolean;
|
|
33
|
+
registerUrl?: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const style: React.CSSProperties = {
|
|
@@ -57,6 +58,7 @@ export const LoginModal: FC<Props> = ({
|
|
|
57
58
|
showForgotPasswordButton = false,
|
|
58
59
|
showSignUpButton = false,
|
|
59
60
|
showPoweredByImage = false,
|
|
61
|
+
registerUrl = 'https://www.ticketfairy.com/register',
|
|
60
62
|
}) => {
|
|
61
63
|
const [error, setError] = useState('')
|
|
62
64
|
return (
|
|
@@ -162,9 +164,23 @@ export const LoginModal: FC<Props> = ({
|
|
|
162
164
|
)}
|
|
163
165
|
{showSignUpButton && (
|
|
164
166
|
<div className="forgot-password">
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
{onSignup !== _identity ? (
|
|
168
|
+
<span
|
|
169
|
+
aria-hidden="true"
|
|
170
|
+
onClick={onSignup}
|
|
171
|
+
style={{ cursor: 'pointer' }}
|
|
172
|
+
>
|
|
173
|
+
Sign up
|
|
174
|
+
</span>
|
|
175
|
+
) : (
|
|
176
|
+
<a
|
|
177
|
+
href={registerUrl}
|
|
178
|
+
target="_blank"
|
|
179
|
+
rel="noopener noreferrer"
|
|
180
|
+
>
|
|
181
|
+
Sign up
|
|
182
|
+
</a>
|
|
183
|
+
)}
|
|
168
184
|
</div>
|
|
169
185
|
)}
|
|
170
186
|
{showPoweredByImage ? <PoweredBy /> : null}
|
|
@@ -66,9 +66,13 @@
|
|
|
66
66
|
.forgot-password {
|
|
67
67
|
text-align: center;
|
|
68
68
|
}
|
|
69
|
-
.forgot-password span
|
|
69
|
+
.forgot-password span,
|
|
70
|
+
.forgot-password a {
|
|
70
71
|
cursor: pointer;
|
|
72
|
+
color: inherit;
|
|
73
|
+
text-decoration: none;
|
|
71
74
|
}
|
|
72
|
-
.forgot-password span:hover
|
|
75
|
+
.forgot-password span:hover,
|
|
76
|
+
.forgot-password a:hover {
|
|
73
77
|
text-decoration: underline;
|
|
74
78
|
}
|
|
@@ -6,6 +6,7 @@ import _get from 'lodash/get'
|
|
|
6
6
|
import _identity from 'lodash/identity'
|
|
7
7
|
import _isEmpaty from 'lodash/isEmpty'
|
|
8
8
|
import React, { FC, useEffect, useState } from 'react'
|
|
9
|
+
import "./style.css"
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
12
|
getPreRegistrationInfluencers,
|
|
@@ -25,6 +26,7 @@ const isWindowDefined = typeof window !== 'undefined'
|
|
|
25
26
|
export const PreRegistrationComplete: FC<
|
|
26
27
|
IPreRegistrationCompleteProps & {
|
|
27
28
|
onGetConfirmationDataError?: (error: AxiosError) => void;
|
|
29
|
+
skipInitialValidation?: boolean;
|
|
28
30
|
themeOptions?: ThemeOptions & {
|
|
29
31
|
input?: CSSProperties;
|
|
30
32
|
checkbox?: CSSProperties;
|
|
@@ -42,6 +44,7 @@ export const PreRegistrationComplete: FC<
|
|
|
42
44
|
onLoginSuccess = _identity,
|
|
43
45
|
logo,
|
|
44
46
|
themeOptions,
|
|
47
|
+
skipInitialValidation = false,
|
|
45
48
|
}) => {
|
|
46
49
|
const themeMui = createTheme(themeOptions)
|
|
47
50
|
|
|
@@ -65,6 +68,12 @@ export const PreRegistrationComplete: FC<
|
|
|
65
68
|
useCookieListener(X_TF_ECOMMERCE, value => setIsLoggedIn(Boolean(value)))
|
|
66
69
|
useEffect(() => {
|
|
67
70
|
const fetchConfirmationData = async () => {
|
|
71
|
+
// Skip validation if this is a freshly completed pre-registration
|
|
72
|
+
if (skipInitialValidation) {
|
|
73
|
+
onGetConfirmationDataSuccess()
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
try {
|
|
69
78
|
if (hash && isLoggedIn) {
|
|
70
79
|
const shareOptionsData = await getPreRegistrationShareOptions({
|
|
@@ -98,6 +107,7 @@ export const PreRegistrationComplete: FC<
|
|
|
98
107
|
onGetConfirmationDataError,
|
|
99
108
|
onGetConfirmationDataSuccess,
|
|
100
109
|
isLoggedIn,
|
|
110
|
+
skipInitialValidation,
|
|
101
111
|
])
|
|
102
112
|
|
|
103
113
|
const onClose = () => {
|
|
@@ -121,9 +131,9 @@ export const PreRegistrationComplete: FC<
|
|
|
121
131
|
{isLoggedIn && _isEmpaty(error) ? (
|
|
122
132
|
<div className={`${classNamePrefix}_pre_registration_complete_container`}>
|
|
123
133
|
<CopyMessageModal showCopyModal={showCopyModal} onClose={onClose} />
|
|
124
|
-
<
|
|
134
|
+
<h2 className={`${classNamePrefix}_pre_registration_complete_header preregistration_confirmation_header`}>
|
|
125
135
|
{pageHeader}
|
|
126
|
-
</
|
|
136
|
+
</h2>
|
|
127
137
|
<div className={`${classNamePrefix}_pre_registration_complete_message`}>
|
|
128
138
|
{pageMessage}
|
|
129
139
|
</div>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
+
import { emailValidator } from '../../validators'
|
|
4
|
+
|
|
3
5
|
export const getFormFieldsNotLoggedIn = (
|
|
4
6
|
clientName?: string | number
|
|
5
7
|
): IFormFieldsSection[] => [
|
|
@@ -38,14 +40,14 @@ export const getFormFieldsNotLoggedIn = (
|
|
|
38
40
|
label: 'Email',
|
|
39
41
|
type: 'email',
|
|
40
42
|
required: true,
|
|
41
|
-
onValidate:
|
|
43
|
+
onValidate: emailValidator,
|
|
42
44
|
},
|
|
43
45
|
{
|
|
44
46
|
name: 'confirmEmail',
|
|
45
47
|
label: 'Confirm Email',
|
|
46
48
|
type: 'email',
|
|
47
49
|
required: true,
|
|
48
|
-
onValidate:
|
|
50
|
+
onValidate: emailValidator,
|
|
49
51
|
},
|
|
50
52
|
],
|
|
51
53
|
},
|
|
@@ -120,14 +122,14 @@ export const getFormFieldsLoggedIn = (
|
|
|
120
122
|
label: 'Email',
|
|
121
123
|
type: 'email',
|
|
122
124
|
required: true,
|
|
123
|
-
onValidate:
|
|
125
|
+
onValidate: emailValidator,
|
|
124
126
|
},
|
|
125
127
|
{
|
|
126
128
|
name: 'confirmEmail',
|
|
127
129
|
label: 'Confirm Email',
|
|
128
130
|
type: 'email',
|
|
129
131
|
required: true,
|
|
130
|
-
onValidate:
|
|
132
|
+
onValidate: emailValidator,
|
|
131
133
|
},
|
|
132
134
|
],
|
|
133
135
|
},
|
|
@@ -18,6 +18,7 @@ import { ForgotPasswordModal, IForgotPasswordProps } from '../forgotPasswordModa
|
|
|
18
18
|
import { LoginModal, Props } from '../loginModal'
|
|
19
19
|
import { getFormFieldsLoggedIn, getFormFieldsNotLoggedIn } from './constants'
|
|
20
20
|
import { FieldsSection } from './FieldsSection'
|
|
21
|
+
import { PreRegistrationComplete } from './PreRegistrationComplete'
|
|
21
22
|
import { getFormInitialValues, updateFormFieldsAttributes } from './utils'
|
|
22
23
|
|
|
23
24
|
const X_TF_ECOMMERCE = 'X-TF-ECOMMERCE'
|
|
@@ -64,8 +65,19 @@ export const PreRegistration: FC<IPreRegistrationProps> = ({
|
|
|
64
65
|
const [isLoggedIn, setIsLoggedIn] = useState(Boolean(getCookieByName(X_TF_ECOMMERCE)))
|
|
65
66
|
const [confirmModalState, setConfirmModalState] = useState({ show: false, message: '' })
|
|
66
67
|
const [, setUserData] = useState({} as IProfileData)
|
|
68
|
+
const [isPreRegistrationComplete, setIsPreRegistrationComplete] = useState(false)
|
|
67
69
|
useCookieListener(X_TF_ECOMMERCE, value => setIsLoggedIn(Boolean(value)))
|
|
68
70
|
|
|
71
|
+
// Check if user already has a pre-registration when logged in
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!isLoggedIn || !isWindowDefined) return
|
|
74
|
+
|
|
75
|
+
const savedHash = window.localStorage.getItem(`pre-registration-hash-${eventId}`)
|
|
76
|
+
if (savedHash) {
|
|
77
|
+
setIsPreRegistrationComplete(true)
|
|
78
|
+
}
|
|
79
|
+
}, [isLoggedIn, eventId])
|
|
80
|
+
|
|
69
81
|
const themeMui = createTheme(themeOptions)
|
|
70
82
|
|
|
71
83
|
const formFieldsLoggedIn = getFormFieldsLoggedIn(CONFIGS.CLIENT_NAME)
|
|
@@ -99,7 +111,10 @@ export const PreRegistration: FC<IPreRegistrationProps> = ({
|
|
|
99
111
|
fetchCountries()
|
|
100
112
|
}, [])
|
|
101
113
|
|
|
102
|
-
const
|
|
114
|
+
const localStorageHash = isWindowDefined
|
|
115
|
+
? window.localStorage.getItem('pre-registration-hash') || ''
|
|
116
|
+
: ''
|
|
117
|
+
const hash = getQueryVariable('hash') || localStorageHash
|
|
103
118
|
const referrerId = getQueryVariable('referrer_id') || ''
|
|
104
119
|
return (
|
|
105
120
|
<ThemeProvider theme={themeMui}>
|
|
@@ -176,120 +191,139 @@ export const PreRegistration: FC<IPreRegistrationProps> = ({
|
|
|
176
191
|
displaySuccessMessage
|
|
177
192
|
/>
|
|
178
193
|
)}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
{isPreRegistrationComplete ? (
|
|
195
|
+
<PreRegistrationComplete
|
|
196
|
+
eventId={eventId}
|
|
197
|
+
logo={logo}
|
|
198
|
+
themeOptions={themeOptions}
|
|
199
|
+
onLoginSuccess={onLoginSuccess}
|
|
200
|
+
data={{} as IShareOptionsData}
|
|
201
|
+
classNamePrefix=""
|
|
202
|
+
onLinkCopied={_identity}
|
|
203
|
+
updateShareActionData={_identity}
|
|
204
|
+
hash={hash}
|
|
205
|
+
shareActionData={{} as ISubmitShareActionAttributes}
|
|
206
|
+
skipInitialValidation={true}
|
|
207
|
+
/>
|
|
208
|
+
) : (
|
|
209
|
+
<>
|
|
210
|
+
<h2>Pre-Registration</h2>
|
|
211
|
+
<Formik
|
|
212
|
+
initialValues={getFormInitialValues(formFields)}
|
|
213
|
+
enableReinitialize={true}
|
|
214
|
+
onSubmit={async (values: FormikValues) => {
|
|
215
|
+
try {
|
|
216
|
+
if (isLoggedIn) {
|
|
217
|
+
if (isPreregistrationStarted) {
|
|
218
|
+
const updatedValues = { ...values }
|
|
219
|
+
const holderAgeDate = new Date(values.holderAge)
|
|
220
|
+
updatedValues.dobDay = holderAgeDate.getDate()
|
|
221
|
+
updatedValues.dobMonth = holderAgeDate.getMonth() + 1
|
|
222
|
+
updatedValues.dobYear = holderAgeDate.getFullYear()
|
|
223
|
+
updatedValues.referrerId = referrerId
|
|
224
|
+
updatedValues.shareHash = hash
|
|
225
|
+
// remove date picker string value
|
|
226
|
+
delete updatedValues.holderAge
|
|
196
227
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
const confirmationData = await confirmPreRegistration(
|
|
229
|
+
eventId,
|
|
230
|
+
updatedValues as IConfirmPreRegistrationRequestData
|
|
231
|
+
)
|
|
232
|
+
if (isWindowDefined) {
|
|
233
|
+
window.localStorage.setItem(
|
|
234
|
+
`pre-registration-hash-${eventId}`,
|
|
235
|
+
_get(confirmationData, 'attributes.hash')
|
|
236
|
+
)
|
|
237
|
+
}
|
|
207
238
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
239
|
+
setIsPreRegistrationComplete(true)
|
|
240
|
+
onConfirmationSuccess(confirmationData)
|
|
241
|
+
} else {
|
|
242
|
+
setConfirmModalState({
|
|
243
|
+
show: true,
|
|
244
|
+
message: 'The preregistration has not started',
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
const bodyFormData = new FormData()
|
|
249
|
+
bodyFormData.append('first_name', values.firstName)
|
|
250
|
+
bodyFormData.append('last_name', values.lastName)
|
|
251
|
+
bodyFormData.append('email', values.email)
|
|
252
|
+
bodyFormData.append('confirm_email', values.confirmEmail)
|
|
253
|
+
bodyFormData.append('zip', values.zip)
|
|
254
|
+
bodyFormData.append('country', values.country)
|
|
255
|
+
bodyFormData.append('password', values.password)
|
|
256
|
+
bodyFormData.append('password_confirmation', values.confirmPassword)
|
|
257
|
+
bodyFormData.append('client_id', CONFIGS.CLIENT_ID)
|
|
258
|
+
bodyFormData.append('client_secret', CONFIGS.CLIENT_SECRET)
|
|
259
|
+
bodyFormData.append('register_for', 'prereg')
|
|
228
260
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
261
|
+
const res = await register(bodyFormData)
|
|
262
|
+
const profileRes = await getProfileData()
|
|
263
|
+
onLoginSuccess(res.data)
|
|
264
|
+
if (isWindowDefined) {
|
|
265
|
+
window.localStorage.setItem(
|
|
266
|
+
'user_data',
|
|
267
|
+
JSON.stringify(_get(profileRes, 'data'))
|
|
268
|
+
)
|
|
269
|
+
setUserData(_get(profileRes, 'data'))
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
const error = e as AxiosError
|
|
274
|
+
let errorMessage = error?.message || 'Error'
|
|
275
|
+
const emailErrors = _get(error, 'response.data.message.email') || ''
|
|
276
|
+
const errorDataMessage = _get(error, 'response.data.message')
|
|
245
277
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
278
|
+
if (typeof errorDataMessage === 'string') {
|
|
279
|
+
errorMessage = errorDataMessage
|
|
280
|
+
}
|
|
281
|
+
if (emailErrors?.length > 0) {
|
|
282
|
+
if (emailErrors[0] === 'The email is already used') {
|
|
283
|
+
setShowModalLogin(true)
|
|
284
|
+
setAlreadyHasUser(true)
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
setError(errorMessage)
|
|
288
|
+
}
|
|
257
289
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
290
|
+
onConfirmationError(error)
|
|
291
|
+
}
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{props => (
|
|
295
|
+
<Form>
|
|
296
|
+
<div className="login-modal-body">
|
|
297
|
+
<FieldsSection
|
|
298
|
+
formFields={formFields}
|
|
299
|
+
values={props.values}
|
|
300
|
+
setFieldValue={props.setFieldValue}
|
|
301
|
+
containerClass="pre-registration"
|
|
302
|
+
countries={countries}
|
|
303
|
+
themeOptions={themeOptions}
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
<div className="button-container">
|
|
307
|
+
<Button
|
|
308
|
+
type="submit"
|
|
309
|
+
variant="contained"
|
|
310
|
+
className="login-register-button"
|
|
311
|
+
disabled={props.isSubmitting}
|
|
312
|
+
>
|
|
313
|
+
{props.isSubmitting ? (
|
|
314
|
+
<CircularProgress size={26} />
|
|
315
|
+
) : isLoggedIn ? (
|
|
316
|
+
'Confirm Pre-Registration'
|
|
317
|
+
) : (
|
|
318
|
+
'Create Account'
|
|
319
|
+
)}
|
|
320
|
+
</Button>
|
|
321
|
+
</div>
|
|
322
|
+
</Form>
|
|
323
|
+
)}
|
|
324
|
+
</Formik>
|
|
325
|
+
</>
|
|
326
|
+
)}
|
|
293
327
|
</div>
|
|
294
328
|
</ThemeProvider>
|
|
295
329
|
)
|
|
@@ -6,7 +6,11 @@ import _isEmpty from 'lodash/isEmpty'
|
|
|
6
6
|
import _map from 'lodash/map'
|
|
7
7
|
import _split from 'lodash/split'
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
combineValidators,
|
|
11
|
+
passwordValidator,
|
|
12
|
+
requiredValidator,
|
|
13
|
+
} from '../../validators'
|
|
10
14
|
import { IShareButton } from '../confirmationContainer'
|
|
11
15
|
|
|
12
16
|
export const getValidateFunctions = ({
|
|
@@ -26,6 +30,10 @@ export const getValidateFunctions = ({
|
|
|
26
30
|
validationFunctions.push(element.onValidate)
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
if (element.name === 'password') {
|
|
34
|
+
validationFunctions.push(passwordValidator)
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
if (element.name === 'confirmEmail') {
|
|
30
38
|
const isSameEmail = (confirmEmail?: string) =>
|
|
31
39
|
values.email !== confirmEmail ? 'Please confirm your email address correctly' : null
|
|
@@ -48,8 +48,8 @@ import ConfirmModal from '../confirmModal'
|
|
|
48
48
|
import Countdown from '../countdown'
|
|
49
49
|
import { VerificationPendingModal } from '../idVerificationContainer/VerificationPendingModal'
|
|
50
50
|
import { LoginModal } from '../loginModal'
|
|
51
|
-
import WaitingList from '../waitingList'
|
|
52
51
|
import { PreRegistration } from '../preRegistration'
|
|
52
|
+
import WaitingList from '../waitingList'
|
|
53
53
|
import { AccessCodeSection } from './AccessCodeSection'
|
|
54
54
|
import { PromoCodeSection } from './PromoCodeSection'
|
|
55
55
|
import { ReferralLogic } from './ReferralLogic'
|
|
@@ -417,6 +417,41 @@ export const TicketsContainer = ({
|
|
|
417
417
|
const handleTicketSelect = (key: string, value: number | string, isTable = false) => {
|
|
418
418
|
localStorage.setItem('selectedTicketsQuantity', value.toString())
|
|
419
419
|
setSelectedTickets(prevState => {
|
|
420
|
+
// Allow multiple ticket types to be selected simultaneously when flag is enabled
|
|
421
|
+
if (event?.allowMultipleTicketTypePurchases === true) {
|
|
422
|
+
// Check if we're switching between tables and regular tickets
|
|
423
|
+
const hasExistingSelection = Object.keys(prevState).some(k => k !== 'isTable')
|
|
424
|
+
const switchingTicketType = hasExistingSelection && prevState.isTable !== isTable
|
|
425
|
+
|
|
426
|
+
// If switching from tables to regular tickets or vice versa, clear all selections
|
|
427
|
+
if (switchingTicketType && Number(value) > 0) {
|
|
428
|
+
return {
|
|
429
|
+
[key]: value,
|
|
430
|
+
isTable,
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// If value is 0, remove this ticket from selection
|
|
435
|
+
if (!value || Number(value) === 0) {
|
|
436
|
+
const newState = { ...prevState }
|
|
437
|
+
delete newState[key]
|
|
438
|
+
// If no ticket keys remain (only isTable left), return empty state
|
|
439
|
+
const ticketKeys = Object.keys(newState).filter(k => k !== 'isTable')
|
|
440
|
+
if (ticketKeys.length === 0) {
|
|
441
|
+
return { isTable: false } as ISelectedTickets
|
|
442
|
+
}
|
|
443
|
+
return { ...newState, isTable: prevState.isTable }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// If value > 0, add or update this ticket while keeping others of the same type selected
|
|
447
|
+
return {
|
|
448
|
+
...prevState,
|
|
449
|
+
[key]: value,
|
|
450
|
+
isTable,
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Default behavior: only one ticket type at a time
|
|
420
455
|
if (Object.keys(prevState)[0] !== key && !value) {
|
|
421
456
|
return prevState
|
|
422
457
|
}
|
|
@@ -441,32 +476,55 @@ export const TicketsContainer = ({
|
|
|
441
476
|
const timeSlotTickets = _flatten(_map(timeSlotGroups, slots => slots))
|
|
442
477
|
|
|
443
478
|
setHandleBookIsLoading(true)
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
479
|
+
|
|
480
|
+
// Unified flow: works for both single and multiple ticket types
|
|
481
|
+
const ticketsList = event?.isTimeSlotEvent ? timeSlotTickets : tickets
|
|
482
|
+
|
|
483
|
+
// Get all selected ticket IDs with quantity > 0 (excluding 'isTable' key)
|
|
484
|
+
const selectedTicketIds = Object.keys(selectedTickets).filter(
|
|
485
|
+
key => key !== 'isTable' && Number(selectedTickets[key]) > 0
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
// Build ticket_types object with all selected tickets (works for 1 or N tickets)
|
|
489
|
+
const ticketTypesData: any = {}
|
|
490
|
+
let totalProductCartQuantity = 0
|
|
491
|
+
let firstTicket: ITicket | null = null
|
|
492
|
+
|
|
493
|
+
selectedTicketIds.forEach(ticketId => {
|
|
494
|
+
const ticket = _find(ticketsList || [], item => String(item.id) === ticketId) as ITicket
|
|
495
|
+
if (ticket) {
|
|
496
|
+
if (!firstTicket) firstTicket = ticket
|
|
497
|
+
const optionName = _get(ticket, 'optionName')
|
|
498
|
+
const quantity = +selectedTickets[ticketId]
|
|
499
|
+
totalProductCartQuantity += quantity
|
|
500
|
+
|
|
501
|
+
ticketTypesData[ticketId] = {
|
|
502
|
+
product_options: {
|
|
503
|
+
[optionName]: ticketId,
|
|
504
|
+
ticket_price: ticket.price,
|
|
505
|
+
},
|
|
506
|
+
quantity,
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
if (!firstTicket) {
|
|
512
|
+
setHandleBookIsLoading(false)
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const firstOptionName = _get(firstTicket, 'optionName')
|
|
517
|
+
const firstTicketId = _get(firstTicket, 'id')
|
|
452
518
|
|
|
453
519
|
const data: ICartRequestData = {
|
|
454
520
|
attributes: {
|
|
455
521
|
alternative_view_id: null,
|
|
456
|
-
product_cart_quantity:
|
|
522
|
+
product_cart_quantity: totalProductCartQuantity,
|
|
457
523
|
product_options: {
|
|
458
|
-
[
|
|
524
|
+
[firstOptionName]: firstTicketId,
|
|
459
525
|
},
|
|
460
526
|
product_id: eventId,
|
|
461
|
-
ticket_types:
|
|
462
|
-
[ticketId]: {
|
|
463
|
-
product_options: {
|
|
464
|
-
[optionName]: ticketId,
|
|
465
|
-
ticket_price: ticket.price,
|
|
466
|
-
},
|
|
467
|
-
quantity: ticketQuantity,
|
|
468
|
-
},
|
|
469
|
-
},
|
|
527
|
+
ticket_types: ticketTypesData,
|
|
470
528
|
},
|
|
471
529
|
}
|
|
472
530
|
|
|
@@ -502,7 +560,7 @@ export const TicketsContainer = ({
|
|
|
502
560
|
: {}
|
|
503
561
|
|
|
504
562
|
const checkoutBody = createCheckoutDataBodyWithDefaultHolder(
|
|
505
|
-
|
|
563
|
+
totalProductCartQuantity,
|
|
506
564
|
userData
|
|
507
565
|
)
|
|
508
566
|
|