@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.
- package/.claude/settings.local.json +6 -11
- package/dist/cjs/index-CUIxdwQn.js +38572 -0
- package/dist/cjs/index-CUIxdwQn.js.map +1 -0
- package/dist/cjs/{index-BAfaeq84.js → index-D5rQiSGP.js} +161 -72
- package/dist/cjs/index-D5rQiSGP.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/{index.umd-Bpwd9vUs.js → index.umd-BoFEW91M.js} +2 -2
- package/dist/cjs/{index.umd-Bpwd9vUs.js.map → index.umd-BoFEW91M.js.map} +1 -1
- package/dist/cjs/index.umd-BzSM62qM.js +13397 -0
- package/dist/cjs/index.umd-BzSM62qM.js.map +1 -0
- package/dist/esm/{index-CJ_gPli9.js → index-Cm7V8Zl3.js} +161 -72
- package/dist/esm/index-Cm7V8Zl3.js.map +1 -0
- package/dist/esm/index-fvLIN6eP.js +38569 -0
- package/dist/esm/index-fvLIN6eP.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/{index.umd-ewNTELOK.js → index.umd-BKBHcCnm.js} +2 -2
- package/dist/esm/{index.umd-ewNTELOK.js.map → index.umd-BKBHcCnm.js.map} +1 -1
- package/dist/esm/index.umd-bIV_YpEF.js +13395 -0
- package/dist/esm/index.umd-bIV_YpEF.js.map +1 -0
- package/dist/types/form/product/ProductVariantsDialog.d.ts +1 -0
- package/dist/types/form/tickets/TicketSelectionMobile.d.ts +0 -1
- package/dist/types/utils/data/ticket.d.ts +1 -0
- package/package.json +1 -1
- package/src/form/PaymentOverviewDrawer.tsx +1 -2
- package/src/form/Shipping.tsx +78 -80
- package/src/form/TicketForm.tsx +10 -8
- package/src/form/payment/PaymentOverviewCheckbox.tsx +60 -62
- package/src/form/product/ProductCard.tsx +5 -2
- package/src/form/product/ProductVariantsDialog.tsx +29 -6
- package/src/form/services/index.tsx +1 -2
- package/src/form/tickets/ReleaseWithMerchandise.tsx +46 -10
- package/src/form/tickets/TicketSelection.tsx +50 -17
- package/src/form/tickets/TicketSelectionMap.tsx +9 -1
- package/src/form/tickets/TicketSelectionMobile.tsx +78 -67
- package/src/form/tickets/TicketWithMerchandiseSelection.tsx +49 -31
- package/src/locales/cs.tsx +1 -1
- package/src/locales/en.tsx +1 -1
- package/src/locales/es.tsx +1 -1
- package/src/locales/pl.tsx +1 -1
- package/src/locales/sk.tsx +1 -1
- package/src/locales/uk.tsx +1 -1
- package/src/utils/data/ticket.ts +1 -0
- package/dist/cjs/index-BAfaeq84.js.map +0 -1
- 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,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';
|
package/src/form/Shipping.tsx
CHANGED
|
@@ -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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
onChange
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
{
|
|
186
|
+
{displayBranchName &&
|
|
187
|
+
shippingMethod.type === ShippingTypes.PACKETA &&
|
|
196
188
|
shippingMethod.id === Number(shippingMethodId) && (
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
)}
|
package/src/form/TicketForm.tsx
CHANGED
|
@@ -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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
32
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 >
|
|
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={
|
|
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(
|
|
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
|
-
|
|
138
|
+
|
|
130
139
|
if (canAddOnlyOneAtATime) {
|
|
131
|
-
|
|
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
|
|
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 >=
|
|
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 >
|
|
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
|
-
|
|
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:
|
|
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={
|
|
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>
|