@eventlook/sdk 1.4.48 → 1.4.49-beta.1

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 (233) hide show
  1. package/.env.example +1 -0
  2. package/dist/cjs/_virtual/_commonjsHelpers.js +8 -0
  3. package/dist/cjs/_virtual/_commonjsHelpers.js.map +1 -0
  4. package/dist/cjs/_virtual/index.js +6 -0
  5. package/dist/cjs/_virtual/index.js.map +1 -0
  6. package/dist/cjs/_virtual/index2.js +6 -0
  7. package/dist/cjs/_virtual/index2.js.map +1 -0
  8. package/dist/cjs/_virtual/index3.js +6 -0
  9. package/dist/cjs/_virtual/index3.js.map +1 -0
  10. package/dist/cjs/_virtual/react-is.development.js +6 -0
  11. package/dist/cjs/_virtual/react-is.development.js.map +1 -0
  12. package/dist/cjs/_virtual/react-is.development2.js +6 -0
  13. package/dist/cjs/_virtual/react-is.development2.js.map +1 -0
  14. package/dist/cjs/_virtual/react-is.production.js +6 -0
  15. package/dist/cjs/_virtual/react-is.production.js.map +1 -0
  16. package/dist/cjs/_virtual/react-is.production.min.js +6 -0
  17. package/dist/cjs/_virtual/react-is.production.min.js.map +1 -0
  18. package/dist/cjs/components/hook-form/FormProvider.js +2 -2
  19. package/dist/cjs/components/hook-form/FormProvider.js.map +1 -1
  20. package/dist/cjs/form/ChildEventDialog.js +3 -3
  21. package/dist/cjs/form/ChildEventDialog.js.map +1 -1
  22. package/dist/cjs/form/ContactPerson.js +1 -1
  23. package/dist/cjs/form/ContactPerson.js.map +1 -1
  24. package/dist/cjs/form/PaymentOverviewBox.js +47 -61
  25. package/dist/cjs/form/PaymentOverviewBox.js.map +1 -1
  26. package/dist/cjs/form/PaymentOverviewDrawer.js +157 -0
  27. package/dist/cjs/form/PaymentOverviewDrawer.js.map +1 -0
  28. package/dist/cjs/form/ReleaseWithMerchandise.js +57 -48
  29. package/dist/cjs/form/ReleaseWithMerchandise.js.map +1 -1
  30. package/dist/cjs/form/Shipping.js +21 -18
  31. package/dist/cjs/form/Shipping.js.map +1 -1
  32. package/dist/cjs/form/TicketForm.js +94 -33
  33. package/dist/cjs/form/TicketForm.js.map +1 -1
  34. package/dist/cjs/form/TicketQuantityControl.js +51 -0
  35. package/dist/cjs/form/TicketQuantityControl.js.map +1 -0
  36. package/dist/cjs/form/TicketSelection.js +5 -6
  37. package/dist/cjs/form/TicketSelection.js.map +1 -1
  38. package/dist/cjs/form/TicketSelectionMobile.js +98 -0
  39. package/dist/cjs/form/TicketSelectionMobile.js.map +1 -0
  40. package/dist/cjs/form/TicketWithMerchandiseSelection.js +3 -5
  41. package/dist/cjs/form/TicketWithMerchandiseSelection.js.map +1 -1
  42. package/dist/cjs/form/index.js +1 -1
  43. package/dist/cjs/form/index.js.map +1 -1
  44. package/dist/cjs/form/merchandise/MerchandiseSelection.js +14 -0
  45. package/dist/cjs/form/merchandise/MerchandiseSelection.js.map +1 -0
  46. package/dist/cjs/form/merchandise/MerchandiseSlider.js +40 -0
  47. package/dist/cjs/form/merchandise/MerchandiseSlider.js.map +1 -0
  48. package/dist/cjs/form/merchendise/MerchandiseSelection.js +19 -0
  49. package/dist/cjs/form/merchendise/MerchandiseSelection.js.map +1 -0
  50. package/dist/cjs/form/merchendise/MerchandiseSlider.js +75 -0
  51. package/dist/cjs/form/merchendise/MerchandiseSlider.js.map +1 -0
  52. package/dist/cjs/form/payment/FeeBox.js +4 -16
  53. package/dist/cjs/form/payment/FeeBox.js.map +1 -1
  54. package/dist/cjs/form/payment/PaymentOverviewCheckbox.js +33 -28
  55. package/dist/cjs/form/payment/PaymentOverviewCheckbox.js.map +1 -1
  56. package/dist/cjs/form/product/ProductCard.js +139 -36
  57. package/dist/cjs/form/product/ProductCard.js.map +1 -1
  58. package/dist/cjs/form/product/ProductVariantsDialog.js +157 -96
  59. package/dist/cjs/form/product/ProductVariantsDialog.js.map +1 -1
  60. package/dist/cjs/form/services/index.js +133 -0
  61. package/dist/cjs/form/services/index.js.map +1 -0
  62. package/dist/cjs/form/style.js +7 -3
  63. package/dist/cjs/form/style.js.map +1 -1
  64. package/dist/cjs/form/tickets/ReleaseDescription.js +23 -0
  65. package/dist/cjs/form/tickets/ReleaseDescription.js.map +1 -0
  66. package/dist/cjs/form/tickets/ReleaseWithMerchandise.js +141 -0
  67. package/dist/cjs/form/tickets/ReleaseWithMerchandise.js.map +1 -0
  68. package/dist/cjs/form/tickets/TicketQuantityControl.js +52 -0
  69. package/dist/cjs/form/tickets/TicketQuantityControl.js.map +1 -0
  70. package/dist/cjs/form/tickets/TicketSelection.js +139 -0
  71. package/dist/cjs/form/tickets/TicketSelection.js.map +1 -0
  72. package/dist/cjs/form/tickets/TicketSelectionMap.js +73 -0
  73. package/dist/cjs/form/tickets/TicketSelectionMap.js.map +1 -0
  74. package/dist/cjs/form/tickets/TicketSelectionMobile.js +90 -0
  75. package/dist/cjs/form/tickets/TicketSelectionMobile.js.map +1 -0
  76. package/dist/cjs/form/tickets/TicketWithMerchandiseSelection.js +117 -0
  77. package/dist/cjs/form/tickets/TicketWithMerchandiseSelection.js.map +1 -0
  78. package/dist/cjs/hooks/useConsentScrollOnDrawerOpen.js +59 -0
  79. package/dist/cjs/hooks/useConsentScrollOnDrawerOpen.js.map +1 -0
  80. package/dist/cjs/hooks/useScrollToFirstError.js +64 -0
  81. package/dist/cjs/hooks/useScrollToFirstError.js.map +1 -0
  82. package/dist/cjs/locales/cs.js +18 -3
  83. package/dist/cjs/locales/cs.js.map +1 -1
  84. package/dist/cjs/locales/en.js +17 -2
  85. package/dist/cjs/locales/en.js.map +1 -1
  86. package/dist/cjs/locales/es.js +16 -1
  87. package/dist/cjs/locales/es.js.map +1 -1
  88. package/dist/cjs/locales/pl.js +16 -1
  89. package/dist/cjs/locales/pl.js.map +1 -1
  90. package/dist/cjs/locales/sk.js +17 -2
  91. package/dist/cjs/locales/sk.js.map +1 -1
  92. package/dist/cjs/locales/uk.js +16 -1
  93. package/dist/cjs/locales/uk.js.map +1 -1
  94. package/dist/cjs/utils/data/global.js +2 -0
  95. package/dist/cjs/utils/data/global.js.map +1 -1
  96. package/dist/esm/_virtual/_commonjsHelpers.js +6 -0
  97. package/dist/esm/_virtual/_commonjsHelpers.js.map +1 -0
  98. package/dist/esm/_virtual/index.js +4 -0
  99. package/dist/esm/_virtual/index.js.map +1 -0
  100. package/dist/esm/_virtual/index2.js +4 -0
  101. package/dist/esm/_virtual/index2.js.map +1 -0
  102. package/dist/esm/_virtual/index3.js +4 -0
  103. package/dist/esm/_virtual/index3.js.map +1 -0
  104. package/dist/esm/_virtual/react-is.development.js +4 -0
  105. package/dist/esm/_virtual/react-is.development.js.map +1 -0
  106. package/dist/esm/_virtual/react-is.development2.js +4 -0
  107. package/dist/esm/_virtual/react-is.development2.js.map +1 -0
  108. package/dist/esm/_virtual/react-is.production.js +4 -0
  109. package/dist/esm/_virtual/react-is.production.js.map +1 -0
  110. package/dist/esm/_virtual/react-is.production.min.js +4 -0
  111. package/dist/esm/_virtual/react-is.production.min.js.map +1 -0
  112. package/dist/esm/components/hook-form/FormProvider.js +2 -2
  113. package/dist/esm/components/hook-form/FormProvider.js.map +1 -1
  114. package/dist/esm/form/ChildEventDialog.js +3 -3
  115. package/dist/esm/form/ChildEventDialog.js.map +1 -1
  116. package/dist/esm/form/ContactPerson.js +1 -1
  117. package/dist/esm/form/ContactPerson.js.map +1 -1
  118. package/dist/esm/form/PaymentOverviewBox.js +48 -62
  119. package/dist/esm/form/PaymentOverviewBox.js.map +1 -1
  120. package/dist/esm/form/PaymentOverviewDrawer.js +153 -0
  121. package/dist/esm/form/PaymentOverviewDrawer.js.map +1 -0
  122. package/dist/esm/form/ReleaseWithMerchandise.js +58 -49
  123. package/dist/esm/form/ReleaseWithMerchandise.js.map +1 -1
  124. package/dist/esm/form/Shipping.js +21 -18
  125. package/dist/esm/form/Shipping.js.map +1 -1
  126. package/dist/esm/form/TicketForm.js +96 -35
  127. package/dist/esm/form/TicketForm.js.map +1 -1
  128. package/dist/esm/form/TicketQuantityControl.js +47 -0
  129. package/dist/esm/form/TicketQuantityControl.js.map +1 -0
  130. package/dist/esm/form/TicketSelection.js +5 -6
  131. package/dist/esm/form/TicketSelection.js.map +1 -1
  132. package/dist/esm/form/TicketSelectionMobile.js +94 -0
  133. package/dist/esm/form/TicketSelectionMobile.js.map +1 -0
  134. package/dist/esm/form/TicketWithMerchandiseSelection.js +4 -6
  135. package/dist/esm/form/TicketWithMerchandiseSelection.js.map +1 -1
  136. package/dist/esm/form/index.js +1 -1
  137. package/dist/esm/form/index.js.map +1 -1
  138. package/dist/esm/form/merchandise/MerchandiseSelection.js +10 -0
  139. package/dist/esm/form/merchandise/MerchandiseSelection.js.map +1 -0
  140. package/dist/esm/form/merchandise/MerchandiseSlider.js +36 -0
  141. package/dist/esm/form/merchandise/MerchandiseSlider.js.map +1 -0
  142. package/dist/esm/form/merchendise/MerchandiseSelection.js +15 -0
  143. package/dist/esm/form/merchendise/MerchandiseSelection.js.map +1 -0
  144. package/dist/esm/form/merchendise/MerchandiseSlider.js +71 -0
  145. package/dist/esm/form/merchendise/MerchandiseSlider.js.map +1 -0
  146. package/dist/esm/form/payment/FeeBox.js +5 -17
  147. package/dist/esm/form/payment/FeeBox.js.map +1 -1
  148. package/dist/esm/form/payment/PaymentOverviewCheckbox.js +35 -30
  149. package/dist/esm/form/payment/PaymentOverviewCheckbox.js.map +1 -1
  150. package/dist/esm/form/product/ProductCard.js +140 -37
  151. package/dist/esm/form/product/ProductCard.js.map +1 -1
  152. package/dist/esm/form/product/ProductVariantsDialog.js +159 -98
  153. package/dist/esm/form/product/ProductVariantsDialog.js.map +1 -1
  154. package/dist/esm/form/services/index.js +129 -0
  155. package/dist/esm/form/services/index.js.map +1 -0
  156. package/dist/esm/form/style.js +7 -3
  157. package/dist/esm/form/style.js.map +1 -1
  158. package/dist/esm/form/tickets/ReleaseDescription.js +19 -0
  159. package/dist/esm/form/tickets/ReleaseDescription.js.map +1 -0
  160. package/dist/esm/form/tickets/ReleaseWithMerchandise.js +137 -0
  161. package/dist/esm/form/tickets/ReleaseWithMerchandise.js.map +1 -0
  162. package/dist/esm/form/tickets/TicketQuantityControl.js +48 -0
  163. package/dist/esm/form/tickets/TicketQuantityControl.js.map +1 -0
  164. package/dist/esm/form/tickets/TicketSelection.js +135 -0
  165. package/dist/esm/form/tickets/TicketSelection.js.map +1 -0
  166. package/dist/esm/form/tickets/TicketSelectionMap.js +69 -0
  167. package/dist/esm/form/tickets/TicketSelectionMap.js.map +1 -0
  168. package/dist/esm/form/tickets/TicketSelectionMobile.js +86 -0
  169. package/dist/esm/form/tickets/TicketSelectionMobile.js.map +1 -0
  170. package/dist/esm/form/tickets/TicketWithMerchandiseSelection.js +113 -0
  171. package/dist/esm/form/tickets/TicketWithMerchandiseSelection.js.map +1 -0
  172. package/dist/esm/hooks/useConsentScrollOnDrawerOpen.js +55 -0
  173. package/dist/esm/hooks/useConsentScrollOnDrawerOpen.js.map +1 -0
  174. package/dist/esm/hooks/useScrollToFirstError.js +60 -0
  175. package/dist/esm/hooks/useScrollToFirstError.js.map +1 -0
  176. package/dist/esm/locales/cs.js +18 -3
  177. package/dist/esm/locales/cs.js.map +1 -1
  178. package/dist/esm/locales/en.js +17 -2
  179. package/dist/esm/locales/en.js.map +1 -1
  180. package/dist/esm/locales/es.js +16 -1
  181. package/dist/esm/locales/es.js.map +1 -1
  182. package/dist/esm/locales/pl.js +16 -1
  183. package/dist/esm/locales/pl.js.map +1 -1
  184. package/dist/esm/locales/sk.js +17 -2
  185. package/dist/esm/locales/sk.js.map +1 -1
  186. package/dist/esm/locales/uk.js +16 -1
  187. package/dist/esm/locales/uk.js.map +1 -1
  188. package/dist/esm/utils/data/global.js +2 -1
  189. package/dist/esm/utils/data/global.js.map +1 -1
  190. package/dist/types/components/Image.d.ts +4 -4
  191. package/dist/types/form/PaymentOverviewDrawer.d.ts +8 -0
  192. package/dist/types/form/merchendise/MerchandiseSelection.d.ts +9 -0
  193. package/dist/types/form/merchendise/MerchandiseSlider.d.ts +10 -0
  194. package/dist/types/form/merchendise/MerchendiseSlider.d.ts +0 -0
  195. package/dist/types/form/style.d.ts +1 -1
  196. package/package.json +5 -1
  197. package/rollup.config.mjs +2 -0
  198. package/src/components/hook-form/FormProvider.tsx +5 -2
  199. package/src/form/ChildEventDialog.tsx +3 -3
  200. package/src/form/ContactPerson.tsx +1 -1
  201. package/src/form/PaymentOverviewBox.tsx +89 -122
  202. package/src/form/PaymentOverviewDrawer.tsx +238 -0
  203. package/src/form/Shipping.tsx +29 -17
  204. package/src/form/TicketForm.tsx +140 -39
  205. package/src/form/index.tsx +3 -1
  206. package/src/form/merchandise/MerchandiseSelection.tsx +24 -0
  207. package/src/form/merchandise/MerchandiseSlider.tsx +62 -0
  208. package/src/form/payment/FeeBox.tsx +4 -31
  209. package/src/form/payment/PaymentOverviewCheckbox.tsx +57 -56
  210. package/src/form/product/ProductCard.tsx +250 -59
  211. package/src/form/product/ProductVariantsDialog.tsx +253 -140
  212. package/src/form/services/index.tsx +263 -0
  213. package/src/form/style.ts +9 -3
  214. package/src/form/tickets/ReleaseDescription.tsx +46 -0
  215. package/src/form/tickets/ReleaseWithMerchandise.tsx +239 -0
  216. package/src/form/tickets/TicketQuantityControl.tsx +94 -0
  217. package/src/form/{TicketSelection.tsx → tickets/TicketSelection.tsx} +24 -128
  218. package/src/form/{TicketSelectionMap.tsx → tickets/TicketSelectionMap.tsx} +9 -1
  219. package/src/form/tickets/TicketSelectionMobile.tsx +177 -0
  220. package/src/form/{TicketWithMerchandiseSelection.tsx → tickets/TicketWithMerchandiseSelection.tsx} +3 -7
  221. package/src/hooks/useConsentScrollOnDrawerOpen.ts +73 -0
  222. package/src/hooks/useScrollToFirstError.ts +94 -0
  223. package/src/locales/cs.tsx +18 -3
  224. package/src/locales/en.tsx +17 -2
  225. package/src/locales/es.tsx +16 -1
  226. package/src/locales/pl.tsx +16 -1
  227. package/src/locales/sk.tsx +17 -2
  228. package/src/locales/uk.tsx +16 -1
  229. package/src/utils/data/global.ts +1 -0
  230. package/tsconfig.json +2 -1
  231. package/.claude/settings.local.json +0 -9
  232. package/src/form/MerchandiseSelection.tsx +0 -29
  233. package/src/form/ReleaseWithMerchandise.tsx +0 -230
@@ -1,12 +1,11 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import {
3
- Badge,
3
+ Box,
4
4
  Button,
5
5
  Dialog,
6
6
  DialogActions,
7
7
  DialogContent,
8
8
  DialogTitle,
9
- IconButton,
10
9
  Stack,
11
10
  Typography,
12
11
  } from '@mui/material';
@@ -17,16 +16,16 @@ import { ITicketFormTicket } from '@utils/types/ticket.type';
17
16
  import { IEventProductForm } from '@utils/types/product.type';
18
17
  import { fCurrency } from '@utils/formatNumber';
19
18
  import useGlobal from '@hooks/useGlobal.ts';
20
- import { Iconify } from '@components';
21
19
 
22
20
  interface Props {
23
21
  eventProduct: IEventProduct;
24
22
  eventId: number;
25
23
  openDialog: boolean;
26
- callback: (variant: IEventProductForm) => void;
24
+ callback: (variants: IEventProductForm[]) => void;
27
25
  onClose?: () => void;
28
26
  selectedQuantityByVariant?: Record<number, number>;
29
27
  isOnlyMerchandise?: boolean;
28
+ canAddOnlyOneAtATime?: boolean;
30
29
  }
31
30
 
32
31
  const ProductVariantsDialog: React.FC<Props> = ({
@@ -37,13 +36,11 @@ const ProductVariantsDialog: React.FC<Props> = ({
37
36
  selectedQuantityByVariant,
38
37
  isOnlyMerchandise,
39
38
  eventId,
39
+ canAddOnlyOneAtATime,
40
40
  }) => {
41
41
  const { t, lang, options } = useGlobal();
42
42
  const { showSnackbar } = useGlobal();
43
- const [selectedVariant, setSelectedVariant] = useState<IEventProductForm | null>(null);
44
- const [variantPrice, setVariantPrice] = useState<number | null>(null);
45
- const [variantQuantity, setVariantQuantity] = useState<number>(1);
46
- const [isVariantClicked, setIsVariantClicked] = useState(false);
43
+ const [draftQuantities, setDraftQuantities] = useState<Record<number, number>>({});
47
44
  const tickets: ITicketFormTicket[] = useWatch({ name: `tickets.${eventId}`, defaultValue: [] });
48
45
  const products: IEventProductForm[] = useWatch({ name: `products.${eventId}`, defaultValue: [] });
49
46
 
@@ -56,43 +53,41 @@ const ProductVariantsDialog: React.FC<Props> = ({
56
53
  }, [openDialog]);
57
54
 
58
55
  useEffect(() => {
59
- if (isVariantClicked) {
60
- setVariantQuantity(
61
- selectedQuantityByVariant &&
62
- selectedVariant &&
63
- selectedQuantityByVariant[selectedVariant.eventProductVariantId] !== undefined
64
- ? selectedQuantityByVariant[selectedVariant.eventProductVariantId]
65
- : 1
66
- );
67
- setIsVariantClicked(false);
56
+ if (openDialog) {
57
+ setDraftQuantities(canAddOnlyOneAtATime ? {} : (selectedQuantityByVariant ?? {}));
68
58
  }
69
- }, [selectedVariant, isVariantClicked]);
70
-
71
- const handleOnClick = (variant: IEventProductVariant) => {
72
- setVariantPrice(variant.priceWithVat);
73
- setSelectedVariant({
74
- eventProductVariantId: variant.id,
75
- productVariantId: variant.productVariant.id,
76
- quantity: 1,
77
- price: variant.priceWithVat,
78
- excludedShippingMethodIds: eventProduct.excludedShippingMethods.map((method) => method.id),
79
- });
80
- setIsVariantClicked(true);
81
- };
59
+ }, [openDialog, selectedQuantityByVariant, canAddOnlyOneAtATime]);
82
60
 
83
61
  const handleOnAdd = () => {
84
- if (!selectedVariant) {
62
+ const selectedVariants = eventProduct.eventProductVariants
63
+ .map((variant) => {
64
+ const quantity = draftQuantities[variant.id] ?? 0;
65
+ const selectedQty = selectedQuantityByVariant?.[variant.id] ?? 0;
66
+ if (quantity <= 0 && selectedQty <= 0) return null;
67
+ return {
68
+ eventProductVariantId: variant.id,
69
+ productVariantId: variant.productVariant.id,
70
+ quantity,
71
+ price: variant.priceWithVat,
72
+ excludedShippingMethodIds: eventProduct.excludedShippingMethods.map(
73
+ (method) => method.id
74
+ ),
75
+ } as IEventProductForm;
76
+ })
77
+ .filter((variant): variant is IEventProductForm => Boolean(variant));
78
+
79
+ if (selectedVariants.length === 0) {
85
80
  showSnackbar(t('components.product_variant_dialog.select_variant'), { variant: 'error' });
86
81
  return;
87
82
  }
88
- callback(selectedVariant);
89
- setSelectedVariant(null);
83
+
84
+ callback(selectedVariants);
85
+ setDraftQuantities({});
90
86
  onClose?.();
91
87
  };
92
88
 
93
- const isVariantDisabled = (variant: IEventProductVariant) => {
89
+ const getUsedCount = (variant: IEventProductVariant) => {
94
90
  let countUsed = 0;
95
- let selectedVariantQuantity = 0;
96
91
 
97
92
  for (const product of products) {
98
93
  if (product.productVariantId === variant.productVariant.id) {
@@ -105,70 +100,104 @@ const ProductVariantsDialog: React.FC<Props> = ({
105
100
  for (const product of ticket.products) {
106
101
  if (product.productVariantId === variant.productVariant.id) {
107
102
  countUsed += product.quantity;
108
- selectedVariantQuantity = product.quantity;
109
103
  }
110
104
  }
111
105
  }
112
106
  }
113
-
114
- return (
115
- (variant.productVariant.quantity &&
116
- countUsed + variant.productVariant.sold >= variant.productVariant.quantity) ||
117
- selectedVariantQuantity >= 10
118
- );
107
+ return countUsed;
119
108
  };
120
109
 
121
- const isVariantSoldOut = () => {
122
- const variant = eventProduct.eventProductVariants.find(
123
- (product) => product.id === selectedVariant?.eventProductVariantId
124
- );
125
- let countUsed = variantQuantity;
110
+ const getMaxAvailable = (variant: IEventProductVariant) => {
111
+ if (!variant.productVariant.quantity) return Number.POSITIVE_INFINITY;
112
+ return variant.productVariant.quantity - variant.productVariant.sold - getUsedCount(variant);
113
+ };
126
114
 
127
- for (const ticket of tickets) {
128
- if (ticket.products) {
129
- for (const product of ticket.products) {
130
- if (product.productVariantId === variant?.productVariant.id) {
131
- countUsed += product.quantity;
132
- }
133
- }
134
- }
135
- }
115
+ const isVariantDisabled = (variant: IEventProductVariant) => {
116
+ if (!variant.productVariant.quantity) return false;
117
+ return getMaxAvailable(variant) <= 0;
118
+ };
136
119
 
137
- if (variant && variant.productVariant.quantity) {
138
- if (
139
- variant.productVariant.sold + countUsed >= variant.productVariant.quantity ||
140
- variantQuantity >= 10
141
- ) {
142
- return true;
120
+ const handleAddQuantity = (variant: IEventProductVariant) => {
121
+ setDraftQuantities((prev) => {
122
+ const current = prev[variant.id] ?? 0;
123
+ const next = current + 1;
124
+ const maxAvailable = getMaxAvailable(variant);
125
+ if (next > 10 || next > maxAvailable) return prev;
126
+ if (canAddOnlyOneAtATime) {
127
+ // Only allow one variant to be selected
128
+ return { [variant.id]: 1 };
143
129
  }
144
- }
145
- return false;
130
+ return { ...prev, [variant.id]: next };
131
+ });
146
132
  };
147
133
 
148
- const handleAddQuantity = () => {
149
- setVariantQuantity(variantQuantity + 1);
150
- setSelectedVariant((prevState) => {
151
- if (!prevState) return null;
152
- return {
153
- ...prevState,
154
- quantity: variantQuantity + 1,
155
- };
134
+ const handleRemoveQuantity = (variant: IEventProductVariant) => {
135
+ setDraftQuantities((prev) => {
136
+ const current = prev[variant.id] ?? 0;
137
+ const next = current - 1;
138
+ if (next <= 0) {
139
+ const rest = { ...prev };
140
+ delete rest[variant.id];
141
+ return rest;
142
+ }
143
+ if (canAddOnlyOneAtATime) {
144
+ // Remove the only variant
145
+ return {};
146
+ }
147
+ return { ...prev, [variant.id]: next };
156
148
  });
157
149
  };
158
150
 
159
- const handleRemoveQuantity = () => {
160
- if (variantQuantity > 0) {
161
- setVariantQuantity(variantQuantity - 1);
151
+ const handleRemoveProduct = () => {
152
+ const variantsToRemove = eventProduct.eventProductVariants
153
+ .filter((variant) => (selectedQuantityByVariant?.[variant.id] ?? 0) > 0)
154
+ .map(
155
+ (variant) =>
156
+ ({
157
+ eventProductVariantId: variant.id,
158
+ productVariantId: variant.productVariant.id,
159
+ quantity: 0,
160
+ price: variant.priceWithVat,
161
+ excludedShippingMethodIds: eventProduct.excludedShippingMethods.map(
162
+ (method) => method.id
163
+ ),
164
+ }) as IEventProductForm
165
+ );
166
+
167
+ if (variantsToRemove.length === 0) {
168
+ return;
162
169
  }
163
- setSelectedVariant((prevState) => {
164
- if (!prevState) return null;
165
- return {
166
- ...prevState,
167
- quantity: variantQuantity - 1,
168
- };
169
- });
170
+
171
+ callback(variantsToRemove);
172
+ setDraftQuantities({});
173
+ onClose?.();
170
174
  };
171
175
 
176
+ const hasSelectedVariants = eventProduct.eventProductVariants.some(
177
+ (variant) => (selectedQuantityByVariant?.[variant.id] ?? 0) > 0
178
+ );
179
+
180
+ const variantsById = useMemo(
181
+ () => new Map(eventProduct.eventProductVariants.map((variant) => [variant.id, variant])),
182
+ [eventProduct.eventProductVariants]
183
+ );
184
+
185
+ const totalDraftPrice = useMemo(
186
+ () =>
187
+ Object.entries(draftQuantities).reduce((total, [variantId, qty]) => {
188
+ const variant = variantsById.get(Number(variantId));
189
+ if (!variant) return total;
190
+ return total + variant.priceWithVat * qty;
191
+ }, 0),
192
+ [draftQuantities, variantsById]
193
+ );
194
+
195
+ const hasDraftSelection = useMemo(
196
+ () =>
197
+ eventProduct.eventProductVariants.some((variant) => (draftQuantities[variant.id] ?? 0) > 0),
198
+ [eventProduct.eventProductVariants, draftQuantities]
199
+ );
200
+
172
201
  return (
173
202
  <Dialog
174
203
  open={openDialog}
@@ -187,16 +216,19 @@ const ProductVariantsDialog: React.FC<Props> = ({
187
216
  },
188
217
  }}
189
218
  >
190
- <DialogTitle />
191
- <DialogContent>
219
+ <DialogTitle
220
+ sx={{ textAlign: 'center', fontWeight: 700, fontSize: { xs: '1.5rem', sm: '1.5rem' } }}
221
+ >
222
+ {eventProduct.product.name}
223
+ </DialogTitle>
224
+ <DialogContent sx={{ pb: 0 }}>
192
225
  <Stack spacing={1}>
193
226
  <Image
194
227
  src={eventProduct.product.previewImage.url}
195
228
  alt={eventProduct.product.name}
196
- ratio="1/1"
197
- sx={{ borderRadius: 2 }}
229
+ ratio="16/9"
230
+ sx={{ borderRadius: 1 }}
198
231
  />
199
- <Typography variant="h5">{eventProduct.product.name}</Typography>
200
232
  <Typography variant="body2" color="primary">
201
233
  {t('form.labels.category')}: {eventProduct.product.category.value.text}
202
234
  </Typography>
@@ -207,64 +239,145 @@ const ProductVariantsDialog: React.FC<Props> = ({
207
239
  </Stack>
208
240
  </DialogContent>
209
241
  <DialogActions sx={{ justifyContent: 'flex-start' }}>
210
- <Stack spacing={2} width="100%">
211
- <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap textAlign="left">
212
- {eventProduct.eventProductVariants.map((variant) => (
213
- <Button
214
- key={variant.id}
215
- onClick={() => handleOnClick(variant)}
216
- variant={
217
- variant.id === selectedVariant?.eventProductVariantId ? 'contained' : 'outlined'
218
- }
219
- size="large"
220
- disabled={isOnlyMerchandise ? false : isVariantDisabled(variant)}
221
- >
222
- <Badge
223
- color="primary"
224
- badgeContent={
225
- selectedQuantityByVariant ? selectedQuantityByVariant[variant.id] : 0
226
- }
227
- sx={{ position: 'unset' }}
242
+ <Stack spacing={1} width="100%">
243
+ <Stack spacing={1}>
244
+ {eventProduct.eventProductVariants.map((variant) => {
245
+ const draftQty = draftQuantities[variant.id] ?? 0;
246
+ const maxAvailable = getMaxAvailable(variant);
247
+ const isAddDisabled = isVariantDisabled(variant) || maxAvailable <= 0;
248
+ const isAnotherSelected =
249
+ canAddOnlyOneAtATime &&
250
+ Object.keys(draftQuantities).length > 0 &&
251
+ !draftQuantities[variant.id];
252
+ return (
253
+ <Stack
254
+ key={variant.id}
255
+ direction="row"
256
+ spacing={1}
257
+ alignItems="center"
258
+ width="100%"
228
259
  >
229
- {variant.productVariant.value}
230
- </Badge>
231
- </Button>
232
- ))}
260
+ <Box
261
+ sx={{
262
+ minWidth: 64,
263
+ maxWidth: 64,
264
+ height: 36,
265
+ borderRadius: 1,
266
+ border: 1,
267
+ borderColor: 'divider',
268
+ bgcolor: 'background.paper',
269
+ display: 'flex',
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ }}
273
+ >
274
+ <Typography variant="body2" fontWeight={600}>
275
+ {variant.productVariant.value}
276
+ </Typography>
277
+ </Box>
278
+ {draftQty > 0 ? (
279
+ <Box
280
+ sx={{
281
+ flex: 1,
282
+ display: 'grid',
283
+ gridTemplateColumns: canAddOnlyOneAtATime
284
+ ? 'repeat(2, minmax(0, 1fr))'
285
+ : 'repeat(3, minmax(0, 1fr))',
286
+ gap: 1,
287
+ alignItems: 'center',
288
+ }}
289
+ >
290
+ <Button
291
+ variant="outlined"
292
+ onClick={() => handleRemoveQuantity(variant)}
293
+ disabled={draftQty < 1}
294
+ sx={{
295
+ minWidth: 0,
296
+ height: 36,
297
+ borderRadius: 1,
298
+ fontWeight: 700,
299
+ borderColor: 'grey.300',
300
+ color: 'text.primary',
301
+ }}
302
+ >
303
+ -
304
+ </Button>
305
+ <Typography
306
+ sx={{
307
+ minWidth: 0,
308
+ height: 36,
309
+ borderRadius: 1,
310
+ border: 1,
311
+ borderColor: 'divider',
312
+ boxSizing: 'border-box',
313
+ display: 'flex',
314
+ alignItems: 'center',
315
+ justifyContent: 'center',
316
+ textAlign: 'center',
317
+ fontWeight: 700,
318
+ color: 'text.primary',
319
+ }}
320
+ >
321
+ {draftQty}
322
+ </Typography>
323
+ {!canAddOnlyOneAtATime && (
324
+ <Button
325
+ variant="contained"
326
+ onClick={() => handleAddQuantity(variant)}
327
+ disabled={draftQty >= 10 || draftQty >= maxAvailable}
328
+ sx={{ minWidth: 0, height: 36, borderRadius: 1, fontWeight: 700 }}
329
+ >
330
+ +
331
+ </Button>
332
+ )}
333
+ </Box>
334
+ ) : (
335
+ <Button
336
+ variant="contained"
337
+ onClick={() => handleAddQuantity(variant)}
338
+ disabled={isOnlyMerchandise ? false : isAddDisabled || isAnotherSelected}
339
+ sx={{
340
+ flex: 1,
341
+ height: 36,
342
+ borderRadius: 1,
343
+ fontWeight: 700,
344
+ textTransform: 'none',
345
+ }}
346
+ >
347
+ {t('add')}
348
+ </Button>
349
+ )}
350
+
351
+ <Typography variant="body2">
352
+ {variant.priceWithVat > 0
353
+ ? fCurrency(variant.priceWithVat, lang, eventProduct.product.currency)
354
+ : t('free')}
355
+ </Typography>
356
+ </Stack>
357
+ );
358
+ })}
233
359
  </Stack>
234
- <Stack direction="row" justifyContent="space-between" alignItems="center" pt={1}>
235
- {selectedVariant && isOnlyMerchandise && (
236
- <Stack direction="row" alignItems="center" spacing={1}>
237
- <IconButton
238
- size="small"
239
- color="primary"
240
- onClick={handleRemoveQuantity}
241
- disabled={variantQuantity < 1}
242
- >
243
- <Iconify icon="eva:minus-fill" />
244
- </IconButton>
245
- <Typography color="grey.500">{variantQuantity}</Typography>
246
- <IconButton
247
- size="small"
248
- color="primary"
249
- onClick={handleAddQuantity}
250
- disabled={isVariantSoldOut()}
251
- >
252
- <Iconify icon="eva:plus-fill" />
253
- </IconButton>
254
- </Stack>
255
- )}
256
- <Typography variant="h4" textAlign="right" mt={0}>
257
- {variantPrice !== null
258
- ? variantPrice > 0
259
- ? fCurrency(variantPrice, lang, eventProduct.product.currency)
260
- : t('free')
261
- : t('unselected')}
360
+ <Stack
361
+ direction="row"
362
+ spacing={1}
363
+ justifyContent="space-between"
364
+ alignItems="center"
365
+ width="100%"
366
+ >
367
+ <Typography variant="body2">{t('form.labels.total')}</Typography>
368
+ <Typography variant="body2" fontWeight={600}>
369
+ {fCurrency(totalDraftPrice, lang, eventProduct.product.currency)}
262
370
  </Typography>
263
371
  </Stack>
264
372
  <Stack direction="row" spacing={1} justifyContent="flex-end" width="100%">
373
+ {hasSelectedVariants && !canAddOnlyOneAtATime && (
374
+ <Button variant="outlined" color="error" onClick={handleRemoveProduct}>
375
+ {t('remove')}
376
+ </Button>
377
+ )}
265
378
  {onClose && <Button onClick={onClose}>{t('cancel')}</Button>}
266
- <Button variant="contained" onClick={handleOnAdd} disabled={!selectedVariant}>
267
- {t('add')}
379
+ <Button variant="contained" onClick={handleOnAdd} disabled={!hasDraftSelection}>
380
+ {t('confirm')}
268
381
  </Button>
269
382
  </Stack>
270
383
  </Stack>