tycho-components 0.43.5 → 0.44.0

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;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Build-time whitelabel: name, logo, favicon only.
3
+ * Brand packs are served at site root: {VITE_APP_ORIGIN}/brands/{id}/
4
+ * (not bundled per app — upload packs on each host / shared nginx).
5
+ */
6
+ export type BrandId = 'tycho' | 'corpushub';
7
+ export type AppCode = 'admin' | 'auth' | 'cs-analyzer' | 'dictionary' | 'editor' | 'io' | 'lexicon' | 'parser' | 'psd-reindexer' | 'reserved' | 'search' | 'syntrees' | 'viewer';
8
+ export type BrandConfig = {
9
+ id: BrandId;
10
+ name: string;
11
+ completeName: string;
12
+ logo: string;
13
+ documentTitle: string;
14
+ /** Absolute URL prefix: https://host/brands/{id} */
15
+ faviconBase: string;
16
+ socialMedia?: {
17
+ youtube?: string;
18
+ };
19
+ };
20
+ export declare function brandAssetBase(brandId: BrandId, originRaw: string | undefined): string;
21
+ /**
22
+ * Resolve platform branding for a front-end app.
23
+ * Pass `options` from Vite plugins (Node) where `import.meta.env` may be unavailable.
24
+ */
25
+ export declare function createBrand(app: AppCode, options?: {
26
+ brandId?: string;
27
+ origin?: string;
28
+ }): BrandConfig;
29
+ /** Shared helper for tests / tooling. */
30
+ export declare function brandFromEnv(raw: string | undefined): BrandId;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Build-time whitelabel: name, logo, favicon only.
3
+ * Brand packs are served at site root: {VITE_APP_ORIGIN}/brands/{id}/
4
+ * (not bundled per app — upload packs on each host / shared nginx).
5
+ */
6
+ const APP_TITLE_SUFFIX = {
7
+ admin: 'Admin',
8
+ auth: 'Auth',
9
+ 'cs-analyzer': 'CS Analyzer',
10
+ dictionary: 'Dictionary',
11
+ editor: 'Editor',
12
+ io: 'IO',
13
+ lexicon: 'Lexicon',
14
+ parser: 'Parser',
15
+ 'psd-reindexer': 'PSD Re-Indexer',
16
+ reserved: 'Reserved Area',
17
+ search: 'Search',
18
+ syntrees: 'Syntrees',
19
+ viewer: 'Viewer',
20
+ };
21
+ const DEFAULT_YOUTUBE = 'https://www.youtube.com/@tychoplatform';
22
+ function trimOrigin(raw) {
23
+ return (raw || '').replace(/\/+$/, '');
24
+ }
25
+ export function brandAssetBase(brandId, originRaw) {
26
+ const origin = trimOrigin(originRaw);
27
+ if (!origin) {
28
+ // Last resort for misconfigured builds — prefer setting VITE_APP_ORIGIN.
29
+ return `/brands/${brandId}`;
30
+ }
31
+ return `${origin}/brands/${brandId}`;
32
+ }
33
+ function resolveBrandId(raw) {
34
+ return raw?.toLowerCase() === 'corpushub' ? 'corpushub' : 'tycho';
35
+ }
36
+ function readEnvBrandId() {
37
+ try {
38
+ return import.meta.env?.VITE_APP_BRAND;
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ function readEnvOrigin() {
45
+ try {
46
+ return import.meta.env?.VITE_APP_ORIGIN;
47
+ }
48
+ catch {
49
+ return undefined;
50
+ }
51
+ }
52
+ /**
53
+ * Resolve platform branding for a front-end app.
54
+ * Pass `options` from Vite plugins (Node) where `import.meta.env` may be unavailable.
55
+ */
56
+ export function createBrand(app, options) {
57
+ const id = resolveBrandId(options?.brandId ?? readEnvBrandId());
58
+ const origin = options?.origin ?? readEnvOrigin();
59
+ const faviconBase = brandAssetBase(id, origin);
60
+ const suffix = APP_TITLE_SUFFIX[app];
61
+ const meta = id === 'corpushub'
62
+ ? {
63
+ name: 'CorpusHub',
64
+ completeName: 'CorpusHub',
65
+ documentTitle: `CorpusHub - ${suffix}`,
66
+ }
67
+ : {
68
+ name: 'Tycho',
69
+ completeName: 'Tycho Platform',
70
+ documentTitle: `Tycho Platform - ${suffix}`,
71
+ };
72
+ return {
73
+ id,
74
+ ...meta,
75
+ faviconBase,
76
+ logo: `${faviconBase}/logo.png`,
77
+ socialMedia: {
78
+ youtube: DEFAULT_YOUTUBE,
79
+ },
80
+ };
81
+ }
82
+ /** Shared helper for tests / tooling. */
83
+ export function brandFromEnv(raw) {
84
+ return resolveBrandId(raw);
85
+ }
@@ -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";
@@ -35,5 +35,7 @@ export { useCorpusUtils } from "./useCorpusUtils";
35
35
  export { useLoggedUtils } from "./useLoggedUtils";
36
36
  export { useMessageUtils } from "./useMessageUtils";
37
37
  export { useTourUtils } from "./useTourUtils";
38
+ export { brandAssetBase, brandFromEnv, createBrand, } from "./brand";
39
+ export type { AppCode, BrandConfig, BrandId } from "./brand";
38
40
  export { useDriverTour, loadKnowledgeBase, defaultMatchPathnames, buildAnchorMap, flattenWorkflowSteps, getWorkflowsForPath, buildDriveSteps, createTourDriver, hasMatchingWorkflow, resolveAnchor, resolveAnchorById, waitForSelector, localizeKnowledgeBase, pickLocalizedString, resolveTourLocale, TOUR_FALLBACK_LOCALE, TOUR_LOCALES, } from "./tour";
39
41
  export type { AnchorSelector, Driver, KnowledgeBase, KnowledgeBaseRaw, LocalizedString, PopoverSide, ResolveKnowledgeBaseUrl, TourAnchor, TourButtonLabels, TourStep, TourStepCopy, TourStepStructural, TourWorkflow, TourWorkflowRaw, UseDriverTourOptions, } from "./tour";
@@ -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";
@@ -19,4 +19,5 @@ export { useCorpusUtils } from "./useCorpusUtils";
19
19
  export { useLoggedUtils } from "./useLoggedUtils";
20
20
  export { useMessageUtils } from "./useMessageUtils";
21
21
  export { useTourUtils } from "./useTourUtils";
22
+ export { brandAssetBase, brandFromEnv, createBrand, } from "./brand";
22
23
  export { useDriverTour, loadKnowledgeBase, defaultMatchPathnames, buildAnchorMap, flattenWorkflowSteps, getWorkflowsForPath, buildDriveSteps, createTourDriver, hasMatchingWorkflow, resolveAnchor, resolveAnchorById, waitForSelector, localizeKnowledgeBase, pickLocalizedString, resolveTourLocale, TOUR_FALLBACK_LOCALE, TOUR_LOCALES, } from "./tour";
@@ -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
  }
@@ -1,5 +1,7 @@
1
1
  import { HelpAction } from './HelpButton/HelpButton';
2
+ import type { HeaderBrand, HeaderSocialMedia } from './types/Brand';
2
3
  import './styles.scss';
4
+ export type { HeaderBrand, HeaderSocialMedia } from './types/Brand';
3
5
  type Props = {
4
6
  tool: string;
5
7
  redirect?: string;
@@ -22,12 +24,9 @@ type Props = {
22
24
  hideNotifications?: boolean;
23
25
  hideLanguageSelector?: boolean;
24
26
  hideHelpButton?: boolean;
25
- /** Overrides i18n `label.platform` (e.g. CorpusHub whitelabel). */
26
- platformName?: string;
27
- /** Overrides i18n `label.platform.complete` in the apps drawer. */
28
- platformCompleteName?: string;
29
- /** Overrides the apps-drawer platform tile image. */
30
- platformLogo?: string;
27
+ /** Overrides i18n platform labels and apps-drawer logo (e.g. CorpusHub whitelabel). */
28
+ brand?: HeaderBrand;
29
+ /** Optional social links shown in the apps drawer Quick section. */
30
+ socialMedia?: HeaderSocialMedia;
31
31
  };
32
- export default function Header({ tool, redirect, autoload, hideKeyboard, customHeader, notifications, keyboardLayout, navigateHome, navigateCorpora, navigateLogout, navigateNotLogged, helpActions, useOpenCorpora, hideCorpora, hideNotifications, hideLanguageSelector, hideHelpButton, platformName, platformCompleteName, platformLogo, }: Props): import("react").JSX.Element;
33
- export {};
32
+ export default function Header({ tool, redirect, autoload, hideKeyboard, customHeader, notifications, keyboardLayout, navigateHome, navigateCorpora, navigateLogout, navigateNotLogged, helpActions, useOpenCorpora, hideCorpora, hideNotifications, hideLanguageSelector, hideHelpButton, brand, socialMedia, }: Props): import("react").JSX.Element;
@@ -8,10 +8,10 @@ import HeaderUser from './HeaderUser';
8
8
  import './styles.scss';
9
9
  export default function Header({ tool, redirect, autoload, hideKeyboard, customHeader, notifications, keyboardLayout, navigateHome, navigateCorpora, navigateLogout, navigateNotLogged = () => {
10
10
  location.href = '/auth';
11
- }, helpActions, useOpenCorpora = true, hideCorpora = false, hideNotifications = false, hideLanguageSelector = false, hideHelpButton = false, platformName, platformCompleteName, platformLogo, }) {
11
+ }, helpActions, useOpenCorpora = true, hideCorpora = false, hideNotifications = false, hideLanguageSelector = false, hideHelpButton = false, brand, socialMedia, }) {
12
12
  const { t } = useTranslation('header');
13
13
  const homeTextsClass = cx('app-title', {
14
14
  pointer: navigateHome !== undefined,
15
15
  });
16
- return (_jsxs("div", { className: "ds-header", children: [_jsxs("div", { className: "header-left", children: [_jsx(HeaderApps, { navigateLogout: navigateLogout, navigateNotLogged: navigateNotLogged, hideKeyboard: hideKeyboard, notifications: notifications, keyboardLayout: keyboardLayout, helpActions: helpActions, platformCompleteName: platformCompleteName, platformLogo: platformLogo }), _jsxs("div", { className: homeTextsClass, onClick: () => navigateHome && navigateHome(), children: [_jsx("span", { className: "title", children: platformName ?? t('label.platform') }), _jsx("span", { className: "subtitle", children: tool })] }), _jsxs("div", { className: "header-corpus", children: [!hideCorpora && (_jsx(HeaderCorpora, { redirect: redirect, autoload: autoload, navigateCorpora: navigateCorpora, useOpenCorpora: useOpenCorpora })), customHeader && customHeader] })] }), _jsxs("div", { className: "header-right", children: [_jsx(HeaderButtons, { hideKeyboard: hideKeyboard, notifications: notifications, keyboardLayout: keyboardLayout, helpActions: helpActions, hideNotifications: hideNotifications, hideLanguageSelector: hideLanguageSelector, hideHelpButton: hideHelpButton }), _jsx(HeaderUser, { navigateLogout: navigateLogout, navigateNotLogged: navigateNotLogged })] })] }));
16
+ return (_jsxs("div", { className: "ds-header", children: [_jsxs("div", { className: "header-left", children: [_jsx(HeaderApps, { navigateLogout: navigateLogout, navigateNotLogged: navigateNotLogged, hideKeyboard: hideKeyboard, notifications: notifications, keyboardLayout: keyboardLayout, helpActions: helpActions, brand: brand, socialMedia: socialMedia }), _jsxs("div", { className: homeTextsClass, onClick: () => navigateHome && navigateHome(), children: [_jsx("span", { className: "title", children: brand?.name ?? t('label.platform') }), _jsx("span", { className: "subtitle", children: tool })] }), _jsxs("div", { className: "header-corpus", children: [!hideCorpora && (_jsx(HeaderCorpora, { redirect: redirect, autoload: autoload, navigateCorpora: navigateCorpora, useOpenCorpora: useOpenCorpora })), customHeader && customHeader] })] }), _jsxs("div", { className: "header-right", children: [_jsx(HeaderButtons, { hideKeyboard: hideKeyboard, notifications: notifications, keyboardLayout: keyboardLayout, helpActions: helpActions, hideNotifications: hideNotifications, hideLanguageSelector: hideLanguageSelector, hideHelpButton: hideHelpButton }), _jsx(HeaderUser, { navigateLogout: navigateLogout, navigateNotLogged: navigateNotLogged })] })] }));
17
17
  }
@@ -1,4 +1,5 @@
1
1
  import { HelpAction } from "../HelpButton/HelpButton";
2
+ import type { HeaderBrand, HeaderSocialMedia } from "../types/Brand";
2
3
  import "./style.scss";
3
4
  type Props = {
4
5
  navigateLogout?: () => void;
@@ -10,8 +11,8 @@ type Props = {
10
11
  };
11
12
  keyboardLayout?: string;
12
13
  helpActions?: HelpAction[];
13
- platformCompleteName?: string;
14
- platformLogo?: string;
14
+ brand?: HeaderBrand;
15
+ socialMedia?: HeaderSocialMedia;
15
16
  };
16
- export default function HeaderApps({ navigateLogout, navigateNotLogged, hideKeyboard, notifications, keyboardLayout, helpActions, platformCompleteName, platformLogo, }: Props): import("react").JSX.Element;
17
+ export default function HeaderApps({ navigateLogout, navigateNotLogged, hideKeyboard, notifications, keyboardLayout, helpActions, brand, socialMedia, }: Props): import("react").JSX.Element;
17
18
  export {};
@@ -11,7 +11,7 @@ import { AvailableApps } from "../types/App";
11
11
  import logo from "./logo.png";
12
12
  import youtube from "./youtube.png";
13
13
  import "./style.scss";
14
- export default function HeaderApps({ navigateLogout, navigateNotLogged, hideKeyboard, notifications, keyboardLayout, helpActions, platformCompleteName, platformLogo, }) {
14
+ export default function HeaderApps({ navigateLogout, navigateNotLogged, hideKeyboard, notifications, keyboardLayout, helpActions, brand, socialMedia, }) {
15
15
  const { t } = useTranslation("header-app");
16
16
  const { getCorpus, hasCorpus } = useCorpusUtils();
17
17
  const { isAdminOfAnyCorpus, isEditorOfAnyCorpus, isLogged } = useLoggedUtils();
@@ -20,22 +20,33 @@ export default function HeaderApps({ navigateLogout, navigateNotLogged, hideKeyb
20
20
  const resources = [
21
21
  {
22
22
  code: "platform",
23
- image: platformLogo || logo,
23
+ image: brand?.logo || logo,
24
24
  visibility: "public",
25
25
  external: platformOrigin || undefined,
26
26
  },
27
- {
28
- code: "youtube",
29
- image: youtube,
30
- visibility: "public",
31
- external: "https://www.youtube.com/@tychoplatform",
32
- },
27
+ ...(socialMedia?.youtube
28
+ ? [
29
+ {
30
+ code: "youtube",
31
+ image: youtube,
32
+ visibility: "public",
33
+ external: socialMedia.youtube,
34
+ },
35
+ ]
36
+ : []),
33
37
  ];
34
38
  const goto = (app, blank) => {
35
- const shouldAppendCorpusUid = app.appendCorpusUid !== false;
36
- const url = hasCorpus() && shouldAppendCorpusUid
37
- ? `/${app.code}/${getCorpus().uid}`
38
- : `/${app.code}`;
39
+ let url;
40
+ if (app.external) {
41
+ url = app.external;
42
+ }
43
+ else {
44
+ const shouldAppendCorpusUid = app.appendCorpusUid !== false;
45
+ url =
46
+ hasCorpus() && shouldAppendCorpusUid
47
+ ? `/${app.code}/${getCorpus().uid}`
48
+ : `/${app.code}`;
49
+ }
39
50
  if (blank) {
40
51
  window.open(url, "_blank");
41
52
  }
@@ -50,12 +61,12 @@ export default function HeaderApps({ navigateLogout, navigateNotLogged, hideKeyb
50
61
  return null;
51
62
  if (item.editor && !isEditorOfAnyCorpus())
52
63
  return null;
53
- return (_jsxs("div", { className: "item", title: t(`${item.code}.desc`), onClick: () => goto(item, item.external ? true : false), children: [item.icon && _jsx(Icon, { name: item.icon, size: "medium" }), item.image && _jsx("img", { src: item.image }), _jsx("span", { className: "title-app", children: item.code === "platform" && platformCompleteName
54
- ? platformCompleteName
64
+ return (_jsxs("div", { className: "item", title: t(`${item.code}.desc`), onClick: () => goto(item, item.external ? true : false), children: [item.icon && _jsx(Icon, { name: item.icon, size: "medium" }), item.image && _jsx("img", { src: item.image }), _jsx("span", { className: "title-app", children: item.code === "platform" && brand?.completeName
65
+ ? brand.completeName
55
66
  : t(`${item.code}.name`) }), _jsxs("div", { className: "options", children: [item.visibility && (_jsx(Tag, { text: t(`common:label.${item.visibility}`), size: "small", color: "green", className: "d-none" })), _jsx(IconButton, { name: "open_in_new", size: "x-small", className: "icon-open", mode: "ghost", onClick: (e) => {
56
67
  e.stopPropagation();
57
68
  goto(item, true);
58
69
  }, title: t("label.open.tab") })] })] }, idx.valueOf()));
59
70
  };
60
- return (_jsxs("div", { className: "header-apps-container", children: [_jsx(IconButton, { name: "apps", className: "icon-apps", size: "large", onClick: () => setOpen(!open), filledIcon: true, "data-tour": "header-apps" }), open && (_jsxs(Drawer, { anchor: "left", open: true, onClose: () => setOpen(false), className: "offcanvas-apps", children: [_jsxs("div", { className: "header", children: [_jsx(IconButton, { name: "close", size: "medium", mode: "ghost", iconSize: "medium", onClick: () => setOpen(false) }), _jsx("span", { className: "header-apps-title", children: platformCompleteName ?? t("header:label.platform.complete") }), _jsx("div", { className: "header-profile-apps", children: _jsx(HeaderUser, { navigateLogout: navigateLogout, navigateNotLogged: navigateNotLogged }) })] }), _jsxs("div", { className: "body", children: [_jsx("div", { className: "title", children: t("label.tools") }), AvailableApps.map((item, idx) => renderItem(item, idx)), _jsx("div", { className: "title", children: t("label.quick") }), resources.map((item, idx) => renderItem(item, idx))] }), _jsx("div", { className: "footer", children: _jsx("div", { className: "header-apps-buttons", children: _jsx(HeaderButtons, { hideKeyboard: hideKeyboard, notifications: notifications, keyboardLayout: keyboardLayout, helpActions: helpActions, mobile: true }) }) })] }))] }));
71
+ return (_jsxs("div", { className: "header-apps-container", children: [_jsx(IconButton, { name: "apps", className: "icon-apps", size: "large", onClick: () => setOpen(!open), filledIcon: true, "data-tour": "header-apps" }), open && (_jsxs(Drawer, { anchor: "left", open: true, onClose: () => setOpen(false), className: "offcanvas-apps", children: [_jsxs("div", { className: "header", children: [_jsx(IconButton, { name: "close", size: "medium", mode: "ghost", iconSize: "medium", onClick: () => setOpen(false) }), _jsx("span", { className: "header-apps-title", children: brand?.completeName ?? t("header:label.platform.complete") }), _jsx("div", { className: "header-profile-apps", children: _jsx(HeaderUser, { navigateLogout: navigateLogout, navigateNotLogged: navigateNotLogged }) })] }), _jsxs("div", { className: "body", children: [_jsx("div", { className: "title", children: t("label.tools") }), AvailableApps.map((item, idx) => renderItem(item, idx)), _jsx("div", { className: "title", children: t("label.quick") }), resources.map((item, idx) => renderItem(item, idx))] }), _jsx("div", { className: "footer", children: _jsx("div", { className: "header-apps-buttons", children: _jsx(HeaderButtons, { hideKeyboard: hideKeyboard, notifications: notifications, keyboardLayout: keyboardLayout, helpActions: helpActions, mobile: true }) }) })] }))] }));
61
72
  }
@@ -0,0 +1,8 @@
1
+ export type HeaderBrand = {
2
+ name?: string;
3
+ completeName?: string;
4
+ logo?: string;
5
+ };
6
+ export type HeaderSocialMedia = {
7
+ youtube?: string;
8
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -5,6 +5,7 @@ export { default as Logout } from './Base/Logout';
5
5
  export { default as NotFound } from './Base/NotFound';
6
6
  export { default as Unauthorized } from './Base/Unauthorized';
7
7
  export { default as Header } from './Header';
8
+ export type { HeaderBrand, HeaderSocialMedia } from './Header/types/Brand';
8
9
  export { default as HeaderCorpus } from './Header/HeaderCorpora/HeaderCorpus';
9
10
  export { default as HelpButton } from './Header/HelpButton';
10
11
  export type { HelpAction } from './Header/HelpButton/HelpButton';
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.44.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {