@scm-manager/ui-components 4.0.0-REACT19-20251102-220352 → 4.0.0-REACT19-20251104-141645

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scm-manager/ui-components",
3
- "version": "4.0.0-REACT19-20251102-220352",
3
+ "version": "4.0.0-REACT19-20251104-141645",
4
4
  "description": "UI Components for SCM-Manager and its plugins",
5
5
  "main": "src/index.ts",
6
6
  "files": [
@@ -34,8 +34,8 @@
34
34
  "@scm-manager/eslint-config": "^2.18.2",
35
35
  "@scm-manager/prettier-config": "^2.12.0",
36
36
  "@scm-manager/tsconfig": "^2.13.0",
37
- "@scm-manager/ui-syntaxhighlighting": "4.0.0-REACT19-20251102-220352",
38
- "@scm-manager/ui-types": "4.0.0-REACT19-20251102-220352",
37
+ "@scm-manager/ui-syntaxhighlighting": "4.0.0-REACT19-20251104-141645",
38
+ "@scm-manager/ui-types": "4.0.0-REACT19-20251104-141645",
39
39
  "@storybook/addon-actions": "^9.0.8",
40
40
  "@storybook/addon-docs": "^9.1.5",
41
41
  "@storybook/addon-essentials": "^9.0.0-alpha.12",
@@ -66,9 +66,9 @@
66
66
  "vitest": "^3.2.4"
67
67
  },
68
68
  "dependencies": {
69
- "@scm-manager/ui-api": "4.0.0-REACT19-20251102-220352",
70
- "@scm-manager/ui-core": "4.0.0-REACT19-20251102-220352",
71
- "@scm-manager/ui-extensions": "4.0.0-REACT19-20251102-220352",
69
+ "@scm-manager/ui-api": "4.0.0-REACT19-20251104-141645",
70
+ "@scm-manager/ui-core": "4.0.0-REACT19-20251104-141645",
71
+ "@scm-manager/ui-extensions": "4.0.0-REACT19-20251104-141645",
72
72
  "deepmerge": "^4.2.2",
73
73
  "hast-util-sanitize": "^3.0.2",
74
74
  "react-diff-view": "2.6.0",
@@ -15,34 +15,26 @@
15
15
  */
16
16
 
17
17
  import React, { FC, ReactNode, useEffect } from "react";
18
- import { useLocation } from "react-router-dom";
19
- import { ErrorBoundary as ReactErrorBoundary, FallbackProps } from "react-error-boundary";
18
+ import { isRouteErrorResponse, useLocation, useRouteError } from "react-router-dom";
20
19
  import { useTranslation } from "react-i18next";
21
20
  import classNames from "classnames";
22
21
  import styled from "styled-components";
23
22
  import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api";
23
+ import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary";
24
24
  import { useDocumentTitle } from "@scm-manager/ui-core";
25
25
  import ErrorNotification from "./ErrorNotification";
26
26
  import ErrorPage from "./ErrorPage";
27
27
  import { Subtitle, Title } from "./layout";
28
28
  import Icon from "./Icon";
29
29
 
30
- type ErrorState = {
30
+ type ErrorDisplayProps = {
31
31
  error: Error;
32
+ errorInfo?: { componentStack?: string }; // Optional gemacht
33
+ fallback?: React.ComponentType<{ error: Error }>;
32
34
  };
33
35
 
34
- type ExportedProps = {
35
- fallback?: React.ComponentType<ErrorState>;
36
- children?: ReactNode;
37
- };
38
-
39
- type ErrorDisplayProps = {
40
- error: Error;
41
- // react-error-boundary does not pass errorInfo to the fallback component by default
42
- // for performance reasons, but it can be logged via the `onError` prop.
43
- // We keep it in the signature for compatibility with the custom fallback prop.
44
- errorInfo?: { componentStack: string };
45
- fallback?: React.ComponentType<ErrorState>;
36
+ type GlobalErrorBoundaryProps = {
37
+ fallback?: React.ComponentType<{ error: Error }>;
46
38
  };
47
39
 
48
40
  const RedirectIconContainer = styled.div`
@@ -52,6 +44,7 @@ const RedirectIconContainer = styled.div`
52
44
  const RedirectPage: FC = () => {
53
45
  const [t] = useTranslation("commons");
54
46
  useDocumentTitle(t("errorNotification.prefix"));
47
+
55
48
  return (
56
49
  <section className="section">
57
50
  <div className="container">
@@ -72,7 +65,7 @@ const RedirectPage: FC = () => {
72
65
  );
73
66
  };
74
67
 
75
- const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: FallbackComponent }) => {
68
+ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, fallback: FallbackComponent }) => {
76
69
  const loginLink = useIndexLink("login");
77
70
  const [t] = useTranslation("commons");
78
71
  const location = useLocation();
@@ -87,41 +80,47 @@ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: Fallb
87
80
  if (isMissingLink) {
88
81
  if (loginLink) {
89
82
  return <RedirectPage />;
83
+ } else {
84
+ return (
85
+ <ErrorPage error={error} title={t("errorNotification.prefix")} subtitle={t("errorNotification.forbidden")} />
86
+ );
90
87
  }
91
- return (
92
- <ErrorPage error={error} title={t("errorNotification.prefix")} subtitle={t("errorNotification.forbidden")} />
93
- );
94
88
  }
95
89
 
96
- const fallbackProps = {
97
- error,
98
- errorInfo,
99
- };
100
-
101
90
  if (FallbackComponent) {
102
- return <FallbackComponent {...fallbackProps} />;
91
+ return <FallbackComponent error={error} />;
103
92
  }
93
+
104
94
  return <ErrorNotification error={error} />;
105
95
  };
106
96
 
107
- const ErrorBoundary: FC<ExportedProps> = ({ children, fallback }) => {
97
+ const ErrorBoundary: FC<GlobalErrorBoundaryProps & { children?: ReactNode }> = ({ fallback, children }) => {
98
+ const rawError = useRouteError();
108
99
  const location = useLocation();
109
100
 
110
- const ErrorBoundaryFallback = ({ error }: FallbackProps) => (
111
- <ErrorDisplay
112
- error={error}
113
- // For compatibility with potential custom fallbacks that expect errorInfo.
114
- // The actual component stack is available in the `onError` callback below.
115
- errorInfo={{ componentStack: "" }}
116
- fallback={fallback}
117
- />
118
- );
101
+ if (!rawError) {
102
+ const ErrorBoundaryFallback = ({ error }: { error: Error }) => (
103
+ <ErrorDisplay error={error} errorInfo={{ componentStack: "" }} fallback={fallback} />
104
+ );
119
105
 
120
- return (
121
- <ReactErrorBoundary resetKeys={[location.pathname]} FallbackComponent={ErrorBoundaryFallback}>
122
- {children}
123
- </ReactErrorBoundary>
124
- );
106
+ return (
107
+ <ReactErrorBoundary resetKeys={[location.pathname]} FallbackComponent={ErrorBoundaryFallback}>
108
+ {children}
109
+ </ReactErrorBoundary>
110
+ );
111
+ }
112
+
113
+ let error: Error;
114
+
115
+ if (isRouteErrorResponse(rawError)) {
116
+ error = new Error(`${rawError.status} ${rawError.statusText}`);
117
+ } else if (rawError instanceof Error) {
118
+ error = rawError;
119
+ } else {
120
+ error = new Error(rawError ? String(rawError) : "Unknown Error");
121
+ }
122
+
123
+ return <ErrorDisplay error={error} errorInfo={{ componentStack: "Loader/Action Error" }} fallback={fallback} />;
125
124
  };
126
125
 
127
126
  export default ErrorBoundary;
@@ -57,7 +57,7 @@ class ConfigurationBinder {
57
57
  return { path, element: Component };
58
58
  }
59
59
 
60
- bindGlobal(to: string, labelI18nKey: string, linkName: string, ConfigurationComponent: any, sortKey?: string) {
60
+ bindAdminSetting(to: string, labelI18nKey: string, linkName: string, ConfigurationComponent: any, sortKey?: string) {
61
61
  // create predicate based on the link name of the index resource
62
62
  // if the linkname is not available, the navigation link and the route are not bound to the extension points
63
63
  const configPredicate = (props: any) => {
@@ -74,7 +74,7 @@ class ConfigurationBinder {
74
74
  binder.bind("admin.setting", ConfigNavLink, configPredicate, sortKey ?? linkName);
75
75
 
76
76
  // route for global configuration, passes the link from the index resource to component
77
- const ConfigRoute = ({ ...additionalProps }: GlobalRouteProps) => {
77
+ const ConfigRoute = ({ ...additionalProps }: GlobalRouteProps = {} as GlobalRouteProps) => {
78
78
  return this.route(
79
79
  sanitizedTo,
80
80
  <TitledGlobalSettingComponent i18nNamespace={this.i18nNamespace} label={labelI18nKey}>
@@ -84,28 +84,19 @@ class ConfigurationBinder {
84
84
  };
85
85
 
86
86
  // bind config route to extension point
87
- binder.bind("admin.route", ConfigRoute);
87
+ binder.bind("admin.settings.route", ConfigRoute);
88
88
  }
89
89
 
90
- bindAdmin(
91
- to: string,
92
- labelI18nKey: string,
93
- icon: string,
94
- linkName: string,
95
- Component: React.ComponentType<{ link: string }>,
96
- ) {
97
- const predicate = ({ links }: extensionPoints.AdminRoute["props"]) => links![linkName];
98
-
90
+ bindAdmin(to: string, labelI18nKey: string, icon: string, Component: React.ComponentType) {
99
91
  const AdminNavLink = withTranslation(this.i18nNamespace)(
100
92
  ({ t }: WithTranslation & extensionPoints.AdminNavigation["props"]) =>
101
93
  this.navLink("/admin/" + to, labelI18nKey, t, { icon }),
102
94
  );
103
- binder.bind("admin.navigation", AdminNavLink, predicate);
95
+ binder.bind("admin.navigation", AdminNavLink);
104
96
 
105
- const AdminRoute: extensionPoints.AdminRoute["type"] = ({ links }) =>
106
- this.route(to, <Component link={(links![linkName] as Link).href} />);
97
+ const AdminRoute: extensionPoints.AdminRoute["type"] = () => this.route(to, <Component />);
107
98
 
108
- binder.bind<extensionPoints.AdminRoute>("admin.route", AdminRoute, predicate);
99
+ binder.bind<extensionPoints.AdminRoute>("admin.route", AdminRoute);
109
100
  }
110
101
 
111
102
  bindRepository(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
@@ -29,16 +29,13 @@ const useMatchesRoute = (pattern?: string) => {
29
29
  const useActiveMatch = ({ to, activeWhenMatch, activeOnlyWhenExact = true }: RoutingProps) => {
30
30
  const location = useLocation();
31
31
  const resolved = useResolvedPath(to);
32
- const isActive = new RegExp(resolved.pathname).test(location.pathname);
32
+ const isActive = !resolved.hash && location.pathname.startsWith(resolved.pathname);
33
33
  const match = useMatchesRoute(activeWhenMatch);
34
34
 
35
35
  if (activeOnlyWhenExact) {
36
36
  return isActive;
37
37
  } else if (activeWhenMatch) {
38
- if (isActive) {
39
- return true;
40
- }
41
- return match;
38
+ return isActive || match;
42
39
  }
43
40
  return false;
44
41
  };