tf-checkout-react 1.4.18 → 1.4.20

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.
@@ -140,10 +140,7 @@ export const TicketsSection = (
140
140
  <Button
141
141
  className="ticket-delete"
142
142
  onClick={() => {
143
- handleCancelReservation(
144
- dropdownData.seatId,
145
- dropdownData.tierId
146
- )
143
+ handleCancelReservation(dropdownData.seatId, dropdownData.tierId)
147
144
  }}
148
145
  >
149
146
  {ticketDeleteButtonContent}
@@ -166,7 +163,9 @@ export const TicketsSection = (
166
163
  {`${currencySymbol} ${finalPrice}`}
167
164
  </span>
168
165
  <span className="fees">
169
- {selectedTicketData.fee_included ? ' (incl. Fees)' : ' (excl. Fees)'}
166
+ {selectedTicketData.fee_included
167
+ ? ' (incl. Fees)'
168
+ : ' (excl. Fees)'}
170
169
  </span>
171
170
  </div>
172
171
  {!_isEmpty(selectedTicketData.ticket_type_deposit) && (
@@ -190,7 +189,7 @@ export const TicketsSection = (
190
189
  {showDescription && (
191
190
  <Tooltip
192
191
  title="View Ticket Info"
193
- placement='left'
192
+ placement="left"
194
193
  arrow
195
194
  componentsProps={{
196
195
  tooltip: {
@@ -201,7 +200,7 @@ export const TicketsSection = (
201
200
  color: 'common.black',
202
201
  },
203
202
  },
204
- }
203
+ },
205
204
  }}
206
205
  >
207
206
  <div
@@ -251,12 +250,14 @@ export const TicketsSection = (
251
250
  )}
252
251
  </div>
253
252
  </div>
254
- {selectedTicketsInfo[dropdownData.seatId] &&
253
+ {selectedTicketsInfo[dropdownData.seatId] && (
255
254
  <div
256
255
  className="ticket-description-content"
257
- dangerouslySetInnerHTML={createMarkup(selectedTicketData.description || "")}
256
+ dangerouslySetInnerHTML={createMarkup(
257
+ selectedTicketData.description || ''
258
+ )}
258
259
  />
259
- }
260
+ )}
260
261
  </>
261
262
  )}
262
263
  </div>
@@ -1,15 +1,16 @@
1
1
  import { Alert } from '@mui/material'
2
2
  import axios from 'axios'
3
- import { identity } from 'lodash'
4
3
  import _find from 'lodash/find'
5
4
  import _forEach from 'lodash/forEach'
6
5
  import _get from 'lodash/get'
6
+ import _identity from 'lodash/identity'
7
7
  import _isEmpty from 'lodash/isEmpty'
8
+ import _isEqual from 'lodash/isEqual'
8
9
  import _keys from 'lodash/keys'
9
10
  import _map from 'lodash/map'
10
11
  import _values from 'lodash/values'
11
12
  import moment from 'moment'
12
- import React, { useCallback, useEffect, useRef,useState } from 'react'
13
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
13
14
  import Countdown from 'react-countdown'
14
15
 
15
16
  import {
@@ -44,8 +45,8 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
44
45
  },
45
46
  mapContainerId,
46
47
  timerMessage = '',
47
- onAddToCartSuccess = identity,
48
- onCountdownFinish = identity,
48
+ onAddToCartSuccess = _identity,
49
+ onCountdownFinish = _identity,
49
50
  ticketDeleteButtonContent,
50
51
  ticketInfoContent,
51
52
  } = props
@@ -65,20 +66,16 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
65
66
  [seatId: string]: string;
66
67
  }>({})
67
68
  const [seatMapStatuses, setSeatMapStatuses] = useState('')
68
- const [reservedSeats, setReservedSeats] = useState<
69
- Array<SeatReservationData>
70
- >([])
69
+ const [reservedSeats, setReservedSeats] = useState<Array<SeatReservationData>>([])
71
70
  const [isLoadingSeatMapData, setIsLoadingSeatMapData] = useState(true)
72
- const [isLoadingStatuses, setIsLoadingStatuses] = useState(true)
71
+ const [isLoadingStatuses, setIsLoadingStatuses] = useState(false)
73
72
  const [isReserving, setIsReserving] = useState(false)
74
73
  const [isAddingToCart, setIsAddingToCart] = useState(false)
75
74
  const [error, setError] = useState<string | null>(null)
76
75
  const [showTimer, setShowTimer] = useState(
77
76
  Date.now() <= Number(localStorage.getItem(`reservationStart-${eventId}`))
78
77
  )
79
- const [guestCounts, setGuestCounts] = useState<IGuestCounts>(
80
- {} as IGuestCounts
81
- )
78
+ const [guestCounts, setGuestCounts] = useState<IGuestCounts>({} as IGuestCounts)
82
79
  const isGuestCountsSet = useRef(false)
83
80
 
84
81
  const updateGuestCounts = (data: any) => {
@@ -87,9 +84,7 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
87
84
  const seatTicketsArray = _values(tierTickets)
88
85
 
89
86
  setGuestCounts((prevState: any) => ({
90
- [item.seatId]: Number(
91
- seatTicketsArray[0].ticket_type_min_number_of_guests
92
- ),
87
+ [item.seatId]: Number(seatTicketsArray[0].ticket_type_min_number_of_guests),
93
88
  ...prevState,
94
89
  }))
95
90
  })
@@ -132,14 +127,12 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
132
127
  const statusesResponse = await getSeatMapStatuses(eventId)
133
128
  const statuses = _get(statusesResponse, 'data.attributes') || {}
134
129
  const reservationData: Array<SeatReservationData> = []
135
- const ownReservations: string[] = getOwnReservationsBasedOnStatuses(
136
- statuses
137
- )
130
+ const ownReservations: string[] = getOwnReservationsBasedOnStatuses(statuses)
138
131
 
139
132
  _forEach(ownReservations, reservation => {
140
133
  const tierIdOfReservation = getTierIdBasedOnSeatId(
141
134
  reservation,
142
- eventSeatsRef.current,
135
+ eventSeatsRef.current
143
136
  ) as string
144
137
  reservationData.push({
145
138
  seatId: reservation,
@@ -149,10 +142,19 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
149
142
  })
150
143
 
151
144
  localStorage.setItem('reservationData', JSON.stringify(reservationData))
152
- setSeatMapStatuses(statusesResponse.data.attributes)
153
- setReservedSeats(reservationData)
145
+
146
+ if (!_isEqual(seatMapStatuses, statusesResponse.data.attributes)) {
147
+ setSeatMapStatuses(statusesResponse.data.attributes)
148
+ }
149
+ if (!_isEqual(reservationData, reservedSeats)) {
150
+ setReservedSeats(reservationData)
151
+ }
152
+
154
153
  // automatically set ticket/table type if it's the only one
155
- if (ticketTypeTierRelationsRef.current) {
154
+ if (
155
+ ticketTypeTierRelationsRef.current &&
156
+ !_isEqual(reservationData, reservedSeats)
157
+ ) {
156
158
  if (!isGuestCountsSet.current) {
157
159
  updateGuestCounts(reservationData)
158
160
  isGuestCountsSet.current = true
@@ -164,9 +166,7 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
164
166
  setSelectedTickets((prevState: any) => ({
165
167
  ...prevState,
166
168
  [item.seatId]:
167
- seatTicketsArray.length === 1
168
- ? seatTicketsArray[0].ticket_type_id
169
- : '',
169
+ seatTicketsArray.length === 1 ? seatTicketsArray[0].ticket_type_id : '',
170
170
  }))
171
171
  })
172
172
 
@@ -177,26 +177,24 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
177
177
  }
178
178
  } catch (error) {
179
179
  setError('Something went wrong')
180
- } finally {
181
- setIsLoadingStatuses(false)
182
180
  }
183
- }, [eventId])
181
+ }, [eventId, seatMapStatuses, reservedSeats])
184
182
 
185
- const startTimer = useCallback((duration: number) => {
186
- setShowTimer(true)
183
+ const startTimer = useCallback(
184
+ (duration: number) => {
185
+ setShowTimer(true)
187
186
 
188
- if (!localStorage.getItem(`reservationStart-${eventId}`)) {
189
- localStorage.setItem(
190
- `reservationStart-${eventId}`,
191
- String(Date.now() + duration)
192
- )
193
- }
194
- }, [])
187
+ if (!localStorage.getItem(`reservationStart-${eventId}`)) {
188
+ localStorage.setItem(`reservationStart-${eventId}`, String(Date.now() + duration))
189
+ }
190
+ },
191
+ [eventId]
192
+ )
195
193
 
196
194
  const endTimer = useCallback(() => {
197
195
  localStorage.removeItem(`reservationStart-${eventId}`)
198
196
  setShowTimer(false)
199
- }, [])
197
+ }, [eventId])
200
198
 
201
199
  const handleSeatReservation = async (
202
200
  eventId: string,
@@ -209,9 +207,7 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
209
207
  await fetchSeatMapReservations()
210
208
 
211
209
  startTimer(seatMapData.seatReservationTime * 60000)
212
- const reservationData = JSON.parse(
213
- localStorage.getItem('reservationData') || ''
214
- )
210
+ const reservationData = JSON.parse(localStorage.getItem('reservationData') || '')
215
211
  setReservedSeats(reservationData)
216
212
  updateGuestCounts(reservationData)
217
213
 
@@ -220,9 +216,11 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
220
216
  const [firstItem] = _keys(ticketTypeTierRelationsRef.current[tierId])
221
217
  handleTicketSelect(relations.length === 1 ? firstItem : '', seatId)
222
218
  } catch (error) {
223
- setError((error as any)?.response?.data?.message === 'Selected seat is not available'
224
- ? 'No more of this ticket type are available right now - they’re either sold out or in people’s shopping carts. Try refreshing the page!'
225
- : 'Something went wrong'
219
+ setError(
220
+ (error as any)?.response?.data?.message === 'Selected seat is not available'
221
+ ? // eslint-disable-next-line max-len
222
+ 'No more of this ticket type are available right now - they’re either sold out or in people’s shopping carts. Try refreshing the page!'
223
+ : 'Something went wrong'
226
224
  )
227
225
  } finally {
228
226
  setIsReserving(false)
@@ -238,14 +236,10 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
238
236
  try {
239
237
  await removeSeatReserve(eventId, tierId, [seatId])
240
238
  await fetchSeatMapReservations()
241
- if (
242
- _isEmpty(JSON.parse(localStorage.getItem('reservationData') as string))
243
- ) {
239
+ if (_isEmpty(JSON.parse(localStorage.getItem('reservationData') as string))) {
244
240
  endTimer()
245
241
  }
246
- const reservationData = JSON.parse(
247
- localStorage.getItem('reservationData') || ''
248
- )
242
+ const reservationData = JSON.parse(localStorage.getItem('reservationData') || '')
249
243
  const currentSelectedTickets = { ...selectedTickets }
250
244
  delete currentSelectedTickets[seatId]
251
245
  setReservedSeats(reservationData)
@@ -262,10 +256,9 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
262
256
  seat: { tierId, seatId, status },
263
257
  } = seatInfo
264
258
 
265
- const reservationData = JSON.parse(
266
- localStorage.getItem('reservationData') || '[]'
267
- )
268
- if (status === 'B' || status === 'R' || status === 'BLOCKED' || status === 'RE') return
259
+ const reservationData = JSON.parse(localStorage.getItem('reservationData') || '[]')
260
+ if (status === 'B' || status === 'R' || status === 'BLOCKED' || status === 'RE')
261
+ return
269
262
  if (_find(reservationData, data => data.seatId === seatId)) {
270
263
  handleCancelSeatReservtion(eventId, tierId, seatId)
271
264
  } else if (reservationData.length >= 10) {
@@ -320,19 +313,20 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
320
313
  }
321
314
 
322
315
  useEffect(() => {
323
- if (
324
- Date.now() > Number(localStorage.getItem(`reservationStart-${eventId}`))
325
- ) {
316
+ if (Date.now() > Number(localStorage.getItem(`reservationStart-${eventId}`))) {
326
317
  setSelectedTickets({})
327
318
  setReservedSeats([])
328
319
  }
329
320
 
330
321
  const makeRequests = async () => {
331
322
  try {
323
+ setIsLoadingStatuses(true)
332
324
  await fetchSeatMapData()
333
325
  await fetchSeatMapReservations()
334
326
  } catch (error) {
335
327
  setError('Something went wrong')
328
+ } finally {
329
+ setIsLoadingStatuses(false)
336
330
  }
337
331
  }
338
332
 
@@ -341,7 +335,9 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
341
335
  if (country) {
342
336
  localStorage.setItem('eventCountry', country)
343
337
  }
338
+ }, [country, eventId, fetchSeatMapData])
344
339
 
340
+ useEffect(() => {
345
341
  const intervalId = setInterval(() => {
346
342
  fetchSeatMapReservations()
347
343
  }, 3000)
@@ -349,7 +345,7 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
349
345
  return () => {
350
346
  clearInterval(intervalId)
351
347
  }
352
- }, [fetchSeatMapData, fetchSeatMapReservations])
348
+ }, [fetchSeatMapReservations])
353
349
 
354
350
  return (
355
351
  <>
@@ -406,21 +402,20 @@ export const SeatMapContainer = (props: IMapContainerProps) => {
406
402
  guestCounts={guestCounts}
407
403
  setGuestCounts={setGuestCounts}
408
404
  />
409
- {seatMapData.seatMap && seatMapStatuses && (
410
- <SeatMapComponent
411
- seatMapProps={{
412
- seatData: seatMapData.seatMap,
413
- statuses: seatMapStatuses,
414
- tierPrices: seatMapData.tierPrices,
415
- seatMapType: seatMapData.seatMapType,
416
- ticketTypeTierRelations: ticketTypeTierRelationsRef.current,
417
- seatMapEvents: { onSeatClick },
418
- isReserving,
419
- predefinedSeats: seatMapData.predefinedSeats,
420
- }}
421
- mapContainerId={mapContainerId}
422
- />
423
- )}
405
+ <SeatMapComponent
406
+ seatMapProps={{
407
+ seatData: seatMapData.seatMap,
408
+ statuses: seatMapStatuses,
409
+ tierPrices: seatMapData.tierPrices,
410
+ seatMapType: seatMapData.seatMapType,
411
+ ticketTypeTierRelations: ticketTypeTierRelationsRef.current,
412
+ seatMapEvents: { onSeatClick },
413
+ isReserving,
414
+ predefinedSeats: seatMapData.predefinedSeats,
415
+ isLoadingSeatMapData,
416
+ }}
417
+ mapContainerId={mapContainerId}
418
+ />
424
419
  </>
425
420
  )
426
421
  }
@@ -39,7 +39,7 @@ const options: StripeCardNumberElementOptions = {
39
39
  export interface ICheckoutForm {
40
40
  total: string;
41
41
  currency: string;
42
- onSubmit: (error: any) => Promise<any>;
42
+ onSubmit: (error: any, data?: object) => Promise<any>;
43
43
  error?: string | null;
44
44
  stripeCardOptions?: StripeCardNumberElementOptions;
45
45
  stripe_client_secret: string;
@@ -49,6 +49,7 @@ export interface ICheckoutForm {
49
49
  conditions: any;
50
50
  disableZipSection: boolean;
51
51
  paymentButtonText?: string;
52
+ forPaymentPlan?: boolean;
52
53
  }
53
54
 
54
55
  interface AddressTypes {
@@ -67,10 +68,11 @@ const CheckoutForm = ({
67
68
  currency,
68
69
  billing_info,
69
70
  isLoading = false,
70
- handleSetLoading = () => {},
71
+ handleSetLoading = _identity,
71
72
  conditions = [],
72
73
  disableZipSection,
73
74
  paymentButtonText,
75
+ forPaymentPlan = false,
74
76
  }: ICheckoutForm) => {
75
77
  const stripe = useStripe()
76
78
  const elements = useElements()
@@ -109,6 +111,14 @@ const CheckoutForm = ({
109
111
  address.postal_code = postalCode
110
112
  }
111
113
 
114
+ if (forPaymentPlan) {
115
+ const setupResponse = await stripe.confirmCardSetup(stripe_client_secret, {
116
+ payment_method: { card: card || { token: '' } },
117
+ })
118
+ onSubmit(null, { paymentMethodId: setupResponse.setupIntent?.payment_method })
119
+ return
120
+ }
121
+
112
122
  const paymentMethodReq = await stripe.createPaymentMethod({
113
123
  type: 'card',
114
124
  card: card || { token: '' },
@@ -158,9 +168,7 @@ const CheckoutForm = ({
158
168
 
159
169
  useEffect(() => {
160
170
  if (typeof window !== 'undefined') {
161
- const userData = JSON.parse(
162
- window.localStorage.getItem('user_data') || ''
163
- )
171
+ const userData = JSON.parse(window.localStorage.getItem('user_data') || '')
164
172
  const zipCode = _get(userData, 'zip', '')
165
173
  zipCode && setPostalCode(zipCode)
166
174
  }
@@ -185,9 +193,7 @@ const CheckoutForm = ({
185
193
 
186
194
  return (
187
195
  <div className="stripe_payment_container">
188
- {!!stripeError && (
189
- <div className="checkout_error_block">{stripeError}</div>
190
- )}
196
+ {!!stripeError && <div className="checkout_error_block">{stripeError}</div>}
191
197
  <form onSubmit={handleSubmit}>
192
198
  <div className="card_form_inner">
193
199
  <div className="card_number_element">
@@ -203,9 +209,7 @@ const CheckoutForm = ({
203
209
  <div className="elements">
204
210
  <div className="expiration_element">
205
211
  <span className="card_label_text">Expiration date</span>
206
- <CardExpiryElement
207
- options={{ ...options, ...stripeCardOptions }}
208
- />
212
+ <CardExpiryElement options={{ ...options, ...stripeCardOptions }} />
209
213
  </div>
210
214
  <div className="cvc_element">
211
215
  <span className="card_label_text">CVC</span>
@@ -225,10 +229,7 @@ const CheckoutForm = ({
225
229
  )}
226
230
  </div>
227
231
  {checkboxes?.map((checkbox: any) => (
228
- <div
229
- className={'billing-info-container__singleField'}
230
- key={checkbox.id}
231
- >
232
+ <div className={'billing-info-container__singleField'} key={checkbox.id}>
232
233
  <div className="conditions-block">
233
234
  <CheckboxField
234
235
  name={checkbox.id}
@@ -241,13 +242,13 @@ const CheckoutForm = ({
241
242
  </div>
242
243
  ))}
243
244
  <div
244
- className={`payment_button ${
245
- buttonIsDiabled ? 'disabled-payment-button' : ''
246
- }`}
245
+ className={`payment_button ${buttonIsDiabled ? 'disabled-payment-button' : ''}`}
247
246
  >
248
247
  <button disabled={buttonIsDiabled} type="submit">
249
248
  {isLoading ? (
250
249
  <CircularProgress size={26} />
250
+ ) : forPaymentPlan ? (
251
+ 'Confirm Payment Plan'
251
252
  ) : (
252
253
  `${
253
254
  paymentButtonText ? paymentButtonText : 'Pay'
@@ -0,0 +1,19 @@
1
+ export interface IPaymentPlanConfig {
2
+ requires_deposit: boolean;
3
+ deposit: number;
4
+ interval: number;
5
+ non_refundable_type: string | null;
6
+ non_refundable_amount: number;
7
+ has_admin_fee: boolean;
8
+ admin_fee: number;
9
+ total_installments: number;
10
+ price_per_installment: number;
11
+ total: number;
12
+ stripe_setup_intent_secret: string;
13
+ saved_card: IPaymentPlanConfigCard;
14
+ }
15
+
16
+ export interface IPaymentPlanConfigCard {
17
+ last_4_digits: string | null;
18
+ stripe_payment_method_id: string | null;
19
+ }
@@ -26,6 +26,7 @@ interface ISeatMapContainerProps {
26
26
  tierPrices: any;
27
27
  isReserving: boolean;
28
28
  predefinedSeats: any;
29
+ isLoadingSeatMapData: boolean;
29
30
  };
30
31
  loading?: boolean;
31
32
  mapContainerId?: string;