@evenicanpm/storefront-core 2.4.1 → 2.5.0
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/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +4 -3
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +3 -3
- package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +1 -0
- package/src/api-manager/datasources/e4/address/e4-address.datasource.ts +5 -14
- package/src/api-manager/datasources/e4/address/e4-address.translator.ts +4 -3
- package/src/api-manager/datasources/e4/categories/e4-categories.translator.ts +1 -1
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +29 -3
- package/src/api-manager/datasources/e4/product/e4-product.translator.ts +1 -1
- package/src/api-manager/index.ts +1 -1
- package/src/api-manager/lib/get-graphql-client.ts +1 -1
- package/src/api-manager/services/cart/mutations/add-discount-code.ts +0 -11
- package/src/api-manager/services/create-query.ts +13 -7
- package/src/api-manager/services/user/mutations/add-to-wishlist.ts +1 -1
- package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +1 -1
- package/src/auth/AUTH GUIDE.md +880 -0
- package/src/cms/blocks/components/featured-categories.tsx +18 -15
- package/src/cms/blocks/components/featured-products.tsx +3 -2
- package/src/cms/blocks/components/footer/sections/footer-contact.tsx +0 -2
- package/src/cms/blocks/components/footer/sections/footer-logo.tsx +2 -1
- package/src/components/SearchInput.stories.tsx +12 -10
- package/src/components/base-layout.tsx +42 -0
- package/src/components/categories/category-list/category-list.tsx +32 -12
- package/src/components/header/__tests__/header.test.tsx +0 -24
- package/src/components/header/components/mobile-header.tsx +16 -8
- package/src/components/header/header.tsx +6 -3
- package/src/components/mini-cart/components/bottom-actions.tsx +7 -2
- package/src/components/mini-cart/components/cart-item.tsx +16 -13
- package/src/components/mini-cart/components/empty-view.tsx +12 -6
- package/src/components/mini-cart/components/top-header.tsx +9 -8
- package/src/components/mini-cart/mini-cart-trigger.tsx +11 -5
- package/src/components/mini-cart/mini-cart.tsx +30 -15
- package/src/components/mobile-navigation/mobile-navigation-bar.tsx +6 -2
- package/src/components/navbar/categories.tsx +8 -2
- package/src/components/navbar/category-based-menu/category-based-menu.tsx +6 -2
- package/src/components/navbar/mega-menu/mega-menu.tsx +22 -14
- package/src/components/navbar/mobile-menu/mobile-menu.test.tsx +8 -11
- package/src/components/navbar/mobile-menu/mobile-menu.tsx +13 -11
- package/src/components/navbar/nav-list/nav-list.tsx +3 -2
- package/src/components/navbar/navbar.tsx +4 -2
- package/src/components/product-cards/product-card/product-card.tsx +50 -26
- package/src/components/product-cards/product-card-plp/product-card.tsx +37 -16
- package/src/components/product-cards/product-card-plp-list/product-card.tsx +38 -16
- package/src/components/product-cards/product-card-search/product-card.tsx +34 -13
- package/src/components/product-dialog/ProductDialog.stories.tsx +20 -18
- package/src/components/product-dialog/compound/product-dialog.tsx +7 -4
- package/src/components/product-dimensions/compound/dimensions-group.tsx +4 -2
- package/src/components/product-dimensions/compound/dimensions.tsx +15 -6
- package/src/components/product-quantity-buttons/compound/quantity-buttons-root.tsx +21 -13
- package/src/components/products-view/compound/products-grid-view.tsx +11 -4
- package/src/components/products-view/compound/products-list-view.tsx +11 -4
- package/src/components/search-bar/compound/search-bar-root.tsx +6 -4
- package/src/components/search-bar/compound/textfield-adornment-category.tsx +25 -8
- package/src/components/search-bar/hooks/use-click-outside.ts +7 -1
- package/src/components/search-bar/hooks/use-search.ts +3 -3
- package/src/components/user/index.ts +3 -0
- package/src/components/user/use-user-auth.ts +163 -0
- package/src/components/user/user.tsx +215 -0
- package/src/components/wishlist-dialogs/add-to-wishlist/AddToWishlist.stories.tsx +20 -15
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +11 -8
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-list.tsx +18 -3
- package/src/components/wishlist-dialogs/add-to-wishlist/index.tsx +0 -1
- package/src/components/wishlist-dialogs/create-wishlist/compound/create-wishlist-dialog.tsx +6 -2
- package/src/hooks/use-overflow-detect.ts +4 -4
- package/src/lib/page-slugs.ts +16 -0
- package/src/pages/account/account-navigation.tsx +7 -14
- package/src/pages/account/account-routes.ts +4 -9
- package/src/pages/account/addresses/address-form.tsx +38 -16
- package/src/pages/account/addresses/address-item.tsx +18 -8
- package/src/pages/account/dashboard-header.tsx +20 -13
- package/src/pages/account/orders/order-history-filters-panel.tsx +21 -20
- package/src/pages/account/orders/order-history-header.tsx +14 -4
- package/src/pages/account/orders/order-history-root.tsx +3 -1
- package/src/pages/account/orders/order-history-row.tsx +12 -4
- package/src/pages/account/orders/order-history-search-and-filter.tsx +88 -59
- package/src/pages/account/orders/order-history-search-dropdown.tsx +26 -9
- package/src/pages/account/orders/order-history-sort.tsx +3 -1
- package/src/pages/account/orders/order-history-table.tsx +27 -5
- package/src/pages/account/orders/order-progress.tsx +12 -6
- package/src/pages/account/orders/order-row.tsx +18 -6
- package/src/pages/account/orders/order-summary.tsx +24 -8
- package/src/pages/account/orders/ordered-products.tsx +15 -5
- package/src/pages/account/profile/user-info.tsx +11 -3
- package/src/pages/account/table-row-skeleton.tsx +1 -1
- package/src/pages/account/wishlist/create-new-list.tsx +33 -9
- package/src/pages/account/wishlist/wishlist-item.tsx +18 -6
- package/src/pages/cart/cart-item.tsx +21 -7
- package/src/pages/cart/checkout-form.tsx +55 -22
- package/src/pages/cart/coupon-entry.tsx +143 -36
- package/src/pages/cart/estimate-shipping.tsx +45 -21
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +22 -14
- package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +37 -16
- package/src/pages/checkout/checkout-alt-form/steps/address/address-card.tsx +9 -3
- package/src/pages/checkout/checkout-alt-form/steps/address/delivery-address.tsx +84 -51
- package/src/pages/checkout/checkout-alt-form/steps/address/new-address-form.tsx +41 -19
- package/src/pages/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +17 -10
- package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +15 -6
- package/src/pages/checkout/checkout-alt-form/steps/shipping/delivery-options.tsx +6 -2
- package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +12 -4
- package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +12 -5
- package/src/pages/checkout/checkout-alt-summary/list-item.tsx +17 -10
- package/src/pages/confirmation/address.tsx +6 -2
- package/src/pages/confirmation/confirmation-summary.tsx +12 -4
- package/src/pages/confirmation/ordered-products.tsx +12 -4
- package/src/pages/product-details/bopis/store-card/shop-card.tsx +2 -2
- package/src/pages/product-details/bopis/store-card/utils/getDaysResources.ts +1 -1
- package/src/pages/product-details/product-intro/compound/product-info.tsx +41 -18
- package/src/pages/product-details/product-intro/compound/product-intro-images.tsx +18 -12
- package/src/pages/product-details/product-intro/compound/product-intro.tsx +20 -10
- package/src/pages/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +1 -1
- package/src/pages/product-details/product-tabs.tsx +20 -12
- package/src/pages/product-list/product-list-view.tsx +4 -2
- package/src/pages/product-list/search-bar.tsx +7 -7
- package/src/pages/product-list/side-nav.tsx +5 -3
- package/src/pages/quickorder/order-upload.tsx +1 -1
- package/src/pages/quickorder/provider.tsx +26 -14
- package/src/pages/quickorder/quick-order.tsx +49 -22
- package/src/providers/nav-provider/index.tsx +16 -11
- package/tsconfig.json +0 -5
- package/src/components/header/components/user.tsx +0 -322
- /package/src/components/{header → user}/__tests__/user.test.tsx +0 -0
|
@@ -18,7 +18,13 @@ import { withZodSchema } from "formik-validator-zod";
|
|
|
18
18
|
import isFunction from "lodash/isFunction";
|
|
19
19
|
import { useTranslations } from "next-intl";
|
|
20
20
|
import type React from "react";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
createContext,
|
|
23
|
+
type ReactNode,
|
|
24
|
+
useContext,
|
|
25
|
+
useEffect,
|
|
26
|
+
useMemo,
|
|
27
|
+
} from "react";
|
|
22
28
|
import { z } from "zod";
|
|
23
29
|
|
|
24
30
|
interface CreateNewListContextValue {
|
|
@@ -74,9 +80,13 @@ const CreateNewList = ({
|
|
|
74
80
|
const handleClose = () => {
|
|
75
81
|
if (isFunction(onClose)) onClose();
|
|
76
82
|
};
|
|
83
|
+
const contextValue = useMemo(
|
|
84
|
+
() => ({ formik, handleClose, t }),
|
|
85
|
+
[formik, handleClose, t],
|
|
86
|
+
);
|
|
77
87
|
|
|
78
88
|
return (
|
|
79
|
-
<CreateNewListContext.Provider value={
|
|
89
|
+
<CreateNewListContext.Provider value={contextValue}>
|
|
80
90
|
<Dialog
|
|
81
91
|
open={open}
|
|
82
92
|
onClose={handleClose}
|
|
@@ -88,14 +98,16 @@ const CreateNewList = ({
|
|
|
88
98
|
);
|
|
89
99
|
};
|
|
90
100
|
|
|
91
|
-
|
|
101
|
+
const CreateNewListTitle = () => {
|
|
92
102
|
const ctx = useContext(CreateNewListContext);
|
|
93
103
|
if (!ctx) return null;
|
|
94
104
|
const { t } = ctx;
|
|
95
105
|
return <H5 mb={4}>{t("formTitle")}</H5>;
|
|
96
106
|
};
|
|
97
107
|
|
|
98
|
-
CreateNewList.
|
|
108
|
+
CreateNewList.Title = CreateNewListTitle;
|
|
109
|
+
|
|
110
|
+
const CreateNewListForm = ({ children }: { children: React.ReactNode }) => {
|
|
99
111
|
const ctx = useContext(CreateNewListContext);
|
|
100
112
|
if (!ctx) return null;
|
|
101
113
|
const { formik } = ctx;
|
|
@@ -108,7 +120,9 @@ CreateNewList.Form = ({ children }: { children: React.ReactNode }) => {
|
|
|
108
120
|
);
|
|
109
121
|
};
|
|
110
122
|
|
|
111
|
-
CreateNewList.
|
|
123
|
+
CreateNewList.Form = CreateNewListForm;
|
|
124
|
+
|
|
125
|
+
const CreateNewListNameField = () => {
|
|
112
126
|
const ctx = useContext(CreateNewListContext);
|
|
113
127
|
if (!ctx) return null;
|
|
114
128
|
const { formik, t } = ctx;
|
|
@@ -129,7 +143,9 @@ CreateNewList.NameField = () => {
|
|
|
129
143
|
);
|
|
130
144
|
};
|
|
131
145
|
|
|
132
|
-
CreateNewList.
|
|
146
|
+
CreateNewList.NameField = CreateNewListNameField;
|
|
147
|
+
|
|
148
|
+
const CreateNewListCommentField = () => {
|
|
133
149
|
const ctx = useContext(CreateNewListContext);
|
|
134
150
|
if (!ctx) return null;
|
|
135
151
|
const { formik, t } = ctx;
|
|
@@ -149,7 +165,9 @@ CreateNewList.CommentField = () => {
|
|
|
149
165
|
);
|
|
150
166
|
};
|
|
151
167
|
|
|
152
|
-
CreateNewList.
|
|
168
|
+
CreateNewList.CommentField = CreateNewListCommentField;
|
|
169
|
+
|
|
170
|
+
const CreateNewListPrivacyField = () => {
|
|
153
171
|
const ctx = useContext(CreateNewListContext);
|
|
154
172
|
if (!ctx) return null;
|
|
155
173
|
const { formik, t } = ctx;
|
|
@@ -170,6 +188,8 @@ CreateNewList.PrivacyField = () => {
|
|
|
170
188
|
);
|
|
171
189
|
};
|
|
172
190
|
|
|
191
|
+
CreateNewList.PrivacyField = CreateNewListPrivacyField;
|
|
192
|
+
|
|
173
193
|
CreateNewList.Actions = ({ children }: { children: React.ReactNode }) => {
|
|
174
194
|
return (
|
|
175
195
|
<Grid
|
|
@@ -186,7 +206,7 @@ CreateNewList.Actions = ({ children }: { children: React.ReactNode }) => {
|
|
|
186
206
|
);
|
|
187
207
|
};
|
|
188
208
|
|
|
189
|
-
|
|
209
|
+
const CreateNewListSave = () => {
|
|
190
210
|
const ctx = useContext(CreateNewListContext);
|
|
191
211
|
if (!ctx) return null;
|
|
192
212
|
const { t } = ctx;
|
|
@@ -203,7 +223,9 @@ CreateNewList.Save = () => {
|
|
|
203
223
|
);
|
|
204
224
|
};
|
|
205
225
|
|
|
206
|
-
CreateNewList.
|
|
226
|
+
CreateNewList.Save = CreateNewListSave;
|
|
227
|
+
|
|
228
|
+
const CreateNewListCancel = () => {
|
|
207
229
|
const ctx = useContext(CreateNewListContext);
|
|
208
230
|
if (!ctx) return null;
|
|
209
231
|
const { handleClose, t } = ctx;
|
|
@@ -220,4 +242,6 @@ CreateNewList.Cancel = () => {
|
|
|
220
242
|
);
|
|
221
243
|
};
|
|
222
244
|
|
|
245
|
+
CreateNewList.Cancel = CreateNewListCancel;
|
|
246
|
+
|
|
223
247
|
export default CreateNewList;
|
|
@@ -14,7 +14,7 @@ import { Box, IconButton } from "@mui/material";
|
|
|
14
14
|
import type { SxProps } from "@mui/material/styles";
|
|
15
15
|
import { format } from "date-fns";
|
|
16
16
|
import { useTranslations } from "next-intl";
|
|
17
|
-
import { createContext, type ReactNode, useContext } from "react";
|
|
17
|
+
import { createContext, type ReactNode, useContext, useMemo } from "react";
|
|
18
18
|
import { MdDelete as Delete } from "react-icons/md";
|
|
19
19
|
import { useCreateAccountRoutes } from "../account-routes";
|
|
20
20
|
|
|
@@ -54,9 +54,13 @@ const WishlistItem = ({ wishlist, children, sx }: RootProps) => {
|
|
|
54
54
|
console.log(error);
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
|
+
const contextValue = useMemo(
|
|
58
|
+
() => ({ wishlist, handleDelete }),
|
|
59
|
+
[wishlist, handleDelete],
|
|
60
|
+
);
|
|
57
61
|
|
|
58
62
|
return (
|
|
59
|
-
<WishlistItemContext.Provider value={
|
|
63
|
+
<WishlistItemContext.Provider value={contextValue}>
|
|
60
64
|
<TableRow
|
|
61
65
|
sx={{
|
|
62
66
|
display: "grid",
|
|
@@ -73,7 +77,7 @@ const WishlistItem = ({ wishlist, children, sx }: RootProps) => {
|
|
|
73
77
|
);
|
|
74
78
|
};
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
const WishlistItemName = () => {
|
|
77
81
|
const { wishlists: wishlistsRoute } = useCreateAccountRoutes();
|
|
78
82
|
const ctx = useContext(WishlistItemContext);
|
|
79
83
|
if (!ctx) return null;
|
|
@@ -88,7 +92,9 @@ WishlistItem.Name = () => {
|
|
|
88
92
|
);
|
|
89
93
|
};
|
|
90
94
|
|
|
91
|
-
WishlistItem.
|
|
95
|
+
WishlistItem.Name = WishlistItemName;
|
|
96
|
+
|
|
97
|
+
const WishlistItemDate = () => {
|
|
92
98
|
const ctx = useContext(WishlistItemContext);
|
|
93
99
|
if (!ctx) return null;
|
|
94
100
|
const { wishlist } = ctx;
|
|
@@ -102,7 +108,9 @@ WishlistItem.Date = () => {
|
|
|
102
108
|
);
|
|
103
109
|
};
|
|
104
110
|
|
|
105
|
-
WishlistItem.
|
|
111
|
+
WishlistItem.Date = WishlistItemDate;
|
|
112
|
+
|
|
113
|
+
const WishlistItemProductCount = () => {
|
|
106
114
|
const ctx = useContext(WishlistItemContext);
|
|
107
115
|
const t = useTranslations("Account.Wishlists");
|
|
108
116
|
if (!ctx) return null;
|
|
@@ -114,6 +122,8 @@ WishlistItem.ProductCount = () => {
|
|
|
114
122
|
);
|
|
115
123
|
};
|
|
116
124
|
|
|
125
|
+
WishlistItem.ProductCount = WishlistItemProductCount;
|
|
126
|
+
|
|
117
127
|
WishlistItem.Actions = ({ children }: { children?: ReactNode }) => {
|
|
118
128
|
return (
|
|
119
129
|
<Box display="flex" alignItems="center" gap={1} justifySelf="end">
|
|
@@ -122,7 +132,7 @@ WishlistItem.Actions = ({ children }: { children?: ReactNode }) => {
|
|
|
122
132
|
);
|
|
123
133
|
};
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
const WishlistItemDelete = () => {
|
|
126
136
|
const ctx = useContext(WishlistItemContext);
|
|
127
137
|
if (!ctx) return null;
|
|
128
138
|
const { wishlist, handleDelete } = ctx;
|
|
@@ -133,4 +143,6 @@ WishlistItem.Delete = () => {
|
|
|
133
143
|
);
|
|
134
144
|
};
|
|
135
145
|
|
|
146
|
+
WishlistItem.Delete = WishlistItemDelete;
|
|
147
|
+
|
|
136
148
|
export default WishlistItem;
|
|
@@ -203,7 +203,7 @@ CartItemRoot.Right = ({ children }: { children: React.ReactNode }) => {
|
|
|
203
203
|
// --------------------
|
|
204
204
|
// Functional subcomponents
|
|
205
205
|
// --------------------
|
|
206
|
-
|
|
206
|
+
const CartItemRootImage = () => {
|
|
207
207
|
const ctx = useContext(CartItemContext);
|
|
208
208
|
if (!ctx) return null;
|
|
209
209
|
const { cartLine } = ctx;
|
|
@@ -219,7 +219,9 @@ CartItemRoot.Image = () => {
|
|
|
219
219
|
);
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
CartItemRoot.
|
|
222
|
+
CartItemRoot.Image = CartItemRootImage;
|
|
223
|
+
|
|
224
|
+
const CartItemRootRemoveButton = () => {
|
|
223
225
|
const ctx = useContext(CartItemContext);
|
|
224
226
|
const t = useTranslations("Cart.CartLine");
|
|
225
227
|
if (!ctx) return null;
|
|
@@ -237,7 +239,9 @@ CartItemRoot.RemoveButton = () => {
|
|
|
237
239
|
);
|
|
238
240
|
};
|
|
239
241
|
|
|
240
|
-
CartItemRoot.
|
|
242
|
+
CartItemRoot.RemoveButton = CartItemRootRemoveButton;
|
|
243
|
+
|
|
244
|
+
const CartItemRootTitle = () => {
|
|
241
245
|
const ctx = useContext(CartItemContext);
|
|
242
246
|
if (!ctx) return null;
|
|
243
247
|
const { cartLine } = ctx;
|
|
@@ -251,7 +255,9 @@ CartItemRoot.Title = () => {
|
|
|
251
255
|
);
|
|
252
256
|
};
|
|
253
257
|
|
|
254
|
-
CartItemRoot.
|
|
258
|
+
CartItemRoot.Title = CartItemRootTitle;
|
|
259
|
+
|
|
260
|
+
const CartItemRootDimensions = () => {
|
|
255
261
|
const ctx = useContext(CartItemContext);
|
|
256
262
|
if (!ctx) return null;
|
|
257
263
|
const { cartLine } = ctx;
|
|
@@ -271,7 +277,9 @@ CartItemRoot.Dimensions = () => {
|
|
|
271
277
|
);
|
|
272
278
|
};
|
|
273
279
|
|
|
274
|
-
CartItemRoot.
|
|
280
|
+
CartItemRoot.Dimensions = CartItemRootDimensions;
|
|
281
|
+
|
|
282
|
+
const CartItemRootPrice = () => {
|
|
275
283
|
const ctx = useContext(CartItemContext);
|
|
276
284
|
const { formatCurrency } = useCurrencyFormatter();
|
|
277
285
|
|
|
@@ -321,7 +329,9 @@ CartItemRoot.Price = () => {
|
|
|
321
329
|
);
|
|
322
330
|
};
|
|
323
331
|
|
|
324
|
-
CartItemRoot.
|
|
332
|
+
CartItemRoot.Price = CartItemRootPrice;
|
|
333
|
+
|
|
334
|
+
const CartItemRootQuantity = () => {
|
|
325
335
|
const ctx = useContext(CartItemContext);
|
|
326
336
|
const t = useTranslations("Cart.CartLine");
|
|
327
337
|
if (!ctx) return null;
|
|
@@ -361,7 +371,9 @@ CartItemRoot.Quantity = () => {
|
|
|
361
371
|
);
|
|
362
372
|
};
|
|
363
373
|
|
|
364
|
-
CartItemRoot.
|
|
374
|
+
CartItemRoot.Quantity = CartItemRootQuantity;
|
|
375
|
+
|
|
376
|
+
const CartItemRootComment = () => {
|
|
365
377
|
const ctx = useContext(CartItemContext);
|
|
366
378
|
const t = useTranslations("Cart.CartLine");
|
|
367
379
|
if (!ctx) return null;
|
|
@@ -454,6 +466,8 @@ CartItemRoot.Comment = () => {
|
|
|
454
466
|
return <CartItemRoot.Right>{content}</CartItemRoot.Right>;
|
|
455
467
|
};
|
|
456
468
|
|
|
469
|
+
CartItemRoot.Comment = CartItemRootComment;
|
|
470
|
+
|
|
457
471
|
const CartItem = Object.assign(CartItemRoot, {
|
|
458
472
|
// wrappers
|
|
459
473
|
Body: CartItemRoot.Body,
|
|
@@ -20,7 +20,14 @@ import { debounce } from "lodash";
|
|
|
20
20
|
import Link from "next/link";
|
|
21
21
|
import { useFormatter, useTranslations } from "next-intl";
|
|
22
22
|
import type React from "react";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
createContext,
|
|
25
|
+
useContext,
|
|
26
|
+
useEffect,
|
|
27
|
+
useMemo,
|
|
28
|
+
useRef,
|
|
29
|
+
useState,
|
|
30
|
+
} from "react";
|
|
24
31
|
|
|
25
32
|
interface CheckoutFormContextValue {
|
|
26
33
|
cartId: string;
|
|
@@ -76,29 +83,45 @@ const CheckoutForm = ({
|
|
|
76
83
|
}
|
|
77
84
|
}, [commentFromData]);
|
|
78
85
|
|
|
86
|
+
const contextValue = useMemo(
|
|
87
|
+
() => ({
|
|
88
|
+
cartId,
|
|
89
|
+
cartLineIds,
|
|
90
|
+
cartTotal,
|
|
91
|
+
countries,
|
|
92
|
+
appliedCoupons,
|
|
93
|
+
commentFromData,
|
|
94
|
+
checkoutDisabled,
|
|
95
|
+
selectedCountry,
|
|
96
|
+
setSelectedCountry,
|
|
97
|
+
states,
|
|
98
|
+
updateCartComment,
|
|
99
|
+
textRef,
|
|
100
|
+
}),
|
|
101
|
+
[
|
|
102
|
+
cartId,
|
|
103
|
+
cartLineIds,
|
|
104
|
+
cartTotal,
|
|
105
|
+
countries,
|
|
106
|
+
appliedCoupons,
|
|
107
|
+
commentFromData,
|
|
108
|
+
checkoutDisabled,
|
|
109
|
+
selectedCountry,
|
|
110
|
+
setSelectedCountry,
|
|
111
|
+
states,
|
|
112
|
+
updateCartComment,
|
|
113
|
+
textRef,
|
|
114
|
+
],
|
|
115
|
+
);
|
|
116
|
+
|
|
79
117
|
return (
|
|
80
|
-
<CheckoutFormContext.Provider
|
|
81
|
-
value={{
|
|
82
|
-
cartId,
|
|
83
|
-
cartLineIds,
|
|
84
|
-
cartTotal,
|
|
85
|
-
countries,
|
|
86
|
-
appliedCoupons,
|
|
87
|
-
commentFromData,
|
|
88
|
-
checkoutDisabled,
|
|
89
|
-
selectedCountry,
|
|
90
|
-
setSelectedCountry,
|
|
91
|
-
states,
|
|
92
|
-
updateCartComment,
|
|
93
|
-
textRef,
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
118
|
+
<CheckoutFormContext.Provider value={contextValue}>
|
|
96
119
|
<Card sx={{ padding: 3 }}>{children}</Card>
|
|
97
120
|
</CheckoutFormContext.Provider>
|
|
98
121
|
);
|
|
99
122
|
};
|
|
100
123
|
|
|
101
|
-
|
|
124
|
+
const CheckoutFormTotal = () => {
|
|
102
125
|
const ctx = useContext(CheckoutFormContext);
|
|
103
126
|
const { number } = useFormatter();
|
|
104
127
|
const t = useTranslations("Cart.Sidebar");
|
|
@@ -118,7 +141,9 @@ CheckoutForm.Total = () => {
|
|
|
118
141
|
);
|
|
119
142
|
};
|
|
120
143
|
|
|
121
|
-
CheckoutForm.
|
|
144
|
+
CheckoutForm.Total = CheckoutFormTotal;
|
|
145
|
+
|
|
146
|
+
const CheckoutFormNotes = () => {
|
|
122
147
|
const ctx = useContext(CheckoutFormContext);
|
|
123
148
|
const t = useTranslations("Cart.Sidebar");
|
|
124
149
|
if (!ctx) return null;
|
|
@@ -148,7 +173,9 @@ CheckoutForm.Notes = () => {
|
|
|
148
173
|
);
|
|
149
174
|
};
|
|
150
175
|
|
|
151
|
-
CheckoutForm.
|
|
176
|
+
CheckoutForm.Notes = CheckoutFormNotes;
|
|
177
|
+
|
|
178
|
+
const CheckoutFormCoupons = () => {
|
|
152
179
|
const ctx = useContext(CheckoutFormContext);
|
|
153
180
|
if (!ctx) return null;
|
|
154
181
|
const { appliedCoupons, cartId } = ctx;
|
|
@@ -165,7 +192,9 @@ CheckoutForm.Coupons = () => {
|
|
|
165
192
|
);
|
|
166
193
|
};
|
|
167
194
|
|
|
168
|
-
CheckoutForm.
|
|
195
|
+
CheckoutForm.Coupons = CheckoutFormCoupons;
|
|
196
|
+
|
|
197
|
+
const CheckoutFormEstimateShipping = () => {
|
|
169
198
|
const ctx = useContext(CheckoutFormContext);
|
|
170
199
|
if (!ctx) return null;
|
|
171
200
|
const { countries, states, setSelectedCountry, cartId, cartLineIds } = ctx;
|
|
@@ -190,7 +219,9 @@ CheckoutForm.EstimateShipping = () => {
|
|
|
190
219
|
);
|
|
191
220
|
};
|
|
192
221
|
|
|
193
|
-
CheckoutForm.
|
|
222
|
+
CheckoutForm.EstimateShipping = CheckoutFormEstimateShipping;
|
|
223
|
+
|
|
224
|
+
const CheckoutFormCheckoutButton = () => {
|
|
194
225
|
const ctx = useContext(CheckoutFormContext);
|
|
195
226
|
const t = useTranslations("Cart.Sidebar");
|
|
196
227
|
if (!ctx) return null;
|
|
@@ -210,4 +241,6 @@ CheckoutForm.CheckoutButton = () => {
|
|
|
210
241
|
);
|
|
211
242
|
};
|
|
212
243
|
|
|
244
|
+
CheckoutForm.CheckoutButton = CheckoutFormCheckoutButton;
|
|
245
|
+
|
|
213
246
|
export default CheckoutForm;
|
|
@@ -9,6 +9,7 @@ import Close from "@mui/icons-material/Close";
|
|
|
9
9
|
import {
|
|
10
10
|
Box,
|
|
11
11
|
Button,
|
|
12
|
+
CircularProgress,
|
|
12
13
|
IconButton,
|
|
13
14
|
List,
|
|
14
15
|
ListItem,
|
|
@@ -17,15 +18,19 @@ import {
|
|
|
17
18
|
} from "@mui/material";
|
|
18
19
|
import { useTranslations } from "next-intl";
|
|
19
20
|
import type React from "react";
|
|
20
|
-
import { createContext, useContext, useState } from "react";
|
|
21
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
21
22
|
|
|
22
23
|
interface CouponContextValue {
|
|
23
24
|
textValue: string;
|
|
24
25
|
setTextValue: (v: string) => void;
|
|
26
|
+
couponMessage: string;
|
|
27
|
+
couponMessageSeverity: "success" | "error";
|
|
25
28
|
appliedCoupons: Coupon[];
|
|
26
29
|
handleApplyCoupon: () => void;
|
|
27
30
|
handleRemoveCoupon: (coupon: string) => void;
|
|
31
|
+
isApplyingCoupon: boolean;
|
|
28
32
|
cartId: string;
|
|
33
|
+
setCouponMessage: (msg: string) => void;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
const CouponContext = createContext<CouponContextValue | null>(null);
|
|
@@ -40,60 +45,139 @@ const CouponEntry = ({
|
|
|
40
45
|
cartId,
|
|
41
46
|
children,
|
|
42
47
|
}: CouponEntryProps) => {
|
|
48
|
+
const t = useTranslations("Cart.Coupons");
|
|
43
49
|
const [textValue, setTextValue] = useState("");
|
|
44
|
-
const
|
|
50
|
+
const [couponMessage, setCouponMessage] = useState("");
|
|
51
|
+
const [couponMessageSeverity, setCouponMessageSeverity] = useState<
|
|
52
|
+
"success" | "error"
|
|
53
|
+
>("error");
|
|
54
|
+
const { mutate: updateCoupon, isPending: isApplyingCoupon } =
|
|
55
|
+
useAddDiscountCode();
|
|
45
56
|
const { mutate: removeCoupon } = useRemoveDiscountcode();
|
|
46
57
|
|
|
47
58
|
const handleApplyCoupon = () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
const enteredCode = textValue.trim();
|
|
60
|
+
if (!enteredCode) return;
|
|
61
|
+
setCouponMessage("");
|
|
62
|
+
updateCoupon(
|
|
63
|
+
{ discountCode: enteredCode, cartId },
|
|
64
|
+
{
|
|
65
|
+
onSuccess: (result) => {
|
|
66
|
+
// Treat as success only when the entered code is present in returned coupons.
|
|
67
|
+
const hasAppliedCoupon = (result?.Coupons ?? []).some(
|
|
68
|
+
(coupon) =>
|
|
69
|
+
(coupon?.Code ?? "").toLowerCase() === enteredCode.toLowerCase(),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (!hasAppliedCoupon) {
|
|
73
|
+
setCouponMessageSeverity("error");
|
|
74
|
+
setCouponMessage(t("invalidCoupon"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setCouponMessageSeverity("success");
|
|
79
|
+
setCouponMessage(t("couponAppliedSuccessfully"));
|
|
80
|
+
setTextValue("");
|
|
81
|
+
},
|
|
82
|
+
onError: () => {
|
|
83
|
+
setCouponMessageSeverity("error");
|
|
84
|
+
setCouponMessage(t("failedToApplyCoupon"));
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
);
|
|
51
88
|
};
|
|
52
89
|
|
|
53
90
|
const handleRemoveCoupon = (coupon: string) => {
|
|
54
91
|
removeCoupon({ discountCode: coupon, cartId });
|
|
55
92
|
};
|
|
93
|
+
const contextValue = useMemo(
|
|
94
|
+
() => ({
|
|
95
|
+
textValue,
|
|
96
|
+
setTextValue,
|
|
97
|
+
couponMessage,
|
|
98
|
+
couponMessageSeverity,
|
|
99
|
+
appliedCoupons,
|
|
100
|
+
handleApplyCoupon,
|
|
101
|
+
handleRemoveCoupon,
|
|
102
|
+
isApplyingCoupon,
|
|
103
|
+
cartId,
|
|
104
|
+
setCouponMessage,
|
|
105
|
+
}),
|
|
106
|
+
[
|
|
107
|
+
textValue,
|
|
108
|
+
setTextValue,
|
|
109
|
+
couponMessage,
|
|
110
|
+
couponMessageSeverity,
|
|
111
|
+
appliedCoupons,
|
|
112
|
+
handleApplyCoupon,
|
|
113
|
+
handleRemoveCoupon,
|
|
114
|
+
isApplyingCoupon,
|
|
115
|
+
cartId,
|
|
116
|
+
setCouponMessage,
|
|
117
|
+
],
|
|
118
|
+
);
|
|
56
119
|
|
|
57
120
|
return (
|
|
58
|
-
<CouponContext.Provider
|
|
59
|
-
value={{
|
|
60
|
-
textValue,
|
|
61
|
-
setTextValue,
|
|
62
|
-
appliedCoupons,
|
|
63
|
-
handleApplyCoupon,
|
|
64
|
-
handleRemoveCoupon,
|
|
65
|
-
cartId,
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
121
|
+
<CouponContext.Provider value={contextValue}>
|
|
68
122
|
<Box>{children}</Box>
|
|
69
123
|
</CouponContext.Provider>
|
|
70
124
|
);
|
|
71
125
|
};
|
|
72
126
|
|
|
73
|
-
|
|
127
|
+
const CouponEntryInput = () => {
|
|
74
128
|
const ctx = useContext(CouponContext);
|
|
75
129
|
const t = useTranslations("Cart.Coupons");
|
|
130
|
+
|
|
131
|
+
// Auto-hide message after 3.5 seconds
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!ctx?.couponMessage) return;
|
|
134
|
+
|
|
135
|
+
const timer = setTimeout(() => {
|
|
136
|
+
ctx.setCouponMessage("");
|
|
137
|
+
}, 3500);
|
|
138
|
+
|
|
139
|
+
return () => clearTimeout(timer);
|
|
140
|
+
}, [ctx?.couponMessage, ctx?.setCouponMessage]);
|
|
141
|
+
|
|
76
142
|
if (!ctx) return null;
|
|
77
|
-
const { textValue, setTextValue } = ctx;
|
|
143
|
+
const { textValue, setTextValue, couponMessage, couponMessageSeverity } = ctx;
|
|
78
144
|
|
|
79
145
|
return (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
146
|
+
<>
|
|
147
|
+
<TextField
|
|
148
|
+
fullWidth
|
|
149
|
+
size="small"
|
|
150
|
+
label={t("coupon")}
|
|
151
|
+
variant="outlined"
|
|
152
|
+
placeholder={t("coupon")}
|
|
153
|
+
value={textValue}
|
|
154
|
+
onChange={(e) => setTextValue(e.target.value)}
|
|
155
|
+
/>
|
|
156
|
+
{couponMessage ? (
|
|
157
|
+
<Box
|
|
158
|
+
sx={{
|
|
159
|
+
mt: 1,
|
|
160
|
+
fontSize: 14,
|
|
161
|
+
color:
|
|
162
|
+
couponMessageSeverity === "success"
|
|
163
|
+
? "success.main"
|
|
164
|
+
: "error.main",
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{couponMessage}
|
|
168
|
+
</Box>
|
|
169
|
+
) : null}
|
|
170
|
+
</>
|
|
89
171
|
);
|
|
90
172
|
};
|
|
91
173
|
|
|
92
|
-
CouponEntry.
|
|
174
|
+
CouponEntry.Input = CouponEntryInput;
|
|
175
|
+
|
|
176
|
+
const CouponEntryApplyButton = () => {
|
|
93
177
|
const ctx = useContext(CouponContext);
|
|
94
178
|
const t = useTranslations("Cart.Coupons");
|
|
95
179
|
if (!ctx) return null;
|
|
96
|
-
const { handleApplyCoupon } = ctx;
|
|
180
|
+
const { handleApplyCoupon, isApplyingCoupon, textValue } = ctx;
|
|
97
181
|
|
|
98
182
|
return (
|
|
99
183
|
<Button
|
|
@@ -101,14 +185,20 @@ CouponEntry.ApplyButton = () => {
|
|
|
101
185
|
color="primary"
|
|
102
186
|
fullWidth
|
|
103
187
|
sx={{ mt: 2, mb: 4 }}
|
|
188
|
+
disabled={isApplyingCoupon || !textValue.trim()}
|
|
104
189
|
onClick={handleApplyCoupon}
|
|
190
|
+
startIcon={
|
|
191
|
+
isApplyingCoupon ? <CircularProgress size={16} color="inherit" /> : null
|
|
192
|
+
}
|
|
105
193
|
>
|
|
106
194
|
{t("applyCouponButton")}
|
|
107
195
|
</Button>
|
|
108
196
|
);
|
|
109
197
|
};
|
|
110
198
|
|
|
111
|
-
CouponEntry.
|
|
199
|
+
CouponEntry.ApplyButton = CouponEntryApplyButton;
|
|
200
|
+
|
|
201
|
+
const CouponEntryAppliedList = () => {
|
|
112
202
|
const ctx = useContext(CouponContext);
|
|
113
203
|
const t = useTranslations("Cart.Coupons");
|
|
114
204
|
if (!ctx) return null;
|
|
@@ -117,20 +207,22 @@ CouponEntry.AppliedList = () => {
|
|
|
117
207
|
if (!appliedCoupons.length) return null;
|
|
118
208
|
|
|
119
209
|
return (
|
|
120
|
-
|
|
210
|
+
<Box>
|
|
121
211
|
<FlexBox alignItems="center" columnGap={1} mb={2}>
|
|
122
212
|
<Span fontWeight="600">{t("appliedCoupons")}</Span>
|
|
123
213
|
</FlexBox>
|
|
124
|
-
<List>
|
|
214
|
+
<List sx={{ p: 0, display: "flex", flexDirection: "column", gap: 1 }}>
|
|
125
215
|
{appliedCoupons.map((coupon) => (
|
|
126
216
|
<CouponEntry.AppliedItem key={coupon.Code} coupon={coupon} />
|
|
127
217
|
))}
|
|
128
218
|
</List>
|
|
129
|
-
|
|
219
|
+
</Box>
|
|
130
220
|
);
|
|
131
221
|
};
|
|
132
222
|
|
|
133
|
-
CouponEntry.
|
|
223
|
+
CouponEntry.AppliedList = CouponEntryAppliedList;
|
|
224
|
+
|
|
225
|
+
const CouponEntryAppliedItem = ({ coupon }: { coupon: Coupon }) => {
|
|
134
226
|
const ctx = useContext(CouponContext);
|
|
135
227
|
if (!ctx) return null;
|
|
136
228
|
const { handleRemoveCoupon } = ctx;
|
|
@@ -139,17 +231,32 @@ CouponEntry.AppliedItem = ({ coupon }: { coupon: Coupon }) => {
|
|
|
139
231
|
secondaryAction={
|
|
140
232
|
<IconButton
|
|
141
233
|
edge="end"
|
|
142
|
-
|
|
234
|
+
size="small"
|
|
143
235
|
onClick={() => handleRemoveCoupon(coupon.Code ?? "")}
|
|
144
236
|
title="Remove Coupon"
|
|
237
|
+
sx={{ ml: 1 }}
|
|
145
238
|
>
|
|
146
|
-
<Close />
|
|
239
|
+
<Close fontSize="small" />
|
|
147
240
|
</IconButton>
|
|
148
241
|
}
|
|
242
|
+
sx={{
|
|
243
|
+
width: "100%",
|
|
244
|
+
px: 1.5,
|
|
245
|
+
py: 1,
|
|
246
|
+
border: "1px solid",
|
|
247
|
+
borderColor: "primary.main",
|
|
248
|
+
borderRadius: 2,
|
|
249
|
+
backgroundColor: "background.paper",
|
|
250
|
+
"&:hover": {
|
|
251
|
+
backgroundColor: "action.hover",
|
|
252
|
+
},
|
|
253
|
+
}}
|
|
149
254
|
>
|
|
150
|
-
<ListItemText primary={coupon.Code} />
|
|
255
|
+
<ListItemText primary={coupon.Code} sx={{ my: 0, fontSize: 14 }} />
|
|
151
256
|
</ListItem>
|
|
152
257
|
);
|
|
153
258
|
};
|
|
154
259
|
|
|
260
|
+
CouponEntry.AppliedItem = CouponEntryAppliedItem;
|
|
261
|
+
|
|
155
262
|
export default CouponEntry;
|