tf-checkout-react-native 2.0.0-beta.18 → 2.0.0-beta.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tf-checkout-react-native",
3
- "version": "2.0.0-beta.18",
3
+ "version": "2.0.0-beta.19",
4
4
  "description": "TheTicketFairy ReactNative Checkout library",
5
5
  "homepage": "https://github.com/theticketfairy/tf-shared-libraries#readme",
6
6
  "author": "The Ticket Fairy",
@@ -116,7 +116,20 @@ export const Tickets: FC<TicketsProps> = ({
116
116
  const checkout = useCheckoutContext()
117
117
  const theme = useTheme()
118
118
  const s = useTicketStyles(theme)
119
- const ticketsQuery = hooks.useTicketsWithMeta(eventId)
119
+ const catalog = hooks.useTicketCatalog(eventId, timeSlotDates)
120
+ const {
121
+ ticketsQuery,
122
+ eventInfo,
123
+ slotQuery,
124
+ isTimeSlot,
125
+ dates,
126
+ slotDate,
127
+ setSlotDate,
128
+ qty,
129
+ setQty,
130
+ refetchCatalog,
131
+ current: catalogCurrent,
132
+ } = catalog
120
133
  const waitingBuyer = hooks.useAuthSession()
121
134
  const cachedProfile = hooks.useProfile({ enabled: false }).data
122
135
  const waitingPrefill = waitingListProfilePrefill(
@@ -132,42 +145,17 @@ export const Tickets: FC<TicketsProps> = ({
132
145
  const bundleStarting = bundleWait !== null
133
146
  const promoMeta = ticketsQuery.data?.promo
134
147
 
135
- const [qty, setQty] = useState<Record<string, number>>({})
136
- const [slotDate, setSlotDate] = useState('')
137
-
138
- // Time-slot / calendar events: pick a date, then buy from that session's slots.
139
- const eventInfo = hooks.useEvent(eventId).data
140
- const isTimeSlot = Boolean(timeSlotDates?.length || eventInfo?.isTimeSlotEvent)
141
- const dates = useMemo(
142
- () => timeSlotDates ?? eventInfo?.availableDates ?? [],
143
- [timeSlotDates, eventInfo?.availableDates]
144
- )
145
- useEffect(() => {
146
- if (isTimeSlot && !slotDate && dates.length) setSlotDate(dates[0])
147
- }, [isTimeSlot, slotDate, dates])
148
- const slotQuery = hooks.useTimeSlotTickets(eventId, isTimeSlot ? slotDate : undefined)
149
- const refetchCatalog = () =>
150
- Promise.all([
151
- ticketsQuery.refetch(),
152
- ...(isTimeSlot && slotDate ? [slotQuery.refetch()] : []),
153
- ])
154
-
155
- const tickets = useMemo(
156
- () => (isTimeSlot ? (slotQuery.data ?? []) : (ticketsQuery.data?.tickets ?? [])),
157
- [isTimeSlot, slotQuery.data, ticketsQuery.data?.tickets]
148
+ const tickets = catalog.tickets
149
+ const [error, updateError] = hooks.useScopedState(catalog.scope, '')
150
+ const setError = (message: string) => {
151
+ if (catalogCurrent()) updateError(message)
152
+ }
153
+ const [code, setCode] = hooks.useScopedState(catalog.scope, '')
154
+ const [codeState, setCodeState] = hooks.useScopedState<'idle' | 'busy'>(
155
+ catalog.scope,
156
+ 'idle'
158
157
  )
159
- const [error, setError] = useState('')
160
- const [code, setCode] = useState('')
161
- const [codeState, setCodeState] = useState<'idle' | 'busy'>('idle')
162
- const [promoOpen, setPromoOpen] = useState(false)
163
- // Another event: its tickets, date and code start fresh.
164
- hooks.useOnEventChange(eventId, () => {
165
- setQty({})
166
- setSlotDate('')
167
- setCode('')
168
- setPromoOpen(false)
169
- setError('')
170
- })
158
+ const [promoOpen, setPromoOpen] = hooks.useScopedState(catalog.scope, false)
171
159
  const [visibleDescription, setVisibleDescription] = useState<string | null>(null)
172
160
  const attributionEvent = useRef<string | null>(null)
173
161
 
@@ -175,7 +163,12 @@ export const Tickets: FC<TicketsProps> = ({
175
163
  // per event. Explicit props win. URL reading (web-only) is intentionally
176
164
  // skipped on native — `readAttributionFromUrl` is accepted for prop parity.
177
165
  useEffect(() => {
178
- if (attributionEvent.current === eventId) return
166
+ if (
167
+ !catalogCurrent() ||
168
+ !catalog.attributionReady ||
169
+ attributionEvent.current === eventId
170
+ )
171
+ return
179
172
  attributionEvent.current = eventId
180
173
 
181
174
  const promo = initialPromoCode?.trim() ?? ''
@@ -192,13 +185,27 @@ export const Tickets: FC<TicketsProps> = ({
192
185
  .applyPromoCode(eventId, promo)
193
186
  .catch(() => undefined)
194
187
  .finally(() => {
195
- refetchCatalog().finally(() => setCodeState('idle'))
188
+ if (!catalogCurrent()) return
189
+ return refetchCatalog()
190
+ .catch((e) => {
191
+ if (catalogCurrent()) setError(errorMessage(e))
192
+ })
193
+ .finally(() => {
194
+ if (catalogCurrent()) setCodeState('idle')
195
+ })
196
196
  })
197
197
  }
198
198
  // Checkout actions and query functions are stable; this effect is scoped to
199
199
  // one attribution pass per event.
200
200
  // eslint-disable-next-line react-hooks/exhaustive-deps
201
- }, [eventId, initialPromoCode, referrer, readAttributionFromUrl])
201
+ }, [
202
+ eventId,
203
+ initialPromoCode,
204
+ referrer,
205
+ readAttributionFromUrl,
206
+ catalog.instance,
207
+ catalog.attributionReady,
208
+ ])
202
209
 
203
210
  // Seed the currency state once so a multi-currency event can offer a switcher
204
211
  // on the ticket screen (before the cart is primed). No-op for single-currency.
@@ -211,9 +218,11 @@ export const Tickets: FC<TicketsProps> = ({
211
218
  checkout
212
219
  .syncCurrency(eventId, nextCode)
213
220
  .then((res) => {
214
- if (res.requiresCatalogRefresh) {
221
+ if (catalogCurrent() && res.requiresCatalogRefresh) {
215
222
  setQty({})
216
- refetchCatalog()
223
+ refetchCatalog().catch((e) => {
224
+ if (catalogCurrent()) setError(errorMessage(e))
225
+ })
217
226
  }
218
227
  })
219
228
  .catch(() => undefined)
@@ -259,7 +268,9 @@ export const Tickets: FC<TicketsProps> = ({
259
268
  : undefined
260
269
 
261
270
  const prime = () => {
271
+ if (!catalogCurrent() || catalog.busy || codeState === 'busy' || catalog.error) return
262
272
  setError('')
273
+ const continuationCurrent = catalog.capturePrimeContinuation()
263
274
  const selections: TicketSelection[] = selected.map((t) => ({
264
275
  id: t.id,
265
276
  name: t.name,
@@ -272,7 +283,9 @@ export const Tickets: FC<TicketsProps> = ({
272
283
  // payment SDK nothing is primed, and the error names that flow.
273
284
  checkout
274
285
  .setTickets(eventId, selections)
275
- .then(() => onContinue?.())
286
+ .then(() => {
287
+ if (continuationCurrent()) onContinue?.()
288
+ })
276
289
  .catch((e) =>
277
290
  setError(
278
291
  e instanceof ConfirmerBusyError
@@ -283,19 +296,34 @@ export const Tickets: FC<TicketsProps> = ({
283
296
  }
284
297
 
285
298
  const applyCode = () => {
286
- if (!code.trim()) return
299
+ if (
300
+ !catalogCurrent() ||
301
+ !catalog.attributionReady ||
302
+ catalog.busy ||
303
+ codeState === 'busy' ||
304
+ !code.trim()
305
+ )
306
+ return
307
+ setError('')
287
308
  setCodeState('busy')
288
309
  checkout
289
310
  .applyPromoCode(eventId, code.trim())
290
311
  .catch(() => undefined)
291
312
  .finally(() => {
292
- refetchCatalog().finally(() => setCodeState('idle'))
313
+ if (!catalogCurrent()) return
314
+ return refetchCatalog()
315
+ .catch((e) => {
316
+ if (catalogCurrent()) setError(errorMessage(e))
317
+ })
318
+ .finally(() => {
319
+ if (catalogCurrent()) setCodeState('idle')
320
+ })
293
321
  })
294
322
  }
295
323
 
296
324
  const isAccess = promoMeta?.isAccessCodeEnabled
297
325
  const showCodeInput = promoMeta?.isPromotionsEnabled || isAccess
298
- const listQuery = isTimeSlot ? slotQuery : ticketsQuery
326
+ const listQuery = catalog
299
327
  const currency = checkout.currency
300
328
 
301
329
  const renderRow = (t: TicketType) => {
@@ -416,7 +444,7 @@ export const Tickets: FC<TicketsProps> = ({
416
444
  // Time-slot layout groups the date's tickets under their time heading.
417
445
  const timeGroups: { time: string; items: TicketType[] }[] = []
418
446
  if (isTimeSlot)
419
- for (const t of tickets) {
447
+ for (const t of catalog.datedTickets) {
420
448
  const time = t.timeSlot?.time ?? ''
421
449
  const g = timeGroups.find((x) => x.time === time)
422
450
  if (g) g.items.push(t)
@@ -452,7 +480,6 @@ export const Tickets: FC<TicketsProps> = ({
452
480
  value={slotDate}
453
481
  onChange={(d) => {
454
482
  setSlotDate(d)
455
- setQty({})
456
483
  }}
457
484
  options={dates.map((d) => ({ label: d, value: d }))}
458
485
  />
@@ -473,7 +500,7 @@ export const Tickets: FC<TicketsProps> = ({
473
500
  </View>
474
501
  ) : null}
475
502
 
476
- {isTimeSlot && !slotQuery.isLoading && tickets.length === 0 && slotDate ? (
503
+ {isTimeSlot && !slotQuery.isLoading && !slotQuery.data?.length && slotDate ? (
477
504
  <MutedText>No sessions available on this date.</MutedText>
478
505
  ) : null}
479
506
 
@@ -486,6 +513,13 @@ export const Tickets: FC<TicketsProps> = ({
486
513
  ))
487
514
  : tickets.map(renderRow)}
488
515
 
516
+ {isTimeSlot && catalog.regularTickets.length > 0 ? (
517
+ <>
518
+ <Text style={s.slotTime}>{createTranslator()('tickets.getTickets')}</Text>
519
+ {catalog.regularTickets.map(renderRow)}
520
+ </>
521
+ ) : null}
522
+
489
523
  {/* Promo / access code */}
490
524
  {showCodeInput ? (
491
525
  <View style={s.promo}>
@@ -511,7 +545,12 @@ export const Tickets: FC<TicketsProps> = ({
511
545
  variant="ghost"
512
546
  onPress={applyCode}
513
547
  loading={codeState === 'busy'}
514
- disabled={codeState === 'busy' || !code.trim()}
548
+ disabled={
549
+ !catalog.attributionReady ||
550
+ catalog.busy ||
551
+ codeState === 'busy' ||
552
+ !code.trim()
553
+ }
515
554
  >
516
555
  {isAccess ? 'ENTER' : 'APPLY'}
517
556
  </Button>
@@ -541,6 +580,9 @@ export const Tickets: FC<TicketsProps> = ({
541
580
  checkout.orderFixed ||
542
581
  checkout.isBusy ||
543
582
  bundleStarting ||
583
+ catalog.busy ||
584
+ codeState === 'busy' ||
585
+ !!catalog.error ||
544
586
  selected.length === 0,
545
587
  title: continueTitle,
546
588
  })
@@ -548,7 +590,14 @@ export const Tickets: FC<TicketsProps> = ({
548
590
  <Button
549
591
  onPress={prime}
550
592
  loading={checkout.isBusy}
551
- disabled={checkout.isBusy || bundleStarting || selected.length === 0}
593
+ disabled={
594
+ checkout.isBusy ||
595
+ bundleStarting ||
596
+ catalog.busy ||
597
+ codeState === 'busy' ||
598
+ !!catalog.error ||
599
+ selected.length === 0
600
+ }
552
601
  >
553
602
  {checkout.isBusy
554
603
  ? 'Reserving…'