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

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 (60) hide show
  1. package/README.md +92 -8
  2. package/dist/index.d.mts +7410 -290
  3. package/dist/index.d.ts +7410 -290
  4. package/dist/index.js +23539 -5909
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +23561 -5857
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +22 -10
  9. package/scripts/build-seat-map.mjs +31 -0
  10. package/scripts/seat-map-entry.jsx +22 -0
  11. package/src/CardInput.tsx +64 -3
  12. package/src/EmbeddedCheckout.tsx +129 -36
  13. package/src/auth/AuthModal.tsx +90 -6
  14. package/src/auth/EmailCodeForm.tsx +91 -0
  15. package/src/auth/RegisterForm.tsx +91 -5
  16. package/src/auth/TwoFactorForm.tsx +74 -0
  17. package/src/auth/index.ts +4 -0
  18. package/src/configure.ts +8 -1
  19. package/src/confirmer.ts +112 -20
  20. package/src/generated/seatMapWidget.ts +3 -0
  21. package/src/index.tsx +1 -1
  22. package/src/sections/Accommodation.tsx +57 -0
  23. package/src/sections/AccountRewards.tsx +108 -0
  24. package/src/sections/AddOns.tsx +99 -148
  25. package/src/sections/BillingInfo.tsx +63 -64
  26. package/src/sections/Bundles.tsx +467 -0
  27. package/src/sections/EventDiscussionView.tsx +637 -0
  28. package/src/sections/Passes.tsx +177 -0
  29. package/src/sections/Payment.tsx +169 -52
  30. package/src/sections/PostReaction.tsx +122 -0
  31. package/src/sections/Rewards.tsx +320 -0
  32. package/src/sections/SeatMap.tsx +225 -0
  33. package/src/sections/SeatsIoMap.tsx +110 -0
  34. package/src/sections/Streaming.tsx +278 -0
  35. package/src/sections/TicketChange.tsx +379 -0
  36. package/src/sections/TicketGift.tsx +206 -0
  37. package/src/sections/TicketRefund.tsx +104 -0
  38. package/src/sections/Tickets.tsx +289 -208
  39. package/src/sections/Trips.tsx +87 -0
  40. package/src/sections/WaiverSigning.tsx +429 -0
  41. package/src/sections/billing/PhoneIdentity.tsx +169 -0
  42. package/src/sections/discussion/CommentImages.tsx +366 -0
  43. package/src/sections/discussion/SwipeReply.tsx +88 -0
  44. package/src/sections/index.ts +26 -0
  45. package/src/sections/rewards/RewardsLadder.tsx +167 -0
  46. package/src/sections/rewards/RewardsTree.tsx +117 -0
  47. package/src/storage.ts +8 -9
  48. package/src/ui/CheckoutProvider.tsx +25 -13
  49. package/src/ui/Field.tsx +21 -3
  50. package/src/ui/Frozen.tsx +22 -0
  51. package/src/ui/InstallmentPicker.tsx +15 -18
  52. package/src/ui/PaymentMethods.tsx +6 -3
  53. package/src/ui/ProviderPages.tsx +134 -0
  54. package/src/ui/QuantitySelect.tsx +26 -2
  55. package/src/ui/Select.tsx +4 -1
  56. package/src/ui/SignaturePad.tsx +218 -0
  57. package/src/ui/index.ts +1 -0
  58. package/src/ui/primitives.tsx +13 -2
  59. package/src/ui/useAccountT.ts +16 -0
  60. package/src/useCheckout.ts +3 -1
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `<SignaturePad/>` — the native drawing area for a drawn signature. Built
3
+ * from React Native's own `PanResponder` and `View`s, so it adds no native
4
+ * module to the consumer app (`react-native-svg` is not one of the SDK's
5
+ * peers). Each segment of a stroke renders as a thin, rotated `View`; when a
6
+ * stroke ends the pad reports a PNG data URL made by the shared core and the
7
+ * ink drawn so far (`signatureStrokeReport`). '' and 0 once cleared. A stroke
8
+ * the system interrupts (the app going to the background, an OS gesture)
9
+ * ends the same way as a released one, so what is saved is what is shown.
10
+ *
11
+ * Drawing stays smooth on long signatures: points live in a ref, a point
12
+ * closer than `MIN_STEP` to the last one is not kept, the pad re-renders at
13
+ * most once a frame, and a finished stroke never re-renders (`StrokeView`).
14
+ *
15
+ * The pad keeps the gesture inside a `ScrollView` (it refuses to hand the
16
+ * responder over), so drawing does not scroll the page.
17
+ */
18
+ import React, { memo, useEffect, useMemo, useRef, useState, type FC } from 'react'
19
+ import { PanResponder, Pressable, StyleSheet, Text, View } from 'react-native'
20
+ import {
21
+ SIGNATURE_INK,
22
+ SIGNATURE_PAPER,
23
+ signatureStrokeReport,
24
+ type SignatureStroke,
25
+ } from 'tf-checkout-shared'
26
+
27
+ import { useTheme } from './CheckoutProvider'
28
+
29
+ export interface SignaturePadProps {
30
+ onChange: (pngDataUrl: string, inkLength: number) => void
31
+ /** Accessible name of the drawing area. */
32
+ label: string
33
+ clearLabel: string
34
+ disabled?: boolean
35
+ }
36
+
37
+ const LINE_WIDTH = 2.5
38
+ const PAD_HEIGHT = 160
39
+ /** Pad points between kept points: finer moves add views, not detail. */
40
+ const MIN_STEP = 1.5
41
+
42
+ /** One stroke's segments. `points` changes only while the stroke is drawn. */
43
+ const StrokeView = memo<{ stroke: SignatureStroke; points: number }>(({ stroke }) =>
44
+ stroke.length === 1 ? (
45
+ <View
46
+ pointerEvents="none"
47
+ style={[
48
+ styles.dot,
49
+ { left: stroke[0].x - LINE_WIDTH / 2, top: stroke[0].y - LINE_WIDTH / 2 },
50
+ ]}
51
+ />
52
+ ) : (
53
+ <>
54
+ {stroke.slice(1).map((p, i) => {
55
+ const a = stroke[i]
56
+ const length = Math.hypot(p.x - a.x, p.y - a.y)
57
+ const angle = Math.atan2(p.y - a.y, p.x - a.x)
58
+ return (
59
+ <View
60
+ key={i}
61
+ pointerEvents="none"
62
+ style={[
63
+ styles.segment,
64
+ {
65
+ width: length + LINE_WIDTH,
66
+ left: (a.x + p.x) / 2 - (length + LINE_WIDTH) / 2,
67
+ top: (a.y + p.y) / 2 - LINE_WIDTH / 2,
68
+ transform: [{ rotate: `${angle}rad` }],
69
+ },
70
+ ]}
71
+ />
72
+ )
73
+ })}
74
+ </>
75
+ )
76
+ )
77
+ StrokeView.displayName = 'StrokeView'
78
+
79
+ export const SignaturePad: FC<SignaturePadProps> = ({
80
+ onChange,
81
+ label,
82
+ clearLabel,
83
+ disabled,
84
+ }) => {
85
+ const theme = useTheme()
86
+ const strokesRef = useRef<SignatureStroke[]>([])
87
+ // Bumped to re-render; the strokes themselves stay in the ref.
88
+ const [, setFrame] = useState(0)
89
+ const frameRequest = useRef<number | null>(null)
90
+ const size = useRef({ width: 0, height: PAD_HEIGHT })
91
+ const report = useRef(onChange)
92
+ report.current = onChange
93
+ const disabledRef = useRef(disabled)
94
+ disabledRef.current = disabled
95
+
96
+ const render = () => {
97
+ if (frameRequest.current !== null) cancelAnimationFrame(frameRequest.current)
98
+ frameRequest.current = null
99
+ setFrame((n) => n + 1)
100
+ }
101
+ const renderNextFrame = () => {
102
+ if (frameRequest.current !== null) return
103
+ frameRequest.current = requestAnimationFrame(render)
104
+ }
105
+ useEffect(
106
+ () => () => {
107
+ if (frameRequest.current !== null) cancelAnimationFrame(frameRequest.current)
108
+ },
109
+ []
110
+ )
111
+
112
+ // Paints every kept point first, so the report and the pad show the same.
113
+ const endStroke = () => {
114
+ render()
115
+ const ended = signatureStrokeReport(strokesRef.current, {
116
+ padWidth: size.current.width,
117
+ padHeight: size.current.height,
118
+ lineWidth: LINE_WIDTH,
119
+ })
120
+ if (ended) report.current(ended.pngDataUrl, ended.inkLength)
121
+ }
122
+
123
+ const responder = useMemo(
124
+ () =>
125
+ PanResponder.create({
126
+ onStartShouldSetPanResponder: () => !disabledRef.current,
127
+ onMoveShouldSetPanResponder: () => !disabledRef.current,
128
+ onPanResponderTerminationRequest: () => false,
129
+ onPanResponderGrant: (e) => {
130
+ const { locationX: x, locationY: y } = e.nativeEvent
131
+ strokesRef.current = [...strokesRef.current, [{ x, y }]]
132
+ render()
133
+ },
134
+ onPanResponderMove: (e) => {
135
+ const { locationX: x, locationY: y } = e.nativeEvent
136
+ const last = strokesRef.current[strokesRef.current.length - 1]
137
+ if (!last) return
138
+ const previous = last[last.length - 1]
139
+ if (Math.hypot(x - previous.x, y - previous.y) < MIN_STEP) return
140
+ last.push({ x, y })
141
+ renderNextFrame()
142
+ },
143
+ onPanResponderRelease: endStroke,
144
+ onPanResponderTerminate: endStroke,
145
+ }),
146
+ // eslint-disable-next-line react-hooks/exhaustive-deps
147
+ []
148
+ )
149
+
150
+ const clear = () => {
151
+ strokesRef.current = []
152
+ render()
153
+ report.current('', 0)
154
+ }
155
+
156
+ return (
157
+ <View style={styles.wrap}>
158
+ <View
159
+ accessible
160
+ accessibilityRole="image"
161
+ accessibilityLabel={label}
162
+ onLayout={(e) => {
163
+ size.current = {
164
+ width: e.nativeEvent.layout.width,
165
+ height: e.nativeEvent.layout.height,
166
+ }
167
+ }}
168
+ style={[
169
+ styles.pad,
170
+ {
171
+ borderColor: theme.border,
172
+ borderRadius: theme.radiusControl,
173
+ // The stored image is black on white, so the pad shows the same.
174
+ backgroundColor: SIGNATURE_PAPER,
175
+ },
176
+ ]}
177
+ {...responder.panHandlers}
178
+ >
179
+ {strokesRef.current.map((stroke, si) => (
180
+ <StrokeView key={si} stroke={stroke} points={stroke.length} />
181
+ ))}
182
+ </View>
183
+ <Pressable
184
+ accessibilityRole="button"
185
+ onPress={clear}
186
+ disabled={disabled}
187
+ style={styles.clear}
188
+ >
189
+ <Text style={{ color: theme.accent, fontFamily: theme.fontFamily }}>
190
+ {clearLabel}
191
+ </Text>
192
+ </Pressable>
193
+ </View>
194
+ )
195
+ }
196
+
197
+ const styles = StyleSheet.create({
198
+ wrap: { gap: 6 },
199
+ pad: {
200
+ height: PAD_HEIGHT,
201
+ borderWidth: 1,
202
+ overflow: 'hidden',
203
+ },
204
+ segment: {
205
+ position: 'absolute',
206
+ height: LINE_WIDTH,
207
+ borderRadius: LINE_WIDTH / 2,
208
+ backgroundColor: SIGNATURE_INK,
209
+ },
210
+ dot: {
211
+ position: 'absolute',
212
+ width: LINE_WIDTH,
213
+ height: LINE_WIDTH,
214
+ borderRadius: LINE_WIDTH / 2,
215
+ backgroundColor: SIGNATURE_INK,
216
+ },
217
+ clear: { alignSelf: 'flex-start', paddingVertical: 4 },
218
+ })
package/src/ui/index.ts CHANGED
@@ -8,6 +8,7 @@ export {
8
8
  type CheckoutProviderProps,
9
9
  } from './CheckoutProvider'
10
10
  export * from './primitives'
11
+ export * from './Frozen'
11
12
  export * from './Field'
12
13
  export * from './Select'
13
14
  export * from './QuantitySelect'
@@ -17,6 +17,7 @@ import {
17
17
 
18
18
  import { useTheme } from './CheckoutProvider'
19
19
  import type { ResolvedTheme } from './theme'
20
+ import { useFrozen } from './Frozen'
20
21
 
21
22
  /** Small spinner. */
22
23
  export const Loader: FC<{ size?: 'small' | 'large'; color?: string }> = ({
@@ -79,7 +80,16 @@ export const MutedText: FC<{ children: ReactNode; style?: StyleProp<TextStyle> }
79
80
  export const ErrorText: FC<{ children: ReactNode }> = ({ children }) => {
80
81
  const theme = useTheme()
81
82
  const s = useStyles(theme)
82
- return <Text style={s.errorText}>{children}</Text>
83
+ // Announced when it appears, like role="alert" on web.
84
+ return (
85
+ <Text
86
+ style={s.errorText}
87
+ accessibilityRole="alert"
88
+ accessibilityLiveRegion="assertive"
89
+ >
90
+ {children}
91
+ </Text>
92
+ )
83
93
  }
84
94
 
85
95
  export interface ButtonProps {
@@ -99,7 +109,8 @@ export const Button: FC<ButtonProps> = ({
99
109
  }) => {
100
110
  const theme = useTheme()
101
111
  const s = useStyles(theme)
102
- const isDisabled = disabled || loading
112
+ const frozen = useFrozen()
113
+ const isDisabled = disabled || loading || frozen
103
114
  const ghost = variant === 'ghost'
104
115
  return (
105
116
  <Pressable
@@ -0,0 +1,16 @@
1
+ import { useMemo } from 'react'
2
+ import {
3
+ createTranslator,
4
+ resolveLocale,
5
+ translateAccountMessage,
6
+ type TranslateVars,
7
+ } from 'tf-checkout-shared'
8
+
9
+ export const useAccountT = (locale?: string) => {
10
+ const translate = useMemo(
11
+ () => createTranslator({ locale: resolveLocale(locale) }),
12
+ [locale]
13
+ )
14
+ return (english: string, vars?: TranslateVars) =>
15
+ translateAccountMessage(translate, english, vars)
16
+ }
@@ -18,6 +18,7 @@ import {
18
18
  type UseCheckout,
19
19
  } from 'tf-checkout-shared'
20
20
 
21
+ import { claimDefaultCardField } from './CardInput'
21
22
  import { rnConfirmer, rnPorts } from './instance'
22
23
 
23
24
  export interface UseCheckoutOptions {
@@ -30,6 +31,7 @@ export interface UseCheckoutOptions {
30
31
 
31
32
  export const useCheckout = (opts: UseCheckoutOptions = {}): UseCheckout =>
32
33
  useSharedCheckout({
33
- ports: { confirmer: rnConfirmer, ...rnPorts, ...opts.ports },
34
+ // A Pay without its own claim uses the default ticket field (`checkout`).
35
+ ports: { confirmer: rnConfirmer, ...rnPorts, claimDefaultCardField, ...opts.ports },
34
36
  options: opts.options,
35
37
  })