@evenicanpm/storefront-core 2.4.2 → 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.
Files changed (142) hide show
  1. package/CHANGELOG.md +7 -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
@@ -4,7 +4,7 @@ import type { Cart } from "@evenicanpm/storefront-core/src/api-manager/schemas/c
4
4
  import { MiniCart } from "@evenicanpm/storefront-core/src/components/mini-cart/index";
5
5
  import { useNotification } from "@evenicanpm/storefront-core/src/providers/notifications/use-notification";
6
6
  import { Badge, Drawer, IconButton } from "@mui/material";
7
- import React, { createContext, useContext, useState } from "react";
7
+ import React, { createContext, useContext, useMemo, useState } from "react";
8
8
 
9
9
  const BADGE_STYLES = {
10
10
  "& .MuiBadge-badge": {
@@ -43,8 +43,10 @@ const MiniCartTrigger = ({ cart, children }: MiniCartTriggerProps) => {
43
43
  }
44
44
  }, [alert]);
45
45
 
46
+ const contextValue = useMemo(() => ({ cart, open, setOpen }), [cart, open]);
47
+
46
48
  return (
47
- <MiniCartTriggerContext.Provider value={{ cart, open, setOpen }}>
49
+ <MiniCartTriggerContext.Provider value={contextValue}>
48
50
  {children}
49
51
  </MiniCartTriggerContext.Provider>
50
52
  );
@@ -52,7 +54,7 @@ const MiniCartTrigger = ({ cart, children }: MiniCartTriggerProps) => {
52
54
 
53
55
  /* ---------- Subcomponents ---------- */
54
56
 
55
- MiniCartTrigger.Badge = ({ children }: { children: React.ReactNode }) => {
57
+ const TriggerBadge = ({ children }: { children: React.ReactNode }) => {
56
58
  const ctx = useContext(MiniCartTriggerContext);
57
59
  if (!ctx) return null;
58
60
  const { cart } = ctx;
@@ -67,7 +69,7 @@ MiniCartTrigger.Badge = ({ children }: { children: React.ReactNode }) => {
67
69
  );
68
70
  };
69
71
 
70
- MiniCartTrigger.Button = ({ children }: { children: React.ReactNode }) => {
72
+ const TriggerButton = ({ children }: { children: React.ReactNode }) => {
71
73
  const ctx = useContext(MiniCartTriggerContext);
72
74
  if (!ctx) return null;
73
75
  const { setOpen } = ctx;
@@ -78,7 +80,7 @@ MiniCartTrigger.Button = ({ children }: { children: React.ReactNode }) => {
78
80
  );
79
81
  };
80
82
 
81
- MiniCartTrigger.Drawer = () => {
83
+ const TriggerDrawer = () => {
82
84
  const ctx = useContext(MiniCartTriggerContext);
83
85
  if (!ctx) return null;
84
86
  const { cart, open, setOpen } = ctx;
@@ -94,4 +96,8 @@ MiniCartTrigger.Drawer = () => {
94
96
  );
95
97
  };
96
98
 
99
+ MiniCartTrigger.Badge = TriggerBadge;
100
+ MiniCartTrigger.Button = TriggerButton;
101
+ MiniCartTrigger.Drawer = TriggerDrawer;
102
+
97
103
  export default MiniCartTrigger;
@@ -12,7 +12,7 @@ import Divider from "@mui/material/Divider";
12
12
  import { useRouter } from "next/navigation";
13
13
  import { useFormatter } from "next-intl";
14
14
  import type React from "react";
15
- import { createContext, useContext } from "react";
15
+ import { createContext, useContext, useMemo } from "react";
16
16
  import type { Cart, CartLine } from "@/schemas/cart.schema";
17
17
 
18
18
  interface MiniCartContextValue {
@@ -74,17 +74,27 @@ const MiniCart = ({ cart, closeDrawer, children }: MiniCartProps) => {
74
74
  push(path);
75
75
  };
76
76
 
77
+ const contextValue = useMemo(
78
+ () => ({
79
+ cart,
80
+ closeDrawer,
81
+ currency,
82
+ handleCartAmountChange,
83
+ shouldShowLoadingSpinner,
84
+ handleNavigate,
85
+ }),
86
+ [
87
+ cart,
88
+ closeDrawer,
89
+ currency,
90
+ handleCartAmountChange,
91
+ shouldShowLoadingSpinner,
92
+ handleNavigate,
93
+ ],
94
+ );
95
+
77
96
  return (
78
- <MiniCartContext.Provider
79
- value={{
80
- cart,
81
- closeDrawer,
82
- currency,
83
- handleCartAmountChange,
84
- shouldShowLoadingSpinner,
85
- handleNavigate,
86
- }}
87
- >
97
+ <MiniCartContext.Provider value={contextValue}>
88
98
  <Box width="100%" minWidth={380}>
89
99
  {children}
90
100
  </Box>
@@ -94,7 +104,7 @@ const MiniCart = ({ cart, closeDrawer, children }: MiniCartProps) => {
94
104
 
95
105
  /* ---------- Subcomponents ---------- */
96
106
 
97
- MiniCart.Header = () => {
107
+ const Header = () => {
98
108
  const ctx = useContext(MiniCartContext);
99
109
  if (!ctx) return null;
100
110
  const { cart, closeDrawer } = ctx;
@@ -106,11 +116,11 @@ MiniCart.Header = () => {
106
116
  );
107
117
  };
108
118
 
109
- MiniCart.Divider = () => {
119
+ const MiniCartDivider = () => {
110
120
  return <Divider />;
111
121
  };
112
122
 
113
- MiniCart.Items = () => {
123
+ const Items = () => {
114
124
  const ctx = useContext(MiniCartContext);
115
125
  if (!ctx) return null;
116
126
  const {
@@ -149,7 +159,7 @@ MiniCart.Items = () => {
149
159
  );
150
160
  };
151
161
 
152
- MiniCart.Actions = () => {
162
+ const Actions = () => {
153
163
  const ctx = useContext(MiniCartContext);
154
164
  if (!ctx) return null;
155
165
  const { cart, currency, handleNavigate } = ctx;
@@ -166,4 +176,9 @@ MiniCart.Actions = () => {
166
176
  ) : null;
167
177
  };
168
178
 
179
+ MiniCart.Header = Header;
180
+ MiniCart.Divider = MiniCartDivider;
181
+ MiniCart.Items = Items;
182
+ MiniCart.Actions = Actions;
183
+
169
184
  export default MiniCart;
@@ -21,7 +21,7 @@ import Box from "@mui/material/Box";
21
21
  import type { Theme } from "@mui/material/styles";
22
22
  import useMediaQuery from "@mui/material/useMediaQuery";
23
23
  import { useTranslations } from "next-intl";
24
- import { createContext, Fragment, useContext } from "react";
24
+ import { createContext, Fragment, useContext, useMemo } from "react";
25
25
 
26
26
  /**
27
27
  Usage Examples:
@@ -73,11 +73,15 @@ export default function MobileNavigationBar({
73
73
  ];
74
74
 
75
75
  const listToRender = menuList || defaultMenuList;
76
+ const contextValue = useMemo(
77
+ () => ({ menuList: listToRender }),
78
+ [listToRender],
79
+ );
76
80
 
77
81
  if (!DOWN_900) return null;
78
82
 
79
83
  return (
80
- <MobileNavContext.Provider value={{ menuList: listToRender }}>
84
+ <MobileNavContext.Provider value={contextValue}>
81
85
  <Box position="relative" display="flex" flexDirection="column">
82
86
  <Wrapper>
83
87
  {children ??
@@ -6,7 +6,12 @@ import { CategoryMenuButton } from "@evenicanpm/storefront-core/src/components/n
6
6
 
7
7
  import { Paragraph } from "@evenicanpm/storefront-core/src/components/Typography";
8
8
  import { useTranslations } from "next-intl";
9
- import React, { createContext, type ReactNode, useContext } from "react";
9
+ import React, {
10
+ createContext,
11
+ type ReactNode,
12
+ useContext,
13
+ useMemo,
14
+ } from "react";
10
15
  import { MdChevronRight as ChevronRight } from "react-icons/md";
11
16
 
12
17
  type CategoriesContextValue = {
@@ -23,9 +28,10 @@ export default function Categories({
23
28
  const t = useTranslations("Navigation");
24
29
 
25
30
  const label = t("topLevelCategoryName");
31
+ const contextValue = useMemo(() => ({ label }), [label]);
26
32
 
27
33
  return (
28
- <CategoriesContext.Provider value={{ label }}>
34
+ <CategoriesContext.Provider value={contextValue}>
29
35
  <CategoryMenu
30
36
  render={(handler) =>
31
37
  React.isValidElement(children) ? (
@@ -23,7 +23,7 @@ import Avatar from "@mui/material/Avatar";
23
23
  import Box from "@mui/material/Box";
24
24
  import Link from "next/link";
25
25
  import type React from "react";
26
- import { createContext, useContext, useState } from "react";
26
+ import { createContext, useContext, useMemo, useState } from "react";
27
27
 
28
28
  type MenuContextValue = {
29
29
  openList: string;
@@ -48,9 +48,13 @@ type RootProps = {
48
48
 
49
49
  const CategoryBasedMenu = ({ title, menuList, children }: RootProps) => {
50
50
  const [openList, setOpenList] = useState(menuList[0]?.title || "");
51
+ const contextValue = useMemo(
52
+ () => ({ openList, setOpenList, menuList }),
53
+ [openList, menuList],
54
+ );
51
55
 
52
56
  return (
53
- <MenuContext.Provider value={{ openList, setOpenList, menuList }}>
57
+ <MenuContext.Provider value={contextValue}>
54
58
  <Wrapper>
55
59
  <CategoryBasedMenu.Trigger>{title}</CategoryBasedMenu.Trigger>
56
60
  <MenusContainer className="menu-list">
@@ -17,7 +17,7 @@ import Card from "@mui/material/Card";
17
17
  import Grid from "@mui/material/Grid2";
18
18
  import List from "@mui/material/List";
19
19
  import type React from "react";
20
- import { createContext, type RefObject, useContext } from "react";
20
+ import { createContext, type RefObject, useContext, useMemo } from "react";
21
21
 
22
22
  type MegaMenuContextType = {
23
23
  isLeftOverflowing: boolean;
@@ -51,16 +51,18 @@ const MegaMenu = ({
51
51
  }) => {
52
52
  const { elementRef, isLeftOverflowing, isRightOverflowing, checkOverflow } =
53
53
  useOverflowDetect();
54
+ const contextValue = useMemo(
55
+ () => ({
56
+ elementRef: elementRef as RefObject<HTMLLIElement>,
57
+ isLeftOverflowing,
58
+ isRightOverflowing,
59
+ checkOverflow,
60
+ }),
61
+ [elementRef, isLeftOverflowing, isRightOverflowing, checkOverflow],
62
+ );
54
63
 
55
64
  return (
56
- <MegaMenuContext.Provider
57
- value={{
58
- elementRef: elementRef as RefObject<HTMLLIElement>,
59
- isLeftOverflowing,
60
- isRightOverflowing,
61
- checkOverflow,
62
- }}
63
- >
65
+ <MegaMenuContext.Provider value={contextValue}>
64
66
  <Wrapper onMouseOver={checkOverflow}>
65
67
  {title && (
66
68
  <FlexRowCenter fontWeight={600} alignItems="flex-end" gap={0.3}>
@@ -74,7 +76,7 @@ const MegaMenu = ({
74
76
  );
75
77
  };
76
78
 
77
- MegaMenu.Trigger = ({ children }: { children?: React.ReactNode }) => {
79
+ const Trigger = ({ children }: { children?: React.ReactNode }) => {
78
80
  return (
79
81
  <FlexRowCenter fontWeight={600} alignItems="flex-end" gap={0.3}>
80
82
  {children}
@@ -83,7 +85,7 @@ MegaMenu.Trigger = ({ children }: { children?: React.ReactNode }) => {
83
85
  );
84
86
  };
85
87
 
86
- MegaMenu.Container = ({ children }: { children: React.ReactNode }) => {
88
+ const Container = ({ children }: { children: React.ReactNode }) => {
87
89
  const { elementRef, isLeftOverflowing, isRightOverflowing } = useMegaMenu();
88
90
  return (
89
91
  <MenusContainer
@@ -99,7 +101,7 @@ MegaMenu.Container = ({ children }: { children: React.ReactNode }) => {
99
101
  );
100
102
  };
101
103
 
102
- MegaMenu.Column = ({
104
+ const Column = ({
103
105
  children,
104
106
  count,
105
107
  }: {
@@ -116,7 +118,7 @@ MegaMenu.Column = ({
116
118
  );
117
119
  };
118
120
 
119
- MegaMenu.Category = ({
121
+ const Category = ({
120
122
  title,
121
123
  children,
122
124
  }: {
@@ -133,7 +135,7 @@ MegaMenu.Category = ({
133
135
  );
134
136
  };
135
137
 
136
- MegaMenu.Item = ({
138
+ const Item = ({
137
139
  href,
138
140
  children,
139
141
  }: {
@@ -147,4 +149,10 @@ MegaMenu.Item = ({
147
149
  );
148
150
  };
149
151
 
152
+ MegaMenu.Trigger = Trigger;
153
+ MegaMenu.Container = Container;
154
+ MegaMenu.Column = Column;
155
+ MegaMenu.Category = Category;
156
+ MegaMenu.Item = Item;
157
+
150
158
  export default MegaMenu;
@@ -3,7 +3,7 @@ import MobileMenu from "@evenicanpm/storefront-core/src/components/navbar/mobile
3
3
  import { transformCmsNav } from "@evenicanpm/storefront-core/src/components/navbar/utils/transform-nav";
4
4
  import { fireEvent, render, screen } from "@testing-library/react";
5
5
  import type React from "react";
6
- import { beforeEach, describe, it, vi } from "vitest";
6
+ import { beforeEach, describe, expect, it, vi } from "vitest";
7
7
 
8
8
  const useContext = vi.fn();
9
9
 
@@ -20,7 +20,7 @@ vi.mock("@evenicanpm/storefront-core/src/providers/nav-provider", () => {
20
20
  };
21
21
  });
22
22
  describe.skip("MobileMenu", () => {
23
- const cmsNav = transformCmsNav(mockCmsData as unknown as CmsNavItem[]);
23
+ const cmsNav = transformCmsNav(mockCmsData);
24
24
  const handleClose = vi.fn();
25
25
  beforeEach(() => {
26
26
  vi.clearAllMocks();
@@ -38,7 +38,7 @@ describe.skip("MobileMenu", () => {
38
38
  </MobileMenu.Drawer>,
39
39
  );
40
40
 
41
- expect(screen.getByText("My Page")).toBeInTheDocument();
41
+ expect(screen.getByText("My Page")).toBeTruthy();
42
42
  });
43
43
 
44
44
  it("should expand nested items when clicked", () => {
@@ -49,7 +49,7 @@ describe.skip("MobileMenu", () => {
49
49
  );
50
50
 
51
51
  fireEvent.click(screen.getByText("My Page"));
52
- expect(screen.getByText("NESTED")).toBeInTheDocument();
52
+ expect(screen.getByText("NESTED")).toBeTruthy();
53
53
  });
54
54
 
55
55
  it("should call onClose when leaf link is clicked", () => {
@@ -60,19 +60,17 @@ describe.skip("MobileMenu", () => {
60
60
  );
61
61
 
62
62
  const link = screen.getByText("NESTED");
63
- expect(link.closest("a")).toHaveAttribute("href", "https://google.com");
63
+ expect(link.closest("a")?.getAttribute("href")).toBe("https://google.com");
64
64
 
65
65
  fireEvent.click(link);
66
66
  expect(handleClose).toHaveBeenCalled();
67
67
  });
68
68
  });
69
69
 
70
- const mockCmsData = [
70
+ const mockCmsData: CmsNavItem[] = [
71
71
  {
72
- id: 3,
73
72
  title: "My Page",
74
73
  menuAttached: false,
75
- order: 1,
76
74
  path: "/pages/my-page",
77
75
  type: "INTERNAL",
78
76
  uiRouterKey: "my-page",
@@ -86,20 +84,19 @@ const mockCmsData = [
86
84
  updatedAt: "2025-01-20T17:27:19.608Z",
87
85
  publishedAt: "2025-01-20T17:27:19.603Z",
88
86
  __contentType: "api::page.page",
89
- navigationItemId: 3,
90
87
  __templateName: "Generic",
91
88
  },
92
89
  items: [
93
90
  {
94
- id: 4,
95
91
  title: "NESTED",
96
92
  menuAttached: false,
97
- order: 1,
98
93
  path: "https://google.com",
99
94
  type: "EXTERNAL",
100
95
  uiRouterKey: "nested",
101
96
  slug: "",
97
+ related: null,
102
98
  external: true,
99
+ items: [],
103
100
  },
104
101
  ],
105
102
  },
@@ -24,7 +24,7 @@ const MobileMenu = ({ children }: RootProps) => {
24
24
  return <>{children}</>;
25
25
  };
26
26
 
27
- MobileMenu.Trigger = () => {
27
+ const Trigger = () => {
28
28
  const { toggleMobileSideNav } = useContext(NavContext);
29
29
  return (
30
30
  <IconButton
@@ -36,7 +36,7 @@ MobileMenu.Trigger = () => {
36
36
  );
37
37
  };
38
38
 
39
- MobileMenu.Drawer = ({ children }: { children: React.ReactNode }) => {
39
+ const MobileMenuDrawer = ({ children }: { children: React.ReactNode }) => {
40
40
  const { isMobileSideNavOpen, toggleMobileSideNav } = useContext(NavContext);
41
41
 
42
42
  return (
@@ -71,12 +71,12 @@ MobileMenu.Drawer = ({ children }: { children: React.ReactNode }) => {
71
71
  );
72
72
  };
73
73
 
74
- MobileMenu.List = ({ data }: { data: TransformedNavItem[] }) => {
74
+ const MenuList = ({ data }: { data: TransformedNavItem[] }) => {
75
75
  const { toggleMobileSideNav } = useContext(NavContext);
76
76
  return (
77
77
  <>
78
78
  {data.map((item, idx) => (
79
- <MobileMenu.Item
79
+ <MenuItem
80
80
  key={item.title + idx}
81
81
  item={item}
82
82
  onClose={toggleMobileSideNav}
@@ -102,7 +102,7 @@ const ACCORDION_SUMMARY_STYLES = {
102
102
  };
103
103
 
104
104
  // Single Item (link or accordion)
105
- MobileMenu.Item = ({
105
+ const MenuItem = ({
106
106
  item,
107
107
  onClose,
108
108
  }: {
@@ -122,11 +122,7 @@ MobileMenu.Item = ({
122
122
  </AccordionSummary>
123
123
  <Box mx={2}>
124
124
  {items.map((sub, idx) => (
125
- <MobileMenu.Item
126
- key={sub.title + idx}
127
- item={sub}
128
- onClose={onClose}
129
- />
125
+ <MenuItem key={sub.title + idx} item={sub} onClose={onClose} />
130
126
  ))}
131
127
  </Box>
132
128
  </Accordion>
@@ -142,8 +138,14 @@ MobileMenu.Item = ({
142
138
  );
143
139
  };
144
140
 
145
- MobileMenu.Divider = () => {
141
+ const MenuDivider = () => {
146
142
  return <Divider sx={{ my: 2 }} />;
147
143
  };
148
144
 
145
+ MobileMenu.Trigger = Trigger;
146
+ MobileMenu.Drawer = MobileMenuDrawer;
147
+ MobileMenu.List = MenuList;
148
+ MobileMenu.Item = MenuItem;
149
+ MobileMenu.Divider = MenuDivider;
150
+
149
151
  export default MobileMenu;
@@ -18,7 +18,7 @@ import {
18
18
  import { NavContext } from "@evenicanpm/storefront-core/src/providers/nav-provider";
19
19
  import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
20
20
  import MenuItem from "@mui/material/MenuItem";
21
- import { createContext, type ReactNode, useContext } from "react";
21
+ import { createContext, type ReactNode, useContext, useMemo } from "react";
22
22
 
23
23
  type NavigationContextValue = {
24
24
  list: TransformedNavItem[];
@@ -118,9 +118,10 @@ export default function Navigation({
118
118
  children,
119
119
  }: Readonly<{ children?: ReactNode }>) {
120
120
  const cmsList = transformCmsNav(useContext(NavContext).cmsNav ?? []);
121
+ const contextValue = useMemo(() => ({ list: cmsList }), [cmsList]);
121
122
 
122
123
  return (
123
- <NavigationContext.Provider value={{ list: cmsList }}>
124
+ <NavigationContext.Provider value={contextValue}>
124
125
  <FlexBox gap={4}>{children ?? <Navigation.List />}</FlexBox>
125
126
  </NavigationContext.Provider>
126
127
  );
@@ -9,7 +9,7 @@ import {
9
9
  NavBarWrapper,
10
10
  } from "@evenicanpm/storefront-core/src/components/navbar/styles";
11
11
  import type React from "react";
12
- import { createContext, useContext } from "react";
12
+ import { createContext, useContext, useMemo } from "react";
13
13
 
14
14
  /**
15
15
  * Usage Examples:
@@ -57,8 +57,10 @@ export default function Navbar({
57
57
  hideCategories = false,
58
58
  children,
59
59
  }: Readonly<Props>) {
60
+ const contextValue = useMemo(() => ({ hideCategories }), [hideCategories]);
61
+
60
62
  return (
61
- <NavbarContext.Provider value={{ hideCategories }}>
63
+ <NavbarContext.Provider value={contextValue}>
62
64
  <NavBarWrapper hoverEffect={false} elevation={elevation} border={border}>
63
65
  {children ?? (
64
66
  <Navbar.Content>
@@ -247,21 +247,35 @@ const ProductCard = ({
247
247
  }
248
248
  };
249
249
 
250
- const internalContextValue = {
251
- handleToggleFavorite,
252
- openProductModal,
253
- handleToggleProductModal,
254
- quantity,
255
- handleIncrementQuantity,
256
- handleDecrementQuantity,
257
- isLineUpdating,
258
- handleAddItemToCart,
259
- handleRemoveFromWishlist,
260
- };
250
+ const productContextValue = useMemo(() => ({ product }), [product]);
251
+ const memoizedInternalContextValue = useMemo(
252
+ () => ({
253
+ handleToggleFavorite,
254
+ openProductModal,
255
+ handleToggleProductModal,
256
+ quantity,
257
+ handleIncrementQuantity,
258
+ handleDecrementQuantity,
259
+ isLineUpdating,
260
+ handleAddItemToCart,
261
+ handleRemoveFromWishlist,
262
+ }),
263
+ [
264
+ handleToggleFavorite,
265
+ openProductModal,
266
+ handleToggleProductModal,
267
+ quantity,
268
+ handleIncrementQuantity,
269
+ handleDecrementQuantity,
270
+ isLineUpdating,
271
+ handleAddItemToCart,
272
+ handleRemoveFromWishlist,
273
+ ],
274
+ );
261
275
 
262
276
  return (
263
- <ProductCardContext.Provider value={{ product }}>
264
- <ProductCardInternalContext.Provider value={internalContextValue}>
277
+ <ProductCardContext.Provider value={productContextValue}>
278
+ <ProductCardInternalContext.Provider value={memoizedInternalContextValue}>
265
279
  <StyledBazaarCard hoverEffect={product.hoverEffect}>
266
280
  {children}
267
281
  </StyledBazaarCard>
@@ -285,7 +299,7 @@ const ProductCard = ({
285
299
  );
286
300
  };
287
301
 
288
- ProductCard.Image = () => {
302
+ const CardImage = () => {
289
303
  const ctx = useContext(ProductCardContext);
290
304
  if (!ctx) return null;
291
305
 
@@ -301,11 +315,11 @@ ProductCard.Image = () => {
301
315
  );
302
316
  };
303
317
 
304
- ProductCard.Body = ({ children }: { children: React.ReactNode }) => {
318
+ const CardBody = ({ children }: { children: React.ReactNode }) => {
305
319
  return <ContentWrapper>{children}</ContentWrapper>;
306
320
  };
307
321
 
308
- ProductCard.Info = ({ children }: { children: React.ReactNode }) => {
322
+ const CardInfo = ({ children }: { children: React.ReactNode }) => {
309
323
  return (
310
324
  <Box flex="1 1 0" minWidth="0px" mr={1}>
311
325
  {children}
@@ -313,11 +327,11 @@ ProductCard.Info = ({ children }: { children: React.ReactNode }) => {
313
327
  );
314
328
  };
315
329
 
316
- ProductCard.Actions = ({ children }: { children: React.ReactNode }) => {
330
+ const CardActions = ({ children }: { children: React.ReactNode }) => {
317
331
  return <>{children}</>;
318
332
  };
319
333
 
320
- ProductCard.Title = () => {
334
+ const CardTitle = () => {
321
335
  const ctx = useContext(ProductCardContext);
322
336
  if (!ctx) return null;
323
337
 
@@ -325,7 +339,7 @@ ProductCard.Title = () => {
325
339
  return <ProductTitle title={title} slug={slug} />;
326
340
  };
327
341
 
328
- ProductCard.Rating = () => {
342
+ const CardRating = () => {
329
343
  const ctx = useContext(ProductCardContext);
330
344
  if (!ctx) return null;
331
345
 
@@ -334,7 +348,7 @@ ProductCard.Rating = () => {
334
348
  return <Rating size="small" value={rating} color="warn" readOnly />;
335
349
  };
336
350
 
337
- ProductCard.Size = () => {
351
+ const CardSize = () => {
338
352
  const ctx = useContext(ProductCardContext);
339
353
  if (!ctx) return null;
340
354
 
@@ -347,7 +361,7 @@ ProductCard.Size = () => {
347
361
  );
348
362
  };
349
363
 
350
- ProductCard.Price = () => {
364
+ const CardPrice = () => {
351
365
  const ctx = useContext(ProductCardContext);
352
366
  if (!ctx) return null;
353
367
 
@@ -377,15 +391,13 @@ const QuantityButtons = () => {
377
391
  );
378
392
  };
379
393
 
380
- ProductCard.QuantityButtons = QuantityButtons;
381
-
382
- ProductCard.HoverActions = ({ children }: { children: React.ReactNode }) => {
394
+ const CardHoverActions = ({ children }: { children: React.ReactNode }) => {
383
395
  return (
384
396
  <HoverIconWrapper className="hover-box"> {children} </HoverIconWrapper>
385
397
  );
386
398
  };
387
399
 
388
- ProductCard.QuickViewButton = () => {
400
+ const CardQuickViewButton = () => {
389
401
  const internal = useContext(ProductCardInternalContext);
390
402
  if (!internal) return null;
391
403
 
@@ -398,7 +410,7 @@ ProductCard.QuickViewButton = () => {
398
410
  );
399
411
  };
400
412
 
401
- ProductCard.FavoriteButton = () => {
413
+ const CardFavoriteButton = () => {
402
414
  const internal = useContext(ProductCardInternalContext);
403
415
  const ctx = useContext(ProductCardContext);
404
416
 
@@ -450,6 +462,18 @@ const WishlistControls = () => {
450
462
  );
451
463
  };
452
464
 
465
+ ProductCard.Image = CardImage;
466
+ ProductCard.Body = CardBody;
467
+ ProductCard.Info = CardInfo;
468
+ ProductCard.Actions = CardActions;
469
+ ProductCard.Title = CardTitle;
470
+ ProductCard.Rating = CardRating;
471
+ ProductCard.Size = CardSize;
472
+ ProductCard.Price = CardPrice;
473
+ ProductCard.QuantityButtons = QuantityButtons;
474
+ ProductCard.HoverActions = CardHoverActions;
475
+ ProductCard.QuickViewButton = CardQuickViewButton;
476
+ ProductCard.FavoriteButton = CardFavoriteButton;
453
477
  ProductCard.WishlistControls = WishlistControls;
454
478
 
455
479
  export default ProductCard;