@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.
- package/CHANGELOG.md +14 -0
- package/package.json +90 -90
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +4 -3
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +3 -3
- package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +1 -0
- package/src/api-manager/datasources/e4/address/e4-address.datasource.ts +5 -14
- package/src/api-manager/datasources/e4/address/e4-address.translator.ts +4 -3
- package/src/api-manager/datasources/e4/categories/e4-categories.translator.ts +1 -1
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +29 -3
- package/src/api-manager/datasources/e4/product/e4-product.translator.ts +1 -1
- package/src/api-manager/index.ts +1 -1
- package/src/api-manager/lib/get-graphql-client.ts +1 -1
- package/src/api-manager/services/cart/mutations/add-discount-code.ts +0 -11
- package/src/api-manager/services/create-query.ts +13 -7
- package/src/api-manager/services/user/mutations/add-to-wishlist.ts +1 -1
- package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +1 -1
- package/src/auth/AUTH GUIDE.md +880 -0
- package/src/cms/blocks/components/featured-categories.tsx +18 -15
- package/src/cms/blocks/components/featured-products.tsx +3 -2
- package/src/cms/blocks/components/footer/data/index.ts +0 -0
- package/src/cms/blocks/components/footer/index.tsx +0 -0
- package/src/cms/blocks/components/footer/sections/footer-app-store.tsx +0 -0
- package/src/cms/blocks/components/footer/sections/footer-contact.tsx +0 -2
- package/src/cms/blocks/components/footer/sections/footer-links.tsx +0 -0
- package/src/cms/blocks/components/footer/sections/footer-logo.tsx +2 -1
- package/src/cms/blocks/components/footer/sections/footer-social-links.tsx +0 -0
- package/src/cms/blocks/components/footer/styles/index.ts +0 -0
- package/src/cms/blocks/components/hero-carousel/index.tsx +0 -0
- package/src/cms/blocks/components/product-carousel/index.tsx +0 -0
- package/src/cms/blocks/components/shared/featured-product-card.tsx +0 -0
- package/src/cms/blocks/components/shared/product-category-item.tsx +0 -0
- package/src/cms/blocks/components/shared/product-grid.tsx +0 -0
- package/src/cms/blocks/components/shared/top-categories-card.tsx +0 -0
- package/src/cms/blocks/components/shared/top-rating-product-card.tsx +0 -0
- package/src/cms/blocks/icons/components/category.tsx +0 -0
- package/src/cms/blocks/icons/components/dotted-star.tsx +0 -0
- package/src/cms/blocks/icons/components/gift-box.tsx +0 -0
- package/src/cms/blocks/icons/components/light.tsx +0 -0
- package/src/cms/blocks/icons/components/new-arrival.tsx +0 -0
- package/src/cms/blocks/icons/components/rank-badge.tsx +0 -0
- package/src/components/SearchInput.stories.tsx +12 -10
- package/src/components/base-layout.tsx +42 -0
- package/src/components/categories/category-list/category-list.tsx +32 -12
- package/src/components/header/__tests__/header.test.tsx +0 -24
- package/src/components/header/components/mobile-header.tsx +16 -8
- package/src/components/header/header.tsx +6 -3
- package/src/components/mini-cart/components/bottom-actions.tsx +7 -2
- package/src/components/mini-cart/components/cart-item.tsx +16 -13
- package/src/components/mini-cart/components/empty-view.tsx +12 -6
- package/src/components/mini-cart/components/top-header.tsx +9 -8
- package/src/components/mini-cart/mini-cart-trigger.tsx +11 -5
- package/src/components/mini-cart/mini-cart.tsx +30 -15
- package/src/components/mobile-navigation/mobile-navigation-bar.tsx +6 -2
- package/src/components/navbar/categories.tsx +8 -2
- package/src/components/navbar/category-based-menu/category-based-menu.tsx +6 -2
- package/src/components/navbar/mega-menu/mega-menu.tsx +22 -14
- package/src/components/navbar/mobile-menu/mobile-menu.test.tsx +8 -11
- package/src/components/navbar/mobile-menu/mobile-menu.tsx +13 -11
- package/src/components/navbar/nav-list/nav-list.tsx +3 -2
- package/src/components/navbar/navbar.tsx +4 -2
- package/src/components/product-cards/product-card/product-card.tsx +50 -26
- package/src/components/product-cards/product-card-plp/product-card.tsx +37 -16
- package/src/components/product-cards/product-card-plp-list/product-card.tsx +38 -16
- package/src/components/product-cards/product-card-search/product-card.tsx +34 -13
- package/src/components/product-dialog/ProductDialog.stories.tsx +20 -18
- package/src/components/product-dialog/compound/product-dialog.tsx +7 -4
- package/src/components/product-dimensions/compound/dimensions-group.tsx +4 -2
- package/src/components/product-dimensions/compound/dimensions.tsx +15 -6
- package/src/components/product-quantity-buttons/compound/quantity-buttons-root.tsx +21 -13
- package/src/components/products-view/compound/products-grid-view.tsx +11 -4
- package/src/components/products-view/compound/products-list-view.tsx +11 -4
- package/src/components/search-bar/compound/search-bar-root.tsx +6 -4
- package/src/components/search-bar/compound/textfield-adornment-category.tsx +25 -8
- package/src/components/search-bar/hooks/use-click-outside.ts +7 -1
- package/src/components/search-bar/hooks/use-search.ts +3 -3
- package/src/components/user/index.ts +3 -0
- package/src/components/user/use-user-auth.ts +163 -0
- package/src/components/user/user.tsx +215 -0
- package/src/components/wishlist-dialogs/add-to-wishlist/AddToWishlist.stories.tsx +20 -15
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +11 -8
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-list.tsx +18 -3
- package/src/components/wishlist-dialogs/add-to-wishlist/index.tsx +0 -1
- package/src/components/wishlist-dialogs/create-wishlist/compound/create-wishlist-dialog.tsx +6 -2
- package/src/hooks/use-overflow-detect.ts +4 -4
- package/src/lib/graphqlRequestSdk.ts +0 -0
- package/src/lib/page-slugs.ts +16 -0
- package/src/pages/account/account-navigation.tsx +7 -14
- package/src/pages/account/account-routes.ts +4 -9
- package/src/pages/account/addresses/address-form.tsx +38 -16
- package/src/pages/account/addresses/address-item.tsx +18 -8
- package/src/pages/account/dashboard-header.tsx +20 -13
- package/src/pages/account/orders/order-history-filters-panel.tsx +21 -20
- package/src/pages/account/orders/order-history-header.tsx +14 -4
- package/src/pages/account/orders/order-history-root.tsx +3 -1
- package/src/pages/account/orders/order-history-row.tsx +12 -4
- package/src/pages/account/orders/order-history-search-and-filter.tsx +88 -59
- package/src/pages/account/orders/order-history-search-dropdown.tsx +26 -9
- package/src/pages/account/orders/order-history-sort.tsx +3 -1
- package/src/pages/account/orders/order-history-table.tsx +27 -5
- package/src/pages/account/orders/order-progress.tsx +12 -6
- package/src/pages/account/orders/order-row.tsx +18 -6
- package/src/pages/account/orders/order-summary.tsx +24 -8
- package/src/pages/account/orders/ordered-products.tsx +15 -5
- package/src/pages/account/profile/user-info.tsx +11 -3
- package/src/pages/account/table-row-skeleton.tsx +1 -1
- package/src/pages/account/wishlist/create-new-list.tsx +33 -9
- package/src/pages/account/wishlist/wishlist-item.tsx +18 -6
- package/src/pages/cart/cart-item.tsx +21 -7
- package/src/pages/cart/checkout-form.tsx +55 -22
- package/src/pages/cart/coupon-entry.tsx +143 -36
- package/src/pages/cart/estimate-shipping.tsx +45 -21
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +22 -14
- package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +37 -16
- package/src/pages/checkout/checkout-alt-form/steps/address/address-card.tsx +9 -3
- package/src/pages/checkout/checkout-alt-form/steps/address/delivery-address.tsx +84 -51
- package/src/pages/checkout/checkout-alt-form/steps/address/new-address-form.tsx +41 -19
- package/src/pages/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +17 -10
- package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +15 -6
- package/src/pages/checkout/checkout-alt-form/steps/shipping/delivery-options.tsx +6 -2
- package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +12 -4
- package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +12 -5
- package/src/pages/checkout/checkout-alt-summary/list-item.tsx +17 -10
- package/src/pages/confirmation/address.tsx +6 -2
- package/src/pages/confirmation/confirmation-summary.tsx +12 -4
- package/src/pages/confirmation/ordered-products.tsx +12 -4
- package/src/pages/product-details/bopis/store-card/shop-card.tsx +2 -2
- package/src/pages/product-details/bopis/store-card/utils/getDaysResources.ts +1 -1
- package/src/pages/product-details/product-intro/compound/product-info.tsx +41 -18
- package/src/pages/product-details/product-intro/compound/product-intro-images.tsx +18 -12
- package/src/pages/product-details/product-intro/compound/product-intro.tsx +20 -10
- package/src/pages/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +1 -1
- package/src/pages/product-details/product-tabs.tsx +20 -12
- package/src/pages/product-list/product-list-view.tsx +4 -2
- package/src/pages/product-list/search-bar.tsx +7 -7
- package/src/pages/product-list/side-nav.tsx +5 -3
- package/src/pages/quickorder/order-upload.tsx +1 -1
- package/src/pages/quickorder/provider.tsx +26 -14
- package/src/pages/quickorder/quick-order.tsx +49 -22
- package/src/providers/nav-provider/index.tsx +16 -11
- package/tsconfig.json +0 -5
- package/src/components/header/components/user.tsx +0 -322
- /package/src/components/{header → user}/__tests__/user.test.tsx +0 -0
|
@@ -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(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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,
|
|
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={
|
|
26
|
+
key={productKey}
|
|
26
27
|
>
|
|
27
28
|
<MediaCard
|
|
28
29
|
name={product?.description ?? ""}
|
|
File without changes
|
|
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;
|
|
File without changes
|
|
@@ -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 {...
|
|
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
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
const Logo = () => {
|
|
81
81
|
return <SiteLogo height="2.125rem" />;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
const Icon = ({ size = "3rem" }: { size?: string }) => {
|
|
42
45
|
return <ShoppingBagIcon style={{ fontSize: size }} />;
|
|
43
46
|
};
|
|
44
47
|
|
|
45
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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;
|