@vtex/faststore-plugin-buyer-portal 1.1.116-poc → 1.1.116-poc-2
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/profile/layouts/ProfileLayout/ProfileLayout.tsx +11 -0
- package/src/features/shared/components/Error/Error.tsx +12 -13
- package/src/features/shared/components/PageLoader/PageLoader.tsx +36 -11
- package/src/features/shared/hooks/index.ts +1 -0
- package/src/features/shared/hooks/useAuth.ts +54 -0
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +9 -6
- 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 +47 -21
- package/src/pages/profile.tsx +49 -24
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
2
|
+
"name": "@vtex/faststore-plugin-buyer-portal",
|
|
3
|
+
"version": "1.1.116-poc-2",
|
|
4
|
+
"description": "A plugin for faststore with buyer portal",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
|
8
|
+
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
|
9
|
+
"prepare": "husky"
|
|
10
|
+
},
|
|
11
|
+
"lint-staged": {
|
|
12
|
+
"*.{js,jsx,ts,tsx}": [
|
|
13
|
+
"eslint --fix"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@types/react-dom": "^19.0.3",
|
|
18
|
+
"react-dom": "^19.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@eslint/js": "^9.29.0",
|
|
22
|
+
"@faststore/core": "^3.41.5",
|
|
23
|
+
"@faststore/ui": "^3.41.5",
|
|
24
|
+
"@types/react": "^18.2.42",
|
|
25
|
+
"cypress": "13",
|
|
26
|
+
"eslint": "^9.29.0",
|
|
27
|
+
"eslint-config-prettier": "^10.1.5",
|
|
28
|
+
"eslint-plugin-import": "^2.32.0",
|
|
29
|
+
"eslint-plugin-prettier": "^5.5.1",
|
|
30
|
+
"eslint-plugin-react": "^7.37.5",
|
|
31
|
+
"globals": "^16.2.0",
|
|
32
|
+
"husky": "^9.1.7",
|
|
33
|
+
"lint-staged": "^16.1.2",
|
|
34
|
+
"next": "13.5.7",
|
|
35
|
+
"typescript": "4.7.3",
|
|
36
|
+
"typescript-eslint": "^8.35.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^18.2.0",
|
|
40
|
+
"react-dom": "^18.2.0"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
44
44
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Skeleton } from "@faststore/ui";
|
|
2
|
+
|
|
1
3
|
import { useContractDetails } from "../../../contracts/hooks";
|
|
2
4
|
import { useOrgUnitBasicData } from "../../../org-units/hooks";
|
|
3
5
|
import { HeaderInside } from "../../../shared/components";
|
|
@@ -88,6 +90,15 @@ export const ProfileLayout = ({
|
|
|
88
90
|
<hr data-fs-bp-profile-divider />
|
|
89
91
|
</div>
|
|
90
92
|
)}
|
|
93
|
+
|
|
94
|
+
{loading && (
|
|
95
|
+
<Skeleton
|
|
96
|
+
size={{
|
|
97
|
+
width: "100%",
|
|
98
|
+
height: "8rem",
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
91
102
|
</section>
|
|
92
103
|
</ContractTabsLayout>
|
|
93
104
|
</GlobalLayout>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { isDevelopment } from "../../utils/environment";
|
|
2
2
|
import { Icon } from "../Icon";
|
|
3
3
|
|
|
4
4
|
export type ErrorProps = {
|
|
@@ -20,18 +20,17 @@ export default function Error({ error }: ErrorProps) {
|
|
|
20
20
|
<button data-fs-bp-error-button onClick={() => window.location.reload()}>
|
|
21
21
|
Try again
|
|
22
22
|
</button>
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</div>
|
|
23
|
+
{isDevelopment() ? (
|
|
24
|
+
<div data-fs-bp-error-details>
|
|
25
|
+
<span data-fs-bp-error-details-type>{error?.tags?.errorType}</span>
|
|
26
|
+
<h2 data-fs-bp-error-details-title>Error Details</h2>
|
|
27
|
+
<p data-fs-bp-error-details-message>{error?.error.message}</p>
|
|
28
|
+
<p data-fs-bp-error-details-stack>Stack: {error?.error.stack}</p>
|
|
29
|
+
<p data-fs-bp-error-details-component>
|
|
30
|
+
Component: {error?.tags?.component}
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
) : null}
|
|
35
34
|
</div>
|
|
36
35
|
);
|
|
37
36
|
}
|
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
import { Skeleton } from "@faststore/ui";
|
|
2
2
|
|
|
3
|
+
import { useBuyerPortal } from "../../hooks";
|
|
4
|
+
import { BaseTabsLayout } from "../../layouts";
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* PageLoader component displays skeleton loaders while page data is being fetched.
|
|
5
8
|
* Used when cookies are not yet available during pre-fetch scenarios.
|
|
6
9
|
*/
|
|
7
10
|
export const PageLoader = () => {
|
|
11
|
+
const { currentOrgUnit, currentUser } = useBuyerPortal();
|
|
8
12
|
return (
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
<BaseTabsLayout data-fs-bp-page-loader>
|
|
14
|
+
<BaseTabsLayout.Navbar
|
|
15
|
+
orgUnit={currentOrgUnit}
|
|
16
|
+
pageName="Loading"
|
|
17
|
+
person={{
|
|
18
|
+
name: currentUser?.name ?? "",
|
|
19
|
+
role: currentUser?.role ?? "",
|
|
20
|
+
id: currentUser?.id ?? "",
|
|
21
|
+
}}
|
|
22
|
+
loading={true}
|
|
23
|
+
/>
|
|
24
|
+
<BaseTabsLayout.Container>
|
|
25
|
+
<BaseTabsLayout.SidebarMenu
|
|
26
|
+
verticalLinks={[]}
|
|
27
|
+
showLetterHighlight={false}
|
|
28
|
+
loading={true}
|
|
29
|
+
/>
|
|
30
|
+
<BaseTabsLayout.Content>
|
|
31
|
+
<div data-fs-bp-page-loader>
|
|
32
|
+
<div data-fs-bp-page-loader-header>
|
|
33
|
+
<Skeleton size={{ width: "12rem", height: "2rem" }} />
|
|
34
|
+
<Skeleton size={{ width: "8rem", height: "1.5rem" }} />
|
|
35
|
+
</div>
|
|
14
36
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
37
|
+
<div data-fs-bp-page-loader-content>
|
|
38
|
+
<Skeleton size={{ width: "100%", height: "4rem" }} />
|
|
39
|
+
<Skeleton size={{ width: "100%", height: "8rem" }} />
|
|
40
|
+
<Skeleton size={{ width: "100%", height: "8rem" }} />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</BaseTabsLayout.Content>
|
|
44
|
+
</BaseTabsLayout.Container>
|
|
45
|
+
</BaseTabsLayout>
|
|
21
46
|
);
|
|
22
47
|
};
|
|
@@ -13,3 +13,4 @@ export { useAddToScope } from "./useAddToScope";
|
|
|
13
13
|
export { useRemoveFromScope } from "./useRemoveFromScope";
|
|
14
14
|
export { usePageItems, type UsePageItemsProps } from "./usePageItems";
|
|
15
15
|
export { useRouterLoading } from "./useRouterLoading";
|
|
16
|
+
export { useAuth, type UseAuthResult } from "./useAuth";
|
|
@@ -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
|
+
};
|
|
@@ -2,8 +2,9 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { DropdownItem } from "@faststore/ui";
|
|
4
4
|
|
|
5
|
+
import { useContractDetails } from "../../../contracts/hooks";
|
|
6
|
+
import { useOrgUnitBasicData } from "../../../org-units/hooks";
|
|
5
7
|
import { Icon } from "../../components";
|
|
6
|
-
import { useBuyerPortal } from "../../hooks";
|
|
7
8
|
import { getContractSettingsLinks } from "../../utils";
|
|
8
9
|
import { BaseTabsLayout } from "../BaseTabsLayout/BaseTabsLayout";
|
|
9
10
|
|
|
@@ -32,17 +33,19 @@ export const ContractTabsLayout = ({
|
|
|
32
33
|
contractId,
|
|
33
34
|
person,
|
|
34
35
|
}: ContractTabsLayoutProps) => {
|
|
35
|
-
const {
|
|
36
|
+
const { orgUnit } = useOrgUnitBasicData(orgUnitId);
|
|
37
|
+
|
|
38
|
+
const { contract } = useContractDetails(contractId, orgUnitId);
|
|
36
39
|
|
|
37
40
|
const verticalLinks = getContractSettingsLinks({
|
|
38
|
-
contractId:
|
|
39
|
-
orgUnitId:
|
|
41
|
+
contractId: contractId,
|
|
42
|
+
orgUnitId: orgUnitId,
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
return (
|
|
43
46
|
<BaseTabsLayout data-fs-bp-contract-tabs-layout>
|
|
44
47
|
<BaseTabsLayout.Navbar
|
|
45
|
-
orgUnit={
|
|
48
|
+
orgUnit={orgUnit}
|
|
46
49
|
pageName={pageName}
|
|
47
50
|
person={{
|
|
48
51
|
name: person.name,
|
|
@@ -61,7 +64,7 @@ export const ContractTabsLayout = ({
|
|
|
61
64
|
</DropdownItem>
|
|
62
65
|
}
|
|
63
66
|
verticalLinks={verticalLinks}
|
|
64
|
-
name={
|
|
67
|
+
name={contract?.name ?? ""}
|
|
65
68
|
/>
|
|
66
69
|
<BaseTabsLayout.Content>{children}</BaseTabsLayout.Content>
|
|
67
70
|
</BaseTabsLayout.Container>
|
|
@@ -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.
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { OrgUnitsDetailsLayout } from "../features/org-units/layouts";
|
|
2
|
-
import { withErrorBoundary } from "../features/shared/components";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { PageLoader, withErrorBoundary } from "../features/shared/components";
|
|
3
|
+
import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
|
|
4
|
+
import { useAuth } from "../features/shared/hooks";
|
|
5
|
+
import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
|
|
6
|
+
import {
|
|
7
|
+
withLoaderErrorBoundary,
|
|
8
|
+
withProviders,
|
|
9
|
+
} from "../features/shared/utils";
|
|
10
|
+
import {
|
|
11
|
+
ClientContext,
|
|
12
|
+
getClientContext,
|
|
13
|
+
} from "../features/shared/utils/getClientContext";
|
|
5
14
|
|
|
6
|
-
import type {
|
|
15
|
+
import type { LoaderData } from "../features/shared/types";
|
|
7
16
|
|
|
8
17
|
export type OrgUnitDetailsPageData = {
|
|
9
18
|
data: {
|
|
@@ -13,35 +22,52 @@ export type OrgUnitDetailsPageData = {
|
|
|
13
22
|
context: {
|
|
14
23
|
clientContext: ClientContext;
|
|
15
24
|
};
|
|
25
|
+
hasError?: boolean;
|
|
26
|
+
error?: ErrorBoundaryProps;
|
|
16
27
|
};
|
|
17
28
|
|
|
18
29
|
type OrgUnitDetailsPageQuery = {
|
|
19
30
|
orgUnitId: string;
|
|
20
31
|
};
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
const loaderFunction = async (
|
|
23
34
|
data: LoaderData<OrgUnitDetailsPageQuery>
|
|
24
|
-
): Promise<
|
|
35
|
+
): Promise<OrgUnitDetailsPageData> => {
|
|
25
36
|
const { orgUnitId } = data.query;
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
throw new Error(`Missing required query param: orgUnitId=${orgUnitId}`);
|
|
29
|
-
}
|
|
38
|
+
const { cookie, userId, ...clientContext } = await getClientContext(data);
|
|
30
39
|
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
});
|
|
40
|
+
return {
|
|
41
|
+
data: {
|
|
42
|
+
orgUnitId,
|
|
43
|
+
userId,
|
|
44
|
+
},
|
|
45
|
+
context: {
|
|
46
|
+
clientContext: { cookie, userId, ...clientContext },
|
|
47
|
+
},
|
|
48
|
+
};
|
|
42
49
|
};
|
|
43
50
|
|
|
44
|
-
const
|
|
51
|
+
export const loader = withLoaderErrorBoundary(loaderFunction, {
|
|
52
|
+
componentName: "OrgUnitDetailsPage",
|
|
53
|
+
redirectToError: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const OrgUnitDetailsPage = ({
|
|
57
|
+
data,
|
|
58
|
+
hasError,
|
|
59
|
+
error,
|
|
60
|
+
}: OrgUnitDetailsPageData) => {
|
|
61
|
+
if (hasError) {
|
|
62
|
+
return <ErrorTabsLayout error={error} />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
66
|
+
|
|
67
|
+
if (isLoading || isAuthenticated === null) {
|
|
68
|
+
return <PageLoader />;
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
return (
|
|
46
72
|
<OrgUnitsDetailsLayout orgUnitId={data.orgUnitId} userId={data.userId} />
|
|
47
73
|
);
|
package/src/pages/profile.tsx
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { ProfileLayout } from "../features/profile/layouts";
|
|
2
|
-
import { withErrorBoundary } from "../features/shared/components";
|
|
2
|
+
import { PageLoader, withErrorBoundary } from "../features/shared/components";
|
|
3
|
+
import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
|
|
4
|
+
import { useAuth } from "../features/shared/hooks";
|
|
5
|
+
import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
|
|
3
6
|
import {
|
|
4
7
|
type ClientContext,
|
|
5
8
|
withProviders,
|
|
6
|
-
|
|
9
|
+
getClientContext,
|
|
10
|
+
withLoaderErrorBoundary,
|
|
7
11
|
} from "../features/shared/utils";
|
|
8
12
|
|
|
9
|
-
import type {
|
|
13
|
+
import type { LoaderData } from "../features/shared/types";
|
|
10
14
|
|
|
11
15
|
export type ProfilePageData = {
|
|
12
16
|
data: {
|
|
@@ -17,6 +21,8 @@ export type ProfilePageData = {
|
|
|
17
21
|
context: {
|
|
18
22
|
clientContext: ClientContext;
|
|
19
23
|
};
|
|
24
|
+
hasError?: boolean;
|
|
25
|
+
error?: ErrorBoundaryProps;
|
|
20
26
|
};
|
|
21
27
|
|
|
22
28
|
export type ProfilePageQuery = {
|
|
@@ -25,32 +31,51 @@ export type ProfilePageQuery = {
|
|
|
25
31
|
userId: string;
|
|
26
32
|
};
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
const loaderFunction = async (
|
|
29
35
|
data: LoaderData<ProfilePageQuery>
|
|
30
|
-
): Promise<
|
|
36
|
+
): Promise<ProfilePageData> => {
|
|
31
37
|
const { contractId, orgUnitId } = data.query;
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
},
|
|
43
|
-
}
|
|
44
|
-
|
|
39
|
+
const { cookie, userId, ...clientContext } = await getClientContext(data);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
data: {
|
|
43
|
+
contractId,
|
|
44
|
+
orgUnitId,
|
|
45
|
+
userId,
|
|
46
|
+
},
|
|
47
|
+
context: {
|
|
48
|
+
clientContext: { cookie, userId, ...clientContext },
|
|
49
|
+
},
|
|
50
|
+
hasError: false,
|
|
51
|
+
error: undefined,
|
|
52
|
+
};
|
|
45
53
|
};
|
|
46
54
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
55
|
+
export const loader = withLoaderErrorBoundary(loaderFunction, {
|
|
56
|
+
componentName: "ProfilePage",
|
|
57
|
+
redirectToError: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const ProfilePage = ({ data, hasError, error }: ProfilePageData) => {
|
|
61
|
+
if (hasError) {
|
|
62
|
+
return <ErrorTabsLayout error={error} />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
66
|
+
|
|
67
|
+
if (isLoading || isAuthenticated === null) {
|
|
68
|
+
return <PageLoader />;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<ProfileLayout
|
|
73
|
+
orgUnitId={data.orgUnitId}
|
|
74
|
+
contractId={data.contractId}
|
|
75
|
+
userId={data.userId}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
54
79
|
|
|
55
80
|
export default withProviders(
|
|
56
81
|
withErrorBoundary(ProfilePage, {
|