@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.
Files changed (63) hide show
  1. package/package.json +7 -3
  2. package/src/api-manager/README.md +52 -27
  3. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +166 -17
  4. package/src/api-manager/datasources/d365/d365-invoice.datasource.ts +100 -0
  5. package/src/api-manager/datasources/d365/d365-order.datasource.ts +75 -18
  6. package/src/api-manager/datasources/d365/d365-product.datasource.ts +19 -1
  7. package/src/api-manager/datasources/d365/d365-user.datasource.ts +96 -22
  8. package/src/api-manager/datasources/d365/d365.datasource.ts +3 -0
  9. package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +10 -3
  10. package/src/api-manager/datasources/e4/e4.datasource.ts +9 -0
  11. package/src/api-manager/datasources/e4/order/e4-order.datasource.ts +7 -4
  12. package/src/api-manager/datasources/e4/user/e4-user.datasource.ts +4 -0
  13. package/src/api-manager/index.ts +54 -3
  14. package/src/api-manager/lib/get-graphql-client.ts +11 -5
  15. package/src/api-manager/schemas/cart.schema.ts +10 -1
  16. package/src/api-manager/schemas/invoice.schema.ts +55 -0
  17. package/src/api-manager/schemas/order.schema.ts +13 -1
  18. package/src/api-manager/schemas/user.schema.ts +8 -0
  19. package/src/api-manager/services/create-query.ts +4 -10
  20. package/src/api-manager/services/invoice/queries/get-invoice-details.ts +18 -0
  21. package/src/api-manager/services/invoice/queries/get-invoices.ts +18 -0
  22. package/src/api-manager/services/order/queries/get-order-details.ts +1 -1
  23. package/src/api-manager/services/order/queries/get-orders.ts +6 -3
  24. package/src/api-manager/services/user/queries/get-customer-balance.ts +20 -0
  25. package/src/api-manager/types/Datasource.ts +21 -2
  26. package/src/auth/better-auth.ts +1 -1
  27. package/src/cms/blocks/block-manager.tsx +18 -3
  28. package/src/cms/blocks/components/cta-banner.tsx +65 -0
  29. package/src/cms/blocks/components/hero-image.tsx +83 -0
  30. package/src/cms/blocks/index.tsx +2 -0
  31. package/src/cms/blocks/interfaces.ts +20 -0
  32. package/src/cms/draft-mode-badge.tsx +26 -0
  33. package/src/cms/queries.ts +81 -0
  34. package/src/components/flex-box/flex-box.tsx +2 -0
  35. package/src/components/header/components/user.tsx +37 -12
  36. package/src/components/mini-cart/mini-cart.tsx +3 -3
  37. package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-header.tsx +1 -0
  38. package/src/pages/account/account-navigation.tsx +88 -60
  39. package/src/pages/account/account-routes.ts +52 -0
  40. package/src/pages/account/orders/order-history-filter-types.tsx +26 -0
  41. package/src/pages/account/orders/order-history-filters-panel.tsx +375 -0
  42. package/src/pages/account/orders/order-history-header.tsx +54 -0
  43. package/src/pages/account/orders/order-history-pagination.tsx +55 -0
  44. package/src/pages/account/orders/order-history-root.tsx +125 -0
  45. package/src/pages/account/orders/order-history-row.tsx +110 -0
  46. package/src/pages/account/orders/order-history-search-and-filter.tsx +449 -0
  47. package/src/pages/account/orders/order-history-search-bar.tsx +84 -0
  48. package/src/pages/account/orders/order-history-search-dropdown.tsx +53 -0
  49. package/src/pages/account/orders/order-history-sort.tsx +90 -0
  50. package/src/pages/account/orders/order-history-table.tsx +54 -0
  51. package/src/pages/account/orders/order-row.tsx +2 -2
  52. package/src/pages/account/orders/orders-drop-down-handler.tsx +19 -0
  53. package/src/pages/account/table-header.tsx +20 -0
  54. package/src/pages/account/wishlist/wishlist-item.tsx +1 -1
  55. package/src/pages/blog/blog-card.tsx +9 -3
  56. package/src/pages/blog/blog-detail-view.tsx +150 -0
  57. package/src/pages/blog/blog-list-view.tsx +59 -0
  58. package/src/pages/cart/estimate-shipping.tsx +6 -5
  59. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +43 -21
  60. package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +144 -19
  61. package/src/pages/cms-page-view.tsx +53 -0
  62. package/src/pages/quickorder/order-upload.tsx +12 -6
  63. 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: SalesOrderInput): Promise<SalesOrder[]>;
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
+ }
@@ -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
- return <Component key={`block-${block.id}`} {...block} />;
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 key={`block-wrapper-${block.id}`} mb={isLast ? 0 : 6}>
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;
@@ -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
+ };
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import Box, { type BoxProps } from "@mui/material/Box";
2
4
 
3
5
  /**
@@ -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 { useRouter } from "next/navigation";
30
+ import { genericOAuthClient } from "better-auth/client/plugins";
25
31
  // Auth components
26
32
  import { createAuthClient } from "better-auth/react";
27
- import { genericOAuthClient } from "better-auth/client/plugins";
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 MENU_ITEMS = [
50
- { href: "/account/profile", label: "Account.Menu.profile" },
51
- { href: "/account/addresses", label: "Account.Menu.addresses" },
52
- { href: "/account/orders", label: "Account.Menu.orders" },
53
- { href: "/account/wish-lists", label: "Account.Menu.wishlists" },
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: typeof MENU_ITEMS;
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: MENU_ITEMS,
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: typeof MENU_ITEMS) => React.ReactNode;
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.length ? "207px" : "75px"})`}>
125
- {cart.CartLines.length > 0 ? (
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.length > 0 ? (
156
+ return cart?.CartLines?.length > 0 ? (
157
157
  <BottomActions
158
158
  total={currency(cart?.TotalAmount || 0)}
159
159
  handleNavigate={handleNavigate}
@@ -13,6 +13,7 @@ import { createAuthClient } from "better-auth/react";
13
13
  import { useTranslations } from "next-intl";
14
14
 
15
15
  const { useSession } = createAuthClient();
16
+
16
17
  // ICONS
17
18
  import { MdFavorite } from "react-icons/md";
18
19