@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
|
@@ -26,7 +26,13 @@ import RemoveRedEye from "@mui/icons-material/RemoveRedEye";
|
|
|
26
26
|
import IconButton from "@mui/material/IconButton";
|
|
27
27
|
import Rating from "@mui/material/Rating";
|
|
28
28
|
import Link from "next/link";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
createContext,
|
|
31
|
+
type ReactNode,
|
|
32
|
+
useContext,
|
|
33
|
+
useMemo,
|
|
34
|
+
useState,
|
|
35
|
+
} from "react";
|
|
30
36
|
|
|
31
37
|
type Product = {
|
|
32
38
|
title: string;
|
|
@@ -52,11 +58,13 @@ type ProductCardPlpProps = {
|
|
|
52
58
|
const ProductCardPlp = ({ product, children }: ProductCardPlpProps) => {
|
|
53
59
|
const [openProductModal, setOpenProductModal] = useState(false);
|
|
54
60
|
const [openAddToWishlistDialog, setOpenAddToWishlistDialog] = useState(false);
|
|
61
|
+
const contextValue = useMemo(
|
|
62
|
+
() => ({ product, setOpenAddToWishlistDialog, setOpenProductModal }),
|
|
63
|
+
[product],
|
|
64
|
+
);
|
|
55
65
|
|
|
56
66
|
return (
|
|
57
|
-
<ProductCardPlpContext.Provider
|
|
58
|
-
value={{ product, setOpenAddToWishlistDialog, setOpenProductModal }}
|
|
59
|
-
>
|
|
67
|
+
<ProductCardPlpContext.Provider value={contextValue}>
|
|
60
68
|
<div>{children}</div>
|
|
61
69
|
{openAddToWishlistDialog && (
|
|
62
70
|
<AddToWishlistDialog
|
|
@@ -76,7 +84,7 @@ const ProductCardPlp = ({ product, children }: ProductCardPlpProps) => {
|
|
|
76
84
|
);
|
|
77
85
|
};
|
|
78
86
|
|
|
79
|
-
|
|
87
|
+
const ImageWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
80
88
|
const ctx = useContext(ProductCardPlpContext);
|
|
81
89
|
if (!ctx) return null;
|
|
82
90
|
|
|
@@ -91,7 +99,7 @@ ProductCardPlp.ImageWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
91
99
|
);
|
|
92
100
|
};
|
|
93
101
|
|
|
94
|
-
|
|
102
|
+
const PlpImage = () => {
|
|
95
103
|
const ctx = useContext(ProductCardPlpContext);
|
|
96
104
|
if (!ctx) return null;
|
|
97
105
|
|
|
@@ -112,19 +120,19 @@ ProductCardPlp.Image = () => {
|
|
|
112
120
|
);
|
|
113
121
|
};
|
|
114
122
|
|
|
115
|
-
|
|
123
|
+
const PlpBody = ({ children }: { children: React.ReactNode }) => {
|
|
116
124
|
return <FlexBetween alignItems="flex-end">{children}</FlexBetween>;
|
|
117
125
|
};
|
|
118
126
|
|
|
119
|
-
|
|
127
|
+
const PlpInfo = ({ children }: { children: React.ReactNode }) => {
|
|
120
128
|
return <div>{children}</div>;
|
|
121
129
|
};
|
|
122
130
|
|
|
123
|
-
|
|
131
|
+
const PlpActions = ({ children }: { children: React.ReactNode }) => {
|
|
124
132
|
return <>{children}</>;
|
|
125
133
|
};
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
const PlpTitle = () => {
|
|
128
136
|
const ctx = useContext(ProductCardPlpContext);
|
|
129
137
|
if (!ctx) return null;
|
|
130
138
|
|
|
@@ -138,7 +146,7 @@ ProductCardPlp.Title = () => {
|
|
|
138
146
|
);
|
|
139
147
|
};
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
const PlpRating = () => {
|
|
142
150
|
const ctx = useContext(ProductCardPlpContext);
|
|
143
151
|
if (!ctx) return null;
|
|
144
152
|
|
|
@@ -147,7 +155,7 @@ ProductCardPlp.Rating = () => {
|
|
|
147
155
|
return <Rating readOnly value={rating} size="small" precision={0.5} />;
|
|
148
156
|
};
|
|
149
157
|
|
|
150
|
-
|
|
158
|
+
const PlpPrice = () => {
|
|
151
159
|
const ctx = useContext(ProductCardPlpContext);
|
|
152
160
|
if (!ctx) return null;
|
|
153
161
|
|
|
@@ -168,13 +176,13 @@ ProductCardPlp.Price = () => {
|
|
|
168
176
|
);
|
|
169
177
|
};
|
|
170
178
|
|
|
171
|
-
|
|
179
|
+
const HoverActions = ({ children }: { children: React.ReactNode }) => {
|
|
172
180
|
return (
|
|
173
181
|
<HoverIconWrapper className="hover-box"> {children} </HoverIconWrapper>
|
|
174
182
|
);
|
|
175
183
|
};
|
|
176
184
|
|
|
177
|
-
|
|
185
|
+
const QuickViewButton = () => {
|
|
178
186
|
const ctx = useContext(ProductCardPlpContext);
|
|
179
187
|
if (!ctx) return null;
|
|
180
188
|
|
|
@@ -191,7 +199,7 @@ ProductCardPlp.QuickViewButton = () => {
|
|
|
191
199
|
);
|
|
192
200
|
};
|
|
193
201
|
|
|
194
|
-
|
|
202
|
+
const PlpFavoriteButton = () => {
|
|
195
203
|
const ctx = useContext(ProductCardPlpContext);
|
|
196
204
|
|
|
197
205
|
const id = ctx?.product?.id;
|
|
@@ -210,7 +218,7 @@ ProductCardPlp.FavoriteButton = () => {
|
|
|
210
218
|
};
|
|
211
219
|
// Using quantity buttons (addtocart) doesn't make sense when we have product variants to select.
|
|
212
220
|
// So hide them for now, implement based on client requirements.
|
|
213
|
-
|
|
221
|
+
const PlpQuantityButtons = () => {
|
|
214
222
|
const ctx = useContext(ProductCardPlpContext);
|
|
215
223
|
|
|
216
224
|
const id = ctx?.product?.id;
|
|
@@ -243,4 +251,17 @@ ProductCardPlp.QuantityButtons = () => {
|
|
|
243
251
|
);
|
|
244
252
|
};
|
|
245
253
|
|
|
254
|
+
ProductCardPlp.ImageWrapper = ImageWrapper;
|
|
255
|
+
ProductCardPlp.Image = PlpImage;
|
|
256
|
+
ProductCardPlp.Body = PlpBody;
|
|
257
|
+
ProductCardPlp.Info = PlpInfo;
|
|
258
|
+
ProductCardPlp.Actions = PlpActions;
|
|
259
|
+
ProductCardPlp.Title = PlpTitle;
|
|
260
|
+
ProductCardPlp.Rating = PlpRating;
|
|
261
|
+
ProductCardPlp.Price = PlpPrice;
|
|
262
|
+
ProductCardPlp.HoverActions = HoverActions;
|
|
263
|
+
ProductCardPlp.QuickViewButton = QuickViewButton;
|
|
264
|
+
ProductCardPlp.FavoriteButton = PlpFavoriteButton;
|
|
265
|
+
ProductCardPlp.QuantityButtons = PlpQuantityButtons;
|
|
266
|
+
|
|
246
267
|
export default ProductCardPlp;
|
|
@@ -19,7 +19,13 @@ import IconButton from "@mui/material/IconButton";
|
|
|
19
19
|
import Rating from "@mui/material/Rating";
|
|
20
20
|
import styled from "@mui/material/styles/styled";
|
|
21
21
|
import Link from "next/link";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
createContext,
|
|
24
|
+
type ReactNode,
|
|
25
|
+
useContext,
|
|
26
|
+
useMemo,
|
|
27
|
+
useState,
|
|
28
|
+
} from "react";
|
|
23
29
|
|
|
24
30
|
// STYLED COMPONENT
|
|
25
31
|
const Wrapper = styled(Card)({
|
|
@@ -83,10 +89,13 @@ type ProductCardPlpListProps = {
|
|
|
83
89
|
const ProductCardPlpList = ({ product, children }: ProductCardPlpListProps) => {
|
|
84
90
|
const [openAddToWishlistDialog, setOpenAddToWishlistDialog] = useState(false);
|
|
85
91
|
const [openProductModal, setOpenProductModal] = useState(false);
|
|
92
|
+
const contextValue = useMemo(
|
|
93
|
+
() => ({ product, setOpenAddToWishlistDialog, setOpenProductModal }),
|
|
94
|
+
[product],
|
|
95
|
+
);
|
|
96
|
+
|
|
86
97
|
return (
|
|
87
|
-
<ProductCardPlpListContext.Provider
|
|
88
|
-
value={{ product, setOpenAddToWishlistDialog, setOpenProductModal }}
|
|
89
|
-
>
|
|
98
|
+
<ProductCardPlpListContext.Provider value={contextValue}>
|
|
90
99
|
<Wrapper>{children}</Wrapper>
|
|
91
100
|
{openProductModal && (
|
|
92
101
|
<ProductViewDialog
|
|
@@ -106,7 +115,7 @@ const ProductCardPlpList = ({ product, children }: ProductCardPlpListProps) => {
|
|
|
106
115
|
);
|
|
107
116
|
};
|
|
108
117
|
|
|
109
|
-
|
|
118
|
+
const QuickViewButton = () => {
|
|
110
119
|
const ctx = useContext(ProductCardPlpListContext);
|
|
111
120
|
if (!ctx) return null;
|
|
112
121
|
|
|
@@ -122,11 +131,11 @@ ProductCardPlpList.QuickViewButton = () => {
|
|
|
122
131
|
);
|
|
123
132
|
};
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
const Body = ({ children }: { children: React.ReactNode }) => {
|
|
126
135
|
return <ContentWrapper>{children}</ContentWrapper>;
|
|
127
136
|
};
|
|
128
137
|
|
|
129
|
-
|
|
138
|
+
const Image = () => {
|
|
130
139
|
const ctx = useContext(ProductCardPlpListContext);
|
|
131
140
|
if (!ctx) return null;
|
|
132
141
|
|
|
@@ -146,19 +155,19 @@ ProductCardPlpList.Image = () => {
|
|
|
146
155
|
);
|
|
147
156
|
};
|
|
148
157
|
|
|
149
|
-
|
|
158
|
+
const Content = ({ children }: { children: React.ReactNode }) => {
|
|
150
159
|
return <div className="content">{children}</div>;
|
|
151
160
|
};
|
|
152
161
|
|
|
153
|
-
|
|
162
|
+
const Info = ({ children }: { children: React.ReactNode }) => {
|
|
154
163
|
return <div>{children}</div>;
|
|
155
164
|
};
|
|
156
165
|
|
|
157
|
-
|
|
166
|
+
const Actions = ({ children }: { children: React.ReactNode }) => {
|
|
158
167
|
return <>{children}</>;
|
|
159
168
|
};
|
|
160
169
|
|
|
161
|
-
|
|
170
|
+
const Tags = () => {
|
|
162
171
|
const ctx = useContext(ProductCardPlpListContext);
|
|
163
172
|
if (!ctx) return null;
|
|
164
173
|
|
|
@@ -167,7 +176,7 @@ ProductCardPlpList.Tags = () => {
|
|
|
167
176
|
return <ProductTags tags={tags} />;
|
|
168
177
|
};
|
|
169
178
|
|
|
170
|
-
|
|
179
|
+
const Title = () => {
|
|
171
180
|
const ctx = useContext(ProductCardPlpListContext);
|
|
172
181
|
if (!ctx) return null;
|
|
173
182
|
|
|
@@ -181,7 +190,7 @@ ProductCardPlpList.Title = () => {
|
|
|
181
190
|
);
|
|
182
191
|
};
|
|
183
192
|
|
|
184
|
-
|
|
193
|
+
const ProductRating = () => {
|
|
185
194
|
const ctx = useContext(ProductCardPlpListContext);
|
|
186
195
|
if (!ctx) return null;
|
|
187
196
|
|
|
@@ -190,7 +199,7 @@ ProductCardPlpList.Rating = () => {
|
|
|
190
199
|
return <Rating size="small" value={rating} color="warn" readOnly />;
|
|
191
200
|
};
|
|
192
201
|
|
|
193
|
-
|
|
202
|
+
const Price = () => {
|
|
194
203
|
const ctx = useContext(ProductCardPlpListContext);
|
|
195
204
|
if (!ctx) return null;
|
|
196
205
|
|
|
@@ -199,7 +208,7 @@ ProductCardPlpList.Price = () => {
|
|
|
199
208
|
return <ProductPrice price={price} discount={discount ?? 0} />;
|
|
200
209
|
};
|
|
201
210
|
|
|
202
|
-
|
|
211
|
+
const PlpFavoriteButton = () => {
|
|
203
212
|
const ctx = useContext(ProductCardPlpListContext);
|
|
204
213
|
|
|
205
214
|
const id = ctx?.product?.id;
|
|
@@ -219,7 +228,7 @@ ProductCardPlpList.FavoriteButton = () => {
|
|
|
219
228
|
|
|
220
229
|
// Using quantity buttons (addtocart) doesn't make sense when we have product variants to select.
|
|
221
230
|
// So hide them for now, implement based on client requirements.
|
|
222
|
-
|
|
231
|
+
const PlpQuantityButtons = () => {
|
|
223
232
|
const ctx = useContext(ProductCardPlpListContext);
|
|
224
233
|
|
|
225
234
|
const id = ctx?.product?.id;
|
|
@@ -252,4 +261,17 @@ ProductCardPlpList.QuantityButtons = () => {
|
|
|
252
261
|
);
|
|
253
262
|
};
|
|
254
263
|
|
|
264
|
+
ProductCardPlpList.QuickViewButton = QuickViewButton;
|
|
265
|
+
ProductCardPlpList.Body = Body;
|
|
266
|
+
ProductCardPlpList.Image = Image;
|
|
267
|
+
ProductCardPlpList.Content = Content;
|
|
268
|
+
ProductCardPlpList.Info = Info;
|
|
269
|
+
ProductCardPlpList.Actions = Actions;
|
|
270
|
+
ProductCardPlpList.Tags = Tags;
|
|
271
|
+
ProductCardPlpList.Title = Title;
|
|
272
|
+
ProductCardPlpList.Rating = ProductRating;
|
|
273
|
+
ProductCardPlpList.Price = Price;
|
|
274
|
+
ProductCardPlpList.FavoriteButton = PlpFavoriteButton;
|
|
275
|
+
ProductCardPlpList.QuantityButtons = PlpQuantityButtons;
|
|
276
|
+
|
|
255
277
|
export default ProductCardPlpList;
|
|
@@ -26,7 +26,13 @@ import { RemoveRedEye } from "@mui/icons-material";
|
|
|
26
26
|
import Box from "@mui/material/Box";
|
|
27
27
|
import Rating from "@mui/material/Rating";
|
|
28
28
|
import Link from "next/link";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
createContext,
|
|
31
|
+
type ReactNode,
|
|
32
|
+
useContext,
|
|
33
|
+
useMemo,
|
|
34
|
+
useState,
|
|
35
|
+
} from "react";
|
|
30
36
|
|
|
31
37
|
type Product = {
|
|
32
38
|
slug: string;
|
|
@@ -56,10 +62,13 @@ const ProductCardSearch = ({ product, children }: ProductCardSearchProps) => {
|
|
|
56
62
|
const handleToggleProductModal = () => {
|
|
57
63
|
setOpenProductModal((prev) => !prev);
|
|
58
64
|
};
|
|
65
|
+
const contextValue = useMemo(
|
|
66
|
+
() => ({ product, handleToggleProductModal, setOpenAddToWishlistDialog }),
|
|
67
|
+
[product, handleToggleProductModal],
|
|
68
|
+
);
|
|
69
|
+
|
|
59
70
|
return (
|
|
60
|
-
<ProductCardSearchContext.Provider
|
|
61
|
-
value={{ product, handleToggleProductModal, setOpenAddToWishlistDialog }}
|
|
62
|
-
>
|
|
71
|
+
<ProductCardSearchContext.Provider value={contextValue}>
|
|
63
72
|
<Card>{children}</Card>
|
|
64
73
|
{openProductModal && (
|
|
65
74
|
<ProductViewDialog
|
|
@@ -81,10 +90,11 @@ const ProductCardSearch = ({ product, children }: ProductCardSearchProps) => {
|
|
|
81
90
|
|
|
82
91
|
// ===== Subcomponents =====
|
|
83
92
|
|
|
84
|
-
|
|
93
|
+
const SearchCardMedia = ({ children }: { children: React.ReactNode }) => {
|
|
85
94
|
return <CardMedia>{children}</CardMedia>;
|
|
86
95
|
};
|
|
87
|
-
|
|
96
|
+
|
|
97
|
+
const SearchImage = () => {
|
|
88
98
|
const ctx = useContext(ProductCardSearchContext);
|
|
89
99
|
if (!ctx) return null;
|
|
90
100
|
const { product } = ctx;
|
|
@@ -102,14 +112,15 @@ ProductCardSearch.Image = () => {
|
|
|
102
112
|
);
|
|
103
113
|
};
|
|
104
114
|
|
|
105
|
-
|
|
115
|
+
const SearchInfo = ({ children }: { children: React.ReactNode }) => {
|
|
106
116
|
return (
|
|
107
117
|
<Box p={1} textAlign="center">
|
|
108
118
|
{children}
|
|
109
119
|
</Box>
|
|
110
120
|
);
|
|
111
121
|
};
|
|
112
|
-
|
|
122
|
+
|
|
123
|
+
const SearchCategory = () => {
|
|
113
124
|
const ctx = useContext(ProductCardSearchContext);
|
|
114
125
|
if (!ctx) return null;
|
|
115
126
|
const { product } = ctx;
|
|
@@ -118,14 +129,14 @@ ProductCardSearch.Category = () => {
|
|
|
118
129
|
return <Small color="grey.500">{categories[0]}</Small>;
|
|
119
130
|
};
|
|
120
131
|
|
|
121
|
-
|
|
132
|
+
const SearchTitle = () => {
|
|
122
133
|
const ctx = useContext(ProductCardSearchContext);
|
|
123
134
|
if (!ctx) return null;
|
|
124
135
|
const { product } = ctx;
|
|
125
136
|
return <Paragraph fontWeight="bold">{product?.title}</Paragraph>;
|
|
126
137
|
};
|
|
127
138
|
|
|
128
|
-
|
|
139
|
+
const SearchPrice = () => {
|
|
129
140
|
const ctx = useContext(ProductCardSearchContext);
|
|
130
141
|
if (!ctx) return null;
|
|
131
142
|
const { product } = ctx;
|
|
@@ -136,7 +147,7 @@ ProductCardSearch.Price = () => {
|
|
|
136
147
|
);
|
|
137
148
|
};
|
|
138
149
|
|
|
139
|
-
|
|
150
|
+
const SearchRating = () => {
|
|
140
151
|
const ctx = useContext(ProductCardSearchContext);
|
|
141
152
|
if (!ctx) return null;
|
|
142
153
|
const { product } = ctx;
|
|
@@ -155,7 +166,7 @@ ProductCardSearch.Rating = () => {
|
|
|
155
166
|
);
|
|
156
167
|
};
|
|
157
168
|
|
|
158
|
-
|
|
169
|
+
const SearchQuickViewButton = () => {
|
|
159
170
|
const ctx = useContext(ProductCardSearchContext);
|
|
160
171
|
if (!ctx) return null;
|
|
161
172
|
|
|
@@ -169,7 +180,7 @@ ProductCardSearch.QuickViewButton = () => {
|
|
|
169
180
|
);
|
|
170
181
|
};
|
|
171
182
|
|
|
172
|
-
|
|
183
|
+
const SearchFavoriteButton = () => {
|
|
173
184
|
const ctx = useContext(ProductCardSearchContext);
|
|
174
185
|
|
|
175
186
|
const id = ctx?.product?.id;
|
|
@@ -194,4 +205,14 @@ ProductCardSearch.FavoriteButton = () => {
|
|
|
194
205
|
);
|
|
195
206
|
};
|
|
196
207
|
|
|
208
|
+
ProductCardSearch.CardMedia = SearchCardMedia;
|
|
209
|
+
ProductCardSearch.Image = SearchImage;
|
|
210
|
+
ProductCardSearch.Info = SearchInfo;
|
|
211
|
+
ProductCardSearch.Category = SearchCategory;
|
|
212
|
+
ProductCardSearch.Title = SearchTitle;
|
|
213
|
+
ProductCardSearch.Price = SearchPrice;
|
|
214
|
+
ProductCardSearch.Rating = SearchRating;
|
|
215
|
+
ProductCardSearch.QuickViewButton = SearchQuickViewButton;
|
|
216
|
+
ProductCardSearch.FavoriteButton = SearchFavoriteButton;
|
|
217
|
+
|
|
197
218
|
export default ProductCardSearch;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Button } from "@mui/material";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import type React from "react";
|
|
3
4
|
import { useArgs } from "storybook/internal/preview-api";
|
|
4
5
|
import { data } from "../../../__mocks__/products";
|
|
5
6
|
import ProductDialog from "./index";
|
|
@@ -13,25 +14,26 @@ const meta = {
|
|
|
13
14
|
export default meta;
|
|
14
15
|
type Story = StoryObj<typeof ProductDialog>;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const WithTriggerRender = (
|
|
18
|
+
args: React.ComponentProps<typeof ProductDialog>,
|
|
19
|
+
) => {
|
|
20
|
+
const [, setArgs] = useArgs();
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<Button variant="contained" onClick={() => setArgs({ openDialog: true })}>
|
|
25
|
+
Open Product Dialog
|
|
26
|
+
</Button>
|
|
27
|
+
<ProductDialog
|
|
28
|
+
{...args}
|
|
29
|
+
handleCloseDialog={() => setArgs({ openDialog: false })}
|
|
30
|
+
/>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const WithTrigger: Story = {
|
|
36
|
+
render: (args) => <WithTriggerRender {...args} />,
|
|
35
37
|
args: {
|
|
36
38
|
productId: Number(data.products[0].ItemId),
|
|
37
39
|
openDialog: false,
|
|
@@ -22,7 +22,7 @@ import Divider from "@mui/material/Divider";
|
|
|
22
22
|
import Grid from "@mui/material/Grid2";
|
|
23
23
|
import IconButton from "@mui/material/IconButton";
|
|
24
24
|
import Rating from "@mui/material/Rating";
|
|
25
|
-
import { createContext, type ReactElement, useContext } from "react";
|
|
25
|
+
import { createContext, type ReactElement, useContext, useMemo } from "react";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* @category Props
|
|
@@ -92,11 +92,14 @@ export default function ProductDialog(props: Readonly<Props>) {
|
|
|
92
92
|
const { data: product } = getProductById.useSuspenseData({
|
|
93
93
|
id: Number(props.productId),
|
|
94
94
|
});
|
|
95
|
+
const contextValue = useMemo(
|
|
96
|
+
() => ({ product, openDialog, handleCloseDialog }),
|
|
97
|
+
[product, openDialog, handleCloseDialog],
|
|
98
|
+
);
|
|
95
99
|
if (!product) return null;
|
|
100
|
+
|
|
96
101
|
return (
|
|
97
|
-
<ProductDialogContext.Provider
|
|
98
|
-
value={{ product, openDialog, handleCloseDialog }}
|
|
99
|
-
>
|
|
102
|
+
<ProductDialogContext.Provider value={contextValue}>
|
|
100
103
|
<Dialog
|
|
101
104
|
open={openDialog}
|
|
102
105
|
maxWidth={false}
|
|
@@ -7,7 +7,7 @@ import { DimensionGroupLabel } from "@evenicanpm/storefront-core/src/components/
|
|
|
7
7
|
import { DimensionGroupList } from "@evenicanpm/storefront-core/src/components/product-dimensions/compound/dimensions-group-list";
|
|
8
8
|
import type { ProductDimensionValueInventoryAvailability } from "@msdyn365-commerce/retail-proxy";
|
|
9
9
|
import Box from "@mui/material/Box";
|
|
10
|
-
import { createContext, type PropsWithChildren } from "react";
|
|
10
|
+
import { createContext, type PropsWithChildren, useMemo } from "react";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @category Props
|
|
@@ -42,8 +42,10 @@ export const DimensionGroup = ({
|
|
|
42
42
|
dimensions,
|
|
43
43
|
children,
|
|
44
44
|
}: DimensionGroupProps) => {
|
|
45
|
+
const contextValue = useMemo(() => ({ dimensions }), [dimensions]);
|
|
46
|
+
|
|
45
47
|
return (
|
|
46
|
-
<DimensionsGroupContext.Provider value={
|
|
48
|
+
<DimensionsGroupContext.Provider value={contextValue}>
|
|
47
49
|
<Box mb={2}>{children}</Box>
|
|
48
50
|
</DimensionsGroupContext.Provider>
|
|
49
51
|
);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createContext,
|
|
6
6
|
type PropsWithChildren,
|
|
7
7
|
type ReactElement,
|
|
8
|
+
useMemo,
|
|
8
9
|
} from "react";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -69,12 +70,20 @@ export const Dimensions = ({
|
|
|
69
70
|
showDimensionError,
|
|
70
71
|
children,
|
|
71
72
|
}: DimensionsProps): ReactElement => {
|
|
72
|
-
const contextValue =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
const contextValue = useMemo(
|
|
74
|
+
() => ({
|
|
75
|
+
dimensionGroups,
|
|
76
|
+
selectDimensions,
|
|
77
|
+
handleChangeVariant,
|
|
78
|
+
showDimensionError,
|
|
79
|
+
}),
|
|
80
|
+
[
|
|
81
|
+
dimensionGroups,
|
|
82
|
+
selectDimensions,
|
|
83
|
+
handleChangeVariant,
|
|
84
|
+
showDimensionError,
|
|
85
|
+
],
|
|
86
|
+
);
|
|
78
87
|
|
|
79
88
|
return (
|
|
80
89
|
<DimensionsContext.Provider value={contextValue}>
|
|
@@ -9,7 +9,7 @@ import type { CartLine } from "@msdyn365-commerce/retail-proxy";
|
|
|
9
9
|
import Button from "@mui/material/Button";
|
|
10
10
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
11
11
|
import { useTranslations } from "next-intl";
|
|
12
|
-
import { createContext, type ReactNode, useContext } from "react";
|
|
12
|
+
import { createContext, type ReactNode, useContext, useMemo } from "react";
|
|
13
13
|
import { MdAdd as Add, MdRemove as Remove } from "react-icons/md";
|
|
14
14
|
// TODO: Encapsulate the addToCart api calls inside this component
|
|
15
15
|
// so no need to pass in props.
|
|
@@ -90,6 +90,18 @@ export function QuantityButtons({
|
|
|
90
90
|
const { handleAddToCart, handleCartAmountChange, loading, currentCartLine } =
|
|
91
91
|
useCart(productId);
|
|
92
92
|
|
|
93
|
+
const value = useMemo<QuantityContextType | null>(() => {
|
|
94
|
+
if (!currentCartLine?.Quantity) return null;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
loading,
|
|
98
|
+
quantity: currentCartLine.Quantity,
|
|
99
|
+
isOOS,
|
|
100
|
+
productCartLine: currentCartLine,
|
|
101
|
+
handleCartAmountChange,
|
|
102
|
+
};
|
|
103
|
+
}, [loading, currentCartLine, isOOS, handleCartAmountChange]);
|
|
104
|
+
|
|
93
105
|
if (!currentCartLine || currentCartLine?.Quantity === 0) {
|
|
94
106
|
return (
|
|
95
107
|
<Button
|
|
@@ -105,15 +117,7 @@ export function QuantityButtons({
|
|
|
105
117
|
);
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
if (!
|
|
109
|
-
|
|
110
|
-
const value: QuantityContextType = {
|
|
111
|
-
loading,
|
|
112
|
-
quantity: currentCartLine?.Quantity,
|
|
113
|
-
isOOS,
|
|
114
|
-
productCartLine: currentCartLine,
|
|
115
|
-
handleCartAmountChange,
|
|
116
|
-
};
|
|
120
|
+
if (!value) return null;
|
|
117
121
|
|
|
118
122
|
return (
|
|
119
123
|
<QuantityContext.Provider value={value}>
|
|
@@ -126,7 +130,7 @@ export function QuantityButtons({
|
|
|
126
130
|
* The remove/decrementor button that appears when the quantity is not 0
|
|
127
131
|
* @category Sub-Component
|
|
128
132
|
*/
|
|
129
|
-
|
|
133
|
+
const RemoveButton = () => {
|
|
130
134
|
const { handleCartAmountChange, loading, productCartLine } = useQuantity();
|
|
131
135
|
|
|
132
136
|
return (
|
|
@@ -152,7 +156,7 @@ QuantityButtons.Remove = () => {
|
|
|
152
156
|
* The add/incrementor button that appears when the quantity is not 0
|
|
153
157
|
* @category Sub-Component
|
|
154
158
|
*/
|
|
155
|
-
|
|
159
|
+
const AddButton = () => {
|
|
156
160
|
const { handleCartAmountChange, loading, productCartLine } = useQuantity();
|
|
157
161
|
|
|
158
162
|
return (
|
|
@@ -178,7 +182,7 @@ QuantityButtons.Add = () => {
|
|
|
178
182
|
* The display component that shows the current quantity.
|
|
179
183
|
* @category Sub-Component
|
|
180
184
|
*/
|
|
181
|
-
|
|
185
|
+
const Quantity = () => {
|
|
182
186
|
const { loading, productCartLine } = useQuantity();
|
|
183
187
|
return (
|
|
184
188
|
<H3
|
|
@@ -194,3 +198,7 @@ QuantityButtons.Quantity = () => {
|
|
|
194
198
|
</H3>
|
|
195
199
|
);
|
|
196
200
|
};
|
|
201
|
+
|
|
202
|
+
QuantityButtons.Remove = RemoveButton;
|
|
203
|
+
QuantityButtons.Add = AddButton;
|
|
204
|
+
QuantityButtons.Quantity = Quantity;
|
|
@@ -16,7 +16,7 @@ import type { ProductsViewProps } from "@evenicanpm/storefront-core/src/componen
|
|
|
16
16
|
import type { ProductSearchResult as Product } from "@msdyn365-commerce/retail-proxy";
|
|
17
17
|
import Grid from "@mui/material/Grid2";
|
|
18
18
|
import type React from "react";
|
|
19
|
-
import { cloneElement } from "react";
|
|
19
|
+
import { cloneElement, useMemo } from "react";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Root component of products grid view.
|
|
@@ -32,9 +32,13 @@ import { cloneElement } from "react";
|
|
|
32
32
|
*/
|
|
33
33
|
export default function ProductsGridView(props: Readonly<ProductsViewProps>) {
|
|
34
34
|
const { products, productPrices, pagination, children } = props;
|
|
35
|
+
const contextValue = useMemo(
|
|
36
|
+
() => ({ products, productPrices }),
|
|
37
|
+
[products, productPrices],
|
|
38
|
+
);
|
|
35
39
|
|
|
36
40
|
return (
|
|
37
|
-
<ProductsViewContext.Provider value={
|
|
41
|
+
<ProductsViewContext.Provider value={contextValue}>
|
|
38
42
|
<Grid container spacing={3}>
|
|
39
43
|
{children}
|
|
40
44
|
</Grid>
|
|
@@ -52,7 +56,7 @@ export default function ProductsGridView(props: Readonly<ProductsViewProps>) {
|
|
|
52
56
|
* @category Sub-Component
|
|
53
57
|
*
|
|
54
58
|
*/
|
|
55
|
-
|
|
59
|
+
const ProductsGrid = ({
|
|
56
60
|
children,
|
|
57
61
|
}: {
|
|
58
62
|
children: React.ReactElement<Record<string, unknown>>;
|
|
@@ -93,7 +97,7 @@ export interface ProductCardProps {
|
|
|
93
97
|
* @param props.price The product price data.
|
|
94
98
|
* @category Sub-Component
|
|
95
99
|
*/
|
|
96
|
-
|
|
100
|
+
const ProductsGridCard = ({ product, price }: ProductCardProps) => {
|
|
97
101
|
if (!product) return null;
|
|
98
102
|
return (
|
|
99
103
|
<Grid size={{ lg: 4, sm: 6, xs: 12 }} key={product.ItemId}>
|
|
@@ -130,3 +134,6 @@ ProductsGridView.Card = ({ product, price }: ProductCardProps) => {
|
|
|
130
134
|
</Grid>
|
|
131
135
|
);
|
|
132
136
|
};
|
|
137
|
+
|
|
138
|
+
ProductsGridView.Grid = ProductsGrid;
|
|
139
|
+
ProductsGridView.Card = ProductsGridCard;
|