@evenicanpm/storefront-core 2.4.2 → 2.5.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 (142) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +90 -90
  3. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +4 -3
  4. package/src/api-manager/datasources/d365/d365-user.datasource.ts +3 -3
  5. package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +1 -0
  6. package/src/api-manager/datasources/e4/address/e4-address.datasource.ts +5 -14
  7. package/src/api-manager/datasources/e4/address/e4-address.translator.ts +4 -3
  8. package/src/api-manager/datasources/e4/categories/e4-categories.translator.ts +1 -1
  9. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +29 -3
  10. package/src/api-manager/datasources/e4/product/e4-product.translator.ts +1 -1
  11. package/src/api-manager/index.ts +1 -1
  12. package/src/api-manager/lib/get-graphql-client.ts +1 -1
  13. package/src/api-manager/services/cart/mutations/add-discount-code.ts +0 -11
  14. package/src/api-manager/services/create-query.ts +13 -7
  15. package/src/api-manager/services/user/mutations/add-to-wishlist.ts +1 -1
  16. package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +1 -1
  17. package/src/auth/AUTH GUIDE.md +880 -0
  18. package/src/cms/blocks/components/featured-categories.tsx +18 -15
  19. package/src/cms/blocks/components/featured-products.tsx +3 -2
  20. package/src/cms/blocks/components/footer/data/index.ts +0 -0
  21. package/src/cms/blocks/components/footer/index.tsx +0 -0
  22. package/src/cms/blocks/components/footer/sections/footer-app-store.tsx +0 -0
  23. package/src/cms/blocks/components/footer/sections/footer-contact.tsx +0 -2
  24. package/src/cms/blocks/components/footer/sections/footer-links.tsx +0 -0
  25. package/src/cms/blocks/components/footer/sections/footer-logo.tsx +2 -1
  26. package/src/cms/blocks/components/footer/sections/footer-social-links.tsx +0 -0
  27. package/src/cms/blocks/components/footer/styles/index.ts +0 -0
  28. package/src/cms/blocks/components/hero-carousel/index.tsx +0 -0
  29. package/src/cms/blocks/components/product-carousel/index.tsx +0 -0
  30. package/src/cms/blocks/components/shared/featured-product-card.tsx +0 -0
  31. package/src/cms/blocks/components/shared/product-category-item.tsx +0 -0
  32. package/src/cms/blocks/components/shared/product-grid.tsx +0 -0
  33. package/src/cms/blocks/components/shared/top-categories-card.tsx +0 -0
  34. package/src/cms/blocks/components/shared/top-rating-product-card.tsx +0 -0
  35. package/src/cms/blocks/icons/components/category.tsx +0 -0
  36. package/src/cms/blocks/icons/components/dotted-star.tsx +0 -0
  37. package/src/cms/blocks/icons/components/gift-box.tsx +0 -0
  38. package/src/cms/blocks/icons/components/light.tsx +0 -0
  39. package/src/cms/blocks/icons/components/new-arrival.tsx +0 -0
  40. package/src/cms/blocks/icons/components/rank-badge.tsx +0 -0
  41. package/src/components/SearchInput.stories.tsx +12 -10
  42. package/src/components/base-layout.tsx +42 -0
  43. package/src/components/categories/category-list/category-list.tsx +32 -12
  44. package/src/components/header/__tests__/header.test.tsx +0 -24
  45. package/src/components/header/components/mobile-header.tsx +16 -8
  46. package/src/components/header/header.tsx +6 -3
  47. package/src/components/mini-cart/components/bottom-actions.tsx +7 -2
  48. package/src/components/mini-cart/components/cart-item.tsx +16 -13
  49. package/src/components/mini-cart/components/empty-view.tsx +12 -6
  50. package/src/components/mini-cart/components/top-header.tsx +9 -8
  51. package/src/components/mini-cart/mini-cart-trigger.tsx +11 -5
  52. package/src/components/mini-cart/mini-cart.tsx +30 -15
  53. package/src/components/mobile-navigation/mobile-navigation-bar.tsx +6 -2
  54. package/src/components/navbar/categories.tsx +8 -2
  55. package/src/components/navbar/category-based-menu/category-based-menu.tsx +6 -2
  56. package/src/components/navbar/mega-menu/mega-menu.tsx +22 -14
  57. package/src/components/navbar/mobile-menu/mobile-menu.test.tsx +8 -11
  58. package/src/components/navbar/mobile-menu/mobile-menu.tsx +13 -11
  59. package/src/components/navbar/nav-list/nav-list.tsx +3 -2
  60. package/src/components/navbar/navbar.tsx +4 -2
  61. package/src/components/product-cards/product-card/product-card.tsx +50 -26
  62. package/src/components/product-cards/product-card-plp/product-card.tsx +37 -16
  63. package/src/components/product-cards/product-card-plp-list/product-card.tsx +38 -16
  64. package/src/components/product-cards/product-card-search/product-card.tsx +34 -13
  65. package/src/components/product-dialog/ProductDialog.stories.tsx +20 -18
  66. package/src/components/product-dialog/compound/product-dialog.tsx +7 -4
  67. package/src/components/product-dimensions/compound/dimensions-group.tsx +4 -2
  68. package/src/components/product-dimensions/compound/dimensions.tsx +15 -6
  69. package/src/components/product-quantity-buttons/compound/quantity-buttons-root.tsx +21 -13
  70. package/src/components/products-view/compound/products-grid-view.tsx +11 -4
  71. package/src/components/products-view/compound/products-list-view.tsx +11 -4
  72. package/src/components/search-bar/compound/search-bar-root.tsx +6 -4
  73. package/src/components/search-bar/compound/textfield-adornment-category.tsx +25 -8
  74. package/src/components/search-bar/hooks/use-click-outside.ts +7 -1
  75. package/src/components/search-bar/hooks/use-search.ts +3 -3
  76. package/src/components/user/index.ts +3 -0
  77. package/src/components/user/use-user-auth.ts +163 -0
  78. package/src/components/user/user.tsx +215 -0
  79. package/src/components/wishlist-dialogs/add-to-wishlist/AddToWishlist.stories.tsx +20 -15
  80. package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +11 -8
  81. package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-list.tsx +18 -3
  82. package/src/components/wishlist-dialogs/add-to-wishlist/index.tsx +0 -1
  83. package/src/components/wishlist-dialogs/create-wishlist/compound/create-wishlist-dialog.tsx +6 -2
  84. package/src/hooks/use-overflow-detect.ts +4 -4
  85. package/src/lib/graphqlRequestSdk.ts +0 -0
  86. package/src/lib/page-slugs.ts +16 -0
  87. package/src/pages/account/account-navigation.tsx +7 -14
  88. package/src/pages/account/account-routes.ts +4 -9
  89. package/src/pages/account/addresses/address-form.tsx +38 -16
  90. package/src/pages/account/addresses/address-item.tsx +18 -8
  91. package/src/pages/account/dashboard-header.tsx +20 -13
  92. package/src/pages/account/orders/order-history-filters-panel.tsx +21 -20
  93. package/src/pages/account/orders/order-history-header.tsx +14 -4
  94. package/src/pages/account/orders/order-history-root.tsx +3 -1
  95. package/src/pages/account/orders/order-history-row.tsx +12 -4
  96. package/src/pages/account/orders/order-history-search-and-filter.tsx +88 -59
  97. package/src/pages/account/orders/order-history-search-dropdown.tsx +26 -9
  98. package/src/pages/account/orders/order-history-sort.tsx +3 -1
  99. package/src/pages/account/orders/order-history-table.tsx +27 -5
  100. package/src/pages/account/orders/order-progress.tsx +12 -6
  101. package/src/pages/account/orders/order-row.tsx +18 -6
  102. package/src/pages/account/orders/order-summary.tsx +24 -8
  103. package/src/pages/account/orders/ordered-products.tsx +15 -5
  104. package/src/pages/account/profile/user-info.tsx +11 -3
  105. package/src/pages/account/table-row-skeleton.tsx +1 -1
  106. package/src/pages/account/wishlist/create-new-list.tsx +33 -9
  107. package/src/pages/account/wishlist/wishlist-item.tsx +18 -6
  108. package/src/pages/cart/cart-item.tsx +21 -7
  109. package/src/pages/cart/checkout-form.tsx +55 -22
  110. package/src/pages/cart/coupon-entry.tsx +143 -36
  111. package/src/pages/cart/estimate-shipping.tsx +45 -21
  112. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +22 -14
  113. package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +37 -16
  114. package/src/pages/checkout/checkout-alt-form/steps/address/address-card.tsx +9 -3
  115. package/src/pages/checkout/checkout-alt-form/steps/address/delivery-address.tsx +84 -51
  116. package/src/pages/checkout/checkout-alt-form/steps/address/new-address-form.tsx +41 -19
  117. package/src/pages/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +17 -10
  118. package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +15 -6
  119. package/src/pages/checkout/checkout-alt-form/steps/shipping/delivery-options.tsx +6 -2
  120. package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +12 -4
  121. package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +12 -5
  122. package/src/pages/checkout/checkout-alt-summary/list-item.tsx +17 -10
  123. package/src/pages/confirmation/address.tsx +6 -2
  124. package/src/pages/confirmation/confirmation-summary.tsx +12 -4
  125. package/src/pages/confirmation/ordered-products.tsx +12 -4
  126. package/src/pages/product-details/bopis/store-card/shop-card.tsx +2 -2
  127. package/src/pages/product-details/bopis/store-card/utils/getDaysResources.ts +1 -1
  128. package/src/pages/product-details/product-intro/compound/product-info.tsx +41 -18
  129. package/src/pages/product-details/product-intro/compound/product-intro-images.tsx +18 -12
  130. package/src/pages/product-details/product-intro/compound/product-intro.tsx +20 -10
  131. package/src/pages/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +1 -1
  132. package/src/pages/product-details/product-tabs.tsx +20 -12
  133. package/src/pages/product-list/product-list-view.tsx +4 -2
  134. package/src/pages/product-list/search-bar.tsx +7 -7
  135. package/src/pages/product-list/side-nav.tsx +5 -3
  136. package/src/pages/quickorder/order-upload.tsx +1 -1
  137. package/src/pages/quickorder/provider.tsx +26 -14
  138. package/src/pages/quickorder/quick-order.tsx +49 -22
  139. package/src/providers/nav-provider/index.tsx +16 -11
  140. package/tsconfig.json +0 -5
  141. package/src/components/header/components/user.tsx +0 -322
  142. /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 { createContext, type ReactNode, useContext, useState } from "react";
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
- ProductCardPlp.ImageWrapper = ({ children }: { children: React.ReactNode }) => {
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
- ProductCardPlp.Image = () => {
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
- ProductCardPlp.Body = ({ children }: { children: React.ReactNode }) => {
123
+ const PlpBody = ({ children }: { children: React.ReactNode }) => {
116
124
  return <FlexBetween alignItems="flex-end">{children}</FlexBetween>;
117
125
  };
118
126
 
119
- ProductCardPlp.Info = ({ children }: { children: React.ReactNode }) => {
127
+ const PlpInfo = ({ children }: { children: React.ReactNode }) => {
120
128
  return <div>{children}</div>;
121
129
  };
122
130
 
123
- ProductCardPlp.Actions = ({ children }: { children: React.ReactNode }) => {
131
+ const PlpActions = ({ children }: { children: React.ReactNode }) => {
124
132
  return <>{children}</>;
125
133
  };
126
134
 
127
- ProductCardPlp.Title = () => {
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
- ProductCardPlp.Rating = () => {
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
- ProductCardPlp.Price = () => {
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
- ProductCardPlp.HoverActions = ({ children }: { children: React.ReactNode }) => {
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
- ProductCardPlp.QuickViewButton = () => {
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
- ProductCardPlp.FavoriteButton = () => {
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
- ProductCardPlp.QuantityButtons = () => {
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 { createContext, type ReactNode, useContext, useState } from "react";
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
- ProductCardPlpList.QuickViewButton = () => {
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
- ProductCardPlpList.Body = ({ children }: { children: React.ReactNode }) => {
134
+ const Body = ({ children }: { children: React.ReactNode }) => {
126
135
  return <ContentWrapper>{children}</ContentWrapper>;
127
136
  };
128
137
 
129
- ProductCardPlpList.Image = () => {
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
- ProductCardPlpList.Content = ({ children }: { children: React.ReactNode }) => {
158
+ const Content = ({ children }: { children: React.ReactNode }) => {
150
159
  return <div className="content">{children}</div>;
151
160
  };
152
161
 
153
- ProductCardPlpList.Info = ({ children }: { children: React.ReactNode }) => {
162
+ const Info = ({ children }: { children: React.ReactNode }) => {
154
163
  return <div>{children}</div>;
155
164
  };
156
165
 
157
- ProductCardPlpList.Actions = ({ children }: { children: React.ReactNode }) => {
166
+ const Actions = ({ children }: { children: React.ReactNode }) => {
158
167
  return <>{children}</>;
159
168
  };
160
169
 
161
- ProductCardPlpList.Tags = () => {
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
- ProductCardPlpList.Title = () => {
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
- ProductCardPlpList.Rating = () => {
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
- ProductCardPlpList.Price = () => {
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
- ProductCardPlpList.FavoriteButton = () => {
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
- ProductCardPlpList.QuantityButtons = () => {
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 { createContext, type ReactNode, useContext, useState } from "react";
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
- ProductCardSearch.CardMedia = ({ children }: { children: React.ReactNode }) => {
93
+ const SearchCardMedia = ({ children }: { children: React.ReactNode }) => {
85
94
  return <CardMedia>{children}</CardMedia>;
86
95
  };
87
- ProductCardSearch.Image = () => {
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
- ProductCardSearch.Info = ({ children }: { children: React.ReactNode }) => {
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
- ProductCardSearch.Category = () => {
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
- ProductCardSearch.Title = () => {
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
- ProductCardSearch.Price = () => {
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
- ProductCardSearch.Rating = () => {
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
- ProductCardSearch.QuickViewButton = () => {
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
- ProductCardSearch.FavoriteButton = () => {
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
- export const WithTrigger: Story = {
17
- render: (args) => {
18
- const [, setArgs] = useArgs();
17
+ const WithTriggerRender = (
18
+ args: React.ComponentProps<typeof ProductDialog>,
19
+ ) => {
20
+ const [, setArgs] = useArgs();
19
21
 
20
- return (
21
- <>
22
- <Button
23
- variant="contained"
24
- onClick={() => setArgs({ openDialog: true })}
25
- >
26
- Open Product Dialog
27
- </Button>
28
- <ProductDialog
29
- {...args}
30
- handleCloseDialog={() => setArgs({ openDialog: false })}
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={{ dimensions }}>
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
- dimensionGroups,
74
- selectDimensions,
75
- handleChangeVariant,
76
- showDimensionError,
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 (!currentCartLine?.Quantity) return null;
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
- QuantityButtons.Remove = () => {
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
- QuantityButtons.Add = () => {
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
- QuantityButtons.Quantity = () => {
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={{ products, productPrices }}>
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
- ProductsGridView.Grid = ({
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
- ProductsGridView.Card = ({ product, price }: ProductCardProps) => {
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;