@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 +1 -1
- package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumbLink.tsx +1 -0
- package/src/features/shared/components/PageLoader/PageLoader.tsx +22 -0
- package/src/features/shared/components/PageLoader/index.ts +1 -0
- package/src/features/shared/components/PageLoader/page-loader.scss +22 -0
- package/src/features/shared/components/index.ts +1 -0
- package/src/features/shared/hooks/index.ts +1 -0
- package/src/features/shared/hooks/useCookieMonitor.ts +58 -0
- package/src/features/shared/types/AuthRouteProps.ts +3 -2
- package/src/features/shared/utils/withAuth.tsx +21 -5
- package/src/features/shared/utils/withAuthLoader.ts +16 -0
- package/src/features/shared/utils/withAuthProvider.tsx +22 -6
- package/src/themes/index.scss +1 -0
package/package.json
CHANGED
|
@@ -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";
|
|
@@ -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
|
-
|
|
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
|
}
|
package/src/themes/index.scss
CHANGED