tf-checkout-react 2.0.0-beta.1 → 2.0.0-beta.2
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/index.esm.js +473 -263
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +473 -263
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/EmbeddedCheckout.tsx +6 -1
- package/src/auth/AuthModal.tsx +3 -3
- package/src/auth/ForgotPasswordForm.tsx +4 -6
- package/src/auth/LoginForm.tsx +2 -2
- package/src/auth/RegisterForm.tsx +2 -2
- package/src/auth/ResetPasswordForm.tsx +7 -7
- package/src/configure.ts +1 -5
- package/src/confirmer.ts +1 -5
- package/src/sections/AddOns.tsx +14 -7
- package/src/sections/BillingInfo.tsx +40 -33
- package/src/sections/Confirmation.tsx +8 -6
- package/src/sections/OrderSummary.tsx +7 -5
- package/src/sections/Payment.tsx +17 -13
- package/src/sections/Tickets.tsx +32 -15
- package/src/sections/billing/BillingAddressFields.tsx +19 -13
- package/src/sections/billing/TicketHoldersCard.tsx +103 -94
- package/src/ui/CheckoutProvider.tsx +5 -1
- package/src/ui/InstallmentPicker.tsx +4 -2
- package/src/ui/PaymentMethods.tsx +82 -76
- package/src/ui/TimerBanner.tsx +7 -3
package/package.json
CHANGED
package/src/EmbeddedCheckout.tsx
CHANGED
|
@@ -372,7 +372,12 @@ export const EmbeddedCheckout: FC<EmbeddedCheckoutProps> = ({
|
|
|
372
372
|
messages,
|
|
373
373
|
className,
|
|
374
374
|
}) => (
|
|
375
|
-
<CheckoutProvider
|
|
375
|
+
<CheckoutProvider
|
|
376
|
+
theme={theme}
|
|
377
|
+
locale={locale}
|
|
378
|
+
messages={messages}
|
|
379
|
+
className={className}
|
|
380
|
+
>
|
|
376
381
|
<Flow
|
|
377
382
|
key={eventId}
|
|
378
383
|
eventId={eventId}
|
package/src/auth/AuthModal.tsx
CHANGED
|
@@ -122,7 +122,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
|
|
122
122
|
if (event.target === event.currentTarget && !auth.isSubmitting) onClose()
|
|
123
123
|
}
|
|
124
124
|
const isPasswordReset = auth.view === 'forgot-password'
|
|
125
|
-
const title = isPasswordReset ? '
|
|
125
|
+
const title = isPasswordReset ? t('auth.passwordReset') : t('auth.title')
|
|
126
126
|
|
|
127
127
|
return (
|
|
128
128
|
<div className={cx('tf-checkout-auth', className)} onMouseDown={closeFromBackdrop}>
|
|
@@ -160,7 +160,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
|
|
160
160
|
)}
|
|
161
161
|
onClick={() => auth.showLogin(auth.suggestedEmail)}
|
|
162
162
|
>
|
|
163
|
-
|
|
163
|
+
{t('auth.logIn')}
|
|
164
164
|
</button>
|
|
165
165
|
<button
|
|
166
166
|
type="button"
|
|
@@ -172,7 +172,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
|
|
172
172
|
)}
|
|
173
173
|
onClick={() => auth.showRegister(auth.suggestedEmail)}
|
|
174
174
|
>
|
|
175
|
-
|
|
175
|
+
{t('auth.signUp')}
|
|
176
176
|
</button>
|
|
177
177
|
</div>
|
|
178
178
|
)}
|
|
@@ -47,7 +47,7 @@ export const ForgotPasswordForm: FC<ForgotPasswordFormProps> = ({
|
|
|
47
47
|
{successMessage}
|
|
48
48
|
</div>
|
|
49
49
|
<Button type="button" block onClick={() => onBackToLogin(email)}>
|
|
50
|
-
|
|
50
|
+
{t('auth.backToLogin')}
|
|
51
51
|
</Button>
|
|
52
52
|
</div>
|
|
53
53
|
)
|
|
@@ -55,9 +55,7 @@ export const ForgotPasswordForm: FC<ForgotPasswordFormProps> = ({
|
|
|
55
55
|
|
|
56
56
|
return (
|
|
57
57
|
<form className="tf-checkout-auth__form" onSubmit={submit} noValidate>
|
|
58
|
-
<p className="tf-checkout-auth__intro">
|
|
59
|
-
Enter your email and we’ll send you instructions to reset your password.
|
|
60
|
-
</p>
|
|
58
|
+
<p className="tf-checkout-auth__intro">{t('auth.forgotIntro')}</p>
|
|
61
59
|
{error && (
|
|
62
60
|
<div className="tf-checkout-auth__alert" role="alert">
|
|
63
61
|
{error}
|
|
@@ -79,14 +77,14 @@ export const ForgotPasswordForm: FC<ForgotPasswordFormProps> = ({
|
|
|
79
77
|
/>
|
|
80
78
|
</Field>
|
|
81
79
|
<Button type="submit" block loading={loading}>
|
|
82
|
-
|
|
80
|
+
{t('auth.sendResetInstructions')}
|
|
83
81
|
</Button>
|
|
84
82
|
<button
|
|
85
83
|
type="button"
|
|
86
84
|
className="tf-checkout-auth__text-button tf-checkout-auth__back"
|
|
87
85
|
onClick={() => onBackToLogin(email)}
|
|
88
86
|
>
|
|
89
|
-
|
|
87
|
+
{t('auth.backToLogin')}
|
|
90
88
|
</button>
|
|
91
89
|
</form>
|
|
92
90
|
)
|
package/src/auth/LoginForm.tsx
CHANGED
|
@@ -88,7 +88,7 @@ export const LoginForm: FC<LoginFormProps> = ({
|
|
|
88
88
|
/>
|
|
89
89
|
</Field>
|
|
90
90
|
<Button type="submit" block loading={loading}>
|
|
91
|
-
|
|
91
|
+
{t('auth.logIn')}
|
|
92
92
|
</Button>
|
|
93
93
|
{allowPasswordReset && onForgotPassword && (
|
|
94
94
|
<button
|
|
@@ -96,7 +96,7 @@ export const LoginForm: FC<LoginFormProps> = ({
|
|
|
96
96
|
className="tf-checkout-auth__text-button tf-checkout-auth__forgot"
|
|
97
97
|
onClick={() => onForgotPassword(email)}
|
|
98
98
|
>
|
|
99
|
-
|
|
99
|
+
{t('auth.forgotPassword')}
|
|
100
100
|
</button>
|
|
101
101
|
)}
|
|
102
102
|
</form>
|
|
@@ -236,7 +236,7 @@ export const RegisterForm: FC<RegisterFormProps> = ({
|
|
|
236
236
|
onChange={(event) => setField('stateId', event.target.value)}
|
|
237
237
|
>
|
|
238
238
|
<option value="">
|
|
239
|
-
{states.length > 0 ? '
|
|
239
|
+
{states.length > 0 ? t('auth.selectState') : t('auth.notRequired')}
|
|
240
240
|
</option>
|
|
241
241
|
{states.map((state) => (
|
|
242
242
|
<option key={state.id} value={state.id}>
|
|
@@ -326,7 +326,7 @@ export const RegisterForm: FC<RegisterFormProps> = ({
|
|
|
326
326
|
</Field>
|
|
327
327
|
</FieldRow>
|
|
328
328
|
<Button type="submit" block loading={loading}>
|
|
329
|
-
|
|
329
|
+
{t('auth.createAccount')}
|
|
330
330
|
</Button>
|
|
331
331
|
</form>
|
|
332
332
|
)
|
|
@@ -28,14 +28,14 @@ export const ResetPasswordForm: FC<ResetPasswordFormProps> = ({
|
|
|
28
28
|
const validation = useMemo(() => {
|
|
29
29
|
if (!submitted) return {}
|
|
30
30
|
return {
|
|
31
|
-
token: token ? '' : '
|
|
31
|
+
token: token ? '' : t('auth.missingToken'),
|
|
32
32
|
password:
|
|
33
33
|
password.length < 8
|
|
34
|
-
? '
|
|
34
|
+
? t('auth.passwordTooShort')
|
|
35
35
|
: !/[@$!%*#?&]/.test(password)
|
|
36
|
-
? '
|
|
36
|
+
? t('auth.passwordNeedsSpecial')
|
|
37
37
|
: '',
|
|
38
|
-
confirmation: password !== confirmation ? '
|
|
38
|
+
confirmation: password !== confirmation ? t('auth.passwordsMustMatch') : '',
|
|
39
39
|
}
|
|
40
40
|
}, [confirmation, password, submitted, token])
|
|
41
41
|
|
|
@@ -58,11 +58,11 @@ export const ResetPasswordForm: FC<ResetPasswordFormProps> = ({
|
|
|
58
58
|
return (
|
|
59
59
|
<div className="tf-checkout-auth__form">
|
|
60
60
|
<div className="tf-checkout-auth__success" role="status">
|
|
61
|
-
|
|
61
|
+
{t('auth.passwordChanged')}
|
|
62
62
|
</div>
|
|
63
63
|
{onBackToLogin && (
|
|
64
64
|
<Button type="button" block onClick={onBackToLogin}>
|
|
65
|
-
|
|
65
|
+
{t('auth.logIn')}
|
|
66
66
|
</Button>
|
|
67
67
|
)}
|
|
68
68
|
</div>
|
|
@@ -105,7 +105,7 @@ export const ResetPasswordForm: FC<ResetPasswordFormProps> = ({
|
|
|
105
105
|
/>
|
|
106
106
|
</Field>
|
|
107
107
|
<Button type="submit" block loading={reset.isPending} disabled={!token}>
|
|
108
|
-
|
|
108
|
+
{t('common.submit')}
|
|
109
109
|
</Button>
|
|
110
110
|
</form>
|
|
111
111
|
)
|
package/src/configure.ts
CHANGED
|
@@ -5,11 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* configureCheckout({ env: 'PROD', clientId, clientSecret })
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
configure,
|
|
10
|
-
type StorageAdapter,
|
|
11
|
-
type TtfConfig,
|
|
12
|
-
} from 'tf-checkout-shared'
|
|
8
|
+
import { configure, type StorageAdapter, type TtfConfig } from 'tf-checkout-shared'
|
|
13
9
|
|
|
14
10
|
const isBrowser =
|
|
15
11
|
typeof window !== 'undefined' && typeof window.localStorage !== 'undefined'
|
package/src/confirmer.ts
CHANGED
|
@@ -17,11 +17,7 @@ import {
|
|
|
17
17
|
type StripeCardElement,
|
|
18
18
|
type StripeElements,
|
|
19
19
|
} from '@stripe/stripe-js'
|
|
20
|
-
import type {
|
|
21
|
-
CheckoutBilling,
|
|
22
|
-
CheckoutPorts,
|
|
23
|
-
PaymentConfirmer,
|
|
24
|
-
} from 'tf-checkout-shared'
|
|
20
|
+
import type { CheckoutBilling, CheckoutPorts, PaymentConfirmer } from 'tf-checkout-shared'
|
|
25
21
|
|
|
26
22
|
import type { WebXenditTokenizer } from './xendit'
|
|
27
23
|
|
package/src/sections/AddOns.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { Button } from '../ui/Button'
|
|
22
22
|
import { Card } from '../ui/Card'
|
|
23
23
|
import { useCheckoutContext } from '../ui/CheckoutProvider'
|
|
24
|
+
import { useT } from '../ui/I18nProvider'
|
|
24
25
|
import { CustomFieldGroupFields } from '../ui/CustomFields'
|
|
25
26
|
import { Loader } from '../ui/Loader'
|
|
26
27
|
import { PriceLine } from '../ui/PriceLine'
|
|
@@ -45,12 +46,16 @@ export const AddOns: FC<AddOnsProps> = ({
|
|
|
45
46
|
eventId,
|
|
46
47
|
onContinue,
|
|
47
48
|
onSkip,
|
|
48
|
-
buttonLabel
|
|
49
|
-
eyebrow
|
|
50
|
-
heading
|
|
49
|
+
buttonLabel,
|
|
50
|
+
eyebrow,
|
|
51
|
+
heading,
|
|
51
52
|
className,
|
|
52
53
|
}) => {
|
|
54
|
+
const t = useT()
|
|
53
55
|
const checkout = useCheckoutContext()
|
|
56
|
+
const buttonText = buttonLabel ?? t('addons.confirmSelection')
|
|
57
|
+
const eyebrowText = eyebrow ?? t('addons.eyebrow')
|
|
58
|
+
const headingText = heading ?? t('addons.heading')
|
|
54
59
|
// No onContinue ⇒ combined form (inside <EmbeddedCheckout/>): apply the selection live
|
|
55
60
|
// (no "confirm" click) — the old checkout applied add-ons at checkout too.
|
|
56
61
|
const combined = !onContinue
|
|
@@ -97,8 +102,8 @@ export const AddOns: FC<AddOnsProps> = ({
|
|
|
97
102
|
const hasRequired = (checkout.requiredAddOns ?? []).some((a) => a.required)
|
|
98
103
|
const head = (
|
|
99
104
|
<div className="tf-checkout-addons__head">
|
|
100
|
-
{
|
|
101
|
-
<span className="tf-checkout-addons__heading">{
|
|
105
|
+
{eyebrowText && <span className="tf-checkout-addons__eyebrow">{eyebrowText}</span>}
|
|
106
|
+
<span className="tf-checkout-addons__heading">{headingText}</span>
|
|
102
107
|
{onSkip && !hasRequired && (
|
|
103
108
|
<button type="button" className="tf-checkout-addons__skip" onClick={onSkip}>
|
|
104
109
|
Skip
|
|
@@ -190,7 +195,9 @@ export const AddOns: FC<AddOnsProps> = ({
|
|
|
190
195
|
)}
|
|
191
196
|
</div>
|
|
192
197
|
{soldOut(a) ? (
|
|
193
|
-
<span className="tf-checkout-tickets__soldout">
|
|
198
|
+
<span className="tf-checkout-tickets__soldout">
|
|
199
|
+
{t('tickets.soldOut')}
|
|
200
|
+
</span>
|
|
194
201
|
) : (
|
|
195
202
|
<QuantitySelect
|
|
196
203
|
value={n}
|
|
@@ -227,7 +234,7 @@ export const AddOns: FC<AddOnsProps> = ({
|
|
|
227
234
|
loading={checkout.isBusy}
|
|
228
235
|
disabled={checkout.isBusy || selectedCount === 0}
|
|
229
236
|
>
|
|
230
|
-
{checkout.isBusy ? '
|
|
237
|
+
{checkout.isBusy ? t('addons.updating') : buttonText}
|
|
231
238
|
</Button>
|
|
232
239
|
)}
|
|
233
240
|
{missing.length > 0 && (
|
|
@@ -34,6 +34,7 @@ import { PhoneInput, type CountryIso2 } from 'react-international-phone'
|
|
|
34
34
|
import { Button } from '../ui/Button'
|
|
35
35
|
import { Card, SectionHeader } from '../ui/Card'
|
|
36
36
|
import { useCheckoutContext, useCheckoutValidation } from '../ui/CheckoutProvider'
|
|
37
|
+
import { useT } from '../ui/I18nProvider'
|
|
37
38
|
import { CustomFieldInput } from '../ui/CustomFields'
|
|
38
39
|
import { Checkbox, Field, FieldRow, Input } from '../ui/Field'
|
|
39
40
|
import { BillingAddressFields } from './billing/BillingAddressFields'
|
|
@@ -85,22 +86,26 @@ export interface CheckoutPrivacyPolicy {
|
|
|
85
86
|
export const BillingInfo: FC<BillingInfoProps> = ({
|
|
86
87
|
eventId,
|
|
87
88
|
onContinue,
|
|
88
|
-
heading
|
|
89
|
-
buttonLabel
|
|
89
|
+
heading,
|
|
90
|
+
buttonLabel,
|
|
90
91
|
onLogin,
|
|
91
92
|
skipWhenProfileComplete = false,
|
|
92
93
|
requireEmailConfirmation = false,
|
|
93
94
|
collectTicketHolders = false,
|
|
94
95
|
collectHolderContact = false,
|
|
95
|
-
ttfOptInLabel
|
|
96
|
+
ttfOptInLabel,
|
|
96
97
|
brandOptInLabel,
|
|
97
98
|
privacyPolicy,
|
|
98
99
|
showPrivacyError = false,
|
|
99
100
|
step,
|
|
100
101
|
className,
|
|
101
102
|
}) => {
|
|
103
|
+
const t = useT()
|
|
102
104
|
const checkout = useCheckoutContext()
|
|
103
105
|
const validation = useCheckoutValidation()
|
|
106
|
+
const headingText = heading ?? t('billing.heading')
|
|
107
|
+
const buttonText = buttonLabel ?? t('billing.continueToPayment')
|
|
108
|
+
const ttfOptInText = ttfOptInLabel ?? t('billing.ttfOptIn')
|
|
104
109
|
const profile = hooks.useProfile({ retry: false })
|
|
105
110
|
const event = hooks.useEvent(eventId)
|
|
106
111
|
// No onContinue ⇒ this is one combined form (inside <EmbeddedCheckout/>): mirror the
|
|
@@ -417,7 +422,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
417
422
|
const complete = missingDetails.length === 0
|
|
418
423
|
const showValidation = validation.attempt > 0
|
|
419
424
|
const missingSet = new Set(missingDetails)
|
|
420
|
-
const fieldError = (key: string, message = '
|
|
425
|
+
const fieldError = (key: string, message = t('billing.fieldRequired')) =>
|
|
421
426
|
showValidation && missingSet.has(key) ? message : undefined
|
|
422
427
|
const dobError = showValidation
|
|
423
428
|
? missingDetails.find(
|
|
@@ -433,7 +438,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
433
438
|
const commit = () => {
|
|
434
439
|
checkout.setBilling(committedBilling)
|
|
435
440
|
checkout.setHolders(committedHolders)
|
|
436
|
-
if (
|
|
441
|
+
if (ttfOptInText || brandOptInLabel) checkout.setConsent(consent)
|
|
437
442
|
}
|
|
438
443
|
|
|
439
444
|
// Combined mode: keep the engine in sync as the form is filled, so <Payment/>
|
|
@@ -485,7 +490,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
485
490
|
return (
|
|
486
491
|
<>
|
|
487
492
|
<Card className={className}>
|
|
488
|
-
<SectionHeader step={step} title={
|
|
493
|
+
<SectionHeader step={step} title={headingText} />
|
|
489
494
|
|
|
490
495
|
{onLogin && !checkout.isLoggedIn && (
|
|
491
496
|
<div className="tf-checkout-billing__login">
|
|
@@ -506,7 +511,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
506
511
|
invalid={Boolean(fieldError('First name'))}
|
|
507
512
|
>
|
|
508
513
|
<Input
|
|
509
|
-
placeholder=
|
|
514
|
+
placeholder={t('billing.firstNamePlaceholder')}
|
|
510
515
|
value={billing.firstName}
|
|
511
516
|
required
|
|
512
517
|
invalid={Boolean(fieldError('First name'))}
|
|
@@ -519,7 +524,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
519
524
|
invalid={Boolean(fieldError('Last name'))}
|
|
520
525
|
>
|
|
521
526
|
<Input
|
|
522
|
-
placeholder=
|
|
527
|
+
placeholder={t('billing.lastNamePlaceholder')}
|
|
523
528
|
value={billing.lastName}
|
|
524
529
|
required
|
|
525
530
|
invalid={Boolean(fieldError('Last name'))}
|
|
@@ -536,7 +541,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
536
541
|
<Input
|
|
537
542
|
type="email"
|
|
538
543
|
autoComplete="email"
|
|
539
|
-
placeholder=
|
|
544
|
+
placeholder={t('billing.emailPlaceholder')}
|
|
540
545
|
value={billing.email}
|
|
541
546
|
required
|
|
542
547
|
invalid={Boolean(fieldError('Valid email address'))}
|
|
@@ -548,14 +553,14 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
548
553
|
<Field
|
|
549
554
|
error={fieldError(
|
|
550
555
|
'Matching email confirmation',
|
|
551
|
-
'
|
|
556
|
+
t('billing.emailMustMatch')
|
|
552
557
|
)}
|
|
553
558
|
invalid={Boolean(fieldError('Matching email confirmation'))}
|
|
554
559
|
>
|
|
555
560
|
<Input
|
|
556
561
|
type="email"
|
|
557
562
|
autoComplete="email"
|
|
558
|
-
placeholder=
|
|
563
|
+
placeholder={t('billing.confirmEmailPlaceholder')}
|
|
559
564
|
value={billing.confirmEmail ?? ''}
|
|
560
565
|
required
|
|
561
566
|
invalid={Boolean(fieldError('Matching email confirmation'))}
|
|
@@ -570,21 +575,21 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
570
575
|
billing.confirmEmail &&
|
|
571
576
|
billing.confirmEmail.trim().toLowerCase() !==
|
|
572
577
|
billing.email.trim().toLowerCase() && (
|
|
573
|
-
<p className="tf-checkout-error-text">
|
|
578
|
+
<p className="tf-checkout-error-text">{t('billing.emailMismatch')}</p>
|
|
574
579
|
)}
|
|
575
580
|
{emailTaken && (
|
|
576
581
|
<p className="tf-checkout-note tf-checkout-billing__email-taken">
|
|
577
|
-
|
|
582
|
+
{t('billing.emailRegistered')}{' '}
|
|
578
583
|
{onLogin ? (
|
|
579
584
|
<button
|
|
580
585
|
type="button"
|
|
581
586
|
className="tf-checkout-billing__login-link"
|
|
582
587
|
onClick={() => onLogin(billing.email)}
|
|
583
588
|
>
|
|
584
|
-
|
|
589
|
+
{t('billing.loginAutofill')}
|
|
585
590
|
</button>
|
|
586
591
|
) : (
|
|
587
|
-
'
|
|
592
|
+
t('billing.loginAutofillPlain')
|
|
588
593
|
)}
|
|
589
594
|
</p>
|
|
590
595
|
)}
|
|
@@ -598,18 +603,18 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
598
603
|
countries={countries}
|
|
599
604
|
error={fieldError(
|
|
600
605
|
f.kind === 'nationality'
|
|
601
|
-
? '
|
|
606
|
+
? t('billing.nationality')
|
|
602
607
|
: f.kind === 'gender'
|
|
603
|
-
? '
|
|
604
|
-
: '
|
|
608
|
+
? t('billing.gender')
|
|
609
|
+
: t('billing.instagramHandle')
|
|
605
610
|
)}
|
|
606
611
|
/>
|
|
607
612
|
))}
|
|
608
613
|
|
|
609
614
|
{gates.showPhone && (
|
|
610
615
|
<Field
|
|
611
|
-
label={
|
|
612
|
-
error={fieldError('Valid phone number', '
|
|
616
|
+
label={gates.phoneRequired ? t('billing.phone') : t('billing.phoneOptional')}
|
|
617
|
+
error={fieldError('Valid phone number', t('billing.phoneInvalid'))}
|
|
613
618
|
invalid={Boolean(fieldError('Valid phone number'))}
|
|
614
619
|
>
|
|
615
620
|
<PhoneInput
|
|
@@ -628,10 +633,10 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
628
633
|
disableDialCodePrefill
|
|
629
634
|
value={billing.phone ?? ''}
|
|
630
635
|
required={gates.phoneRequired}
|
|
631
|
-
placeholder=
|
|
636
|
+
placeholder={t('billing.phonePlaceholder')}
|
|
632
637
|
inputProps={{
|
|
633
638
|
autoComplete: 'tel',
|
|
634
|
-
'aria-label': '
|
|
639
|
+
'aria-label': t('billing.phoneAria'),
|
|
635
640
|
'aria-invalid': Boolean(fieldError('Valid phone number')),
|
|
636
641
|
}}
|
|
637
642
|
onChange={(phone) => setBilling((current) => ({ ...current, phone }))}
|
|
@@ -641,14 +646,16 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
641
646
|
|
|
642
647
|
{gates.showDob && (
|
|
643
648
|
<Field
|
|
644
|
-
label={
|
|
645
|
-
gates.minimumAge
|
|
646
|
-
|
|
649
|
+
label={
|
|
650
|
+
gates.minimumAge
|
|
651
|
+
? t('billing.dobWithAge', { age: gates.minimumAge })
|
|
652
|
+
: t('billing.dob')
|
|
653
|
+
}
|
|
647
654
|
error={
|
|
648
655
|
dobError === 'Date of birth'
|
|
649
|
-
? '
|
|
656
|
+
? t('billing.dobRequired')
|
|
650
657
|
: dobError
|
|
651
|
-
?
|
|
658
|
+
? t('billing.ageMinimum', { age: gates.minimumAge ?? '' })
|
|
652
659
|
: undefined
|
|
653
660
|
}
|
|
654
661
|
invalid={Boolean(dobError)}
|
|
@@ -720,24 +727,24 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
720
727
|
className="tf-checkout-checkbox--consent"
|
|
721
728
|
label={
|
|
722
729
|
<>
|
|
723
|
-
|
|
730
|
+
{t('billing.privacyAgree', { brand: privacyPolicy.brandName })}{' '}
|
|
724
731
|
<a href={privacyPolicy.url} target="_blank" rel="noreferrer">
|
|
725
|
-
{privacyPolicy.linkLabel || '
|
|
732
|
+
{privacyPolicy.linkLabel || t('billing.privacyLink')}
|
|
726
733
|
</a>
|
|
727
734
|
{' *'}
|
|
728
735
|
</>
|
|
729
736
|
}
|
|
730
737
|
/>
|
|
731
738
|
{(showPrivacyError || showValidation) && !consent.ttfOptIn && (
|
|
732
|
-
<div className="tf-checkout-consent__error">
|
|
739
|
+
<div className="tf-checkout-consent__error">{t('common.required')}</div>
|
|
733
740
|
)}
|
|
734
741
|
</div>
|
|
735
|
-
) :
|
|
742
|
+
) : ttfOptInText ? (
|
|
736
743
|
<Checkbox
|
|
737
744
|
checked={consent.ttfOptIn}
|
|
738
745
|
onChange={(e) => setConsent((c) => ({ ...c, ttfOptIn: e.target.checked }))}
|
|
739
746
|
className="tf-checkout-checkbox--consent"
|
|
740
|
-
label={
|
|
747
|
+
label={ttfOptInText}
|
|
741
748
|
/>
|
|
742
749
|
) : null}
|
|
743
750
|
{!combined && (
|
|
@@ -753,7 +760,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
|
|
|
753
760
|
onContinue?.()
|
|
754
761
|
}}
|
|
755
762
|
>
|
|
756
|
-
{
|
|
763
|
+
{buttonText}
|
|
757
764
|
</Button>
|
|
758
765
|
</>
|
|
759
766
|
)}
|
|
@@ -14,6 +14,7 @@ import React, { useState, type FC, type ReactNode } from 'react'
|
|
|
14
14
|
import { Button } from '../ui/Button'
|
|
15
15
|
import { Card } from '../ui/Card'
|
|
16
16
|
import { useCheckoutContext } from '../ui/CheckoutProvider'
|
|
17
|
+
import { useT } from '../ui/I18nProvider'
|
|
17
18
|
import { cx } from '../ui/cx'
|
|
18
19
|
|
|
19
20
|
export interface ConfirmationProps {
|
|
@@ -35,6 +36,7 @@ export const Confirmation: FC<ConfirmationProps> = ({
|
|
|
35
36
|
children,
|
|
36
37
|
className,
|
|
37
38
|
}) => {
|
|
39
|
+
const t = useT()
|
|
38
40
|
const checkout = useCheckoutContext()
|
|
39
41
|
const order = checkout.order
|
|
40
42
|
const [copied, setCopied] = useState(false)
|
|
@@ -78,14 +80,14 @@ export const Confirmation: FC<ConfirmationProps> = ({
|
|
|
78
80
|
<Card className={cx('tf-checkout-confirmation', className)}>
|
|
79
81
|
<span className="tf-checkout-confirmation__check">{pending ? '⏳' : '✓'}</span>
|
|
80
82
|
<strong className="tf-checkout-confirmation__title">
|
|
81
|
-
{pending ? '
|
|
83
|
+
{pending ? t('confirmation.pending') : t('confirmation.confirmed')}
|
|
82
84
|
</strong>
|
|
83
85
|
<p className="tf-checkout-note">
|
|
84
86
|
{pending
|
|
85
|
-
? '
|
|
87
|
+
? t('confirmation.pendingBody')
|
|
86
88
|
: order.alreadyPaid
|
|
87
|
-
? '
|
|
88
|
-
: '
|
|
89
|
+
? t('confirmation.alreadyPaid')
|
|
90
|
+
: t('confirmation.available')}
|
|
89
91
|
</p>
|
|
90
92
|
{order.orderHash && (
|
|
91
93
|
<p className="tf-checkout-confirmation__ref">Reference: {order.orderHash}</p>
|
|
@@ -116,14 +118,14 @@ export const Confirmation: FC<ConfirmationProps> = ({
|
|
|
116
118
|
{/* Referral / share — invite friends for cheaper (or free) tickets. */}
|
|
117
119
|
{!pending && url && (
|
|
118
120
|
<div className="tf-checkout-confirmation__share">
|
|
119
|
-
<strong>
|
|
121
|
+
<strong>{t('referral.cheaperOrFree')}</strong>
|
|
120
122
|
<p className="tf-checkout-note">
|
|
121
123
|
Invite friends with your link. When they buy, you get rewarded.
|
|
122
124
|
</p>
|
|
123
125
|
<div className="tf-checkout-confirmation__share-link">
|
|
124
126
|
<input className="tf-checkout-input" readOnly value={url} />
|
|
125
127
|
<Button variant="secondary" onClick={copy}>
|
|
126
|
-
{copied ? '
|
|
128
|
+
{copied ? t('referral.copied') : t('referral.copyLink')}
|
|
127
129
|
</Button>
|
|
128
130
|
</div>
|
|
129
131
|
<div className="tf-checkout-confirmation__socials">
|
|
@@ -4,6 +4,7 @@ import { hooks } from 'tf-checkout-shared'
|
|
|
4
4
|
|
|
5
5
|
import { Card } from '../ui/Card'
|
|
6
6
|
import { useCheckoutContext } from '../ui/CheckoutProvider'
|
|
7
|
+
import { useT } from '../ui/I18nProvider'
|
|
7
8
|
|
|
8
9
|
export interface OrderSummaryProps {
|
|
9
10
|
eventId?: string
|
|
@@ -12,6 +13,7 @@ export interface OrderSummaryProps {
|
|
|
12
13
|
|
|
13
14
|
/** Storefront-compatible order summary shown after the checkout controls. */
|
|
14
15
|
export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
16
|
+
const t = useT()
|
|
15
17
|
const checkout = useCheckoutContext()
|
|
16
18
|
const event = hooks.useEvent(eventId)
|
|
17
19
|
const breakdown = checkout.breakdown
|
|
@@ -23,13 +25,13 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
23
25
|
<Card className={['tf-checkout-order-summary', className].filter(Boolean).join(' ')}>
|
|
24
26
|
{event.data?.name && (
|
|
25
27
|
<div className="tf-checkout-order-summary__group">
|
|
26
|
-
<div className="tf-checkout-order-summary__label">
|
|
28
|
+
<div className="tf-checkout-order-summary__label">{t('common.event')}</div>
|
|
27
29
|
<div className="tf-checkout-order-summary__event">{event.data.name}</div>
|
|
28
30
|
</div>
|
|
29
31
|
)}
|
|
30
32
|
|
|
31
33
|
<div className="tf-checkout-order-summary__group">
|
|
32
|
-
<div className="tf-checkout-order-summary__label">
|
|
34
|
+
<div className="tf-checkout-order-summary__label">{t('tickets.yourTickets')}</div>
|
|
33
35
|
{breakdown.tickets.map((ticket, index) => {
|
|
34
36
|
const detail = detailed?.tickets[index]
|
|
35
37
|
return (
|
|
@@ -39,7 +41,7 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
39
41
|
>
|
|
40
42
|
<div className="tf-checkout-order-summary__ticket">
|
|
41
43
|
{ticket.quantity} x {ticket.name} - {detail?.unit_price ?? ticket.unit}{' '}
|
|
42
|
-
each
|
|
44
|
+
{t('common.each')}
|
|
43
45
|
</div>
|
|
44
46
|
{detail?.line_total && detail.line_total !== detail.unit_price && (
|
|
45
47
|
<div className="tf-checkout-order-summary__fees">
|
|
@@ -63,7 +65,7 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
63
65
|
>
|
|
64
66
|
<div className="tf-checkout-order-summary__ticket">
|
|
65
67
|
{addOn.quantity} x {addOn.name}
|
|
66
|
-
{addOn.show_price ? ` - ${addOn.unit_price} each` : ''}
|
|
68
|
+
{addOn.show_price ? ` - ${addOn.unit_price} ${t('common.each')}` : ''}
|
|
67
69
|
</div>
|
|
68
70
|
</div>
|
|
69
71
|
))}
|
|
@@ -71,7 +73,7 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
71
73
|
)}
|
|
72
74
|
|
|
73
75
|
<div className="tf-checkout-order-summary__total">
|
|
74
|
-
<div className="tf-checkout-order-summary__label">
|
|
76
|
+
<div className="tf-checkout-order-summary__label">{t('common.total')}</div>
|
|
75
77
|
<div className="tf-checkout-order-summary__amount">
|
|
76
78
|
{detailed?.total ??
|
|
77
79
|
`${breakdown.currencySymbol || `${breakdown.currencyCode} `}${Number(
|