@vtex/faststore-plugin-buyer-portal 1.1.115 → 1.1.116-poc-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 +42 -42
- package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +1 -0
- package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +1 -0
- package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +1 -0
- package/src/features/collections/layouts/CollectionsLayout/CollectionsLayout.tsx +1 -0
- package/src/features/contracts/hooks/index.ts +2 -0
- package/src/features/contracts/hooks/useContractDetails.ts +22 -0
- package/src/features/contracts/hooks/useContractsByOrgUnitId.ts +20 -0
- package/src/features/credit-cards/layouts/CreditCardsLayout/CreditCardLayout.tsx +1 -0
- package/src/features/custom-fields/layouts/CustomFieldsLayout/CustomFieldsLayout.tsx +1 -0
- package/src/features/org-units/hooks/index.ts +1 -0
- package/src/features/org-units/hooks/useOrgUnitBasicData.ts +20 -0
- package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +31 -28
- package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +1 -0
- package/src/features/profile/layouts/ProfileLayout/ProfileLayout.tsx +33 -16
- package/src/features/shared/components/Error/Error.tsx +12 -13
- package/src/features/shared/components/PageLoader/PageLoader.tsx +36 -11
- package/src/features/shared/components/withErrorBoundary/withErrorBoundary.tsx +6 -7
- package/src/features/shared/hooks/index.ts +1 -0
- package/src/features/shared/hooks/useAuth.ts +54 -0
- package/src/features/shared/hooks/useCookieMonitor.ts +2 -6
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +6 -4
- package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +1 -0
- package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +2 -27
- package/src/features/shared/types/AuthRouteProps.ts +2 -8
- package/src/features/shared/utils/withAuth.tsx +10 -62
- package/src/features/shared/utils/withAuthLoader.ts +0 -28
- package/src/features/shared/utils/withAuthProvider.tsx +11 -81
- package/src/features/shared/utils/withLoaderErrorBoundary.ts +71 -79
- package/src/features/shared/utils/withProviders.tsx +1 -2
- package/src/pages/org-unit-details.tsx +32 -58
- package/src/pages/profile.tsx +46 -64
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/router";
|
|
4
|
+
|
|
5
|
+
import { authClient } from "../clients/Auth";
|
|
6
|
+
|
|
7
|
+
import { useBuyerPortal } from "./useBuyerPortal";
|
|
8
|
+
|
|
9
|
+
export type UseAuthResult = {
|
|
10
|
+
isAuthenticated: boolean | null;
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Client-side hook to validate user authentication.
|
|
16
|
+
* Checks access with the server and redirects to home if unauthorized.
|
|
17
|
+
*
|
|
18
|
+
* @returns Object containing authentication status and loading state
|
|
19
|
+
*/
|
|
20
|
+
export const useAuth = (): UseAuthResult => {
|
|
21
|
+
const { clientContext } = useBuyerPortal();
|
|
22
|
+
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
|
23
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
24
|
+
const router = useRouter();
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const validateAccess = async () => {
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const hasAccess = await authClient.validateAccess(clientContext);
|
|
32
|
+
|
|
33
|
+
if (!hasAccess) {
|
|
34
|
+
router.push("/");
|
|
35
|
+
setIsAuthenticated(false);
|
|
36
|
+
} else {
|
|
37
|
+
setIsAuthenticated(true);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
router.push("/");
|
|
41
|
+
setIsAuthenticated(false);
|
|
42
|
+
} finally {
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
validateAccess();
|
|
48
|
+
}, [router]);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isAuthenticated,
|
|
52
|
+
isLoading,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -12,7 +12,6 @@ import { AUT_COOKIE_KEY } from "../utils/constants";
|
|
|
12
12
|
*/
|
|
13
13
|
export const useCookieMonitor = () => {
|
|
14
14
|
const [hasCookie, setHasCookie] = useState(false);
|
|
15
|
-
const [timedOut, setTimedOut] = useState(false);
|
|
16
15
|
|
|
17
16
|
useEffect(() => {
|
|
18
17
|
const checkCookie = () => {
|
|
@@ -59,10 +58,7 @@ export const useCookieMonitor = () => {
|
|
|
59
58
|
|
|
60
59
|
// Cleanup after 5 seconds max
|
|
61
60
|
const timeout = setTimeout(() => {
|
|
62
|
-
console.log(
|
|
63
|
-
"[useCookieMonitor] Polling timeout reached (5s) - cookie never appeared"
|
|
64
|
-
);
|
|
65
|
-
setTimedOut(true);
|
|
61
|
+
console.log("[useCookieMonitor] Polling timeout reached (5s)");
|
|
66
62
|
clearInterval(interval);
|
|
67
63
|
}, 5000);
|
|
68
64
|
|
|
@@ -73,5 +69,5 @@ export const useCookieMonitor = () => {
|
|
|
73
69
|
}
|
|
74
70
|
}, []);
|
|
75
71
|
|
|
76
|
-
return
|
|
72
|
+
return hasCookie;
|
|
77
73
|
};
|
|
@@ -18,6 +18,7 @@ export type ContractTabsLayoutProps = {
|
|
|
18
18
|
image?: ReactNode;
|
|
19
19
|
name: string;
|
|
20
20
|
role?: string;
|
|
21
|
+
id: string;
|
|
21
22
|
};
|
|
22
23
|
loading?: boolean;
|
|
23
24
|
children?: ReactNode;
|
|
@@ -29,8 +30,9 @@ export const ContractTabsLayout = ({
|
|
|
29
30
|
children,
|
|
30
31
|
orgUnitId,
|
|
31
32
|
contractId,
|
|
33
|
+
person,
|
|
32
34
|
}: ContractTabsLayoutProps) => {
|
|
33
|
-
const { currentOrgUnit,
|
|
35
|
+
const { currentOrgUnit, currentContract } = useBuyerPortal();
|
|
34
36
|
|
|
35
37
|
const verticalLinks = getContractSettingsLinks({
|
|
36
38
|
contractId: currentContract?.id ?? contractId ?? "",
|
|
@@ -43,9 +45,9 @@ export const ContractTabsLayout = ({
|
|
|
43
45
|
orgUnit={currentOrgUnit}
|
|
44
46
|
pageName={pageName}
|
|
45
47
|
person={{
|
|
46
|
-
name:
|
|
47
|
-
role:
|
|
48
|
-
id:
|
|
48
|
+
name: person.name,
|
|
49
|
+
role: person.role,
|
|
50
|
+
id: person.id,
|
|
49
51
|
}}
|
|
50
52
|
loading={loading}
|
|
51
53
|
/>
|
|
@@ -78,6 +78,7 @@ export const LoadingTabsLayout = ({ children }: LoadingTabsLayoutProps) => {
|
|
|
78
78
|
image: undefined,
|
|
79
79
|
name: "",
|
|
80
80
|
role: "",
|
|
81
|
+
id: "",
|
|
81
82
|
}}
|
|
82
83
|
loading={true}
|
|
83
84
|
>
|
|
@@ -100,33 +101,7 @@ export const LoadingTabsLayout = ({ children }: LoadingTabsLayoutProps) => {
|
|
|
100
101
|
);
|
|
101
102
|
|
|
102
103
|
case "OrgUnitDetailsLayout":
|
|
103
|
-
return
|
|
104
|
-
<OrgUnitsDetailsLayout
|
|
105
|
-
loading={true}
|
|
106
|
-
data={{
|
|
107
|
-
contracts: [],
|
|
108
|
-
orgUnit: {
|
|
109
|
-
contractMode: "Single",
|
|
110
|
-
customerGroup: { customerIds: [] },
|
|
111
|
-
id: routeParams.orgUnitId,
|
|
112
|
-
name: "",
|
|
113
|
-
path: {
|
|
114
|
-
ids: "",
|
|
115
|
-
names: "",
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
user: {
|
|
119
|
-
name: "",
|
|
120
|
-
roles: [],
|
|
121
|
-
id: "",
|
|
122
|
-
orgUnit: {
|
|
123
|
-
id: routeParams.orgUnitId,
|
|
124
|
-
name: "",
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
}}
|
|
128
|
-
/>
|
|
129
|
-
);
|
|
104
|
+
return <OrgUnitsDetailsLayout loading={true} orgUnitId="" userId="" />;
|
|
130
105
|
|
|
131
106
|
default:
|
|
132
107
|
return <LoadingTabsLayoutContent />;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { ClientContext } from "../utils";
|
|
2
2
|
|
|
3
3
|
export type AuthRouteProps<T> =
|
|
4
|
-
| { authorized: true; data: T; clientContext: ClientContext
|
|
5
|
-
| { authorized: false
|
|
6
|
-
| {
|
|
7
|
-
authorized: false;
|
|
8
|
-
loading: true;
|
|
9
|
-
data?: Partial<T>;
|
|
10
|
-
clientContext?: ClientContext;
|
|
11
|
-
};
|
|
4
|
+
| { authorized: true; data: T; clientContext: ClientContext }
|
|
5
|
+
| { authorized: false };
|
|
@@ -1,83 +1,31 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, type ComponentType } from "react";
|
|
2
2
|
|
|
3
3
|
import { useRouter } from "next/router";
|
|
4
4
|
|
|
5
|
-
import { useCookieMonitor } from "../hooks/useCookieMonitor";
|
|
6
|
-
|
|
7
5
|
import type { AuthRouteProps } from "../types";
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
|
-
* HOC that checks only for authorization
|
|
11
|
-
* Now handles loading state for pre-fetch scenarios.
|
|
8
|
+
* HOC that checks only for authorization
|
|
12
9
|
*/
|
|
13
10
|
export const withAuth = <T extends Record<string, unknown>>(
|
|
14
11
|
Component: ComponentType<T>
|
|
15
12
|
) => {
|
|
16
13
|
return function AuthenticatedComponent(props: AuthRouteProps<T>) {
|
|
17
14
|
const router = useRouter();
|
|
18
|
-
const { hasCookie, timedOut } = useCookieMonitor();
|
|
19
|
-
const reloadAttemptedRef = useRef(false);
|
|
20
|
-
|
|
21
|
-
// Debug logging
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
console.log("[withAuth] Props state:", {
|
|
24
|
-
authorized: props?.authorized,
|
|
25
|
-
loading: props?.loading,
|
|
26
|
-
hasCookie,
|
|
27
|
-
timedOut,
|
|
28
|
-
reloadAttempted: reloadAttemptedRef.current,
|
|
29
|
-
});
|
|
30
|
-
}, [props?.authorized, props?.loading, hasCookie, timedOut]);
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
// If not authorized and not loading, reload the page
|
|
34
|
-
if (
|
|
35
|
-
!props?.authorized &&
|
|
36
|
-
!props?.loading &&
|
|
37
|
-
!reloadAttemptedRef.current
|
|
38
|
-
) {
|
|
39
|
-
console.log("[withAuth] Reloading: not authorized and not loading");
|
|
40
|
-
reloadAttemptedRef.current = true;
|
|
41
|
-
// Use replace to bypass Next.js data cache
|
|
42
|
-
router.replace(router.asPath);
|
|
43
|
-
}
|
|
44
|
-
}, [props?.authorized, props?.loading, router]);
|
|
45
15
|
|
|
46
16
|
useEffect(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
51
|
-
reloadAttemptedRef.current = true;
|
|
52
|
-
// Hard reload to completely bypass Next.js cache
|
|
53
|
-
if (typeof window !== "undefined") {
|
|
54
|
-
window.location.href = router.asPath;
|
|
55
|
-
}
|
|
17
|
+
// If not authorized, reload the page
|
|
18
|
+
if (!props?.authorized) {
|
|
19
|
+
router.reload();
|
|
56
20
|
}
|
|
57
|
-
}, [props?.
|
|
21
|
+
}, [props?.authorized, router]);
|
|
58
22
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (props?.loading && timedOut && !reloadAttemptedRef.current) {
|
|
62
|
-
console.log("[withAuth] Cookie timeout - redirecting to home page");
|
|
63
|
-
reloadAttemptedRef.current = true;
|
|
64
|
-
router.push("/");
|
|
65
|
-
}
|
|
66
|
-
}, [props?.loading, timedOut, router]);
|
|
67
|
-
|
|
68
|
-
// Always render the component, but pass loading state
|
|
69
|
-
// This allows components to handle their own loading states
|
|
70
|
-
if (!props?.authorized && !props?.loading) {
|
|
71
|
-
// Not authorized and not loading = permanently denied
|
|
23
|
+
// If not authorized, render nothing
|
|
24
|
+
if (!props?.authorized) {
|
|
72
25
|
return null;
|
|
73
26
|
}
|
|
74
27
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
...(props?.data || ({} as T)),
|
|
78
|
-
loading: props?.loading || false,
|
|
79
|
-
} as T & { loading: boolean };
|
|
80
|
-
|
|
81
|
-
return <Component {...componentProps} />;
|
|
28
|
+
// If authorized, render the component with the data
|
|
29
|
+
return <Component {...(props.data as T)} />;
|
|
82
30
|
};
|
|
83
31
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { validateAccessService } from "../services";
|
|
2
2
|
|
|
3
|
-
import { getAuthCookie } from "./cookie";
|
|
4
3
|
import { ClientContext, getClientContext } from "./getClientContext";
|
|
5
4
|
|
|
6
5
|
import type { AuthRouteProps } from "../types";
|
|
@@ -9,57 +8,31 @@ import type { LoaderData } from "../types";
|
|
|
9
8
|
/**
|
|
10
9
|
* Utility function to encapsulate access validation in loaders.
|
|
11
10
|
* 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.
|
|
14
11
|
*/
|
|
15
12
|
export const withAuthLoader = async <T>(
|
|
16
13
|
data: LoaderData,
|
|
17
14
|
dataLoader: (clientContext: ClientContext) => Promise<T>
|
|
18
15
|
): Promise<AuthRouteProps<T>> => {
|
|
19
16
|
try {
|
|
20
|
-
// Check if auth cookie exists before proceeding
|
|
21
|
-
const authCookie = getAuthCookie(data);
|
|
22
|
-
|
|
23
|
-
console.log("[withAuthLoader] Cookie check:", {
|
|
24
|
-
hasCookie: !!authCookie,
|
|
25
|
-
cookieLength: authCookie?.length,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (!authCookie || authCookie === "") {
|
|
29
|
-
console.log("[withAuthLoader] Returning loading state (no cookie)");
|
|
30
|
-
return {
|
|
31
|
-
authorized: false,
|
|
32
|
-
loading: true,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
17
|
const { cookie, userId, ...clientContext } = await getClientContext(data);
|
|
37
18
|
|
|
38
19
|
const hasAccess = await validateAccessService({ cookie });
|
|
39
20
|
|
|
40
|
-
console.log("[withAuthLoader] Access validation result:", hasAccess);
|
|
41
|
-
|
|
42
21
|
if (!hasAccess) {
|
|
43
|
-
console.log("[withAuthLoader] Access denied, redirecting to /");
|
|
44
22
|
data.res?.writeHead(302, { Location: "/" });
|
|
45
23
|
data.res?.end();
|
|
46
24
|
|
|
47
25
|
return {
|
|
48
26
|
authorized: false,
|
|
49
|
-
loading: false,
|
|
50
27
|
};
|
|
51
28
|
}
|
|
52
29
|
|
|
53
30
|
const pageData = await dataLoader({ cookie, userId, ...clientContext });
|
|
54
31
|
|
|
55
|
-
console.log(
|
|
56
|
-
"[withAuthLoader] Successfully loaded data, returning authorized"
|
|
57
|
-
);
|
|
58
32
|
return {
|
|
59
33
|
authorized: true,
|
|
60
34
|
data: pageData,
|
|
61
35
|
clientContext: { cookie, userId, ...clientContext },
|
|
62
|
-
loading: false,
|
|
63
36
|
};
|
|
64
37
|
} catch (error) {
|
|
65
38
|
console.error("[withAuthLoader] Auth validation failed:", error);
|
|
@@ -69,7 +42,6 @@ export const withAuthLoader = async <T>(
|
|
|
69
42
|
|
|
70
43
|
return {
|
|
71
44
|
authorized: false,
|
|
72
|
-
loading: false,
|
|
73
45
|
};
|
|
74
46
|
}
|
|
75
47
|
};
|
|
@@ -1,107 +1,37 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, type ComponentType } from "react";
|
|
2
2
|
|
|
3
3
|
import { useRouter } from "next/router";
|
|
4
4
|
|
|
5
5
|
import { BuyerPortalProvider } from "../components";
|
|
6
|
-
import { useCookieMonitor } from "../hooks/useCookieMonitor";
|
|
7
6
|
|
|
8
7
|
import type { AuthRouteProps } from "../types";
|
|
9
8
|
import type { ClientContext } from "./getClientContext";
|
|
10
9
|
|
|
11
10
|
/**
|
|
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.
|
|
11
|
+
* HOC que encapsula lógica de autenticação e provider
|
|
15
12
|
*/
|
|
16
13
|
export const withAuthProvider = <T,>(
|
|
17
14
|
Component: ComponentType<T & { clientContext: ClientContext }>
|
|
18
15
|
) => {
|
|
19
16
|
return function WrappedPage(props: AuthRouteProps<T>) {
|
|
20
17
|
const router = useRouter();
|
|
21
|
-
const { hasCookie, timedOut } = useCookieMonitor();
|
|
22
|
-
const reloadAttemptedRef = useRef(false);
|
|
23
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
24
18
|
|
|
25
|
-
// Debug logging (only in development or when needed)
|
|
26
19
|
useEffect(() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
loading: props?.loading,
|
|
31
|
-
hasCookie,
|
|
32
|
-
timedOut,
|
|
33
|
-
reloadAttempted: reloadAttemptedRef.current,
|
|
34
|
-
routerPath: router.asPath,
|
|
35
|
-
});
|
|
20
|
+
// Se não está autorizado, recarrega a página
|
|
21
|
+
if (!props?.authorized) {
|
|
22
|
+
router.reload();
|
|
36
23
|
}
|
|
37
|
-
}, [
|
|
38
|
-
props?.authorized,
|
|
39
|
-
props?.loading,
|
|
40
|
-
hasCookie,
|
|
41
|
-
timedOut,
|
|
42
|
-
router.asPath,
|
|
43
|
-
isProduction,
|
|
44
|
-
]);
|
|
24
|
+
}, [props?.authorized, router]);
|
|
45
25
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
!props?.authorized &&
|
|
50
|
-
!props?.loading &&
|
|
51
|
-
!reloadAttemptedRef.current
|
|
52
|
-
) {
|
|
53
|
-
console.log(
|
|
54
|
-
"[withAuthProvider] Reloading: not authorized and not loading"
|
|
55
|
-
);
|
|
56
|
-
reloadAttemptedRef.current = true;
|
|
57
|
-
// Use replace to bypass Next.js data cache
|
|
58
|
-
router.replace(router.asPath);
|
|
59
|
-
}
|
|
60
|
-
}, [props?.authorized, props?.loading, router]);
|
|
61
|
-
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
// If we're in loading state and cookie becomes available, do hard reload
|
|
64
|
-
if (props?.loading && hasCookie && !reloadAttemptedRef.current) {
|
|
65
|
-
console.log(
|
|
66
|
-
"[withAuthProvider] Cookie available, performing hard reload to bypass cache"
|
|
67
|
-
);
|
|
68
|
-
reloadAttemptedRef.current = true;
|
|
69
|
-
// Hard reload to completely bypass Next.js cache
|
|
70
|
-
if (typeof window !== "undefined") {
|
|
71
|
-
window.location.href = router.asPath;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}, [props?.loading, hasCookie, router]);
|
|
75
|
-
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
// If we're in loading state and timeout reached, redirect to home
|
|
78
|
-
if (props?.loading && timedOut && !reloadAttemptedRef.current) {
|
|
79
|
-
console.log(
|
|
80
|
-
"[withAuthProvider] Cookie timeout - redirecting to home page"
|
|
81
|
-
);
|
|
82
|
-
reloadAttemptedRef.current = true;
|
|
83
|
-
router.push("/");
|
|
84
|
-
}
|
|
85
|
-
}, [props?.loading, timedOut, router]);
|
|
86
|
-
|
|
87
|
-
// Always render the component, but pass loading state
|
|
88
|
-
// This allows components to handle their own loading states
|
|
89
|
-
if (!props?.authorized && !props?.loading) {
|
|
90
|
-
// Not authorized and not loading = permanently denied
|
|
26
|
+
// Se não está autorizado, não renderiza nada
|
|
27
|
+
if (!props?.authorized) {
|
|
91
28
|
return null;
|
|
92
29
|
}
|
|
93
30
|
|
|
94
|
-
//
|
|
95
|
-
const componentProps = {
|
|
96
|
-
...(props?.data || ({} as T)),
|
|
97
|
-
loading: props?.loading || false,
|
|
98
|
-
} as T & { loading: boolean };
|
|
99
|
-
|
|
100
|
-
const clientContext = props?.clientContext || ({} as ClientContext);
|
|
101
|
-
|
|
31
|
+
// Se autorizado, renderiza o componente envolvido no provider
|
|
102
32
|
return (
|
|
103
|
-
<BuyerPortalProvider clientContext={clientContext}>
|
|
104
|
-
<Component {...
|
|
33
|
+
<BuyerPortalProvider clientContext={props?.clientContext}>
|
|
34
|
+
<Component {...props.data} clientContext={props?.clientContext} />
|
|
105
35
|
</BuyerPortalProvider>
|
|
106
36
|
);
|
|
107
37
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { getClientContext } from "./getClientContext";
|
|
2
2
|
import { serializeLoaderData } from "./serializeLoaderData";
|
|
3
3
|
|
|
4
4
|
import type { LoaderData } from "../types";
|
|
@@ -11,85 +11,77 @@ interface WithLoaderErrorBoundaryOptions {
|
|
|
11
11
|
|
|
12
12
|
export function withLoaderErrorBoundary<TQuery, TReturn>(
|
|
13
13
|
loaderFn: (data: LoaderData<TQuery>) => Promise<TReturn>,
|
|
14
|
-
|
|
15
|
-
_options: WithLoaderErrorBoundaryOptions = {}
|
|
14
|
+
options: WithLoaderErrorBoundaryOptions = {}
|
|
16
15
|
) {
|
|
17
|
-
|
|
16
|
+
const { componentName, onError, redirectToError = true } = options;
|
|
17
|
+
|
|
18
18
|
return async (data: LoaderData<TQuery>): Promise<TReturn> => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
try {
|
|
20
|
+
const result = await loaderFn(data);
|
|
21
|
+
return serializeLoaderData(result);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(`[${componentName || "Loader"}] Error:`, {
|
|
24
|
+
message: (error as Error).message,
|
|
25
|
+
query: data.query,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (onError) {
|
|
29
|
+
onError(error as Error, data.query);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (redirectToError) {
|
|
33
|
+
let clientContext;
|
|
34
|
+
try {
|
|
35
|
+
clientContext = await getClientContext(data);
|
|
36
|
+
} catch (contextError) {
|
|
37
|
+
console.error(
|
|
38
|
+
`[${componentName || "Loader"}] Failed to get client context:`,
|
|
39
|
+
contextError
|
|
40
|
+
);
|
|
41
|
+
clientContext = {
|
|
42
|
+
cookie: "",
|
|
43
|
+
customerId: "",
|
|
44
|
+
userId: "",
|
|
45
|
+
vtexIdclientAutCookie: "",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
// const errorResponse = {
|
|
63
|
-
// error: {
|
|
64
|
-
// error: {
|
|
65
|
-
// message: (error as Error).message,
|
|
66
|
-
// name: (error as Error).name,
|
|
67
|
-
// stack: (error as Error).stack || null,
|
|
68
|
-
// query: serializedQuery,
|
|
69
|
-
// },
|
|
70
|
-
// tags: {
|
|
71
|
-
// component: componentName || "Unknown",
|
|
72
|
-
// errorType: "loader_error",
|
|
73
|
-
// },
|
|
74
|
-
// extra: {
|
|
75
|
-
// query: serializedQuery,
|
|
76
|
-
// },
|
|
77
|
-
// },
|
|
78
|
-
// hasError: true,
|
|
79
|
-
// context: {
|
|
80
|
-
// clientContext,
|
|
81
|
-
// currentOrgUnit: null,
|
|
82
|
-
// currentContract: null,
|
|
83
|
-
// currentUser: null,
|
|
84
|
-
// },
|
|
85
|
-
// data: null,
|
|
86
|
-
// };
|
|
87
|
-
//
|
|
88
|
-
// // Ensure error response is also JSON-serializable
|
|
89
|
-
// return serializeLoaderData(errorResponse) as TReturn;
|
|
90
|
-
// }
|
|
91
|
-
//
|
|
92
|
-
// throw error;
|
|
93
|
-
// }
|
|
94
|
-
// };
|
|
49
|
+
// Serialize query to ensure it's JSON-safe (use null instead of undefined)
|
|
50
|
+
const serializedQuery = data.query
|
|
51
|
+
? JSON.parse(JSON.stringify(data.query))
|
|
52
|
+
: null;
|
|
53
|
+
|
|
54
|
+
const errorResponse = {
|
|
55
|
+
error: {
|
|
56
|
+
error: {
|
|
57
|
+
message: (error as Error).message,
|
|
58
|
+
name: (error as Error).name,
|
|
59
|
+
stack: (error as Error).stack || null,
|
|
60
|
+
query: serializedQuery,
|
|
61
|
+
},
|
|
62
|
+
tags: {
|
|
63
|
+
component: componentName || "Unknown",
|
|
64
|
+
errorType: "loader_error",
|
|
65
|
+
},
|
|
66
|
+
extra: {
|
|
67
|
+
query: serializedQuery,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
hasError: true,
|
|
71
|
+
context: {
|
|
72
|
+
clientContext,
|
|
73
|
+
currentOrgUnit: null,
|
|
74
|
+
currentContract: null,
|
|
75
|
+
currentUser: null,
|
|
76
|
+
},
|
|
77
|
+
data: null,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Ensure error response is also JSON-serializable
|
|
81
|
+
return serializeLoaderData(errorResponse) as TReturn;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
95
87
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ComponentType } from "react";
|
|
2
2
|
|
|
3
|
-
import { withAuth } from "./withAuth";
|
|
4
3
|
import { withBuyerPortal } from "./withBuyerPortal";
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -14,7 +13,7 @@ type ProviderHOC = (Component: ComponentType<any>) => ComponentType<any>;
|
|
|
14
13
|
* Default project providers applied to all pages.
|
|
15
14
|
* Order: right to left (like pipe/compose)
|
|
16
15
|
*/
|
|
17
|
-
const projectProviders: ProviderHOC[] = [
|
|
16
|
+
const projectProviders: ProviderHOC[] = [withBuyerPortal];
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* HOC that applies all default project providers.
|