@vtex/faststore-plugin-buyer-portal 1.1.109 → 1.1.111

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.1.109",
3
+ "version": "1.1.111",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -38,6 +38,7 @@ export const OrgUnitBreadcrumbLink = ({
38
38
  ref={linkRef as React.RefObject<HTMLAnchorElement>}
39
39
  data-fs-bp-breadcrumb-text
40
40
  href={item}
41
+ data-fs-bp-breadcrumb-link
41
42
  >
42
43
  {name}
43
44
  </Link>
@@ -0,0 +1,22 @@
1
+ import { Skeleton } from "@faststore/ui";
2
+
3
+ /**
4
+ * PageLoader component displays skeleton loaders while page data is being fetched.
5
+ * Used when cookies are not yet available during pre-fetch scenarios.
6
+ */
7
+ export const PageLoader = () => {
8
+ return (
9
+ <div data-fs-bp-page-loader>
10
+ <div data-fs-bp-page-loader-header>
11
+ <Skeleton size={{ width: "12rem", height: "2rem" }} />
12
+ <Skeleton size={{ width: "8rem", height: "1.5rem" }} />
13
+ </div>
14
+
15
+ <div data-fs-bp-page-loader-content>
16
+ <Skeleton size={{ width: "100%", height: "4rem" }} />
17
+ <Skeleton size={{ width: "100%", height: "8rem" }} />
18
+ <Skeleton size={{ width: "100%", height: "8rem" }} />
19
+ </div>
20
+ </div>
21
+ );
22
+ };
@@ -0,0 +1 @@
1
+ export { PageLoader } from "./PageLoader";
@@ -0,0 +1,22 @@
1
+ @import "@faststore/ui/src/components/atoms/Skeleton/styles.scss";
2
+
3
+ [data-fs-bp-page-loader] {
4
+ padding: var(--fs-spacing-3);
5
+ width: 100%;
6
+ min-height: 100vh;
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--fs-spacing-4);
10
+ }
11
+
12
+ [data-fs-bp-page-loader-header] {
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--fs-spacing-2);
16
+ }
17
+
18
+ [data-fs-bp-page-loader-content] {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: var(--fs-spacing-3);
22
+ }
@@ -76,3 +76,4 @@ export { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
76
76
  export { withErrorBoundary } from "./withErrorBoundary/withErrorBoundary";
77
77
  export { default as Error } from "./Error/Error";
78
78
  export { IconBookmarked } from "./IconBookmarked/IconBookmarked";
79
+ export { PageLoader } from "./PageLoader";
@@ -1,4 +1,5 @@
1
1
  export { useBuyerPortal } from "./useBuyerPortal";
2
+ export { useCookieMonitor } from "./useCookieMonitor";
2
3
  export { useDrawerProps, type DrawerProps } from "./useDrawerProps";
3
4
  export {
4
5
  useMutation,
@@ -0,0 +1,58 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import storeConfig from "discovery.config";
4
+
5
+ import { AUT_COOKIE_KEY } from "../utils/constants";
6
+
7
+ /**
8
+ * Hook to monitor when authentication cookie becomes available.
9
+ * Used to detect when cookies load after pre-fetch scenarios.
10
+ *
11
+ * @returns boolean indicating if auth cookie is present
12
+ */
13
+ export const useCookieMonitor = () => {
14
+ const [hasCookie, setHasCookie] = useState(false);
15
+
16
+ useEffect(() => {
17
+ const checkCookie = () => {
18
+ if (typeof document === "undefined") return false;
19
+
20
+ const cookieName = `${AUT_COOKIE_KEY}_${storeConfig?.api.storeId}`;
21
+ const cookies = document.cookie.split(";");
22
+
23
+ const authCookie = cookies.find((cookie) =>
24
+ cookie.trim().startsWith(`${cookieName}=`)
25
+ );
26
+
27
+ return !!authCookie && authCookie.split("=")[1]?.trim() !== "";
28
+ };
29
+
30
+ // Check immediately
31
+ const initialCheck = checkCookie();
32
+ setHasCookie(initialCheck);
33
+
34
+ // If no cookie yet, poll for it
35
+ if (!initialCheck) {
36
+ const interval = setInterval(() => {
37
+ const hasAuthCookie = checkCookie();
38
+
39
+ if (hasAuthCookie) {
40
+ setHasCookie(true);
41
+ clearInterval(interval);
42
+ }
43
+ }, 100); // Check every 100ms
44
+
45
+ // Cleanup after 5 seconds max
46
+ const timeout = setTimeout(() => {
47
+ clearInterval(interval);
48
+ }, 5000);
49
+
50
+ return () => {
51
+ clearInterval(interval);
52
+ clearTimeout(timeout);
53
+ };
54
+ }
55
+ }, []);
56
+
57
+ return hasCookie;
58
+ };
@@ -1,5 +1,6 @@
1
1
  import { ClientContext } from "../utils";
2
2
 
3
3
  export type AuthRouteProps<T> =
4
- | { authorized: true; data: T; clientContext: ClientContext }
5
- | { authorized: false };
4
+ | { authorized: true; data: T; clientContext: ClientContext; loading: false }
5
+ | { authorized: false; loading: false }
6
+ | { authorized: false; loading: true };
@@ -2,25 +2,41 @@ import { useEffect, type ComponentType } from "react";
2
2
 
3
3
  import { useRouter } from "next/router";
4
4
 
5
+ import { PageLoader } from "../components";
6
+ import { useCookieMonitor } from "../hooks/useCookieMonitor";
7
+
5
8
  import type { AuthRouteProps } from "../types";
6
9
 
7
10
  /**
8
- * HOC that checks only for authorization
11
+ * HOC that checks only for authorization.
12
+ * Now handles loading state for pre-fetch scenarios.
9
13
  */
10
14
  export const withAuth = <T extends Record<string, unknown>>(
11
15
  Component: ComponentType<T>
12
16
  ) => {
13
17
  return function AuthenticatedComponent(props: AuthRouteProps<T>) {
14
18
  const router = useRouter();
19
+ const hasCookie = useCookieMonitor();
15
20
 
16
21
  useEffect(() => {
17
- // If not authorized, reload the page
18
- if (!props?.authorized) {
22
+ // If not authorized and not loading, reload the page
23
+ if (!props?.authorized && !props?.loading) {
19
24
  router.reload();
20
25
  }
21
- }, [props?.authorized, router]);
26
+ }, [props?.authorized, props?.loading, router]);
27
+
28
+ useEffect(() => {
29
+ if (props?.loading && hasCookie) {
30
+ router.reload();
31
+ }
32
+ }, [props?.loading, hasCookie, router]);
33
+
34
+ // If loading (cookie not yet available), show skeleton
35
+ if (props?.loading) {
36
+ return <PageLoader />;
37
+ }
22
38
 
23
- // If not authorized, render nothing
39
+ // If not authorized and not loading, render nothing
24
40
  if (!props?.authorized) {
25
41
  return null;
26
42
  }
@@ -1,5 +1,6 @@
1
1
  import { validateAccessService } from "../services";
2
2
 
3
+ import { getAuthCookie } from "./cookie";
3
4
  import { ClientContext, getClientContext } from "./getClientContext";
4
5
 
5
6
  import type { AuthRouteProps } from "../types";
@@ -8,12 +9,24 @@ import type { LoaderData } from "../types";
8
9
  /**
9
10
  * Utility function to encapsulate access validation in loaders.
10
11
  * Similar to withAuthProvider but for server-side usage.
12
+ *
13
+ * Now supports a loading state for when cookies are not yet available during pre-fetch.
11
14
  */
12
15
  export const withAuthLoader = async <T>(
13
16
  data: LoaderData,
14
17
  dataLoader: (clientContext: ClientContext) => Promise<T>
15
18
  ): Promise<AuthRouteProps<T>> => {
16
19
  try {
20
+ // Check if auth cookie exists before proceeding
21
+ const authCookie = getAuthCookie(data);
22
+
23
+ if (!authCookie || authCookie === "") {
24
+ return {
25
+ authorized: false,
26
+ loading: true,
27
+ };
28
+ }
29
+
17
30
  const { cookie, userId, ...clientContext } = await getClientContext(data);
18
31
 
19
32
  const hasAccess = await validateAccessService({ cookie });
@@ -24,6 +37,7 @@ export const withAuthLoader = async <T>(
24
37
 
25
38
  return {
26
39
  authorized: false,
40
+ loading: false,
27
41
  };
28
42
  }
29
43
 
@@ -33,6 +47,7 @@ export const withAuthLoader = async <T>(
33
47
  authorized: true,
34
48
  data: pageData,
35
49
  clientContext: { cookie, userId, ...clientContext },
50
+ loading: false,
36
51
  };
37
52
  } catch (error) {
38
53
  console.error("Auth validation failed:", error);
@@ -42,6 +57,7 @@ export const withAuthLoader = async <T>(
42
57
 
43
58
  return {
44
59
  authorized: false,
60
+ loading: false,
45
61
  };
46
62
  }
47
63
  };
@@ -2,28 +2,44 @@ import { useEffect, type ComponentType } from "react";
2
2
 
3
3
  import { useRouter } from "next/router";
4
4
 
5
- import { BuyerPortalProvider } from "../components";
5
+ import { BuyerPortalProvider, PageLoader } from "../components";
6
+ import { useCookieMonitor } from "../hooks/useCookieMonitor";
6
7
 
7
8
  import type { AuthRouteProps } from "../types";
8
9
  import type { ClientContext } from "./getClientContext";
9
10
 
10
11
  /**
11
- * HOC que encapsula lógica de autenticação e provider
12
+ * HOC que encapsula lógica de autenticação e provider.
13
+ * Now handles loading state for pre-fetch scenarios.
14
+ * Monitors cookie availability and reloads when cookie becomes available.
12
15
  */
13
16
  export const withAuthProvider = <T,>(
14
17
  Component: ComponentType<T & { clientContext: ClientContext }>
15
18
  ) => {
16
19
  return function WrappedPage(props: AuthRouteProps<T>) {
17
20
  const router = useRouter();
21
+ const hasCookie = useCookieMonitor();
18
22
 
19
23
  useEffect(() => {
20
- // Se não está autorizado, recarrega a página
21
- if (!props?.authorized) {
24
+ // Se não está autorizado e não está carregando, recarrega a página
25
+ if (!props?.authorized && !props?.loading) {
22
26
  router.reload();
23
27
  }
24
- }, [props?.authorized, router]);
28
+ }, [props?.authorized, props?.loading, router]);
25
29
 
26
- // Se não está autorizado, não renderiza nada
30
+ useEffect(() => {
31
+ // If we're in loading state and cookie becomes available, reload to fetch data
32
+ if (props?.loading && hasCookie) {
33
+ router.reload();
34
+ }
35
+ }, [props?.loading, hasCookie, router]);
36
+
37
+ // Se está carregando (cookie ainda não disponível), mostra skeleton
38
+ if (props?.loading) {
39
+ return <PageLoader />;
40
+ }
41
+
42
+ // Se não está autorizado e não está carregando, não renderiza nada
27
43
  if (!props?.authorized) {
28
44
  return null;
29
45
  }
@@ -1,4 +1,5 @@
1
1
  @import "../features/profile/components/ProfileSummary/profile-summary.scss";
2
+ @import "../features/shared/components/PageLoader/page-loader.scss";
2
3
  @import "./layouts";
3
4
 
4
5
  // ----------------------------------------------------------