@evenicanpm/storefront-core 1.0.0-alpha → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api-manager/api-manager.config.json +1 -1
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +4 -4
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +100 -7
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +39 -17
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +220 -49
- package/src/api-manager/datasources/d365/utils/decode-jwt.ts +2 -2
- package/src/api-manager/datasources/e4/e4-cart.datasource.ts +6 -0
- package/src/api-manager/datasources/e4/e4-product.datasource.ts +5 -0
- package/src/api-manager/datasources/e4/e4-user.datasource.ts +39 -15
- package/src/api-manager/datasources/e4/e4.datasource.ts +1 -0
- package/src/api-manager/datasources/e4/e4.translator.ts +2 -1
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +5231 -38282
- package/src/api-manager/index.ts +58 -11
- package/src/api-manager/lib/get-graphql-client.ts +7 -7
- package/src/api-manager/schemas/address.schema.ts +1 -19
- package/src/api-manager/schemas/cart.schema.ts +18 -0
- package/src/api-manager/schemas/product.schema.ts +7 -0
- package/src/api-manager/schemas/user.schema.ts +52 -1
- package/src/api-manager/services/address/queries/get-states.ts +1 -1
- package/src/api-manager/services/cart/mutations/checkout.ts +16 -1
- package/src/api-manager/services/cart/queries/get-cart-lines-inventory.ts +15 -0
- package/src/api-manager/services/create-extension-query.ts +26 -0
- package/src/api-manager/services/create-mutation.ts +5 -1
- package/src/api-manager/services/create-query.ts +0 -1
- package/src/api-manager/services/organization/queries/get-channel-configuration.ts +1 -1
- package/src/api-manager/services/product/queries/get-product-prices.ts +16 -0
- package/src/api-manager/services/product/queries/search-products.ts +1 -0
- package/src/api-manager/services/user/mutations/add-to-wishlist.ts +24 -0
- package/src/api-manager/services/user/mutations/copy-cart-to-wishlist.ts +23 -0
- package/src/api-manager/services/user/mutations/create-wishlist.ts +4 -4
- package/src/api-manager/services/user/mutations/delete-wishlist.ts +4 -3
- package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +21 -0
- package/src/api-manager/services/user/mutations/update-wishlist.ts +23 -0
- package/src/api-manager/services/user/queries/get-wishlist-details.ts +11 -4
- package/src/api-manager/services/user/queries/get-wishlists.ts +4 -3
- package/src/api-manager/types/Datasource.ts +33 -5
- package/src/auth/auth-options.ts +2 -2
- package/src/auth/msal.ts +9 -9
- package/src/auth/next-auth-cookie-manager.ts +5 -3
- package/src/auth/providers/aadb2c-provider.ts +3 -4
- package/src/cms/endpoints.ts +2 -3
- package/src/components/_components/LazyImage.tsx +5 -2
- package/src/components/_components/header/components/user.tsx +1 -2
- package/src/components/_components/product-cards/product-card-1/components/quantity-buttons.tsx +17 -8
- package/src/components/_components/product-cards/product-card-1/product-card-1-wrapper.tsx +304 -0
- package/src/components/_components/product-cards/product-card-1/product-card.tsx +16 -55
- package/src/components/_components/product-cards/product-card-8/product-card.tsx +80 -60
- package/src/components/_components/product-cards/product-card-8/styles/index.ts +10 -0
- package/src/components/_components/product-cards/product-card-wishlist/components/hover-actions.tsx +37 -0
- package/src/components/_components/product-cards/product-card-wishlist/index.ts +1 -0
- package/src/components/_components/product-cards/product-card-wishlist/product-card.tsx +118 -0
- package/src/components/_components/product-cards/product-card-wishlist/styles/index.ts +51 -0
- package/src/components/_components/products-view/product-view-dialog.tsx +48 -10
- package/src/components/_components/products-view/products-grid-view.tsx +31 -17
- package/src/components/_components/products-view/products-list-view.tsx +30 -16
- package/src/components/_components/search-box/__test__/search-input-category.test.tsx +110 -0
- package/src/components/_components/search-box/__test__/search-input.test.tsx +137 -0
- package/src/components/_components/search-box/__test__/search-results.json +165 -0
- package/src/components/_components/search-box/components/search-result.tsx +66 -12
- package/src/components/_components/search-box/search-input-category.tsx +135 -32
- package/src/components/_components/search-box/styles/index.ts +2 -0
- package/src/components/_components/section-header/section-creator.tsx +1 -0
- package/src/components/_components/wishlist/add-to-wishlist-dialog.tsx +161 -0
- package/src/components/_components/wishlist/components/create-new-list.tsx +167 -0
- package/src/components/_components/wishlist/components/create-wishlist-button.tsx +40 -0
- package/src/components/_components/wishlist/components/dashboard-header.tsx +123 -0
- package/src/components/_components/wishlist/components/navigation.tsx +87 -0
- package/src/components/_components/wishlist/components/no-records.tsx +20 -0
- package/src/components/_components/wishlist/components/wishlist-modal-item.tsx +66 -0
- package/src/components/_components/wishlist/remove-from-wishlist-dialog.tsx +174 -0
- package/src/components/_components/wishlist/styles.ts +61 -0
- package/src/components/countries-input.tsx +6 -12
- package/src/lib/cart-cookie-handler-constants.ts +8 -0
- package/src/lib/cart-cookie-handler.ts +27 -5
- package/src/lib/create-graphql-client.ts +1 -2
- package/src/lib/product-list-types.ts +41 -0
- package/src/config.ts +0 -47
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { Box, Button, IconButton, Theme, styled } from "@mui/material";
|
|
4
|
+
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
// ICON COMPONENTS
|
|
7
|
+
import { MdMenu as Menu } from "react-icons/md";
|
|
8
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
9
|
+
import SideNav from "@evenicanpm/storefront-core/src/components/_components/side-nav/index";
|
|
10
|
+
import { H2 } from "@evenicanpm/storefront-core/src/components/_components/Typography";
|
|
11
|
+
import FlexBox from "@evenicanpm/storefront-core/src/components/_components/flex-box/flex-box";
|
|
12
|
+
// LOCAL CUSTOM COMPONENTS
|
|
13
|
+
import Navigation from "./navigation";
|
|
14
|
+
import { IconType } from "react-icons";
|
|
15
|
+
|
|
16
|
+
// STYLED COMPONENT
|
|
17
|
+
const StyledBox = styled("div")(({ theme }) => ({
|
|
18
|
+
display: "flex",
|
|
19
|
+
marginTop: theme.spacing(-2),
|
|
20
|
+
marginBottom: theme.spacing(3),
|
|
21
|
+
"& .headerHold": {
|
|
22
|
+
flexGrow: 1,
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
justifyContent: "space-between",
|
|
25
|
+
},
|
|
26
|
+
[theme.breakpoints.up("md")]: {
|
|
27
|
+
"& .sidenav": { display: "none" },
|
|
28
|
+
},
|
|
29
|
+
[theme.breakpoints.down("md")]: {
|
|
30
|
+
flexDirection: "column",
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// ==============================================================
|
|
35
|
+
|
|
36
|
+
type WithButtons = {
|
|
37
|
+
title: string;
|
|
38
|
+
titleMessage?: string;
|
|
39
|
+
buttons: ReactNode;
|
|
40
|
+
Icon: IconType;
|
|
41
|
+
href?: never;
|
|
42
|
+
buttonText?: never;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type WithGeneratedButton = {
|
|
46
|
+
title: string;
|
|
47
|
+
titleMessage?: string;
|
|
48
|
+
href?: string;
|
|
49
|
+
buttonText?: string;
|
|
50
|
+
Icon: IconType;
|
|
51
|
+
buttons?: ReactNode;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type Props = WithButtons | WithGeneratedButton;
|
|
55
|
+
|
|
56
|
+
// ==============================================================
|
|
57
|
+
|
|
58
|
+
export default function DashboardHeader({
|
|
59
|
+
title,
|
|
60
|
+
titleMessage,
|
|
61
|
+
buttons,
|
|
62
|
+
href,
|
|
63
|
+
buttonText,
|
|
64
|
+
Icon,
|
|
65
|
+
}: Props) {
|
|
66
|
+
const t = useTranslations();
|
|
67
|
+
const isTablet = useMediaQuery((theme: Theme) =>
|
|
68
|
+
theme.breakpoints.down(1025),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const GENERATED_BUTTON = href ? (
|
|
72
|
+
<Button
|
|
73
|
+
href={href}
|
|
74
|
+
color="primary"
|
|
75
|
+
LinkComponent={Link}
|
|
76
|
+
sx={{ bgcolor: "primary.light", px: 4 }}
|
|
77
|
+
>
|
|
78
|
+
{t(buttonText)}
|
|
79
|
+
</Button>
|
|
80
|
+
) : null;
|
|
81
|
+
|
|
82
|
+
const HEADER_LINKS = (
|
|
83
|
+
<FlexBox gap={1} justifyContent="flex-end">
|
|
84
|
+
{buttons || GENERATED_BUTTON}
|
|
85
|
+
</FlexBox>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<StyledBox>
|
|
90
|
+
<FlexBox mt={2} className="headerHold">
|
|
91
|
+
<FlexBox alignItems="center" gap={0.5}>
|
|
92
|
+
{Icon && (
|
|
93
|
+
<IconButton color="primary">
|
|
94
|
+
<Icon />
|
|
95
|
+
</IconButton>
|
|
96
|
+
)}
|
|
97
|
+
<H2 my={0} lineHeight={1} ellipsis>
|
|
98
|
+
{titleMessage ? titleMessage : t(title)}
|
|
99
|
+
</H2>
|
|
100
|
+
</FlexBox>
|
|
101
|
+
|
|
102
|
+
<FlexBox className="button-container" justifyContent="flex-end">
|
|
103
|
+
{!isTablet && HEADER_LINKS}
|
|
104
|
+
</FlexBox>
|
|
105
|
+
|
|
106
|
+
<div className="sidenav">
|
|
107
|
+
<SideNav
|
|
108
|
+
position="left"
|
|
109
|
+
handler={(close) => (
|
|
110
|
+
<IconButton onClick={close}>
|
|
111
|
+
<Menu fontSize="small" />
|
|
112
|
+
</IconButton>
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
<Navigation />
|
|
116
|
+
</SideNav>
|
|
117
|
+
</div>
|
|
118
|
+
</FlexBox>
|
|
119
|
+
|
|
120
|
+
{isTablet ? <Box mt={2}>{HEADER_LINKS}</Box> : null}
|
|
121
|
+
</StyledBox>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePathname } from "next/navigation";
|
|
4
|
+
import { Fragment } from "react";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
|
|
7
|
+
// ICON COMPONENTS
|
|
8
|
+
import {
|
|
9
|
+
MdOutlineShoppingBag as ShoppingBagOutlined,
|
|
10
|
+
MdFavoriteBorder as FavoriteBorder,
|
|
11
|
+
MdPerson as Person,
|
|
12
|
+
MdPlace as Place,
|
|
13
|
+
} from "react-icons/md";
|
|
14
|
+
|
|
15
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
16
|
+
import FlexBox from "@evenicanpm/storefront-core/src/components/_components/flex-box/flex-box";
|
|
17
|
+
import {
|
|
18
|
+
Paragraph,
|
|
19
|
+
Span,
|
|
20
|
+
} from "@evenicanpm/storefront-core/src/components/_components/Typography";
|
|
21
|
+
|
|
22
|
+
// API
|
|
23
|
+
import getUser from "@evenicanpm/storefront-core/src/api-manager/services/user/queries/get-user";
|
|
24
|
+
import getUserCounts from "@evenicanpm/storefront-core/src/api-manager/services/user/queries/get-user-counts";
|
|
25
|
+
|
|
26
|
+
// STYLED COMPONENTS
|
|
27
|
+
import { MainContainer, StyledNavLink } from "../styles";
|
|
28
|
+
|
|
29
|
+
const MENU_ITEMS = [
|
|
30
|
+
{
|
|
31
|
+
title: "DASHBOARD",
|
|
32
|
+
list: [
|
|
33
|
+
{
|
|
34
|
+
href: "/account/orders",
|
|
35
|
+
title: "orders",
|
|
36
|
+
Icon: ShoppingBagOutlined,
|
|
37
|
+
count: 0,
|
|
38
|
+
},
|
|
39
|
+
{ href: "/account/wish-lists", title: "wishlists", Icon: FavoriteBorder },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: "ACCOUNT SETTINGS",
|
|
44
|
+
list: [
|
|
45
|
+
{ href: "/account/profile", title: "profile", Icon: Person },
|
|
46
|
+
{ href: "/account/addresses", title: "addresses", Icon: Place, count: 0 },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export default function Navigation() {
|
|
52
|
+
const pathname = usePathname();
|
|
53
|
+
const t = useTranslations("Account.Menu");
|
|
54
|
+
|
|
55
|
+
const { data: user } = getUser.useData();
|
|
56
|
+
const { data: counts } = getUserCounts.useData(String(user?.RecordId) || "");
|
|
57
|
+
|
|
58
|
+
MENU_ITEMS[0].list[0].count = counts?.orderCount;
|
|
59
|
+
MENU_ITEMS[1].list[1].count = counts?.addressCount;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<MainContainer>
|
|
63
|
+
{MENU_ITEMS.map((item) => (
|
|
64
|
+
<Fragment key={item.title}>
|
|
65
|
+
<Paragraph p="26px 30px 1rem" color="grey.600" fontSize={12}>
|
|
66
|
+
{item.title}
|
|
67
|
+
</Paragraph>
|
|
68
|
+
|
|
69
|
+
{item.list.map(({ Icon, count, href, title }) => (
|
|
70
|
+
<StyledNavLink
|
|
71
|
+
href={href}
|
|
72
|
+
key={title}
|
|
73
|
+
isCurrentPath={pathname.includes(href)}
|
|
74
|
+
>
|
|
75
|
+
<FlexBox alignItems="center" gap={1}>
|
|
76
|
+
<Icon color="inherit" fontSize="large" className="nav-icon" />
|
|
77
|
+
<Span>{t(title)}</Span>
|
|
78
|
+
</FlexBox>
|
|
79
|
+
|
|
80
|
+
<Span>{count}</Span>
|
|
81
|
+
</StyledNavLink>
|
|
82
|
+
))}
|
|
83
|
+
</Fragment>
|
|
84
|
+
))}
|
|
85
|
+
</MainContainer>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box } from "@mui/material";
|
|
3
|
+
import { H4 } from "@evenicanpm/storefront-core/src/components/_components/Typography";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
|
|
6
|
+
type Props = { message: string };
|
|
7
|
+
|
|
8
|
+
export default function NoRecords({ message }: Props) {
|
|
9
|
+
const t = useTranslations();
|
|
10
|
+
return (
|
|
11
|
+
<Box
|
|
12
|
+
display="flex"
|
|
13
|
+
justifyContent="center"
|
|
14
|
+
alignItems="center"
|
|
15
|
+
height="50vh"
|
|
16
|
+
>
|
|
17
|
+
<H4 color="grey.500">{t(message)}</H4>
|
|
18
|
+
</Box>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Button } from "@mui/material";
|
|
2
|
+
import { format } from "date-fns";
|
|
3
|
+
// MUI ICON COMPONENTS
|
|
4
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
5
|
+
import { Paragraph } from "@evenicanpm/storefront-core/src/components/_components/Typography";
|
|
6
|
+
// LOCAL CUSTOM COMPONENT
|
|
7
|
+
|
|
8
|
+
// API
|
|
9
|
+
import { Wishlist } from "@/api-manager/schemas/user.schema";
|
|
10
|
+
import { StyledTableRow } from "../styles";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
wishlist: Wishlist;
|
|
14
|
+
addToWishlist?: (wishlistId: string) => void;
|
|
15
|
+
removeFromWishlist?: (wishlistId: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function WishlistModalItem({
|
|
19
|
+
wishlist,
|
|
20
|
+
addToWishlist,
|
|
21
|
+
removeFromWishlist,
|
|
22
|
+
}: Props) {
|
|
23
|
+
const handleClick = () => {
|
|
24
|
+
if (addToWishlist) {
|
|
25
|
+
addToWishlist(wishlist.Id);
|
|
26
|
+
} else if (removeFromWishlist) {
|
|
27
|
+
removeFromWishlist(wishlist.Id);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return (
|
|
31
|
+
<StyledTableRow
|
|
32
|
+
sx={{
|
|
33
|
+
display: "grid",
|
|
34
|
+
gridTemplateColumns: "2fr 1fr 1fr 1fr", // Define equal column structure
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
gap: 2,
|
|
37
|
+
padding: "10px 15px",
|
|
38
|
+
width: "500px",
|
|
39
|
+
cursor: "default",
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<Paragraph ellipsis sx={{ fontWeight: "bold" }}>
|
|
43
|
+
{wishlist?.Name ?? ""}
|
|
44
|
+
</Paragraph>
|
|
45
|
+
<Paragraph ellipsis>
|
|
46
|
+
{" "}
|
|
47
|
+
{format(
|
|
48
|
+
new Date(wishlist?.CreatedDateTime ?? wishlist?.LastUpdated ?? ""),
|
|
49
|
+
"MMM dd, yyyy",
|
|
50
|
+
)}
|
|
51
|
+
</Paragraph>
|
|
52
|
+
<Paragraph ellipsis>
|
|
53
|
+
{" "}
|
|
54
|
+
{`${wishlist?.ProductListLines?.length} products`}
|
|
55
|
+
</Paragraph>
|
|
56
|
+
|
|
57
|
+
<Button
|
|
58
|
+
onClick={handleClick}
|
|
59
|
+
color="primary"
|
|
60
|
+
sx={{ bgcolor: "primary.light", px: 4 }}
|
|
61
|
+
>
|
|
62
|
+
{addToWishlist ? "Add" : "Remove"}
|
|
63
|
+
</Button>
|
|
64
|
+
</StyledTableRow>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React, { Fragment, useCallback, useEffect } from "react";
|
|
2
|
+
import isFunction from "lodash/isFunction";
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Grid, Button, Dialog, DialogContent } from "@mui/material";
|
|
5
|
+
import { MdFavorite } from "react-icons/md";
|
|
6
|
+
// Global Custom components
|
|
7
|
+
import DashboardHeader from "./components/dashboard-header";
|
|
8
|
+
import WishlistModalItem from "./components/wishlist-modal-item";
|
|
9
|
+
import NoRecords from "./components/no-records";
|
|
10
|
+
// API
|
|
11
|
+
import getWishlists from "@/api-manager/services/user/queries/get-wishlists";
|
|
12
|
+
import {
|
|
13
|
+
ExtendedProductListLine,
|
|
14
|
+
Wishlist,
|
|
15
|
+
WishlistLinesInput,
|
|
16
|
+
} from "@evenicanpm/storefront-core/src/api-manager/schemas/user.schema";
|
|
17
|
+
import { useNotification } from "@evenicanpm/storefront-core/src/components/Notifications/use-notification";
|
|
18
|
+
import { useSession } from "next-auth/react";
|
|
19
|
+
import useRemoveFromWishlist from "@/api-manager/services/user/mutations/remove-from-wishlist";
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
open: boolean;
|
|
23
|
+
// product?: ProductDetails;
|
|
24
|
+
productId?: number;
|
|
25
|
+
onSuccess?: () => void;
|
|
26
|
+
handleClose: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function RemoveFromWishlistDialog({
|
|
30
|
+
open,
|
|
31
|
+
productId,
|
|
32
|
+
handleClose,
|
|
33
|
+
onSuccess,
|
|
34
|
+
}: Props) {
|
|
35
|
+
const { data: wishlists, isLoading } = getWishlists.useSuspenseData();
|
|
36
|
+
const t = useTranslations("Account.Wishlists");
|
|
37
|
+
const { setNotification } = useNotification();
|
|
38
|
+
|
|
39
|
+
const { mutateAsync: removeFromWishlist } = useRemoveFromWishlist();
|
|
40
|
+
|
|
41
|
+
const { data: session } = useSession();
|
|
42
|
+
const isAuthenticated = !!session;
|
|
43
|
+
|
|
44
|
+
const [relevantWishlists, setRelevantWishlists] = React.useState<Wishlist[]>(
|
|
45
|
+
[],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const handleCloseDialog = () => {
|
|
49
|
+
if (isFunction(handleClose)) {
|
|
50
|
+
handleClose();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// get wishlists that the product is in
|
|
55
|
+
const getRelevantWishlists = useCallback(
|
|
56
|
+
(wishlists: Wishlist[]) => {
|
|
57
|
+
return wishlists.filter((wishlist) =>
|
|
58
|
+
wishlist.ProductListLines?.some((line) => line.ProductId === productId),
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
[productId],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const handleRemoveFromWishlist = useCallback(
|
|
65
|
+
async (wishlistId: string) => {
|
|
66
|
+
// get the selected wishlist
|
|
67
|
+
const wishlist = relevantWishlists.find(
|
|
68
|
+
(wishlist) => wishlist.Id === wishlistId,
|
|
69
|
+
);
|
|
70
|
+
if (wishlist) {
|
|
71
|
+
try {
|
|
72
|
+
const extendedWishlistLine = wishlist.ProductListLines?.find(
|
|
73
|
+
(item: ExtendedProductListLine) => item.ProductId === productId,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (extendedWishlistLine) {
|
|
77
|
+
const wishlistLinesInput: WishlistLinesInput = {
|
|
78
|
+
Id: wishlist.Id,
|
|
79
|
+
ProductListLines: [extendedWishlistLine],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await removeFromWishlist(wishlistLinesInput);
|
|
83
|
+
|
|
84
|
+
setNotification({
|
|
85
|
+
message: t("wishlistLineRemoveSuccess"),
|
|
86
|
+
severity: "success",
|
|
87
|
+
});
|
|
88
|
+
if (isFunction(onSuccess)) {
|
|
89
|
+
onSuccess();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
setNotification({
|
|
94
|
+
message: t("wishlistLineRemoveFailed"),
|
|
95
|
+
severity: "error",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[
|
|
101
|
+
relevantWishlists,
|
|
102
|
+
removeFromWishlist,
|
|
103
|
+
setNotification,
|
|
104
|
+
t,
|
|
105
|
+
onSuccess,
|
|
106
|
+
productId,
|
|
107
|
+
],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
const withlistsWithProduct = getRelevantWishlists(wishlists);
|
|
112
|
+
setRelevantWishlists(withlistsWithProduct);
|
|
113
|
+
if (open && withlistsWithProduct.length === 1) {
|
|
114
|
+
// delete right away
|
|
115
|
+
handleRemoveFromWishlist(withlistsWithProduct[0].Id);
|
|
116
|
+
}
|
|
117
|
+
}, [open, getRelevantWishlists, handleRemoveFromWishlist, wishlists]);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Dialog
|
|
121
|
+
open={open}
|
|
122
|
+
onClose={handleCloseDialog}
|
|
123
|
+
aria-labelledby="form-dialog-title"
|
|
124
|
+
>
|
|
125
|
+
<DialogContent>
|
|
126
|
+
<Fragment>
|
|
127
|
+
<DashboardHeader
|
|
128
|
+
title="Account.Wishlists.heading"
|
|
129
|
+
Icon={MdFavorite}
|
|
130
|
+
href=""
|
|
131
|
+
buttonText=""
|
|
132
|
+
/>
|
|
133
|
+
{isAuthenticated && !isLoading ? (
|
|
134
|
+
relevantWishlists && relevantWishlists.length > 0 ? (
|
|
135
|
+
relevantWishlists?.map((wishlist: Wishlist) => (
|
|
136
|
+
<WishlistModalItem
|
|
137
|
+
key={wishlist.Id}
|
|
138
|
+
wishlist={wishlist}
|
|
139
|
+
removeFromWishlist={handleRemoveFromWishlist}
|
|
140
|
+
/>
|
|
141
|
+
))
|
|
142
|
+
) : (
|
|
143
|
+
<NoRecords message="Account.Wishlists.noWishlists" />
|
|
144
|
+
)
|
|
145
|
+
) : (
|
|
146
|
+
<NoRecords message="Account.Wishlists.wishlistAddUnauthenticated" />
|
|
147
|
+
)}
|
|
148
|
+
</Fragment>
|
|
149
|
+
<Grid container spacing={3}>
|
|
150
|
+
<Grid
|
|
151
|
+
item
|
|
152
|
+
sm={12}
|
|
153
|
+
xs={12}
|
|
154
|
+
sx={{
|
|
155
|
+
display: "flex",
|
|
156
|
+
justifyContent: "flex-start",
|
|
157
|
+
gap: 2,
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
<Button
|
|
161
|
+
color="primary"
|
|
162
|
+
variant="outlined"
|
|
163
|
+
size="small"
|
|
164
|
+
onClick={handleCloseDialog}
|
|
165
|
+
sx={{ height: 30.75 }}
|
|
166
|
+
>
|
|
167
|
+
{t("btnCancel")}
|
|
168
|
+
</Button>
|
|
169
|
+
</Grid>
|
|
170
|
+
</Grid>
|
|
171
|
+
</DialogContent>
|
|
172
|
+
</Dialog>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Card, { CardProps } from "@mui/material/Card";
|
|
2
|
+
import styled from "@mui/material/styles/styled";
|
|
3
|
+
import NavLink, {
|
|
4
|
+
NavLinkProps,
|
|
5
|
+
} from "@evenicanpm/storefront-core/src/components/_components/nav-link/nav-link";
|
|
6
|
+
import { ComponentType } from "react";
|
|
7
|
+
|
|
8
|
+
type ExtendedNavLinkProps = NavLinkProps & {
|
|
9
|
+
isCurrentPath?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const MainContainer: ComponentType<CardProps> = styled(Card)(
|
|
13
|
+
({ theme }) => ({
|
|
14
|
+
paddingBottom: "1.5rem",
|
|
15
|
+
[theme.breakpoints.down("md")]: {
|
|
16
|
+
boxShadow: "none",
|
|
17
|
+
overflowY: "auto",
|
|
18
|
+
height: "calc(100vh - 64px)",
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
) as typeof Card;
|
|
22
|
+
|
|
23
|
+
export const StyledNavLink: ComponentType<ExtendedNavLinkProps> = styled(
|
|
24
|
+
NavLink,
|
|
25
|
+
{
|
|
26
|
+
shouldForwardProp: (prop) => prop !== "isCurrentPath",
|
|
27
|
+
},
|
|
28
|
+
)<ExtendedNavLinkProps>(({ theme, isCurrentPath }) => ({
|
|
29
|
+
display: "flex",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
borderLeft: "4px solid",
|
|
32
|
+
paddingLeft: "1.5rem",
|
|
33
|
+
paddingRight: "1.5rem",
|
|
34
|
+
marginBottom: "1.25rem",
|
|
35
|
+
justifyContent: "space-between",
|
|
36
|
+
borderColor: isCurrentPath ? theme.palette.primary.main : "transparent",
|
|
37
|
+
"& .nav-icon": {
|
|
38
|
+
color: isCurrentPath ? theme.palette.primary.main : theme.palette.grey[600],
|
|
39
|
+
},
|
|
40
|
+
"&:hover": {
|
|
41
|
+
borderColor: theme.palette.primary.main,
|
|
42
|
+
"& .nav-icon": { color: theme.palette.primary.main },
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
export const StyledTableRow: ComponentType<CardProps> = styled(Card)(
|
|
47
|
+
({ theme }) => ({
|
|
48
|
+
gap: 16,
|
|
49
|
+
marginBlock: 16,
|
|
50
|
+
display: "grid",
|
|
51
|
+
borderRadius: 10,
|
|
52
|
+
cursor: "pointer",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
padding: ".6rem 1.2rem",
|
|
55
|
+
gridTemplateColumns: "1.5fr 2fr 1.5fr auto",
|
|
56
|
+
[theme.breakpoints.down("sm")]: {
|
|
57
|
+
gap: 8,
|
|
58
|
+
gridTemplateColumns: "repeat(2, 1fr)",
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
Autocomplete,
|
|
4
4
|
AutocompleteRenderInputParams,
|
|
5
5
|
Box,
|
|
6
|
-
InputProps,
|
|
7
6
|
TextField,
|
|
8
7
|
} from "@mui/material";
|
|
9
8
|
import React from "react";
|
|
@@ -28,12 +27,14 @@ export default function CountryInput({
|
|
|
28
27
|
) => setCountryInputValue(value);
|
|
29
28
|
|
|
30
29
|
const renderOption = (
|
|
31
|
-
|
|
30
|
+
props: React.HTMLAttributes<HTMLLIElement> & {
|
|
31
|
+
key: any;
|
|
32
|
+
},
|
|
32
33
|
option: CountryRegionInfo,
|
|
33
|
-
index: number,
|
|
34
34
|
) => {
|
|
35
|
+
const { key, ...optionProps } = props;
|
|
35
36
|
return (
|
|
36
|
-
<Box key={
|
|
37
|
+
<Box key={key} component="li" {...optionProps}>
|
|
37
38
|
{option.ShortName}
|
|
38
39
|
</Box>
|
|
39
40
|
);
|
|
@@ -45,11 +46,6 @@ export default function CountryInput({
|
|
|
45
46
|
label={label}
|
|
46
47
|
variant="outlined"
|
|
47
48
|
placeholder="Select Country"
|
|
48
|
-
slotProps={{
|
|
49
|
-
input: {
|
|
50
|
-
"data-testid": "country-input",
|
|
51
|
-
} as InputProps,
|
|
52
|
-
}}
|
|
53
49
|
/>
|
|
54
50
|
);
|
|
55
51
|
|
|
@@ -66,9 +62,7 @@ export default function CountryInput({
|
|
|
66
62
|
data-testid="country-autocomplete"
|
|
67
63
|
inputValue={countryInputValue}
|
|
68
64
|
onInputChange={handleAutoCompleteChange}
|
|
69
|
-
renderOption={(props, option,
|
|
70
|
-
renderOption(props, option, state?.index)
|
|
71
|
-
}
|
|
65
|
+
renderOption={(props, option) => renderOption(props, option)}
|
|
72
66
|
renderInput={renderInput}
|
|
73
67
|
getOptionLabel={getOptionLabel}
|
|
74
68
|
/>
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { cookies } from "next/headers";
|
|
3
|
+
import {
|
|
4
|
+
CART_COOKIE_NAME,
|
|
5
|
+
CartCookieType,
|
|
6
|
+
} from "./cart-cookie-handler-constants";
|
|
3
7
|
|
|
4
|
-
const getCartCookie = async ()
|
|
8
|
+
const getCartCookie = async (): Promise<string | null> =>
|
|
9
|
+
(await getCartCookieWithAuthStatus())?.id ?? null;
|
|
5
10
|
|
|
6
|
-
const setCartCookie = async (cartId: string) => {
|
|
7
|
-
|
|
11
|
+
const setCartCookie = async (cartId: string, isAuthenticated?: boolean) => {
|
|
12
|
+
const cookieValue = `${isAuthenticated ? CartCookieType.Auth : CartCookieType.Anon}:${cartId}`;
|
|
13
|
+
(await cookies()).set(CART_COOKIE_NAME, cookieValue);
|
|
8
14
|
};
|
|
9
15
|
|
|
10
16
|
const deleteCartCookie = async () => {
|
|
11
|
-
(await cookies()).delete(
|
|
17
|
+
(await cookies()).delete(CART_COOKIE_NAME);
|
|
12
18
|
};
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
const getCartCookieWithAuthStatus = async () => {
|
|
21
|
+
const cartCookie = (await cookies()).get(CART_COOKIE_NAME)?.value;
|
|
22
|
+
if (!cartCookie) return null;
|
|
23
|
+
const parts = cartCookie.split(":");
|
|
24
|
+
if (parts.length !== 2) return null;
|
|
25
|
+
return {
|
|
26
|
+
type: parts[0] as CartCookieType,
|
|
27
|
+
id: parts[1] as string,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
getCartCookie,
|
|
33
|
+
setCartCookie,
|
|
34
|
+
deleteCartCookie,
|
|
35
|
+
getCartCookieWithAuthStatus,
|
|
36
|
+
};
|
|
@@ -5,7 +5,6 @@ import { GraphQLClient } from "graphql-request";
|
|
|
5
5
|
// and one here to isntantiate because graphql-request library must be same
|
|
6
6
|
// for this file and the sdk
|
|
7
7
|
import { getSdk } from "@/lib/graphqlRequestSdk";
|
|
8
|
-
import { getConfig } from "@config";
|
|
9
8
|
|
|
10
9
|
// typescript can't infer type without reference
|
|
11
10
|
export type E4Sdk = ReturnType<typeof getSdk>;
|
|
@@ -15,7 +14,7 @@ export type E4Sdk = ReturnType<typeof getSdk>;
|
|
|
15
14
|
* @returns a new graphql request client with all of E4 queries and mutations
|
|
16
15
|
*/
|
|
17
16
|
const createGraphQLClient = async (): Promise<E4Sdk> => {
|
|
18
|
-
const API_URL =
|
|
17
|
+
const API_URL = process.env.NEXT_PUBLIC_E4_API_ENDPOINT || "";
|
|
19
18
|
const client = new GraphQLClient(API_URL, {
|
|
20
19
|
credentials: "include", // Ensures cookies are included
|
|
21
20
|
headers: {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ProductRefinerValue } from "@msdyn365-commerce/retail-proxy";
|
|
2
|
+
|
|
3
|
+
export const PageKey = {
|
|
4
|
+
TOP: "perPage",
|
|
5
|
+
PAGE: "page",
|
|
6
|
+
REFINEMENT: "refiner",
|
|
7
|
+
VIEW: "view",
|
|
8
|
+
SORT: "sort",
|
|
9
|
+
SEARCH: "search",
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export type ProductListSearchParams = {
|
|
13
|
+
perPage?: typeof PageKey.TOP;
|
|
14
|
+
page?: typeof PageKey.PAGE;
|
|
15
|
+
refiner: typeof PageKey.REFINEMENT;
|
|
16
|
+
search: typeof PageKey.SEARCH;
|
|
17
|
+
sort: typeof PageKey.SORT;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type PageKey = (typeof PageKey)[keyof typeof PageKey];
|
|
21
|
+
|
|
22
|
+
export type ViewMode = "grid" | "list";
|
|
23
|
+
|
|
24
|
+
export type ProductListParamHandlers = {
|
|
25
|
+
handlePageChange: (page: number) => void;
|
|
26
|
+
handlePerPageChange: (perPage: number) => void;
|
|
27
|
+
handleRefinerChange: (refiner: ProductRefinerValue) => void;
|
|
28
|
+
handleSortChange: (sort: SortOption) => void;
|
|
29
|
+
handleViewChange: (view: ViewMode) => void;
|
|
30
|
+
handleSearchChange: (searchTerm: string) => void;
|
|
31
|
+
handleRefinersClear?: () => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type SortOption = {
|
|
35
|
+
field: string;
|
|
36
|
+
code: string;
|
|
37
|
+
isDefault?: boolean;
|
|
38
|
+
isDescending: boolean;
|
|
39
|
+
displayName: string;
|
|
40
|
+
defaultForSearch?: boolean;
|
|
41
|
+
};
|