@vtex/faststore-plugin-buyer-portal 1.1.92 → 1.1.93

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 (48) hide show
  1. package/package.json +1 -1
  2. package/public/buyer-portal-icons.svg +1 -1
  3. package/src/features/addresses/services/get-addresses-by-unit-id.service.ts +13 -1
  4. package/src/features/addresses/services/get-addresses.service.ts +15 -2
  5. package/src/features/buying-policies/services/get-buying-policies.service.ts +19 -21
  6. package/src/features/contracts/services/get-contract-details.service.ts +13 -1
  7. package/src/features/contracts/services/get-contracts-org-by-unit-id.service.ts +18 -11
  8. package/src/features/contracts/services/update-contract-status.service.ts +18 -11
  9. package/src/features/profile/layouts/ProfileLayout/profile-layout.scss +1 -0
  10. package/src/features/shared/clients/Client.ts +84 -8
  11. package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +4 -1
  12. package/src/features/shared/components/Error/Error.tsx +31 -0
  13. package/src/features/shared/components/Error/error.scss +71 -0
  14. package/src/features/shared/components/ErrorBoundary/ErrorBoundary.tsx +63 -0
  15. package/src/features/shared/components/ErrorBoundary/types.ts +14 -0
  16. package/src/features/shared/components/index.ts +3 -0
  17. package/src/features/shared/components/withErrorBoundary/withErrorBoundary.tsx +35 -0
  18. package/src/features/shared/layouts/BaseTabsLayout/SidebarMenu.tsx +9 -2
  19. package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +4 -2
  20. package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +119 -0
  21. package/src/features/shared/layouts/ErrorTabsLayout/error-tabs-layout.scss +1 -0
  22. package/src/features/shared/utils/environment.ts +41 -0
  23. package/src/features/shared/utils/extractErrorMessage.ts +22 -0
  24. package/src/features/shared/utils/getHome.tsx +5 -0
  25. package/src/features/shared/utils/index.ts +2 -0
  26. package/src/features/shared/utils/withClientErrorBoundary.ts +61 -0
  27. package/src/features/shared/utils/withLoaderErrorBoundary.ts +46 -0
  28. package/src/features/users/clients/UsersClient.ts +0 -1
  29. package/src/pages/address-details.tsx +38 -11
  30. package/src/pages/addresses.tsx +35 -6
  31. package/src/pages/budgets-details.tsx +38 -8
  32. package/src/pages/budgets.tsx +33 -8
  33. package/src/pages/buying-policies.tsx +36 -12
  34. package/src/pages/buying-policy-details.tsx +38 -8
  35. package/src/pages/collections.tsx +36 -12
  36. package/src/pages/cost-centers.tsx +38 -8
  37. package/src/pages/credit-cards.tsx +38 -8
  38. package/src/pages/home.tsx +22 -5
  39. package/src/pages/org-unit-details.tsx +43 -7
  40. package/src/pages/org-units.tsx +39 -8
  41. package/src/pages/payment-methods.tsx +38 -8
  42. package/src/pages/po-numbers.tsx +38 -8
  43. package/src/pages/profile.tsx +31 -6
  44. package/src/pages/releases.tsx +33 -8
  45. package/src/pages/role-details.tsx +39 -7
  46. package/src/pages/roles.tsx +28 -7
  47. package/src/pages/user-details.tsx +39 -8
  48. package/src/pages/users.tsx +25 -7
@@ -27,12 +27,14 @@ export const ContractTabsLayout = ({
27
27
  pageName,
28
28
  loading = false,
29
29
  children,
30
+ orgUnitId,
31
+ contractId,
30
32
  }: ContractTabsLayoutProps) => {
31
33
  const { currentOrgUnit, currentUser, currentContract } = useBuyerPortal();
32
34
 
33
35
  const verticalLinks = getContractSettingsLinks({
34
- contractId: currentContract?.id ?? "",
35
- orgUnitId: currentOrgUnit?.id ?? "",
36
+ contractId: currentContract?.id ?? contractId ?? "",
37
+ orgUnitId: currentOrgUnit?.id ?? orgUnitId ?? "",
36
38
  });
37
39
 
38
40
  return (
@@ -0,0 +1,119 @@
1
+ import { type ReactNode } from "react";
2
+
3
+ import { useRouter } from "next/router";
4
+
5
+ import { OrgUnitDetailsNavbar } from "../../../org-units/components";
6
+ import { HeaderInside } from "../../components";
7
+ import Error from "../../components/Error/Error";
8
+ import { useBuyerPortal } from "../../hooks";
9
+ import { getTabsLayoutConfigFromRoute } from "../../utils/routeLayoutMapping";
10
+ import { ContractTabsLayout } from "../ContractTabsLayout/ContractTabsLayout";
11
+ import { FinanceTabsLayout } from "../FinanceTabsLayout/FinanceTabsLayout";
12
+ import { OrgUnitTabsLayout } from "../OrgUnitTabsLayout/OrgUnitTabLayout";
13
+
14
+ export type ErrorTabsLayoutContentProps = {
15
+ title?: string;
16
+ error?: {
17
+ error: Error;
18
+ tags: {
19
+ component: string;
20
+ errorType: string;
21
+ };
22
+ };
23
+ };
24
+
25
+ export type ErrorTabsLayoutProps = {
26
+ children?: ReactNode;
27
+ error?: {
28
+ error: Error;
29
+ tags: {
30
+ component: string;
31
+ errorType: string;
32
+ };
33
+ };
34
+ };
35
+
36
+ export const ErrorTabsLayoutContent = ({
37
+ title,
38
+ error,
39
+ }: ErrorTabsLayoutContentProps) => (
40
+ <div>
41
+ <section data-fs-bp-profile>
42
+ <HeaderInside title={title} />
43
+ <Error error={error?.error} tags={error?.tags} />
44
+ </section>
45
+ </div>
46
+ );
47
+
48
+ export const ErrorTabsLayout = ({ children, error }: ErrorTabsLayoutProps) => {
49
+ const router = useRouter();
50
+
51
+ const {
52
+ currentOrgUnit: orgUnit,
53
+ currentUser: user,
54
+ currentContract: contract,
55
+ } = useBuyerPortal();
56
+
57
+ const orgUnitId = router.query.orgUnitId as string;
58
+ const customerId = router.query.contractId as string;
59
+
60
+ const layoutConfig = getTabsLayoutConfigFromRoute(router.route);
61
+
62
+ if (!layoutConfig) {
63
+ return <>{children}</>;
64
+ }
65
+
66
+ const pageTitle = layoutConfig.pageTitle;
67
+
68
+ switch (layoutConfig.layout) {
69
+ case "ContractTabsLayout":
70
+ return (
71
+ <ContractTabsLayout
72
+ orgUnitName={orgUnit?.name ?? ""}
73
+ orgUnitId={orgUnitId}
74
+ contractId={customerId}
75
+ contractName={contract?.name ?? ""}
76
+ pageName="Contract"
77
+ person={{
78
+ image: undefined,
79
+ name: user?.name ?? "",
80
+ role: user?.role ?? "",
81
+ }}
82
+ >
83
+ <ErrorTabsLayoutContent title={pageTitle} error={error} />
84
+ </ContractTabsLayout>
85
+ );
86
+
87
+ case "FinanceTabsLayout":
88
+ return (
89
+ <FinanceTabsLayout pageName={layoutConfig.pageName}>
90
+ <ErrorTabsLayoutContent title={pageTitle} error={error} />
91
+ </FinanceTabsLayout>
92
+ );
93
+
94
+ case "OrgUnitTabsLayout":
95
+ return (
96
+ <OrgUnitTabsLayout pageName={layoutConfig.pageName}>
97
+ <ErrorTabsLayoutContent title={pageTitle} error={error} />
98
+ </OrgUnitTabsLayout>
99
+ );
100
+
101
+ case "OrgUnitDetailsLayout":
102
+ return (
103
+ <div data-fs-bp-create-user-error-state>
104
+ <OrgUnitDetailsNavbar
105
+ orgName={orgUnit?.name ?? ""}
106
+ person={{
107
+ name: user?.name ?? "",
108
+ role: user?.role ?? "",
109
+ }}
110
+ loading={false}
111
+ />
112
+ <ErrorTabsLayoutContent title={pageTitle} error={error} />
113
+ </div>
114
+ );
115
+
116
+ default:
117
+ return <ErrorTabsLayoutContent error={error} />;
118
+ }
119
+ };
@@ -0,0 +1 @@
1
+ @import "../../components//Error/error.scss";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Utility functions for environment detection
3
+ */
4
+
5
+ /**
6
+ * Checks if the application is running on localhost
7
+ * @returns {boolean} True if running on localhost, false otherwise
8
+ */
9
+ export const isLocalhost = (): boolean => {
10
+ if (typeof window === "undefined") {
11
+ return false;
12
+ }
13
+
14
+ const hostname = window.location.hostname;
15
+
16
+ return (
17
+ hostname === "localhost" ||
18
+ hostname === "127.0.0.1" ||
19
+ hostname === "0.0.0.0" ||
20
+ hostname.startsWith("192.168.") ||
21
+ hostname.startsWith("10.") ||
22
+ hostname.startsWith("172.") ||
23
+ hostname.endsWith(".local")
24
+ );
25
+ };
26
+
27
+ /**
28
+ * Checks if the application is running in development mode
29
+ * @returns {boolean} True if in development mode, false otherwise
30
+ */
31
+ export const isDevelopment = (): boolean => {
32
+ return process.env.NODE_ENV === "development";
33
+ };
34
+
35
+ /**
36
+ * Checks if detailed error information should be shown
37
+ * @returns {boolean} True if error details should be shown, false otherwise
38
+ */
39
+ export const shouldShowErrorDetails = (): boolean => {
40
+ return isLocalhost() && isDevelopment();
41
+ };
@@ -0,0 +1,22 @@
1
+ export const extractErrorMessage = (
2
+ response: Response,
3
+ responseData: unknown
4
+ ): string => {
5
+ if (
6
+ responseData &&
7
+ typeof responseData === "object" &&
8
+ "message" in responseData
9
+ ) {
10
+ return String((responseData as { message: unknown }).message);
11
+ }
12
+
13
+ if (responseData) {
14
+ try {
15
+ return JSON.stringify(responseData);
16
+ } catch {
17
+ return String(responseData);
18
+ }
19
+ }
20
+
21
+ return `HTTP ${response.status}: ${response.statusText}`;
22
+ };
@@ -0,0 +1,5 @@
1
+ import storeConfig from "discovery.config";
2
+
3
+ export const goHome = () => {
4
+ window.location.assign(storeConfig.storeUrl);
5
+ };
@@ -33,3 +33,5 @@ export {
33
33
  } from "./search";
34
34
  export { toQueryParams } from "./toQueryParams";
35
35
  export { getValidPage } from "./getValidPage";
36
+ export { withLoaderErrorBoundary } from "./withLoaderErrorBoundary";
37
+ export { withClientErrorBoundary } from "./withClientErrorBoundary";
@@ -0,0 +1,61 @@
1
+ import { goHome } from "./getHome";
2
+
3
+ import type { ClientError } from "../clients/Client";
4
+
5
+ interface WithClientErrorBoundaryOptions {
6
+ componentName?: string;
7
+ onError?: (
8
+ error: ClientError,
9
+ requestInfo: {
10
+ url: string;
11
+ method: string;
12
+ data?: unknown;
13
+ }
14
+ ) => void;
15
+ }
16
+
17
+ export const handleUnauthorizedRedirect = (error: ClientError) => {
18
+ if (error.status === 401) {
19
+ if (typeof window !== "undefined") {
20
+ goHome();
21
+ }
22
+ }
23
+ };
24
+
25
+ export function withClientErrorBoundary<TArgs extends unknown[], TReturn>(
26
+ clientFunction: (...args: TArgs) => Promise<TReturn>,
27
+ options: WithClientErrorBoundaryOptions = {}
28
+ ) {
29
+ const { componentName, onError } = options;
30
+
31
+ return async (...args: TArgs): Promise<TReturn> => {
32
+ try {
33
+ return await clientFunction(...args);
34
+ } catch (error) {
35
+ if (onError && error instanceof Error) {
36
+ const clientError = error as Partial<ClientError>;
37
+ const requestInfo = {
38
+ url:
39
+ typeof clientError.url === "string" ? clientError.url : "unknown",
40
+ method:
41
+ typeof clientError.method === "string"
42
+ ? clientError.method
43
+ : "unknown",
44
+ data:
45
+ clientError.responseData !== undefined
46
+ ? clientError.responseData
47
+ : undefined,
48
+ };
49
+
50
+ onError(error as ClientError, requestInfo);
51
+ }
52
+
53
+ if (error instanceof Error && (error as ClientError).status === 401) {
54
+ return null as TReturn;
55
+ }
56
+
57
+ console.error(`Error in ${componentName || "client function"}:`, error);
58
+ throw error;
59
+ }
60
+ };
61
+ }
@@ -0,0 +1,46 @@
1
+ import type { LoaderData } from "../types";
2
+
3
+ interface WithLoaderErrorBoundaryOptions {
4
+ componentName?: string;
5
+ onError?: (error: Error, query: unknown) => void;
6
+ redirectToError?: boolean;
7
+ }
8
+
9
+ export function withLoaderErrorBoundary<TQuery, TReturn>(
10
+ loaderFn: (data: LoaderData<TQuery>) => Promise<TReturn>,
11
+ options: WithLoaderErrorBoundaryOptions = {}
12
+ ) {
13
+ const { componentName, onError, redirectToError = true } = options;
14
+
15
+ return async (data: LoaderData<TQuery>): Promise<TReturn> => {
16
+ try {
17
+ return await loaderFn(data);
18
+ } catch (error) {
19
+ if (onError) {
20
+ onError(error as Error, data.query);
21
+ }
22
+
23
+ if (redirectToError) {
24
+ return {
25
+ error: {
26
+ error: {
27
+ message: (error as Error).message,
28
+ name: (error as Error).name,
29
+ stack: (error as Error).stack,
30
+ },
31
+ tags: {
32
+ component: componentName || "Unknown",
33
+ errorType: "loader_error",
34
+ },
35
+ extra: {
36
+ query: data.query,
37
+ },
38
+ },
39
+ hasError: true,
40
+ } as TReturn;
41
+ }
42
+
43
+ throw error;
44
+ }
45
+ };
46
+ }
@@ -48,7 +48,6 @@ class UsersClient extends Client {
48
48
  });
49
49
  } catch (err) {
50
50
  console.error(err);
51
- console.error(`Error listing ${page} Users page(s)`);
52
51
  }
53
52
 
54
53
  return { users, total };
@@ -4,15 +4,21 @@ import { getAddressLocationService } from "../features/addresses/services/locati
4
4
  import { getAddressRecipientsService } from "../features/addresses/services/recipients/get-address-recipients.service";
5
5
  import { getContractDetailsService } from "../features/contracts/services";
6
6
  import { getOrgUnitBasicDataService } from "../features/org-units/services";
7
- import { BuyerPortalProvider } from "../features/shared/components";
7
+ import {
8
+ BuyerPortalProvider,
9
+ withErrorBoundary,
10
+ } from "../features/shared/components";
11
+ import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
8
12
  import {
9
13
  PAGE_PARAMS,
10
14
  SEARCH_PARAMS,
11
15
  } from "../features/shared/hooks/usePageItems";
16
+ import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
12
17
  import {
13
18
  type ClientContext,
14
19
  getClientContext,
15
20
  getValidPage,
21
+ withLoaderErrorBoundary,
16
22
  } from "../features/shared/utils";
17
23
  import { getUserByIdService } from "../features/users/services";
18
24
 
@@ -57,11 +63,13 @@ export type AddressDetailsPageData = {
57
63
  currentContract: ContractData | null;
58
64
  currentUser: UserData | null;
59
65
  };
66
+ hasError?: boolean;
67
+ error?: ErrorBoundaryProps;
60
68
  };
61
69
 
62
- export async function loader(
70
+ const loaderFunction = async (
63
71
  data: LoaderData<AddressDetailsPageQuery>
64
- ): Promise<AddressDetailsPageData> {
72
+ ): Promise<AddressDetailsPageData> => {
65
73
  const {
66
74
  contractId,
67
75
  orgUnitId,
@@ -135,16 +143,35 @@ export async function loader(
135
143
  currentUser: user,
136
144
  },
137
145
  };
138
- }
146
+ };
147
+
148
+ export const loader = withLoaderErrorBoundary(loaderFunction, {
149
+ componentName: "AddressDetailsPage",
150
+ redirectToError: true,
151
+ });
139
152
 
140
- const AddressDetailsPage = ({ data, context }: AddressDetailsPageData) => (
153
+ const AddressDetailsPage = ({
154
+ data,
155
+ context,
156
+ hasError,
157
+ error,
158
+ }: AddressDetailsPageData) => (
141
159
  <BuyerPortalProvider {...context}>
142
- <AddressDetailsLayout
143
- addressDetails={data.addressDetails}
144
- recipientsData={data.recipientsData ?? DEFAULT_ADDRESS_PAGINATED_DATA}
145
- locationsData={data.locationsData ?? DEFAULT_ADDRESS_PAGINATED_DATA}
146
- />
160
+ {hasError ? (
161
+ <ErrorTabsLayout error={error} />
162
+ ) : (
163
+ <AddressDetailsLayout
164
+ addressDetails={data.addressDetails}
165
+ recipientsData={data.recipientsData ?? DEFAULT_ADDRESS_PAGINATED_DATA}
166
+ locationsData={data.locationsData ?? DEFAULT_ADDRESS_PAGINATED_DATA}
167
+ />
168
+ )}
147
169
  </BuyerPortalProvider>
148
170
  );
149
171
 
150
- export default AddressDetailsPage;
172
+ export default withErrorBoundary(AddressDetailsPage, {
173
+ tags: {
174
+ component: "AddressDetailsPage",
175
+ errorType: "address_details_error",
176
+ },
177
+ });
@@ -5,11 +5,17 @@ import {
5
5
  } from "../features/addresses/services";
6
6
  import { getContractDetailsService } from "../features/contracts/services";
7
7
  import { getOrgUnitBasicDataService } from "../features/org-units/services";
8
- import { BuyerPortalProvider } from "../features/shared/components";
8
+ import {
9
+ BuyerPortalProvider,
10
+ withErrorBoundary,
11
+ } from "../features/shared/components";
12
+ import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
13
+ import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
9
14
  import {
10
15
  type ClientContext,
11
16
  getClientContext,
12
17
  getValidPage,
18
+ withLoaderErrorBoundary,
13
19
  } from "../features/shared/utils";
14
20
  import { getUserByIdService } from "../features/users/services";
15
21
 
@@ -30,6 +36,8 @@ export type AddressesPageData = {
30
36
  currentOrgUnit: OrgUnitBasicData | null;
31
37
  currentUser: UserData | null;
32
38
  };
39
+ hasError?: boolean;
40
+ error?: ErrorBoundaryProps;
33
41
  };
34
42
 
35
43
  export type AddressesPageQuery = GetAddressesServiceProps & {
@@ -38,9 +46,9 @@ export type AddressesPageQuery = GetAddressesServiceProps & {
38
46
  page?: string;
39
47
  };
40
48
 
41
- export async function loader(
49
+ const loaderFunction = async (
42
50
  data: LoaderData<AddressesPageQuery>
43
- ): Promise<AddressesPageData> {
51
+ ): Promise<AddressesPageData> => {
44
52
  const { contractId, orgUnitId, search, page: pageString } = data.query;
45
53
 
46
54
  const page = getValidPage(pageString);
@@ -81,7 +89,12 @@ export async function loader(
81
89
  currentContract: contract,
82
90
  },
83
91
  };
84
- }
92
+ };
93
+
94
+ export const loader = withLoaderErrorBoundary(loaderFunction, {
95
+ componentName: "AddressesPage",
96
+ redirectToError: true,
97
+ });
85
98
 
86
99
  const AddressPage = ({
87
100
  data,
@@ -89,10 +102,26 @@ const AddressPage = ({
89
102
  page,
90
103
  total,
91
104
  context,
105
+ hasError,
106
+ error,
92
107
  }: AddressesPageData) => (
93
108
  <BuyerPortalProvider {...context}>
94
- <AddressLayout addresses={data} search={search} page={page} total={total} />
109
+ {hasError ? (
110
+ <ErrorTabsLayout error={error} />
111
+ ) : (
112
+ <AddressLayout
113
+ addresses={data}
114
+ search={search}
115
+ page={page}
116
+ total={total}
117
+ />
118
+ )}
95
119
  </BuyerPortalProvider>
96
120
  );
97
121
 
98
- export default AddressPage;
122
+ export default withErrorBoundary(AddressPage, {
123
+ tags: {
124
+ component: "AddressesPage",
125
+ errorType: "addresses_error",
126
+ },
127
+ });
@@ -3,8 +3,17 @@ import { getBudgetByIdService } from "../features/budgets/services";
3
3
  import { Budget } from "../features/budgets/types";
4
4
  import { getContractDetailsService } from "../features/contracts/services";
5
5
  import { getOrgUnitBasicDataService } from "../features/org-units/services";
6
- import { BuyerPortalProvider } from "../features/shared/components";
7
- import { type ClientContext, getClientContext } from "../features/shared/utils";
6
+ import {
7
+ BuyerPortalProvider,
8
+ withErrorBoundary,
9
+ } from "../features/shared/components";
10
+ import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
11
+ import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
12
+ import {
13
+ type ClientContext,
14
+ getClientContext,
15
+ withLoaderErrorBoundary,
16
+ } from "../features/shared/utils";
8
17
  import { getUserByIdService } from "../features/users/services";
9
18
 
10
19
  import type { ContractData } from "../features/contracts/types";
@@ -20,6 +29,8 @@ export type BudgetsDetailsData = {
20
29
  currentContract: ContractData | null;
21
30
  currentUser: UserData | null;
22
31
  };
32
+ hasError?: boolean;
33
+ error?: ErrorBoundaryProps;
23
34
  };
24
35
 
25
36
  export type BudgetsDetailsQuery = {
@@ -28,9 +39,9 @@ export type BudgetsDetailsQuery = {
28
39
  budgetId: string;
29
40
  };
30
41
 
31
- export async function loader(
42
+ const loaderFunction = async (
32
43
  data: LoaderData<BudgetsDetailsQuery>
33
- ): Promise<BudgetsDetailsData> {
44
+ ): Promise<BudgetsDetailsData> => {
34
45
  const { customerId, cookie, userId, ...clientContext } =
35
46
  await getClientContext(data);
36
47
 
@@ -61,12 +72,31 @@ export async function loader(
61
72
  currentUser: user,
62
73
  },
63
74
  };
64
- }
75
+ };
76
+
77
+ export const loader = withLoaderErrorBoundary(loaderFunction, {
78
+ componentName: "BudgetsDetailsPage",
79
+ redirectToError: true,
80
+ });
65
81
 
66
- const ContractsPage = ({ budget, context }: BudgetsDetailsData) => (
82
+ const BudgetsDetailsPage = ({
83
+ budget,
84
+ context,
85
+ hasError,
86
+ error,
87
+ }: BudgetsDetailsData) => (
67
88
  <BuyerPortalProvider {...context}>
68
- <BudgetsDetailsLayout budget={budget} />
89
+ {hasError ? (
90
+ <ErrorTabsLayout error={error} />
91
+ ) : (
92
+ <BudgetsDetailsLayout budget={budget} />
93
+ )}
69
94
  </BuyerPortalProvider>
70
95
  );
71
96
 
72
- export default ContractsPage;
97
+ export default withErrorBoundary(BudgetsDetailsPage, {
98
+ tags: {
99
+ component: "BudgetsDetailsPage",
100
+ errorType: "budgets_details_error",
101
+ },
102
+ });
@@ -3,8 +3,17 @@ import { listBudgetsService } from "../features/budgets/services";
3
3
  import { BudgetListResponse } from "../features/budgets/types";
4
4
  import { getContractDetailsService } from "../features/contracts/services";
5
5
  import { getOrgUnitBasicDataService } from "../features/org-units/services";
6
- import { BuyerPortalProvider } from "../features/shared/components";
7
- import { type ClientContext, getClientContext } from "../features/shared/utils";
6
+ import {
7
+ BuyerPortalProvider,
8
+ withErrorBoundary,
9
+ } from "../features/shared/components";
10
+ import { ErrorBoundaryProps } from "../features/shared/components/ErrorBoundary/types";
11
+ import { ErrorTabsLayout } from "../features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout";
12
+ import {
13
+ type ClientContext,
14
+ getClientContext,
15
+ withLoaderErrorBoundary,
16
+ } from "../features/shared/utils";
8
17
  import { getUserByIdService } from "../features/users/services";
9
18
 
10
19
  import type { ContractData } from "../features/contracts/types";
@@ -20,6 +29,8 @@ export type BudgetPageData = {
20
29
  currentOrgUnit: OrgUnitBasicData | null;
21
30
  currentUser: UserData | null;
22
31
  };
32
+ hasError?: boolean;
33
+ error?: ErrorBoundaryProps;
23
34
  };
24
35
 
25
36
  export type BudgetPageQuery = {
@@ -27,9 +38,9 @@ export type BudgetPageQuery = {
27
38
  orgUnitId: string;
28
39
  };
29
40
 
30
- export async function loader(
41
+ const loaderFunction = async (
31
42
  data: LoaderData<BudgetPageQuery>
32
- ): Promise<BudgetPageData> {
43
+ ): Promise<BudgetPageData> => {
33
44
  const { customerId, cookie, userId, ...clientContext } =
34
45
  await getClientContext(data);
35
46
 
@@ -62,12 +73,26 @@ export async function loader(
62
73
  currentUser: user,
63
74
  },
64
75
  };
65
- }
76
+ };
77
+
78
+ export const loader = withLoaderErrorBoundary(loaderFunction, {
79
+ componentName: "BudgetsPage",
80
+ redirectToError: true,
81
+ });
66
82
 
67
- const ContractsPage = ({ data, context }: BudgetPageData) => (
83
+ const ContractsPage = ({ data, context, hasError, error }: BudgetPageData) => (
68
84
  <BuyerPortalProvider {...context}>
69
- <BudgetsLayout data={data} />
85
+ {hasError ? (
86
+ <ErrorTabsLayout error={error} />
87
+ ) : (
88
+ <BudgetsLayout data={data} />
89
+ )}
70
90
  </BuyerPortalProvider>
71
91
  );
72
92
 
73
- export default ContractsPage;
93
+ export default withErrorBoundary(ContractsPage, {
94
+ tags: {
95
+ component: "BudgetsPage",
96
+ errorType: "budgets_error",
97
+ },
98
+ });