@evenicanpm/storefront-core 2.2.0 → 2.3.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/package.json +7 -3
- package/src/api-manager/README.md +52 -27
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +166 -17
- package/src/api-manager/datasources/d365/d365-invoice.datasource.ts +100 -0
- package/src/api-manager/datasources/d365/d365-order.datasource.ts +75 -18
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +19 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +125 -36
- package/src/api-manager/datasources/d365/d365.datasource.ts +3 -0
- package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +10 -3
- package/src/api-manager/datasources/e4/e4.datasource.ts +9 -0
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +7 -0
- package/src/api-manager/datasources/e4/order/e4-order.datasource.ts +7 -4
- package/src/api-manager/datasources/e4/user/e4-user.datasource.ts +4 -0
- package/src/api-manager/index.ts +54 -3
- package/src/api-manager/lib/get-graphql-client.ts +11 -5
- package/src/api-manager/schemas/cart.schema.ts +10 -1
- package/src/api-manager/schemas/invoice.schema.ts +55 -0
- package/src/api-manager/schemas/order.schema.ts +13 -1
- package/src/api-manager/schemas/user.schema.ts +8 -0
- package/src/api-manager/services/create-query.ts +4 -10
- package/src/api-manager/services/invoice/queries/get-invoice-details.ts +18 -0
- package/src/api-manager/services/invoice/queries/get-invoices.ts +18 -0
- package/src/api-manager/services/order/queries/get-order-details.ts +1 -1
- package/src/api-manager/services/order/queries/get-orders.ts +6 -3
- package/src/api-manager/services/user/queries/get-customer-balance.ts +20 -0
- package/src/api-manager/types/Datasource.ts +21 -2
- package/src/auth/better-auth.ts +1 -1
- package/src/cms/blocks/block-manager.tsx +18 -3
- package/src/cms/blocks/components/cta-banner.tsx +65 -0
- 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/blocks/components/hero-image.tsx +83 -0
- package/src/cms/blocks/index.tsx +2 -0
- package/src/cms/blocks/interfaces.ts +20 -0
- package/src/cms/draft-mode-badge.tsx +26 -0
- package/src/cms/queries.ts +81 -0
- package/src/components/_tests_/site-logo.test.tsx +1 -1
- package/src/components/flex-box/flex-box.tsx +2 -0
- package/src/components/header/components/user.tsx +35 -11
- package/src/components/header/sticky-header.tsx +1 -0
- package/src/components/mini-cart/mini-cart.tsx +3 -3
- 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/site-logo.tsx +1 -1
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-header.tsx +1 -0
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-item.tsx +3 -1
- package/src/pages/account/account-navigation.tsx +88 -60
- package/src/pages/account/account-routes.ts +86 -0
- package/src/pages/account/orders/order-history-filter-types.tsx +26 -0
- package/src/pages/account/orders/order-history-filters-panel.tsx +375 -0
- package/src/pages/account/orders/order-history-header.tsx +54 -0
- package/src/pages/account/orders/order-history-pagination.tsx +55 -0
- package/src/pages/account/orders/order-history-root.tsx +125 -0
- package/src/pages/account/orders/order-history-row.tsx +110 -0
- package/src/pages/account/orders/order-history-search-and-filter.tsx +449 -0
- package/src/pages/account/orders/order-history-search-bar.tsx +84 -0
- package/src/pages/account/orders/order-history-search-dropdown.tsx +53 -0
- package/src/pages/account/orders/order-history-sort.tsx +90 -0
- package/src/pages/account/orders/order-history-table.tsx +54 -0
- package/src/pages/account/orders/order-row.tsx +2 -2
- package/src/pages/account/orders/orders-drop-down-handler.tsx +19 -0
- package/src/pages/account/table-header.tsx +20 -0
- package/src/pages/account/table-row-skeleton.tsx +31 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +4 -1
- package/src/pages/blog/blog-card.tsx +9 -3
- package/src/pages/blog/blog-detail-view.tsx +152 -0
- package/src/pages/blog/blog-list-view.tsx +59 -0
- package/src/pages/cart/estimate-shipping.tsx +6 -5
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +43 -21
- package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +144 -19
- package/src/pages/cms-page-view.tsx +53 -0
- package/src/pages/product-details/product-description.tsx +25 -13
- 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/quickorder/order-upload.tsx +12 -6
- package/src/pages/quickorder/quick-order.tsx +25 -20
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THIS IS A REACT-QUERY WRAPPER LAYER AROUND THE CMS SDK.
|
|
3
|
+
* IT PROVIDES TYPING AND STRUCTURE TO THE QUERIES USED THROUGHOUT THE APP, AND
|
|
4
|
+
* IT ALSO PROVIDES A SINGLE PLACE TO CONFIGURE QUERY OPTIONS FOR CMS DATA.
|
|
5
|
+
*/
|
|
6
|
+
import { cms } from "@evenicanpm/cms";
|
|
7
|
+
import type { Blog } from "@evenicanpm/cms/src/content/blogs/types";
|
|
8
|
+
import type { Page } from "@evenicanpm/cms/src/content/pages/types";
|
|
9
|
+
import { queryOptions } from "@tanstack/react-query";
|
|
10
|
+
import type { StrapiResponse } from "strapi-sdk-js";
|
|
11
|
+
|
|
12
|
+
// strapi-sdk-js's StrapiResponseMetaPagination is missing pageCount, which Strapi
|
|
13
|
+
// includes when queries use withCount:true. This extends the meta to include it.
|
|
14
|
+
type StrapiPagedResponse<T> = StrapiResponse<T> & {
|
|
15
|
+
meta: {
|
|
16
|
+
pagination: {
|
|
17
|
+
page: number;
|
|
18
|
+
pageSize: number;
|
|
19
|
+
pageCount: number;
|
|
20
|
+
total: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const cmsQueryKeys = {
|
|
26
|
+
navigation: () => ["cms", "navigation"] as const,
|
|
27
|
+
page: (
|
|
28
|
+
slug: string,
|
|
29
|
+
status: "draft" | "published" = "published",
|
|
30
|
+
locale?: string,
|
|
31
|
+
) => ["cms", "pages", slug, status, locale] as const,
|
|
32
|
+
blogs: (page: number, pageSize: number, locale?: string) =>
|
|
33
|
+
["cms", "blogs", page, pageSize, locale] as const,
|
|
34
|
+
blog: (id: string, locale?: string) => ["cms", "blog", id, locale] as const,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const cmsQueryOptions = {
|
|
38
|
+
navigation: () =>
|
|
39
|
+
queryOptions({
|
|
40
|
+
queryKey: cmsQueryKeys.navigation(),
|
|
41
|
+
queryFn: (): Promise<unknown> => cms.navigation.getMainNavigation(),
|
|
42
|
+
}),
|
|
43
|
+
page: (
|
|
44
|
+
slug: string,
|
|
45
|
+
status: "draft" | "published" = "published",
|
|
46
|
+
locale?: string,
|
|
47
|
+
) =>
|
|
48
|
+
queryOptions({
|
|
49
|
+
queryKey: cmsQueryKeys.page(slug, status, locale),
|
|
50
|
+
queryFn: (): Promise<StrapiResponse<Page[]>> =>
|
|
51
|
+
(
|
|
52
|
+
cms.pages.getPage as (opts: {
|
|
53
|
+
slug: string;
|
|
54
|
+
status?: "draft" | "published";
|
|
55
|
+
locale?: string;
|
|
56
|
+
}) => Promise<StrapiResponse<Page[]>>
|
|
57
|
+
)({ slug, status, locale }),
|
|
58
|
+
}),
|
|
59
|
+
blogs: (page: number, pageSize: number, locale?: string) =>
|
|
60
|
+
queryOptions({
|
|
61
|
+
queryKey: cmsQueryKeys.blogs(page, pageSize, locale),
|
|
62
|
+
queryFn: (): Promise<StrapiPagedResponse<Blog[]>> =>
|
|
63
|
+
(
|
|
64
|
+
cms.blogs.getBlogs as (opts: {
|
|
65
|
+
pagination: { page: number; pageSize: number };
|
|
66
|
+
locale?: string;
|
|
67
|
+
}) => Promise<StrapiPagedResponse<Blog[]>>
|
|
68
|
+
)({ pagination: { page, pageSize }, locale }),
|
|
69
|
+
}),
|
|
70
|
+
blog: (id: string, locale?: string) =>
|
|
71
|
+
queryOptions({
|
|
72
|
+
queryKey: cmsQueryKeys.blog(id, locale),
|
|
73
|
+
queryFn: (): Promise<StrapiResponse<Blog>> =>
|
|
74
|
+
(
|
|
75
|
+
cms.blogs.getBlog as (opts: {
|
|
76
|
+
id: string;
|
|
77
|
+
locale?: string;
|
|
78
|
+
}) => Promise<StrapiResponse<Blog>>
|
|
79
|
+
)({ id, locale }),
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
@@ -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/);
|
|
@@ -4,6 +4,7 @@ import useSessionInit from "@evenicanpm/storefront-core/src/api-manager/services
|
|
|
4
4
|
import useSessionLogout from "@evenicanpm/storefront-core/src/api-manager/services/session/mutations/session-logout";
|
|
5
5
|
// API hooks
|
|
6
6
|
import useUpdateUser from "@evenicanpm/storefront-core/src/api-manager/services/user/mutations/update-user";
|
|
7
|
+
import getUser from "@evenicanpm/storefront-core/src/api-manager/services/user/queries/get-user";
|
|
7
8
|
import {
|
|
8
9
|
getMsalInstance,
|
|
9
10
|
initializeMsal,
|
|
@@ -12,6 +13,11 @@ import signOutCustom from "@evenicanpm/storefront-core/src/auth/signout";
|
|
|
12
13
|
import MobileUserIcon from "@evenicanpm/storefront-core/src/components/icons/user";
|
|
13
14
|
import { NavLink } from "@evenicanpm/storefront-core/src/components/nav-link";
|
|
14
15
|
import T from "@evenicanpm/storefront-core/src/components/T";
|
|
16
|
+
import {
|
|
17
|
+
type AccountRoute,
|
|
18
|
+
type AccountRouteOverrides,
|
|
19
|
+
useCreateAccountRoutes,
|
|
20
|
+
} from "@evenicanpm/storefront-core/src/pages/account/account-routes";
|
|
15
21
|
// UI Components
|
|
16
22
|
import {
|
|
17
23
|
Box,
|
|
@@ -21,15 +27,16 @@ import {
|
|
|
21
27
|
ListItemButton,
|
|
22
28
|
Popover,
|
|
23
29
|
} from "@mui/material";
|
|
24
|
-
import {
|
|
30
|
+
import { genericOAuthClient } from "better-auth/client/plugins";
|
|
25
31
|
// Auth components
|
|
26
32
|
import { createAuthClient } from "better-auth/react";
|
|
27
|
-
import {
|
|
33
|
+
import { useRouter } from "next/navigation";
|
|
28
34
|
|
|
29
35
|
const authClient = createAuthClient({
|
|
30
36
|
plugins: [genericOAuthClient()],
|
|
31
37
|
});
|
|
32
38
|
const { useSession: useBetterAuthSession } = authClient;
|
|
39
|
+
|
|
33
40
|
import type React from "react";
|
|
34
41
|
import {
|
|
35
42
|
createContext,
|
|
@@ -46,17 +53,30 @@ interface IdTokenClaims {
|
|
|
46
53
|
family_name?: string;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
const routeToMenuItem = (route: AccountRoute) => ({
|
|
57
|
+
href: route.href,
|
|
58
|
+
label: `Account.Menu.${route.titleKey}`,
|
|
59
|
+
Icon: route.Icon,
|
|
60
|
+
count: route.count,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const buildMenuItems = (overrides?: AccountRouteOverrides) => {
|
|
64
|
+
const routes = useCreateAccountRoutes(overrides);
|
|
65
|
+
return [
|
|
66
|
+
routeToMenuItem(routes.profile),
|
|
67
|
+
routeToMenuItem(routes.orders),
|
|
68
|
+
routeToMenuItem(routes.wishlists),
|
|
69
|
+
routeToMenuItem(routes.invoices),
|
|
70
|
+
routeToMenuItem(routes.addresses),
|
|
71
|
+
];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type MenuItem = { href: string; label: string };
|
|
55
75
|
|
|
56
76
|
interface UserContextValue {
|
|
57
77
|
isAuthenticated: boolean;
|
|
58
78
|
status: string;
|
|
59
|
-
menuItems:
|
|
79
|
+
menuItems: MenuItem[];
|
|
60
80
|
handleLogout: () => Promise<void>;
|
|
61
81
|
handleSignIn: () => void;
|
|
62
82
|
signOut: () => Promise<void>;
|
|
@@ -87,6 +107,8 @@ const User = ({ children }: Props) => {
|
|
|
87
107
|
const { mutateAsync: sessionInit } = useSessionInit();
|
|
88
108
|
const { mutateAsync: updateUser } = useUpdateUser();
|
|
89
109
|
const { mutateAsync: sessionLogout } = useSessionLogout();
|
|
110
|
+
// Register the "me" query on the client so the hydrated cache entry has a queryFn for refetches
|
|
111
|
+
getUser.useData();
|
|
90
112
|
|
|
91
113
|
// session init
|
|
92
114
|
useEffect(() => {
|
|
@@ -143,10 +165,12 @@ const User = ({ children }: Props) => {
|
|
|
143
165
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
144
166
|
const open = Boolean(anchorEl);
|
|
145
167
|
|
|
168
|
+
const menuItems = buildMenuItems();
|
|
169
|
+
|
|
146
170
|
const value: UserContextValue = {
|
|
147
171
|
isAuthenticated,
|
|
148
172
|
status,
|
|
149
|
-
menuItems
|
|
173
|
+
menuItems,
|
|
150
174
|
handleLogout,
|
|
151
175
|
handleSignIn,
|
|
152
176
|
signOut: signOutCustom,
|
|
@@ -198,7 +222,7 @@ User.DesktopTrigger = function DesktopTrigger() {
|
|
|
198
222
|
User.Menu = function Menu({
|
|
199
223
|
children,
|
|
200
224
|
}: {
|
|
201
|
-
children?: (items:
|
|
225
|
+
children?: (items: MenuItem[]) => React.ReactNode;
|
|
202
226
|
}) {
|
|
203
227
|
const ctx = useContext(UserContext);
|
|
204
228
|
if (!ctx) return null;
|
|
@@ -8,6 +8,7 @@ import { Sticky } from "@evenicanpm/ui";
|
|
|
8
8
|
import useTheme from "@mui/material/styles/useTheme";
|
|
9
9
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
10
10
|
import { useCallback, useState } from "react";
|
|
11
|
+
import { AccountRouteOverrides } from "../../pages/account/account-routes";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @category Implementation
|
|
@@ -121,8 +121,8 @@ MiniCart.Items = () => {
|
|
|
121
121
|
} = ctx;
|
|
122
122
|
|
|
123
123
|
return (
|
|
124
|
-
<Box height={`calc(100vh - ${cart?.CartLines
|
|
125
|
-
{cart
|
|
124
|
+
<Box height={`calc(100vh - ${cart?.CartLines?.length ? "207px" : "75px"})`}>
|
|
125
|
+
{cart?.CartLines?.length > 0 ? (
|
|
126
126
|
<Scrollbar>
|
|
127
127
|
{cart?.CartLines?.map((item) => (
|
|
128
128
|
<MiniCartItem
|
|
@@ -153,7 +153,7 @@ MiniCart.Actions = () => {
|
|
|
153
153
|
const ctx = useContext(MiniCartContext);
|
|
154
154
|
if (!ctx) return null;
|
|
155
155
|
const { cart, currency, handleNavigate } = ctx;
|
|
156
|
-
return cart?.CartLines
|
|
156
|
+
return cart?.CartLines?.length > 0 ? (
|
|
157
157
|
<BottomActions
|
|
158
158
|
total={currency(cart?.TotalAmount || 0)}
|
|
159
159
|
handleNavigate={handleNavigate}
|
|
@@ -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",
|
|
@@ -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>
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
Paragraph,
|
|
10
10
|
Span,
|
|
11
11
|
} from "@evenicanpm/storefront-core/src/components/Typography";
|
|
12
|
+
import {
|
|
13
|
+
type AccountRoute,
|
|
14
|
+
type AccountRouteOverrides,
|
|
15
|
+
useCreateAccountRoutes,
|
|
16
|
+
} from "@evenicanpm/storefront-core/src/pages/account/account-routes";
|
|
12
17
|
// STYLED COMPONENTS
|
|
13
18
|
import {
|
|
14
19
|
MainContainer,
|
|
@@ -17,88 +22,114 @@ import {
|
|
|
17
22
|
import { usePathname } from "next/navigation";
|
|
18
23
|
import { useTranslations } from "next-intl";
|
|
19
24
|
import { createContext, Fragment, type ReactNode } from "react";
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
MdFavoriteBorder as FavoriteBorder,
|
|
23
|
-
MdPerson as Person,
|
|
24
|
-
MdPlace as Place,
|
|
25
|
-
MdOutlineShoppingBag as ShoppingBagOutlined,
|
|
26
|
-
} from "react-icons/md";
|
|
25
|
+
import type { IconType } from "react-icons";
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
*
|
|
30
29
|
*** Extend menu:
|
|
31
30
|
<AccountNavigation
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
items={[
|
|
32
|
+
...DEFAULT_MENU_ITEMS,
|
|
33
|
+
{
|
|
34
|
+
title: "EXTRAS",
|
|
35
|
+
list: [
|
|
36
|
+
{
|
|
37
|
+
href: "/account/rewards",
|
|
38
|
+
title: "rewards",
|
|
39
|
+
Icon: RewardsIcon,
|
|
40
|
+
count: 5,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
]}
|
|
46
45
|
/>;
|
|
47
46
|
|
|
48
47
|
*** Override completely:
|
|
49
48
|
<AccountNavigation>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
<AccountNavigation.Section title="Custom Section">
|
|
50
|
+
<AccountNavigation.Item
|
|
51
|
+
href="/custom"
|
|
52
|
+
Icon={CustomIcon}
|
|
53
|
+
label="Custom Page"
|
|
54
|
+
/>
|
|
55
|
+
</AccountNavigation.Section>
|
|
57
56
|
</AccountNavigation>;
|
|
58
57
|
*/
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
59
|
+
interface NavItem {
|
|
60
|
+
href: string;
|
|
61
|
+
title: string;
|
|
62
|
+
Icon: IconType;
|
|
63
|
+
count?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface NavSection {
|
|
67
|
+
title: string;
|
|
68
|
+
list: NavItem[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param route
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
const routeToNavItem = (route: AccountRoute): NavItem => {
|
|
77
|
+
return {
|
|
78
|
+
href: route.href,
|
|
79
|
+
title: route.titleKey,
|
|
80
|
+
Icon: route.Icon,
|
|
81
|
+
count: route.count,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const DEFAULT_MENU_ITEMS = (
|
|
86
|
+
overrides?: AccountRouteOverrides,
|
|
87
|
+
): NavSection[] => {
|
|
88
|
+
const routes = useCreateAccountRoutes(overrides);
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
title: "DASHBOARD",
|
|
92
|
+
list: [
|
|
93
|
+
routeToNavItem(routes.orders),
|
|
94
|
+
routeToNavItem(routes.wishlists),
|
|
95
|
+
routeToNavItem(routes.invoices),
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: "ACCOUNT SETTINGS",
|
|
100
|
+
list: [routeToNavItem(routes.profile), routeToNavItem(routes.addresses)],
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
};
|
|
81
104
|
|
|
82
105
|
interface AccountNavigationContextValue {
|
|
83
|
-
items:
|
|
106
|
+
items: NavSection[];
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
const AccountNavigationContext =
|
|
87
110
|
createContext<AccountNavigationContextValue | null>(null);
|
|
111
|
+
|
|
88
112
|
interface AccountNavigationProps {
|
|
89
|
-
items?:
|
|
113
|
+
items?: NavSection[];
|
|
90
114
|
children?: ReactNode;
|
|
115
|
+
routeOverrides?: AccountRouteOverrides;
|
|
91
116
|
}
|
|
92
117
|
|
|
93
|
-
const AccountNavigation = ({
|
|
118
|
+
const AccountNavigation = ({
|
|
119
|
+
items,
|
|
120
|
+
children,
|
|
121
|
+
routeOverrides,
|
|
122
|
+
}: AccountNavigationProps) => {
|
|
94
123
|
const pathname = usePathname();
|
|
95
124
|
const t = useTranslations("Account.Menu");
|
|
96
125
|
|
|
97
126
|
const { data: user } = getUser.useData();
|
|
98
127
|
const { data: counts } = getUserCounts.useData(String(user?.RecordId) || "");
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
|
|
129
|
+
const defaultItems = DEFAULT_MENU_ITEMS(routeOverrides);
|
|
130
|
+
|
|
131
|
+
// Render either the props items or the default items
|
|
132
|
+
const itemsWCounts = (items || defaultItems).map((section) => ({
|
|
102
133
|
...section,
|
|
103
134
|
list: section.list.map((item) => {
|
|
104
135
|
if (item.href === "/account/orders") {
|
|
@@ -112,10 +143,10 @@ const AccountNavigation = ({ items, children }: AccountNavigationProps) => {
|
|
|
112
143
|
}));
|
|
113
144
|
|
|
114
145
|
return (
|
|
115
|
-
<AccountNavigationContext.Provider value={{ items:
|
|
146
|
+
<AccountNavigationContext.Provider value={{ items: itemsWCounts }}>
|
|
116
147
|
<MainContainer>
|
|
117
148
|
{children ||
|
|
118
|
-
|
|
149
|
+
itemsWCounts.map((section) => (
|
|
119
150
|
<AccountNavigation.Section
|
|
120
151
|
key={section.title}
|
|
121
152
|
title={section.title}
|
|
@@ -151,10 +182,7 @@ AccountNavigation.Section = ({ title, children }: SectionProps) => {
|
|
|
151
182
|
);
|
|
152
183
|
};
|
|
153
184
|
|
|
154
|
-
interface ItemProps {
|
|
155
|
-
href: string;
|
|
156
|
-
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
157
|
-
count?: number;
|
|
185
|
+
interface ItemProps extends Pick<NavItem, "href" | "Icon" | "count"> {
|
|
158
186
|
label: string;
|
|
159
187
|
isActive?: boolean;
|
|
160
188
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { IconType } from "react-icons";
|
|
3
|
+
import {
|
|
4
|
+
MdFavoriteBorder as FavoriteBorder,
|
|
5
|
+
MdDocumentScanner as InvoicesIcon,
|
|
6
|
+
MdPerson as Person,
|
|
7
|
+
MdPlace as Place,
|
|
8
|
+
MdOutlineShoppingBag as ShoppingBagOutlined,
|
|
9
|
+
} from "react-icons/md";
|
|
10
|
+
|
|
11
|
+
export interface AccountRoute {
|
|
12
|
+
href: string;
|
|
13
|
+
titleKey: string;
|
|
14
|
+
Icon: IconType;
|
|
15
|
+
count?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AccountRouteOverrides {
|
|
19
|
+
wishlists?: { href?: string; titleKey?: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaults = {
|
|
23
|
+
orders: {
|
|
24
|
+
href: "/account/orders",
|
|
25
|
+
titleKey: "orders",
|
|
26
|
+
Icon: ShoppingBagOutlined,
|
|
27
|
+
count: 0,
|
|
28
|
+
},
|
|
29
|
+
wishlists: {
|
|
30
|
+
href: "/account/wish-lists",
|
|
31
|
+
titleKey: "wishlists",
|
|
32
|
+
Icon: FavoriteBorder,
|
|
33
|
+
},
|
|
34
|
+
invoices: {
|
|
35
|
+
href: "/account/invoices",
|
|
36
|
+
titleKey: "invoices",
|
|
37
|
+
Icon: InvoicesIcon,
|
|
38
|
+
},
|
|
39
|
+
profile: { href: "/account/profile", titleKey: "profile", Icon: Person },
|
|
40
|
+
addresses: {
|
|
41
|
+
href: "/account/addresses",
|
|
42
|
+
titleKey: "addresses",
|
|
43
|
+
Icon: Place,
|
|
44
|
+
count: 0,
|
|
45
|
+
},
|
|
46
|
+
} as const satisfies Record<string, AccountRoute>;
|
|
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
|
+
|
|
64
|
+
export type AccountRouteKey = keyof typeof defaults;
|
|
65
|
+
|
|
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
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export enum FilterSourceType {
|
|
2
|
+
Origin = "Origin",
|
|
3
|
+
CreatedDate = "CreatedDate",
|
|
4
|
+
OrderStatus = "OrderStatus",
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface OriginFilter {
|
|
8
|
+
sourceType: FilterSourceType.Origin;
|
|
9
|
+
label: string;
|
|
10
|
+
value: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreatedDateFilter {
|
|
14
|
+
sourceType: FilterSourceType.CreatedDate;
|
|
15
|
+
label: string;
|
|
16
|
+
startValue?: string;
|
|
17
|
+
endValue?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OrderStatusFilter {
|
|
21
|
+
sourceType: FilterSourceType.OrderStatus;
|
|
22
|
+
label: string;
|
|
23
|
+
value: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type FilterOption = OriginFilter | CreatedDateFilter | OrderStatusFilter;
|