@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
@@ -19,21 +19,24 @@ const FeaturedCategories = (categoriesData: FeaturedCategoriesBlock) => {
19
19
  <Container>
20
20
  <h1 style={{ fontSize: "25px" }}>Best Selling Categories</h1>
21
21
  <Grid container spacing={2}>
22
- {categoriesData?.categories?.map((category: Category, i: number) => {
23
- return (
24
- <Grid
25
- size={3}
26
- display="flex"
27
- justifyContent="center"
28
- key={`category_index_${i}`}
29
- >
30
- <CategoryCard
31
- name={category.name}
32
- imgUrl={category?.image?.url}
33
- ></CategoryCard>
34
- </Grid>
35
- );
36
- })}
22
+ {categoriesData?.categories?.map(
23
+ (category: Category, index: number) => {
24
+ const categoryKey = `${category.name || "category-item"}-${index}`;
25
+ return (
26
+ <Grid
27
+ size={3}
28
+ display="flex"
29
+ justifyContent="center"
30
+ key={categoryKey}
31
+ >
32
+ <CategoryCard
33
+ name={category.name}
34
+ imgUrl={category?.image?.url}
35
+ ></CategoryCard>
36
+ </Grid>
37
+ );
38
+ },
39
+ )}
37
40
  </Grid>
38
41
  </Container>
39
42
  </Box>
@@ -16,13 +16,14 @@ const FeaturedProducts = (productsData: FeaturedProductsBlock) => {
16
16
  <Container>
17
17
  <h1 style={{ fontSize: "25px" }}>Featured Products</h1>
18
18
  <Grid container spacing={2}>
19
- {productsData?.products?.map((product: Product, i: number) => {
19
+ {productsData?.products?.map((product: Product, index: number) => {
20
+ const productKey = `${product?.description || "product-item"}-${index}`;
20
21
  return (
21
22
  <Grid
22
23
  size={3}
23
24
  display="flex"
24
25
  justifyContent="center"
25
- key={`product_index_${i}`}
26
+ key={productKey}
26
27
  >
27
28
  <MediaCard
28
29
  name={product?.description ?? ""}
File without changes
File without changes
@@ -5,8 +5,6 @@ import SocialLinks from "@/cms/blocks/components/footer/sections/footer-social-l
5
5
  import { Heading } from "@/cms/blocks/components/footer/styles/index";
6
6
 
7
7
  interface Props {
8
- isDark?: boolean;
9
- title?: string;
10
8
  address?: string;
11
9
  email?: string;
12
10
  phoneNumber?: string;
@@ -17,6 +17,7 @@ export default function LogoSection(props: Readonly<Props>) {
17
17
  const theme = useTheme();
18
18
  const downMd = useMediaQuery(theme.breakpoints.down(1150));
19
19
  const logoHeight = downMd ? "2.125rem" : "3rem";
20
+ const logoProps = downMd ? { height: "2.125rem" } : {};
20
21
 
21
22
  return (
22
23
  <Grid size={{ lg: 3, md: 6, sm: 6, xs: 12 }}>
@@ -29,7 +30,7 @@ export default function LogoSection(props: Readonly<Props>) {
29
30
  />
30
31
  </Box>
31
32
  ) : (
32
- <SiteLogo {...(downMd ? { height: "2.125rem" } : {})} />
33
+ <SiteLogo {...logoProps} />
33
34
  )}
34
35
 
35
36
  <Paragraph mb={2.5} color="grey.500">
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -11,19 +11,21 @@ const meta = {
11
11
  export default meta;
12
12
  type Story = StoryObj<typeof SearchInput>;
13
13
 
14
+ const BasicRender = (args: React.ComponentProps<typeof SearchInput>) => {
15
+ const [value, setValue] = useState("");
16
+ return (
17
+ <SearchInput
18
+ {...args}
19
+ value={value}
20
+ onChange={(e) => setValue(e.target.value)}
21
+ />
22
+ );
23
+ };
24
+
14
25
  export const Basic: Story = {
15
26
  args: {
16
27
  placeholder: "Search...",
17
28
  disabled: false,
18
29
  },
19
- render: (args) => {
20
- const [value, setValue] = useState("");
21
- return (
22
- <SearchInput
23
- {...args}
24
- value={value}
25
- onChange={(e) => setValue(e.target.value)}
26
- />
27
- );
28
- },
30
+ render: (args) => <BasicRender {...args} />,
29
31
  };
@@ -0,0 +1,42 @@
1
+ "use server";
2
+ import getCategories from "@evenicanpm/storefront-core/src/api-manager/services/categories/queries/get-categories";
3
+ import { getQueryClient } from "@evenicanpm/storefront-core/src/api-manager/services/get-query-client";
4
+ import { MobileNavigationBar } from "@evenicanpm/storefront-core/src/components/mobile-navigation";
5
+ import { Navbar } from "@evenicanpm/storefront-core/src/components/navbar";
6
+ import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
7
+ import type React from "react";
8
+
9
+ interface BaseLayoutProps {
10
+ children: React.ReactNode;
11
+ header?: React.ReactNode;
12
+ footer?: React.ReactNode;
13
+ showNavbar?: boolean;
14
+ }
15
+
16
+ export default async function BaseLayout({
17
+ children,
18
+ header,
19
+ footer,
20
+ showNavbar = true,
21
+ }: Readonly<BaseLayoutProps>) {
22
+ const queryClient = getQueryClient();
23
+ await getCategories.prefetchData(queryClient);
24
+
25
+ return (
26
+ <HydrationBoundary state={dehydrate(queryClient)}>
27
+ {(header || showNavbar) && (
28
+ <header>
29
+ {header}
30
+ {showNavbar && <Navbar border={1} />}
31
+ </header>
32
+ )}
33
+
34
+ <main>{children}</main>
35
+
36
+ {footer}
37
+
38
+ {/* Small screen bottom menu like in mobile apps */}
39
+ <MobileNavigationBar />
40
+ </HydrationBoundary>
41
+ );
42
+ }
@@ -9,7 +9,7 @@ import CategoryListItem from "@evenicanpm/storefront-core/src/components/categor
9
9
  import MegaMenu1 from "@evenicanpm/storefront-core/src/components/categories/mega-menu/mega-menu-1";
10
10
  import MegaMenu2 from "@evenicanpm/storefront-core/src/components/categories/mega-menu/mega-menu-2";
11
11
  import type { CategoryHierarchy } from "@evenicanpm/storefront-core/src/lib/category-helpers";
12
- import { createContext, type ReactNode, useContext } from "react";
12
+ import { createContext, type ReactNode, useContext, useMemo } from "react";
13
13
 
14
14
  type MegaMenuType = "default" | "mega-menu-1" | "mega-menu-2";
15
15
 
@@ -33,6 +33,27 @@ interface RootProps {
33
33
  children?: ReactNode;
34
34
  }
35
35
 
36
+ const defaultMegaMenuRenderer = (children: CategoryHierarchy[]) => (
37
+ <CategoryList.SubMenu items={children} />
38
+ );
39
+
40
+ const megaMenu1Renderer = (children: CategoryHierarchy[]) => (
41
+ <MegaMenu1 data={children} />
42
+ );
43
+
44
+ const megaMenu2Renderer = (children: CategoryHierarchy[]) => (
45
+ <MegaMenu2 data={children} />
46
+ );
47
+
48
+ const megaMenuRenderers: Record<
49
+ MegaMenuType,
50
+ (children: CategoryHierarchy[]) => ReactNode
51
+ > = {
52
+ default: defaultMegaMenuRenderer,
53
+ "mega-menu-1": megaMenu1Renderer,
54
+ "mega-menu-2": megaMenu2Renderer,
55
+ };
56
+
36
57
  const CategoryList = ({
37
58
  open,
38
59
  position = "absolute",
@@ -47,17 +68,13 @@ const CategoryList = ({
47
68
  cats = fetchedCategories;
48
69
  }
49
70
 
50
- const megaMenuRenderers: Record<
51
- MegaMenuType,
52
- (children: CategoryHierarchy[]) => ReactNode
53
- > = {
54
- default: (children) => <CategoryList.SubMenu items={children} />,
55
- "mega-menu-1": (children) => <MegaMenu1 data={children} />,
56
- "mega-menu-2": (children) => <MegaMenu2 data={children} />,
57
- };
71
+ const contextValue = useMemo(
72
+ () => ({ open, position, megaMenuRenderers }),
73
+ [open, position],
74
+ );
58
75
 
59
76
  return (
60
- <CategoryListContext.Provider value={{ open, position, megaMenuRenderers }}>
77
+ <CategoryListContext.Provider value={contextValue}>
61
78
  <StyledRoot open={open} position={position}>
62
79
  {children ??
63
80
  cats?.map((c) => <CategoryList.Item key={c.RecordId} category={c} />)}
@@ -69,7 +86,7 @@ const CategoryList = ({
69
86
  interface ItemProps {
70
87
  category: CategoryHierarchy;
71
88
  }
72
- CategoryList.Item = ({ category }: ItemProps) => {
89
+ const Item = ({ category }: ItemProps) => {
73
90
  const ctx = useContext(CategoryListContext);
74
91
  if (!ctx) return null;
75
92
  const { megaMenuRenderers } = ctx;
@@ -98,7 +115,7 @@ CategoryList.Item = ({ category }: ItemProps) => {
98
115
  interface SubMenuProps {
99
116
  items: CategoryHierarchy[];
100
117
  }
101
- CategoryList.SubMenu = ({ items }: SubMenuProps) => {
118
+ const SubMenu = ({ items }: SubMenuProps) => {
102
119
  return (
103
120
  <SubMenuStyledRoot elevation={2}>
104
121
  {items.map((c) => (
@@ -108,4 +125,7 @@ CategoryList.SubMenu = ({ items }: SubMenuProps) => {
108
125
  );
109
126
  };
110
127
 
128
+ CategoryList.Item = Item;
129
+ CategoryList.SubMenu = SubMenu;
130
+
111
131
  export default CategoryList;
@@ -1,5 +1,4 @@
1
1
  import { render, screen } from "@testing-library/react";
2
- import type { ReactNode } from "react";
3
2
  import { vi } from "vitest";
4
3
  import Header from "../header";
5
4
 
@@ -47,23 +46,6 @@ vi.mock(
47
46
  }),
48
47
  );
49
48
 
50
- vi.mock(
51
- "@evenicanpm/storefront-core/src/components/header/components/user",
52
- () => {
53
- const User = ({ children }: { children?: ReactNode }) => (
54
- <div data-testid="user-component">User: {children}</div>
55
- );
56
- User.DesktopTrigger = () => <div data-testid="user-trigger">Trigger</div>;
57
- User.Menu = () => <div data-testid="user-menu">Menu</div>;
58
- User.LoginButton = () => (
59
- <button type="button" data-testid="login-button">
60
- Login
61
- </button>
62
- );
63
- return { default: User };
64
- },
65
- );
66
-
67
49
  describe("header", () => {
68
50
  it("renders header wrapper with children", () => {
69
51
  render(
@@ -108,12 +90,6 @@ describe("header", () => {
108
90
  expect(screen.getByText("Mid Content")).toBeInTheDocument();
109
91
  });
110
92
 
111
- it("renders User subcomponent", () => {
112
- render(<Header.User />);
113
-
114
- expect(screen.getByTestId("user-component")).toBeInTheDocument();
115
- });
116
-
117
93
  it("renders Cart subcomponent", () => {
118
94
  render(<Header.Cart />);
119
95
 
@@ -4,7 +4,6 @@ import {
4
4
  FlexBetween,
5
5
  FlexBox,
6
6
  } from "@evenicanpm/storefront-core/src/components/flex-box/index";
7
- import User from "@evenicanpm/storefront-core/src/components/header/components/user";
8
7
  import useHeader from "@evenicanpm/storefront-core/src/components/header/hooks/use-header";
9
8
  import MobileCartBag from "@evenicanpm/storefront-core/src/components/icons/cart-bag";
10
9
  import Search from "@evenicanpm/storefront-core/src/components/icons/search";
@@ -18,6 +17,7 @@ import { SearchBarViewSm as SearchBar } from "@evenicanpm/storefront-core/src/co
18
17
  import SiteLogo from "@evenicanpm/storefront-core/src/components/site-logo";
19
18
  // CUSTOM COMPONENTS
20
19
  import { Paragraph } from "@evenicanpm/storefront-core/src/components/Typography";
20
+ import User from "@evenicanpm/storefront-core/src/components/user";
21
21
  import { NavContext } from "@evenicanpm/storefront-core/src/providers/nav-provider";
22
22
  import Box from "@mui/material/Box";
23
23
  import Drawer from "@mui/material/Drawer";
@@ -59,7 +59,7 @@ const MobileHeader = ({ children }: MobileHeaderRootProps) => {
59
59
  );
60
60
  };
61
61
 
62
- MobileHeader.Menu = () => {
62
+ const Menu = () => {
63
63
  const { categories, cmsNav } = useContext(NavContext);
64
64
  const categoriesNavItems = transformCategoryNav(categories ?? []);
65
65
  const cmsNavItems = transformCmsNav(cmsNav ?? []);
@@ -77,11 +77,11 @@ MobileHeader.Menu = () => {
77
77
  );
78
78
  };
79
79
 
80
- MobileHeader.Logo = () => {
80
+ const Logo = () => {
81
81
  return <SiteLogo height="2.125rem" />;
82
82
  };
83
83
 
84
- MobileHeader.Icons = ({ children }: { children: ReactNode }) => {
84
+ const Icons = ({ children }: { children: ReactNode }) => {
85
85
  return (
86
86
  <FlexBox justifyContent="end" flex={1}>
87
87
  {children}
@@ -89,7 +89,7 @@ MobileHeader.Icons = ({ children }: { children: ReactNode }) => {
89
89
  );
90
90
  };
91
91
 
92
- MobileHeader.User = () => {
92
+ const MobileUser = () => {
93
93
  return (
94
94
  <User>
95
95
  <User.MobileTrigger />
@@ -97,7 +97,7 @@ MobileHeader.User = () => {
97
97
  );
98
98
  };
99
99
 
100
- MobileHeader.Search = () => {
100
+ const MobileSearch = () => {
101
101
  const { toggleSearchBar } = useMobileHeader();
102
102
  return (
103
103
  <IconButton onClick={toggleSearchBar}>
@@ -106,7 +106,7 @@ MobileHeader.Search = () => {
106
106
  );
107
107
  };
108
108
 
109
- MobileHeader.Cart = () => {
109
+ const Cart = () => {
110
110
  const { data: cart } = getCart.useData();
111
111
  return cart ? (
112
112
  <MiniCartTrigger cart={cart}>
@@ -120,7 +120,7 @@ MobileHeader.Cart = () => {
120
120
  ) : null;
121
121
  };
122
122
 
123
- MobileHeader.SearchDrawer = () => {
123
+ const SearchDrawer = () => {
124
124
  const { searchBarOpen, toggleSearchBar } = useMobileHeader();
125
125
  const t = useTranslations("Search");
126
126
 
@@ -144,4 +144,12 @@ MobileHeader.SearchDrawer = () => {
144
144
  );
145
145
  };
146
146
 
147
+ MobileHeader.Menu = Menu;
148
+ MobileHeader.Logo = Logo;
149
+ MobileHeader.Icons = Icons;
150
+ MobileHeader.User = MobileUser;
151
+ MobileHeader.Search = MobileSearch;
152
+ MobileHeader.Cart = Cart;
153
+ MobileHeader.SearchDrawer = SearchDrawer;
154
+
147
155
  export default MobileHeader;
@@ -6,7 +6,6 @@ import getCart from "@evenicanpm/storefront-core/src/api-manager/services/cart/q
6
6
  import FlexBox from "@evenicanpm/storefront-core/src/components/flex-box/flex-box";
7
7
  // LOCAL CUSTOM COMPONENTS
8
8
  import CategoriesMenu from "@evenicanpm/storefront-core/src/components/header/components/categories-menu";
9
- import User from "@evenicanpm/storefront-core/src/components/header/components/user";
10
9
  // STYLED COMPONENTS
11
10
  import {
12
11
  HeaderWrapper,
@@ -14,6 +13,7 @@ import {
14
13
  } from "@evenicanpm/storefront-core/src/components/header/styles/index";
15
14
  import MiniCartTrigger from "@evenicanpm/storefront-core/src/components/mini-cart/mini-cart-trigger";
16
15
  import SiteLogo from "@evenicanpm/storefront-core/src/components/site-logo";
16
+ import User from "@evenicanpm/storefront-core/src/components/user";
17
17
  import { useAppSession } from "@evenicanpm/storefront-core/src/hooks/use-nextauth-session";
18
18
  import clsx from "clsx";
19
19
  import type { ReactNode } from "react";
@@ -51,7 +51,7 @@ Header.MidSlot = ({ children }: { children: ReactNode }) => {
51
51
  return <>{children}</>;
52
52
  };
53
53
 
54
- Header.User = () => {
54
+ const HeaderUser = () => {
55
55
  const { session } = useAppSession();
56
56
  const isAuthenticated = !!session;
57
57
  return (
@@ -68,7 +68,7 @@ Header.User = () => {
68
68
  );
69
69
  };
70
70
 
71
- Header.Cart = () => {
71
+ const HeaderCart = () => {
72
72
  const { data: cart } = getCart.useData();
73
73
  return cart ? (
74
74
  <MiniCartTrigger cart={cart}>
@@ -82,4 +82,7 @@ Header.Cart = () => {
82
82
  ) : null;
83
83
  };
84
84
 
85
+ Header.User = HeaderUser;
86
+ Header.Cart = HeaderCart;
87
+
85
88
  export default Header;
@@ -4,7 +4,7 @@ import Box from "@mui/material/Box";
4
4
  import Button, { type ButtonProps } from "@mui/material/Button";
5
5
  import { useTranslations } from "next-intl";
6
6
  import type React from "react";
7
- import { createContext, useContext } from "react";
7
+ import { createContext, useContext, useMemo } from "react";
8
8
 
9
9
  interface BottomActionsContextValue {
10
10
  total: string;
@@ -26,8 +26,13 @@ const BottomActions = ({
26
26
  handleNavigate,
27
27
  children,
28
28
  }: BottomActionsProps) => {
29
+ const contextValue = useMemo(
30
+ () => ({ total, handleNavigate }),
31
+ [total, handleNavigate],
32
+ );
33
+
29
34
  return (
30
- <BottomActionsContext.Provider value={{ total, handleNavigate }}>
35
+ <BottomActionsContext.Provider value={contextValue}>
31
36
  <Box p={2.5} display="flex" flexDirection="column" gap={1}>
32
37
  {children}
33
38
  </Box>
@@ -18,7 +18,7 @@ import IconButton from "@mui/material/IconButton";
18
18
  import Link from "next/link";
19
19
  import { useFormatter, useTranslations } from "next-intl";
20
20
  import type React from "react";
21
- import { createContext, useContext } from "react";
21
+ import { createContext, useContext, useMemo } from "react";
22
22
  import type { CartLine } from "@/schemas/cart.schema";
23
23
 
24
24
  interface MiniCartItemContextValue {
@@ -47,15 +47,13 @@ const MiniCartItem = ({
47
47
  closeDrawer,
48
48
  children,
49
49
  }: MiniCartItemProps) => {
50
+ const contextValue = useMemo(
51
+ () => ({ item, handleCartAmountChange, isLineUpdating, closeDrawer }),
52
+ [item, handleCartAmountChange, isLineUpdating, closeDrawer],
53
+ );
54
+
50
55
  return (
51
- <MiniCartItemContext.Provider
52
- value={{
53
- item,
54
- handleCartAmountChange,
55
- isLineUpdating,
56
- closeDrawer,
57
- }}
58
- >
56
+ <MiniCartItemContext.Provider value={contextValue}>
59
57
  <FlexBox
60
58
  py={2}
61
59
  px={2.5}
@@ -72,7 +70,7 @@ const MiniCartItem = ({
72
70
 
73
71
  /* ---------- Subcomponents ---------- */
74
72
 
75
- MiniCartItem.QuantityControls = () => {
73
+ const QuantityControls = () => {
76
74
  const ctx = useContext(MiniCartItemContext);
77
75
  const t = useTranslations("Cart.CartLine");
78
76
  if (!ctx) return null;
@@ -133,7 +131,7 @@ MiniCartItem.QuantityControls = () => {
133
131
  );
134
132
  };
135
133
 
136
- MiniCartItem.ProductImage = () => {
134
+ const ProductImage = () => {
137
135
  const ctx = useContext(MiniCartItemContext);
138
136
  if (!ctx) return null;
139
137
  const { item, closeDrawer } = ctx;
@@ -151,7 +149,7 @@ MiniCartItem.ProductImage = () => {
151
149
  );
152
150
  };
153
151
 
154
- MiniCartItem.ProductDetails = () => {
152
+ const ProductDetails = () => {
155
153
  const { number } = useFormatter();
156
154
  const currency = (value: number) =>
157
155
  number(value, { style: "currency", currency: "USD" });
@@ -186,7 +184,7 @@ MiniCartItem.ProductDetails = () => {
186
184
  );
187
185
  };
188
186
 
189
- MiniCartItem.RemoveButton = () => {
187
+ const RemoveButton = () => {
190
188
  const ctx = useContext(MiniCartItemContext);
191
189
  const t = useTranslations("Cart.CartLine");
192
190
  if (!ctx) return null;
@@ -203,4 +201,9 @@ MiniCartItem.RemoveButton = () => {
203
201
  );
204
202
  };
205
203
 
204
+ MiniCartItem.QuantityControls = QuantityControls;
205
+ MiniCartItem.ProductImage = ProductImage;
206
+ MiniCartItem.ProductDetails = ProductDetails;
207
+ MiniCartItem.RemoveButton = RemoveButton;
208
+
206
209
  export default MiniCartItem;
@@ -4,7 +4,7 @@ import { FlexBox } from "@evenicanpm/storefront-core/src/components/flex-box/ind
4
4
  import { Paragraph } from "@evenicanpm/storefront-core/src/components/Typography";
5
5
  import { useTranslations } from "next-intl";
6
6
  import type React from "react";
7
- import { createContext, useContext } from "react";
7
+ import { createContext, useContext, useMemo } from "react";
8
8
  import { MdShoppingBag as ShoppingBagIcon } from "react-icons/md";
9
9
 
10
10
  interface EmptyCartContextValue {
@@ -20,10 +20,13 @@ export interface EmptyCartProps {
20
20
 
21
21
  const EmptyCart = ({ message, children }: EmptyCartProps) => {
22
22
  const t = useTranslations("Cart");
23
+ const contextValue = useMemo(
24
+ () => ({ message: message || t("emptyCartMsg") }),
25
+ [message, t],
26
+ );
27
+
23
28
  return (
24
- <EmptyCartContext.Provider
25
- value={{ message: message || t("emptyCartMsg") }}
26
- >
29
+ <EmptyCartContext.Provider value={contextValue}>
27
30
  <FlexBox
28
31
  alignItems="center"
29
32
  flexDirection="column"
@@ -38,11 +41,11 @@ const EmptyCart = ({ message, children }: EmptyCartProps) => {
38
41
 
39
42
  /* ---------- Subcomponents ---------- */
40
43
 
41
- EmptyCart.Icon = ({ size = "3rem" }: { size?: string }) => {
44
+ const Icon = ({ size = "3rem" }: { size?: string }) => {
42
45
  return <ShoppingBagIcon style={{ fontSize: size }} />;
43
46
  };
44
47
 
45
- EmptyCart.Message = ({
48
+ const Message = ({
46
49
  fontSize = 15,
47
50
  color = "grey.600",
48
51
  maxWidth = 200,
@@ -67,4 +70,7 @@ EmptyCart.Message = ({
67
70
  );
68
71
  };
69
72
 
73
+ EmptyCart.Icon = Icon;
74
+ EmptyCart.Message = Message;
75
+
70
76
  export default EmptyCart;
@@ -9,7 +9,7 @@ import ClearIcon from "@mui/icons-material/Clear";
9
9
  import IconButton from "@mui/material/IconButton";
10
10
  import { useTranslations } from "next-intl";
11
11
  import type React from "react";
12
- import { createContext, useContext } from "react";
12
+ import { createContext, useContext, useMemo } from "react";
13
13
  import { MdOutlineShoppingBag as CartBagIcon } from "react-icons/md";
14
14
 
15
15
  interface TopHeaderContextValue {
@@ -26,8 +26,10 @@ export interface TopHeaderProps {
26
26
  }
27
27
 
28
28
  const TopHeader = ({ total, toggle, children }: TopHeaderProps) => {
29
+ const contextValue = useMemo(() => ({ total, toggle }), [total, toggle]);
30
+
29
31
  return (
30
- <TopHeaderContext.Provider value={{ total, toggle }}>
32
+ <TopHeaderContext.Provider value={contextValue}>
31
33
  <FlexBetween mx={3} height={74}>
32
34
  {children}
33
35
  </FlexBetween>
@@ -37,7 +39,7 @@ const TopHeader = ({ total, toggle, children }: TopHeaderProps) => {
37
39
 
38
40
  /* ---------- Subcomponents ---------- */
39
41
 
40
- TopHeader.Heading = ({
42
+ const Heading = ({
41
43
  icon = <CartBagIcon color="inherit" />,
42
44
  }: {
43
45
  icon?: React.ReactNode;
@@ -57,11 +59,7 @@ TopHeader.Heading = ({
57
59
  );
58
60
  };
59
61
 
60
- TopHeader.CloseButton = ({
61
- icon = <ClearIcon />,
62
- }: {
63
- icon?: React.ReactNode;
64
- }) => {
62
+ const CloseButton = ({ icon = <ClearIcon /> }: { icon?: React.ReactNode }) => {
65
63
  const ctx = useContext(TopHeaderContext);
66
64
  const t = useTranslations("Cart");
67
65
  if (!ctx) return null;
@@ -74,4 +72,7 @@ TopHeader.CloseButton = ({
74
72
  );
75
73
  };
76
74
 
75
+ TopHeader.Heading = Heading;
76
+ TopHeader.CloseButton = CloseButton;
77
+
77
78
  export default TopHeader;