@eventlook/sdk 1.5.0-beta.7 → 1.5.0-beta.8

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 (44) hide show
  1. package/.claude/settings.local.json +6 -11
  2. package/dist/cjs/index-CUIxdwQn.js +38572 -0
  3. package/dist/cjs/index-CUIxdwQn.js.map +1 -0
  4. package/dist/cjs/{index-BAfaeq84.js → index-D5rQiSGP.js} +161 -72
  5. package/dist/cjs/index-D5rQiSGP.js.map +1 -0
  6. package/dist/cjs/index.js +1 -1
  7. package/dist/cjs/{index.umd-Bpwd9vUs.js → index.umd-BoFEW91M.js} +2 -2
  8. package/dist/cjs/{index.umd-Bpwd9vUs.js.map → index.umd-BoFEW91M.js.map} +1 -1
  9. package/dist/cjs/index.umd-BzSM62qM.js +13397 -0
  10. package/dist/cjs/index.umd-BzSM62qM.js.map +1 -0
  11. package/dist/esm/{index-CJ_gPli9.js → index-Cm7V8Zl3.js} +161 -72
  12. package/dist/esm/index-Cm7V8Zl3.js.map +1 -0
  13. package/dist/esm/index-fvLIN6eP.js +38569 -0
  14. package/dist/esm/index-fvLIN6eP.js.map +1 -0
  15. package/dist/esm/index.js +1 -1
  16. package/dist/esm/{index.umd-ewNTELOK.js → index.umd-BKBHcCnm.js} +2 -2
  17. package/dist/esm/{index.umd-ewNTELOK.js.map → index.umd-BKBHcCnm.js.map} +1 -1
  18. package/dist/esm/index.umd-bIV_YpEF.js +13395 -0
  19. package/dist/esm/index.umd-bIV_YpEF.js.map +1 -0
  20. package/dist/types/form/product/ProductVariantsDialog.d.ts +1 -0
  21. package/dist/types/form/tickets/TicketSelectionMobile.d.ts +0 -1
  22. package/dist/types/utils/data/ticket.d.ts +1 -0
  23. package/package.json +1 -1
  24. package/src/form/PaymentOverviewDrawer.tsx +1 -2
  25. package/src/form/Shipping.tsx +78 -80
  26. package/src/form/TicketForm.tsx +10 -8
  27. package/src/form/payment/PaymentOverviewCheckbox.tsx +60 -62
  28. package/src/form/product/ProductCard.tsx +5 -2
  29. package/src/form/product/ProductVariantsDialog.tsx +29 -6
  30. package/src/form/services/index.tsx +1 -2
  31. package/src/form/tickets/ReleaseWithMerchandise.tsx +46 -10
  32. package/src/form/tickets/TicketSelection.tsx +50 -17
  33. package/src/form/tickets/TicketSelectionMap.tsx +9 -1
  34. package/src/form/tickets/TicketSelectionMobile.tsx +78 -67
  35. package/src/form/tickets/TicketWithMerchandiseSelection.tsx +49 -31
  36. package/src/locales/cs.tsx +1 -1
  37. package/src/locales/en.tsx +1 -1
  38. package/src/locales/es.tsx +1 -1
  39. package/src/locales/pl.tsx +1 -1
  40. package/src/locales/sk.tsx +1 -1
  41. package/src/locales/uk.tsx +1 -1
  42. package/src/utils/data/ticket.ts +1 -0
  43. package/dist/cjs/index-BAfaeq84.js.map +0 -1
  44. package/dist/esm/index-CJ_gPli9.js.map +0 -1
@@ -10,6 +10,7 @@ interface Props {
10
10
  selectedQuantityByVariant?: Record<number, number>;
11
11
  isOnlyMerchandise?: boolean;
12
12
  canAddOnlyOneAtATime?: boolean;
13
+ maxSelectableQuantity?: number;
13
14
  }
14
15
  declare const ProductVariantsDialog: React.FC<Props>;
15
16
  export default ProductVariantsDialog;
@@ -6,7 +6,6 @@ interface Props {
6
6
  event: IEvent;
7
7
  activeReleases?: IReleaseShort[];
8
8
  showLoading: boolean;
9
- soldOutReleaseCategoryNames: string[];
10
9
  tickets: ITicketFormTicket[];
11
10
  isQuantityDisabled: (value: number, releaseId: number | '') => boolean;
12
11
  setValue: (name: string, value: any) => void;
@@ -0,0 +1 @@
1
+ export declare const MAX_TICKETS_PER_ORDER = 10;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventlook/sdk",
3
- "version": "1.5.0-beta.7",
3
+ "version": "1.5.0-beta.8",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -1,6 +1,5 @@
1
1
  import { Box, Button, SwipeableDrawer, Typography } from '@mui/material';
2
- import React, { useEffect } from 'react';
3
- import { useRef, useState } from 'react';
2
+ import React, { useEffect, useRef, useState } from 'react';
4
3
  import PaymentOverviewBox from './PaymentOverviewBox';
5
4
  import { IEvent } from '@utils/types/event.type';
6
5
  import useGlobal from '@hooks/useGlobal';
@@ -136,90 +136,89 @@ const Shipping: React.FC<Props> = ({ event }) => {
136
136
  {t('event.tickets.stepper.5.error')}
137
137
  </Typography>
138
138
  ) : (
139
- <>
140
- <Controller
141
- name="shipping.shippingMethodId"
142
- control={control}
143
- render={({ field, fieldState: { error } }) => (
144
- <FormControl component="fieldset" sx={{ width: '100%' }}>
145
- <RadioGroup
146
- {...field}
147
- onChange={(event, value) => {
148
- field.onChange(event);
149
- const selectedShippingMethod = filteredShippingMethods.find(
150
- (method) => method.id === Number(value)
151
- );
139
+ <Controller
140
+ name="shipping.shippingMethodId"
141
+ control={control}
142
+ render={({ field, fieldState: { error } }) => (
143
+ <FormControl component="fieldset" sx={{ width: '100%' }}>
144
+ <RadioGroup
145
+ {...field}
146
+ onChange={(event, value) => {
147
+ field.onChange(event);
148
+ const selectedShippingMethod = filteredShippingMethods.find(
149
+ (method) => method.id === Number(value)
150
+ );
152
151
 
153
- if (selectedShippingMethod?.type === ShippingTypes.PACKETA) {
154
- openPacketaWidget();
155
- }
156
- }}
157
- >
158
- {filteredShippingMethods.map((shippingMethod) => (
159
- <ShippingMethodItem
160
- key={shippingMethod.id}
161
- active={Number(shippingMethodId) === shippingMethod.id}
162
- hasError={!!error}
163
- sx={{
164
- '& .MuiFormControlLabel-labelPlacementEnd': {
165
- mr: 0,
166
- width: '100%',
167
- },
168
- }}
169
- >
170
- <FormControlLabel
171
- value={shippingMethod.id}
172
- control={<Radio />}
173
- label={
174
- <Stack
175
- direction="row"
176
- justifyContent="space-between"
177
- alignItems="center"
178
- width="100%"
179
- >
180
- <Stack direction="column">
181
- <Stack direction="row" alignItems="center" spacing={1}>
182
- <Typography sx={{ lineHeight: 1.2 }}>
183
- {t(`shipping_method.types.${shippingMethod.type}`)}
184
- </Typography>
185
- {paymentImages[shippingMethod.type]}
186
- </Stack>
187
- {displayBranchName &&
188
- shippingMethod.type === ShippingTypes.PACKETA &&
189
- shippingMethod.id === Number(shippingMethodId) && (
190
- <Typography variant="caption" sx={{ lineHeight: 1 }}>
191
- {displayBranchName}
192
- </Typography>
193
- )}
152
+ if (selectedShippingMethod?.type === ShippingTypes.PACKETA) {
153
+ openPacketaWidget();
154
+ }
155
+ }}
156
+ >
157
+ {filteredShippingMethods.map((shippingMethod) => (
158
+ <ShippingMethodItem
159
+ key={shippingMethod.id}
160
+ active={Number(shippingMethodId) === shippingMethod.id}
161
+ hasError={!!error}
162
+ sx={{
163
+ '& .MuiFormControlLabel-labelPlacementEnd': {
164
+ mr: 0,
165
+ width: '100%',
166
+ },
167
+ }}
168
+ >
169
+ <FormControlLabel
170
+ value={shippingMethod.id}
171
+ control={<Radio />}
172
+ label={
173
+ <Stack
174
+ direction="row"
175
+ justifyContent="space-between"
176
+ alignItems="center"
177
+ width="100%"
178
+ >
179
+ <Stack direction="column">
180
+ <Stack direction="row" alignItems="center" spacing={1}>
181
+ <Typography sx={{ lineHeight: 1.2 }}>
182
+ {t(`shipping_method.types.${shippingMethod.type}`)}
183
+ </Typography>
184
+ {paymentImages[shippingMethod.type]}
194
185
  </Stack>
195
- {shippingMethod.type === ShippingTypes.PACKETA &&
186
+ {displayBranchName &&
187
+ shippingMethod.type === ShippingTypes.PACKETA &&
196
188
  shippingMethod.id === Number(shippingMethodId) && (
197
- <Box>
198
- <Button
199
- onClick={handleChangeBranch}
200
- variant="outlined"
201
- size="small"
202
- sx={{ px: 1, whiteSpace: 'nowrap' }}
203
- >
204
- {t('event.tickets.shipping.choose_address')}
205
- </Button>
206
- </Box>
189
+ <Typography variant="caption" sx={{ lineHeight: 1 }}>
190
+ {displayBranchName}
191
+ </Typography>
207
192
  )}
208
193
  </Stack>
209
- }
210
- sx={{
211
- '&:not(:last-of-type)': {
212
- mb: 0,
213
- },
214
- '& .MuiFormControlLabel-label': {
215
- width: '100%',
216
- mr: 0,
217
- },
218
- }}
219
- />
220
- </ShippingMethodItem>
221
- ))}
222
- </RadioGroup>
194
+ {shippingMethod.type === ShippingTypes.PACKETA &&
195
+ shippingMethod.id === Number(shippingMethodId) && (
196
+ <Box>
197
+ <Button
198
+ onClick={handleChangeBranch}
199
+ variant="outlined"
200
+ size="small"
201
+ sx={{ px: 1, whiteSpace: 'nowrap' }}
202
+ >
203
+ {t('event.tickets.shipping.choose_address')}
204
+ </Button>
205
+ </Box>
206
+ )}
207
+ </Stack>
208
+ }
209
+ sx={{
210
+ '&:not(:last-of-type)': {
211
+ mb: 0,
212
+ },
213
+ '& .MuiFormControlLabel-label': {
214
+ width: '100%',
215
+ mr: 0,
216
+ },
217
+ }}
218
+ />
219
+ </ShippingMethodItem>
220
+ ))}
221
+ </RadioGroup>
223
222
 
224
223
  {!!error && (
225
224
  <FormHelperText error={!!error} sx={{ mx: 0 }}>
@@ -229,7 +228,6 @@ const Shipping: React.FC<Props> = ({ event }) => {
229
228
  </FormControl>
230
229
  )}
231
230
  />
232
- </>
233
231
  )}
234
232
  </>
235
233
  )}
@@ -546,7 +546,7 @@ const TicketForm: React.FC<Props> = ({
546
546
  ) : (
547
547
  <FormProvider
548
548
  methods={methods}
549
- // @ts-ignore
549
+ // @ts-ignore -- handleSubmit type mismatch with FormProvider onSubmit prop
550
550
  onSubmit={methods.handleSubmit(onSubmit, onInvalid)}
551
551
  formId={EVENTLOOK_ORDER_FORM_ID}
552
552
  >
@@ -696,17 +696,19 @@ const TicketForm: React.FC<Props> = ({
696
696
  </Grid>
697
697
  </Grid>
698
698
 
699
- <PaymentOverviewDrawer
700
- event={event}
701
- totalPrice={values.total}
702
- termsAndConditionsRef={termsAndConditionsRef}
703
- onOpenChange={setIsPaymentOverviewDrawerOpen}
704
- />
699
+ {!isIframe && (
700
+ <PaymentOverviewDrawer
701
+ event={event}
702
+ totalPrice={values.total}
703
+ termsAndConditionsRef={termsAndConditionsRef}
704
+ onOpenChange={setIsPaymentOverviewDrawerOpen}
705
+ />
706
+ )}
705
707
 
706
708
  <EmailConfirmation
707
709
  open={formStep === 2 && !isIframe}
708
710
  onClose={() => setFormStep(1)}
709
- // @ts-ignore
711
+ // @ts-ignore -- handleSubmit type mismatch with onConfirm prop
710
712
  onConfirm={methods.handleSubmit(onSubmit, onInvalid)}
711
713
  />
712
714
  </FormProvider>
@@ -15,73 +15,71 @@ const PaymentOverviewCheckbox: React.FC<Props> = ({ checkboxName, label, value }
15
15
  const { control } = useFormContext();
16
16
 
17
17
  return (
18
- <>
19
- <Controller
20
- name={checkboxName}
21
- control={control}
22
- render={({ field }) => (
23
- <Stack
24
- direction="row"
25
- justifyContent="space-between"
26
- alignItems="center"
27
- useFlexGap
28
- spacing={1}
29
- sx={{ width: '100%', mb: 0.5 }}
18
+ <Controller
19
+ name={checkboxName}
20
+ control={control}
21
+ render={({ field }) => (
22
+ <Stack
23
+ direction="row"
24
+ justifyContent="space-between"
25
+ alignItems="center"
26
+ useFlexGap
27
+ spacing={1}
28
+ sx={{ width: '100%', mb: 0.5 }}
29
+ >
30
+ <Typography variant="body2" sx={{ flex: 1 }}>
31
+ {label}
32
+ </Typography>
33
+ <Box
34
+ sx={{
35
+ position: 'relative',
36
+ display: 'flex',
37
+ alignItems: 'center',
38
+ }}
30
39
  >
31
- <Typography variant="body2" sx={{ flex: 1 }}>
32
- {label}
40
+ <Typography
41
+ variant="body2"
42
+ sx={{
43
+ mr: field.value ? 0 : 1,
44
+ ...(field.value
45
+ ? {
46
+ position: 'absolute',
47
+ right: 0,
48
+ }
49
+ : {}),
50
+ }}
51
+ >
52
+ {value}
33
53
  </Typography>
34
- <Box
54
+ <Button
55
+ variant="outlined"
56
+ color="primary"
57
+ size="small"
58
+ startIcon={<Iconify icon="carbon:add" />}
59
+ onClick={(event) => {
60
+ event.preventDefault();
61
+ event.stopPropagation();
62
+ field.onChange(true);
63
+ }}
35
64
  sx={{
36
- position: 'relative',
37
- display: 'flex',
38
- alignItems: 'center',
65
+ borderRadius: 1,
66
+ textTransform: 'none',
67
+ px: 1,
68
+ width: 96,
69
+ minWidth: 96,
70
+ color: 'text.primary',
71
+ visibility: field.value ? 'hidden' : 'visible',
72
+ pointerEvents: field.value ? 'none' : 'auto',
39
73
  }}
74
+ tabIndex={field.value ? -1 : 0}
75
+ aria-hidden={field.value}
40
76
  >
41
- <Typography
42
- variant="body2"
43
- sx={{
44
- mr: field.value ? 0 : 1,
45
- ...(field.value
46
- ? {
47
- position: 'absolute',
48
- right: 0,
49
- }
50
- : {}),
51
- }}
52
- >
53
- {value}
54
- </Typography>
55
- <Button
56
- variant="outlined"
57
- color="primary"
58
- size="small"
59
- startIcon={<Iconify icon="carbon:add" />}
60
- onClick={(event) => {
61
- event.preventDefault();
62
- event.stopPropagation();
63
- field.onChange(true);
64
- }}
65
- sx={{
66
- borderRadius: 1,
67
- textTransform: 'none',
68
- px: 1,
69
- width: 96,
70
- minWidth: 96,
71
- color: 'text.primary',
72
- visibility: field.value ? 'hidden' : 'visible',
73
- pointerEvents: field.value ? 'none' : 'auto',
74
- }}
75
- tabIndex={field.value ? -1 : 0}
76
- aria-hidden={field.value}
77
- >
78
- {t('add')}
79
- </Button>
80
- </Box>
81
- </Stack>
82
- )}
83
- />
84
- </>
77
+ {t('add')}
78
+ </Button>
79
+ </Box>
80
+ </Stack>
81
+ )}
82
+ />
85
83
  );
86
84
  };
87
85
 
@@ -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';
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';
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
  +
@@ -13,13 +13,12 @@ import {
13
13
  Stack,
14
14
  Typography,
15
15
  } from '@mui/material';
16
- import { useWatch, Controller } from 'react-hook-form';
16
+ import { useWatch, Controller, useFormContext } from 'react-hook-form';
17
17
  import { fCurrency } from '@utils/formatNumber';
18
18
  import useGlobal from '@hooks/useGlobal';
19
19
  import { Iconify } from '@components';
20
20
  import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
21
21
  import { IEvent } from '@utils/types/event.type';
22
- import { useFormContext } from 'react-hook-form';
23
22
 
24
23
  interface Props {
25
24
  event: IEvent;
@@ -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) => {
@@ -55,9 +65,23 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
55
65
  return getSelectedQuantity(releaseId) >= getAvailableTicketsForRelease(release);
56
66
  };
57
67
 
58
- const addRelease = (productsToAdd?: IEventProductForm[]) => {
59
- const normalizedProducts = productsToAdd ?? [];
60
- const quantity = normalizedProducts.length ? normalizedProducts.length : 1;
68
+ const addRelease = (productsToAdd?: IEventProductForm[] | IEventProductForm) => {
69
+ const normalizedProducts = Array.isArray(productsToAdd)
70
+ ? productsToAdd
71
+ : productsToAdd
72
+ ? [productsToAdd]
73
+ : [];
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);
61
85
  const extraFields = release.extraFields?.length
62
86
  ? Array.from({ length: quantity }, () =>
63
87
  release.extraFields!.map((field) => ({
@@ -74,7 +98,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
74
98
  quantity,
75
99
  itemName: '',
76
100
  price: 0,
77
- products: normalizedProducts,
101
+ products: selectedProducts,
78
102
  extraFields,
79
103
  },
80
104
  ]);
@@ -87,9 +111,10 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
87
111
  if (addedRelease) {
88
112
  const increment = normalizedProducts.length ? normalizedProducts.length : 1;
89
113
  const maxQuantity = getAvailableTicketsForRelease(addedRelease);
114
+ const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
90
115
  const availableIncrement = Math.max(
91
116
  0,
92
- Math.min(increment, maxQuantity - Number(addedRelease.quantity))
117
+ Math.min(increment, maxQuantity - Number(addedRelease.quantity), remainingOrderCapacity)
93
118
  );
94
119
  if (availableIncrement === 0) return;
95
120
 
@@ -150,6 +175,13 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
150
175
  }
151
176
  }, [tickets, release.id, setValue]);
152
177
 
178
+ const remainingOrderCapacity = Math.max(0, MAX_TICKETS_PER_ORDER - countSelectedTickets());
179
+ const nextRelease = activeReleases?.find(
180
+ (item) =>
181
+ item.releaseCategoryName === release.releaseCategoryName && item.order === release.order + 1
182
+ );
183
+ const hasSelectedNextRelease = !!nextRelease && getSelectedQuantity(nextRelease.id) > 0;
184
+
153
185
  return (
154
186
  <Box
155
187
  sx={{
@@ -172,7 +204,8 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
172
204
  <Stack direction="row" alignItems="center" justifyContent="space-between">
173
205
  <Stack>
174
206
  <Typography variant="body2">
175
- {release.price === 0 ? t('free') : fCurrency(release.price, lang, currency)}
207
+ {release.price === 0 ? t('free') : fCurrency(release.price, lang, currency)} -{' '}
208
+ {release.name}
176
209
  </Typography>
177
210
 
178
211
  <ReleaseDescription
@@ -185,9 +218,11 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
185
218
 
186
219
  <TicketQuantityControl
187
220
  quantity={getSelectedQuantity(release.id)}
188
- isDisabled={release.locked}
189
- canAddFirst={!release.locked}
190
- canAddMore={!isMaxQuantity(release.id)}
221
+ isDisabled={release.locked || hasSelectedNextRelease}
222
+ canAddFirst={!release.locked && !hasSelectedNextRelease && remainingOrderCapacity > 0}
223
+ canAddMore={
224
+ !isMaxQuantity(release.id) && !hasSelectedNextRelease && remainingOrderCapacity > 0
225
+ }
191
226
  addLabel={t('add')}
192
227
  onDecrement={() => decreaseQuantity()}
193
228
  onIncrement={() =>
@@ -221,6 +256,7 @@ const ReleaseWithMerchandise: React.FC<Props> = ({
221
256
  selectedQuantityByVariant={getSelectedQuantityByVariant(products, tickets)}
222
257
  eventId={eventId}
223
258
  canAddOnlyOneAtATime
259
+ maxSelectableQuantity={remainingOrderCapacity}
224
260
  />
225
261
  )}
226
262
  </Stack>