@evenicanpm/storefront-core 2.3.0 → 2.4.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/CHANGELOG.md +8 -0
- package/package.json +29 -9
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +11 -0
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +23 -1
- package/src/api-manager/datasources/d365/d365-order.datasource.ts +6 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +29 -14
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +7 -0
- package/src/api-manager/index.ts +2 -1
- package/src/api-manager/schemas/product.schema.ts +1 -1
- package/src/auth/msal.ts +1 -4
- package/src/cms/blocks/block-manager.tsx +1 -1
- package/src/cms/blocks/components/footer/footer-content.tsx +61 -0
- package/src/cms/blocks/components/footer/index.tsx +9 -71
- package/src/cms/blocks/components/footer/interfaces.ts +4 -0
- package/src/cms/blocks/components/footer/sections/footer-contact.tsx +18 -9
- package/src/cms/blocks/components/footer/sections/footer-logo.tsx +17 -2
- package/src/cms/endpoints.ts +7 -2
- package/src/components/BazaarMenu.tsx +1 -1
- package/src/components/Typography.tsx +4 -2
- package/src/components/_tests_/site-logo.test.tsx +1 -1
- package/src/components/carousel-cards/carousel-card-1/CarouselCard1.stories.tsx +1 -1
- package/src/components/categories/category-menu.tsx +1 -1
- package/src/components/countries-input.tsx +4 -0
- package/src/components/header/components/user.tsx +4 -5
- package/src/components/navbar/mobile-menu/mobile-menu.test.tsx +1 -1
- package/src/components/navbar/utils/transform-nav.test.ts +1 -1
- package/src/components/navbar/utils/transform-nav.ts +1 -1
- package/src/components/product-cards/product-card/product-card.tsx +5 -2
- package/src/components/product-cards/product-card/styles/index.ts +1 -5
- package/src/components/product-cards/product-card-plp/product-card.tsx +4 -12
- package/src/components/product-cards/product-card-plp/styles/index.ts +15 -0
- package/src/components/products-view/compound/products-grid-view.tsx +5 -1
- package/src/components/products-view/compound/products-list-view.tsx +5 -1
- package/src/components/site-logo.tsx +1 -1
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-item.tsx +3 -1
- package/src/global.d.ts +3 -0
- package/src/pages/account/account-navigation.tsx +2 -2
- package/src/pages/account/account-routes.ts +38 -4
- package/src/pages/account/addresses/address-form.tsx +20 -2
- package/src/pages/account/table-row-skeleton.tsx +31 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +3 -1
- package/src/pages/blog/blog-detail-view.tsx +3 -1
- package/src/pages/cart/cart-item.tsx +1 -7
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +14 -3
- package/src/pages/checkout/checkout-alt-form/steps/address/address-card.tsx +5 -3
- package/src/pages/checkout/checkout-alt-form/steps/address/new-address-form.tsx +1 -1
- package/src/pages/cms-page-view.tsx +1 -1
- package/src/pages/confirmation/ordered-products.tsx +3 -1
- package/src/pages/product-details/bopis/find-in-store-modal.tsx +4 -4
- package/src/pages/product-details/bopis/pickup-option-select.tsx +2 -2
- package/src/pages/product-details/bopis/search-header.tsx +2 -2
- package/src/pages/product-details/product-description.tsx +25 -13
- package/src/pages/product-details/product-intro/compound/context.ts +8 -3
- package/src/pages/product-details/product-intro/compound/product-info.tsx +25 -19
- package/src/pages/product-details/product-intro/compound/product-intro-images.tsx +26 -8
- package/src/pages/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +17 -21
- package/src/pages/product-details/product-tabs.tsx +20 -4
- package/src/pages/product-list/product-list-view.tsx +2 -1
- package/src/providers/nav-provider/index.tsx +1 -1
- package/src/providers/nav-provider/utils/createLink.ts +1 -1
- package/tsconfig.json +1 -2
- package/__mocks__/countries.ts +0 -11
- package/__mocks__/create-mutation.ts +0 -68
- package/__mocks__/create-query.ts +0 -94
- package/__mocks__/data/categories.json +0 -795
- package/__mocks__/get-product-by-id.ts +0 -6
- package/__mocks__/next-auth-react.ts +0 -9
- package/__mocks__/next-font.js +0 -4
- package/__mocks__/next-headers.js +0 -13
- package/__mocks__/next-image.tsx +0 -18
- package/__mocks__/next-link.js +0 -19
- package/__mocks__/next-navigation.ts +0 -29
- package/__mocks__/product-dimensions.ts +0 -635
- package/__mocks__/products.ts +0 -533
- package/__mocks__/query-client.ts +0 -3
- package/__mocks__/wishlists.json +0 -408
- package/src/cms/blog.ts +0 -229
- package/src/cms/interfaces/navigation.ts +0 -52
- package/src/cms/interfaces/product-data.ts +0 -83
- package/src/cms/pages.ts +0 -149
|
@@ -7,6 +7,9 @@ import { Heading } from "@/cms/blocks/components/footer/styles/index";
|
|
|
7
7
|
interface Props {
|
|
8
8
|
isDark?: boolean;
|
|
9
9
|
title?: string;
|
|
10
|
+
address?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
phoneNumber?: string;
|
|
10
13
|
social?: SocialLink[];
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -15,17 +18,23 @@ const AboutLinks = (props: Props) => {
|
|
|
15
18
|
<Grid size={{ lg: 3, md: 6, sm: 6, xs: 12 }}>
|
|
16
19
|
<Heading>Contact Us</Heading>
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
{props.address && (
|
|
22
|
+
<Paragraph py={0.6} color="grey.500">
|
|
23
|
+
{props.address}
|
|
24
|
+
</Paragraph>
|
|
25
|
+
)}
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
{props.email && (
|
|
28
|
+
<Paragraph py={0.6} color="grey.500">
|
|
29
|
+
Email: {props.email}
|
|
30
|
+
</Paragraph>
|
|
31
|
+
)}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
{props.phoneNumber && (
|
|
34
|
+
<Paragraph py={0.6} mb={2} color="grey.500">
|
|
35
|
+
Phone: {props.phoneNumber}
|
|
36
|
+
</Paragraph>
|
|
37
|
+
)}
|
|
29
38
|
|
|
30
39
|
<SocialLinks socials={props.social} />
|
|
31
40
|
</Grid>
|
|
@@ -2,20 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
import SiteLogo from "@evenicanpm/storefront-core/src/components/site-logo";
|
|
4
4
|
import { Paragraph } from "@evenicanpm/storefront-core/src/components/Typography";
|
|
5
|
+
import { Box } from "@mui/material";
|
|
5
6
|
import Grid from "@mui/material/Grid2";
|
|
6
7
|
import useTheme from "@mui/material/styles/useTheme";
|
|
7
8
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
9
|
+
import cmsEndPoints from "@/cms/endpoints";
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
12
|
+
logoImage?: { url: string; alternativeText?: string };
|
|
10
13
|
description: string;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export default function LogoSection(props: Readonly<Props>) {
|
|
14
17
|
const theme = useTheme();
|
|
15
|
-
const downMd = useMediaQuery(theme.breakpoints.down(1150));
|
|
18
|
+
const downMd = useMediaQuery(theme.breakpoints.down(1150));
|
|
19
|
+
const logoHeight = downMd ? "2.125rem" : "3rem";
|
|
20
|
+
|
|
16
21
|
return (
|
|
17
22
|
<Grid size={{ lg: 3, md: 6, sm: 6, xs: 12 }}>
|
|
18
|
-
|
|
23
|
+
{props.logoImage?.url ? (
|
|
24
|
+
<Box mb={1}>
|
|
25
|
+
<img
|
|
26
|
+
src={cmsEndPoints.buildCmsMediaUrl(props.logoImage.url)}
|
|
27
|
+
alt={props.logoImage.alternativeText || "Logo"}
|
|
28
|
+
style={{ height: logoHeight, width: "auto" }}
|
|
29
|
+
/>
|
|
30
|
+
</Box>
|
|
31
|
+
) : (
|
|
32
|
+
<SiteLogo {...(downMd ? { height: "2.125rem" } : {})} />
|
|
33
|
+
)}
|
|
19
34
|
|
|
20
35
|
<Paragraph mb={2.5} color="grey.500">
|
|
21
36
|
{props.description}
|
package/src/cms/endpoints.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Formats } from "@/cms/interfaces/product-data";
|
|
2
|
-
|
|
3
1
|
const apiKeys = {
|
|
4
2
|
pages: "/cms/pages",
|
|
5
3
|
blogs: "/cms/blogs",
|
|
@@ -42,6 +40,13 @@ export const extendCmsEndpoints = (
|
|
|
42
40
|
|
|
43
41
|
export type sizes = "small" | "medium" | "large" | "thumbnail";
|
|
44
42
|
|
|
43
|
+
interface Formats {
|
|
44
|
+
small?: { url: string };
|
|
45
|
+
medium?: { url: string };
|
|
46
|
+
large?: { url: string };
|
|
47
|
+
thumbnail?: { url: string };
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
/**
|
|
46
51
|
* @param formats formats array from CMS data
|
|
47
52
|
* @param size size of the image
|
|
@@ -6,9 +6,11 @@ import clsx from "clsx";
|
|
|
6
6
|
|
|
7
7
|
// ==============================================================
|
|
8
8
|
type Ellipsis = { ellipsis: number };
|
|
9
|
-
export
|
|
9
|
+
export type EllipsisProps = BoxProps & {
|
|
10
10
|
ellipsis?: boolean;
|
|
11
|
-
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
12
14
|
// ==============================================================
|
|
13
15
|
|
|
14
16
|
const StyledBox = styled(Box, {
|
|
@@ -15,7 +15,7 @@ vi.mock("next/link", () => ({
|
|
|
15
15
|
describe("SiteLogo", () => {
|
|
16
16
|
it("renders logo image", () => {
|
|
17
17
|
render(<SiteLogo />);
|
|
18
|
-
const logo = screen.getByAltText("
|
|
18
|
+
const logo = screen.getByAltText("e4 Logo");
|
|
19
19
|
expect(logo).toBeInTheDocument();
|
|
20
20
|
// next/image rewrites src to an optimized URL (e.g. /_next/image?url=...)
|
|
21
21
|
expect(logo.getAttribute("src")).toMatch(/logo-blue\.png/);
|
|
@@ -57,6 +57,10 @@ export default function CountryInput({
|
|
|
57
57
|
fullWidth
|
|
58
58
|
value={selectedCountry}
|
|
59
59
|
options={countries}
|
|
60
|
+
getOptionKey={(option) => option.CountryRegionId}
|
|
61
|
+
isOptionEqualToValue={(option, value) =>
|
|
62
|
+
option.CountryRegionId === value?.CountryRegionId
|
|
63
|
+
}
|
|
60
64
|
onChange={(_event, newValue) => {
|
|
61
65
|
setSelectedCountry(newValue);
|
|
62
66
|
}}
|
|
@@ -16,7 +16,7 @@ import T from "@evenicanpm/storefront-core/src/components/T";
|
|
|
16
16
|
import {
|
|
17
17
|
type AccountRoute,
|
|
18
18
|
type AccountRouteOverrides,
|
|
19
|
-
|
|
19
|
+
useCreateAccountRoutes,
|
|
20
20
|
} from "@evenicanpm/storefront-core/src/pages/account/account-routes";
|
|
21
21
|
// UI Components
|
|
22
22
|
import {
|
|
@@ -61,7 +61,7 @@ const routeToMenuItem = (route: AccountRoute) => ({
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
const buildMenuItems = (overrides?: AccountRouteOverrides) => {
|
|
64
|
-
const routes =
|
|
64
|
+
const routes = useCreateAccountRoutes(overrides);
|
|
65
65
|
return [
|
|
66
66
|
routeToMenuItem(routes.profile),
|
|
67
67
|
routeToMenuItem(routes.orders),
|
|
@@ -89,10 +89,9 @@ const UserContext = createContext<UserContextValue | undefined>(undefined);
|
|
|
89
89
|
|
|
90
90
|
interface Props {
|
|
91
91
|
children?: ReactNode;
|
|
92
|
-
routeOverrides?: AccountRouteOverrides;
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
const User = ({ children
|
|
94
|
+
const User = ({ children }: Props) => {
|
|
96
95
|
const { data: session, isPending } = useBetterAuthSession();
|
|
97
96
|
const status = isPending
|
|
98
97
|
? "loading"
|
|
@@ -166,7 +165,7 @@ const User = ({ children, routeOverrides }: Props) => {
|
|
|
166
165
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
167
166
|
const open = Boolean(anchorEl);
|
|
168
167
|
|
|
169
|
-
const menuItems = buildMenuItems(
|
|
168
|
+
const menuItems = buildMenuItems();
|
|
170
169
|
|
|
171
170
|
const value: UserContextValue = {
|
|
172
171
|
isAuthenticated,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { CmsNavItem } from "@evenicanpm/cms/src/content/navigation/types";
|
|
1
2
|
import MobileMenu from "@evenicanpm/storefront-core/src/components/navbar/mobile-menu/mobile-menu";
|
|
2
3
|
import { transformCmsNav } from "@evenicanpm/storefront-core/src/components/navbar/utils/transform-nav";
|
|
3
4
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
5
|
import type React from "react";
|
|
5
6
|
import { beforeEach, describe, it, vi } from "vitest";
|
|
6
|
-
import type { CmsNavItem } from "@/cms/interfaces/navigation";
|
|
7
7
|
|
|
8
8
|
const useContext = vi.fn();
|
|
9
9
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import type { CmsNavItem } from "@evenicanpm/cms/src/content/navigation/types";
|
|
1
2
|
import {
|
|
2
3
|
transformCategoryNav,
|
|
3
4
|
transformCmsNav,
|
|
4
5
|
} from "@evenicanpm/storefront-core/src/components/navbar/utils/transform-nav";
|
|
5
6
|
import type { CategoryHierarchy } from "@evenicanpm/storefront-core/src/lib/category-helpers";
|
|
6
7
|
import { describe, expect, it } from "vitest";
|
|
7
|
-
import type { CmsNavItem } from "@/cms/interfaces/navigation";
|
|
8
8
|
|
|
9
9
|
describe("transform nav items", () => {
|
|
10
10
|
it("should recursively transform deeply nested categories and its children with correct href, title, and items", () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { CmsNavItem } from "@evenicanpm/cms/src/content/navigation/types";
|
|
1
2
|
import type { CategoryHierarchy } from "@evenicanpm/storefront-core/src/lib/category-helpers";
|
|
2
3
|
import type { SvgIconComponent } from "@mui/icons-material";
|
|
3
|
-
import type { CmsNavItem } from "@/cms/interfaces/navigation";
|
|
4
4
|
|
|
5
5
|
type MenuType = "default" | "mega-menu" | "full-screen-mega-menu";
|
|
6
6
|
export interface TransformedNavItem {
|
|
@@ -60,8 +60,11 @@ function findChild(
|
|
|
60
60
|
if (!React.isValidElement(child)) return;
|
|
61
61
|
if (child.type === component) {
|
|
62
62
|
found = true;
|
|
63
|
-
} else if (child.props?.children) {
|
|
64
|
-
found ||= findChild(
|
|
63
|
+
} else if ((child.props as { children?: ReactNode })?.children) {
|
|
64
|
+
found ||= findChild(
|
|
65
|
+
(child.props as { children?: ReactNode }).children,
|
|
66
|
+
component,
|
|
67
|
+
);
|
|
65
68
|
}
|
|
66
69
|
});
|
|
67
70
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// GLOBAL CUSTOM COMPONENT
|
|
2
2
|
import BazaarCard from "@evenicanpm/storefront-core/src/components/BazaarCard";
|
|
3
3
|
import styled from "@mui/material/styles/styled";
|
|
4
|
-
import type { StyledComponent } from "@mui/styled-engine";
|
|
5
4
|
|
|
6
5
|
export const StyledBazaarCard = styled(BazaarCard)({
|
|
7
6
|
height: "100%",
|
|
@@ -46,7 +45,4 @@ export const ContentWrapper = styled("div")({
|
|
|
46
45
|
},
|
|
47
46
|
});
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
StyledBazaarCard as StyledComponent<typeof BazaarCard, object, "div">;
|
|
51
|
-
|
|
52
|
-
export default StyledBazaarCardType;
|
|
48
|
+
export default StyledBazaarCard;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FlexBetween,
|
|
3
|
-
FlexBox,
|
|
4
|
-
} from "@evenicanpm/storefront-core/src/components/flex-box/index";
|
|
1
|
+
import { FlexBetween } from "@evenicanpm/storefront-core/src/components/flex-box/index";
|
|
5
2
|
import ImageWithFallback from "@evenicanpm/storefront-core/src/components/ImageWithFallback";
|
|
6
3
|
// LOCAL CUSTOM COMPONENTS
|
|
7
4
|
import DiscountChip from "@evenicanpm/storefront-core/src/components/product-cards/discount-chip";
|
|
@@ -9,6 +6,7 @@ import FavoriteButton from "@evenicanpm/storefront-core/src/components/product-c
|
|
|
9
6
|
// STYLED COMPONENTS
|
|
10
7
|
import {
|
|
11
8
|
HoverIconWrapper,
|
|
9
|
+
PlpImageWrapper,
|
|
12
10
|
PriceText,
|
|
13
11
|
} from "@evenicanpm/storefront-core/src/components/product-cards/product-card-plp/styles/index";
|
|
14
12
|
import QuantityButtons from "@evenicanpm/storefront-core/src/components/product-cards/quantity-buttons";
|
|
@@ -86,15 +84,9 @@ ProductCardPlp.ImageWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
86
84
|
|
|
87
85
|
return (
|
|
88
86
|
<Link href={buildDetailUrl(title, id)}>
|
|
89
|
-
<
|
|
90
|
-
className="hover-box-container"
|
|
91
|
-
position="relative"
|
|
92
|
-
bgcolor="grey.50"
|
|
93
|
-
borderRadius={3}
|
|
94
|
-
mb={2}
|
|
95
|
-
>
|
|
87
|
+
<PlpImageWrapper className="hover-box-container">
|
|
96
88
|
{children}
|
|
97
|
-
</
|
|
89
|
+
</PlpImageWrapper>
|
|
98
90
|
</Link>
|
|
99
91
|
);
|
|
100
92
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { FlexBox } from "@evenicanpm/storefront-core/src/components/flex-box/index";
|
|
3
4
|
import styled from "@mui/material/styles/styled";
|
|
4
5
|
|
|
5
6
|
export const PriceText = styled("p")(({ theme }) => ({
|
|
@@ -17,6 +18,20 @@ export const PriceText = styled("p")(({ theme }) => ({
|
|
|
17
18
|
},
|
|
18
19
|
}));
|
|
19
20
|
|
|
21
|
+
export const PlpImageWrapper = styled(FlexBox)(({ theme }) => ({
|
|
22
|
+
position: "relative",
|
|
23
|
+
backgroundColor: theme.palette.grey[50],
|
|
24
|
+
borderRadius: theme.shape.borderRadius * 3,
|
|
25
|
+
marginBottom: theme.spacing(2),
|
|
26
|
+
|
|
27
|
+
"& img": {
|
|
28
|
+
maxHeight: 300,
|
|
29
|
+
objectFit: "contain",
|
|
30
|
+
width: "100%",
|
|
31
|
+
borderRadius: 12,
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
20
35
|
export const HoverIconWrapper = styled("div")({
|
|
21
36
|
zIndex: 2,
|
|
22
37
|
top: "7px",
|
|
@@ -52,7 +52,11 @@ export default function ProductsGridView(props: Readonly<ProductsViewProps>) {
|
|
|
52
52
|
* @category Sub-Component
|
|
53
53
|
*
|
|
54
54
|
*/
|
|
55
|
-
ProductsGridView.Grid = ({
|
|
55
|
+
ProductsGridView.Grid = ({
|
|
56
|
+
children,
|
|
57
|
+
}: {
|
|
58
|
+
children: React.ReactElement<Record<string, unknown>>;
|
|
59
|
+
}) => {
|
|
56
60
|
const { products, productPrices } = useProductsView();
|
|
57
61
|
|
|
58
62
|
return (
|
|
@@ -38,7 +38,11 @@ export default function ProductsListView(props: Readonly<ProductsViewProps>) {
|
|
|
38
38
|
* @category Sub-Component
|
|
39
39
|
*
|
|
40
40
|
*/
|
|
41
|
-
ProductsListView.List = ({
|
|
41
|
+
ProductsListView.List = ({
|
|
42
|
+
children,
|
|
43
|
+
}: {
|
|
44
|
+
children: React.ReactElement<Record<string, unknown>>;
|
|
45
|
+
}) => {
|
|
42
46
|
const { products, productPrices } = useProductsView();
|
|
43
47
|
return (
|
|
44
48
|
<>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "@evenicanpm/storefront-core/src/components/wishlist-dialogs/add-to-wishlist/compound/context";
|
|
10
10
|
import UseWishlist from "@evenicanpm/storefront-core/src/components/wishlist-dialogs/add-to-wishlist/hooks/use-add-to-wishlist";
|
|
11
11
|
import { StyledTableRow } from "@evenicanpm/storefront-core/src/components/wishlist-dialogs/styles";
|
|
12
|
+
import { useCreateAccountRoutes } from "@evenicanpm/storefront-core/src/pages/account/account-routes";
|
|
12
13
|
import { Add, Delete } from "@mui/icons-material";
|
|
13
14
|
import { IconButton } from "@mui/material";
|
|
14
15
|
import { format } from "date-fns";
|
|
@@ -25,6 +26,7 @@ export default function WishlistDialogItem() {
|
|
|
25
26
|
|
|
26
27
|
const itemCtx = useContext(WishListItemContext);
|
|
27
28
|
const dialogCtx = useContext(AddToWishListDialogContext);
|
|
29
|
+
const { wishlists: wishlistsRoute } = useCreateAccountRoutes();
|
|
28
30
|
|
|
29
31
|
if (!itemCtx || !dialogCtx)
|
|
30
32
|
throw new Error("WishlistModalItem must be used within a wishlist context");
|
|
@@ -59,7 +61,7 @@ export default function WishlistDialogItem() {
|
|
|
59
61
|
}}
|
|
60
62
|
>
|
|
61
63
|
<Paragraph ellipsis sx={{ fontWeight: "bold" }}>
|
|
62
|
-
<Link href={
|
|
64
|
+
<Link href={`${wishlistsRoute.href}/${wishlist?.Id}`}>
|
|
63
65
|
{wishlist?.Name ?? ""}
|
|
64
66
|
</Link>
|
|
65
67
|
</Paragraph>
|
package/src/global.d.ts
ADDED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
type AccountRoute,
|
|
14
14
|
type AccountRouteOverrides,
|
|
15
|
-
|
|
15
|
+
useCreateAccountRoutes,
|
|
16
16
|
} from "@evenicanpm/storefront-core/src/pages/account/account-routes";
|
|
17
17
|
// STYLED COMPONENTS
|
|
18
18
|
import {
|
|
@@ -85,7 +85,7 @@ const routeToNavItem = (route: AccountRoute): NavItem => {
|
|
|
85
85
|
export const DEFAULT_MENU_ITEMS = (
|
|
86
86
|
overrides?: AccountRouteOverrides,
|
|
87
87
|
): NavSection[] => {
|
|
88
|
-
const routes =
|
|
88
|
+
const routes = useCreateAccountRoutes(overrides);
|
|
89
89
|
return [
|
|
90
90
|
{
|
|
91
91
|
title: "DASHBOARD",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
1
2
|
import type { IconType } from "react-icons";
|
|
2
3
|
import {
|
|
3
4
|
MdFavoriteBorder as FavoriteBorder,
|
|
@@ -44,9 +45,42 @@ const defaults = {
|
|
|
44
45
|
},
|
|
45
46
|
} as const satisfies Record<string, AccountRoute>;
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Context for overriding account route configuration.
|
|
50
|
+
* Allows parent providers to customize route paths and title keys
|
|
51
|
+
* without modifying the default route definitions.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* <RoutesOverrideContext.Provider value={{ wishlists: { href: "/account/order-templates", titleKey: "orderTemplates" } }}>
|
|
56
|
+
* {children}
|
|
57
|
+
* </RoutesOverrideContext.Provider>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const RoutesOverrideContext = createContext<
|
|
61
|
+
AccountRouteOverrides | undefined
|
|
62
|
+
>(undefined);
|
|
63
|
+
|
|
47
64
|
export type AccountRouteKey = keyof typeof defaults;
|
|
48
65
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Hook that builds the account routes map, merging defaults with context-level
|
|
68
|
+
* and call-site overrides. Override priority (highest wins):
|
|
69
|
+
* 1. `overrides` parameter passed directly to the hook
|
|
70
|
+
* 2. Values from {@link RoutesOverrideContext}
|
|
71
|
+
* 3. Built-in defaults
|
|
72
|
+
*
|
|
73
|
+
* @param overrides - Optional route overrides applied at the call site
|
|
74
|
+
* @returns The full set of account routes with any overrides merged in
|
|
75
|
+
*/
|
|
76
|
+
export const useCreateAccountRoutes = (overrides?: AccountRouteOverrides) => {
|
|
77
|
+
const ctxOverrides = useContext(RoutesOverrideContext);
|
|
78
|
+
return {
|
|
79
|
+
...defaults,
|
|
80
|
+
wishlists: {
|
|
81
|
+
...defaults.wishlists,
|
|
82
|
+
...ctxOverrides?.wishlists,
|
|
83
|
+
...overrides?.wishlists,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -11,6 +11,7 @@ import type { CountryRegionInfo } from "@msdyn365-commerce/retail-proxy";
|
|
|
11
11
|
import {
|
|
12
12
|
Box,
|
|
13
13
|
Button,
|
|
14
|
+
CircularProgress,
|
|
14
15
|
FormControl,
|
|
15
16
|
InputLabel,
|
|
16
17
|
MenuItem,
|
|
@@ -25,7 +26,7 @@ import { withZodSchema } from "formik-validator-zod";
|
|
|
25
26
|
import _ from "lodash";
|
|
26
27
|
import { useRouter } from "next/navigation";
|
|
27
28
|
import { useLocale, useTranslations } from "next-intl";
|
|
28
|
-
import React, { createContext, useContext } from "react";
|
|
29
|
+
import React, { createContext, useContext, useRef } from "react";
|
|
29
30
|
import { z } from "zod";
|
|
30
31
|
|
|
31
32
|
interface AddressFormValues {
|
|
@@ -71,6 +72,7 @@ const AddressForm = ({ address, children }: RootProps) => {
|
|
|
71
72
|
|
|
72
73
|
const { mutateAsync: createAddress } = useCreateAddress();
|
|
73
74
|
const { mutateAsync: updateAddress } = useUpdateAddress();
|
|
75
|
+
const isSubmittingRef = useRef(false);
|
|
74
76
|
|
|
75
77
|
const FormSchema = z.object({
|
|
76
78
|
Name: z.string().nonempty(t("Validation.required", { field: t("name") })),
|
|
@@ -99,6 +101,9 @@ const AddressForm = ({ address, children }: RootProps) => {
|
|
|
99
101
|
};
|
|
100
102
|
|
|
101
103
|
const handleSubmit = async (values: AddressFormValues) => {
|
|
104
|
+
if (isSubmittingRef.current) return;
|
|
105
|
+
isSubmittingRef.current = true;
|
|
106
|
+
|
|
102
107
|
let newAddress: Address = {
|
|
103
108
|
Name: values.Name,
|
|
104
109
|
Street: values.Street,
|
|
@@ -128,6 +133,7 @@ const AddressForm = ({ address, children }: RootProps) => {
|
|
|
128
133
|
severity: "error",
|
|
129
134
|
});
|
|
130
135
|
console.log(error);
|
|
136
|
+
isSubmittingRef.current = false;
|
|
131
137
|
}
|
|
132
138
|
};
|
|
133
139
|
|
|
@@ -291,8 +297,20 @@ AddressForm.Actions = ({ children }: { children?: React.ReactNode }) => {
|
|
|
291
297
|
|
|
292
298
|
AddressForm.SubmitButton = () => {
|
|
293
299
|
const t = useTranslations("Address");
|
|
300
|
+
const ctx = useContext(FormContext);
|
|
301
|
+
const isSubmitting = ctx?.formik.isSubmitting ?? false;
|
|
294
302
|
return (
|
|
295
|
-
<Button
|
|
303
|
+
<Button
|
|
304
|
+
type="submit"
|
|
305
|
+
variant="contained"
|
|
306
|
+
color="primary"
|
|
307
|
+
disabled={isSubmitting}
|
|
308
|
+
startIcon={
|
|
309
|
+
isSubmitting ? (
|
|
310
|
+
<CircularProgress size={20} color="inherit" />
|
|
311
|
+
) : undefined
|
|
312
|
+
}
|
|
313
|
+
>
|
|
296
314
|
{t("save")}
|
|
297
315
|
</Button>
|
|
298
316
|
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import TableRow from "@evenicanpm/storefront-core/src/pages/account/table-row";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import Skeleton from "@mui/material/Skeleton";
|
|
4
|
+
import type { SxProps, Theme } from "@mui/material/styles";
|
|
5
|
+
|
|
6
|
+
interface TableRowSkeletonProps {
|
|
7
|
+
sx?: SxProps<Theme>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function TableRowSkeleton({
|
|
11
|
+
sx = { gridTemplateColumns: "2fr 1fr 1fr 1fr 1fr" },
|
|
12
|
+
}: TableRowSkeletonProps) {
|
|
13
|
+
return (
|
|
14
|
+
<TableRow sx={sx}>
|
|
15
|
+
<Skeleton variant="text" width="60%" />
|
|
16
|
+
<Box textAlign="center">
|
|
17
|
+
<Skeleton
|
|
18
|
+
variant="rounded"
|
|
19
|
+
width={70}
|
|
20
|
+
height={24}
|
|
21
|
+
sx={{ mx: "auto" }}
|
|
22
|
+
/>
|
|
23
|
+
</Box>
|
|
24
|
+
<Skeleton variant="text" width="70%" sx={{ mx: { sm: "auto" } }} />
|
|
25
|
+
<Skeleton variant="text" width="50%" sx={{ mx: { sm: "auto" } }} />
|
|
26
|
+
<Box display={{ sm: "inline-flex", xs: "none" }} justifyContent="end">
|
|
27
|
+
<Skeleton variant="circular" width={32} height={32} />
|
|
28
|
+
</Box>
|
|
29
|
+
</TableRow>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -16,6 +16,7 @@ import { format } from "date-fns";
|
|
|
16
16
|
import { useTranslations } from "next-intl";
|
|
17
17
|
import { createContext, type ReactNode, useContext } from "react";
|
|
18
18
|
import { MdDelete as Delete } from "react-icons/md";
|
|
19
|
+
import { useCreateAccountRoutes } from "../account-routes";
|
|
19
20
|
|
|
20
21
|
interface WishlistItemContextValue {
|
|
21
22
|
wishlist: Wishlist;
|
|
@@ -73,13 +74,14 @@ const WishlistItem = ({ wishlist, children, sx }: RootProps) => {
|
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
WishlistItem.Name = () => {
|
|
77
|
+
const { wishlists: wishlistsRoute } = useCreateAccountRoutes();
|
|
76
78
|
const ctx = useContext(WishlistItemContext);
|
|
77
79
|
if (!ctx) return null;
|
|
78
80
|
const { wishlist } = ctx;
|
|
79
81
|
return (
|
|
80
82
|
<Paragraph ellipsis>
|
|
81
83
|
<NavLink3
|
|
82
|
-
href={
|
|
84
|
+
href={`${wishlistsRoute.href}/${wishlist?.Id}`}
|
|
83
85
|
text={wishlist?.Name ?? ""}
|
|
84
86
|
/>
|
|
85
87
|
</Paragraph>
|
|
@@ -7,7 +7,7 @@ import { useSuspenseQuery } from "@tanstack/react-query";
|
|
|
7
7
|
import Link from "next/link";
|
|
8
8
|
import { cmsQueryOptions } from "../../cms/queries";
|
|
9
9
|
import { BorderShadowCard } from "../../components/BoxShadowCard";
|
|
10
|
-
import { Display, Subtitle } from "../../components/Typography";
|
|
10
|
+
import { Display, Paragraph, Subtitle } from "../../components/Typography";
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
id: string;
|
|
@@ -29,6 +29,7 @@ export const BlogDetailView = ({ id, locale, cmsLocale }: Props) => {
|
|
|
29
29
|
const title = blogData?.title;
|
|
30
30
|
const shortDescription = blogData?.shortDescription;
|
|
31
31
|
const content = blogData?.content;
|
|
32
|
+
const author = blogData?.author;
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
35
|
<Container className="mt-2 mb-2" maxWidth="xl">
|
|
@@ -128,6 +129,7 @@ export const BlogDetailView = ({ id, locale, cmsLocale }: Props) => {
|
|
|
128
129
|
</Grid>
|
|
129
130
|
<Grid size={{ xs: 12, md: 3 }} order={{ xs: 1, md: 2 }}>
|
|
130
131
|
<Box sx={{ paddingBottom: "15px" }}>
|
|
132
|
+
{author && <Paragraph sx={noSpaceSx}>{author}</Paragraph>}
|
|
131
133
|
<Subtitle sx={noSpaceSx}>Published on: {publishDate}</Subtitle>
|
|
132
134
|
</Box>
|
|
133
135
|
</Grid>
|
|
@@ -416,13 +416,6 @@ CartItemRoot.Comment = () => {
|
|
|
416
416
|
</FlexBox>
|
|
417
417
|
</>
|
|
418
418
|
);
|
|
419
|
-
} else if (isLoading) {
|
|
420
|
-
content = (
|
|
421
|
-
<CircularProgress
|
|
422
|
-
size={30}
|
|
423
|
-
sx={{ marginTop: "auto", marginLeft: "auto" }}
|
|
424
|
-
/>
|
|
425
|
-
);
|
|
426
419
|
} else {
|
|
427
420
|
content = (
|
|
428
421
|
<>
|
|
@@ -448,6 +441,7 @@ CartItemRoot.Comment = () => {
|
|
|
448
441
|
<Button
|
|
449
442
|
color="primary"
|
|
450
443
|
variant="outlined"
|
|
444
|
+
disabled={isLoading}
|
|
451
445
|
sx={{ width: 150, mt: "auto", ml: "auto" }}
|
|
452
446
|
onClick={() => setIsWritingComment(true)}
|
|
453
447
|
>
|