@lmnto/h-mall-shared 1.0.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 (262) hide show
  1. package/app.css +457 -0
  2. package/bitbucket-pipelines.yml +223 -0
  3. package/package.json +43 -0
  4. package/public/gif/balloon.gif +0 -0
  5. package/public/gif/gift.gif +0 -0
  6. package/public/img/ai-dialog.png +0 -0
  7. package/public/img/ai-x1-banner.png +0 -0
  8. package/public/img/ai-x1.png +0 -0
  9. package/public/img/alert-Icon.png +0 -0
  10. package/public/img/aleta-payment-kyc-require.png +0 -0
  11. package/public/img/aleta-payment-pending.png +0 -0
  12. package/public/img/aleta-payment-rejected.png +0 -0
  13. package/public/img/aleta-payment-subscription-required.png +0 -0
  14. package/public/img/boost-banner-bg.png +0 -0
  15. package/public/img/boost-banner-right.png +0 -0
  16. package/public/img/boost-banner.png +0 -0
  17. package/public/img/boost-dialog-bg.png +0 -0
  18. package/public/img/boostPower.png +0 -0
  19. package/public/img/boostTooltip.png +0 -0
  20. package/public/img/brand-icon.svg +23 -0
  21. package/public/img/brand-text.svg +11 -0
  22. package/public/img/card-icon.png +0 -0
  23. package/public/img/cashback-Icon.png +0 -0
  24. package/public/img/crown.svg +9 -0
  25. package/public/img/empty-box.png +0 -0
  26. package/public/img/gen3-dialog.png +0 -0
  27. package/public/img/gen3-home-banner-bg.png +0 -0
  28. package/public/img/gen3-image.png +0 -0
  29. package/public/img/gift.png +0 -0
  30. package/public/img/img-placeholder-illustration.svg +6 -0
  31. package/public/img/logo.svg +31 -0
  32. package/public/img/offerBanner.png +0 -0
  33. package/public/img/pagenotfound.png +0 -0
  34. package/public/img/partial-pay-payment-successful.png +0 -0
  35. package/public/img/phoneShippingBG.png +0 -0
  36. package/public/img/placeholder-image.jpg +0 -0
  37. package/public/img/powerIcon.png +0 -0
  38. package/public/img/product-01.png +0 -0
  39. package/public/img/product-02.jpg +0 -0
  40. package/public/img/product-03.jpg +0 -0
  41. package/public/img/product-04.jpg +0 -0
  42. package/public/img/product-05.png +0 -0
  43. package/public/img/product-06.png +0 -0
  44. package/public/img/profile-avatar.svg +12 -0
  45. package/public/img/promotion-icon.png +0 -0
  46. package/public/img/session-expired.png +0 -0
  47. package/public/img/shipping-alert-icon.png +0 -0
  48. package/public/img/shippingIcon.png +0 -0
  49. package/public/img/smart-pay-banner-how.png +0 -0
  50. package/public/img/smart-pay-banner-own.png +0 -0
  51. package/public/img/smart-pay-chose.png +0 -0
  52. package/public/img/smart-pay-easy.png +0 -0
  53. package/public/img/smart-pay-payment.png +0 -0
  54. package/public/img/smart-plan-fire.gif +0 -0
  55. package/public/img/smart-plan-sparkle.gif +0 -0
  56. package/public/img/special.png +0 -0
  57. package/public/img/specialOfferBg.png +0 -0
  58. package/public/img/stop-shipping-banner.png +0 -0
  59. package/public/img/subscriptionBanner.png +0 -0
  60. package/public/img/subscriptionIcon.png +0 -0
  61. package/public/img/successIcon.png +0 -0
  62. package/public/img/wallet-icon.png +0 -0
  63. package/public/img/world-wide-shipping.png +0 -0
  64. package/public/next.svg +1 -0
  65. package/public/vercel.svg +1 -0
  66. package/shared/components/AddressFormComponent.tsx +215 -0
  67. package/shared/components/AddressShippingFormComponent.tsx +223 -0
  68. package/shared/components/AiPaymentDialog.tsx +95 -0
  69. package/shared/components/AppLoadingComponent.tsx +15 -0
  70. package/shared/components/AppLoadingComponentV2.tsx +18 -0
  71. package/shared/components/Button.tsx +48 -0
  72. package/shared/components/BuySubscriptionDialog.tsx +75 -0
  73. package/shared/components/CartDrawerComponent.tsx +324 -0
  74. package/shared/components/CashbackComponent.tsx +48 -0
  75. package/shared/components/CashbackMigrationComponent.tsx +41 -0
  76. package/shared/components/CommissionNonEligibleComponent.tsx +27 -0
  77. package/shared/components/CustomNextImage.tsx +50 -0
  78. package/shared/components/CustomNextLink.tsx +46 -0
  79. package/shared/components/EditAddressFormComponent.tsx +254 -0
  80. package/shared/components/EmptyCartMessageComponent.tsx +25 -0
  81. package/shared/components/EmptyCartMessageForCartPage.tsx +61 -0
  82. package/shared/components/EmptyLegacyOrderMessageForOrdersPage.tsx +52 -0
  83. package/shared/components/EmptyOrderMessageForOrdersPage.tsx +62 -0
  84. package/shared/components/EmptyOrderMessageForShippingOrdersPage.tsx +62 -0
  85. package/shared/components/Footer.tsx +71 -0
  86. package/shared/components/GreetingDialog.tsx +96 -0
  87. package/shared/components/Header.tsx +106 -0
  88. package/shared/components/HoldShippingDialog.tsx +165 -0
  89. package/shared/components/HomeBanners.tsx +298 -0
  90. package/shared/components/InactiveCartDialog.tsx +69 -0
  91. package/shared/components/InputCartQuantity.tsx +342 -0
  92. package/shared/components/Inputfield.tsx +110 -0
  93. package/shared/components/ListItemComponent.tsx +82 -0
  94. package/shared/components/LoadingIconComponent.tsx +284 -0
  95. package/shared/components/OrderNotFoundComponent.tsx +56 -0
  96. package/shared/components/PageHeader.tsx +42 -0
  97. package/shared/components/PhoneNumberCountries.tsx +133 -0
  98. package/shared/components/PhoneShippingDialog.tsx +77 -0
  99. package/shared/components/PickupDialog.tsx +92 -0
  100. package/shared/components/ProductNotFoundComponent.tsx +56 -0
  101. package/shared/components/ProductPriceComponent.tsx +32 -0
  102. package/shared/components/ProfileMenu.tsx +120 -0
  103. package/shared/components/RequestCreditNoteInvoiceDialog.tsx +214 -0
  104. package/shared/components/RequestInvoiceDialog.tsx +194 -0
  105. package/shared/components/SelectCities.tsx +133 -0
  106. package/shared/components/SelectComponent.tsx +334 -0
  107. package/shared/components/SelectCountries.tsx +179 -0
  108. package/shared/components/SelectPickUpStore.tsx +131 -0
  109. package/shared/components/SelectStates.tsx +144 -0
  110. package/shared/components/ServerTime.tsx +68 -0
  111. package/shared/components/SessionDialogComponent.tsx +60 -0
  112. package/shared/components/ShippingAddressFormDialogComponent.tsx +51 -0
  113. package/shared/components/Sidebar.tsx +281 -0
  114. package/shared/components/SpecialOfferDialog.tsx +193 -0
  115. package/shared/components/StatusComponents.tsx +25 -0
  116. package/shared/components/StepperComponent.tsx +79 -0
  117. package/shared/components/StepperSmartPayComponent.tsx +97 -0
  118. package/shared/components/SubscriptionPurchaseDialogComponent.tsx +81 -0
  119. package/shared/components/TableComponent.tsx +144 -0
  120. package/shared/components/TablePaginationComponent.tsx +151 -0
  121. package/shared/components/aleta/AletaManagerComponent.tsx +52 -0
  122. package/shared/components/aleta/KycPendingStatus.tsx +33 -0
  123. package/shared/components/aleta/KycRejectedStatus.tsx +53 -0
  124. package/shared/components/aleta/KycRequiredStatus.tsx +53 -0
  125. package/shared/components/aleta/SubscriptionRequiredStatus.tsx +45 -0
  126. package/shared/components/icons/AletaWalletIcon.tsx +21 -0
  127. package/shared/components/icons/BrandIcon.tsx +97 -0
  128. package/shared/components/icons/BrandText.tsx +47 -0
  129. package/shared/components/icons/CartIcon.tsx +21 -0
  130. package/shared/components/icons/DoneIcon.tsx +27 -0
  131. package/shared/components/icons/HomnifiIcon.tsx +23 -0
  132. package/shared/components/icons/InfoIcon.tsx +16 -0
  133. package/shared/components/icons/SmartPayProductIcon.tsx +31 -0
  134. package/shared/components/payments/DisclaimerAlertDialog.tsx +205 -0
  135. package/shared/components/payments/EmptyAssets.tsx +23 -0
  136. package/shared/components/payments/PaybyWalletItemComponent.tsx +116 -0
  137. package/shared/components/payments/PaymentAssetItemComponent.tsx +101 -0
  138. package/shared/components/payments/RequestProcessingDialog.tsx +123 -0
  139. package/shared/components/payments/WalletSectionManager.tsx +82 -0
  140. package/shared/components/payments/WalletSliderComponent.tsx +326 -0
  141. package/shared/components/payments/WalletTermsAndConditionComponent.tsx +88 -0
  142. package/shared/constants/address-shipping.schema.ts +56 -0
  143. package/shared/constants/address.schema.ts +56 -0
  144. package/shared/constants/app.ts +323 -0
  145. package/shared/constants/environment.ts +23 -0
  146. package/shared/constants/feature-flags.ts +15 -0
  147. package/shared/constants/query-keys.ts +106 -0
  148. package/shared/constants/routes.ts +41 -0
  149. package/shared/constants/socket.ts +15 -0
  150. package/shared/contexts/HomeProductsContext.tsx +51 -0
  151. package/shared/contexts/MainWrapperContext.tsx +289 -0
  152. package/shared/contexts/WebSocketContext.tsx +155 -0
  153. package/shared/hooks/useAddressForm.ts +169 -0
  154. package/shared/hooks/useAddresses.ts +28 -0
  155. package/shared/hooks/useAppForm.ts +39 -0
  156. package/shared/hooks/useCart.ts +207 -0
  157. package/shared/hooks/useCopy.ts +45 -0
  158. package/shared/hooks/useCountries.ts +27 -0
  159. package/shared/hooks/useCustomRouter.ts +51 -0
  160. package/shared/hooks/useDebounce.ts +21 -0
  161. package/shared/hooks/useInvoice.ts +41 -0
  162. package/shared/hooks/useOnlineUser.ts +38 -0
  163. package/shared/hooks/usePayments.ts +101 -0
  164. package/shared/hooks/useShippingAddressForm.ts +70 -0
  165. package/shared/hooks/useShippingAddresses.ts +33 -0
  166. package/shared/hooks/useStores.ts +29 -0
  167. package/shared/hooks/useUserActivity.ts +43 -0
  168. package/shared/i18n/client.ts +27 -0
  169. package/shared/i18n/locales/en.ts +383 -0
  170. package/shared/i18n/locales/it.ts +373 -0
  171. package/shared/i18n/server.ts +7 -0
  172. package/shared/services/addresses.service.ts +27 -0
  173. package/shared/services/api/core/ApiError.ts +29 -0
  174. package/shared/services/api/core/ApiRequestOptions.ts +25 -0
  175. package/shared/services/api/core/ApiResult.ts +11 -0
  176. package/shared/services/api/core/CancelablePromise.ts +130 -0
  177. package/shared/services/api/core/OpenAPI.ts +37 -0
  178. package/shared/services/api/core/request.ts +376 -0
  179. package/shared/services/api/index.ts +64 -0
  180. package/shared/services/api/models/AddAllProductToShippingCartDto.ts +7 -0
  181. package/shared/services/api/models/AddProductToCartDto.ts +8 -0
  182. package/shared/services/api/models/AddProductToShippingCartDto.ts +8 -0
  183. package/shared/services/api/models/AdminPlaceOrderDto.ts +16 -0
  184. package/shared/services/api/models/AssignPickupSerialNumberDto.ts +10 -0
  185. package/shared/services/api/models/CreateUserAddressDto.ts +19 -0
  186. package/shared/services/api/models/DeletCartProductsArrayDto.ts +7 -0
  187. package/shared/services/api/models/GenerateInvoiceDto.ts +7 -0
  188. package/shared/services/api/models/OrderConfirmationEmailDto.ts +8 -0
  189. package/shared/services/api/models/PlaceOrderDto.ts +9 -0
  190. package/shared/services/api/models/PlaceShipmentOrderDto.ts +8 -0
  191. package/shared/services/api/models/ProductBoostDto.ts +18 -0
  192. package/shared/services/api/models/RemoveAssignedSerialNumberDto.ts +9 -0
  193. package/shared/services/api/models/RemoveProductToCartDto.ts +8 -0
  194. package/shared/services/api/models/ShipmentItem.ts +8 -0
  195. package/shared/services/api/models/ShippingCartUpdateBillingAddressesDto.ts +7 -0
  196. package/shared/services/api/models/ShippingCartUpdateShippingMethodDto.ts +7 -0
  197. package/shared/services/api/models/UnfreezeWalletDto.ts +8 -0
  198. package/shared/services/api/models/UpdateCartBillingAddressesDto.ts +7 -0
  199. package/shared/services/api/models/UpdateCartPaymentMethodDto.ts +9 -0
  200. package/shared/services/api/models/UpdateCartShippingMethodDto.ts +7 -0
  201. package/shared/services/api/models/UpdateCartStoreDto.ts +7 -0
  202. package/shared/services/api/models/UpdateProductDto.ts +11 -0
  203. package/shared/services/api/models/UpdateShipmentItemStatusDto.ts +24 -0
  204. package/shared/services/api/models/UpdateShippingCartPaymentMethodDto.ts +9 -0
  205. package/shared/services/api/models/UpdateUserAddressDto.ts +19 -0
  206. package/shared/services/api/models/ValidateAuthCodeDto.ts +8 -0
  207. package/shared/services/api/models/WithdrawOrderWalletDto.ts +8 -0
  208. package/shared/services/api/services/AddressesService.ts +160 -0
  209. package/shared/services/api/services/AdminsDevicesService.ts +28 -0
  210. package/shared/services/api/services/AdminsOrdersService.ts +117 -0
  211. package/shared/services/api/services/AdminsPickupsService.ts +57 -0
  212. package/shared/services/api/services/AdminsProductsService.ts +86 -0
  213. package/shared/services/api/services/CartsService.ts +190 -0
  214. package/shared/services/api/services/CashBackQueueService.ts +39 -0
  215. package/shared/services/api/services/CategoriesService.ts +65 -0
  216. package/shared/services/api/services/DefaultService.ts +19 -0
  217. package/shared/services/api/services/FeaturesFlagsService.ts +20 -0
  218. package/shared/services/api/services/IossQueueService.ts +39 -0
  219. package/shared/services/api/services/OrderCallbackService.ts +67 -0
  220. package/shared/services/api/services/OrderEmailsService.ts +40 -0
  221. package/shared/services/api/services/OrderImportService.ts +228 -0
  222. package/shared/services/api/services/OrdersService.ts +183 -0
  223. package/shared/services/api/services/PaymentGatewayService.ts +19 -0
  224. package/shared/services/api/services/PaymentsService.ts +88 -0
  225. package/shared/services/api/services/ProductsService.ts +67 -0
  226. package/shared/services/api/services/ShipmentOrderCallbackService.ts +67 -0
  227. package/shared/services/api/services/ShipmentOrdersService.ts +88 -0
  228. package/shared/services/api/services/ShipmentsService.ts +30 -0
  229. package/shared/services/api/services/ShippingCartService.ts +247 -0
  230. package/shared/services/api/services/ShippingMethodsService.ts +20 -0
  231. package/shared/services/api/services/StoresService.ts +25 -0
  232. package/shared/services/api/services/SyncServiceQueueService.ts +39 -0
  233. package/shared/services/api/services/UpgradeOrdersService.ts +26 -0
  234. package/shared/services/api/services/UsersAuthService.ts +45 -0
  235. package/shared/services/api/services/UsersService.ts +53 -0
  236. package/shared/services/api/services/XeraQueueService.ts +39 -0
  237. package/shared/services/index.ts +0 -0
  238. package/shared/services/payment-method.service.ts +29 -0
  239. package/shared/stores/cartStore.ts +108 -0
  240. package/shared/stores/countriesStore.ts +62 -0
  241. package/shared/stores/formStore.ts +19 -0
  242. package/shared/stores/loadingStore.ts +12 -0
  243. package/shared/stores/userStore.ts +32 -0
  244. package/shared/types/SelectComponent.types.ts +31 -0
  245. package/shared/types/addressForm.ts +27 -0
  246. package/shared/types/cart.ts +208 -0
  247. package/shared/types/category.ts +6 -0
  248. package/shared/types/creditNote.ts +0 -0
  249. package/shared/types/feature-flags.ts +5 -0
  250. package/shared/types/icon.ts +5 -0
  251. package/shared/types/index.d.ts +84 -0
  252. package/shared/types/order.ts +332 -0
  253. package/shared/types/pagination.ts +5 -0
  254. package/shared/types/payments.ts +68 -0
  255. package/shared/types/product.ts +54 -0
  256. package/shared/types/shoppingCart.ts +219 -0
  257. package/shared/types/upgradeCart.ts +226 -0
  258. package/shared/types/user.ts +36 -0
  259. package/shared/utils/app.util.ts +261 -0
  260. package/shared/utils/notifications.util.ts +39 -0
  261. package/shared/utils/user-session.util.ts +171 -0
  262. package/tailwind.config.ts +234 -0
@@ -0,0 +1,342 @@
1
+ import { IconButton, Input, Typography } from "@material-tailwind/react";
2
+ import {
3
+ startTransition,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { FiMinus, FiPlus } from "react-icons/fi";
11
+ export type InputCartQuantityProps = {
12
+ size: string;
13
+ quantity?: number;
14
+ maximumUserCanPurchase: number;
15
+ isLoading?: boolean;
16
+ canProductBeRemoved?: boolean;
17
+ onQuantityChange?: (qty: number) => void;
18
+ onErrorChange?: (hasError: boolean) => void;
19
+ maxCartQuantity?: number | null;
20
+ quantityStepper?: number | null;
21
+ };
22
+
23
+ export default function InputCartQuantity({
24
+ size = "sm",
25
+ quantity,
26
+ maximumUserCanPurchase,
27
+ isLoading,
28
+ canProductBeRemoved,
29
+ maxCartQuantity,
30
+ quantityStepper = 1,
31
+ onQuantityChange,
32
+ onErrorChange,
33
+ }: InputCartQuantityProps) {
34
+ //
35
+ const [currentQty, setCurrentQty] = useState<number>();
36
+ const [inputValue, setInputValue] = useState<string>("");
37
+ const [hasError, setHasError] = useState(false);
38
+ const [errorMessage, setErrorMessage] = useState<string>("");
39
+ const prevQuantityRef = useRef<number | undefined>(undefined);
40
+
41
+ // Calculate min and max quantities
42
+ const minQuantity = useMemo(() => {
43
+ return 1 * (quantityStepper ?? 1);
44
+ }, [quantityStepper]);
45
+
46
+ const maxQuantity = useMemo(() => {
47
+ return maximumUserCanPurchase === Infinity
48
+ ? Infinity
49
+ : maximumUserCanPurchase;
50
+ }, [maximumUserCanPurchase]);
51
+
52
+ // Update input value when quantity prop changes (from parent)
53
+ useEffect(() => {
54
+ if (quantity !== undefined && quantity !== prevQuantityRef.current) {
55
+ prevQuantityRef.current = quantity;
56
+ startTransition(() => {
57
+ setCurrentQty(quantity);
58
+ setInputValue(quantity.toString());
59
+ setHasError(false);
60
+ setErrorMessage("");
61
+ onErrorChange?.(false);
62
+ });
63
+ }
64
+ }, [quantity, onErrorChange]);
65
+
66
+ const validateAndSetQuantity = useCallback(
67
+ (value: string) => {
68
+ // Allow empty string while typing
69
+ if (value === "") {
70
+ setInputValue("");
71
+ setHasError(false);
72
+ setErrorMessage("");
73
+ onErrorChange?.(false);
74
+ return;
75
+ }
76
+
77
+ // Check if it's a valid number
78
+ const numValue = Number(value);
79
+ if (isNaN(numValue) || !Number.isInteger(numValue)) {
80
+ setHasError(true);
81
+ setErrorMessage("Please enter a valid number");
82
+ onErrorChange?.(true);
83
+ return;
84
+ }
85
+
86
+ // Prevent typing 0 in the input (0 can only be set via decrement button)
87
+ if (numValue === 0) {
88
+ setHasError(true);
89
+ setErrorMessage(`Minimum quantity is ${minQuantity}`);
90
+ onErrorChange?.(true);
91
+ // Reset to current quantity
92
+ if (currentQty !== undefined) {
93
+ setInputValue(currentQty.toString());
94
+ }
95
+ return;
96
+ }
97
+
98
+ // Check if value is less than minimum
99
+ if (numValue < minQuantity) {
100
+ setHasError(true);
101
+ setErrorMessage(`Minimum quantity is ${minQuantity}`);
102
+ onErrorChange?.(true);
103
+ return;
104
+ }
105
+
106
+ // Check if value exceeds maximum
107
+ if (maxQuantity !== Infinity && numValue > maxQuantity) {
108
+ setHasError(true);
109
+ setErrorMessage(`Maximum quantity is ${maxQuantity}`);
110
+ onErrorChange?.(true);
111
+ return;
112
+ }
113
+
114
+ // Check if value is a multiple of quantityStepper
115
+ const stepper = quantityStepper ?? 1;
116
+ if (stepper > 1 && numValue % stepper !== 0) {
117
+ const nearestValid = Math.round(numValue / stepper) * stepper;
118
+ setHasError(true);
119
+ setErrorMessage(
120
+ `Quantity must be a multiple of ${stepper}. Nearest valid: ${nearestValid}`,
121
+ );
122
+ onErrorChange?.(true);
123
+ return;
124
+ }
125
+
126
+ // Check maxCartQuantity constraint
127
+ if (maxCartQuantity && numValue > maxCartQuantity) {
128
+ setHasError(true);
129
+ setErrorMessage(`Maximum cart quantity is ${maxCartQuantity}`);
130
+ onErrorChange?.(true);
131
+ return;
132
+ }
133
+
134
+ // Valid value - update quantity
135
+ setHasError(false);
136
+ setErrorMessage("");
137
+ onErrorChange?.(false);
138
+ setCurrentQty(numValue);
139
+ onQuantityChange?.(numValue);
140
+ },
141
+ [
142
+ minQuantity,
143
+ maxQuantity,
144
+ quantityStepper,
145
+ maxCartQuantity,
146
+ currentQty,
147
+ onQuantityChange,
148
+ onErrorChange,
149
+ ],
150
+ );
151
+
152
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
153
+ const value = e.target.value;
154
+
155
+ // Allow only digits and empty string, but prevent typing 0
156
+ if (value === "" || (value !== "0" && /^\d+$/.test(value))) {
157
+ setInputValue(value);
158
+
159
+ // If empty, clear error
160
+ if (value === "") {
161
+ setHasError(false);
162
+ setErrorMessage("");
163
+ onErrorChange?.(false);
164
+ }
165
+ }
166
+ };
167
+
168
+ const handleInputBlur = () => {
169
+ // If input is empty, reset to current quantity
170
+ if (inputValue === "" && currentQty !== undefined) {
171
+ setInputValue(currentQty.toString());
172
+ setHasError(false);
173
+ setErrorMessage("");
174
+ onErrorChange?.(false);
175
+ return;
176
+ }
177
+
178
+ // Validate and apply the value
179
+ validateAndSetQuantity(inputValue);
180
+ };
181
+
182
+ const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
183
+ // Allow Enter key to trigger validation
184
+ if (e.key === "Enter") {
185
+ e.currentTarget.blur();
186
+ }
187
+
188
+ // Allow Escape to cancel and revert
189
+ if (e.key === "Escape" && currentQty !== undefined) {
190
+ setInputValue(currentQty.toString());
191
+ setHasError(false);
192
+ setErrorMessage("");
193
+ onErrorChange?.(false);
194
+ e.currentTarget.blur();
195
+ }
196
+ };
197
+
198
+ const handleAddQty = useCallback(() => {
199
+ if (!currentQty) return;
200
+ if (maxCartQuantity && maxCartQuantity <= currentQty) return;
201
+
202
+ const stepper = quantityStepper ?? 1;
203
+ const newQuantity = Math.min(
204
+ currentQty + stepper,
205
+ maxQuantity === Infinity ? Infinity : maxQuantity,
206
+ );
207
+
208
+ // Check maxCartQuantity constraint
209
+ const finalQuantity =
210
+ maxCartQuantity && newQuantity > maxCartQuantity
211
+ ? maxCartQuantity
212
+ : newQuantity;
213
+
214
+ setCurrentQty(finalQuantity);
215
+ setInputValue(finalQuantity.toString());
216
+ setHasError(false);
217
+ setErrorMessage("");
218
+ onErrorChange?.(false);
219
+ onQuantityChange?.(finalQuantity);
220
+ }, [
221
+ currentQty,
222
+ maxCartQuantity,
223
+ quantityStepper,
224
+ maxQuantity,
225
+ onQuantityChange,
226
+ onErrorChange,
227
+ ]);
228
+
229
+ const handleRemoveQty = useCallback(() => {
230
+ if (!currentQty) return;
231
+
232
+ const stepper = quantityStepper ?? 1;
233
+ const newQuantity = currentQty - stepper;
234
+
235
+ if (newQuantity < minQuantity) {
236
+ if (!canProductBeRemoved) return;
237
+ // If can be removed and newQuantity is 0 or less, allow 0
238
+ const finalQuantity = Math.max(0, newQuantity);
239
+ setCurrentQty(finalQuantity);
240
+ setInputValue(finalQuantity.toString());
241
+ setHasError(false);
242
+ setErrorMessage("");
243
+ onErrorChange?.(false);
244
+ onQuantityChange?.(finalQuantity);
245
+ return;
246
+ }
247
+
248
+ setCurrentQty(newQuantity);
249
+ setInputValue(newQuantity.toString());
250
+ setHasError(false);
251
+ setErrorMessage("");
252
+ onErrorChange?.(false);
253
+ onQuantityChange?.(newQuantity);
254
+ }, [
255
+ currentQty,
256
+ quantityStepper,
257
+ minQuantity,
258
+ canProductBeRemoved,
259
+ onQuantityChange,
260
+ onErrorChange,
261
+ ]);
262
+
263
+ const isIncrementDisabled = useMemo(() => {
264
+ if (!currentQty) return true;
265
+ if (isLoading || hasError) return true;
266
+ if (maxCartQuantity && maxCartQuantity <= currentQty) return true;
267
+ if (maxQuantity !== Infinity && currentQty >= maxQuantity) return true;
268
+ return false;
269
+ }, [currentQty, isLoading, hasError, maxCartQuantity, maxQuantity]);
270
+
271
+ const isDecrementDisabled = useMemo(() => {
272
+ if (currentQty === undefined || currentQty === null) return true;
273
+ if (isLoading || hasError) return true;
274
+ if (currentQty <= 0) return true; // Can't go below 0
275
+ if (!canProductBeRemoved && currentQty <= minQuantity) return true;
276
+ return false;
277
+ }, [currentQty, isLoading, hasError, canProductBeRemoved, minQuantity]);
278
+
279
+ const inputWidth = size === "lg" ? "w-[60px]" : "w-[35px]";
280
+ const inputPadding = size === "lg" ? "py-[5px]" : "py-[2px]";
281
+ const inputTextSize = size === "lg" ? "text-base" : "text-sm";
282
+
283
+ return (
284
+ <>
285
+ <div className="flex flex-col gap-2 items-center justify-center ">
286
+ <div
287
+ className={`flex gap-1 items-center justify-end ${size === "lg" ? "gap-2 md:gap-2.5" : "md:gap-1.5"}`}
288
+ >
289
+ <IconButton
290
+ variant="outlined"
291
+ className={`flex border-slate-100 hover:border-slate-400 hover:bg-gray-100/20 focus:!border-slate-300 focus:ring-gray-300/30 text-slate-500 hover:text-black-500
292
+ ${size === "lg" ? "w-6 h-6 md:w-9 md:h-9" : "w-7 h-6"}
293
+ ${isDecrementDisabled ? "opacity-50 cursor-not-allowed" : ""}`}
294
+ onClick={handleRemoveQty}
295
+ disabled={isDecrementDisabled}
296
+ >
297
+ <FiMinus className="w-4 h-5" />
298
+ </IconButton>
299
+ <div className={`!min-w-0 ${inputWidth}`}>
300
+ <Input
301
+ type="text"
302
+ value={inputValue || ""}
303
+ onChange={handleInputChange}
304
+ onBlur={handleInputBlur}
305
+ onKeyDown={handleInputKeyDown}
306
+ className={`!py-2 text-center ${inputTextSize} ${inputWidth} !border bg-white text-gray-900 shadow-gray-200/5 ring-4 ring-transparent placeholder:text-gray-500 placeholder:opacity-100 focus:ring-gray-300/10 shadow-none ${inputPadding} ${
307
+ hasError
308
+ ? "!border-red-300 focus:!border-red-400 focus:!border-t-red-400"
309
+ : "!border-slate-100 focus:!border-slate-300 focus:!border-t-slate-300"
310
+ }`}
311
+ labelProps={{
312
+ className: "hidden",
313
+ }}
314
+ containerProps={{ className: "!min-w-0" }}
315
+ crossOrigin={undefined}
316
+ tabIndex={0}
317
+ disabled={isLoading}
318
+ />
319
+ </div>
320
+ <IconButton
321
+ variant="outlined"
322
+ className={`flex border-slate-100 hover:border-slate-400 hover:bg-gray-100/20 focus:!border-slate-300 focus:ring-gray-300/30 text-slate-500 hover:text-black-500
323
+ ${size === "lg" ? "w-6 h-6 md:w-9 md:h-9" : "w-7 h-6"}
324
+ ${isIncrementDisabled ? "opacity-50 cursor-not-allowed" : ""}`}
325
+ onClick={handleAddQty}
326
+ disabled={isIncrementDisabled}
327
+ >
328
+ <FiPlus className="w-4 h-5" />
329
+ </IconButton>
330
+ </div>
331
+ {hasError && errorMessage && (
332
+ <Typography
333
+ variant="small"
334
+ className="text-xs font-medium text-red-500 text-right"
335
+ >
336
+ {errorMessage}
337
+ </Typography>
338
+ )}
339
+ </div>
340
+ </>
341
+ );
342
+ }
@@ -0,0 +1,110 @@
1
+ import { Input, Typography } from "@material-tailwind/react";
2
+ import { HTMLInputTypeAttribute, useEffect, useState } from "react";
3
+ import { UseFormReturn } from "react-hook-form";
4
+
5
+ import { useFormStore } from "../stores/formStore";
6
+ import { renderTitleWithRedAsterisk } from "../utils/app.util";
7
+
8
+ export interface InputFieldProps {
9
+ title: string;
10
+ placeholder?: string;
11
+ value?: string;
12
+ type?: HTMLInputTypeAttribute;
13
+ name?: string;
14
+ formId: string;
15
+ disabled?: boolean;
16
+ className?: string;
17
+ readOnly?: boolean;
18
+ format?: string;
19
+ onChange?: (e: React.FormEvent<HTMLInputElement>) => void;
20
+ }
21
+
22
+ export default function Inputfield({
23
+ className,
24
+ title,
25
+ placeholder,
26
+ value,
27
+ name,
28
+ formId,
29
+ type = "text",
30
+ readOnly,
31
+ format,
32
+ disabled,
33
+ onChange,
34
+ }: InputFieldProps) {
35
+ const { reactHookUseForm } = useFormStore();
36
+ const { register, formState, setValue, watch } =
37
+ (reactHookUseForm as { [key: string]: UseFormReturn<any> })?.[formId] ?? {};
38
+ const { errors } = formState ?? {};
39
+ const [error, setError] = useState<any>(null);
40
+ const fieldKey = name ?? title?.toLowerCase();
41
+
42
+ const watchField = watch?.(`${fieldKey}`);
43
+
44
+ useEffect(() => {
45
+ setTimeout(() => {
46
+ if (errors?.[fieldKey]) {
47
+ setError(errors[fieldKey]);
48
+ } else {
49
+ setError(null);
50
+ }
51
+ }, 100);
52
+ }, [watchField, errors, fieldKey]);
53
+
54
+ const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
55
+ setValue(`${fieldKey}`, e.target.value, { shouldValidate: true });
56
+ if (onChange) onChange(e);
57
+ };
58
+
59
+ if (!reactHookUseForm || !register) return <></>;
60
+
61
+ return (
62
+ <div className="w-full">
63
+ <Typography
64
+ variant="lead"
65
+ className="text-xs font-medium text-black-400 mb-1"
66
+ >
67
+ {renderTitleWithRedAsterisk(title)}
68
+ {format && <span className="text-purple-500">&nbsp;E.g: {format}</span>}
69
+ </Typography>
70
+ <Input
71
+ placeholder={placeholder ?? title}
72
+ value={value}
73
+ type={type}
74
+ readOnly={readOnly}
75
+ error={error ? true : false}
76
+ disabled={disabled}
77
+ crossOrigin={undefined}
78
+ labelProps={{
79
+ className: "hidden",
80
+ }}
81
+ className={`
82
+ w-full min-w-full h-10 text-xs font-normal p-3
83
+ bg-white rounded-md
84
+ text-gray-900
85
+ shadow-gray-200/5
86
+ ring-2 ring-transparent
87
+ placeholder:text-gray-300 placeholder:opacity-100
88
+ !border !border-slate-100 !border-t-slate-100
89
+ ${error ? "!border-red-500 !border-t-red-500 ring-red-50 bg-red-100/10" : "!border-t-slate-100"}
90
+ focus:!border-slate-300
91
+ focus:ring-gray-300/10
92
+ focus:bg-offWhite
93
+ shadow-none
94
+ ${className}
95
+ `}
96
+ containerProps={{ className: "h-10 min-w-min" }}
97
+ {...register(`${fieldKey}`, { onChange: handleOnChange })}
98
+ ></Input>
99
+
100
+ {error && error?.message && (
101
+ <Typography
102
+ variant="lead"
103
+ className="text-red-500 font-normal text-[0.6rem] px-1 block md:absolute -bottom-[1.1rem] left-0 errorText"
104
+ >
105
+ {error?.message}{" "}
106
+ </Typography>
107
+ )}
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,82 @@
1
+ import { Badge, ListItem, ListItemPrefix } from "@material-tailwind/react";
2
+ import React from "react";
3
+
4
+ import useCustomRouter from "../hooks/useCustomRouter";
5
+ import CustomNextLink from "./CustomNextLink";
6
+
7
+ export type ListItemComponentProps = {
8
+ href?: string;
9
+ prefix: any;
10
+ suffix?: any;
11
+ ListItemName: string;
12
+ active: boolean;
13
+ target?: string;
14
+ onClick?: () => void;
15
+ cartProductsCount: number | React.JSX.Element;
16
+ customAction?: () => void;
17
+ customActionEnable?: boolean;
18
+ };
19
+
20
+ export default function ListItemComponent({
21
+ href,
22
+ prefix,
23
+ ListItemName,
24
+ active,
25
+ target = "_self",
26
+ onClick,
27
+ cartProductsCount,
28
+ customAction = () => {},
29
+ customActionEnable = false,
30
+ }: ListItemComponentProps) {
31
+ //
32
+ const { push } = useCustomRouter();
33
+
34
+ return (
35
+ <ListItem
36
+ className={`p-3 lg:p-2.5 xl:p-3 text-slate-600 rounded-lg
37
+ hover:text-purple-500 hover:bg-purple-100
38
+ focus:text-purple-500 focus:bg-purple-200
39
+ active:text-purple-500 active:bg-purple-50
40
+ ${active ? "text-purple-500 bg-purple-100" : ""}
41
+ focus:outline-none focus:ring-0`}
42
+ onClick={() => {
43
+ if (customActionEnable) {
44
+ customAction();
45
+ } else {
46
+ target != "_blank" && push(href);
47
+ }
48
+ }}
49
+ >
50
+ {customActionEnable ? (
51
+ <div className="w-fit mr-12 items-center text-base font-medium md:font-normal">
52
+ <div className="flex items-center gap-2">
53
+ {prefix && (
54
+ <ListItemPrefix className="m-0 w-fit">{prefix}</ListItemPrefix>
55
+ )}
56
+ {ListItemName}
57
+ </div>
58
+ </div>
59
+ ) : (
60
+ <CustomNextLink
61
+ href={href}
62
+ className="flex justify-between items-center w-full text-base font-medium md:font-normal"
63
+ target={target}
64
+ onClick={onClick}
65
+ >
66
+ <div className="flex items-center gap-2">
67
+ {prefix && (
68
+ <ListItemPrefix className="m-0">{prefix}</ListItemPrefix>
69
+ )}
70
+ {ListItemName}
71
+ </div>
72
+ </CustomNextLink>
73
+ )}
74
+ {!!cartProductsCount && (
75
+ <Badge
76
+ content={cartProductsCount}
77
+ className="min-h-fit text-[0.625rem] px-1.5 right-1 rounded-xl leading-normal bg-blue-300 text-white"
78
+ ></Badge>
79
+ )}
80
+ </ListItem>
81
+ );
82
+ }