@vtex/faststore-plugin-buyer-portal 1.1.91 → 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 (55) 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/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +3 -1
  10. package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumb.tsx +2 -0
  11. package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumbPath.tsx +12 -12
  12. package/src/features/org-units/components/OrgUnitBreadcrumb/org-unit-breadcrumb.scss +47 -36
  13. package/src/features/org-units/types/OrgUnitBreadcrumbTypes.ts +2 -0
  14. package/src/features/profile/layouts/ProfileLayout/profile-layout.scss +1 -0
  15. package/src/features/shared/clients/Client.ts +84 -8
  16. package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +4 -1
  17. package/src/features/shared/components/Error/Error.tsx +31 -0
  18. package/src/features/shared/components/Error/error.scss +71 -0
  19. package/src/features/shared/components/ErrorBoundary/ErrorBoundary.tsx +63 -0
  20. package/src/features/shared/components/ErrorBoundary/types.ts +14 -0
  21. package/src/features/shared/components/index.ts +3 -0
  22. package/src/features/shared/components/withErrorBoundary/withErrorBoundary.tsx +35 -0
  23. package/src/features/shared/layouts/BaseTabsLayout/Navbar.tsx +1 -1
  24. package/src/features/shared/layouts/BaseTabsLayout/SidebarMenu.tsx +9 -2
  25. package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +9 -6
  26. package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +4 -2
  27. package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +119 -0
  28. package/src/features/shared/layouts/ErrorTabsLayout/error-tabs-layout.scss +1 -0
  29. package/src/features/shared/utils/environment.ts +41 -0
  30. package/src/features/shared/utils/extractErrorMessage.ts +22 -0
  31. package/src/features/shared/utils/getHome.tsx +5 -0
  32. package/src/features/shared/utils/index.ts +2 -0
  33. package/src/features/shared/utils/withClientErrorBoundary.ts +61 -0
  34. package/src/features/shared/utils/withLoaderErrorBoundary.ts +46 -0
  35. package/src/features/users/clients/UsersClient.ts +0 -1
  36. package/src/pages/address-details.tsx +38 -11
  37. package/src/pages/addresses.tsx +35 -6
  38. package/src/pages/budgets-details.tsx +38 -8
  39. package/src/pages/budgets.tsx +33 -8
  40. package/src/pages/buying-policies.tsx +36 -12
  41. package/src/pages/buying-policy-details.tsx +38 -8
  42. package/src/pages/collections.tsx +36 -12
  43. package/src/pages/cost-centers.tsx +38 -8
  44. package/src/pages/credit-cards.tsx +38 -8
  45. package/src/pages/home.tsx +22 -5
  46. package/src/pages/org-unit-details.tsx +43 -7
  47. package/src/pages/org-units.tsx +39 -8
  48. package/src/pages/payment-methods.tsx +38 -8
  49. package/src/pages/po-numbers.tsx +38 -8
  50. package/src/pages/profile.tsx +31 -6
  51. package/src/pages/releases.tsx +33 -8
  52. package/src/pages/role-details.tsx +39 -7
  53. package/src/pages/roles.tsx +28 -7
  54. package/src/pages/user-details.tsx +39 -8
  55. package/src/pages/users.tsx +25 -7
@@ -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
+ }
@@ -0,0 +1,14 @@
1
+ export type ErrorBoundaryProps = {
2
+ error: {
3
+ message: string;
4
+ name: string;
5
+ stack?: string;
6
+ };
7
+ tags: {
8
+ component: string;
9
+ errorType: string;
10
+ };
11
+ extra?: {
12
+ query: unknown;
13
+ };
14
+ };
@@ -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
+ }
@@ -73,7 +73,7 @@ export const Navbar = ({
73
73
  {loading ? (
74
74
  <Skeleton size={{ width: "22rem", height: "2.5rem" }} />
75
75
  ) : (
76
- <OrgUnitBreadcrumb items={breadcrumbList} maxItems={1} />
76
+ <OrgUnitBreadcrumb items={breadcrumbList} maxItems={1} hasDropdown />
77
77
  )}
78
78
 
79
79
  <div data-fs-bp-nav-menu-actions>
@@ -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}
@@ -20,12 +20,15 @@
20
20
  calc(var(--fs-spacing-0) + var(--fs-spacing-3));
21
21
  }
22
22
 
23
- [data-fs-bp-breadcrumb-current] {
24
- font-size: var(--fs-text-size-2);
25
-
26
- @include media("<=phonemid") {
27
- font-size: var(--fs-text-size-1);
28
- }
23
+ [data-fs-bp-breadcrumb-nav] {
24
+ [data-fs-bp-breadcrumb-current],
25
+ [data-fs-bp-breadcrumb-dropdown-trigger] {
26
+ font-size: var(--fs-text-size-2);
27
+
28
+ @include media("<=phonemid") {
29
+ font-size: var(--fs-text-size-1);
30
+ }
31
+ }
29
32
  }
30
33
 
31
34
  [data-fs-bp-nav-menu-btn-container] {
@@ -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
+ });