@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.
- package/package.json +1 -1
- package/public/buyer-portal-icons.svg +1 -1
- package/src/features/addresses/services/get-addresses-by-unit-id.service.ts +13 -1
- package/src/features/addresses/services/get-addresses.service.ts +15 -2
- package/src/features/buying-policies/services/get-buying-policies.service.ts +19 -21
- package/src/features/contracts/services/get-contract-details.service.ts +13 -1
- package/src/features/contracts/services/get-contracts-org-by-unit-id.service.ts +18 -11
- package/src/features/contracts/services/update-contract-status.service.ts +18 -11
- package/src/features/profile/layouts/ProfileLayout/profile-layout.scss +1 -0
- package/src/features/shared/clients/Client.ts +84 -8
- package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +4 -1
- package/src/features/shared/components/Error/Error.tsx +31 -0
- package/src/features/shared/components/Error/error.scss +71 -0
- package/src/features/shared/components/ErrorBoundary/ErrorBoundary.tsx +63 -0
- package/src/features/shared/components/ErrorBoundary/types.ts +14 -0
- package/src/features/shared/components/index.ts +3 -0
- package/src/features/shared/components/withErrorBoundary/withErrorBoundary.tsx +35 -0
- package/src/features/shared/layouts/BaseTabsLayout/SidebarMenu.tsx +9 -2
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +4 -2
- package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +119 -0
- package/src/features/shared/layouts/ErrorTabsLayout/error-tabs-layout.scss +1 -0
- package/src/features/shared/utils/environment.ts +41 -0
- package/src/features/shared/utils/extractErrorMessage.ts +22 -0
- package/src/features/shared/utils/getHome.tsx +5 -0
- package/src/features/shared/utils/index.ts +2 -0
- package/src/features/shared/utils/withClientErrorBoundary.ts +61 -0
- package/src/features/shared/utils/withLoaderErrorBoundary.ts +46 -0
- package/src/features/users/clients/UsersClient.ts +0 -1
- package/src/pages/address-details.tsx +38 -11
- package/src/pages/addresses.tsx +35 -6
- package/src/pages/budgets-details.tsx +38 -8
- package/src/pages/budgets.tsx +33 -8
- package/src/pages/buying-policies.tsx +36 -12
- package/src/pages/buying-policy-details.tsx +38 -8
- package/src/pages/collections.tsx +36 -12
- package/src/pages/cost-centers.tsx +38 -8
- package/src/pages/credit-cards.tsx +38 -8
- package/src/pages/home.tsx +22 -5
- package/src/pages/org-unit-details.tsx +43 -7
- package/src/pages/org-units.tsx +39 -8
- package/src/pages/payment-methods.tsx +38 -8
- package/src/pages/po-numbers.tsx +38 -8
- package/src/pages/profile.tsx +31 -6
- package/src/pages/releases.tsx +33 -8
- package/src/pages/role-details.tsx +39 -7
- package/src/pages/roles.tsx +28 -7
- package/src/pages/user-details.tsx +39 -8
- package/src/pages/users.tsx +25 -7
package/package.json
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { compareItems, statusFilters } from "../../shared/utils";
|
|
2
|
+
import {
|
|
3
|
+
handleUnauthorizedRedirect,
|
|
4
|
+
withClientErrorBoundary,
|
|
5
|
+
} from "../../shared/utils/withClientErrorBoundary";
|
|
2
6
|
import { addressesClient } from "../clients/AddressesClient";
|
|
3
7
|
|
|
4
8
|
export type GetAddressesByUnitIdServiceProps = Partial<{
|
|
@@ -13,7 +17,7 @@ export type GetAddressesByUnitIdServiceProps = Partial<{
|
|
|
13
17
|
cookie: string;
|
|
14
18
|
};
|
|
15
19
|
|
|
16
|
-
export const
|
|
20
|
+
export const getAddressesByUnitIdServiceFn = async ({
|
|
17
21
|
orgUnitId,
|
|
18
22
|
status,
|
|
19
23
|
type,
|
|
@@ -46,3 +50,11 @@ export const getAddressesByUnitIdService = async ({
|
|
|
46
50
|
|
|
47
51
|
return { data: formattedAddress, total };
|
|
48
52
|
};
|
|
53
|
+
|
|
54
|
+
export const getAddressesByUnitIdService = withClientErrorBoundary(
|
|
55
|
+
getAddressesByUnitIdServiceFn,
|
|
56
|
+
{
|
|
57
|
+
componentName: "AddressesByUnitId",
|
|
58
|
+
onError: handleUnauthorizedRedirect,
|
|
59
|
+
}
|
|
60
|
+
);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
compareItems,
|
|
3
|
+
statusFilters,
|
|
4
|
+
withClientErrorBoundary,
|
|
5
|
+
} from "../../shared/utils";
|
|
6
|
+
import { handleUnauthorizedRedirect } from "../../shared/utils/withClientErrorBoundary";
|
|
2
7
|
import { addressesClient } from "../clients/AddressesClient";
|
|
3
8
|
|
|
4
9
|
import type { AddressData } from "../types/AddressData";
|
|
@@ -15,7 +20,7 @@ export type GetAddressesServiceProps = Partial<{
|
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
//TODO: Check if deprecated so we can remove this
|
|
18
|
-
export const
|
|
23
|
+
export const getAddressesServiceFn = async ({
|
|
19
24
|
search = "",
|
|
20
25
|
customerId,
|
|
21
26
|
unitId,
|
|
@@ -54,3 +59,11 @@ export const getAddressesService = async ({
|
|
|
54
59
|
})
|
|
55
60
|
.sort((a, b) => compareItems(a, b, sort));
|
|
56
61
|
};
|
|
62
|
+
|
|
63
|
+
export const getAddressesService = withClientErrorBoundary(
|
|
64
|
+
getAddressesServiceFn,
|
|
65
|
+
{
|
|
66
|
+
componentName: "AddressesService",
|
|
67
|
+
onError: handleUnauthorizedRedirect,
|
|
68
|
+
}
|
|
69
|
+
);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withClientErrorBoundary,
|
|
3
|
+
handleUnauthorizedRedirect,
|
|
4
|
+
} from "../../shared/utils/withClientErrorBoundary";
|
|
1
5
|
import { buyingPoliciesClient } from "../clients/BuyingPoliciesClient";
|
|
2
6
|
import { BuyingPolicy } from "../types";
|
|
3
7
|
|
|
@@ -9,7 +13,7 @@ export type GetBuyingPoliciesServiceProps = {
|
|
|
9
13
|
page?: number;
|
|
10
14
|
};
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
const getBuyingPoliciesFunction = async ({
|
|
13
17
|
search,
|
|
14
18
|
page = 1,
|
|
15
19
|
contractId,
|
|
@@ -19,25 +23,19 @@ export const getBuyingPoliciesService = async ({
|
|
|
19
23
|
data: BuyingPolicy[];
|
|
20
24
|
total: number;
|
|
21
25
|
}> => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error("Error in getBuyingPoliciesService:", {
|
|
32
|
-
error,
|
|
33
|
-
orgUnitId,
|
|
34
|
-
contractId,
|
|
35
|
-
errorMessage: (error as { message?: string })?.message,
|
|
36
|
-
});
|
|
26
|
+
return await buyingPoliciesClient.getBuyingPoliciesByOrgUnitId(
|
|
27
|
+
contractId,
|
|
28
|
+
orgUnitId,
|
|
29
|
+
cookie,
|
|
30
|
+
search,
|
|
31
|
+
page
|
|
32
|
+
);
|
|
33
|
+
};
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
export const getBuyingPoliciesService = withClientErrorBoundary(
|
|
36
|
+
getBuyingPoliciesFunction,
|
|
37
|
+
{
|
|
38
|
+
componentName: "BuyingPoliciesService",
|
|
39
|
+
onError: handleUnauthorizedRedirect,
|
|
42
40
|
}
|
|
43
|
-
|
|
41
|
+
);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withClientErrorBoundary,
|
|
3
|
+
handleUnauthorizedRedirect,
|
|
4
|
+
} from "../../shared/utils/withClientErrorBoundary";
|
|
1
5
|
import { contractsClient } from "../clients/ContractsClient";
|
|
2
6
|
|
|
3
7
|
type GetContractDetailsServiceProps = {
|
|
@@ -6,7 +10,7 @@ type GetContractDetailsServiceProps = {
|
|
|
6
10
|
unitId: string;
|
|
7
11
|
};
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
const getContractDetailsFunction = async ({
|
|
10
14
|
contractId,
|
|
11
15
|
cookie,
|
|
12
16
|
unitId,
|
|
@@ -19,3 +23,11 @@ export const getContractDetailsService = async ({
|
|
|
19
23
|
|
|
20
24
|
return contract;
|
|
21
25
|
};
|
|
26
|
+
|
|
27
|
+
export const getContractDetailsService = withClientErrorBoundary(
|
|
28
|
+
getContractDetailsFunction,
|
|
29
|
+
{
|
|
30
|
+
componentName: "ContractDetails",
|
|
31
|
+
onError: handleUnauthorizedRedirect,
|
|
32
|
+
}
|
|
33
|
+
);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withClientErrorBoundary,
|
|
3
|
+
handleUnauthorizedRedirect,
|
|
4
|
+
} from "../../shared/utils/withClientErrorBoundary";
|
|
1
5
|
import { contractsClient } from "../clients/ContractsClient";
|
|
2
6
|
|
|
3
7
|
import type { ContractData } from "../types";
|
|
@@ -10,7 +14,7 @@ export type GetContractsByOrgUnitIdServiceProps = {
|
|
|
10
14
|
cookie: string;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
const getContractsByOrgUnitIdFunction = async ({
|
|
14
18
|
orgUnitId,
|
|
15
19
|
cookie,
|
|
16
20
|
}: GetContractsByOrgUnitIdServiceProps): Promise<ContractData[]> => {
|
|
@@ -20,16 +24,19 @@ export const getContractsByOrgUnitIdService = async ({
|
|
|
20
24
|
return contractsData;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
contractsData.push(...contracts);
|
|
29
|
-
} catch (err) {
|
|
30
|
-
console.error("Failed to fetch contracts", err);
|
|
31
|
-
return contractsData;
|
|
32
|
-
}
|
|
27
|
+
const { contracts } = await contractsClient.getContractsByOrgUnitId(
|
|
28
|
+
orgUnitId,
|
|
29
|
+
cookie
|
|
30
|
+
);
|
|
31
|
+
contractsData.push(...contracts);
|
|
33
32
|
|
|
34
33
|
return contractsData;
|
|
35
34
|
};
|
|
35
|
+
|
|
36
|
+
export const getContractsByOrgUnitIdService = withClientErrorBoundary(
|
|
37
|
+
getContractsByOrgUnitIdFunction,
|
|
38
|
+
{
|
|
39
|
+
componentName: "GetContractsByOrgUnitId",
|
|
40
|
+
onError: handleUnauthorizedRedirect,
|
|
41
|
+
}
|
|
42
|
+
);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withClientErrorBoundary,
|
|
3
|
+
handleUnauthorizedRedirect,
|
|
4
|
+
} from "../../shared/utils/withClientErrorBoundary";
|
|
1
5
|
import { contractsClient } from "../clients/ContractsClient";
|
|
2
6
|
|
|
3
7
|
export type UpdateContractStatusServiceProps = {
|
|
@@ -7,21 +11,24 @@ export type UpdateContractStatusServiceProps = {
|
|
|
7
11
|
unitId: string;
|
|
8
12
|
};
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
const updateContractStatusFunction = async ({
|
|
11
15
|
contractId,
|
|
12
16
|
isActive,
|
|
13
17
|
cookie,
|
|
14
18
|
unitId,
|
|
15
19
|
}: UpdateContractStatusServiceProps): Promise<{ isActive: boolean } | null> => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
20
|
+
const data = await contractsClient.updateContractStatus(
|
|
21
|
+
{ unitId: unitId, contractId, isActive },
|
|
22
|
+
cookie
|
|
23
|
+
);
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
} catch (err) {
|
|
24
|
-
console.error("Failed to update contract status", err);
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
25
|
+
return data;
|
|
27
26
|
};
|
|
27
|
+
|
|
28
|
+
export const updateContractStatusService = withClientErrorBoundary(
|
|
29
|
+
updateContractStatusFunction,
|
|
30
|
+
{
|
|
31
|
+
componentName: "UpdateContractStatus",
|
|
32
|
+
onError: handleUnauthorizedRedirect,
|
|
33
|
+
}
|
|
34
|
+
);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
@import "@faststore/ui/src/components/molecules/Toggle/styles.scss";
|
|
6
6
|
@import "../../../shared/components/InternalTopbar/internal-top-bar.scss";
|
|
7
7
|
@import "../../../shared/components/HeaderInside/header-inside.scss";
|
|
8
|
+
@import "../../../shared/components/Error/error.scss";
|
|
8
9
|
|
|
9
10
|
width: 100%;
|
|
10
11
|
|
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
getAuthFromCookie,
|
|
4
4
|
getCookieWithoutSessionObjects,
|
|
5
5
|
} from "../utils/cookie";
|
|
6
|
+
import { extractErrorMessage } from "../utils/extractErrorMessage";
|
|
6
7
|
|
|
7
|
-
interface RequestConfig<Input =
|
|
8
|
+
interface RequestConfig<Input = unknown> {
|
|
8
9
|
url: string;
|
|
9
10
|
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
10
11
|
data?: Input;
|
|
@@ -12,6 +13,37 @@ interface RequestConfig<Input = never> {
|
|
|
12
13
|
params?: Record<string, string | number | boolean>;
|
|
13
14
|
ignoreContentType?: boolean;
|
|
14
15
|
cache?: RequestCache;
|
|
16
|
+
componentName?: string; // For error tracking
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ClientError extends Error {
|
|
20
|
+
public status?: number;
|
|
21
|
+
public statusText?: string;
|
|
22
|
+
public url?: string;
|
|
23
|
+
public method?: string;
|
|
24
|
+
public responseData?: unknown;
|
|
25
|
+
public componentName?: string;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
message: string,
|
|
29
|
+
options: {
|
|
30
|
+
status?: number;
|
|
31
|
+
statusText?: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
method?: string;
|
|
34
|
+
responseData?: unknown;
|
|
35
|
+
componentName?: string;
|
|
36
|
+
} = {}
|
|
37
|
+
) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "ClientError";
|
|
40
|
+
this.status = options.status;
|
|
41
|
+
this.statusText = options.statusText;
|
|
42
|
+
this.url = options.url;
|
|
43
|
+
this.method = options.method;
|
|
44
|
+
this.responseData = options.responseData;
|
|
45
|
+
this.componentName = options.componentName;
|
|
46
|
+
}
|
|
15
47
|
}
|
|
16
48
|
|
|
17
49
|
class Client {
|
|
@@ -21,6 +53,22 @@ class Client {
|
|
|
21
53
|
this.baseURL = baseURL;
|
|
22
54
|
}
|
|
23
55
|
|
|
56
|
+
private createClientError(
|
|
57
|
+
message: string,
|
|
58
|
+
response: Response,
|
|
59
|
+
responseData: unknown,
|
|
60
|
+
requestConfig: RequestConfig
|
|
61
|
+
): ClientError {
|
|
62
|
+
return new ClientError(message, {
|
|
63
|
+
status: response.status,
|
|
64
|
+
statusText: response.statusText,
|
|
65
|
+
url: this.baseURL + requestConfig.url,
|
|
66
|
+
method: requestConfig.method,
|
|
67
|
+
responseData,
|
|
68
|
+
componentName: requestConfig.componentName,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
24
72
|
async request<Return, Input = never>({
|
|
25
73
|
url,
|
|
26
74
|
method,
|
|
@@ -29,6 +77,7 @@ class Client {
|
|
|
29
77
|
params = {},
|
|
30
78
|
ignoreContentType = false,
|
|
31
79
|
cache = "no-cache",
|
|
80
|
+
componentName,
|
|
32
81
|
}: RequestConfig<Input>): Promise<Return> {
|
|
33
82
|
const config: RequestInit = {
|
|
34
83
|
method,
|
|
@@ -59,24 +108,51 @@ class Client {
|
|
|
59
108
|
}
|
|
60
109
|
|
|
61
110
|
if (!response.ok) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
111
|
+
const errorMessage = extractErrorMessage(response, responseData);
|
|
112
|
+
|
|
113
|
+
const clientError = this.createClientError(
|
|
114
|
+
errorMessage,
|
|
115
|
+
response,
|
|
116
|
+
responseData,
|
|
117
|
+
{
|
|
118
|
+
url,
|
|
119
|
+
method,
|
|
120
|
+
data,
|
|
121
|
+
headers,
|
|
122
|
+
params,
|
|
123
|
+
ignoreContentType,
|
|
124
|
+
cache,
|
|
125
|
+
componentName,
|
|
126
|
+
}
|
|
66
127
|
);
|
|
128
|
+
|
|
129
|
+
throw clientError;
|
|
67
130
|
}
|
|
68
131
|
|
|
69
132
|
return responseData as Return;
|
|
70
133
|
} catch (error) {
|
|
71
|
-
|
|
134
|
+
if (error instanceof ClientError) {
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const clientError = new ClientError(
|
|
139
|
+
error instanceof Error ? error.message : "Network error",
|
|
140
|
+
{
|
|
141
|
+
url: this.baseURL + url,
|
|
142
|
+
method,
|
|
143
|
+
componentName,
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
throw clientError;
|
|
72
148
|
}
|
|
73
149
|
}
|
|
74
150
|
|
|
75
151
|
get<Return>(
|
|
76
152
|
url: string,
|
|
77
|
-
config: Partial<RequestConfig
|
|
153
|
+
config: Partial<RequestConfig<never>> = {}
|
|
78
154
|
): Promise<Return> {
|
|
79
|
-
return this.request<Return>({ url, method: "GET", ...config });
|
|
155
|
+
return this.request<Return, never>({ url, method: "GET", ...config });
|
|
80
156
|
}
|
|
81
157
|
|
|
82
158
|
post<Return, Input>(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createContext, type ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
import { LoadingTabsLayout } from "../../layouts";
|
|
4
|
+
import { ErrorBoundary } from "../ErrorBoundary/ErrorBoundary";
|
|
4
5
|
|
|
5
6
|
import type { ContractData } from "../../../contracts/types";
|
|
6
7
|
import type { OrgUnitBasicData } from "../../../org-units/types";
|
|
@@ -31,7 +32,9 @@ export const BuyerPortalProvider = ({
|
|
|
31
32
|
}: BuyerPortalProviderProps) => {
|
|
32
33
|
return (
|
|
33
34
|
<BuyerPortalContext.Provider value={value}>
|
|
34
|
-
<
|
|
35
|
+
<ErrorBoundary>
|
|
36
|
+
<LoadingTabsLayout>{children}</LoadingTabsLayout>
|
|
37
|
+
</ErrorBoundary>
|
|
35
38
|
</BuyerPortalContext.Provider>
|
|
36
39
|
);
|
|
37
40
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isDevelopment } from "../../utils/environment";
|
|
2
|
+
import { Icon } from "../Icon";
|
|
3
|
+
|
|
4
|
+
export type ErrorProps = {
|
|
5
|
+
error?: Error;
|
|
6
|
+
tags?: {
|
|
7
|
+
component: string;
|
|
8
|
+
errorType: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function Error({ error, tags }: ErrorProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div data-fs-bp-error>
|
|
15
|
+
<Icon name="Warning" width={48} height={48} data-fs-icon-loading="true" />
|
|
16
|
+
<h2 data-fs-bp-error-title>Something went wrong!</h2>
|
|
17
|
+
<button data-fs-bp-error-button onClick={() => window.location.reload()}>
|
|
18
|
+
Try again
|
|
19
|
+
</button>
|
|
20
|
+
{isDevelopment() ? (
|
|
21
|
+
<div data-fs-bp-error-details>
|
|
22
|
+
<span data-fs-bp-error-details-type>{tags?.errorType}</span>
|
|
23
|
+
<h2 data-fs-bp-error-details-title>Error Details</h2>
|
|
24
|
+
<p data-fs-bp-error-details-message>{error?.message}</p>
|
|
25
|
+
<p data-fs-bp-error-details-stack>Stack: {error?.stack}</p>
|
|
26
|
+
<p data-fs-bp-error-details-component>Component: {tags?.component}</p>
|
|
27
|
+
</div>
|
|
28
|
+
) : null}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[data-fs-bp-error] {
|
|
2
|
+
@import "@faststore/ui/src/components/atoms/Icon/styles.scss";
|
|
3
|
+
|
|
4
|
+
flex: 1;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
gap: var(--fs-spacing-4);
|
|
10
|
+
height: 100%;
|
|
11
|
+
min-height: calc(100vh / 2);
|
|
12
|
+
|
|
13
|
+
[data-fs-bp-error-title] {
|
|
14
|
+
font-size: var(--fs-text-size-3);
|
|
15
|
+
font-weight: var(--fs-text-weight-semibold);
|
|
16
|
+
line-height: calc(var(--fs-text-size-4) + var(--fs-scale));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[data-fs-bp-error-button] {
|
|
20
|
+
font-size: var(--fs-text-size-1);
|
|
21
|
+
font-weight: var(--fs-text-weight-semibold);
|
|
22
|
+
color: #1f1f1f;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[data-fs-icon="true"] {
|
|
27
|
+
color: #d31a15;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
[data-fs-bp-error-details] {
|
|
31
|
+
background-color: #1f1f1f;
|
|
32
|
+
border-top: calc(var(--fs-border-width) * 5) solid #d31a15;
|
|
33
|
+
padding: var(--fs-spacing-4);
|
|
34
|
+
border-radius: var(--fs-border-radius-medium);
|
|
35
|
+
position: relative;
|
|
36
|
+
|
|
37
|
+
[data-fs-bp-error-details-type] {
|
|
38
|
+
position: absolute;
|
|
39
|
+
right: var(--fs-spacing-4);
|
|
40
|
+
top: var(--fs-spacing-4);
|
|
41
|
+
background: #f1f1f1;
|
|
42
|
+
padding: var(--fs-spacing-1);
|
|
43
|
+
border-radius: var(--fs-border-radius-medium);
|
|
44
|
+
font-size: var(--fs-text-size-0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
[data-fs-bp-error-details-title] {
|
|
48
|
+
font-size: var(--fs-text-size-3);
|
|
49
|
+
color: #e2e2e2;
|
|
50
|
+
font-weight: var(--fs-text-weight-semibold);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-fs-bp-error-details-message] {
|
|
54
|
+
margin: var(--fs-spacing-4) 0;
|
|
55
|
+
font-size: var(--fs-text-size-2);
|
|
56
|
+
font-weight: var(--fs-text-weight-medium);
|
|
57
|
+
color: #e2e2e2;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[data-fs-bp-error-details-stack] {
|
|
61
|
+
font-size: var(--fs-text-size-2);
|
|
62
|
+
margin: var(--fs-spacing-4) 0;
|
|
63
|
+
font-weight: var(--fs-text-weight-medium);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[data-fs-bp-error-details-component] {
|
|
67
|
+
font-size: var(--fs-text-size-2);
|
|
68
|
+
font-weight: var(--fs-text-weight-medium);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { Component, type ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { ErrorTabsLayout } from "../../layouts/ErrorTabsLayout/ErrorTabsLayout";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
tags?: {
|
|
11
|
+
component: string;
|
|
12
|
+
errorType: string;
|
|
13
|
+
};
|
|
14
|
+
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface State {
|
|
18
|
+
hasError: boolean;
|
|
19
|
+
error?: Error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
23
|
+
constructor(props: Props) {
|
|
24
|
+
super(props);
|
|
25
|
+
this.state = { hasError: false };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static getDerivedStateFromError(error: Error): State {
|
|
29
|
+
return { hasError: true, error };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
33
|
+
if (this.props.onError) {
|
|
34
|
+
this.props.onError(error, errorInfo);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render() {
|
|
39
|
+
if (this.state.hasError) {
|
|
40
|
+
if (this.props.fallback) {
|
|
41
|
+
return this.props.fallback;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<ErrorTabsLayout
|
|
46
|
+
error={{
|
|
47
|
+
error: this.state.error ?? {
|
|
48
|
+
message: "Unknown error",
|
|
49
|
+
name: "Unknown error",
|
|
50
|
+
stack: "Unknown error",
|
|
51
|
+
},
|
|
52
|
+
tags: this.props.tags ?? {
|
|
53
|
+
component: "Unknown",
|
|
54
|
+
errorType: "Unknown",
|
|
55
|
+
},
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this.props.children;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -72,3 +72,6 @@ export type { CounterProps as PaginatorCounterProps } from "./Paginator/Counter"
|
|
|
72
72
|
export type { NextPageButtonProps as PaginatorNextPageButtonProps } from "./Paginator/NextPageButton";
|
|
73
73
|
|
|
74
74
|
export { EmptyState, type EmptyStateProps } from "./EmptyState/EmptyState";
|
|
75
|
+
export { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
|
|
76
|
+
export { withErrorBoundary } from "./withErrorBoundary/withErrorBoundary";
|
|
77
|
+
export { default as Error } from "./Error/Error";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
import { ErrorBoundary } from "../ErrorBoundary/ErrorBoundary";
|
|
6
|
+
|
|
7
|
+
type WithErrorBoundaryOptions = {
|
|
8
|
+
fallback?: React.ReactNode;
|
|
9
|
+
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
|
10
|
+
tags?: {
|
|
11
|
+
component: string;
|
|
12
|
+
errorType: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function withErrorBoundary<P extends Record<string, unknown>>(
|
|
17
|
+
WrappedComponent: React.ComponentType<P>,
|
|
18
|
+
options: WithErrorBoundaryOptions = {}
|
|
19
|
+
) {
|
|
20
|
+
const { onError, tags } = options;
|
|
21
|
+
|
|
22
|
+
const ComponentWithErrorBoundary = (props: P) => {
|
|
23
|
+
return (
|
|
24
|
+
<ErrorBoundary onError={onError} tags={tags}>
|
|
25
|
+
<WrappedComponent {...props} />
|
|
26
|
+
</ErrorBoundary>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
ComponentWithErrorBoundary.displayName = `WithErrorBoundary(${
|
|
31
|
+
WrappedComponent.displayName || WrappedComponent.name || "Component"
|
|
32
|
+
})`;
|
|
33
|
+
|
|
34
|
+
return ComponentWithErrorBoundary;
|
|
35
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
+
import { useRouter } from "next/router";
|
|
4
|
+
|
|
3
5
|
import { VerticalNav } from "../../components";
|
|
4
6
|
import { useBuyerPortal } from "../../hooks";
|
|
5
7
|
import {
|
|
@@ -22,6 +24,11 @@ export type SidebarMenuProps = {
|
|
|
22
24
|
export const SidebarMenu = ({ ...otherProps }: SidebarMenuProps) => {
|
|
23
25
|
const { currentOrgUnit, currentContract } = useBuyerPortal();
|
|
24
26
|
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
|
|
29
|
+
const orgUnitId = router.query.orgUnitId as string;
|
|
30
|
+
const customerId = router.query.contractId as string;
|
|
31
|
+
|
|
25
32
|
const idContract =
|
|
26
33
|
(currentContract?.id
|
|
27
34
|
? currentContract?.id
|
|
@@ -31,8 +38,8 @@ export const SidebarMenu = ({ ...otherProps }: SidebarMenuProps) => {
|
|
|
31
38
|
<aside data-fs-bp-sidebar-menu {...otherProps}>
|
|
32
39
|
<VerticalNav.Menu title="Contract">
|
|
33
40
|
{getContractSettingsLinks({
|
|
34
|
-
orgUnitId: currentOrgUnit?.id ?? "",
|
|
35
|
-
contractId: idContract,
|
|
41
|
+
orgUnitId: orgUnitId ?? currentOrgUnit?.id ?? "",
|
|
42
|
+
contractId: customerId ?? idContract,
|
|
36
43
|
}).map((option) => (
|
|
37
44
|
<VerticalNav.Link key={option.name} link={option.link}>
|
|
38
45
|
{option.name}
|