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.
- package/README.md +92 -8
- package/dist/index.d.mts +7410 -290
- package/dist/index.d.ts +7410 -290
- package/dist/index.js +23539 -5909
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23561 -5857
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -10
- package/scripts/build-seat-map.mjs +31 -0
- package/scripts/seat-map-entry.jsx +22 -0
- package/src/CardInput.tsx +64 -3
- package/src/EmbeddedCheckout.tsx +129 -36
- package/src/auth/AuthModal.tsx +90 -6
- package/src/auth/EmailCodeForm.tsx +91 -0
- package/src/auth/RegisterForm.tsx +91 -5
- package/src/auth/TwoFactorForm.tsx +74 -0
- package/src/auth/index.ts +4 -0
- package/src/configure.ts +8 -1
- package/src/confirmer.ts +112 -20
- package/src/generated/seatMapWidget.ts +3 -0
- package/src/index.tsx +1 -1
- package/src/sections/Accommodation.tsx +57 -0
- package/src/sections/AccountRewards.tsx +108 -0
- package/src/sections/AddOns.tsx +99 -148
- package/src/sections/BillingInfo.tsx +63 -64
- package/src/sections/Bundles.tsx +467 -0
- package/src/sections/EventDiscussionView.tsx +637 -0
- package/src/sections/Passes.tsx +177 -0
- package/src/sections/Payment.tsx +169 -52
- package/src/sections/PostReaction.tsx +122 -0
- package/src/sections/Rewards.tsx +320 -0
- package/src/sections/SeatMap.tsx +225 -0
- package/src/sections/SeatsIoMap.tsx +110 -0
- package/src/sections/Streaming.tsx +278 -0
- package/src/sections/TicketChange.tsx +379 -0
- package/src/sections/TicketGift.tsx +206 -0
- package/src/sections/TicketRefund.tsx +104 -0
- package/src/sections/Tickets.tsx +289 -208
- package/src/sections/Trips.tsx +87 -0
- package/src/sections/WaiverSigning.tsx +429 -0
- package/src/sections/billing/PhoneIdentity.tsx +169 -0
- package/src/sections/discussion/CommentImages.tsx +366 -0
- package/src/sections/discussion/SwipeReply.tsx +88 -0
- package/src/sections/index.ts +26 -0
- package/src/sections/rewards/RewardsLadder.tsx +167 -0
- package/src/sections/rewards/RewardsTree.tsx +117 -0
- package/src/storage.ts +8 -9
- package/src/ui/CheckoutProvider.tsx +25 -13
- package/src/ui/Field.tsx +21 -3
- package/src/ui/Frozen.tsx +22 -0
- package/src/ui/InstallmentPicker.tsx +15 -18
- package/src/ui/PaymentMethods.tsx +6 -3
- package/src/ui/ProviderPages.tsx +134 -0
- package/src/ui/QuantitySelect.tsx +26 -2
- package/src/ui/Select.tsx +4 -1
- package/src/ui/SignaturePad.tsx +218 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/primitives.tsx +13 -2
- package/src/ui/useAccountT.ts +16 -0
- 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
package/src/ui/primitives.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
+
}
|
package/src/useCheckout.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
})
|