@evenicanpm/storefront-core 2.2.0 → 2.3.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 +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 +96 -22
- 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/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/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/flex-box/flex-box.tsx +2 -0
- package/src/components/header/components/user.tsx +37 -12
- package/src/components/mini-cart/mini-cart.tsx +3 -3
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-header.tsx +1 -0
- package/src/pages/account/account-navigation.tsx +88 -60
- package/src/pages/account/account-routes.ts +52 -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/wishlist/wishlist-item.tsx +1 -1
- package/src/pages/blog/blog-card.tsx +9 -3
- package/src/pages/blog/blog-detail-view.tsx +150 -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/quickorder/order-upload.tsx +12 -6
- package/src/pages/quickorder/quick-order.tsx +25 -20
|
@@ -7,10 +7,16 @@ import type {
|
|
|
7
7
|
GetStatesInput,
|
|
8
8
|
} from "@evenicanpm/storefront-core/src/api-manager/schemas/address.schema";
|
|
9
9
|
import type * as cartSchemas from "@evenicanpm/storefront-core/src/api-manager/schemas/cart.schema";
|
|
10
|
+
import type {
|
|
11
|
+
GetInvoicesDetailsInput,
|
|
12
|
+
GetInvoicesInput,
|
|
13
|
+
Invoice,
|
|
14
|
+
InvoiceDetails,
|
|
15
|
+
} from "@evenicanpm/storefront-core/src/api-manager/schemas/invoice.schema";
|
|
10
16
|
import type {
|
|
11
17
|
GetSalesOrderDetailsInput,
|
|
18
|
+
GetSalesOrdersInput,
|
|
12
19
|
SalesOrder,
|
|
13
|
-
SalesOrderInput,
|
|
14
20
|
} from "@evenicanpm/storefront-core/src/api-manager/schemas/order.schema";
|
|
15
21
|
import type {
|
|
16
22
|
ChannelConfiguration,
|
|
@@ -47,6 +53,7 @@ export interface DatasourceApis {
|
|
|
47
53
|
order: OrderApi;
|
|
48
54
|
address: AddressApi;
|
|
49
55
|
organization: OrganizationApi;
|
|
56
|
+
invoice: InvoiceApi;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export abstract class Datasource {
|
|
@@ -59,6 +66,7 @@ export abstract class Datasource {
|
|
|
59
66
|
abstract address: Partial<AddressApi>;
|
|
60
67
|
abstract organization: Partial<OrganizationApi>;
|
|
61
68
|
abstract session: Partial<SessionApi>;
|
|
69
|
+
abstract invoice: Partial<InvoiceApi>;
|
|
62
70
|
|
|
63
71
|
constructor(name: string) {
|
|
64
72
|
this.name = name;
|
|
@@ -89,6 +97,9 @@ export interface UserApi {
|
|
|
89
97
|
updateUser(user: userSchemas.User): Promise<userSchemas.User>;
|
|
90
98
|
getUser(): Promise<userSchemas.User | null>;
|
|
91
99
|
getUserCounts(userId: string): Promise<userSchemas.UserCounts>;
|
|
100
|
+
getCustomerBalance(
|
|
101
|
+
input: userSchemas.GetCustomerBalanceInput,
|
|
102
|
+
): Promise<userSchemas.CustomerBalances>;
|
|
92
103
|
// wishlists
|
|
93
104
|
getWishlists(): Promise<userSchemas.Wishlist[]>;
|
|
94
105
|
getWishlistDetails(
|
|
@@ -199,7 +210,7 @@ export interface CategoryApi {
|
|
|
199
210
|
* Order Data Actions
|
|
200
211
|
*/
|
|
201
212
|
export interface OrderApi {
|
|
202
|
-
getSalesOrders(input:
|
|
213
|
+
getSalesOrders(input: GetSalesOrdersInput): Promise<SalesOrder[]>;
|
|
203
214
|
getSalesOrderDetails(input: GetSalesOrderDetailsInput): Promise<SalesOrder>;
|
|
204
215
|
}
|
|
205
216
|
|
|
@@ -214,3 +225,11 @@ export interface AddressApi {
|
|
|
214
225
|
updateAddress(address: Address): Promise<Address>;
|
|
215
226
|
deleteAddress(addressId: string): Promise<string>;
|
|
216
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Invoice Data Actions
|
|
231
|
+
*/
|
|
232
|
+
export interface InvoiceApi {
|
|
233
|
+
getInvoices(input: GetInvoicesInput): Promise<Invoice[]>;
|
|
234
|
+
getInvoiceDetails(input: GetInvoicesDetailsInput): Promise<InvoiceDetails>;
|
|
235
|
+
}
|
package/src/auth/better-auth.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { betterAuth } from "better-auth";
|
|
2
|
-
import { genericOAuth, customSession } from "better-auth/plugins";
|
|
3
2
|
import { nextCookies } from "better-auth/next-js";
|
|
3
|
+
import { customSession, genericOAuth } from "better-auth/plugins";
|
|
4
4
|
|
|
5
5
|
const tenantName = process.env.NEXT_PUBLIC_ENTRA_EXTERNAL_ID_TENANT_NAME || "";
|
|
6
6
|
const loginAuthority = `https://${tenantName}.ciamlogin.com/${tenantName}.onmicrosoft.com`;
|
|
@@ -3,12 +3,14 @@ import type { Block } from "@/cms/blocks/interfaces";
|
|
|
3
3
|
import {
|
|
4
4
|
BrandLogosScroller,
|
|
5
5
|
CategoryCarousel,
|
|
6
|
+
CtaBanner,
|
|
6
7
|
FeaturedCategories,
|
|
7
8
|
FeaturedProducts,
|
|
8
9
|
FeaturePill,
|
|
9
10
|
Footer,
|
|
10
11
|
Hero,
|
|
11
12
|
HeroCarousel,
|
|
13
|
+
HeroImage,
|
|
12
14
|
Image,
|
|
13
15
|
ProductCarousel,
|
|
14
16
|
ProductSectionFullWidth,
|
|
@@ -18,6 +20,8 @@ import {
|
|
|
18
20
|
|
|
19
21
|
const componentMap = {
|
|
20
22
|
"blocks.hero-section": Hero,
|
|
23
|
+
"blocks.hero-image": HeroImage,
|
|
24
|
+
"blocks.cta-banner": CtaBanner,
|
|
21
25
|
"blocks.features-pill": FeaturePill, // Should replace this with bazaar-react-ts/src/pages-sections/market-2/section-2/section-2.tsx
|
|
22
26
|
"blocks.featured-products": FeaturedProducts,
|
|
23
27
|
"blocks.featured-categories": FeaturedCategories,
|
|
@@ -40,8 +44,16 @@ const getBlockComponent: <B extends Block>(
|
|
|
40
44
|
) => JSX.Element | null = (block: Block) => {
|
|
41
45
|
const Component = componentMap[
|
|
42
46
|
block.__component as keyof typeof componentMap
|
|
43
|
-
] as React.ComponentType<Block
|
|
44
|
-
|
|
47
|
+
] as React.ComponentType<Block> | undefined;
|
|
48
|
+
if (!Component) {
|
|
49
|
+
console.warn(
|
|
50
|
+
`BlockManager: no component mapped for "${block.__component}"`,
|
|
51
|
+
);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return (
|
|
55
|
+
<Component key={`block-${block.__component}-${block.id}`} {...block} />
|
|
56
|
+
);
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
interface BlockManagerProps {
|
|
@@ -55,7 +67,10 @@ const BlockManager: React.FC<BlockManagerProps> = ({ blocks }) => {
|
|
|
55
67
|
{blocks.map((block, idx) => {
|
|
56
68
|
const isLast = idx === blocks.length - 1;
|
|
57
69
|
return (
|
|
58
|
-
<Box
|
|
70
|
+
<Box
|
|
71
|
+
key={`block-wrapper-${block.__component}-${block.id}`}
|
|
72
|
+
mb={isLast ? 0 : 6}
|
|
73
|
+
>
|
|
59
74
|
{getBlockComponent(block, idx)}
|
|
60
75
|
</Box>
|
|
61
76
|
);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Box, Button, Container, Typography } from "@mui/material";
|
|
2
|
+
import NextLink from "next/link";
|
|
3
|
+
import type { CtaBannerBlock } from "@/cms/blocks/interfaces";
|
|
4
|
+
|
|
5
|
+
const CtaBanner = ({
|
|
6
|
+
heading,
|
|
7
|
+
subtext,
|
|
8
|
+
buttonText,
|
|
9
|
+
buttonLink,
|
|
10
|
+
backgroundColor,
|
|
11
|
+
}: CtaBannerBlock) => {
|
|
12
|
+
const bgColor = backgroundColor || "#1a237e";
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Box
|
|
16
|
+
sx={{
|
|
17
|
+
backgroundColor: bgColor,
|
|
18
|
+
color: "#fff",
|
|
19
|
+
py: { xs: 6, md: 8 },
|
|
20
|
+
px: 2,
|
|
21
|
+
textAlign: "center",
|
|
22
|
+
width: "100%",
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Container maxWidth="md">
|
|
26
|
+
<Typography
|
|
27
|
+
variant="h3"
|
|
28
|
+
component="h2"
|
|
29
|
+
sx={{ fontWeight: 700, mb: subtext ? 2 : 3, color: "#fff" }}
|
|
30
|
+
>
|
|
31
|
+
{heading}
|
|
32
|
+
</Typography>
|
|
33
|
+
{subtext && (
|
|
34
|
+
<Typography
|
|
35
|
+
variant="h6"
|
|
36
|
+
sx={{ color: "rgba(255,255,255,0.85)", mb: 3, fontWeight: 400 }}
|
|
37
|
+
>
|
|
38
|
+
{subtext}
|
|
39
|
+
</Typography>
|
|
40
|
+
)}
|
|
41
|
+
{buttonText && buttonLink && (
|
|
42
|
+
<Button
|
|
43
|
+
component={NextLink}
|
|
44
|
+
href={buttonLink}
|
|
45
|
+
variant="contained"
|
|
46
|
+
size="large"
|
|
47
|
+
sx={{
|
|
48
|
+
backgroundColor: "#fff",
|
|
49
|
+
color: bgColor,
|
|
50
|
+
fontWeight: 700,
|
|
51
|
+
px: 4,
|
|
52
|
+
"&:hover": {
|
|
53
|
+
backgroundColor: "rgba(255,255,255,0.9)",
|
|
54
|
+
},
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{buttonText}
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
</Container>
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default CtaBanner;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Box, Button, Container, Typography } from "@mui/material";
|
|
2
|
+
import NextImage from "next/image";
|
|
3
|
+
import NextLink from "next/link";
|
|
4
|
+
import type { HeroImageBlock } from "@/cms/blocks/interfaces";
|
|
5
|
+
|
|
6
|
+
const sxBanner = {
|
|
7
|
+
position: "relative",
|
|
8
|
+
backgroundColor: "grey.900",
|
|
9
|
+
color: "white",
|
|
10
|
+
height: 500,
|
|
11
|
+
width: "100%",
|
|
12
|
+
display: "flex",
|
|
13
|
+
alignItems: "center",
|
|
14
|
+
justifyContent: "center",
|
|
15
|
+
overflow: "hidden",
|
|
16
|
+
marginBottom: "50px",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const contentContainer = {
|
|
20
|
+
position: "absolute",
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
display: "flex",
|
|
26
|
+
flexDirection: "column",
|
|
27
|
+
justifyContent: "center",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
zIndex: 2,
|
|
30
|
+
textAlign: "center",
|
|
31
|
+
padding: "0 20px",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const overlayStyle = {
|
|
35
|
+
position: "absolute",
|
|
36
|
+
inset: 0,
|
|
37
|
+
backgroundColor: "rgba(10,25,60,0.55)",
|
|
38
|
+
zIndex: 1,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const HeroImage = ({
|
|
42
|
+
heading,
|
|
43
|
+
subheading,
|
|
44
|
+
buttonText,
|
|
45
|
+
buttonLink,
|
|
46
|
+
image,
|
|
47
|
+
}: HeroImageBlock) => {
|
|
48
|
+
const imageUrl = image?.url;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Box sx={sxBanner}>
|
|
52
|
+
{imageUrl && (
|
|
53
|
+
<NextImage
|
|
54
|
+
src={imageUrl}
|
|
55
|
+
alt={heading}
|
|
56
|
+
fill
|
|
57
|
+
style={{ objectFit: "cover", objectPosition: "center" }}
|
|
58
|
+
priority
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
<Box sx={overlayStyle} />
|
|
62
|
+
<Container sx={contentContainer}>
|
|
63
|
+
<Typography variant="h1" gutterBottom>
|
|
64
|
+
{heading}
|
|
65
|
+
</Typography>
|
|
66
|
+
{subheading && (
|
|
67
|
+
<Typography variant="h5" gutterBottom sx={{ marginBottom: "30px" }}>
|
|
68
|
+
{subheading}
|
|
69
|
+
</Typography>
|
|
70
|
+
)}
|
|
71
|
+
{buttonText && buttonLink && (
|
|
72
|
+
<NextLink href={buttonLink} passHref>
|
|
73
|
+
<Button color="primary" size="large" variant="contained">
|
|
74
|
+
{buttonText}
|
|
75
|
+
</Button>
|
|
76
|
+
</NextLink>
|
|
77
|
+
)}
|
|
78
|
+
</Container>
|
|
79
|
+
</Box>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default HeroImage;
|
package/src/cms/blocks/index.tsx
CHANGED
|
@@ -14,3 +14,5 @@ export { default as ProductCarousel } from "@/cms/blocks/components/product-caro
|
|
|
14
14
|
export { default as ProductSectionFullWidth } from "@/cms/blocks/components/product-section-fullwidth/index";
|
|
15
15
|
export { default as RichText } from "@/cms/blocks/components/rich-text";
|
|
16
16
|
export { default as Services } from "@/cms/blocks/components/services/index";
|
|
17
|
+
export { default as CtaBanner } from "./components/cta-banner";
|
|
18
|
+
export { default as HeroImage } from "./components/hero-image";
|
|
@@ -141,6 +141,26 @@ export interface RichTextBlock {
|
|
|
141
141
|
content: string;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
export interface HeroImageBlock {
|
|
145
|
+
id: number;
|
|
146
|
+
__component: "blocks.hero-image";
|
|
147
|
+
heading: string;
|
|
148
|
+
subheading?: string | null;
|
|
149
|
+
buttonText?: string | null;
|
|
150
|
+
buttonLink?: string | null;
|
|
151
|
+
image?: Media | null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface CtaBannerBlock {
|
|
155
|
+
id: number;
|
|
156
|
+
__component: "blocks.cta-banner";
|
|
157
|
+
heading: string;
|
|
158
|
+
subtext?: string | null;
|
|
159
|
+
buttonText?: string | null;
|
|
160
|
+
buttonLink?: string | null;
|
|
161
|
+
backgroundColor?: string | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
144
164
|
export interface ImageFormat {
|
|
145
165
|
ext: string;
|
|
146
166
|
url: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Box, Chip } from "@mui/material";
|
|
2
|
+
|
|
3
|
+
export function DraftModeBadge() {
|
|
4
|
+
return (
|
|
5
|
+
<Box
|
|
6
|
+
sx={{
|
|
7
|
+
position: "fixed",
|
|
8
|
+
bottom: 16,
|
|
9
|
+
right: 16,
|
|
10
|
+
zIndex: 9999,
|
|
11
|
+
}}
|
|
12
|
+
>
|
|
13
|
+
<Chip
|
|
14
|
+
label="Draft Mode"
|
|
15
|
+
size="small"
|
|
16
|
+
sx={{
|
|
17
|
+
backgroundColor: "#f59e0b",
|
|
18
|
+
color: "#000",
|
|
19
|
+
fontWeight: 700,
|
|
20
|
+
fontSize: "0.75rem",
|
|
21
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
</Box>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -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
|
+
createAccountRoutes,
|
|
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 = createAccountRoutes(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>;
|
|
@@ -69,9 +89,10 @@ const UserContext = createContext<UserContextValue | undefined>(undefined);
|
|
|
69
89
|
|
|
70
90
|
interface Props {
|
|
71
91
|
children?: ReactNode;
|
|
92
|
+
routeOverrides?: AccountRouteOverrides;
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
const User = ({ children }: Props) => {
|
|
95
|
+
const User = ({ children, routeOverrides }: Props) => {
|
|
75
96
|
const { data: session, isPending } = useBetterAuthSession();
|
|
76
97
|
const status = isPending
|
|
77
98
|
? "loading"
|
|
@@ -87,6 +108,8 @@ const User = ({ children }: Props) => {
|
|
|
87
108
|
const { mutateAsync: sessionInit } = useSessionInit();
|
|
88
109
|
const { mutateAsync: updateUser } = useUpdateUser();
|
|
89
110
|
const { mutateAsync: sessionLogout } = useSessionLogout();
|
|
111
|
+
// Register the "me" query on the client so the hydrated cache entry has a queryFn for refetches
|
|
112
|
+
getUser.useData();
|
|
90
113
|
|
|
91
114
|
// session init
|
|
92
115
|
useEffect(() => {
|
|
@@ -143,10 +166,12 @@ const User = ({ children }: Props) => {
|
|
|
143
166
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
144
167
|
const open = Boolean(anchorEl);
|
|
145
168
|
|
|
169
|
+
const menuItems = buildMenuItems(routeOverrides);
|
|
170
|
+
|
|
146
171
|
const value: UserContextValue = {
|
|
147
172
|
isAuthenticated,
|
|
148
173
|
status,
|
|
149
|
-
menuItems
|
|
174
|
+
menuItems,
|
|
150
175
|
handleLogout,
|
|
151
176
|
handleSignIn,
|
|
152
177
|
signOut: signOutCustom,
|
|
@@ -198,7 +223,7 @@ User.DesktopTrigger = function DesktopTrigger() {
|
|
|
198
223
|
User.Menu = function Menu({
|
|
199
224
|
children,
|
|
200
225
|
}: {
|
|
201
|
-
children?: (items:
|
|
226
|
+
children?: (items: MenuItem[]) => React.ReactNode;
|
|
202
227
|
}) {
|
|
203
228
|
const ctx = useContext(UserContext);
|
|
204
229
|
if (!ctx) return null;
|
|
@@ -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}
|