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.
- package/README.md +115 -24
- package/dist/index.d.mts +8195 -282
- package/dist/index.d.ts +8195 -282
- package/dist/index.js +32346 -7001
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32586 -7128
- package/dist/index.mjs.map +1 -1
- package/package.json +23 -11
- package/scripts/build-seat-map.mjs +31 -0
- package/scripts/seat-map-entry.jsx +22 -0
- package/scripts/verify-discussion-support.cjs +131 -0
- package/src/CardInput.tsx +64 -3
- package/src/EmbeddedCheckout.tsx +140 -38
- package/src/auth/AuthModal.tsx +186 -9
- 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 +19 -14
- 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/AccountDeletion.tsx +347 -0
- package/src/sections/AccountEmailEnrichment.tsx +122 -0
- package/src/sections/AccountInformation.tsx +139 -0
- package/src/sections/AccountPaymentPlan.tsx +105 -0
- package/src/sections/AccountRewards.tsx +108 -0
- package/src/sections/AddOns.tsx +99 -148
- package/src/sections/BillingInfo.tsx +80 -78
- package/src/sections/Bundles.tsx +467 -0
- package/src/sections/EventDiscussionView.tsx +655 -0
- package/src/sections/NotificationPreferences.tsx +244 -0
- package/src/sections/OrderSummary.tsx +29 -2
- package/src/sections/Passes.tsx +177 -0
- package/src/sections/Payment.tsx +170 -53
- package/src/sections/PhoneAuthentication.tsx +371 -0
- package/src/sections/PostReaction.tsx +122 -0
- package/src/sections/ProfileSettings.tsx +446 -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/billing/TicketHoldersCard.tsx +4 -0
- package/src/sections/discussion/CommentImages.tsx +366 -0
- package/src/sections/discussion/SwipeReply.tsx +88 -0
- package/src/sections/index.ts +46 -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 +51 -15
- package/src/ui/CustomFields.tsx +48 -12
- 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 +214 -0
- package/src/ui/QuantitySelect.tsx +28 -3
- package/src/ui/Select.tsx +4 -1
- package/src/ui/SignaturePad.tsx +218 -0
- package/src/ui/index.ts +3 -0
- package/src/ui/primitives.tsx +13 -2
- package/src/ui/useAccountT.ts +16 -0
- package/src/useCheckout.ts +3 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { hooks, type NotificationPreferencesFlow } from 'tf-checkout-shared'
|
|
12
|
+
import { useTheme } from '../ui/CheckoutProvider'
|
|
13
|
+
import { resolveTheme, type CheckoutTheme } from '../ui/theme'
|
|
14
|
+
import { useAccountT } from '../ui/useAccountT'
|
|
15
|
+
|
|
16
|
+
export interface NotificationPreferencesProps {
|
|
17
|
+
locale?: string
|
|
18
|
+
translate?: (english: string, values?: Record<string, string | number>) => string
|
|
19
|
+
showHeader?: boolean
|
|
20
|
+
onBack?: () => void
|
|
21
|
+
onSignIn?: () => void
|
|
22
|
+
onSaved?: () => void
|
|
23
|
+
theme?: CheckoutTheme
|
|
24
|
+
style?: StyleProp<ViewStyle>
|
|
25
|
+
renderSaveButton?: (label: string, onPress: () => void, disabled: boolean) => ReactNode
|
|
26
|
+
}
|
|
27
|
+
export const NotificationPreferences = (props: NotificationPreferencesProps) => {
|
|
28
|
+
const flow = hooks.useNotificationPreferences()
|
|
29
|
+
return <NotificationPreferencesView {...props} preferences={flow} />
|
|
30
|
+
}
|
|
31
|
+
export const NotificationPreferencesView = ({
|
|
32
|
+
preferences: flow,
|
|
33
|
+
locale,
|
|
34
|
+
translate,
|
|
35
|
+
showHeader = true,
|
|
36
|
+
onBack,
|
|
37
|
+
onSignIn,
|
|
38
|
+
onSaved,
|
|
39
|
+
theme: override,
|
|
40
|
+
style,
|
|
41
|
+
renderSaveButton,
|
|
42
|
+
}: NotificationPreferencesProps & { preferences: NotificationPreferencesFlow }) => {
|
|
43
|
+
const defaultT = useAccountT(locale)
|
|
44
|
+
const t = translate ?? defaultT
|
|
45
|
+
const inherited = useTheme()
|
|
46
|
+
const theme = useMemo(
|
|
47
|
+
() => (override ? resolveTheme({ ...inherited, ...override }) : inherited),
|
|
48
|
+
[inherited, override]
|
|
49
|
+
)
|
|
50
|
+
const assertOwner = hooks.useSessionAction()
|
|
51
|
+
const act = (action?: () => unknown) => {
|
|
52
|
+
try {
|
|
53
|
+
assertOwner()
|
|
54
|
+
action?.()
|
|
55
|
+
} catch {
|
|
56
|
+
/* Retired account control. */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (flow.preferences?.available) flow.begin()
|
|
61
|
+
}, [flow.scopeKey, Boolean(flow.preferences), flow.preferences?.available])
|
|
62
|
+
const text = { color: theme.text, fontFamily: theme.fontFamily }
|
|
63
|
+
const button = (label: string, action: () => unknown, disabled = flow.busy) => (
|
|
64
|
+
<Pressable
|
|
65
|
+
accessibilityRole="button"
|
|
66
|
+
accessibilityLabel={t(label)}
|
|
67
|
+
accessibilityState={{ disabled, busy: flow.busy }}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
onPress={() => act(action)}
|
|
70
|
+
style={[s.button, { borderColor: theme.border }, disabled && s.disabled]}
|
|
71
|
+
>
|
|
72
|
+
<Text style={[text, { color: theme.accent }]}>{t(label)}</Text>
|
|
73
|
+
</Pressable>
|
|
74
|
+
)
|
|
75
|
+
if (flow.confirmingAccount || (flow.signedIn && flow.query.isPending))
|
|
76
|
+
return (
|
|
77
|
+
<ActivityIndicator
|
|
78
|
+
accessibilityLabel={t('Loading notification preferences…')}
|
|
79
|
+
color={theme.accent}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
if (!flow.signedIn)
|
|
83
|
+
return (
|
|
84
|
+
<View style={[s.container, style]}>
|
|
85
|
+
{flow.admission.error && (
|
|
86
|
+
<>
|
|
87
|
+
<Text accessibilityRole="alert" style={{ color: theme.error }}>
|
|
88
|
+
{t(flow.admission.error.message)}
|
|
89
|
+
</Text>
|
|
90
|
+
{button('Try again', flow.retry, false)}
|
|
91
|
+
</>
|
|
92
|
+
)}
|
|
93
|
+
<Text style={text}>{t('Sign in to choose your event updates.')}</Text>
|
|
94
|
+
{onSignIn && button('Sign in', onSignIn, false)}
|
|
95
|
+
</View>
|
|
96
|
+
)
|
|
97
|
+
if (!flow.preferences || !flow.preferences.available)
|
|
98
|
+
return (
|
|
99
|
+
<View style={[s.container, style]}>
|
|
100
|
+
<Text accessibilityRole="alert" style={{ color: theme.error }}>
|
|
101
|
+
{t(
|
|
102
|
+
flow.query.error?.message ??
|
|
103
|
+
flow.preferences?.reason ??
|
|
104
|
+
'Notification preferences are not available right now.'
|
|
105
|
+
)}
|
|
106
|
+
</Text>
|
|
107
|
+
{button('Try again', flow.retry, false)}
|
|
108
|
+
</View>
|
|
109
|
+
)
|
|
110
|
+
const save = async () => {
|
|
111
|
+
if (await flow.save()) act(onSaved)
|
|
112
|
+
}
|
|
113
|
+
const disabled = flow.busy || !flow.editor
|
|
114
|
+
const cancel = () => {
|
|
115
|
+
if (flow.cancel()) {
|
|
116
|
+
if (onBack) act(onBack)
|
|
117
|
+
else flow.begin()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return (
|
|
121
|
+
<View style={[s.container, style]}>
|
|
122
|
+
{showHeader && (
|
|
123
|
+
<Text accessibilityRole="header" style={[text, s.title]}>
|
|
124
|
+
{t('Notifications')}
|
|
125
|
+
</Text>
|
|
126
|
+
)}
|
|
127
|
+
<Text style={[text, s.description]}>
|
|
128
|
+
{t(
|
|
129
|
+
'Choose which optional event recommendations you receive. Ticket and account messages stay the same.'
|
|
130
|
+
)}
|
|
131
|
+
</Text>
|
|
132
|
+
<View
|
|
133
|
+
accessibilityRole="radiogroup"
|
|
134
|
+
accessibilityLabel={t('Event updates')}
|
|
135
|
+
style={{ gap: 12 }}
|
|
136
|
+
>
|
|
137
|
+
{(
|
|
138
|
+
[
|
|
139
|
+
[
|
|
140
|
+
'everything',
|
|
141
|
+
'Everything',
|
|
142
|
+
'Recommendations for any events that match your interests.',
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
'my_events',
|
|
146
|
+
'My events only',
|
|
147
|
+
'Recommendations for events you have tickets for or have saved.',
|
|
148
|
+
],
|
|
149
|
+
] as const
|
|
150
|
+
).map(([value, label, description]) => (
|
|
151
|
+
<Pressable
|
|
152
|
+
key={value}
|
|
153
|
+
accessibilityRole="radio"
|
|
154
|
+
accessibilityLabel={t(label)}
|
|
155
|
+
accessibilityState={{ checked: flow.editor?.audience === value, disabled }}
|
|
156
|
+
disabled={disabled}
|
|
157
|
+
onPress={() => flow.choose(value)}
|
|
158
|
+
style={[s.choice, { borderColor: theme.border }, disabled && s.disabled]}
|
|
159
|
+
>
|
|
160
|
+
<View style={[s.radio, { borderColor: theme.accent }]}>
|
|
161
|
+
{flow.editor?.audience === value && (
|
|
162
|
+
<View style={[s.dot, { backgroundColor: theme.accent }]} />
|
|
163
|
+
)}
|
|
164
|
+
</View>
|
|
165
|
+
<View style={{ flex: 1, gap: 6 }}>
|
|
166
|
+
<Text style={text}>{t(label)}</Text>
|
|
167
|
+
<Text
|
|
168
|
+
style={{
|
|
169
|
+
color: theme.muted,
|
|
170
|
+
fontFamily: theme.fontFamily,
|
|
171
|
+
lineHeight: 21,
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{t(description)}
|
|
175
|
+
</Text>
|
|
176
|
+
</View>
|
|
177
|
+
</Pressable>
|
|
178
|
+
))}
|
|
179
|
+
</View>
|
|
180
|
+
{flow.error && (
|
|
181
|
+
<>
|
|
182
|
+
<Text accessibilityRole="alert" style={{ color: theme.error }}>
|
|
183
|
+
{t(flow.error)}
|
|
184
|
+
</Text>
|
|
185
|
+
{button('Try again', flow.retry)}
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
{flow.saved && (
|
|
189
|
+
<Text accessibilityLiveRegion="polite" style={text}>
|
|
190
|
+
{t('Your notification preferences have been saved.')}
|
|
191
|
+
</Text>
|
|
192
|
+
)}
|
|
193
|
+
{renderSaveButton
|
|
194
|
+
? renderSaveButton(
|
|
195
|
+
t('Save'),
|
|
196
|
+
() =>
|
|
197
|
+
act(() => {
|
|
198
|
+
void save()
|
|
199
|
+
}),
|
|
200
|
+
disabled
|
|
201
|
+
)
|
|
202
|
+
: button(
|
|
203
|
+
'Save',
|
|
204
|
+
() => {
|
|
205
|
+
void save()
|
|
206
|
+
},
|
|
207
|
+
disabled
|
|
208
|
+
)}
|
|
209
|
+
{button('Cancel', cancel)}
|
|
210
|
+
</View>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
const s = StyleSheet.create({
|
|
214
|
+
container: { gap: 20, paddingVertical: 16 },
|
|
215
|
+
title: { fontSize: 24, fontWeight: '600' },
|
|
216
|
+
description: { lineHeight: 24 },
|
|
217
|
+
choice: {
|
|
218
|
+
flexDirection: 'row',
|
|
219
|
+
alignItems: 'center',
|
|
220
|
+
gap: 16,
|
|
221
|
+
padding: 16,
|
|
222
|
+
minHeight: 72,
|
|
223
|
+
borderWidth: 1,
|
|
224
|
+
borderRadius: 16,
|
|
225
|
+
},
|
|
226
|
+
radio: {
|
|
227
|
+
width: 24,
|
|
228
|
+
height: 24,
|
|
229
|
+
borderRadius: 12,
|
|
230
|
+
borderWidth: 1,
|
|
231
|
+
alignItems: 'center',
|
|
232
|
+
justifyContent: 'center',
|
|
233
|
+
},
|
|
234
|
+
dot: { width: 14, height: 14, borderRadius: 7 },
|
|
235
|
+
button: {
|
|
236
|
+
minHeight: 48,
|
|
237
|
+
alignItems: 'center',
|
|
238
|
+
justifyContent: 'center',
|
|
239
|
+
borderWidth: 1,
|
|
240
|
+
borderRadius: 24,
|
|
241
|
+
padding: 12,
|
|
242
|
+
},
|
|
243
|
+
disabled: { opacity: 0.5 },
|
|
244
|
+
})
|
|
@@ -8,7 +8,14 @@
|
|
|
8
8
|
import React, { type FC } from 'react'
|
|
9
9
|
import { StyleSheet, View } from 'react-native'
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
hooks,
|
|
13
|
+
memberCreditRow,
|
|
14
|
+
createTranslator,
|
|
15
|
+
type LocaleCode,
|
|
16
|
+
type Messages,
|
|
17
|
+
type Translate,
|
|
18
|
+
} from 'tf-checkout-shared'
|
|
12
19
|
|
|
13
20
|
import { BodyText, Card, Divider, MutedText, SectionHeader } from '../ui/primitives'
|
|
14
21
|
import { useCheckoutContext, useTheme } from '../ui/CheckoutProvider'
|
|
@@ -22,15 +29,28 @@ const money = (symbol: string, code: string, amount: number): string =>
|
|
|
22
29
|
export interface OrderSummaryProps {
|
|
23
30
|
/** Event whose name to show; omit to skip the event line. */
|
|
24
31
|
eventId?: string
|
|
32
|
+
locale?: LocaleCode
|
|
33
|
+
messages?: Partial<Messages>
|
|
34
|
+
translate?: Translate
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
/** Storefront-compatible order summary shown after the checkout controls. */
|
|
28
|
-
export const OrderSummary: FC<OrderSummaryProps> = ({
|
|
38
|
+
export const OrderSummary: FC<OrderSummaryProps> = ({
|
|
39
|
+
eventId,
|
|
40
|
+
locale,
|
|
41
|
+
messages,
|
|
42
|
+
translate,
|
|
43
|
+
}) => {
|
|
29
44
|
const checkout = useCheckoutContext()
|
|
30
45
|
const theme = useTheme()
|
|
31
46
|
const event = hooks.useEvent(eventId)
|
|
32
47
|
const breakdown = checkout.breakdown
|
|
33
48
|
const detailed = checkout.priceBreakdown
|
|
49
|
+
const memberCredit = memberCreditRow(
|
|
50
|
+
detailed,
|
|
51
|
+
breakdown,
|
|
52
|
+
(translate ?? createTranslator({ locale, messages }))('summary.memberCredit')
|
|
53
|
+
)
|
|
34
54
|
|
|
35
55
|
if (!breakdown || breakdown.tickets.length === 0) return null
|
|
36
56
|
|
|
@@ -87,6 +107,13 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId }) => {
|
|
|
87
107
|
</View>
|
|
88
108
|
) : null}
|
|
89
109
|
|
|
110
|
+
{memberCredit ? (
|
|
111
|
+
<View style={styles.totalRow}>
|
|
112
|
+
<BodyText>{memberCredit.name}</BodyText>
|
|
113
|
+
<BodyText style={styles.tabular}>{memberCredit.amount}</BodyText>
|
|
114
|
+
</View>
|
|
115
|
+
) : null}
|
|
116
|
+
|
|
90
117
|
<Divider />
|
|
91
118
|
<View style={styles.totalRow}>
|
|
92
119
|
<BodyText style={[styles.total, { fontSize: theme.fontSize + 1 }]}>
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { type FC } from 'react'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { hooks, type PassesProps } from 'tf-checkout-shared'
|
|
4
|
+
import {
|
|
5
|
+
BodyText,
|
|
6
|
+
Button,
|
|
7
|
+
Card,
|
|
8
|
+
ErrorText,
|
|
9
|
+
Loader,
|
|
10
|
+
SectionHeader,
|
|
11
|
+
} from '../ui/primitives'
|
|
12
|
+
import { Input } from '../ui/Field'
|
|
13
|
+
import { useAccountT } from '../ui/useAccountT'
|
|
14
|
+
|
|
15
|
+
export const Passes: FC<PassesProps> = ({ onTicketsChanged, locale }) => {
|
|
16
|
+
const t = useAccountT(locale)
|
|
17
|
+
const flow = hooks.useCustomerPasses(onTicketsChanged)
|
|
18
|
+
const { state } = flow
|
|
19
|
+
if (state.command)
|
|
20
|
+
return (
|
|
21
|
+
<Card>
|
|
22
|
+
<SectionHeader
|
|
23
|
+
title={t(
|
|
24
|
+
state.command.kind === 'redeem'
|
|
25
|
+
? 'Use an event credit'
|
|
26
|
+
: 'Cancel this booking'
|
|
27
|
+
)}
|
|
28
|
+
/>
|
|
29
|
+
<BodyText>
|
|
30
|
+
{state.command.kind === 'redeem'
|
|
31
|
+
? t('Use one event credit for {{event}}?', {
|
|
32
|
+
event: state.command.event.name,
|
|
33
|
+
})
|
|
34
|
+
: t('Cancel {{event}} and return its credit to your pass?', {
|
|
35
|
+
event: state.command.booking.event_name || t('this event'),
|
|
36
|
+
})}
|
|
37
|
+
</BodyText>
|
|
38
|
+
{state.error && <ErrorText>{t(state.error)}</ErrorText>}
|
|
39
|
+
<Button
|
|
40
|
+
loading={state.busy}
|
|
41
|
+
disabled={state.busy}
|
|
42
|
+
onPress={() => void flow.submit()}
|
|
43
|
+
>
|
|
44
|
+
{t(state.submitted ? 'Check my booking' : 'Confirm')}
|
|
45
|
+
</Button>
|
|
46
|
+
{!state.submitted && (
|
|
47
|
+
<Button variant="ghost" onPress={flow.back}>
|
|
48
|
+
{t('Back')}
|
|
49
|
+
</Button>
|
|
50
|
+
)}
|
|
51
|
+
</Card>
|
|
52
|
+
)
|
|
53
|
+
if (!state.passId)
|
|
54
|
+
return (
|
|
55
|
+
<View style={{ gap: 12 }}>
|
|
56
|
+
<SectionHeader title={t('My passes')} />
|
|
57
|
+
{flow.passes.isLoading && <Loader />}
|
|
58
|
+
{flow.passes.error && (
|
|
59
|
+
<>
|
|
60
|
+
<ErrorText>{flow.passes.error.message}</ErrorText>
|
|
61
|
+
<Button onPress={() => void flow.passes.refetch()}>{t('Try again')}</Button>
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
{flow.passes.data?.passes.length === 0 && (
|
|
65
|
+
<BodyText>{t('You do not have any event passes yet.')}</BodyText>
|
|
66
|
+
)}
|
|
67
|
+
{flow.passes.data?.passes.map((pass) => (
|
|
68
|
+
<Card key={pass.id}>
|
|
69
|
+
<SectionHeader title={pass.name || t('Event pass')} />
|
|
70
|
+
<BodyText>
|
|
71
|
+
{t('{{remaining}} of {{total}} event credits remaining', {
|
|
72
|
+
remaining: pass.credits_remaining,
|
|
73
|
+
total: pass.credits_total,
|
|
74
|
+
})}
|
|
75
|
+
</BodyText>
|
|
76
|
+
<BodyText>{t(pass.status)}</BodyText>
|
|
77
|
+
<Button onPress={() => flow.open(pass.id)}>{t('View pass')}</Button>
|
|
78
|
+
</Card>
|
|
79
|
+
))}
|
|
80
|
+
{state.page > 1 && (
|
|
81
|
+
<Button variant="ghost" onPress={() => flow.page(state.page - 1)}>
|
|
82
|
+
{t('Previous page')}
|
|
83
|
+
</Button>
|
|
84
|
+
)}
|
|
85
|
+
{flow.passes.data?.meta.has_more && (
|
|
86
|
+
<Button onPress={() => flow.page(state.page + 1)}>{t('Next page')}</Button>
|
|
87
|
+
)}
|
|
88
|
+
</View>
|
|
89
|
+
)
|
|
90
|
+
const pass = flow.pass.data
|
|
91
|
+
return (
|
|
92
|
+
<View style={{ gap: 12 }}>
|
|
93
|
+
<Button variant="ghost" onPress={() => flow.open()}>
|
|
94
|
+
{t('Back to passes')}
|
|
95
|
+
</Button>
|
|
96
|
+
{flow.pass.isLoading && <Loader />}
|
|
97
|
+
{[flow.pass.error, flow.events.error, flow.bookings.error]
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.map((error, index) => (
|
|
100
|
+
<ErrorText key={index}>{error!.message}</ErrorText>
|
|
101
|
+
))}
|
|
102
|
+
{pass && (
|
|
103
|
+
<Card>
|
|
104
|
+
<SectionHeader title={pass.name || t('Event pass')} />
|
|
105
|
+
<BodyText>{pass.description || ''}</BodyText>
|
|
106
|
+
<BodyText>
|
|
107
|
+
{t('{{remaining}} of {{total}} event credits remaining', {
|
|
108
|
+
remaining: pass.credits_remaining,
|
|
109
|
+
total: pass.credits_total,
|
|
110
|
+
})}
|
|
111
|
+
</BodyText>
|
|
112
|
+
{pass.valid_until && (
|
|
113
|
+
<BodyText>{t('Valid until {{date}}', { date: pass.valid_until })}</BodyText>
|
|
114
|
+
)}
|
|
115
|
+
</Card>
|
|
116
|
+
)}
|
|
117
|
+
{state.message && <BodyText>{t(state.message)}</BodyText>}
|
|
118
|
+
<SectionHeader title={t('Your bookings')} />
|
|
119
|
+
{flow.bookings.data?.redemptions.length === 0 && (
|
|
120
|
+
<BodyText>{t('You have not booked any events with this pass yet.')}</BodyText>
|
|
121
|
+
)}
|
|
122
|
+
{flow.bookings.data?.redemptions.map((booking) => (
|
|
123
|
+
<Card key={booking.id}>
|
|
124
|
+
<BodyText>{booking.event_name || t('Event')}</BodyText>
|
|
125
|
+
<BodyText>{t(booking.status)}</BodyText>
|
|
126
|
+
{booking.can_cancel && (
|
|
127
|
+
<Button variant="ghost" onPress={() => flow.cancel(booking)}>
|
|
128
|
+
{t('Cancel booking')}
|
|
129
|
+
</Button>
|
|
130
|
+
)}
|
|
131
|
+
</Card>
|
|
132
|
+
))}
|
|
133
|
+
{state.after && (
|
|
134
|
+
<Button variant="ghost" onPress={() => flow.bookingPage()}>
|
|
135
|
+
{t('First bookings')}
|
|
136
|
+
</Button>
|
|
137
|
+
)}
|
|
138
|
+
{flow.bookings.data?.next_cursor && (
|
|
139
|
+
<Button onPress={() => flow.bookingPage(flow.bookings.data!.next_cursor!)}>
|
|
140
|
+
{t('More bookings')}
|
|
141
|
+
</Button>
|
|
142
|
+
)}
|
|
143
|
+
<SectionHeader title={t('Choose an event')} />
|
|
144
|
+
<Input
|
|
145
|
+
value={state.search}
|
|
146
|
+
onChangeText={flow.search}
|
|
147
|
+
placeholder={t('Search events')}
|
|
148
|
+
/>
|
|
149
|
+
{flow.events.isFetching && <Loader />}
|
|
150
|
+
{flow.events.data?.events.length === 0 && (
|
|
151
|
+
<BodyText>{t('No eligible events found.')}</BodyText>
|
|
152
|
+
)}
|
|
153
|
+
{flow.events.data?.events.map((event) => (
|
|
154
|
+
<Card key={event.event_id}>
|
|
155
|
+
<SectionHeader title={event.name} />
|
|
156
|
+
<BodyText>{event.start_date || ''}</BodyText>
|
|
157
|
+
<BodyText>{event.ticket_type_name || ''}</BodyText>
|
|
158
|
+
{event.available && pass?.status === 'active' && pass.credits_remaining > 0 ? (
|
|
159
|
+
<Button onPress={() => flow.choose(event)}>{t('Use one credit')}</Button>
|
|
160
|
+
) : (
|
|
161
|
+
<BodyText>{t('Not available')}</BodyText>
|
|
162
|
+
)}
|
|
163
|
+
</Card>
|
|
164
|
+
))}
|
|
165
|
+
{state.eventPage > 1 && (
|
|
166
|
+
<Button variant="ghost" onPress={() => flow.eventPage(state.eventPage - 1)}>
|
|
167
|
+
{t('Previous events')}
|
|
168
|
+
</Button>
|
|
169
|
+
)}
|
|
170
|
+
{flow.events.data?.meta.has_more && (
|
|
171
|
+
<Button onPress={() => flow.eventPage(state.eventPage + 1)}>
|
|
172
|
+
{t('More events')}
|
|
173
|
+
</Button>
|
|
174
|
+
)}
|
|
175
|
+
</View>
|
|
176
|
+
)
|
|
177
|
+
}
|