@eventlook/sdk 1.4.49 → 1.4.51

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 (56) hide show
  1. package/dist/cjs/form/PaymentOverviewBox.js +0 -6
  2. package/dist/cjs/form/PaymentOverviewBox.js.map +1 -1
  3. package/dist/cjs/form/TicketForm.js +1 -1
  4. package/dist/cjs/form/TicketForm.js.map +1 -1
  5. package/dist/cjs/form/product/ProductCard.js +3 -2
  6. package/dist/cjs/form/product/ProductCard.js.map +1 -1
  7. package/dist/cjs/form/product/ProductVariantsDialog.js +23 -8
  8. package/dist/cjs/form/product/ProductVariantsDialog.js.map +1 -1
  9. package/dist/cjs/form/services/index.js +1 -18
  10. package/dist/cjs/form/services/index.js.map +1 -1
  11. package/dist/cjs/form/tickets/ReleaseWithMerchandise.js +30 -7
  12. package/dist/cjs/form/tickets/ReleaseWithMerchandise.js.map +1 -1
  13. package/dist/cjs/form/tickets/TicketSelection.js +37 -14
  14. package/dist/cjs/form/tickets/TicketSelection.js.map +1 -1
  15. package/dist/cjs/form/tickets/TicketSelectionMap.js +7 -1
  16. package/dist/cjs/form/tickets/TicketSelectionMap.js.map +1 -1
  17. package/dist/cjs/form/tickets/TicketSelectionMobile.js +17 -7
  18. package/dist/cjs/form/tickets/TicketSelectionMobile.js.map +1 -1
  19. package/dist/cjs/form/tickets/TicketWithMerchandiseSelection.js +30 -20
  20. package/dist/cjs/form/tickets/TicketWithMerchandiseSelection.js.map +1 -1
  21. package/dist/cjs/utils/data/ticket.js +6 -0
  22. package/dist/cjs/utils/data/ticket.js.map +1 -0
  23. package/dist/esm/form/PaymentOverviewBox.js +0 -6
  24. package/dist/esm/form/PaymentOverviewBox.js.map +1 -1
  25. package/dist/esm/form/TicketForm.js +1 -1
  26. package/dist/esm/form/TicketForm.js.map +1 -1
  27. package/dist/esm/form/product/ProductCard.js +3 -2
  28. package/dist/esm/form/product/ProductCard.js.map +1 -1
  29. package/dist/esm/form/product/ProductVariantsDialog.js +23 -8
  30. package/dist/esm/form/product/ProductVariantsDialog.js.map +1 -1
  31. package/dist/esm/form/services/index.js +2 -19
  32. package/dist/esm/form/services/index.js.map +1 -1
  33. package/dist/esm/form/tickets/ReleaseWithMerchandise.js +30 -7
  34. package/dist/esm/form/tickets/ReleaseWithMerchandise.js.map +1 -1
  35. package/dist/esm/form/tickets/TicketSelection.js +38 -15
  36. package/dist/esm/form/tickets/TicketSelection.js.map +1 -1
  37. package/dist/esm/form/tickets/TicketSelectionMap.js +7 -1
  38. package/dist/esm/form/tickets/TicketSelectionMap.js.map +1 -1
  39. package/dist/esm/form/tickets/TicketSelectionMobile.js +17 -7
  40. package/dist/esm/form/tickets/TicketSelectionMobile.js.map +1 -1
  41. package/dist/esm/form/tickets/TicketWithMerchandiseSelection.js +30 -20
  42. package/dist/esm/form/tickets/TicketWithMerchandiseSelection.js.map +1 -1
  43. package/dist/esm/utils/data/ticket.js +4 -0
  44. package/dist/esm/utils/data/ticket.js.map +1 -0
  45. package/package.json +1 -1
  46. package/src/form/PaymentOverviewBox.tsx +11 -11
  47. package/src/form/TicketForm.tsx +8 -6
  48. package/src/form/product/ProductCard.tsx +5 -2
  49. package/src/form/product/ProductVariantsDialog.tsx +29 -6
  50. package/src/form/services/index.tsx +36 -36
  51. package/src/form/tickets/ReleaseWithMerchandise.tsx +39 -8
  52. package/src/form/tickets/TicketSelection.tsx +50 -17
  53. package/src/form/tickets/TicketSelectionMap.tsx +9 -1
  54. package/src/form/tickets/TicketSelectionMobile.tsx +77 -67
  55. package/src/form/tickets/TicketWithMerchandiseSelection.tsx +49 -31
  56. package/src/utils/data/ticket.ts +1 -0
@@ -22,6 +22,7 @@ import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
22
22
  import { getSelectedQuantityByVariant } from '@utils/product';
23
23
  import useGlobal from '@hooks/useGlobal.ts';
24
24
  import { fCurrency } from '@utils/formatNumber';
25
+ import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
25
26
 
26
27
  interface Props {
27
28
  eventProduct: IEventProduct;
@@ -100,7 +101,7 @@ const ProductCard: React.FC<Props> = ({ eventProduct, eventId, isOnlyMerchandise
100
101
 
101
102
  const onChangeSimpleProductQuantity = (nextQuantity: number) => {
102
103
  if (!simpleVariant) return;
103
- if (nextQuantity < 0 || nextQuantity > 10) return;
104
+ if (nextQuantity < 0 || nextQuantity > MAX_TICKETS_PER_ORDER) return;
104
105
  if (
105
106
  nextQuantity > simpleProductQuantity &&
106
107
  nextQuantity - simpleProductQuantity > simpleProductMaxAvailable
@@ -153,7 +154,9 @@ const ProductCard: React.FC<Props> = ({ eventProduct, eventId, isOnlyMerchandise
153
154
  </Box>
154
155
  <IconButton
155
156
  onClick={() => onChangeSimpleProductQuantity(simpleProductQuantity + 1)}
156
- disabled={simpleProductQuantity >= 10 || simpleProductMaxAvailable <= 0}
157
+ disabled={
158
+ simpleProductQuantity >= MAX_TICKETS_PER_ORDER || simpleProductMaxAvailable <= 0
159
+ }
157
160
  sx={{
158
161
  flex: '1 1 0',
159
162
  height: 36,
@@ -15,6 +15,7 @@ import { useWatch } from 'react-hook-form';
15
15
  import { ITicketFormTicket } from '@utils/types/ticket.type';
16
16
  import { IEventProductForm } from '@utils/types/product.type';
17
17
  import { fCurrency } from '@utils/formatNumber';
18
+ import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
18
19
  import useGlobal from '@hooks/useGlobal.ts';
19
20
 
20
21
  interface Props {
@@ -26,6 +27,7 @@ interface Props {
26
27
  selectedQuantityByVariant?: Record<number, number>;
27
28
  isOnlyMerchandise?: boolean;
28
29
  canAddOnlyOneAtATime?: boolean;
30
+ maxSelectableQuantity?: number;
29
31
  }
30
32
 
31
33
  const ProductVariantsDialog: React.FC<Props> = ({
@@ -37,6 +39,7 @@ const ProductVariantsDialog: React.FC<Props> = ({
37
39
  isOnlyMerchandise,
38
40
  eventId,
39
41
  canAddOnlyOneAtATime,
42
+ maxSelectableQuantity,
40
43
  }) => {
41
44
  const { t, lang, options } = useGlobal();
42
45
  const { showSnackbar } = useGlobal();
@@ -53,8 +56,14 @@ const ProductVariantsDialog: React.FC<Props> = ({
53
56
  }, [openDialog]);
54
57
 
55
58
  useEffect(() => {
56
- if (openDialog) {
57
- setDraftQuantities(canAddOnlyOneAtATime ? {} : (selectedQuantityByVariant ?? {}));
59
+ if (openDialog && canAddOnlyOneAtATime) {
60
+ setDraftQuantities({});
61
+ }
62
+ }, [openDialog, canAddOnlyOneAtATime]);
63
+
64
+ useEffect(() => {
65
+ if (openDialog && !canAddOnlyOneAtATime) {
66
+ setDraftQuantities(selectedQuantityByVariant ?? {});
58
67
  }
59
68
  }, [openDialog, selectedQuantityByVariant, canAddOnlyOneAtATime]);
60
69
 
@@ -126,11 +135,18 @@ const ProductVariantsDialog: React.FC<Props> = ({
126
135
  const current = prev[variant.id] ?? 0;
127
136
  const next = current + 1;
128
137
  const maxAvailable = getMaxAvailable(variant);
129
- if (next > 10 || next > maxAvailable) return prev;
138
+
130
139
  if (canAddOnlyOneAtATime) {
131
- // Only allow one variant to be selected
140
+ if (maxAvailable < 1) return prev;
132
141
  return { [variant.id]: 1 };
133
142
  }
143
+
144
+ const maxSelectable =
145
+ maxSelectableQuantity === undefined
146
+ ? MAX_TICKETS_PER_ORDER
147
+ : Math.max(0, Math.min(maxSelectableQuantity, MAX_TICKETS_PER_ORDER));
148
+
149
+ if (next > maxSelectable || next > maxAvailable) return prev;
134
150
  return { ...prev, [variant.id]: next };
135
151
  });
136
152
  };
@@ -248,7 +264,14 @@ const ProductVariantsDialog: React.FC<Props> = ({
248
264
  {eventProduct.eventProductVariants.map((variant) => {
249
265
  const draftQty = draftQuantities[variant.id] ?? 0;
250
266
  const maxAvailable = getMaxAvailable(variant);
251
- const isAddDisabled = isVariantDisabled(variant) || maxAvailable <= 0;
267
+ const maxSelectable =
268
+ maxSelectableQuantity === undefined
269
+ ? MAX_TICKETS_PER_ORDER
270
+ : Math.max(0, Math.min(maxSelectableQuantity, MAX_TICKETS_PER_ORDER));
271
+ const isAddDisabled =
272
+ isVariantDisabled(variant) ||
273
+ maxAvailable <= 0 ||
274
+ (!canAddOnlyOneAtATime && maxSelectable <= 0);
252
275
  const isAnotherSelected =
253
276
  canAddOnlyOneAtATime &&
254
277
  Object.keys(draftQuantities).length > 0 &&
@@ -330,7 +353,7 @@ const ProductVariantsDialog: React.FC<Props> = ({
330
353
  variant="contained"
331
354
  onClick={() => handleAddQuantity(variant)}
332
355
  aria-label={t('components.product_variant_dialog.increase_quantity')}
333
- disabled={draftQty >= 10 || draftQty >= maxAvailable}
356
+ disabled={draftQty >= maxSelectable || draftQty >= maxAvailable}
334
357
  sx={{ minWidth: 0, height: 36, borderRadius: 1, fontWeight: 700 }}
335
358
  >
336
359
  +
@@ -138,18 +138,18 @@ const Services: React.FC<Props> = ({ event }) => {
138
138
 
139
139
  return (
140
140
  <Stack spacing={1}>
141
- <BorderedCheckbox
142
- name="ticketInsurance"
143
- disabled={false}
144
- title={t('event.tickets.insurance.label')}
145
- price={
146
- <>
147
- + {fCurrency(ticketInsurancePricePerUnit, lang, event.currency)} /{' '}
148
- {t('event.tickets.insurance.per_ticket')}
149
- </>
150
- }
151
- onChange={handleServiceChange}
152
- />
141
+ {/*<BorderedCheckbox*/}
142
+ {/* name="ticketInsurance"*/}
143
+ {/* disabled={false}*/}
144
+ {/* title={t('event.tickets.insurance.label')}*/}
145
+ {/* price={*/}
146
+ {/* <>*/}
147
+ {/* + {fCurrency(ticketInsurancePricePerUnit, lang, event.currency)} /{' '}*/}
148
+ {/* {t('event.tickets.insurance.per_ticket')}*/}
149
+ {/* </>*/}
150
+ {/* }*/}
151
+ {/* onChange={handleServiceChange}*/}
152
+ {/*/>*/}
153
153
 
154
154
  <BorderedCheckbox
155
155
  name="smsNotification"
@@ -199,30 +199,30 @@ const Services: React.FC<Props> = ({ event }) => {
199
199
  </DialogTitle>
200
200
  <DialogContent sx={{ px: { xs: 3, sm: 4 }, pb: { xs: 4, sm: 5 } }}>
201
201
  <Stack spacing={3}>
202
- <Box>
203
- <Typography variant="subtitle2" fontWeight={700} gutterBottom>
204
- {t('event.tickets.insurance.label')}
205
- </Typography>
206
- <Typography variant="body2" paragraph>
207
- {t('event.tickets.insurance.modal.description')}
208
- </Typography>
209
- <Typography variant="body2" paragraph>
210
- {t('event.tickets.insurance.modal.coverage')}
211
- </Typography>
212
- <BorderedCheckbox
213
- name="ticketInsurance"
214
- disabled={false}
215
- title={t('event.tickets.insurance.label')}
216
- price={
217
- <>
218
- + {fCurrency(ticketInsurancePricePerUnit, lang, event.currency)} /{' '}
219
- {t('event.tickets.insurance.per_ticket')}
220
- </>
221
- }
222
- onChange={handleServiceChange}
223
- />
224
- </Box>
225
- <Divider />
202
+ {/*<Box>*/}
203
+ {/* <Typography variant="subtitle2" fontWeight={700} gutterBottom>*/}
204
+ {/* {t('event.tickets.insurance.label')}*/}
205
+ {/* </Typography>*/}
206
+ {/* <Typography variant="body2" paragraph>*/}
207
+ {/* {t('event.tickets.insurance.modal.description')}*/}
208
+ {/* </Typography>*/}
209
+ {/* <Typography variant="body2" paragraph>*/}
210
+ {/* {t('event.tickets.insurance.modal.coverage')}*/}
211
+ {/* </Typography>*/}
212
+ {/* <BorderedCheckbox*/}
213
+ {/* name="ticketInsurance"*/}
214
+ {/* disabled={false}*/}
215
+ {/* title={t('event.tickets.insurance.label')}*/}
216
+ {/* price={*/}
217
+ {/* <>*/}
218
+ {/* + {fCurrency(ticketInsurancePricePerUnit, lang, event.currency)} /{' '}*/}
219
+ {/* {t('event.tickets.insurance.per_ticket')}*/}
220
+ {/* </>*/}
221
+ {/* }*/}
222
+ {/* onChange={handleServiceChange}*/}
223
+ {/* />*/}
224
+ {/*</Box>*/}
225
+ {/*<Divider />*/}
226
226
  <Box>
227
227
  <Typography variant="subtitle2" fontWeight={700} gutterBottom>
228
228
  {t('event.tickets.sms_notification.label')}
@@ -8,6 +8,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
8
8
  import { IEventProductForm } from '@utils/types/product.type';
9
9
  import { fCurrency } from '@utils/formatNumber';
10
10
  import { Currencies } from '@utils/data/currency';
11
+ import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
11
12
  import { getSelectedQuantityByVariant } from '@utils/product';
12
13
  import ReleaseExtraFields from '@form/extra-field/ReleaseExtraFields';
13
14
  import ReleaseDescription from '@form/tickets/ReleaseDescription';
@@ -43,10 +44,19 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
43
44
  const getSelectedQuantity = (id: number) =>
44
45
  tickets.find((ticket) => ticket.releaseId === id)?.quantity || 0;
45
46
 
47
+ const countSelectedTickets = () => {
48
+ let count = 0;
49
+ for (const ticket of tickets) {
50
+ count += Number(ticket.quantity || 0);
51
+ }
52
+
53
+ return count;
54
+ };
55
+
46
56
  const getAvailableTicketsForRelease = (release: ITicketFormTicket): number => {
47
57
  const selectedRelease = activeReleases?.find((item) => item.id === release.releaseId);
48
58
  const availableQuantity = selectedRelease ? selectedRelease.availableTickets : 0;
49
- return availableQuantity > 10 ? 10 : availableQuantity;
59
+ return availableQuantity > MAX_TICKETS_PER_ORDER ? MAX_TICKETS_PER_ORDER : availableQuantity;
50
60
  };
51
61
 
52
62
  const isMaxQuantity = (releaseId: number) => {
@@ -61,7 +71,17 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
61
71
  : productsToAdd
62
72
  ? [productsToAdd]
63
73
  : [];
64
- const quantity = normalizedProducts.length ? normalizedProducts.length : 1;
74
+ const requestedQuantity = normalizedProducts.length ? normalizedProducts.length : 1;
75
+ const releaseMaxQuantity = Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER);
76
+ const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
77
+ const quantity = Math.min(requestedQuantity, releaseMaxQuantity, remainingOrderCapacity);
78
+
79
+ if (quantity <= 0) {
80
+ setOpenVariantDialog(null);
81
+ return;
82
+ }
83
+
84
+ const selectedProducts = normalizedProducts.slice(0, quantity);
65
85
  const extraFields = release.extraFields?.length
66
86
  ? Array.from({ length: quantity }, () =>
67
87
  release.extraFields!.map((field) => ({
@@ -78,7 +98,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
78
98
  quantity,
79
99
  itemName: '',
80
100
  price: 0,
81
- products: normalizedProducts,
101
+ products: selectedProducts,
82
102
  extraFields,
83
103
  },
84
104
  ]);
@@ -95,9 +115,10 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
95
115
  if (addedRelease) {
96
116
  const increment = normalizedProducts.length ? normalizedProducts.length : 1;
97
117
  const maxQuantity = getAvailableTicketsForRelease(addedRelease);
118
+ const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
98
119
  const availableIncrement = Math.max(
99
120
  0,
100
- Math.min(increment, maxQuantity - Number(addedRelease.quantity))
121
+ Math.min(increment, maxQuantity - Number(addedRelease.quantity), remainingOrderCapacity)
101
122
  );
102
123
  if (availableIncrement === 0) return;
103
124
 
@@ -158,6 +179,13 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
158
179
  }
159
180
  }, [tickets, release.id, setValue]);
160
181
 
182
+ const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
183
+ const nextRelease = activeReleases?.find(
184
+ (item) =>
185
+ item.releaseCategoryName === release.releaseCategoryName && item.order === release.order + 1
186
+ );
187
+ const hasSelectedNextRelease = !!nextRelease && getSelectedQuantity(nextRelease.id) > 0;
188
+
161
189
  return (
162
190
  <Box
163
191
  sx={{
@@ -173,7 +201,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
173
201
  <Stack spacing={0}>
174
202
  <Box>
175
203
  <Typography variant="subtitle2" fontWeight={700}>
176
- {getReleaseTitle(release)}
204
+ {getReleaseTitle(release)} - {release.name}
177
205
  </Typography>
178
206
  </Box>
179
207
 
@@ -193,9 +221,11 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
193
221
 
194
222
  <TicketQuantityControl
195
223
  quantity={getSelectedQuantity(release.id)}
196
- isDisabled={release.locked}
197
- canAddFirst={!release.locked}
198
- canAddMore={!isMaxQuantity(release.id)}
224
+ isDisabled={release.locked || hasSelectedNextRelease}
225
+ canAddFirst={!release.locked && !hasSelectedNextRelease && remainingOrderCapacity > 0}
226
+ canAddMore={
227
+ !isMaxQuantity(release.id) && !hasSelectedNextRelease && remainingOrderCapacity > 0
228
+ }
199
229
  addLabel={t('add')}
200
230
  onDecrement={() => decreaseQuantity()}
201
231
  onIncrement={() =>
@@ -229,6 +259,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
229
259
  selectedQuantityByVariant={getSelectedQuantityByVariant(products, tickets)}
230
260
  eventId={eventId}
231
261
  canAddOnlyOneAtATime
262
+ maxSelectableQuantity={remainingOrderCapacity}
232
263
  />
233
264
  )}
234
265
  </Stack>
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { useFormContext, useWatch } from 'react-hook-form';
3
3
  import { Box, Divider, Stack, Typography } from '@mui/material';
4
4
  import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
@@ -9,6 +9,7 @@ import { IEvent } from '@utils/types/event.type';
9
9
  import useResponsive from '@hooks/useResponsive';
10
10
  import ReleaseExtraFields from '@form/extra-field/ReleaseExtraFields';
11
11
  import { EventType } from '@utils/data/event';
12
+ import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
12
13
  import useGlobal from '@hooks/useGlobal.ts';
13
14
  import TicketSelectionMobile from './TicketSelectionMobile';
14
15
 
@@ -25,7 +26,6 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
25
26
  defaultValue: [],
26
27
  }) as ITicketFormTicket[];
27
28
  const eventTimeslotId = watch('eventTimeslotId');
28
- const [soldOutReleaseCategoryNames, setSoldOutReleaseCategoryNames] = useState<string[]>([]);
29
29
  const isProcessingRef = useRef(false);
30
30
  const { data: activeReleases, mutate } = useEventActiveReleases(
31
31
  event.id,
@@ -68,7 +68,7 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
68
68
  // const getAvailableTicketsForRelease = (release: ITicketFormTicket): number => {
69
69
  // const selectedRelease = activeReleases?.find((item) => item.id === release.releaseId);
70
70
  // const availableQuantity = selectedRelease ? selectedRelease.availableTickets : 0;
71
- // return availableQuantity > 10 ? 10 : availableQuantity;
71
+ // return availableQuantity > MAX_TICKETS_PER_ORDER ? MAX_TICKETS_PER_ORDER : availableQuantity;
72
72
  // };
73
73
 
74
74
  const countReleaseCategories = (): number => {
@@ -83,7 +83,15 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
83
83
  item2.releaseCategoryName === item.releaseCategoryName && item2.order === item.order + 1
84
84
  );
85
85
  const selected = tickets.find((ticket) => ticket.releaseId === item.id);
86
- return !!nextRelease && item.locked && !!selected && index + 1 == tickets.length;
86
+ const maxSelectable = Math.min(item.availableTickets || 0, MAX_TICKETS_PER_ORDER);
87
+
88
+ return (
89
+ !!nextRelease &&
90
+ item.locked &&
91
+ !!selected &&
92
+ Number(selected.quantity) >= maxSelectable &&
93
+ index + 1 == tickets.length
94
+ );
87
95
  });
88
96
  return lockedSelectedReleases && lockedSelectedReleases.includes(true);
89
97
  };
@@ -102,8 +110,8 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
102
110
  const isQuantityDisabled = (value: number, releaseId: number | '') => {
103
111
  const releaseSelected = tickets.find((item) => item.releaseId === releaseId);
104
112
  return releaseSelected && releaseSelected.quantity
105
- ? countSelectedTickets() + value - releaseSelected.quantity > 10
106
- : countSelectedTickets() + value > 10;
113
+ ? countSelectedTickets() + value - releaseSelected.quantity > MAX_TICKETS_PER_ORDER
114
+ : countSelectedTickets() + value > MAX_TICKETS_PER_ORDER;
107
115
  };
108
116
 
109
117
  const removeTicket = (indexToRemove: number) => {
@@ -113,21 +121,47 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
113
121
 
114
122
  const selectedTickets = async () => {
115
123
  const releases = await mutate();
124
+ const currentReleases = releases || activeReleases || [];
116
125
  const allFilled = tickets.filter((item) => !item.releaseId || !item.quantity);
117
126
 
118
- const soldOutReleaseCategories = activeReleases?.filter((release) =>
127
+ const soldOutReleaseCategories = currentReleases.filter((release) =>
119
128
  tickets.find(
120
129
  (ticket) =>
121
130
  release.id === ticket.releaseId &&
122
- ticket.quantity === release.availableTickets &&
123
- release.availableTickets !== 10
131
+ Number(ticket.quantity) >= Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER)
124
132
  )
125
133
  );
126
- setSoldOutReleaseCategoryNames(
127
- soldOutReleaseCategories?.map((item) => item.releaseCategoryName) || []
128
- );
129
134
 
130
- const hasSelectableRelease = activeReleases?.some(
135
+ if (currentReleases.length) {
136
+ let hasChanges = false;
137
+ const updatedReleases = currentReleases.map((release) => {
138
+ const previousRelease = currentReleases.find(
139
+ (item) =>
140
+ item.releaseCategoryName === release.releaseCategoryName &&
141
+ item.order === release.order - 1
142
+ );
143
+
144
+ if (!release.locked || !previousRelease) return release;
145
+
146
+ const previousTicket = tickets.find((ticket) => ticket.releaseId === previousRelease.id);
147
+ const previousMaxSelectable = Math.min(
148
+ previousRelease.availableTickets || 0,
149
+ MAX_TICKETS_PER_ORDER
150
+ );
151
+ const shouldUnlock = Number(previousTicket?.quantity || 0) >= previousMaxSelectable;
152
+
153
+ if (!shouldUnlock) return release;
154
+
155
+ hasChanges = true;
156
+ return { ...release, locked: false };
157
+ });
158
+
159
+ if (hasChanges) {
160
+ await mutate(updatedReleases, false);
161
+ }
162
+ }
163
+
164
+ const hasSelectableRelease = currentReleases.some(
131
165
  (release) => !isReleaseSelected(release.id) && !release.locked
132
166
  );
133
167
 
@@ -136,9 +170,9 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
136
170
  selectedReleaseIsSoldOut(releases) &&
137
171
  tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
138
172
  !allFilled.length) ||
139
- (activeReleases &&
140
- soldOutReleaseCategories?.length &&
141
- activeReleases?.length > tickets.length &&
173
+ (currentReleases.length &&
174
+ soldOutReleaseCategories.length &&
175
+ currentReleases.length > tickets.length &&
142
176
  tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
143
177
  !allFilled.length) ||
144
178
  (tickets.length < countReleaseCategories() && !allFilled.length);
@@ -184,7 +218,6 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
184
218
  event={event}
185
219
  activeReleases={activeReleases}
186
220
  showLoading={showLoading}
187
- soldOutReleaseCategoryNames={soldOutReleaseCategoryNames}
188
221
  tickets={tickets}
189
222
  isQuantityDisabled={isQuantityDisabled}
190
223
  setValue={setValue as (name: string, value: any) => void}
@@ -5,6 +5,7 @@ import { Button } from '@mui/material';
5
5
  import { iframe, TicketSelection } from '@seat-picker/seat-picker-sdk';
6
6
  import { useFormContext } from 'react-hook-form';
7
7
  import { ITicketForm, ITicketFormTicket, ITicketLocation } from '@utils/types/ticket.type.ts';
8
+ import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
8
9
  import Iconify from '@components/iconify/Iconify';
9
10
 
10
11
  interface Props {
@@ -43,11 +44,18 @@ const TicketSelectionMap: React.FC<Props> = ({ event }) => {
43
44
  [] as { quantity: number; seat: ITicketLocation; ticket: any }[]
44
45
  );
45
46
 
47
+ let remainingTicketCapacity = MAX_TICKETS_PER_ORDER;
48
+
46
49
  for (const groupedSeat of groupedSeatsByZone) {
50
+ if (remainingTicketCapacity <= 0) break;
51
+
52
+ const quantity = Math.min(groupedSeat.quantity, remainingTicketCapacity);
53
+ remainingTicketCapacity -= quantity;
54
+
47
55
  tickets.push({
48
56
  releaseId: groupedSeat.ticket.id,
49
57
  price: groupedSeat.ticket.price,
50
- quantity: groupedSeat.quantity,
58
+ quantity,
51
59
  itemName: `${groupedSeat.ticket.releaseCategoryName} - ${groupedSeat.ticket.name}`,
52
60
  products: [],
53
61
  extraFields: [],
@@ -4,6 +4,7 @@ import { fCurrency } from '@utils/formatNumber';
4
4
  import { IEvent } from '@utils/types/event.type';
5
5
  import { IReleaseShort } from '@utils/types/release.type';
6
6
  import { ITicketFormTicket } from '@utils/types/ticket.type';
7
+ import { MAX_TICKETS_PER_ORDER } from '@utils/data/ticket';
7
8
  import useGlobal from '@hooks/useGlobal';
8
9
  import ReleaseDescription from './ReleaseDescription';
9
10
  import TicketQuantityControl from './TicketQuantityControl';
@@ -12,7 +13,6 @@ interface Props {
12
13
  event: IEvent;
13
14
  activeReleases?: IReleaseShort[];
14
15
  showLoading: boolean;
15
- soldOutReleaseCategoryNames: string[];
16
16
  tickets: ITicketFormTicket[];
17
17
  isQuantityDisabled: (value: number, releaseId: number | '') => boolean;
18
18
  setValue: (name: string, value: any) => void;
@@ -24,7 +24,6 @@ const TicketSelectionMobile: React.FC<Props> = ({
24
24
  event,
25
25
  activeReleases,
26
26
  showLoading,
27
- soldOutReleaseCategoryNames,
28
27
  tickets,
29
28
  isQuantityDisabled,
30
29
  setValue,
@@ -47,8 +46,11 @@ const TicketSelectionMobile: React.FC<Props> = ({
47
46
  return Number(ticket?.quantity || 0);
48
47
  };
49
48
 
49
+ const isReleaseVisible = (release: IReleaseShort) =>
50
+ !release.locked || getReleaseQuantity(release.id) > 0;
51
+
50
52
  const updateReleaseQuantity = (release: IReleaseShort, nextQuantity: number) => {
51
- const maxAvailable = Math.min(release.availableTickets || 0, 10);
53
+ const maxAvailable = Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER);
52
54
  const clampedQuantity = Math.max(0, Math.min(nextQuantity, maxAvailable));
53
55
  const ticketIndex = getTicketIndexByRelease(release.id);
54
56
 
@@ -100,76 +102,84 @@ const TicketSelectionMobile: React.FC<Props> = ({
100
102
 
101
103
  return (
102
104
  <Stack spacing={2}>
103
- {activeReleases?.map((release) => {
104
- const quantity = getReleaseQuantity(release.id);
105
- const ticketIndex = getTicketIndexByRelease(release.id);
106
- const maxAvailable = Math.min(release.availableTickets || 0, 10);
107
- const isLocked =
108
- release.locked && !soldOutReleaseCategoryNames.includes(release.releaseCategoryName);
109
- const isDisabled = isLocked && quantity === 0;
110
- const canAddFirst = maxAvailable > 0 && !isQuantityDisabled(1, release.id);
111
- const canAddMore = quantity < maxAvailable && !isQuantityDisabled(quantity + 1, release.id);
112
-
113
- return (
114
- <Box
115
- key={release.id}
116
- sx={{
117
- pt: 1,
118
- pr: 0.5,
119
- pb: 0.5,
120
- pl: 2,
121
- borderRadius: 1,
122
- bgcolor: (theme) => (isLight ? theme.palette.grey[100] : theme.palette.grey[800]),
123
- }}
124
- >
125
- <Stack spacing={0}>
126
- <Box>
127
- <Typography variant="subtitle2" fontWeight={700}>
128
- {getReleaseTitle(release)}
129
- </Typography>
130
- </Box>
131
-
132
- <Stack direction="row" alignItems="center" justifyContent="space-between">
133
- <Stack>
134
- <Typography variant="body2">
135
- {release.price === 0
136
- ? t('free')
137
- : fCurrency(release.price, lang, event.currency)}
105
+ {activeReleases
106
+ ?.filter((release) => isReleaseVisible(release))
107
+ .map((release) => {
108
+ const quantity = getReleaseQuantity(release.id);
109
+ const ticketIndex = getTicketIndexByRelease(release.id);
110
+ const maxAvailable = Math.min(release.availableTickets || 0, MAX_TICKETS_PER_ORDER);
111
+ const isLocked = release.locked;
112
+ const nextRelease = activeReleases?.find(
113
+ (item) =>
114
+ item.releaseCategoryName === release.releaseCategoryName &&
115
+ item.order === release.order + 1
116
+ );
117
+ const hasSelectedNextRelease = !!nextRelease && getReleaseQuantity(nextRelease.id) > 0;
118
+ const isDisabled = hasSelectedNextRelease || (isLocked && quantity === 0);
119
+ const canAddFirst = maxAvailable > 0 && !isQuantityDisabled(1, release.id);
120
+ const canAddMore =
121
+ quantity < maxAvailable && !isQuantityDisabled(quantity + 1, release.id);
122
+
123
+ return (
124
+ <Box
125
+ key={release.id}
126
+ sx={{
127
+ pt: 1,
128
+ pr: 0.5,
129
+ pb: 0.5,
130
+ pl: 2,
131
+ borderRadius: 1,
132
+ bgcolor: (theme) => (isLight ? theme.palette.grey[100] : theme.palette.grey[800]),
133
+ }}
134
+ >
135
+ <Stack spacing={0}>
136
+ <Box>
137
+ <Typography variant="subtitle2" fontWeight={700}>
138
+ {getReleaseTitle(release)} - {release.name}
138
139
  </Typography>
139
-
140
- <ReleaseDescription
141
- description={release.description}
142
- isExpanded={Boolean(expandedReleaseIds[release.id])}
143
- onToggle={() => toggleReleaseDescription(release.id)}
144
- moreInfoLabel={t('more_info')}
140
+ </Box>
141
+
142
+ <Stack direction="row" alignItems="center" justifyContent="space-between">
143
+ <Stack>
144
+ <Typography variant="body2">
145
+ {release.price === 0
146
+ ? t('free')
147
+ : fCurrency(release.price, lang, event.currency)}
148
+ </Typography>
149
+
150
+ <ReleaseDescription
151
+ description={release.description}
152
+ isExpanded={Boolean(expandedReleaseIds[release.id])}
153
+ onToggle={() => toggleReleaseDescription(release.id)}
154
+ moreInfoLabel={t('more_info')}
155
+ />
156
+ </Stack>
157
+
158
+ <TicketQuantityControl
159
+ quantity={quantity}
160
+ isDisabled={isDisabled}
161
+ canAddFirst={canAddFirst}
162
+ canAddMore={canAddMore}
163
+ addLabel={t('add')}
164
+ onDecrement={() => updateReleaseQuantity(release, quantity - 1)}
165
+ onIncrement={() => updateReleaseQuantity(release, quantity + 1)}
166
+ onAddFirst={() => updateReleaseQuantity(release, 1)}
145
167
  />
146
168
  </Stack>
147
169
 
148
- <TicketQuantityControl
149
- quantity={quantity}
150
- isDisabled={isDisabled}
151
- canAddFirst={canAddFirst}
152
- canAddMore={canAddMore}
153
- addLabel={t('add')}
154
- onDecrement={() => updateReleaseQuantity(release, quantity - 1)}
155
- onIncrement={() => updateReleaseQuantity(release, quantity + 1)}
156
- onAddFirst={() => updateReleaseQuantity(release, 1)}
170
+ <ReleaseDescription
171
+ description={release.description}
172
+ isExpanded={Boolean(expandedReleaseIds[release.id])}
173
+ onToggle={() => toggleReleaseDescription(release.id)}
174
+ moreInfoLabel={t('more_info')}
175
+ showCollapse
157
176
  />
158
- </Stack>
159
177
 
160
- <ReleaseDescription
161
- description={release.description}
162
- isExpanded={Boolean(expandedReleaseIds[release.id])}
163
- onToggle={() => toggleReleaseDescription(release.id)}
164
- moreInfoLabel={t('more_info')}
165
- showCollapse
166
- />
167
-
168
- {ticketIndex >= 0 && getExtraFields(release.id, ticketIndex)}
169
- </Stack>
170
- </Box>
171
- );
172
- })}
178
+ {ticketIndex >= 0 && getExtraFields(release.id, ticketIndex)}
179
+ </Stack>
180
+ </Box>
181
+ );
182
+ })}
173
183
  </Stack>
174
184
  );
175
185
  };