tycho-components 0.38.7 → 0.39.1

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 (37) hide show
  1. package/dist/common/AppToast/AppToast.d.ts +1 -0
  2. package/dist/common/AppToast/AppToast.js +53 -20
  3. package/dist/common/AppToast/ToastMessage.d.ts +14 -4
  4. package/dist/common/AppToast/style.scss +3 -0
  5. package/dist/configs/CommonDispatchBridge.d.ts +2 -0
  6. package/dist/configs/CommonDispatchBridge.js +13 -0
  7. package/dist/configs/api/apiHandledError.d.ts +16 -0
  8. package/dist/configs/api/apiHandledError.js +22 -0
  9. package/dist/configs/api/applyAuthErrorHandling.d.ts +15 -0
  10. package/dist/configs/api/applyAuthErrorHandling.js +72 -0
  11. package/dist/configs/api/createApiClient.d.ts +16 -0
  12. package/dist/configs/api/createApiClient.js +59 -0
  13. package/dist/configs/api/dispatchApiError.d.ts +16 -0
  14. package/dist/configs/api/dispatchApiError.js +71 -0
  15. package/dist/configs/api/index.d.ts +18 -0
  16. package/dist/configs/api/index.js +18 -0
  17. package/dist/configs/api/resolveAccessDeniedMessage.d.ts +2 -0
  18. package/dist/configs/api/resolveAccessDeniedMessage.js +19 -0
  19. package/dist/configs/index.d.ts +5 -1
  20. package/dist/configs/index.js +3 -0
  21. package/dist/configs/localization/CommonTexts.d.ts +33 -6
  22. package/dist/configs/localization/CommonTexts.js +36 -9
  23. package/dist/configs/services/ErrorReportService.d.ts +34 -0
  24. package/dist/configs/services/ErrorReportService.js +63 -0
  25. package/dist/configs/store/commonDispatchBridge.d.ts +6 -0
  26. package/dist/configs/store/commonDispatchBridge.js +7 -0
  27. package/dist/configs/tour/index.d.ts +1 -0
  28. package/dist/configs/useMessageUtils.js +2 -2
  29. package/dist/features/ServerErrorReport/ServerErrorReport.d.ts +11 -0
  30. package/dist/features/ServerErrorReport/ServerErrorReport.js +49 -0
  31. package/dist/features/ServerErrorReport/index.d.ts +2 -0
  32. package/dist/features/ServerErrorReport/index.js +2 -0
  33. package/dist/features/ServerErrorReport/styles.scss +20 -0
  34. package/dist/shell/Base/ErrorBoundary.js +17 -13
  35. package/package.json +3 -2
  36. package/dist/configs/api.d.ts +0 -10
  37. package/dist/configs/api.js +0 -52
@@ -1,2 +1,3 @@
1
1
  import 'react-toastify/dist/ReactToastify.css';
2
+ import './style.scss';
2
3
  export default function AppToast(): import("react").JSX.Element;
@@ -1,34 +1,65 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useContext, useEffect } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useContext, useEffect, useRef, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import ReactLoading from 'react-loading';
5
5
  import { ToastContainer, toast } from 'react-toastify';
6
6
  import 'react-toastify/dist/ReactToastify.css';
7
7
  import CommonContext from '../../configs/CommonContext';
8
8
  import { message } from '../../configs/store/actions';
9
+ import ServerErrorReport from '../../features/ServerErrorReport';
9
10
  import { EMPTY_TOAST } from './ToastMessage';
11
+ import './style.scss';
10
12
  export default function AppToast() {
11
13
  const { t } = useTranslation('common');
12
14
  const { dispatch, state } = useContext(CommonContext);
15
+ const [reportModalOpen, setReportModalOpen] = useState(false);
16
+ const [reportErrorContext, setReportErrorContext] = useState();
17
+ const openingReportModalRef = useRef(false);
13
18
  const handleClose = () => {
14
19
  toast.dismiss();
15
20
  dispatch(message(EMPTY_TOAST));
21
+ if (openingReportModalRef.current) {
22
+ openingReportModalRef.current = false;
23
+ return;
24
+ }
25
+ setReportModalOpen(false);
26
+ setReportErrorContext(undefined);
16
27
  };
17
28
  const handleClipboard = () => {
18
29
  navigator.clipboard.writeText(state.message.value);
19
30
  };
31
+ const handleToastAction = () => {
32
+ if (state.message.action === 'report') {
33
+ setReportErrorContext(state.message.errorContext);
34
+ openingReportModalRef.current = true;
35
+ setReportModalOpen(true);
36
+ toast.dismiss();
37
+ dispatch(message(EMPTY_TOAST));
38
+ return;
39
+ }
40
+ handleClipboard();
41
+ };
42
+ const handleReportModalClose = () => {
43
+ setReportModalOpen(false);
44
+ setReportErrorContext(undefined);
45
+ };
46
+ const handleReportSent = () => {
47
+ setReportModalOpen(false);
48
+ setReportErrorContext(undefined);
49
+ dispatch(message({ value: t('server.error.report.sent'), type: 'success' }));
50
+ };
20
51
  const getLoading = () => (_jsxs("div", { className: "d-flex", children: [_jsx(ReactLoading, { type: "spinningBubbles", color: "blue", height: 24, width: 24 }), _jsx("span", { className: "ms-3", children: state.toastLoadingText ?? t('generic.loading') })] }));
21
52
  const attachCloseToEscape = () => {
22
53
  const closeOnEscape = (e) => {
23
- if (e.keyCode === 27)
54
+ if (e.key === 'Escape' && !reportModalOpen)
24
55
  handleClose();
25
56
  };
26
57
  window.addEventListener('keydown', closeOnEscape);
27
58
  return () => window.removeEventListener('keydown', closeOnEscape);
28
59
  };
29
60
  useEffect(() => {
30
- attachCloseToEscape();
31
- }, []);
61
+ return attachCloseToEscape();
62
+ }, [reportModalOpen]);
32
63
  useEffect(() => {
33
64
  if (state.toastLoading) {
34
65
  toast(getLoading(), {
@@ -44,32 +75,34 @@ export default function AppToast() {
44
75
  });
45
76
  }
46
77
  else {
47
- toast.dismiss();
78
+ toast.dismiss('loading');
48
79
  }
49
80
  }, [state.toastLoading, state.toastLoadingText]);
50
81
  useEffect(() => {
51
82
  if (state.message && state.message.value !== '') {
83
+ const persistent = state.message.autoClose === false;
84
+ const toastOptions = {
85
+ onClose: () => handleClose(),
86
+ onClick: () => handleToastAction(),
87
+ ...(persistent
88
+ ? { autoClose: false, closeOnClick: false }
89
+ : {}),
90
+ ...(state.message.action === 'report'
91
+ ? { bodyClassName: 'toast-report-action' }
92
+ : {}),
93
+ };
52
94
  switch (state.message.type) {
53
95
  case 'error':
54
- toast.error(state.message.value, {
55
- onClose: () => handleClose(),
56
- onClick: () => handleClipboard(),
57
- });
96
+ toast.error(state.message.value, toastOptions);
58
97
  break;
59
98
  case 'warning':
60
- toast.warning(state.message.value, {
61
- onClose: () => handleClose(),
62
- });
99
+ toast.warning(state.message.value, toastOptions);
63
100
  break;
64
101
  case 'success':
65
- toast.success(state.message.value, {
66
- onClose: () => handleClose(),
67
- });
102
+ toast.success(state.message.value, toastOptions);
68
103
  break;
69
104
  default:
70
- toast(state.message.value, {
71
- onClose: () => handleClose(),
72
- });
105
+ toast(state.message.value, toastOptions);
73
106
  break;
74
107
  }
75
108
  }
@@ -77,5 +110,5 @@ export default function AppToast() {
77
110
  dispatch(message(EMPTY_TOAST));
78
111
  }
79
112
  }, [state.message]);
80
- return _jsx(ToastContainer, { closeOnClick: true });
113
+ return (_jsxs(_Fragment, { children: [_jsx(ToastContainer, { closeOnClick: state.message.action !== 'report' }), reportModalOpen && (_jsx(ServerErrorReport, { errorContext: reportErrorContext, corpus: state.corpus, onClose: handleReportModalClose, onSent: handleReportSent }))] }));
81
114
  }
@@ -1,8 +1,18 @@
1
+ export type ToastAction = 'report' | 'copy';
2
+ export type ServerErrorContext = {
3
+ status?: number;
4
+ url?: string;
5
+ method?: string;
6
+ responseMessage?: string;
7
+ responseDescription?: string;
8
+ requestId?: string;
9
+ occurredAt?: string;
10
+ };
1
11
  export default interface ToastMessage {
2
12
  value: string;
3
13
  type: string;
14
+ autoClose?: boolean;
15
+ action?: ToastAction;
16
+ errorContext?: ServerErrorContext;
4
17
  }
5
- export declare const EMPTY_TOAST: {
6
- value: string;
7
- type: string;
8
- };
18
+ export declare const EMPTY_TOAST: ToastMessage;
@@ -0,0 +1,3 @@
1
+ .toast-report-action {
2
+ cursor: pointer;
3
+ }
@@ -0,0 +1,2 @@
1
+ /** Registers CommonContext.dispatch for non-React callers (e.g. axios interceptors). */
2
+ export default function CommonDispatchBridge(): null;
@@ -0,0 +1,13 @@
1
+ import { useContext, useEffect } from 'react';
2
+ import CommonContext from './CommonContext';
3
+ import { registerCommonDispatch } from './store/commonDispatchBridge';
4
+ /** Registers CommonContext.dispatch for non-React callers (e.g. axios interceptors). */
5
+ export default function CommonDispatchBridge() {
6
+ const { dispatch } = useContext(CommonContext);
7
+ useEffect(() => {
8
+ registerCommonDispatch(dispatch);
9
+ const noopDispatch = () => undefined;
10
+ return () => registerCommonDispatch(noopDispatch);
11
+ }, [dispatch]);
12
+ return null;
13
+ }
@@ -0,0 +1,16 @@
1
+ import type { AxiosError } from 'axios';
2
+ export type ApiErrorHandled = AxiosError & {
3
+ apiErrorHandled?: boolean;
4
+ /** @deprecated use apiErrorHandled */
5
+ authErrorHandled?: boolean;
6
+ };
7
+ export declare function isServerError(status: number | undefined): boolean;
8
+ export declare function markApiErrorHandled(error: AxiosError): void;
9
+ /** True when the API client already surfaced a 5xx error via dispatchApiError. */
10
+ export declare function isHandledApiError(reason: unknown): boolean;
11
+ /** @deprecated use markApiErrorHandled */
12
+ export declare const markAuthErrorHandled: typeof markApiErrorHandled;
13
+ /** @deprecated use isHandledApiError */
14
+ export declare const isHandledAuthError: typeof isHandledApiError;
15
+ /** @deprecated use ApiErrorHandled */
16
+ export type AuthErrorHandled = ApiErrorHandled;
@@ -0,0 +1,22 @@
1
+ export function isServerError(status) {
2
+ return status !== undefined && status >= 500 && status < 600;
3
+ }
4
+ export function markApiErrorHandled(error) {
5
+ error.apiErrorHandled = true;
6
+ }
7
+ /** True when the API client already surfaced a 5xx error via dispatchApiError. */
8
+ export function isHandledApiError(reason) {
9
+ if (!reason || typeof reason !== 'object')
10
+ return false;
11
+ const err = reason;
12
+ if (err.apiErrorHandled || err.authErrorHandled)
13
+ return true;
14
+ const status = err.response?.status;
15
+ if (!err.config || status === undefined)
16
+ return false;
17
+ return isServerError(status);
18
+ }
19
+ /** @deprecated use markApiErrorHandled */
20
+ export const markAuthErrorHandled = markApiErrorHandled;
21
+ /** @deprecated use isHandledApiError */
22
+ export const isHandledAuthError = isHandledApiError;
@@ -0,0 +1,15 @@
1
+ import type { AxiosInstance } from 'axios';
2
+ export type AuthErrorHandling = 'toast' | 'redirect' | 'none';
3
+ export type ApplyAuthErrorHandlingOptions = {
4
+ authErrorHandling?: AuthErrorHandling;
5
+ /** HTTP statuses that trigger auth handling. Defaults to [401, 403]. */
6
+ authStatuses?: Array<401 | 403>;
7
+ redirect?: {
8
+ on401?: string;
9
+ on403?: string;
10
+ };
11
+ onBeforeRedirect?: (status: 401 | 403) => void;
12
+ skipAuthHandlingInStorybook?: boolean;
13
+ };
14
+ /** Opt-in 401/403 handling per app. Not part of the shared 5xx dispatchError path. */
15
+ export declare function applyAuthErrorHandling(api: AxiosInstance, options: ApplyAuthErrorHandlingOptions): AxiosInstance;
@@ -0,0 +1,72 @@
1
+ import { markApiErrorHandled } from './apiHandledError';
2
+ import CookieStorage from '../CookieStorage';
3
+ import { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
4
+ import { logged, message } from '../store/actions';
5
+ import { getCommonDispatch } from '../store/commonDispatchBridge';
6
+ const isStorybook = () => typeof window !== 'undefined' &&
7
+ (window.location.port === '6006' ||
8
+ window.__STORYBOOK__);
9
+ function defaultRedirectUrls() {
10
+ const publicUrl = import.meta.env.VITE_APP_PUBLIC_URL;
11
+ return {
12
+ on401: import.meta.env.VITE_APP_AUTH_URL,
13
+ on403: import.meta.env.VITE_APP_UNAUTHORIZED_URL ??
14
+ (publicUrl ? `${publicUrl}/unauthorized` : undefined),
15
+ };
16
+ }
17
+ function clearAuthStorage() {
18
+ CookieStorage.removeJwtToken();
19
+ sessionStorage.clear();
20
+ localStorage.clear();
21
+ }
22
+ function handleToastAuthError(status) {
23
+ if (status === 401) {
24
+ clearAuthStorage();
25
+ getCommonDispatch()?.(logged(undefined));
26
+ }
27
+ getCommonDispatch()?.(message({
28
+ value: resolveAccessDeniedMessage(),
29
+ type: 'error',
30
+ }));
31
+ }
32
+ function handleRedirectAuthError(status, redirect, onBeforeRedirect, skipAuthHandlingInStorybook = true) {
33
+ if (skipAuthHandlingInStorybook && isStorybook()) {
34
+ return;
35
+ }
36
+ onBeforeRedirect?.(status);
37
+ if (status === 401) {
38
+ clearAuthStorage();
39
+ if (redirect.on401) {
40
+ window.location.replace(redirect.on401);
41
+ }
42
+ return;
43
+ }
44
+ if (redirect.on403) {
45
+ window.location.href = redirect.on403;
46
+ }
47
+ }
48
+ /** Opt-in 401/403 handling per app. Not part of the shared 5xx dispatchError path. */
49
+ export function applyAuthErrorHandling(api, options) {
50
+ const { authErrorHandling = 'none', authStatuses = [401, 403], redirect: redirectOverrides, onBeforeRedirect, skipAuthHandlingInStorybook = true, } = options;
51
+ if (authErrorHandling === 'none') {
52
+ return api;
53
+ }
54
+ const redirect = { ...defaultRedirectUrls(), ...redirectOverrides };
55
+ api.interceptors.response.use((response) => response, async (error) => {
56
+ const status = error.response?.status;
57
+ if (status === 401 || status === 403) {
58
+ if (!authStatuses.includes(status)) {
59
+ return Promise.reject(error);
60
+ }
61
+ if (authErrorHandling === 'toast') {
62
+ handleToastAuthError(status);
63
+ }
64
+ else if (authErrorHandling === 'redirect') {
65
+ handleRedirectAuthError(status, redirect, onBeforeRedirect, skipAuthHandlingInStorybook);
66
+ }
67
+ markApiErrorHandled(error);
68
+ }
69
+ return Promise.reject(error);
70
+ });
71
+ return api;
72
+ }
@@ -0,0 +1,16 @@
1
+ import { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
2
+ export type ServerErrorHandling = 'toast' | 'none';
3
+ export type CreateApiClientOptions = {
4
+ baseURL: string;
5
+ attachJwt?: boolean;
6
+ /** How to surface 5xx responses. Defaults to 'toast'. */
7
+ serverErrorHandling?: ServerErrorHandling;
8
+ augmentRequest?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
9
+ };
10
+ /**
11
+ * Shared API client: JWT attachment + global 5xx dispatchError toast.
12
+ * Handled 5xx responses resolve (they do not reach .catch()).
13
+ * 4xx are left to front-end callers unless the app opts in via
14
+ * applyAuthErrorHandling() in its local api.ts wrapper.
15
+ */
16
+ export declare function createApiClient(options: CreateApiClientOptions): AxiosInstance;
@@ -0,0 +1,59 @@
1
+ import axios from 'axios';
2
+ import { isServerError, markApiErrorHandled } from './apiHandledError';
3
+ import CookieStorage from '../CookieStorage';
4
+ import { dispatchServerApiError } from './dispatchApiError';
5
+ const DEFAULT_HEADERS = {
6
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
7
+ Pragma: 'no-cache',
8
+ 'Content-Type': 'application/json',
9
+ Accept: 'application/json',
10
+ };
11
+ function toHandledServerErrorResponse(error) {
12
+ const status = error.response?.status ?? 500;
13
+ return {
14
+ data: null,
15
+ status,
16
+ statusText: error.response?.statusText ?? 'Internal Server Error',
17
+ headers: error.response?.headers ?? {},
18
+ config: error.config,
19
+ };
20
+ }
21
+ /**
22
+ * Shared API client: JWT attachment + global 5xx dispatchError toast.
23
+ * Handled 5xx responses resolve (they do not reach .catch()).
24
+ * 4xx are left to front-end callers unless the app opts in via
25
+ * applyAuthErrorHandling() in its local api.ts wrapper.
26
+ */
27
+ export function createApiClient(options) {
28
+ const { baseURL, attachJwt = true, serverErrorHandling = 'toast', augmentRequest, } = options;
29
+ const api = axios.create({
30
+ headers: DEFAULT_HEADERS,
31
+ baseURL,
32
+ });
33
+ api.interceptors.response.use((response) => response, async (error) => {
34
+ const status = error.response?.status;
35
+ if (isServerError(status) && serverErrorHandling === 'toast') {
36
+ dispatchServerApiError({ err: error });
37
+ markApiErrorHandled(error);
38
+ return toHandledServerErrorResponse(error);
39
+ }
40
+ if (error.status === null) {
41
+ console.log(JSON.stringify(error));
42
+ }
43
+ return Promise.reject(error);
44
+ });
45
+ api.interceptors.request.use((config) => {
46
+ if (!config?.headers) {
47
+ throw new Error('no header available');
48
+ }
49
+ if (attachJwt) {
50
+ const token = CookieStorage.getJwtToken();
51
+ if (token) {
52
+ // eslint-disable-next-line no-param-reassign
53
+ config.headers.Authorization = `Bearer ${token}`;
54
+ }
55
+ }
56
+ return augmentRequest ? augmentRequest(config) : config;
57
+ });
58
+ return api;
59
+ }
@@ -0,0 +1,16 @@
1
+ import type { AxiosResponseHeaders, RawAxiosResponseHeaders } from 'axios';
2
+ import type { TFunction } from 'i18next';
3
+ type DispatchApiErrorOptions = {
4
+ err: unknown;
5
+ t?: TFunction;
6
+ key?: string;
7
+ autoClose?: boolean;
8
+ };
9
+ export declare function extractRequestId(headers?: RawAxiosResponseHeaders | AxiosResponseHeaders): string | undefined;
10
+ export declare function dispatchApiError({ err, t, key, autoClose, }: DispatchApiErrorOptions): void;
11
+ /** Persistent 5xx toast that opens the server-error report modal on click. */
12
+ export declare function dispatchServerApiError({ err, t, }: {
13
+ err: unknown;
14
+ t?: TFunction;
15
+ }): void;
16
+ export {};
@@ -0,0 +1,71 @@
1
+ import i18n from 'i18next';
2
+ import { getErrorMessageValue } from '../useMessageUtils';
3
+ import { message } from '../store/actions';
4
+ import { getCommonDispatch } from '../store/commonDispatchBridge';
5
+ const REQUEST_ID_HEADERS = [
6
+ 'x-request-id',
7
+ 'x-correlation-id',
8
+ 'traceparent',
9
+ 'x-amzn-trace-id',
10
+ ];
11
+ function normalizeHeaderValue(value) {
12
+ if (typeof value === 'string' && value.trim()) {
13
+ return value.trim();
14
+ }
15
+ if (Array.isArray(value) && typeof value[0] === 'string' && value[0].trim()) {
16
+ return value[0].trim();
17
+ }
18
+ return undefined;
19
+ }
20
+ export function extractRequestId(headers) {
21
+ if (!headers || typeof headers !== 'object') {
22
+ return undefined;
23
+ }
24
+ const normalized = Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
25
+ for (const header of REQUEST_ID_HEADERS) {
26
+ const value = normalizeHeaderValue(normalized[header]);
27
+ if (value) {
28
+ return value;
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ function buildServerErrorContext(err) {
34
+ if (!err || typeof err !== 'object' || !('response' in err)) {
35
+ return {};
36
+ }
37
+ const axiosErr = err;
38
+ const data = axiosErr.response?.data;
39
+ return {
40
+ status: axiosErr.response?.status,
41
+ url: axiosErr.config?.url,
42
+ method: axiosErr.config?.method?.toUpperCase(),
43
+ responseMessage: typeof data?.message === 'string' ? data.message : undefined,
44
+ responseDescription: typeof data?.description === 'string' ? data.description : undefined,
45
+ requestId: extractRequestId(axiosErr.response?.headers),
46
+ occurredAt: new Date().toISOString(),
47
+ };
48
+ }
49
+ export function dispatchApiError({ err, t, key, autoClose, }) {
50
+ const translate = t ?? i18n.t.bind(i18n);
51
+ const value = getErrorMessageValue({ err, t: translate, key });
52
+ getCommonDispatch()?.(message({
53
+ value,
54
+ type: 'error',
55
+ autoClose,
56
+ action: 'copy',
57
+ }));
58
+ }
59
+ /** Persistent 5xx toast that opens the server-error report modal on click. */
60
+ export function dispatchServerApiError({ err, t, }) {
61
+ const translate = t ?? i18n.t.bind(i18n);
62
+ const value = translate('server.error.report.prompt', { ns: 'common' });
63
+ const errorContext = buildServerErrorContext(err);
64
+ getCommonDispatch()?.(message({
65
+ value,
66
+ type: 'error',
67
+ autoClose: false,
68
+ action: 'report',
69
+ errorContext,
70
+ }));
71
+ }
@@ -0,0 +1,18 @@
1
+ /** Platform API roots (formerly separate env vars per service). */
2
+ export declare const platformApi: {
3
+ base: string;
4
+ revision: string;
5
+ catalog: string;
6
+ editor: string;
7
+ };
8
+ export declare const parserApiBase: string;
9
+ declare const api: import("axios").AxiosInstance;
10
+ export { createApiClient } from './createApiClient';
11
+ export type { CreateApiClientOptions, ServerErrorHandling } from './createApiClient';
12
+ export { applyAuthErrorHandling } from './applyAuthErrorHandling';
13
+ export type { ApplyAuthErrorHandlingOptions, AuthErrorHandling, } from './applyAuthErrorHandling';
14
+ export { isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, } from './apiHandledError';
15
+ export type { ApiErrorHandled, AuthErrorHandled } from './apiHandledError';
16
+ export { dispatchApiError, dispatchServerApiError } from './dispatchApiError';
17
+ export { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
18
+ export default api;
@@ -0,0 +1,18 @@
1
+ import { applyAuthErrorHandling } from './applyAuthErrorHandling';
2
+ import { createApiClient } from './createApiClient';
3
+ const platformApiBase = import.meta.env.VITE_APP_PLATFORM_API;
4
+ /** Platform API roots (formerly separate env vars per service). */
5
+ export const platformApi = {
6
+ base: platformApiBase,
7
+ revision: `${platformApiBase}/revision`,
8
+ catalog: `${platformApiBase}/catalog`,
9
+ editor: `${platformApiBase}/editor`,
10
+ };
11
+ export const parserApiBase = import.meta.env.VITE_APP_PARSER_API;
12
+ const api = applyAuthErrorHandling(createApiClient({ baseURL: platformApiBase }), { authErrorHandling: 'redirect' });
13
+ export { createApiClient } from './createApiClient';
14
+ export { applyAuthErrorHandling } from './applyAuthErrorHandling';
15
+ export { isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, } from './apiHandledError';
16
+ export { dispatchApiError, dispatchServerApiError } from './dispatchApiError';
17
+ export { resolveAccessDeniedMessage } from './resolveAccessDeniedMessage';
18
+ export default api;
@@ -0,0 +1,2 @@
1
+ /** Resolves the access-denied toast message, with BaseTexts fallback when i18n is not ready. */
2
+ export declare function resolveAccessDeniedMessage(): string;
@@ -0,0 +1,19 @@
1
+ import i18n from 'i18next';
2
+ import { BaseTexts } from '../localization/BaseTexts';
3
+ const ACCESS_DENIED_KEY = 'error.access.authorization';
4
+ function resolveLocale() {
5
+ const lng = i18n.resolvedLanguage || i18n.language || 'en';
6
+ if (lng.startsWith('pt'))
7
+ return 'pt-BR';
8
+ if (lng.startsWith('it'))
9
+ return 'it';
10
+ return 'en';
11
+ }
12
+ /** Resolves the access-denied toast message, with BaseTexts fallback when i18n is not ready. */
13
+ export function resolveAccessDeniedMessage() {
14
+ const translated = i18n.t(ACCESS_DENIED_KEY, { ns: 'message' });
15
+ if (translated && translated !== ACCESS_DENIED_KEY) {
16
+ return translated;
17
+ }
18
+ return BaseTexts[resolveLocale()][ACCESS_DENIED_KEY];
19
+ }
@@ -1,6 +1,10 @@
1
1
  export { CommonProvider } from "./CommonContext";
2
2
  export { default as CommonContext } from "./CommonContext";
3
+ export { default as CommonDispatchBridge } from "./CommonDispatchBridge";
3
4
  export { default as CookieStorage } from "./CookieStorage";
5
+ export { createApiClient, applyAuthErrorHandling, dispatchApiError, dispatchServerApiError, resolveAccessDeniedMessage, isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, platformApi, parserApiBase, } from "./api";
6
+ export type { ApplyAuthErrorHandlingOptions, AuthErrorHandling, CreateApiClientOptions, ServerErrorHandling, ApiErrorHandled, AuthErrorHandled, } from "./api";
7
+ export { getCommonDispatch, registerCommonDispatch, } from "./store/commonDispatchBridge";
4
8
  export { commonResources, featureResources, mergeResources, commonLocalization, } from "./Localization";
5
9
  export { default as ProfileService } from "./services/ProfileService";
6
10
  export type { Corpus, CorpusRequest, Github } from "./types/Corpus";
@@ -30,4 +34,4 @@ export { useLoggedUtils } from "./useLoggedUtils";
30
34
  export { useMessageUtils } from "./useMessageUtils";
31
35
  export { useTourUtils } from "./useTourUtils";
32
36
  export { useDriverTour, loadKnowledgeBase, defaultMatchPathnames, buildAnchorMap, flattenWorkflowSteps, getWorkflowsForPath, buildDriveSteps, createTourDriver, hasMatchingWorkflow, resolveAnchor, resolveAnchorById, waitForSelector, localizeKnowledgeBase, pickLocalizedString, resolveTourLocale, TOUR_FALLBACK_LOCALE, TOUR_LOCALES, } from "./tour";
33
- export type { AnchorSelector, KnowledgeBase, KnowledgeBaseRaw, LocalizedString, PopoverSide, ResolveKnowledgeBaseUrl, TourAnchor, TourButtonLabels, TourStep, TourStepCopy, TourStepStructural, TourWorkflow, TourWorkflowRaw, UseDriverTourOptions, } from "./tour";
37
+ export type { AnchorSelector, Driver, KnowledgeBase, KnowledgeBaseRaw, LocalizedString, PopoverSide, ResolveKnowledgeBaseUrl, TourAnchor, TourButtonLabels, TourStep, TourStepCopy, TourStepStructural, TourWorkflow, TourWorkflowRaw, UseDriverTourOptions, } from "./tour";
@@ -1,6 +1,9 @@
1
1
  export { CommonProvider } from "./CommonContext";
2
2
  export { default as CommonContext } from "./CommonContext";
3
+ export { default as CommonDispatchBridge } from "./CommonDispatchBridge";
3
4
  export { default as CookieStorage } from "./CookieStorage";
5
+ export { createApiClient, applyAuthErrorHandling, dispatchApiError, dispatchServerApiError, resolveAccessDeniedMessage, isHandledApiError, isHandledAuthError, markApiErrorHandled, markAuthErrorHandled, platformApi, parserApiBase, } from "./api";
6
+ export { getCommonDispatch, registerCommonDispatch, } from "./store/commonDispatchBridge";
4
7
  export { commonResources, featureResources, mergeResources, commonLocalization, } from "./Localization";
5
8
  export { default as ProfileService } from "./services/ProfileService";
6
9
  export { CorpusStatusNames } from "./types/CorpusStatus";
@@ -42,6 +42,14 @@ export declare const CommonTexts: {
42
42
  'user.status.visitor': string;
43
43
  'update.success': string;
44
44
  'internal.server.error': string;
45
+ 'server.error.report.prompt': string;
46
+ 'server.error.report.modal.title': string;
47
+ 'server.error.report.modal.subtitle': string;
48
+ 'server.error.report.modal.placeholder': string;
49
+ 'server.error.report.modal.send': string;
50
+ 'server.error.report.validation': string;
51
+ 'server.error.report.send.failed': string;
52
+ 'server.error.report.sent': string;
45
53
  'table.label.rows-page': string;
46
54
  'table.label.items': string;
47
55
  'table.pagination.scroll-strip-left': string;
@@ -51,9 +59,10 @@ export declare const CommonTexts: {
51
59
  'document.status.deleted': string;
52
60
  'document.status.editing': string;
53
61
  'document.status.edited': string;
54
- 'document.status.revision': string;
55
- 'document.status.completed': string;
62
+ 'document.status.pos': string;
63
+ 'document.status.pos_done': string;
56
64
  'document.status.syntactic': string;
65
+ 'document.status.completed': string;
57
66
  'document.status.copying': string;
58
67
  'document.status.error': string;
59
68
  'sentence.status.todo': string;
@@ -121,6 +130,14 @@ export declare const CommonTexts: {
121
130
  'user.status.visitor': string;
122
131
  'update.success': string;
123
132
  'internal.server.error': string;
133
+ 'server.error.report.prompt': string;
134
+ 'server.error.report.modal.title': string;
135
+ 'server.error.report.modal.subtitle': string;
136
+ 'server.error.report.modal.placeholder': string;
137
+ 'server.error.report.modal.send': string;
138
+ 'server.error.report.validation': string;
139
+ 'server.error.report.send.failed': string;
140
+ 'server.error.report.sent': string;
124
141
  'table.label.rows-page': string;
125
142
  'table.label.items': string;
126
143
  'table.pagination.scroll-strip-left': string;
@@ -130,9 +147,10 @@ export declare const CommonTexts: {
130
147
  'document.status.deleted': string;
131
148
  'document.status.editing': string;
132
149
  'document.status.edited': string;
133
- 'document.status.revision': string;
134
- 'document.status.completed': string;
150
+ 'document.status.pos': string;
151
+ 'document.status.pos_done': string;
135
152
  'document.status.syntactic': string;
153
+ 'document.status.completed': string;
136
154
  'document.status.copying': string;
137
155
  'document.status.error': string;
138
156
  'sentence.status.todo': string;
@@ -200,6 +218,14 @@ export declare const CommonTexts: {
200
218
  'user.status.visitor': string;
201
219
  'update.success': string;
202
220
  'internal.server.error': string;
221
+ 'server.error.report.prompt': string;
222
+ 'server.error.report.modal.title': string;
223
+ 'server.error.report.modal.subtitle': string;
224
+ 'server.error.report.modal.placeholder': string;
225
+ 'server.error.report.modal.send': string;
226
+ 'server.error.report.validation': string;
227
+ 'server.error.report.send.failed': string;
228
+ 'server.error.report.sent': string;
203
229
  'table.label.rows-page': string;
204
230
  'table.label.items': string;
205
231
  'table.pagination.scroll-strip-left': string;
@@ -209,9 +235,10 @@ export declare const CommonTexts: {
209
235
  'document.status.deleted': string;
210
236
  'document.status.editing': string;
211
237
  'document.status.edited': string;
212
- 'document.status.revision': string;
213
- 'document.status.completed': string;
238
+ 'document.status.pos': string;
239
+ 'document.status.pos_done': string;
214
240
  'document.status.syntactic': string;
241
+ 'document.status.completed': string;
215
242
  'document.status.copying': string;
216
243
  'document.status.error': string;
217
244
  'sentence.status.todo': string;
@@ -42,6 +42,14 @@ export const CommonTexts = {
42
42
  'user.status.visitor': 'Visitor',
43
43
  'update.success': 'saved!',
44
44
  'internal.server.error': 'An unexpected error occurred. Contact the administrator.',
45
+ 'server.error.report.prompt': 'Something went wrong on our end. Click here to tell us what happened.',
46
+ 'server.error.report.modal.title': 'Report a problem',
47
+ 'server.error.report.modal.subtitle': 'Describe what you were doing when the error occurred. We will use your message to investigate.',
48
+ 'server.error.report.modal.placeholder': 'What were you trying to do? Any extra details help us reproduce the issue.',
49
+ 'server.error.report.modal.send': 'Send report',
50
+ 'server.error.report.validation': 'Please describe what happened before sending.',
51
+ 'server.error.report.send.failed': 'We could not send your report. Please try again in a moment.',
52
+ 'server.error.report.sent': 'Thank you — your report was sent.',
45
53
  'table.label.rows-page': 'Items per page',
46
54
  'table.label.items': '{{first}} - {{last}} of {{total}}',
47
55
  'table.pagination.scroll-strip-left': 'Scroll page numbers left',
@@ -51,9 +59,10 @@ export const CommonTexts = {
51
59
  'document.status.deleted': 'Deleted',
52
60
  'document.status.editing': 'Editing',
53
61
  'document.status.edited': 'Edited',
54
- 'document.status.revision': 'In revision',
62
+ 'document.status.pos': 'In POS revision',
63
+ 'document.status.pos_done': 'POS revision done',
64
+ 'document.status.syntactic': 'In syntatic revision',
55
65
  'document.status.completed': 'Completed',
56
- 'document.status.syntactic': 'Revision',
57
66
  'document.status.copying': 'Copying',
58
67
  'document.status.error': 'Error',
59
68
  'sentence.status.todo': 'To do',
@@ -121,6 +130,14 @@ export const CommonTexts = {
121
130
  'user.status.visitor': 'Visitante',
122
131
  'update.success': 'salvo!',
123
132
  'internal.server.error': 'Ocorreu um erro inesperado. Entre em contato com o administrador.',
133
+ 'server.error.report.prompt': 'Algo deu errado do nosso lado. Clique aqui para nos contar o que aconteceu.',
134
+ 'server.error.report.modal.title': 'Reportar um problema',
135
+ 'server.error.report.modal.subtitle': 'Descreva o que você estava fazendo quando o erro ocorreu. Usaremos sua mensagem para investigar.',
136
+ 'server.error.report.modal.placeholder': 'O que você estava tentando fazer? Detalhes extras nos ajudam a reproduzir o problema.',
137
+ 'server.error.report.modal.send': 'Enviar relatório',
138
+ 'server.error.report.validation': 'Descreva o que aconteceu antes de enviar.',
139
+ 'server.error.report.send.failed': 'Não foi possível enviar seu relatório. Tente novamente em instantes.',
140
+ 'server.error.report.sent': 'Obrigado — seu relatório foi enviado.',
124
141
  'table.label.rows-page': 'Itens por página',
125
142
  'table.label.items': '{{first}} - {{last}} de {{total}}',
126
143
  'table.pagination.scroll-strip-left': 'Rolar números das páginas à esquerda',
@@ -128,11 +145,12 @@ export const CommonTexts = {
128
145
  'tooltip.copy': 'Copiar',
129
146
  'tooltip.copied': 'Copiado!',
130
147
  'document.status.deleted': 'Excluído',
131
- 'document.status.editing': 'Editando',
148
+ 'document.status.editing': 'Em edição',
132
149
  'document.status.edited': 'Editado',
133
- 'document.status.revision': 'Em revisão',
134
- 'document.status.completed': 'Completo',
135
- 'document.status.syntactic': 'Revisão',
150
+ 'document.status.pos': 'Em revisão POS',
151
+ 'document.status.pos_done': 'Revisão POS concluída',
152
+ 'document.status.syntactic': 'Em revisão sintática',
153
+ 'document.status.completed': 'Finalizado',
136
154
  'document.status.copying': 'Copiando',
137
155
  'document.status.error': 'Erro',
138
156
  'sentence.status.todo': 'A fazer',
@@ -200,6 +218,14 @@ export const CommonTexts = {
200
218
  'user.status.visitor': 'Visitatore',
201
219
  'update.success': 'salvato!',
202
220
  'internal.server.error': "Si è verificato un errore inaspettato. Contatta l'amministratore.",
221
+ 'server.error.report.prompt': 'Qualcosa è andato storto da parte nostra. Clicca qui per raccontarci cosa è successo.',
222
+ 'server.error.report.modal.title': 'Segnala un problema',
223
+ 'server.error.report.modal.subtitle': "Descrivi cosa stavi facendo quando si è verificato l'errore. Useremo il tuo messaggio per indagare.",
224
+ 'server.error.report.modal.placeholder': 'Cosa stavi cercando di fare? Dettagli aggiuntivi ci aiutano a riprodurre il problema.',
225
+ 'server.error.report.modal.send': 'Invia segnalazione',
226
+ 'server.error.report.validation': 'Descrivi cosa è successo prima di inviare.',
227
+ 'server.error.report.send.failed': 'Impossibile inviare la segnalazione. Riprova tra poco.',
228
+ 'server.error.report.sent': 'Grazie — la tua segnalazione è stata inviata.',
203
229
  'table.label.rows-page': 'Elementi per pagina',
204
230
  'table.label.items': '{{first}} - {{last}} di {{total}}',
205
231
  'table.pagination.scroll-strip-left': 'Scorri i numeri di pagina a sinistra',
@@ -207,11 +233,12 @@ export const CommonTexts = {
207
233
  'tooltip.copy': 'Copia',
208
234
  'tooltip.copied': 'Copiato!',
209
235
  'document.status.deleted': 'Eliminato',
210
- 'document.status.editing': 'Modifica',
236
+ 'document.status.editing': 'In modifica',
211
237
  'document.status.edited': 'Modificato',
212
- 'document.status.revision': 'In revisione',
238
+ 'document.status.pos': 'In revisione POS',
239
+ 'document.status.pos_done': 'Revisione POS completata',
240
+ 'document.status.syntactic': 'In revisione sintatica',
213
241
  'document.status.completed': 'Completato',
214
- 'document.status.syntactic': 'Revisione',
215
242
  'document.status.copying': 'Copiando',
216
243
  'document.status.error': 'Errore',
217
244
  'sentence.status.todo': 'Da fare',
@@ -0,0 +1,34 @@
1
+ import type { ServerErrorContext } from '../../common/AppToast/ToastMessage';
2
+ export type ReportSendContext = {
3
+ locale?: string;
4
+ corpusUid?: string;
5
+ corpusName?: string;
6
+ };
7
+ type ClientContext = {
8
+ timestamp?: string;
9
+ timezone?: string;
10
+ appUrl?: string;
11
+ environment?: string;
12
+ locale?: string;
13
+ corpusUid?: string;
14
+ corpusName?: string;
15
+ userAgent?: string;
16
+ viewport?: string;
17
+ screen?: string;
18
+ referrer?: string;
19
+ pageUrl?: string;
20
+ };
21
+ type ErrorReportPayload = {
22
+ userMessage: string;
23
+ errorContext?: ServerErrorContext;
24
+ sendContext?: ReportSendContext;
25
+ };
26
+ declare function send({ userMessage, errorContext, sendContext, }: ErrorReportPayload): Promise<import("axios").AxiosResponse<any, {
27
+ userMessage: string;
28
+ errorContext: ServerErrorContext | undefined;
29
+ clientContext: ClientContext;
30
+ }, {}, any>>;
31
+ declare const ErrorReportService: {
32
+ send: typeof send;
33
+ };
34
+ export default ErrorReportService;
@@ -0,0 +1,63 @@
1
+ import { createApiClient } from '../api/createApiClient';
2
+ function getFunctionsApiBase() {
3
+ const explicit = import.meta.env.VITE_APP_FUNCTIONS_API;
4
+ if (explicit?.trim()) {
5
+ return explicit.trim().replace(/\/+$/, '');
6
+ }
7
+ const origin = import.meta.env.VITE_APP_ORIGIN;
8
+ if (origin?.trim()) {
9
+ return `${origin.trim().replace(/\/+$/, '')}/api/functions`;
10
+ }
11
+ return undefined;
12
+ }
13
+ function buildClientContext(sendContext) {
14
+ const now = new Date();
15
+ const timezone = typeof Intl !== 'undefined'
16
+ ? Intl.DateTimeFormat().resolvedOptions().timeZone
17
+ : undefined;
18
+ const clientContext = {
19
+ timestamp: now.toISOString(),
20
+ timezone,
21
+ environment: import.meta.env.MODE,
22
+ locale: sendContext?.locale,
23
+ corpusUid: sendContext?.corpusUid,
24
+ corpusName: sendContext?.corpusName,
25
+ };
26
+ const appUrl = import.meta.env.VITE_APP_PUBLIC_URL;
27
+ if (appUrl) {
28
+ clientContext.appUrl = appUrl;
29
+ }
30
+ if (typeof navigator !== 'undefined' && navigator.userAgent) {
31
+ clientContext.userAgent = navigator.userAgent;
32
+ }
33
+ if (typeof window !== 'undefined') {
34
+ clientContext.pageUrl = window.location.href;
35
+ clientContext.viewport = `${window.innerWidth}×${window.innerHeight}`;
36
+ const dpr = window.devicePixelRatio ?? 1;
37
+ clientContext.screen =
38
+ typeof screen !== 'undefined'
39
+ ? `${screen.width}×${screen.height} (DPR: ${dpr})`
40
+ : 'unknown';
41
+ }
42
+ if (typeof document !== 'undefined' && document.referrer) {
43
+ clientContext.referrer = document.referrer;
44
+ }
45
+ return clientContext;
46
+ }
47
+ function send({ userMessage, errorContext, sendContext, }) {
48
+ const functionsApiBase = getFunctionsApiBase();
49
+ if (!functionsApiBase) {
50
+ return Promise.reject(new Error('functions.api.unavailable'));
51
+ }
52
+ return createApiClient({
53
+ baseURL: functionsApiBase,
54
+ attachJwt: true,
55
+ serverErrorHandling: 'none',
56
+ }).post('/error-report', {
57
+ userMessage,
58
+ errorContext,
59
+ clientContext: buildClientContext(sendContext),
60
+ });
61
+ }
62
+ const ErrorReportService = { send };
63
+ export default ErrorReportService;
@@ -0,0 +1,6 @@
1
+ import type { Dispatch } from 'react';
2
+ import type { StoreAction } from './types';
3
+ type CommonDispatch = Dispatch<StoreAction>;
4
+ export declare function registerCommonDispatch(dispatch: CommonDispatch): void;
5
+ export declare function getCommonDispatch(): CommonDispatch | null;
6
+ export {};
@@ -0,0 +1,7 @@
1
+ let commonDispatch = null;
2
+ export function registerCommonDispatch(dispatch) {
3
+ commonDispatch = dispatch;
4
+ }
5
+ export function getCommonDispatch() {
6
+ return commonDispatch;
7
+ }
@@ -4,3 +4,4 @@ export { buildDriveSteps, createTourDriver, hasMatchingWorkflow, } from './drive
4
4
  export { localizeKnowledgeBase, pickLocalizedString, resolveTourLocale, TOUR_FALLBACK_LOCALE, TOUR_LOCALES, } from './localizeKnowledgeBase';
5
5
  export { resolveAnchor, resolveAnchorById, waitForSelector, } from './anchorResolver';
6
6
  export type { AnchorSelector, KnowledgeBase, KnowledgeBaseRaw, LocalizedString, PopoverSide, ResolveKnowledgeBaseUrl, TourAnchor, TourButtonLabels, TourStep, TourStepCopy, TourStepStructural, TourWorkflow, TourWorkflowRaw, UseDriverTourOptions, } from './types';
7
+ export type { Driver } from 'driver.js';
@@ -1,4 +1,5 @@
1
1
  import { useContext } from "react";
2
+ import { dispatchApiError } from "./api/dispatchApiError";
2
3
  import { message, silentLoading, toastLoading } from "../configs/store/actions";
3
4
  import CommonContext from "./CommonContext";
4
5
  const translateErrorValue = (t, namespace, rawValue) => {
@@ -49,8 +50,7 @@ export const useMessageUtils = () => {
49
50
  : toastLoading(val));
50
51
  };
51
52
  const dispatchError = ({ err, t, key }) => {
52
- const value = getErrorMessageValue({ err, t, key });
53
- dispatch(message({ value, type: "error" }));
53
+ dispatchApiError({ err, t, key });
54
54
  };
55
55
  const dispatchMessage = ({ key, ns, t }) => {
56
56
  const entry = ns ? `${ns}:${key}` : `message:${key}`;
@@ -0,0 +1,11 @@
1
+ import type { ServerErrorContext } from '../../common/AppToast/ToastMessage';
2
+ import type { Corpus } from '../../configs/types/Corpus';
3
+ import './styles.scss';
4
+ type Props = {
5
+ errorContext?: ServerErrorContext;
6
+ corpus?: Corpus;
7
+ onClose: () => void;
8
+ onSent: () => void;
9
+ };
10
+ export default function ServerErrorReport({ errorContext, corpus, onClose, onSent, }: Props): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import axios from 'axios';
3
+ import { useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import AppModal from '../../common/AppModal/AppModal';
6
+ import AppTextArea from '../../common/AppTextArea/AppTextArea';
7
+ import ErrorReportService from '../../configs/services/ErrorReportService';
8
+ import './styles.scss';
9
+ export default function ServerErrorReport({ errorContext, corpus, onClose, onSent, }) {
10
+ const { t, i18n } = useTranslation('common');
11
+ const [userMessage, setUserMessage] = useState('');
12
+ const [sending, setSending] = useState(false);
13
+ const [sendError, setSendError] = useState(null);
14
+ const handleSend = () => {
15
+ if (!userMessage.trim()) {
16
+ setSendError(t('server.error.report.validation'));
17
+ return;
18
+ }
19
+ setSending(true);
20
+ setSendError(null);
21
+ ErrorReportService.send({
22
+ userMessage,
23
+ errorContext,
24
+ sendContext: {
25
+ locale: i18n.resolvedLanguage || i18n.language,
26
+ corpusUid: corpus?.uid,
27
+ corpusName: corpus?.name,
28
+ },
29
+ })
30
+ .then(() => {
31
+ onSent();
32
+ })
33
+ .catch((error) => {
34
+ console.error('Failed to send server error report:', axios.isAxiosError(error)
35
+ ? {
36
+ message: error.message,
37
+ status: error.response?.status,
38
+ data: error.response?.data,
39
+ url: error.config?.url,
40
+ }
41
+ : error);
42
+ setSendError(t('server.error.report.send.failed'));
43
+ })
44
+ .finally(() => {
45
+ setSending(false);
46
+ });
47
+ };
48
+ return (_jsx(AppModal, { title: t('server.error.report.modal.title'), subtitle: t('server.error.report.modal.subtitle'), close: onClose, confirm: handleSend, disableConfirm: sending || !userMessage.trim(), confirmLabel: t('server.error.report.modal.send'), className: "server-error-report-modal", children: _jsxs("div", { className: "server-error-report-modal__body", children: [_jsx(AppTextArea, { value: userMessage, onChange: (e) => setUserMessage(e.target.value), placeholder: t('server.error.report.modal.placeholder'), rows: 6, disabled: sending, "aria-label": t('server.error.report.modal.placeholder') }), sendError && (_jsx("p", { className: "server-error-report-modal__error", role: "alert", children: sendError }))] }) }));
49
+ }
@@ -0,0 +1,2 @@
1
+ import ServerErrorReport from './ServerErrorReport';
2
+ export default ServerErrorReport;
@@ -0,0 +1,2 @@
1
+ import ServerErrorReport from './ServerErrorReport';
2
+ export default ServerErrorReport;
@@ -0,0 +1,20 @@
1
+ .server-error-report-modal {
2
+ &__body {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--spacing-200);
6
+ }
7
+
8
+ &__error {
9
+ margin: 0;
10
+ color: var(--text-danger);
11
+ @include body-small-1;
12
+ }
13
+
14
+ .app-text-area {
15
+ min-height: 160px;
16
+ border: var(--border-default);
17
+ border-radius: var(--radius-100);
18
+ background-color: var(--background-default);
19
+ }
20
+ }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
+ import { isHandledApiError } from '../../configs/api';
4
5
  import logo from './logo.png';
5
6
  import './style.scss';
6
7
  export default function ErrorBoundary({ children }) {
@@ -9,27 +10,30 @@ export default function ErrorBoundary({ children }) {
9
10
  hasError: false,
10
11
  error: null,
11
12
  });
12
- // Hook to simulate the behavior of componentDidCatch
13
13
  useEffect(() => {
14
14
  const handleError = (error) => {
15
15
  console.error('Caught by ErrorBoundary:', error);
16
16
  setState({ hasError: true, error });
17
17
  };
18
- // Catch unhandled promise rejections globally
19
- window.addEventListener('unhandledrejection', (event) => {
18
+ const onUnhandledRejection = (event) => {
19
+ if (isHandledApiError(event.reason)) {
20
+ event.preventDefault();
21
+ return;
22
+ }
20
23
  handleError(event.reason);
21
- });
22
- // Catch uncaught errors globally
23
- window.addEventListener('error', (event) => {
24
+ };
25
+ const onError = (event) => {
26
+ if (isHandledApiError(event.error)) {
27
+ event.preventDefault();
28
+ return;
29
+ }
24
30
  handleError(event.error);
25
- });
31
+ };
32
+ window.addEventListener('unhandledrejection', onUnhandledRejection);
33
+ window.addEventListener('error', onError);
26
34
  return () => {
27
- window.removeEventListener('unhandledrejection', (event) => {
28
- handleError(event.reason);
29
- });
30
- window.removeEventListener('error', (event) => {
31
- handleError(event.error);
32
- });
35
+ window.removeEventListener('unhandledrejection', onUnhandledRejection);
36
+ window.removeEventListener('error', onError);
33
37
  };
34
38
  }, []);
35
39
  if (state.hasError && state.error) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tycho-components",
3
3
  "private": false,
4
- "version": "0.38.7",
4
+ "version": "0.39.1",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
@@ -49,7 +49,6 @@
49
49
  "dependencies": {
50
50
  "@hookform/resolvers": "^5.2.2",
51
51
  "@tanstack/react-table": "^8.20.6",
52
- "axios": "^1.13.6",
53
52
  "classnames": "^2.5.1",
54
53
  "cytoscape": "^3.28.1",
55
54
  "cytoscape-dagre": "^2.5.0",
@@ -74,6 +73,7 @@
74
73
  "@emotion/styled": "^11.13.0",
75
74
  "@mui/icons-material": "7.3.0",
76
75
  "@mui/material": "7.3.0",
76
+ "axios": "^1.13.0",
77
77
  "i18next": "^23.3.0",
78
78
  "i18next-browser-languagedetector": "^7.1.0",
79
79
  "react": ">=17 <19",
@@ -101,6 +101,7 @@
101
101
  "@typescript-eslint/eslint-plugin": "^5.62.0",
102
102
  "@typescript-eslint/parser": "^5.62.0",
103
103
  "@vitejs/plugin-react": "^5.0.0",
104
+ "axios": "^1.13.6",
104
105
  "eslint": "^8.57.1",
105
106
  "eslint-plugin-react-hooks": "^4.6.2",
106
107
  "eslint-plugin-react-refresh": "^0.4.1",
@@ -1,10 +0,0 @@
1
- /** Platform API roots (formerly separate env vars per service). */
2
- export declare const platformApi: {
3
- base: string;
4
- revision: string;
5
- catalog: string;
6
- editor: string;
7
- };
8
- export declare const parserApiBase: string;
9
- declare const api: import("axios").AxiosInstance;
10
- export default api;
@@ -1,52 +0,0 @@
1
- import axios from 'axios';
2
- import CookieStorage from './CookieStorage';
3
- const platformApiBase = import.meta.env.VITE_APP_PLATFORM_API;
4
- /** Platform API roots (formerly separate env vars per service). */
5
- export const platformApi = {
6
- base: platformApiBase,
7
- revision: `${platformApiBase}/revision`,
8
- catalog: `${platformApiBase}/catalog`,
9
- editor: `${platformApiBase}/editor`,
10
- };
11
- export const parserApiBase = import.meta.env.VITE_APP_PARSER_API;
12
- const api = axios.create({
13
- headers: {
14
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
15
- Pragma: 'no-cache',
16
- 'Content-Type': 'application/json',
17
- Accept: 'application/json',
18
- },
19
- baseURL: platformApiBase,
20
- });
21
- const isStorybook = () => typeof window !== 'undefined' &&
22
- (window.location.port === '6006' ||
23
- window.__STORYBOOK__);
24
- api.interceptors.response.use((response) => response, async (error) => {
25
- if (error.response?.status === 401) {
26
- CookieStorage.removeJwtToken();
27
- sessionStorage.clear();
28
- localStorage.clear();
29
- if (!isStorybook()) {
30
- window.location.replace(import.meta.env.VITE_APP_AUTH_URL);
31
- }
32
- }
33
- else if (error.response?.status === 403 && !isStorybook()) {
34
- window.location.href = import.meta.env.VITE_APP_UNAUTHORIZED_URL;
35
- }
36
- else if (error.status === null) {
37
- console.log(JSON.stringify(error));
38
- }
39
- return Promise.reject(error);
40
- });
41
- api.interceptors.request.use((config) => {
42
- if (!config?.headers) {
43
- throw new Error('no header available');
44
- }
45
- const token = CookieStorage.getJwtToken();
46
- if (!token)
47
- return config;
48
- // eslint-disable-next-line no-param-reassign
49
- config.headers.Authorization = `Bearer ${token}`;
50
- return config;
51
- });
52
- export default api;