tycho-components 0.43.5 → 0.43.6

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.
@@ -5,9 +5,18 @@ export type ApiErrorHandled = AxiosError & {
5
5
  authErrorHandled?: boolean;
6
6
  };
7
7
  export declare function isServerError(status: number | undefined): boolean;
8
+ export declare function isAccessDeniedStatus(status: number | undefined): boolean;
9
+ /** HTTP status from an Axios error or a thrown Response-like object. */
10
+ export declare function getHttpErrorStatus(reason: unknown): number | undefined;
8
11
  export declare function markApiErrorHandled(error: AxiosError): void;
9
12
  /** True when the API client already surfaced a 5xx error via dispatchApiError. */
10
13
  export declare function isHandledApiError(reason: unknown): boolean;
14
+ /**
15
+ * Unmarked 401/403 from the API (or a Response with that status).
16
+ * Not treated as "handled" so callers can still catch; ErrorBoundary uses this
17
+ * to show access-denied instead of the client report prompt.
18
+ */
19
+ export declare function isAccessDeniedApiError(reason: unknown): boolean;
11
20
  /** @deprecated use markApiErrorHandled */
12
21
  export declare const markAuthErrorHandled: typeof markApiErrorHandled;
13
22
  /** @deprecated use isHandledApiError */
@@ -1,6 +1,22 @@
1
1
  export function isServerError(status) {
2
2
  return status !== undefined && status >= 500 && status < 600;
3
3
  }
4
+ export function isAccessDeniedStatus(status) {
5
+ return status === 401 || status === 403;
6
+ }
7
+ /** HTTP status from an Axios error or a thrown Response-like object. */
8
+ export function getHttpErrorStatus(reason) {
9
+ if (!reason || typeof reason !== 'object')
10
+ return undefined;
11
+ const err = reason;
12
+ if (typeof err.response?.status === 'number') {
13
+ return err.response.status;
14
+ }
15
+ if (typeof err.status === 'number') {
16
+ return err.status;
17
+ }
18
+ return undefined;
19
+ }
4
20
  export function markApiErrorHandled(error) {
5
21
  error.apiErrorHandled = true;
6
22
  }
@@ -16,6 +32,14 @@ export function isHandledApiError(reason) {
16
32
  return false;
17
33
  return isServerError(status);
18
34
  }
35
+ /**
36
+ * Unmarked 401/403 from the API (or a Response with that status).
37
+ * Not treated as "handled" so callers can still catch; ErrorBoundary uses this
38
+ * to show access-denied instead of the client report prompt.
39
+ */
40
+ export function isAccessDeniedApiError(reason) {
41
+ return isAccessDeniedStatus(getHttpErrorStatus(reason));
42
+ }
19
43
  /** @deprecated use markApiErrorHandled */
20
44
  export const markAuthErrorHandled = markApiErrorHandled;
21
45
  /** @deprecated use isHandledApiError */
@@ -1,7 +1,7 @@
1
1
  import { markApiErrorHandled } from './apiHandledError';
2
2
  import CookieStorage from '../CookieStorage';
3
- import { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
4
- import { logged, message } from '../store/actions';
3
+ import { dispatchAccessDeniedError } from './dispatchApiError';
4
+ import { logged } from '../store/actions';
5
5
  import { getCommonDispatch } from '../store/commonDispatchBridge';
6
6
  import StorybookUtils from '../../functions/StorybookUtils';
7
7
  const { isStorybook } = StorybookUtils;
@@ -23,10 +23,7 @@ function handleToastAuthError(status) {
23
23
  clearAuthStorage();
24
24
  getCommonDispatch()?.(logged(undefined));
25
25
  }
26
- getCommonDispatch()?.(message({
27
- value: resolveAccessDeniedMessage(),
28
- type: 'error',
29
- }));
26
+ dispatchAccessDeniedError();
30
27
  }
31
28
  function handleRedirectAuthError(status, redirect, onBeforeRedirect, skipAuthHandlingInStorybook = true) {
32
29
  if (skipAuthHandlingInStorybook && isStorybook()) {
@@ -20,4 +20,6 @@ export declare function dispatchClientError({ err, t, }: {
20
20
  err: unknown;
21
21
  t?: TFunction;
22
22
  }): void;
23
+ /** Toast for 401/403 — access denied, not a reportable client crash. */
24
+ export declare function dispatchAccessDeniedError(): void;
23
25
  export {};
@@ -2,6 +2,7 @@ import i18n from 'i18next';
2
2
  import { getErrorMessageValue } from '../useMessageUtils';
3
3
  import { message } from '../store/actions';
4
4
  import { getCommonDispatch } from '../store/commonDispatchBridge';
5
+ import { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
5
6
  const REQUEST_ID_HEADERS = [
6
7
  'x-request-id',
7
8
  'x-correlation-id',
@@ -117,3 +118,10 @@ export function dispatchClientError({ err, t, }) {
117
118
  errorContext,
118
119
  }));
119
120
  }
121
+ /** Toast for 401/403 — access denied, not a reportable client crash. */
122
+ export function dispatchAccessDeniedError() {
123
+ getCommonDispatch()?.(message({
124
+ value: resolveAccessDeniedMessage(),
125
+ type: 'error',
126
+ }));
127
+ }
@@ -11,8 +11,8 @@ export { createApiClient } from './createApiClient';
11
11
  export type { CreateApiClientOptions, ServerErrorHandling } from './createApiClient';
12
12
  export { applyAuthErrorHandling } from './applyAuthErrorHandling';
13
13
  export type { ApplyAuthErrorHandlingOptions, AuthErrorHandling, } from './applyAuthErrorHandling';
14
- export { isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, } from './apiHandledError';
14
+ export { isHandledApiError, isHandledAuthError, isAccessDeniedApiError, getHttpErrorStatus, markApiErrorHandled, markAuthErrorHandled, } from './apiHandledError';
15
15
  export type { ApiErrorHandled, AuthErrorHandled } from './apiHandledError';
16
- export { buildClientErrorContext, dispatchApiError, dispatchClientError, dispatchServerApiError, } from './dispatchApiError';
16
+ export { buildClientErrorContext, dispatchApiError, dispatchAccessDeniedError, dispatchClientError, dispatchServerApiError, } from './dispatchApiError';
17
17
  export { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
18
18
  export default api;
@@ -12,7 +12,7 @@ export const parserApiBase = import.meta.env.VITE_APP_PARSER_API;
12
12
  const api = applyAuthErrorHandling(createApiClient({ baseURL: platformApiBase }), { authErrorHandling: 'redirect' });
13
13
  export { createApiClient } from './createApiClient';
14
14
  export { applyAuthErrorHandling } from './applyAuthErrorHandling';
15
- export { isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, } from './apiHandledError';
16
- export { buildClientErrorContext, dispatchApiError, dispatchClientError, dispatchServerApiError, } from './dispatchApiError';
15
+ export { isHandledApiError, isHandledAuthError, isAccessDeniedApiError, getHttpErrorStatus, markApiErrorHandled, markAuthErrorHandled, } from './apiHandledError';
16
+ export { buildClientErrorContext, dispatchApiError, dispatchAccessDeniedError, dispatchClientError, dispatchServerApiError, } from './dispatchApiError';
17
17
  export { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
18
18
  export default api;
@@ -2,7 +2,7 @@ export { CommonProvider } from "./CommonContext";
2
2
  export { default as CommonContext } from "./CommonContext";
3
3
  export { default as CommonDispatchBridge } from "./CommonDispatchBridge";
4
4
  export { default as CookieStorage } from "./CookieStorage";
5
- export { createApiClient, applyAuthErrorHandling, dispatchApiError, dispatchClientError, dispatchServerApiError, resolveAccessDeniedMessage, isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, platformApi, parserApiBase, } from "./api";
5
+ export { createApiClient, applyAuthErrorHandling, dispatchApiError, dispatchAccessDeniedError, dispatchClientError, dispatchServerApiError, resolveAccessDeniedMessage, isHandledApiError, isAccessDeniedApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, platformApi, parserApiBase, } from "./api";
6
6
  export type { ApplyAuthErrorHandlingOptions, AuthErrorHandling, CreateApiClientOptions, ServerErrorHandling, ApiErrorHandled, AuthErrorHandled, } from "./api";
7
7
  export { getCommonDispatch, registerCommonDispatch, } from "./store/commonDispatchBridge";
8
8
  export { commonResources, featureResources, mergeResources, commonLocalization, } from "./Localization";
@@ -2,7 +2,7 @@ export { CommonProvider } from "./CommonContext";
2
2
  export { default as CommonContext } from "./CommonContext";
3
3
  export { default as CommonDispatchBridge } from "./CommonDispatchBridge";
4
4
  export { default as CookieStorage } from "./CookieStorage";
5
- export { createApiClient, applyAuthErrorHandling, dispatchApiError, dispatchClientError, dispatchServerApiError, resolveAccessDeniedMessage, isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, platformApi, parserApiBase, } from "./api";
5
+ export { createApiClient, applyAuthErrorHandling, dispatchApiError, dispatchAccessDeniedError, dispatchClientError, dispatchServerApiError, resolveAccessDeniedMessage, isHandledApiError, isAccessDeniedApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, platformApi, parserApiBase, } from "./api";
6
6
  export { getCommonDispatch, registerCommonDispatch, } from "./store/commonDispatchBridge";
7
7
  export { commonResources, featureResources, mergeResources, commonLocalization, } from "./Localization";
8
8
  export { default as ProfileService } from "./services/ProfileService";
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from 'react';
3
- import { dispatchClientError, isHandledApiError } from '../../configs/api';
3
+ import { dispatchAccessDeniedError, dispatchClientError, isAccessDeniedApiError, isHandledApiError, } from '../../configs/api';
4
4
  /** Survives remounts; one client-report toast per full page load. */
5
5
  let globalErrorReported = false;
6
6
  function toError(reason) {
@@ -38,6 +38,11 @@ export default function ErrorBoundary({ children }) {
38
38
  event.preventDefault();
39
39
  return;
40
40
  }
41
+ if (isAccessDeniedApiError(event.reason)) {
42
+ event.preventDefault();
43
+ dispatchAccessDeniedError();
44
+ return;
45
+ }
41
46
  event.preventDefault();
42
47
  handleError(event.reason);
43
48
  };
@@ -46,6 +51,11 @@ export default function ErrorBoundary({ children }) {
46
51
  event.preventDefault();
47
52
  return;
48
53
  }
54
+ if (isAccessDeniedApiError(event.error)) {
55
+ event.preventDefault();
56
+ dispatchAccessDeniedError();
57
+ return;
58
+ }
49
59
  event.preventDefault();
50
60
  handleError(event.error);
51
61
  };
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { useNavigate, useRouteError } from "react-router-dom";
5
- import { dispatchClientError } from "../../configs/api";
5
+ import { dispatchAccessDeniedError, dispatchClientError, isAccessDeniedApiError, } from "../../configs/api";
6
6
  import CookieStorage from "../../configs/CookieStorage";
7
7
  import logo from "./logo.png";
8
8
  import "./style.scss";
@@ -42,13 +42,17 @@ export default function ErrorFound() {
42
42
  const navigate = useNavigate();
43
43
  const { t } = useTranslation("base");
44
44
  const routeError = useRouteError();
45
- const error = toError(routeError);
45
+ const accessDenied = isAccessDeniedApiError(routeError);
46
46
  useEffect(() => {
47
47
  if (routeErrorReported) {
48
48
  return;
49
49
  }
50
50
  routeErrorReported = true;
51
51
  const id = window.setTimeout(() => {
52
+ if (isAccessDeniedApiError(routeError)) {
53
+ dispatchAccessDeniedError();
54
+ return;
55
+ }
52
56
  dispatchClientError({ err: toError(routeError) });
53
57
  }, 0);
54
58
  return () => window.clearTimeout(id);
@@ -60,5 +64,7 @@ export default function ErrorFound() {
60
64
  const redirectUri = CookieStorage.getRedirectUri();
61
65
  redirectUri ? (window.location.href = redirectUri) : handleRedirect();
62
66
  };
63
- return (_jsx("div", { className: "box-container", children: _jsxs("div", { className: "box", children: [_jsx("img", { src: logo, alt: "" }), _jsx("div", { className: "title", children: t("label.error.found") })] }) }));
67
+ return (_jsx("div", { className: "box-container", children: _jsxs("div", { className: "box", children: [_jsx("img", { src: logo, alt: "" }), _jsx("div", { className: "title", children: accessDenied
68
+ ? t("unauthorized.label.disclaimer")
69
+ : t("label.error.found") })] }) }));
64
70
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tycho-components",
3
3
  "private": false,
4
- "version": "0.43.5",
4
+ "version": "0.43.6",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {