tf-checkout-react-native 2.0.0-beta.0 → 2.0.0-beta.11

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.
Files changed (71) hide show
  1. package/README.md +115 -24
  2. package/dist/index.d.mts +8195 -282
  3. package/dist/index.d.ts +8195 -282
  4. package/dist/index.js +32346 -7001
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +32586 -7128
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +23 -11
  9. package/scripts/build-seat-map.mjs +31 -0
  10. package/scripts/seat-map-entry.jsx +22 -0
  11. package/scripts/verify-discussion-support.cjs +131 -0
  12. package/src/CardInput.tsx +64 -3
  13. package/src/EmbeddedCheckout.tsx +140 -38
  14. package/src/auth/AuthModal.tsx +186 -9
  15. package/src/auth/EmailCodeForm.tsx +91 -0
  16. package/src/auth/RegisterForm.tsx +91 -5
  17. package/src/auth/TwoFactorForm.tsx +74 -0
  18. package/src/auth/index.ts +4 -0
  19. package/src/configure.ts +19 -14
  20. package/src/confirmer.ts +112 -20
  21. package/src/generated/seatMapWidget.ts +3 -0
  22. package/src/index.tsx +1 -1
  23. package/src/sections/Accommodation.tsx +57 -0
  24. package/src/sections/AccountDeletion.tsx +347 -0
  25. package/src/sections/AccountEmailEnrichment.tsx +122 -0
  26. package/src/sections/AccountInformation.tsx +139 -0
  27. package/src/sections/AccountPaymentPlan.tsx +105 -0
  28. package/src/sections/AccountRewards.tsx +108 -0
  29. package/src/sections/AddOns.tsx +99 -148
  30. package/src/sections/BillingInfo.tsx +80 -78
  31. package/src/sections/Bundles.tsx +467 -0
  32. package/src/sections/EventDiscussionView.tsx +655 -0
  33. package/src/sections/NotificationPreferences.tsx +244 -0
  34. package/src/sections/OrderSummary.tsx +29 -2
  35. package/src/sections/Passes.tsx +177 -0
  36. package/src/sections/Payment.tsx +170 -53
  37. package/src/sections/PhoneAuthentication.tsx +371 -0
  38. package/src/sections/PostReaction.tsx +122 -0
  39. package/src/sections/ProfileSettings.tsx +446 -0
  40. package/src/sections/Rewards.tsx +320 -0
  41. package/src/sections/SeatMap.tsx +225 -0
  42. package/src/sections/SeatsIoMap.tsx +110 -0
  43. package/src/sections/Streaming.tsx +278 -0
  44. package/src/sections/TicketChange.tsx +379 -0
  45. package/src/sections/TicketGift.tsx +206 -0
  46. package/src/sections/TicketRefund.tsx +104 -0
  47. package/src/sections/Tickets.tsx +289 -208
  48. package/src/sections/Trips.tsx +87 -0
  49. package/src/sections/WaiverSigning.tsx +429 -0
  50. package/src/sections/billing/PhoneIdentity.tsx +169 -0
  51. package/src/sections/billing/TicketHoldersCard.tsx +4 -0
  52. package/src/sections/discussion/CommentImages.tsx +366 -0
  53. package/src/sections/discussion/SwipeReply.tsx +88 -0
  54. package/src/sections/index.ts +46 -0
  55. package/src/sections/rewards/RewardsLadder.tsx +167 -0
  56. package/src/sections/rewards/RewardsTree.tsx +117 -0
  57. package/src/storage.ts +8 -9
  58. package/src/ui/CheckoutProvider.tsx +51 -15
  59. package/src/ui/CustomFields.tsx +48 -12
  60. package/src/ui/Field.tsx +21 -3
  61. package/src/ui/Frozen.tsx +22 -0
  62. package/src/ui/InstallmentPicker.tsx +15 -18
  63. package/src/ui/PaymentMethods.tsx +6 -3
  64. package/src/ui/ProviderPages.tsx +214 -0
  65. package/src/ui/QuantitySelect.tsx +28 -3
  66. package/src/ui/Select.tsx +4 -1
  67. package/src/ui/SignaturePad.tsx +218 -0
  68. package/src/ui/index.ts +3 -0
  69. package/src/ui/primitives.tsx +13 -2
  70. package/src/ui/useAccountT.ts +16 -0
  71. package/src/useCheckout.ts +3 -1
@@ -25,6 +25,16 @@ import React, {
25
25
  import { Linking, Pressable, StyleSheet, Text, View } from 'react-native'
26
26
 
27
27
  import {
28
+ billingDobIso,
29
+ changeBillingPhone,
30
+ changeBillingField,
31
+ billingDraftFrom,
32
+ billingFilledHolderIndexes,
33
+ holderDraftsForSlots,
34
+ holderDraftsFrom,
35
+ holderDraftsReloaded,
36
+ holderDraftsMirroringBuyer,
37
+ patchedHolderDraft,
28
38
  hooks,
29
39
  mapProfileToBilling,
30
40
  missingCheckoutDetails,
@@ -43,9 +53,11 @@ import {
43
53
  } from '../ui/CheckoutProvider'
44
54
  import { CustomFieldInput } from '../ui/CustomFields'
45
55
  import { Checkbox, Field, Input } from '../ui/Field'
56
+ import { FrozenFields } from '../ui/Frozen'
46
57
  import { Button, Card, ErrorText, SectionHeader } from '../ui/primitives'
47
58
  import type { ResolvedTheme } from '../ui/theme'
48
59
  import { BillingAddressFields } from './billing/BillingAddressFields'
60
+ import { BillingPhoneIdentity } from './billing/PhoneIdentity'
49
61
  import { BuyerField } from './billing/BuyerField'
50
62
  import { TicketHoldersCard } from './billing/TicketHoldersCard'
51
63
  import type { BillingDraft, HolderDraft } from './billing/types'
@@ -110,52 +122,42 @@ export const BillingInfo: FC<BillingInfoProps> = ({
110
122
  const validation = useCheckoutValidation()
111
123
  const theme = useTheme()
112
124
  const s = useStyles(theme)
113
- const profile = hooks.useProfile({ retry: false })
125
+ const profile = hooks.useCheckoutBuyerProfile(eventId)
114
126
  const event = hooks.useEvent(eventId)
115
127
  // No onContinue ⇒ this is one combined form (inside <EmbeddedCheckout/>): mirror the
116
128
  // fields into the engine live and drop the "continue" button, so the buyer
117
129
  // fills billing + pays with ONE button — the way the old checkout flowed.
118
130
  const combined = !onContinue
119
131
 
120
- const [billing, setBilling] = useState<BillingDraft>({
121
- firstName: checkout.billing?.firstName ?? '',
122
- lastName: checkout.billing?.lastName ?? '',
123
- email: checkout.billing?.email ?? '',
124
- confirmEmail: checkout.billing?.confirmEmail ?? '',
125
- phone: checkout.billing?.phone,
126
- streetAddress: checkout.billing?.streetAddress,
127
- city: checkout.billing?.city,
128
- zip: checkout.billing?.zip,
129
- countryId: checkout.billing?.countryId,
130
- stateId: checkout.billing?.stateId,
131
- countryCode: checkout.billing?.countryCode,
132
- stateCode: checkout.billing?.stateCode,
133
- nationality: checkout.billing?.nationality,
134
- gender: checkout.billing?.gender,
135
- instagramHandle: checkout.billing?.instagramHandle,
136
- })
132
+ const [billing, setBilling] = useState<BillingDraft>(() =>
133
+ billingDraftFrom(checkout.billing)
134
+ )
137
135
  const [dataCapture, setDataCapture] = useState<CustomFieldValues>(
138
136
  checkout.billing?.dataCapture ?? {}
139
137
  )
140
138
  const [holders, setHolders] = useState<HolderDraft[]>(() =>
141
- checkout.holders.map((holder) => ({
142
- firstName: holder.firstName,
143
- lastName: holder.lastName,
144
- email: holder.email,
145
- phone: holder.phone ?? '',
146
- ticketTypeId: holder.ticketTypeId,
147
- ticketDataCapture: holder.ticketDataCapture ?? {},
148
- }))
139
+ holderDraftsFrom(checkout.holders, checkout.backfilledHolderIndexes)
149
140
  )
150
141
  const [consent, setConsent] = useState({ ttfOptIn: false, brandOptIn: false })
151
- const [dob, setDob] = useState(() => {
152
- const b = checkout.billing
153
- if (!b?.dobDay || !b.dobMonth || !b.dobYear) return ''
154
- return `${b.dobYear}-${String(b.dobMonth).padStart(2, '0')}-${String(
155
- b.dobDay
156
- ).padStart(2, '0')}`
157
- }) // ISO yyyy-mm-dd
142
+ const [dob, setDob] = useState(() => billingDobIso(checkout.billing)) // ISO yyyy-mm-dd
143
+ // A placement that ended without a charge: show what a retry submits.
144
+ hooks.useOnOrderReleased(checkout.orderFixed, () => {
145
+ setBilling(billingDraftFrom(checkout.billing))
146
+ setDataCapture(checkout.billing?.dataCapture ?? {})
147
+ setDob(billingDobIso(checkout.billing))
148
+ setConsent(checkout.consent)
149
+ setHolders((prev) =>
150
+ holderDraftsReloaded(prev, checkout.holders, checkout.backfilledHolderIndexes)
151
+ )
152
+ })
158
153
  const restoredHolders = useRef(checkout.holders)
154
+ // Another event: its holders and custom answers start empty; the buyer's
155
+ // own details stay.
156
+ hooks.useOnEventChange(checkout.eventId, () => {
157
+ restoredHolders.current = []
158
+ setHolders([])
159
+ setDataCapture({})
160
+ })
159
161
  const profileSeeded = useRef(false)
160
162
  const countryDefaultApplied = useRef(Boolean(checkout.billing?.countryId))
161
163
 
@@ -233,38 +235,21 @@ export const BillingInfo: FC<BillingInfoProps> = ({
233
235
 
234
236
  // Resize holders to the slot count, preserving typed values; tag ticket type.
235
237
  useEffect(() => {
236
- setHolders((prev) =>
237
- slots.map((slot, i) => ({
238
- firstName: prev[i]?.firstName ?? restoredHolders.current[i]?.firstName ?? '',
239
- lastName: prev[i]?.lastName ?? restoredHolders.current[i]?.lastName ?? '',
240
- email: prev[i]?.email ?? restoredHolders.current[i]?.email ?? '',
241
- phone: prev[i]?.phone ?? restoredHolders.current[i]?.phone ?? '',
242
- ticketDataCapture:
243
- prev[i]?.ticketDataCapture ??
244
- restoredHolders.current[i]?.ticketDataCapture ??
245
- {},
246
- ticketTypeId: slot.ticketTypeId,
247
- ticketName: slot.ticketName,
248
- }))
249
- )
238
+ setHolders((prev) => holderDraftsForSlots(prev, slots, restoredHolders.current))
250
239
  }, [slots])
251
240
 
252
241
  // Match the storefront: ticket 1 silently starts as the buyer. The legacy
253
242
  // checkout has no extra "use buyer details" control.
254
243
  useEffect(() => {
255
244
  if (!showHolders) return
245
+ // Ticket 1 stops copying the buyer once the buyer edits it.
256
246
  setHolders((prev) =>
257
- prev.map((holder, index) =>
258
- index === 0
259
- ? {
260
- ...holder,
261
- firstName: billing.firstName,
262
- lastName: billing.lastName,
263
- email: billing.email,
264
- phone: billing.phone ?? '',
265
- }
266
- : holder
267
- )
247
+ holderDraftsMirroringBuyer(prev, {
248
+ firstName: billing.firstName,
249
+ lastName: billing.lastName,
250
+ email: billing.email,
251
+ phone: billing.phone,
252
+ })
268
253
  )
269
254
  }, [showHolders, billing.firstName, billing.lastName, billing.email, billing.phone])
270
255
 
@@ -357,10 +342,10 @@ export const BillingInfo: FC<BillingInfoProps> = ({
357
342
  }, [billing.stateId, billing.stateCode, states])
358
343
 
359
344
  const setB = (k: keyof BillingDraft) => (value: string) =>
360
- setBilling((b) => ({ ...b, [k]: value }))
345
+ setBilling((b) => changeBillingField(b, k, value))
361
346
 
362
347
  const patchHolder = (i: number, patch: Partial<HolderDraft>) =>
363
- setHolders((hs) => hs.map((h, j) => (j === i ? { ...h, ...patch } : h)))
348
+ setHolders((hs) => hs.map((h, j) => (j === i ? patchedHolderDraft(h, patch) : h)))
364
349
 
365
350
  const [dobYear, dobMonth, dobDay] = dob ? dob.split('-').map(Number) : []
366
351
  const committedBilling: CheckoutBilling = {
@@ -413,7 +398,11 @@ export const BillingInfo: FC<BillingInfoProps> = ({
413
398
 
414
399
  const commit = () => {
415
400
  commitBilling(committedBilling)
416
- commitHolders(committedHolders)
401
+ // Holders that take the buyer's details are marked, so a guardian's
402
+ // signature for a child counts on them (billingFilledHolderIndexes).
403
+ commitHolders(committedHolders, {
404
+ backfilled: billingFilledHolderIndexes(holders, { holderNamesRequired }),
405
+ })
417
406
  if (ttfOptInLabel || brandOptInLabel) commitConsent(consent)
418
407
  }
419
408
 
@@ -477,8 +466,23 @@ export const BillingInfo: FC<BillingInfoProps> = ({
477
466
 
478
467
  const privacyInvalid = (showPrivacyError || showValidation) && !consent.ttfOptIn
479
468
 
469
+ const continueButton = !combined ? (
470
+ <Button
471
+ onPress={() => {
472
+ if (!complete) {
473
+ validation.requestValidation()
474
+ return
475
+ }
476
+ commit()
477
+ onContinue?.()
478
+ }}
479
+ >
480
+ {buttonLabel}
481
+ </Button>
482
+ ) : null
483
+
480
484
  return (
481
- <>
485
+ <FrozenFields frozen={checkout.orderFixed}>
482
486
  <Card>
483
487
  <SectionHeader title={heading} />
484
488
 
@@ -491,6 +495,14 @@ export const BillingInfo: FC<BillingInfoProps> = ({
491
495
  </View>
492
496
  ) : null}
493
497
 
498
+ {gates.phoneFirstCheckout && !checkout.isLoggedIn ? (
499
+ <BillingPhoneIdentity
500
+ billing={billing}
501
+ setBilling={setBilling}
502
+ eventId={eventId ?? checkout.eventId ?? undefined}
503
+ whatsappAvailable={Boolean(gates.phoneFirstWhatsapp)}
504
+ />
505
+ ) : null}
494
506
  <View style={s.row}>
495
507
  <View style={s.col}>
496
508
  <Field error={fieldError('First name', 'First name is required.')}>
@@ -598,7 +610,9 @@ export const BillingInfo: FC<BillingInfoProps> = ({
598
610
  <Input
599
611
  placeholder="Phone number"
600
612
  value={billing.phone ?? ''}
601
- onChangeText={(v) => setBilling((current) => ({ ...current, phone: v }))}
613
+ onChangeText={(v) =>
614
+ setBilling((current) => changeBillingPhone(current, v))
615
+ }
602
616
  keyboardType="phone-pad"
603
617
  invalid={Boolean(fieldError('Valid phone number'))}
604
618
  />
@@ -688,20 +702,7 @@ export const BillingInfo: FC<BillingInfoProps> = ({
688
702
  />
689
703
  ) : null}
690
704
 
691
- {!combined ? (
692
- <Button
693
- onPress={() => {
694
- if (!complete) {
695
- validation.requestValidation()
696
- return
697
- }
698
- commit()
699
- onContinue?.()
700
- }}
701
- >
702
- {buttonLabel}
703
- </Button>
704
- ) : null}
705
+ {!showHolders ? continueButton : null}
705
706
  </Card>
706
707
 
707
708
  {showHolders ? (
@@ -730,7 +731,8 @@ export const BillingInfo: FC<BillingInfoProps> = ({
730
731
  }
731
732
  />
732
733
  ) : null}
733
- </>
734
+ {showHolders ? continueButton : null}
735
+ </FrozenFields>
734
736
  )
735
737
  }
736
738