@eventlook/sdk 1.4.43 → 1.4.45
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 +9 -0
- package/dist/cjs/form/Payment.js +0 -2
- package/dist/cjs/form/Payment.js.map +1 -1
- package/dist/cjs/form/PaymentOverviewBox.js +55 -77
- package/dist/cjs/form/PaymentOverviewBox.js.map +1 -1
- package/dist/cjs/form/TicketForm.js +2 -1
- package/dist/cjs/form/TicketForm.js.map +1 -1
- package/dist/cjs/form/TicketSelection.js +24 -5
- package/dist/cjs/form/TicketSelection.js.map +1 -1
- package/dist/cjs/form/TicketWithMerchandiseSelection.js +43 -5
- package/dist/cjs/form/TicketWithMerchandiseSelection.js.map +1 -1
- package/dist/cjs/form/payment/FeeBox.js +1 -1
- package/dist/cjs/form/payment/FeeBox.js.map +1 -1
- package/dist/cjs/locales/cs.js +3 -0
- package/dist/cjs/locales/cs.js.map +1 -1
- package/dist/cjs/locales/en.js +3 -0
- package/dist/cjs/locales/en.js.map +1 -1
- package/dist/cjs/locales/es.js +3 -0
- package/dist/cjs/locales/es.js.map +1 -1
- package/dist/cjs/locales/pl.js +3 -0
- package/dist/cjs/locales/pl.js.map +1 -1
- package/dist/cjs/locales/sk.js +3 -0
- package/dist/cjs/locales/sk.js.map +1 -1
- package/dist/cjs/locales/uk.js +3 -0
- package/dist/cjs/locales/uk.js.map +1 -1
- package/dist/cjs/modules/shopping-cart.js +10 -9
- package/dist/cjs/modules/shopping-cart.js.map +1 -1
- package/dist/esm/form/Payment.js +0 -2
- package/dist/esm/form/Payment.js.map +1 -1
- package/dist/esm/form/PaymentOverviewBox.js +57 -79
- package/dist/esm/form/PaymentOverviewBox.js.map +1 -1
- package/dist/esm/form/TicketForm.js +2 -1
- package/dist/esm/form/TicketForm.js.map +1 -1
- package/dist/esm/form/TicketSelection.js +25 -6
- package/dist/esm/form/TicketSelection.js.map +1 -1
- package/dist/esm/form/TicketWithMerchandiseSelection.js +44 -6
- package/dist/esm/form/TicketWithMerchandiseSelection.js.map +1 -1
- package/dist/esm/form/payment/FeeBox.js +1 -1
- package/dist/esm/form/payment/FeeBox.js.map +1 -1
- package/dist/esm/locales/cs.js +3 -0
- package/dist/esm/locales/cs.js.map +1 -1
- package/dist/esm/locales/en.js +3 -0
- package/dist/esm/locales/en.js.map +1 -1
- package/dist/esm/locales/es.js +3 -0
- package/dist/esm/locales/es.js.map +1 -1
- package/dist/esm/locales/pl.js +3 -0
- package/dist/esm/locales/pl.js.map +1 -1
- package/dist/esm/locales/sk.js +3 -0
- package/dist/esm/locales/sk.js.map +1 -1
- package/dist/esm/locales/uk.js +3 -0
- package/dist/esm/locales/uk.js.map +1 -1
- package/dist/esm/modules/shopping-cart.js +9 -8
- package/dist/esm/modules/shopping-cart.js.map +1 -1
- package/dist/types/locales/cs.d.ts +3 -0
- package/dist/types/locales/en.d.ts +3 -0
- package/dist/types/locales/es.d.ts +3 -0
- package/dist/types/locales/pl.d.ts +3 -0
- package/dist/types/locales/sk.d.ts +3 -0
- package/dist/types/locales/uk.d.ts +3 -0
- package/dist/types/modules/shopping-cart.d.ts +3 -5
- package/dist/types/utils/data/shopping-cart.d.ts +5 -0
- package/dist/types/utils/types/shopping-cart.type.d.ts +42 -40
- package/dist/types/utils/types/ticket.type.d.ts +1 -2
- package/package.json +1 -1
- package/src/form/Payment.tsx +0 -2
- package/src/form/PaymentOverviewBox.tsx +71 -88
- package/src/form/TicketForm.tsx +2 -1
- package/src/form/TicketSelection.tsx +29 -7
- package/src/form/TicketWithMerchandiseSelection.tsx +54 -7
- package/src/form/payment/FeeBox.tsx +2 -3
- package/src/locales/cs.tsx +3 -0
- package/src/locales/en.tsx +3 -0
- package/src/locales/es.tsx +3 -0
- package/src/locales/pl.tsx +3 -0
- package/src/locales/sk.tsx +3 -0
- package/src/locales/uk.tsx +3 -0
- package/src/modules/shopping-cart.ts +14 -11
- package/src/utils/data/shopping-cart.ts +5 -0
- package/src/utils/types/shopping-cart.type.ts +40 -36
- package/src/utils/types/ticket.type.ts +1 -2
|
@@ -2,14 +2,14 @@ import { IPromoCode } from '@utils/types/promo-code.type';
|
|
|
2
2
|
import { ITicketFormTicket } from '@utils/types/ticket.type';
|
|
3
3
|
import { IEventProductForm } from '@utils/types/product.type';
|
|
4
4
|
import { Dayjs } from 'dayjs';
|
|
5
|
-
import {
|
|
5
|
+
import { ShoppingCartStatus } from '@utils/data/shopping-cart';
|
|
6
6
|
export interface ICalculateShoppingCartDto {
|
|
7
7
|
uuid?: string | null;
|
|
8
8
|
eventId: number;
|
|
9
9
|
tickets?: {
|
|
10
10
|
[eventId: string]: ITicketFormTicket[];
|
|
11
11
|
};
|
|
12
|
-
promoCodeIds
|
|
12
|
+
promoCodeIds?: number[] | null;
|
|
13
13
|
products?: {
|
|
14
14
|
[eventId: string]: IEventProductForm[];
|
|
15
15
|
};
|
|
@@ -20,57 +20,59 @@ export interface ICalculateShoppingCartDto {
|
|
|
20
20
|
branchId: string | null;
|
|
21
21
|
price: number;
|
|
22
22
|
};
|
|
23
|
-
firstName
|
|
24
|
-
lastName
|
|
25
|
-
email
|
|
26
|
-
phone
|
|
27
|
-
birthdate
|
|
28
|
-
gender
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
firstName?: string | null;
|
|
24
|
+
lastName?: string | null;
|
|
25
|
+
email?: string | null;
|
|
26
|
+
phone?: string | null;
|
|
27
|
+
birthdate?: Dayjs | null;
|
|
28
|
+
gender?: string | null;
|
|
29
|
+
paymentMethodId?: number | null;
|
|
30
|
+
paymentMethodOptionId?: number | null;
|
|
31
31
|
ticketInsurance?: boolean;
|
|
32
32
|
smsNotification?: boolean;
|
|
33
33
|
}
|
|
34
|
-
export interface
|
|
35
|
-
|
|
34
|
+
export interface ITicketInsurance {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
price: number;
|
|
37
|
+
pricePerUnit: number;
|
|
38
|
+
}
|
|
39
|
+
export interface ISmsNotification {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
price: number;
|
|
42
|
+
}
|
|
43
|
+
export interface IShoppingCartDto {
|
|
36
44
|
id: number;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
uuid: string;
|
|
46
|
+
eventId: number;
|
|
47
|
+
status: ShoppingCartStatus;
|
|
48
|
+
tickets: {
|
|
49
|
+
[eventId: string]: ITicketFormTicket[];
|
|
50
|
+
};
|
|
51
|
+
products: {
|
|
52
|
+
[eventId: string]: IEventProductForm[];
|
|
53
|
+
};
|
|
41
54
|
originalPrice: number;
|
|
42
55
|
totalPrice: number;
|
|
43
56
|
ticketsTotalPrice: number;
|
|
44
57
|
ticketProductsTotal: number;
|
|
45
58
|
productsTotalPrice: number;
|
|
59
|
+
shippingFee: number;
|
|
60
|
+
serviceFee: number;
|
|
61
|
+
paymentMethodFee: number;
|
|
62
|
+
promoCodes: IPromoCode[];
|
|
63
|
+
email: string | null;
|
|
64
|
+
phone: string | null;
|
|
65
|
+
firstName: string | null;
|
|
66
|
+
lastName: string | null;
|
|
67
|
+
birthdate: string | null;
|
|
68
|
+
gender: string | null;
|
|
46
69
|
shipping: {
|
|
47
70
|
shippingMethodId: number | null;
|
|
48
71
|
branchId: string | null;
|
|
49
72
|
price: number;
|
|
50
73
|
};
|
|
51
|
-
expiryTime: string;
|
|
52
|
-
promoCodes: IPromoCode[] | null;
|
|
53
|
-
tickets: {
|
|
54
|
-
[eventId: string]: ITicketFormTicket[];
|
|
55
|
-
};
|
|
56
|
-
products: {
|
|
57
|
-
[eventId: string]: IEventProductForm[];
|
|
58
|
-
};
|
|
59
|
-
firstName: string | null;
|
|
60
|
-
lastName: string | null;
|
|
61
|
-
email: string | null;
|
|
62
|
-
phone: string | null;
|
|
63
|
-
birthdate: Dayjs | null;
|
|
64
|
-
gender: string | null;
|
|
65
|
-
paymentMethodOptionId: number | null;
|
|
66
74
|
paymentMethodId: number | null;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
shippingFee: number;
|
|
71
|
-
ticketInsurance?: boolean;
|
|
72
|
-
ticketInsurancePrice?: number;
|
|
73
|
-
ticketInsurancePricePerUnit?: number;
|
|
74
|
-
smsNotification?: boolean;
|
|
75
|
-
smsNotificationPrice?: number;
|
|
75
|
+
paymentMethodOptionId: number | null;
|
|
76
|
+
ticketInsurance: ITicketInsurance;
|
|
77
|
+
smsNotification: ISmsNotification;
|
|
76
78
|
}
|
|
@@ -68,7 +68,6 @@ export interface ITicketForm {
|
|
|
68
68
|
smsNotification: boolean;
|
|
69
69
|
smsNotificationPrice: number;
|
|
70
70
|
iframeCampaignId?: number;
|
|
71
|
-
promoCode: IPromoCode | null;
|
|
72
71
|
products: {
|
|
73
72
|
[eventId: string]: IEventProductForm[];
|
|
74
73
|
};
|
|
@@ -84,7 +83,7 @@ export interface ITicketForm {
|
|
|
84
83
|
totalFee?: number | null;
|
|
85
84
|
isPaymentVerify: boolean;
|
|
86
85
|
}
|
|
87
|
-
export interface ITicketBody extends
|
|
86
|
+
export interface ITicketBody extends ITicketForm {
|
|
88
87
|
promoCodeIds: number[];
|
|
89
88
|
}
|
|
90
89
|
export interface IPaidTicket {
|
package/package.json
CHANGED
package/src/form/Payment.tsx
CHANGED
|
@@ -87,12 +87,10 @@ const Payment: React.FC<Props> = ({ event }) => {
|
|
|
87
87
|
setValue('promoCodes', [...promoCodes, promo]);
|
|
88
88
|
showSnackbar(res.message);
|
|
89
89
|
setPromoCodeError(null);
|
|
90
|
-
setValue('promoCode', promo);
|
|
91
90
|
setPromoCode('');
|
|
92
91
|
} catch (err) {
|
|
93
92
|
setPromoCodeError(t('form.validation.promo_code_invalid'));
|
|
94
93
|
console.error(err);
|
|
95
|
-
setValue('promoCode', null);
|
|
96
94
|
}
|
|
97
95
|
},
|
|
98
96
|
1000,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo,
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { Iconify, Image, TextIconLabel } from '@components';
|
|
3
3
|
import { Box, Divider, Grid, IconButton, Stack, Typography } from '@mui/material';
|
|
4
4
|
import PaymentOverviewCheckbox from '@form/payment/PaymentOverviewCheckbox.tsx';
|
|
@@ -16,16 +16,14 @@ import FeeBox from '@form/payment/FeeBox';
|
|
|
16
16
|
import { IPromoCode } from '@utils/types/promo-code.type';
|
|
17
17
|
import { fCurrency } from '@utils/formatNumber';
|
|
18
18
|
import { IEventProductForm, ISelectedProductVariant } from '@utils/types/product.type';
|
|
19
|
-
import { isEqual } from 'lodash';
|
|
20
19
|
import calendarIcon from '@iconify/icons-carbon/calendar';
|
|
21
20
|
import userIcon from '@iconify/icons-carbon/user';
|
|
22
21
|
import { EventType } from '@utils/data/event';
|
|
23
22
|
import dayjs from 'dayjs';
|
|
24
23
|
import useGlobal from '@hooks/useGlobal.ts';
|
|
25
|
-
import {
|
|
26
|
-
import { ICalculateShoppingCartDto,
|
|
24
|
+
import { calculateCart, getCart } from '@modules/shopping-cart.ts';
|
|
25
|
+
import { ICalculateShoppingCartDto, IShoppingCartDto } from '@utils/types/shopping-cart.type.ts';
|
|
27
26
|
import { PromoCodeTypes } from '@utils/data/promo-code.ts';
|
|
28
|
-
import { useFirstRender } from '@hooks/useFirstRender.ts';
|
|
29
27
|
import useActiveEventProducts from '@hooks/data/useActiveEventProducts';
|
|
30
28
|
import useDebounce from '@hooks/useDebounce';
|
|
31
29
|
|
|
@@ -35,9 +33,6 @@ interface Props {
|
|
|
35
33
|
|
|
36
34
|
const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
37
35
|
const { t, lang, options } = useGlobal();
|
|
38
|
-
const firstRender = useFirstRender();
|
|
39
|
-
const secondRender = useRef(false);
|
|
40
|
-
const lastCartRef = useRef<IShoppingCart | null>(null);
|
|
41
36
|
const xs = useResponsive('only', 'xs');
|
|
42
37
|
const md = useResponsive('only', 'md');
|
|
43
38
|
const isMobile = useResponsive('down', 'md');
|
|
@@ -139,44 +134,45 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
139
134
|
|
|
140
135
|
const shoppingCartBody = useMemo<ICalculateShoppingCartDto>(
|
|
141
136
|
() => ({
|
|
142
|
-
uuid
|
|
137
|
+
uuid,
|
|
143
138
|
eventId: event.id,
|
|
144
|
-
tickets: Object.
|
|
145
|
-
(
|
|
146
|
-
|
|
147
|
-
|
|
139
|
+
tickets: Object.fromEntries(
|
|
140
|
+
Object.entries(values.tickets).map(([id, items]) => [
|
|
141
|
+
id,
|
|
142
|
+
items
|
|
143
|
+
.filter((item) => item.quantity && item.releaseId)
|
|
148
144
|
.map((item) => ({
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
products: item.products,
|
|
154
|
-
extraFields: item.extraFields,
|
|
155
|
-
eventTimeslotId: Number(eventTimeslotId) || null,
|
|
156
|
-
}));
|
|
157
|
-
return acc;
|
|
158
|
-
},
|
|
159
|
-
{} as Record<number, ITicketFormTicket[]>
|
|
145
|
+
...item,
|
|
146
|
+
eventTimeslotId: eventTimeslotId ?? null,
|
|
147
|
+
})),
|
|
148
|
+
])
|
|
160
149
|
),
|
|
161
|
-
products: Object.
|
|
162
|
-
(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
products: Object.fromEntries(
|
|
151
|
+
Object.entries(values.products).map(([id, items]) => [
|
|
152
|
+
id,
|
|
153
|
+
items.map(
|
|
154
|
+
({
|
|
155
|
+
eventProductVariantId,
|
|
156
|
+
productVariantId,
|
|
157
|
+
quantity,
|
|
158
|
+
price,
|
|
159
|
+
excludedShippingMethodIds,
|
|
160
|
+
}) => ({
|
|
161
|
+
eventProductVariantId,
|
|
162
|
+
productVariantId,
|
|
163
|
+
quantity,
|
|
164
|
+
price,
|
|
165
|
+
excludedShippingMethodIds,
|
|
166
|
+
})
|
|
167
|
+
),
|
|
168
|
+
])
|
|
173
169
|
),
|
|
174
170
|
shipping: {
|
|
175
|
-
shippingMethodId: shipping?.shippingMethodId
|
|
176
|
-
branchId: shipping?.branchId,
|
|
177
|
-
price: shipping?.price
|
|
171
|
+
shippingMethodId: shipping?.shippingMethodId ?? null,
|
|
172
|
+
branchId: shipping?.branchId ?? null,
|
|
173
|
+
price: shipping?.price ?? 0,
|
|
178
174
|
},
|
|
179
|
-
promoCodeIds: promoCodes?.map((p) => p.id)
|
|
175
|
+
promoCodeIds: promoCodes?.map((p) => p.id) ?? null,
|
|
180
176
|
language: lang,
|
|
181
177
|
firstName,
|
|
182
178
|
lastName,
|
|
@@ -184,8 +180,8 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
184
180
|
phone,
|
|
185
181
|
birthdate,
|
|
186
182
|
gender,
|
|
187
|
-
paymentMethodId: paymentMethodId
|
|
188
|
-
paymentMethodOptionId: paymentMethodOptionId
|
|
183
|
+
paymentMethodId: paymentMethodId ? Number(paymentMethodId) : null,
|
|
184
|
+
paymentMethodOptionId: paymentMethodOptionId ? Number(paymentMethodOptionId) : null,
|
|
189
185
|
ticketInsurance,
|
|
190
186
|
smsNotification,
|
|
191
187
|
}),
|
|
@@ -194,9 +190,7 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
194
190
|
event.id,
|
|
195
191
|
tickets,
|
|
196
192
|
products,
|
|
197
|
-
shipping
|
|
198
|
-
shipping.branchId,
|
|
199
|
-
shipping.price,
|
|
193
|
+
shipping,
|
|
200
194
|
promoCodes,
|
|
201
195
|
lang,
|
|
202
196
|
firstName,
|
|
@@ -215,13 +209,13 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
215
209
|
|
|
216
210
|
const debouncedShoppingCartBody = useDebounce(shoppingCartBody, 300);
|
|
217
211
|
|
|
218
|
-
const setShoppingCartValues = (data:
|
|
212
|
+
const setShoppingCartValues = (data: IShoppingCartDto, recalculate = false) => {
|
|
219
213
|
if (!recalculate) {
|
|
220
214
|
const hasTickets =
|
|
221
215
|
!!data.tickets && typeof data.tickets === 'object' && Object.keys(data.tickets).length > 0;
|
|
222
216
|
|
|
223
217
|
if (hasTickets) {
|
|
224
|
-
setValue('tickets',
|
|
218
|
+
setValue('tickets', data.tickets);
|
|
225
219
|
}
|
|
226
220
|
|
|
227
221
|
const hasProducts =
|
|
@@ -230,7 +224,7 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
230
224
|
Object.keys(data.products).length > 0;
|
|
231
225
|
|
|
232
226
|
if (hasProducts) {
|
|
233
|
-
setValue('products',
|
|
227
|
+
setValue('products', data.products);
|
|
234
228
|
}
|
|
235
229
|
|
|
236
230
|
setValue('promoCodes', data.promoCodes || []);
|
|
@@ -259,11 +253,11 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
259
253
|
setValue('totalFee', feeTotal);
|
|
260
254
|
|
|
261
255
|
setShippingFee(data.shippingFee);
|
|
262
|
-
setValue('ticketInsurance', data.ticketInsurance
|
|
263
|
-
setValue('ticketInsurancePrice', data.
|
|
264
|
-
setValue('ticketInsurancePricePerUnit', data.
|
|
265
|
-
setValue('smsNotification', data.smsNotification
|
|
266
|
-
setValue('smsNotificationPrice', data.
|
|
256
|
+
setValue('ticketInsurance', data.ticketInsurance.enabled);
|
|
257
|
+
setValue('ticketInsurancePrice', data.ticketInsurance.price);
|
|
258
|
+
setValue('ticketInsurancePricePerUnit', data.ticketInsurance.pricePerUnit);
|
|
259
|
+
setValue('smsNotification', data.smsNotification.enabled);
|
|
260
|
+
setValue('smsNotificationPrice', data.smsNotification.price);
|
|
267
261
|
};
|
|
268
262
|
|
|
269
263
|
const totalItemCount = useMemo(() => {
|
|
@@ -335,30 +329,16 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
335
329
|
return discount;
|
|
336
330
|
}, [promoCodes, flatTickets, flatProducts]);
|
|
337
331
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
} else {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
(async () => {
|
|
346
|
-
const existing = localStorage.getItem('cartToken');
|
|
347
|
-
const res = await getCreateShoppingCart(existing, event.id);
|
|
348
|
-
const data: IShoppingCart = res.data;
|
|
349
|
-
|
|
350
|
-
localStorage.setItem('cartToken', data.uuid);
|
|
351
|
-
setShoppingCartValues(data);
|
|
352
|
-
})();
|
|
332
|
+
const hasOrderId = useMemo(() => {
|
|
333
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
334
|
+
return urlParams.has('id');
|
|
353
335
|
}, []);
|
|
354
336
|
|
|
355
337
|
useEffect(() => {
|
|
356
|
-
if (
|
|
357
|
-
|
|
338
|
+
if (!hasOrderId) {
|
|
339
|
+
recalculateItems(debouncedShoppingCartBody);
|
|
358
340
|
}
|
|
359
|
-
|
|
360
|
-
recalculateItems(debouncedShoppingCartBody);
|
|
361
|
-
}, [debouncedShoppingCartBody]);
|
|
341
|
+
}, [debouncedShoppingCartBody, hasOrderId]);
|
|
362
342
|
|
|
363
343
|
/** Iframe code **/
|
|
364
344
|
useEffect(() => {
|
|
@@ -378,27 +358,30 @@ const PaymentOverviewBox: React.FC<Props> = ({ event }) => {
|
|
|
378
358
|
};
|
|
379
359
|
/** End Iframe code **/
|
|
380
360
|
|
|
381
|
-
const updateCartIfChanged = (newCart: IShoppingCart, recalculate = false) => {
|
|
382
|
-
if (isEqual(lastCartRef.current, newCart)) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
lastCartRef.current = newCart;
|
|
387
|
-
setShoppingCartValues(newCart, recalculate);
|
|
388
|
-
};
|
|
389
|
-
|
|
390
361
|
const recalculateItems = useCallback(async (body: ICalculateShoppingCartDto) => {
|
|
391
|
-
const { uuid } = body;
|
|
392
|
-
if (!uuid) return;
|
|
393
362
|
try {
|
|
394
|
-
const
|
|
395
|
-
|
|
363
|
+
const isInitial = !body.uuid;
|
|
364
|
+
|
|
365
|
+
let response: any;
|
|
366
|
+
if (isInitial) {
|
|
367
|
+
const existing = localStorage.getItem('cartToken');
|
|
368
|
+
const storedUuid =
|
|
369
|
+
existing && existing !== 'undefined' && existing !== 'null' ? existing : null;
|
|
370
|
+
response = await getCart(body.eventId, storedUuid);
|
|
371
|
+
} else {
|
|
372
|
+
response = await calculateCart(body);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const cart = response?.data;
|
|
396
376
|
|
|
397
377
|
if (cart) {
|
|
398
|
-
|
|
378
|
+
if (cart.uuid) {
|
|
379
|
+
localStorage.setItem('cartToken', cart.uuid);
|
|
380
|
+
}
|
|
381
|
+
setShoppingCartValues(cart, !isInitial);
|
|
399
382
|
}
|
|
400
383
|
} catch (err) {
|
|
401
|
-
console.error('Error
|
|
384
|
+
console.error('Error calculating cart:', err);
|
|
402
385
|
}
|
|
403
386
|
}, []);
|
|
404
387
|
|
package/src/form/TicketForm.tsx
CHANGED
|
@@ -220,7 +220,6 @@ const TicketForm: React.FC<Props> = ({
|
|
|
220
220
|
smsNotification: false,
|
|
221
221
|
smsNotificationPrice: 0,
|
|
222
222
|
iframeCampaignId: undefined,
|
|
223
|
-
promoCode: null,
|
|
224
223
|
promoCodes: [],
|
|
225
224
|
products: {},
|
|
226
225
|
shipping: {
|
|
@@ -327,6 +326,7 @@ const TicketForm: React.FC<Props> = ({
|
|
|
327
326
|
} else {
|
|
328
327
|
try {
|
|
329
328
|
const data: ITicketBody = cloneObject(values);
|
|
329
|
+
data.paymentMethodId = Number(values.paymentMethodId);
|
|
330
330
|
data.promoCodeIds = values.promoCodes?.map((item) => item.id);
|
|
331
331
|
const urlParams = new URLSearchParams(window.location.search);
|
|
332
332
|
const iframeCallback = urlParams.get('callback');
|
|
@@ -350,6 +350,7 @@ const TicketForm: React.FC<Props> = ({
|
|
|
350
350
|
{} as Record<number, ITicketFormTicket[]>
|
|
351
351
|
);
|
|
352
352
|
const { data: orderData } = await postOrder(data);
|
|
353
|
+
localStorage.removeItem('cartToken');
|
|
353
354
|
const items = [
|
|
354
355
|
...orderData.tickets.map((ticket) => ({
|
|
355
356
|
item_id: ticket.number,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
3
3
|
import {
|
|
4
4
|
Box,
|
|
@@ -39,6 +39,7 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
39
39
|
}) as ITicketFormTicket[];
|
|
40
40
|
const eventTimeslotId = watch('eventTimeslotId');
|
|
41
41
|
const [soldOutReleaseCategoryNames, setSoldOutReleaseCategoryNames] = useState<string[]>([]);
|
|
42
|
+
const isProcessingRef = useRef(false);
|
|
42
43
|
const { data: activeReleases, mutate } = useEventActiveReleases(
|
|
43
44
|
event.id,
|
|
44
45
|
false,
|
|
@@ -48,8 +49,8 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
48
49
|
const showLoading = !activeReleases && event.type !== EventType.RECURRING;
|
|
49
50
|
|
|
50
51
|
useEffect(() => {
|
|
51
|
-
selectedTickets();
|
|
52
|
-
}, [tickets]);
|
|
52
|
+
if (!isProcessingRef.current) selectedTickets();
|
|
53
|
+
}, [tickets, activeReleases]);
|
|
53
54
|
|
|
54
55
|
const isReleaseSelected = (id: number) => !!tickets.find((ticket) => ticket.releaseId === id);
|
|
55
56
|
|
|
@@ -92,7 +93,7 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
92
93
|
const lockedSelectedReleases: boolean[] | undefined = releases?.map((item, index) => {
|
|
93
94
|
const nextRelease = releases?.find(
|
|
94
95
|
(item2) =>
|
|
95
|
-
item2.releaseCategoryName === item.releaseCategoryName && item2.order === item.order
|
|
96
|
+
item2.releaseCategoryName === item.releaseCategoryName && item2.order === item.order + 1
|
|
96
97
|
);
|
|
97
98
|
const selected = tickets.find((ticket) => ticket.releaseId === item.id);
|
|
98
99
|
return !!nextRelease && item.locked && !!selected && index + 1 == tickets.length;
|
|
@@ -139,7 +140,11 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
139
140
|
soldOutReleaseCategories?.map((item) => item.releaseCategoryName) || []
|
|
140
141
|
);
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
const hasSelectableRelease = activeReleases?.some(
|
|
144
|
+
(release) => !isReleaseSelected(release.id) && !release.locked
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const shouldAddRow =
|
|
143
148
|
(soldOutReleaseCategories &&
|
|
144
149
|
selectedReleaseIsSoldOut(releases) &&
|
|
145
150
|
tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
|
|
@@ -149,8 +154,16 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
149
154
|
activeReleases?.length > tickets.length &&
|
|
150
155
|
tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
|
|
151
156
|
!allFilled.length) ||
|
|
152
|
-
(tickets.length < countReleaseCategories() && !allFilled.length)
|
|
153
|
-
|
|
157
|
+
(tickets.length < countReleaseCategories() && !allFilled.length);
|
|
158
|
+
|
|
159
|
+
const shouldRemoveEmptyRows =
|
|
160
|
+
allFilled.length > 0 &&
|
|
161
|
+
tickets.length > 1 &&
|
|
162
|
+
!hasSelectableRelease &&
|
|
163
|
+
(!soldOutReleaseCategories?.length || !selectedReleaseIsSoldOut(releases));
|
|
164
|
+
|
|
165
|
+
if (shouldAddRow) {
|
|
166
|
+
isProcessingRef.current = true;
|
|
154
167
|
setValue(`tickets.${event.id}`, [
|
|
155
168
|
...tickets,
|
|
156
169
|
{
|
|
@@ -162,6 +175,15 @@ const TicketSelection: React.FC<Props> = ({ event }) => {
|
|
|
162
175
|
extraFields: [],
|
|
163
176
|
},
|
|
164
177
|
]);
|
|
178
|
+
setTimeout(() => (isProcessingRef.current = false), 0);
|
|
179
|
+
} else if (shouldRemoveEmptyRows) {
|
|
180
|
+
// Only remove completely empty rows (no releaseId), keep rows where user started selecting
|
|
181
|
+
const nonEmptyTickets = tickets.filter((item) => item.releaseId);
|
|
182
|
+
if (nonEmptyTickets.length < tickets.length) {
|
|
183
|
+
isProcessingRef.current = true;
|
|
184
|
+
setValue(`tickets.${event.id}`, nonEmptyTickets);
|
|
185
|
+
setTimeout(() => (isProcessingRef.current = false), 0);
|
|
186
|
+
}
|
|
165
187
|
}
|
|
166
188
|
};
|
|
167
189
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect,
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
3
3
|
import { Box, Divider, Skeleton, Stack, Typography } from '@mui/material';
|
|
4
4
|
import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
|
|
@@ -22,6 +22,7 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
22
22
|
const { setValue, watch } = useFormContext<ITicketForm>();
|
|
23
23
|
const tickets: ITicketFormTicket[] = useWatch({ name: `tickets.${event.id}`, defaultValue: [] });
|
|
24
24
|
const eventTimeslotId = watch('eventTimeslotId');
|
|
25
|
+
const isProcessingRef = useRef(false);
|
|
25
26
|
const { data: activeReleases, mutate } = useEventActiveReleases(
|
|
26
27
|
event.id,
|
|
27
28
|
false,
|
|
@@ -29,8 +30,8 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
29
30
|
);
|
|
30
31
|
|
|
31
32
|
useEffect(() => {
|
|
32
|
-
selectedTickets();
|
|
33
|
-
}, [tickets]);
|
|
33
|
+
if (!isProcessingRef.current) selectedTickets();
|
|
34
|
+
}, [tickets, activeReleases]);
|
|
34
35
|
|
|
35
36
|
const countReleaseCategories = (): number => {
|
|
36
37
|
const grouped = groupBy(activeReleases || [], 'releaseCategoryName');
|
|
@@ -41,7 +42,7 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
41
42
|
const lockedSelectedReleases: boolean[] | undefined = releases?.map((item, index) => {
|
|
42
43
|
const nextRelease = releases?.find(
|
|
43
44
|
(item2) =>
|
|
44
|
-
item2.releaseCategoryName === item.releaseCategoryName && item2.order === item.order
|
|
45
|
+
item2.releaseCategoryName === item.releaseCategoryName && item2.order === item.order + 1
|
|
45
46
|
);
|
|
46
47
|
const selected = tickets.find((ticket) => ticket.releaseId === item.id);
|
|
47
48
|
return !!nextRelease && item.locked && !!selected && index + 1 == tickets.length;
|
|
@@ -51,6 +52,8 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
51
52
|
|
|
52
53
|
const countUnlockedReleases = () => activeReleases?.filter((item) => !item.locked).length || 0;
|
|
53
54
|
|
|
55
|
+
const isReleaseSelected = (id: number) => !!tickets.find((ticket) => ticket.releaseId === id);
|
|
56
|
+
|
|
54
57
|
const selectedTickets = async () => {
|
|
55
58
|
const releases = await mutate();
|
|
56
59
|
const allFilled = tickets.filter((item) => !item.releaseId || !item.quantity);
|
|
@@ -64,7 +67,34 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
64
67
|
)
|
|
65
68
|
);
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
// Unlock next releases when current release is sold out
|
|
71
|
+
if (soldOutReleaseCategories?.length && activeReleases) {
|
|
72
|
+
let hasChanges = false;
|
|
73
|
+
const updatedReleases = activeReleases.map((release) => {
|
|
74
|
+
// For each sold-out release, find and unlock the next one
|
|
75
|
+
const soldOutRelease = soldOutReleaseCategories.find(
|
|
76
|
+
(soldOut) =>
|
|
77
|
+
soldOut.releaseCategoryName === release.releaseCategoryName &&
|
|
78
|
+
soldOut.order === release.order - 1 &&
|
|
79
|
+
release.locked
|
|
80
|
+
);
|
|
81
|
+
if (soldOutRelease) {
|
|
82
|
+
hasChanges = true;
|
|
83
|
+
return { ...release, locked: false };
|
|
84
|
+
}
|
|
85
|
+
return release;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (hasChanges) {
|
|
89
|
+
await mutate(updatedReleases, false);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const hasSelectableRelease = activeReleases?.some(
|
|
94
|
+
(release) => !isReleaseSelected(release.id) && !release.locked
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const shouldAddRow =
|
|
68
98
|
(soldOutReleaseCategories &&
|
|
69
99
|
selectedReleaseIsSoldOut(releases) &&
|
|
70
100
|
tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
|
|
@@ -74,8 +104,16 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
74
104
|
activeReleases?.length > tickets.length &&
|
|
75
105
|
tickets.length < soldOutReleaseCategories.length + countUnlockedReleases() &&
|
|
76
106
|
!allFilled.length) ||
|
|
77
|
-
(tickets.length < countReleaseCategories() && !allFilled.length)
|
|
78
|
-
|
|
107
|
+
(tickets.length < countReleaseCategories() && !allFilled.length);
|
|
108
|
+
|
|
109
|
+
const shouldRemoveEmptyRows =
|
|
110
|
+
allFilled.length > 0 &&
|
|
111
|
+
tickets.length > 1 &&
|
|
112
|
+
!hasSelectableRelease &&
|
|
113
|
+
(!soldOutReleaseCategories?.length || !selectedReleaseIsSoldOut(releases));
|
|
114
|
+
|
|
115
|
+
if (shouldAddRow) {
|
|
116
|
+
isProcessingRef.current = true;
|
|
79
117
|
setValue(`tickets.${event.id}`, [
|
|
80
118
|
...tickets,
|
|
81
119
|
{
|
|
@@ -87,6 +125,15 @@ const TicketWithMerchandiseSelection: React.FC<Props> = ({ event }) => {
|
|
|
87
125
|
extraFields: [],
|
|
88
126
|
},
|
|
89
127
|
]);
|
|
128
|
+
setTimeout(() => (isProcessingRef.current = false), 0);
|
|
129
|
+
} else if (shouldRemoveEmptyRows) {
|
|
130
|
+
// Only remove completely empty rows (no releaseId), keep rows where user started selecting
|
|
131
|
+
const nonEmptyTickets = tickets.filter((item) => item.releaseId);
|
|
132
|
+
if (nonEmptyTickets.length < tickets.length) {
|
|
133
|
+
isProcessingRef.current = true;
|
|
134
|
+
setValue(`tickets.${event.id}`, nonEmptyTickets);
|
|
135
|
+
setTimeout(() => (isProcessingRef.current = false), 0);
|
|
136
|
+
}
|
|
90
137
|
}
|
|
91
138
|
};
|
|
92
139
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Stack, Tooltip, Typography } from '@mui/material';
|
|
3
|
-
import {
|
|
4
|
-
import { fCurrency, fPercent } from '@utils/formatNumber';
|
|
3
|
+
import { fCurrency } from '@utils/formatNumber';
|
|
5
4
|
import { Iconify } from '@components/iconify';
|
|
6
5
|
import { IEvent } from '@utils/types/event.type';
|
|
7
6
|
import useResponsive from '@hooks/useResponsive';
|
|
@@ -23,7 +22,7 @@ const FeeBox: React.FC<Props> = ({ event, align = 'left' }) => {
|
|
|
23
22
|
|
|
24
23
|
return (
|
|
25
24
|
<Box mb={1} textAlign={isRight ? 'right' : undefined}>
|
|
26
|
-
{values.
|
|
25
|
+
{values.promoCodes.length > 0 && (
|
|
27
26
|
<Stack
|
|
28
27
|
direction="row"
|
|
29
28
|
justifyContent={isRight ? 'flex-end' : 'space-between'}
|
package/src/locales/cs.tsx
CHANGED
|
@@ -61,6 +61,9 @@ const cs = {
|
|
|
61
61
|
invalid_date: 'Datum není platné.',
|
|
62
62
|
terms_and_conditions: 'Musíte souhlasit s obchodními podmínkami.',
|
|
63
63
|
promo_code_invalid: 'Slevový kód není platný.',
|
|
64
|
+
promo_code_applied: 'Tento slevový kód již byl použit.',
|
|
65
|
+
promo_code_cant_combine: 'Tento slevový kód nelze kombinovat s jinými kódy.',
|
|
66
|
+
min_purchase_not_met: 'Minimální částka nákupu nebyla dosažena: ',
|
|
64
67
|
count_tickets_or_products: 'Musíte vybrat alespoň 1 vstupenku nebo produkt.',
|
|
65
68
|
},
|
|
66
69
|
},
|
package/src/locales/en.tsx
CHANGED
|
@@ -61,6 +61,9 @@ const en = {
|
|
|
61
61
|
invalid_date: 'Date is not valid.',
|
|
62
62
|
terms_and_conditions: 'You must agree to the terms and conditions.',
|
|
63
63
|
promo_code_invalid: 'Promo code is not valid.',
|
|
64
|
+
promo_code_applied: 'This promo code has already been applied.',
|
|
65
|
+
promo_code_cant_combine: 'This promo code cannot be combined with other codes.',
|
|
66
|
+
min_purchase_not_met: 'Minimum purchase amount not met: ',
|
|
64
67
|
count_tickets_or_products: 'You must select at least 1 ticket or product.',
|
|
65
68
|
},
|
|
66
69
|
},
|