diff --git a/frontend/apps/console/package.json b/frontend/apps/console/package.json
index aafd312a96..05b9772ce0 100644
--- a/frontend/apps/console/package.json
+++ b/frontend/apps/console/package.json
@@ -64,12 +64,16 @@
"@thunderid/configure-agent-types": "workspace:^",
"@thunderid/configure-applications": "workspace:^",
"@thunderid/configure-connections": "workspace:^",
+ "@thunderid/configure-design": "workspace:^",
+ "@thunderid/configure-groups": "workspace:^",
+ "@thunderid/configure-import-export": "workspace:^",
"@thunderid/configure-users": "workspace:^",
"@thunderid/configure-user-types": "workspace:^",
"@thunderid/configure-organization-units": "workspace:^",
"@thunderid/configure-translations": "workspace:^",
"@thunderid/configure-resource-servers": "workspace:^",
- "@thunderid/configure-verifiable-presentations": "workspace:^",
+ "@thunderid/configure-settings": "workspace:^",
+ "@thunderid/configure-verifiable-credentials": "workspace:^",
"@thunderid/utils": "workspace:^",
"@wso2/oxygen-ui": "catalog:",
"@wso2/oxygen-ui-icons-react": "catalog:",
diff --git a/frontend/apps/console/src/App.tsx b/frontend/apps/console/src/App.tsx
index 4d1f72660e..e092e1d752 100644
--- a/frontend/apps/console/src/App.tsx
+++ b/frontend/apps/console/src/App.tsx
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import {PageLoader} from '@thunderid/components';
+import {LayoutBuilderProvider, ThemeBuilderProvider} from '@thunderid/configure-design';
+import {GroupCreateProvider} from '@thunderid/configure-groups';
import {OrganizationUnitProvider} from '@thunderid/configure-organization-units';
import {TranslationCreateProvider} from '@thunderid/configure-translations';
import {UserTypeCreateProvider} from '@thunderid/configure-user-types';
@@ -12,9 +14,6 @@ import {BrowserRouter, Navigate, Outlet, Route, Routes} from 'react-router';
import RouteConfig, {ROUTE_SEGMENTS} from './configs/RouteConfig';
import AgentCreateProvider from './features/agents/contexts/AgentCreate/AgentCreateProvider';
import ApplicationCreateProvider from './features/applications/contexts/ApplicationCreate/ApplicationCreateProvider';
-import LayoutBuilderProvider from './features/design/contexts/LayoutBuilder/LayoutBuilderProvider';
-import ThemeBuilderProvider from './features/design/contexts/ThemeBuilder/ThemeBuilderProvider';
-import GroupCreateProvider from './features/groups/contexts/GroupCreate/GroupCreateProvider';
import OrganizationUnitDefaultFlowsSettings from './features/organization-units/OrganizationUnitDefaultFlowsSettings';
import RoleCreateProvider from './features/roles/contexts/RoleCreate/RoleCreateProvider';
import WelcomeRedirect from './features/welcome/components/WelcomeRedirect';
@@ -71,31 +70,39 @@ const ApplicationEditPage = lazy(() =>
);
const ApplicationsListPage = lazy(() => import('./features/applications/pages/ApplicationsListPage'));
const ApplicationTemplateSelectPage = lazy(() => import('./features/applications/pages/ApplicationTemplateSelectPage'));
-const DesignPage = lazy(() => import('./features/design/pages/DesignPage'));
+const DesignPage = lazy(() => import('@thunderid/configure-design').then((m) => ({default: m.DesignPage})));
const LayoutBuilderPage = lazy(() =>
- import('./lib/monaco-setup').then(() => import('./features/design/pages/LayoutBuilderPage')),
+ import('./lib/monaco-setup').then(() =>
+ import('@thunderid/configure-design').then((m) => ({default: m.LayoutBuilderPage})),
+ ),
);
-const ThemeBuilderPage = lazy(() => import('./features/design/pages/ThemeBuilderPage'));
-const ThemeCreatePage = lazy(() => import('./features/design/pages/ThemeCreatePage'));
+const ThemeBuilderPage = lazy(() => import('@thunderid/configure-design').then((m) => ({default: m.ThemeBuilderPage})));
+const ThemeCreatePage = lazy(() => import('@thunderid/configure-design').then((m) => ({default: m.ThemeCreatePage})));
const FlowCreatePage = lazy(() => import('./features/flows/pages/FlowCreatePage'));
const FlowsListPage = lazy(() => import('./features/flows/pages/FlowsListPage'));
-const CreateGroupPage = lazy(() => import('./features/groups/pages/CreateGroupPage'));
-const GroupEditPage = lazy(() => import('./features/groups/pages/GroupEditPage'));
-const GroupsListPage = lazy(() => import('./features/groups/pages/GroupsListPage'));
+const CreateGroupPage = lazy(() => import('@thunderid/configure-groups').then((m) => ({default: m.CreateGroupPage})));
+const GroupEditPage = lazy(() => import('@thunderid/configure-groups').then((m) => ({default: m.GroupEditPage})));
+const GroupsListPage = lazy(() => import('@thunderid/configure-groups').then((m) => ({default: m.GroupsListPage})));
const HomePage = lazy(() => import('./features/home/pages/HomePage'));
const ExportPage = lazy(() =>
- import('./lib/monaco-setup').then(() => import('./features/import-export/pages/ExportPage')),
+ import('./lib/monaco-setup').then(() =>
+ import('@thunderid/configure-import-export').then((m) => ({default: m.ExportPage})),
+ ),
);
const ImportConfigurationSummaryPage = lazy(() =>
- import('./lib/monaco-setup').then(() => import('./features/import-export/pages/ImportConfigurationSummaryPage')),
+ import('./lib/monaco-setup').then(() =>
+ import('@thunderid/configure-import-export').then((m) => ({default: m.ImportConfigurationSummaryPage})),
+ ),
+);
+const ImportConfigurationUploadPage = lazy(() =>
+ import('@thunderid/configure-import-export').then((m) => ({default: m.ImportConfigurationUploadPage})),
);
-const ImportConfigurationUploadPage = lazy(
- () => import('./features/import-export/pages/ImportConfigurationUploadPage'),
+const ImportConfigurationValidatePage = lazy(() =>
+ import('@thunderid/configure-import-export').then((m) => ({default: m.ImportConfigurationValidatePage})),
);
-const ImportConfigurationValidatePage = lazy(
- () => import('./features/import-export/pages/ImportConfigurationValidatePage'),
+const ImportExportPage = lazy(() =>
+ import('@thunderid/configure-import-export').then((m) => ({default: m.ImportExportPage})),
);
-const ImportExportPage = lazy(() => import('./features/import-export/pages/ImportExportPage'));
const ConnectionsListPage = lazy(() =>
import('@thunderid/configure-connections').then((m) => ({default: m.ConnectionsListPage})),
);
@@ -112,27 +119,27 @@ const FlowBuilderPage = lazy(() => import('./features/flows/pages/FlowBuilderPag
const CreateRolePage = lazy(() => import('./features/roles/pages/CreateRolePage'));
const RoleEditPage = lazy(() => import('./features/roles/pages/RoleEditPage'));
const RolesListPage = lazy(() => import('./features/roles/pages/RolesListPage'));
-const SettingsPage = lazy(() => import('./features/settings/pages/SettingsPage'));
+const SettingsPage = lazy(() => import('@thunderid/configure-settings').then((m) => ({default: m.SettingsPage})));
const TrustedIssuerDetailPage = lazy(() =>
import('@thunderid/configure-connections').then((m) => ({default: m.TrustedIssuerDetailPage})),
);
-const VerifiablePresentationsListPage = lazy(
- () => import('./features/verifiable-presentations/pages/VerifiablePresentationsListPage'),
+const VerifiablePresentationsListPage = lazy(() =>
+ import('@thunderid/configure-verifiable-credentials').then((m) => ({default: m.VerifiablePresentationsListPage})),
);
-const VerifiablePresentationCreatePage = lazy(
- () => import('./features/verifiable-presentations/pages/VerifiablePresentationCreatePage'),
+const VerifiablePresentationCreatePage = lazy(() =>
+ import('@thunderid/configure-verifiable-credentials').then((m) => ({default: m.VerifiablePresentationCreatePage})),
);
-const VerifiablePresentationEditPage = lazy(
- () => import('./features/verifiable-presentations/pages/VerifiablePresentationEditPage'),
+const VerifiablePresentationEditPage = lazy(() =>
+ import('@thunderid/configure-verifiable-credentials').then((m) => ({default: m.VerifiablePresentationEditPage})),
);
-const VerifiableCredentialsListPage = lazy(
- () => import('./features/verifiable-credentials/pages/VerifiableCredentialsListPage'),
+const VerifiableCredentialsListPage = lazy(() =>
+ import('@thunderid/configure-verifiable-credentials').then((m) => ({default: m.VerifiableCredentialsListPage})),
);
-const VerifiableCredentialCreatePage = lazy(
- () => import('./features/verifiable-credentials/pages/VerifiableCredentialCreatePage'),
+const VerifiableCredentialCreatePage = lazy(() =>
+ import('@thunderid/configure-verifiable-credentials').then((m) => ({default: m.VerifiableCredentialCreatePage})),
);
-const VerifiableCredentialEditPage = lazy(
- () => import('./features/verifiable-credentials/pages/VerifiableCredentialEditPage'),
+const VerifiableCredentialEditPage = lazy(() =>
+ import('@thunderid/configure-verifiable-credentials').then((m) => ({default: m.VerifiableCredentialEditPage})),
);
const CreateUserTypePage = lazy(() =>
import('@thunderid/configure-user-types').then((m) => ({default: m.CreateUserTypePage})),
diff --git a/frontend/apps/console/src/__tests__/App.test.tsx b/frontend/apps/console/src/__tests__/App.test.tsx
index 15cb8207dd..e72b822406 100644
--- a/frontend/apps/console/src/__tests__/App.test.tsx
+++ b/frontend/apps/console/src/__tests__/App.test.tsx
@@ -66,20 +66,37 @@ vi.mock('../features/applications/pages/ApplicationEditPage', () => ({
default: () =>
Application Edit Page
,
}));
-vi.mock('../features/design/pages/LayoutBuilderPage', () => ({
- default: () => Layout Builder Page
,
+vi.mock('@thunderid/configure-design', async (importOriginal) => ({
+ ...(await importOriginal()),
+ LayoutBuilderPage: () => Layout Builder Page
,
+ LayoutBuilderProvider: ({children}: {children: React.ReactNode}) => children as React.ReactElement,
+ DesignPage: () => Design Page
,
+ ThemeBuilderPage: () => Theme Builder Page
,
+ ThemeCreatePage: () => Theme Create Page
,
+ ThemeBuilderProvider: ({children}: {children: React.ReactNode}) => children as React.ReactElement,
}));
-vi.mock('../features/design/contexts/LayoutBuilder/LayoutBuilderProvider', () => ({
- default: ({children}: {children: React.ReactNode}) => children as React.ReactElement,
+vi.mock('@thunderid/configure-groups', async (importOriginal) => ({
+ ...(await importOriginal()),
+ GroupsListPage: () => Groups List Page
,
+ GroupEditPage: () => Group Edit Page
,
+ CreateGroupPage: () => Create Group Page
,
+ GroupCreateProvider: ({children}: {children: React.ReactNode}) => children as React.ReactElement,
}));
-vi.mock('../features/import-export/pages/ExportPage', () => ({
- default: () => Export Page
,
-}));
-
-vi.mock('../features/import-export/pages/ImportConfigurationSummaryPage', () => ({
- default: () => Import Configuration Summary Page
,
+vi.mock('@thunderid/configure-import-export', async (importOriginal) => ({
+ ...(await importOriginal()),
+ ExportPage: () => Export Page
,
+ ImportConfigurationSummaryPage: () => (
+ Import Configuration Summary Page
+ ),
+ ImportConfigurationUploadPage: () => (
+ Import Configuration Upload Page
+ ),
+ ImportConfigurationValidatePage: () => (
+ Import Configuration Validate Page
+ ),
+ ImportExportPage: () => Import Export Page
,
}));
vi.mock('@thunderid/configure-resource-servers', () => ({
@@ -239,4 +256,76 @@ describe('App', () => {
expect(screen.getByTestId('trusted-issuer-detail-page')).toBeInTheDocument();
});
});
+
+ it('loads DesignPage lazily at /design', async () => {
+ window.history.pushState({}, '', '/design');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('design-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads ThemeCreatePage lazily at /design/themes/create', async () => {
+ window.history.pushState({}, '', '/design/themes/create');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('theme-create-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads ThemeBuilderPage lazily at /design/themes/:themeId', async () => {
+ window.history.pushState({}, '', '/design/themes/theme-123');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('theme-builder-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads GroupsListPage lazily at /groups', async () => {
+ window.history.pushState({}, '', '/groups');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('groups-list-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads GroupEditPage lazily at /groups/:groupId', async () => {
+ window.history.pushState({}, '', '/groups/group-123');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('group-edit-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads CreateGroupPage lazily at /groups/create', async () => {
+ window.history.pushState({}, '', '/groups/create');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('create-group-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads ImportExportPage lazily at /import-export', async () => {
+ window.history.pushState({}, '', '/import-export');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('import-export-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads ImportConfigurationUploadPage lazily at /import-configuration', async () => {
+ window.history.pushState({}, '', '/import-configuration');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('import-configuration-upload-page')).toBeInTheDocument();
+ });
+ });
+
+ it('loads ImportConfigurationValidatePage lazily at /import-configuration/validate', async () => {
+ window.history.pushState({}, '', '/import-configuration/validate');
+ render();
+ await waitFor(() => {
+ expect(screen.getByTestId('import-configuration-validate-page')).toBeInTheDocument();
+ });
+ });
});
diff --git a/frontend/apps/console/src/configs/RouteConfig.ts b/frontend/apps/console/src/configs/RouteConfig.ts
index d9224ea364..2dd3e27406 100644
--- a/frontend/apps/console/src/configs/RouteConfig.ts
+++ b/frontend/apps/console/src/configs/RouteConfig.ts
@@ -3,11 +3,15 @@
import type {AgentTypeRoutePaths} from '@thunderid/configure-agent-types';
import type {ConnectionRoutePaths} from '@thunderid/configure-connections';
+import type {DesignRoutePaths} from '@thunderid/configure-design';
+import type {GroupRoutePaths} from '@thunderid/configure-groups';
+import type {ImportExportRoutePaths} from '@thunderid/configure-import-export';
import type {OrganizationUnitRoutePaths} from '@thunderid/configure-organization-units';
import type {ResourceServerRoutePaths} from '@thunderid/configure-resource-servers';
import type {TranslationRoutePaths} from '@thunderid/configure-translations';
import type {UserTypeRoutePaths} from '@thunderid/configure-user-types';
import type {UserRoutePaths} from '@thunderid/configure-users';
+import type {VerifiableCredentialRoutePaths} from '@thunderid/configure-verifiable-credentials';
/**
* Route paths for the domains Console implements itself, rather than through a
@@ -33,48 +37,16 @@ export interface ConsoleRoutePaths {
detail: (id: string) => string;
create: () => string;
};
- groups: {
- list: () => string;
- detail: (id: string) => string;
- create: () => string;
- };
roles: {
list: () => string;
detail: (id: string) => string;
create: () => string;
};
- verifiableCredentials: {
- list: () => string;
- detail: (id: string) => string;
- create: () => string;
- };
- verifiablePresentations: {
- list: () => string;
- detail: (id: string) => string;
- create: () => string;
- };
flows: {
list: () => string;
create: () => string;
detail: (flowId: string) => string;
};
- design: {
- list: () => string;
- themesCreate: () => string;
- themeDetail: (themeId: string) => string;
- layoutDetail: (layoutId: string) => string;
- };
- importExport: {
- list: () => string;
- };
- export: {
- page: () => string;
- };
- importConfiguration: {
- upload: () => string;
- validate: () => string;
- summary: () => string;
- };
welcome: {
root: () => string;
createProject: () => string;
@@ -112,6 +84,10 @@ export type RouteConfig = OrganizationUnitRoutePaths &
ConnectionRoutePaths &
ResourceServerRoutePaths &
TranslationRoutePaths &
+ VerifiableCredentialRoutePaths &
+ ImportExportRoutePaths &
+ DesignRoutePaths &
+ GroupRoutePaths &
ConsoleRoutePaths;
/**
diff --git a/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx b/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx
index 70245cfe89..ba0f861788 100644
--- a/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx
+++ b/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx
@@ -1,6 +1,12 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
+import {
+ LayoutPresetThumbnail,
+ LayoutThumbnail,
+ ThemeThumbnail,
+ type LayoutPresetVariant,
+} from '@thunderid/configure-design';
import {useConfig} from '@thunderid/contexts';
import {
useGetThemes,
@@ -33,11 +39,6 @@ import {Palette, ExternalLink, Code, Lightbulb, LayoutTemplate} from '@wso2/oxyg
import type {JSX, ChangeEvent} from 'react';
import {useState, useEffect} from 'react';
import {useTranslation} from 'react-i18next';
-import LayoutPresetThumbnail, {
- type LayoutPresetVariant,
-} from '../../../design/components/layouts/LayoutPresetThumbnail';
-import LayoutThumbnail from '../../../design/components/layouts/LayoutThumbnail';
-import ThemeThumbnail from '../../../design/components/themes/ThemeThumbnail';
import useApplicationCreateContext from '../../hooks/useApplicationCreateContext';
import {ApplicationCreateFlowSignInApproach, OrganizationUnitDefaultItem} from '../../models/application-create-flow';
diff --git a/frontend/apps/console/src/features/applications/components/create-application/ConfigureRedirectUris.tsx b/frontend/apps/console/src/features/applications/components/create-application/ConfigureRedirectUris.tsx
index e795d78999..43b72e1944 100644
--- a/frontend/apps/console/src/features/applications/components/create-application/ConfigureRedirectUris.tsx
+++ b/frontend/apps/console/src/features/applications/components/create-application/ConfigureRedirectUris.tsx
@@ -1,6 +1,7 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
+import {isValidOrigin} from '@thunderid/configure-settings';
import {
Alert,
Box,
@@ -19,7 +20,6 @@ import type {JSX} from 'react';
import {useEffect, useState} from 'react';
import {useTranslation} from 'react-i18next';
import DevServerLogo from './DevServerLogo';
-import {isValidOrigin} from '../../../settings/utils/origin';
import useApplicationCreate from '../../contexts/ApplicationCreate/useApplicationCreate';
/** Pure URI-format check, permissive of path/host wildcards (the backend enforces wildcard rules). */
diff --git a/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/IntegrationGuides.tsx b/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/IntegrationGuides.tsx
index d224bb096d..f8f1ca5316 100644
--- a/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/IntegrationGuides.tsx
+++ b/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/IntegrationGuides.tsx
@@ -19,6 +19,7 @@ import {
VueIcon,
} from '@thunderid/components';
import type {Application, OAuth2Config} from '@thunderid/configure-applications';
+import {GatePreview} from '@thunderid/configure-design';
import {useConfig} from '@thunderid/contexts';
import {DefaultTheme, type Theme, useGetTheme} from '@thunderid/design';
import {useLogger} from '@thunderid/logger/react';
@@ -26,7 +27,6 @@ import {Box, Button, Chip, Link, Paper, Stack, Typography, useColorScheme} from
import {ArrowRight, ArrowUpRight, Check, Copy, Sparkles} from '@wso2/oxygen-ui-icons-react';
import {useState, type JSX, type ReactNode} from 'react';
import {Trans, useTranslation} from 'react-i18next';
-import GatePreview from '../../../../../components/GatePreview/GatePreview';
import useGetFlowById from '../../../../flows/api/useGetFlowById';
import {getIntegrationGuideForTemplate} from '../../../utils/getIntegrationGuidesForTemplate';
import getPlaygroundsForTemplate from '../../../utils/getPlaygroundsForTemplate';
diff --git a/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/IntegrationGuides.test.tsx b/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/IntegrationGuides.test.tsx
index fc011de1ee..2669b1ace5 100644
--- a/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/IntegrationGuides.test.tsx
+++ b/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/IntegrationGuides.test.tsx
@@ -47,8 +47,9 @@ vi.mock('../../../../../flows/api/useGetFlowById', () => ({
default: () => ({data: undefined, isLoading: false}),
}));
-vi.mock('../../../../../../components/GatePreview/GatePreview', () => ({
- default: () => ,
+vi.mock('@thunderid/configure-design', async (importOriginal) => ({
+ ...(await importOriginal()),
+ GatePreview: () => ,
}));
const mockWriteText = vi.fn();
diff --git a/frontend/apps/console/src/features/applications/pages/ApplicationCreatePage.tsx b/frontend/apps/console/src/features/applications/pages/ApplicationCreatePage.tsx
index b4b795b373..6bdc643abb 100644
--- a/frontend/apps/console/src/features/applications/pages/ApplicationCreatePage.tsx
+++ b/frontend/apps/console/src/features/applications/pages/ApplicationCreatePage.tsx
@@ -5,11 +5,13 @@ import {FullScreenCreationWizardLayout} from '@thunderid/components';
import {OAuth2GrantTypes, TokenEndpointAuthMethods, useGetApplications} from '@thunderid/configure-applications';
import type {Application, ApplicationType, OAuth2Config} from '@thunderid/configure-applications';
import {AuthenticatorTypes, IdentityProviderTypes, useIdentityProviders} from '@thunderid/configure-connections';
+import {GatePreview, VIEWPORT_WIDTHS, VIEWPORT_HEIGHTS} from '@thunderid/configure-design';
import {
OrganizationUnitPickerScreen,
useGetOrganizationUnit,
useHasMultipleOUs,
} from '@thunderid/configure-organization-units';
+import {useGetCorsConfig, useUpdateCorsConfig} from '@thunderid/configure-settings';
import {useGetUserTypes} from '@thunderid/configure-user-types';
import {DefaultTheme, useGetTheme, type Theme} from '@thunderid/design';
import {useTemplateLiteralResolver} from '@thunderid/hooks';
@@ -29,8 +31,6 @@ import type {BasicFlowDefinition} from '../../flows/models/responses';
import {resolveApplicationMeta, resolveTemplatesDeep} from '../../flows/utils/gatePreviewTransforms';
import generateFlowGraph from '../../flows/utils/generateFlowGraph';
import getFlowPromptComponentsSequence from '../../flows/utils/getFlowPromptComponentsSequence';
-import useGetCorsConfig from '../../settings/api/useGetCorsConfig';
-import useUpdateCorsConfig from '../../settings/api/useUpdateCorsConfig';
import useCreateApplication from '../api/useCreateApplication';
import ConfigureSecuritySettings from '../components/create-application/configure-security-settings/ConfigureSecuritySettings';
import ConfigureApplicationDetails from '../components/create-application/ConfigureApplicationDetails';
@@ -55,8 +55,6 @@ import isRedirectCapableTemplate from '../utils/isRedirectCapableTemplate';
import mergeCorsOrigins from '../utils/mergeCorsOrigins';
import resolveApplicationType from '../utils/resolveApplicationType';
import resolveCreationFlow from '../utils/resolveCreationFlow';
-import GatePreview from '@/components/GatePreview/GatePreview';
-import {VIEWPORT_WIDTHS, VIEWPORT_HEIGHTS} from '@/features/design/components/viewportConstants';
export default function ApplicationCreatePage(): JSX.Element {
const {t} = useTranslation();
diff --git a/frontend/apps/console/src/features/applications/pages/__tests__/ApplicationCreatePage.test.tsx b/frontend/apps/console/src/features/applications/pages/__tests__/ApplicationCreatePage.test.tsx
index f9fcbd904d..4977d88876 100644
--- a/frontend/apps/console/src/features/applications/pages/__tests__/ApplicationCreatePage.test.tsx
+++ b/frontend/apps/console/src/features/applications/pages/__tests__/ApplicationCreatePage.test.tsx
@@ -350,8 +350,15 @@ vi.mock('../../components/create-application/ConfigureDetails', () => ({
default: vi.fn(DefaultConfigureDetailsImpl),
}));
-vi.mock('../../../../components/GatePreview/GatePreview', () => ({
- default: ({showToolbar, viewport}: {showToolbar?: boolean; viewport?: {width: string; height: string}}) => (
+vi.mock('@thunderid/configure-design', async (importOriginal) => ({
+ ...(await importOriginal()),
+ GatePreview: ({
+ showToolbar = undefined,
+ viewport = undefined,
+ }: {
+ showToolbar?: boolean;
+ viewport?: {width: string; height: string};
+ }) => (
),
}));
diff --git a/frontend/apps/console/src/features/applications/utils/mergeCorsOrigins.ts b/frontend/apps/console/src/features/applications/utils/mergeCorsOrigins.ts
index 34b94693ee..c704d8e28a 100644
--- a/frontend/apps/console/src/features/applications/utils/mergeCorsOrigins.ts
+++ b/frontend/apps/console/src/features/applications/utils/mergeCorsOrigins.ts
@@ -1,9 +1,13 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
-import type {AllowedOrigin, CorsValue} from '../../settings/models/responses';
-import {isValidOrigin, normalizeOrigin} from '../../settings/utils/origin';
-import originValueText from '../../settings/utils/originValueText';
+import {
+ isValidOrigin,
+ normalizeOrigin,
+ originValueText,
+ type AllowedOrigin,
+ type CorsValue,
+} from '@thunderid/configure-settings';
/**
* Builds the CORS PUT payload for adding the Configuration step's origins to the deployment's
diff --git a/frontend/apps/console/src/features/design/components/themes/__tests__/TypographyBuilderContent.test.tsx b/frontend/apps/console/src/features/design/components/themes/__tests__/TypographyBuilderContent.test.tsx
deleted file mode 100644
index 409a5c99f1..0000000000
--- a/frontend/apps/console/src/features/design/components/themes/__tests__/TypographyBuilderContent.test.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2026 The ThunderID Authors
-// SPDX-License-Identifier: Apache-2.0
-
-import {render, screen, fireEvent, cleanup} from '@testing-library/react';
-import type {Theme} from '@thunderid/design';
-import {OxygenUIThemeProvider} from '@wso2/oxygen-ui';
-import {useState} from 'react';
-import {describe, it, expect, afterEach, vi} from 'vitest';
-import TypographyBuilderContent from '../TypographyBuilderContent';
-
-vi.mock('react-i18next', () => ({
- useTranslation: () => ({t: (_key: string, fallback?: string) => fallback ?? _key}),
-}));
-
-// useFontStylesheetLink needs a ConfigProvider, which this test tree doesn't otherwise set up.
-vi.mock('@thunderid/contexts', () => ({
- useConfig: () => ({config: {brand: {product_name: 'ThunderID'}}}),
-}));
-
-afterEach(() => {
- cleanup();
-});
-
-interface MutableTypography {
- fontFamily?: string;
- font?: {importURL?: string};
-}
-
-function makeDraft(typography: MutableTypography = {fontFamily: ''}): Theme {
- return {typography} as unknown as Theme;
-}
-
-function typographyOf(draft: Theme): MutableTypography {
- return draft.typography as unknown as MutableTypography;
-}
-
-function renderEditor(draft: Theme) {
- const onUpdate = vi.fn((updater: (d: Theme) => void) => updater(draft));
- render(
-
-
- ,
- );
- return onUpdate;
-}
-
-/** Mirrors the real ThemeConfigPanel.updateDraft: clones the draft and re-renders with the new
- * object, needed for tests that exercise multiple sequential edits (renderEditor's in-place
- * mutation doesn't give a fresh object reference each time). */
-function StatefulEditor({initialDraft, onDraftChange}: {initialDraft: Theme; onDraftChange: (d: Theme) => void}) {
- const [draft, setDraft] = useState(initialDraft);
- const onUpdate = (updater: (d: Theme) => void): void => {
- const next = JSON.parse(JSON.stringify(draft)) as Theme;
- updater(next);
- setDraft(next);
- onDraftChange(next);
- };
- return ;
-}
-
-function renderStatefulEditor(initialDraft: Theme) {
- let currentDraft = initialDraft;
- const utils = render(
-
- {
- currentDraft = d;
- }}
- />
- ,
- );
- return {...utils, getDraft: () => currentDraft};
-}
-
-describe('TypographyBuilderContent font modes', () => {
- it('defaults to the web-safe font picker when no import URL is set', () => {
- renderEditor(makeDraft());
- expect(screen.getByRole('combobox')).toBeTruthy();
- expect(screen.queryByLabelText('Font Import URL')).toBeNull();
- });
-
- it('shows the bare name selected when the theme stores the default font stack', () => {
- renderEditor(makeDraft({fontFamily: "'Inter Variable', sans-serif"}));
- expect(screen.getByRole('combobox').value).toBe('Inter Variable');
- });
-
- it('starts in import mode showing the configured import URL', () => {
- const draft = makeDraft({
- fontFamily: 'Poppins',
- font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'},
- });
- renderEditor(draft);
- const urlField = screen.getByLabelText('Font Import URL');
- expect(urlField.value).toBe('https://fonts.googleapis.com/css2?family=Poppins');
- });
-
- it('reveals the import fields when switching to import mode', () => {
- renderEditor(makeDraft());
- fireEvent.click(screen.getByRole('button', {name: 'Use a Custom Font'}));
- expect(screen.getByLabelText('Font Import URL')).toBeTruthy();
- expect(screen.getAllByLabelText('Font Family').length).toBeGreaterThan(0);
- });
-
- it('clears the inherited family when switching to import so the field shows its placeholder', () => {
- const draft = makeDraft({fontFamily: "'Inter Variable', sans-serif"});
- renderEditor(draft);
- fireEvent.click(screen.getByRole('button', {name: 'Use a Custom Font'}));
- expect(typographyOf(draft).fontFamily).toBe('');
- expect(screen.getByLabelText('Font Family').value).toBe('');
- });
-
- it('typing an import URL writes typography.font.importURL', () => {
- const draft = makeDraft();
- renderEditor(draft);
- fireEvent.click(screen.getByRole('button', {name: 'Use a Custom Font'}));
- fireEvent.change(screen.getByLabelText('Font Import URL'), {
- target: {value: 'https://fonts.bunny.net/css?family=inter'},
- });
- expect(typographyOf(draft).font?.importURL).toBe('https://fonts.bunny.net/css?family=inter');
- });
-
- it('clears the applied import URL when switching to a web-safe font', () => {
- const draft = makeDraft({
- fontFamily: 'Poppins',
- font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'},
- });
- renderEditor(draft);
- fireEvent.click(screen.getByRole('button', {name: 'Use a web-safe font'}));
- expect(typographyOf(draft).font).toBeUndefined();
- });
-
- it('remembers the import URL and family when toggling to web-safe and back', () => {
- const {getDraft} = renderStatefulEditor(
- makeDraft({fontFamily: 'Poppins', font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'}}),
- );
- // Toggling to web-safe drops the import from the applied theme, but remembers it.
- fireEvent.click(screen.getByRole('button', {name: 'Use a web-safe font'}));
- expect(typographyOf(getDraft()).font).toBeUndefined();
- // Toggling back to import restores the remembered URL and family.
- fireEvent.click(screen.getByRole('button', {name: 'Use a Custom Font'}));
- expect(typographyOf(getDraft()).font?.importURL).toBe('https://fonts.googleapis.com/css2?family=Poppins');
- expect(typographyOf(getDraft()).fontFamily).toBe('Poppins');
- });
-
- it('promotes the toggle to import mode when a draft change restores an import URL (e.g. Revert)', () => {
- const webSafeDraft = makeDraft({fontFamily: 'Arial'});
- const {rerender} = render(
-
-
- ,
- );
- expect(screen.queryByLabelText('Font Import URL')).toBeNull();
-
- // A Revert replaces the draft in place with one that carries an import URL again.
- const revertedDraft = makeDraft({
- fontFamily: 'Poppins',
- font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'},
- });
- rerender(
-
-
- ,
- );
- const urlField = screen.getByLabelText('Font Import URL');
- expect(urlField.value).toBe('https://fonts.googleapis.com/css2?family=Poppins');
- });
-
- it('demotes the toggle back to web-safe when a draft change restores a plain family with no import URL (e.g. Revert)', () => {
- const importDraft = makeDraft({
- fontFamily: 'Poppins',
- font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'},
- });
- const {rerender} = render(
-
-
- ,
- );
- expect(screen.getByLabelText('Font Import URL')).toBeTruthy();
-
- // A Revert replaces the draft in place with the original web-safe theme (no import URL).
- const revertedDraft = makeDraft({fontFamily: 'Arial'});
- rerender(
-
-
- ,
- );
- expect(screen.queryByLabelText('Font Import URL')).toBeNull();
- expect(screen.getByRole('combobox')).toBeTruthy();
- });
-
- it('demotes the toggle back to web-safe when a draft change restores an empty family with no import URL (e.g. Revert)', () => {
- const importDraft = makeDraft({
- fontFamily: 'Poppins',
- font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'},
- });
- const {rerender} = render(
-
-
- ,
- );
- expect(screen.getByLabelText('Font Import URL')).toBeTruthy();
-
- // A Revert can restore a draft with neither a family nor an import URL set.
- const revertedDraft = makeDraft();
- rerender(
-
-
- ,
- );
- expect(screen.queryByLabelText('Font Import URL')).toBeNull();
- expect(screen.getByRole('combobox')).toBeTruthy();
- });
-
- it('falls the live preview back to the product default (not a generic serif) for a partial font name', () => {
- renderStatefulEditor(makeDraft());
- fireEvent.click(screen.getByRole('button', {name: 'Use a Custom Font'}));
- fireEvent.change(screen.getByLabelText('Font Family'), {target: {value: 'P'}});
-
- // sx compiles to a generated CSS class via emotion, so assert against the injected stylesheet.
- screen.getByText('The quick brown fox jumps over the lazy dog.');
- const styles = Array.from(document.querySelectorAll('style'))
- .map((style) => style.textContent)
- .join('\n');
- expect(styles).toContain('font-family:P,');
- expect(styles).toMatch(/Inter Variable/);
- });
-
- it('loads the imported font stylesheet into the main document, not just the GatePreview iframe', () => {
- renderEditor(
- makeDraft({fontFamily: 'Poppins', font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'}}),
- );
- const link = document.querySelector(
- 'link[rel="stylesheet"][href="https://fonts.googleapis.com/css2?family=Poppins"]',
- );
- expect(link).not.toBeNull();
- });
-
- it('clearing the import URL also clears the family, reverting the whole selection to default', () => {
- const draft = makeDraft({
- fontFamily: 'Poppins',
- font: {importURL: 'https://fonts.googleapis.com/css2?family=Poppins'},
- });
- renderEditor(draft);
- fireEvent.change(screen.getByLabelText('Font Import URL'), {target: {value: ''}});
- expect(typographyOf(draft).fontFamily).toBe('');
- expect(typographyOf(draft).font).toBeUndefined();
- });
-});
diff --git a/frontend/apps/console/src/features/flows/components/resource-property-panel/PresentationDefinitionSelect.tsx b/frontend/apps/console/src/features/flows/components/resource-property-panel/PresentationDefinitionSelect.tsx
index d43b8cac75..592de93f22 100644
--- a/frontend/apps/console/src/features/flows/components/resource-property-panel/PresentationDefinitionSelect.tsx
+++ b/frontend/apps/console/src/features/flows/components/resource-property-panel/PresentationDefinitionSelect.tsx
@@ -1,7 +1,7 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
-import {useGetVerifiablePresentations} from '@thunderid/configure-verifiable-presentations';
+import {useGetVerifiablePresentations} from '@thunderid/configure-verifiable-credentials';
import {FormControl, FormLabel, MenuItem, TextField} from '@wso2/oxygen-ui';
import type {ChangeEvent, ReactElement} from 'react';
import {useTranslation} from 'react-i18next';
diff --git a/frontend/apps/console/src/features/flows/components/visual-flow/SimulationStepPreview.tsx b/frontend/apps/console/src/features/flows/components/visual-flow/SimulationStepPreview.tsx
index 95bfb92583..8db39c8aa3 100644
--- a/frontend/apps/console/src/features/flows/components/visual-flow/SimulationStepPreview.tsx
+++ b/frontend/apps/console/src/features/flows/components/visual-flow/SimulationStepPreview.tsx
@@ -4,6 +4,7 @@
import {BuilderStaticPanel} from '@thunderid/components';
import {useGetApplication, useGetApplications} from '@thunderid/configure-applications';
import type {BasicApplication} from '@thunderid/configure-applications';
+import {GatePreview} from '@thunderid/configure-design';
import {DefaultTheme, DesignResolveType, useGetDesignResolve, type Theme} from '@thunderid/design';
import {useTemplateLiteralResolver} from '@thunderid/hooks';
import type {EmbeddedFlowComponent} from '@thunderid/react';
@@ -58,7 +59,6 @@ import {
type PreviewComponent,
} from '../../utils/gatePreviewTransforms';
import type {SimulationOption} from '../../utils/getSimulationOptions';
-import GatePreview from '@/components/GatePreview/GatePreview';
/**
* Props interface of {@link SimulationStepPreview}
diff --git a/frontend/apps/console/src/features/flows/components/visual-flow/__tests__/SimulationStepPreview.test.tsx b/frontend/apps/console/src/features/flows/components/visual-flow/__tests__/SimulationStepPreview.test.tsx
index 369aae2683..9af728f0b0 100644
--- a/frontend/apps/console/src/features/flows/components/visual-flow/__tests__/SimulationStepPreview.test.tsx
+++ b/frontend/apps/console/src/features/flows/components/visual-flow/__tests__/SimulationStepPreview.test.tsx
@@ -69,28 +69,17 @@ vi.mock('@thunderid/design', () => ({
useGetDesignResolve: ({id}: {id: string}) => (id ? mockDesignResolve : {data: undefined, isError: false}),
}));
-vi.mock('@thunderid/configure-applications', async (importOriginal) => ({
- ...(await importOriginal()),
- useGetApplications: () => ({
- data: {applications: mockApplications},
- }),
- useGetApplication: (applicationId: string) => ({
- data: applicationId
- ? {id: applicationId, name: 'My App', themeId: 'theme-1', logoUrl: 'https://myapp.example/logo.png'}
- : undefined,
- }),
-}));
-
-vi.mock('@/components/GatePreview/GatePreview', () => ({
- default: ({
- mock,
- onSubmit,
- onComponentHover,
- toolbarStart,
- toolbarEnd,
- colorScheme,
- theme,
- additionalData,
+vi.mock('@thunderid/configure-design', async (importOriginal) => ({
+ ...(await importOriginal()),
+ GatePreview: ({
+ mock = undefined,
+ onSubmit = undefined,
+ onComponentHover = undefined,
+ toolbarStart = undefined,
+ toolbarEnd = undefined,
+ colorScheme = undefined,
+ theme = undefined,
+ additionalData = undefined,
}: {
mock?: {id?: string}[];
onSubmit?: (component: {id?: string; components?: {id?: string}[]}) => void;
@@ -130,6 +119,18 @@ vi.mock('@/components/GatePreview/GatePreview', () => ({
),
}));
+vi.mock('@thunderid/configure-applications', async (importOriginal) => ({
+ ...(await importOriginal()),
+ useGetApplications: () => ({
+ data: {applications: mockApplications},
+ }),
+ useGetApplication: (applicationId: string) => ({
+ data: applicationId
+ ? {id: applicationId, name: 'My App', themeId: 'theme-1', logoUrl: 'https://myapp.example/logo.png'}
+ : undefined,
+ }),
+}));
+
const createSimulation = (overrides: Partial = {}): FlowSimulation => ({
isSimulating: true,
startAt: vi.fn(),
diff --git a/frontend/apps/console/src/features/flows/utils/resolveUpstreamOtpLength.ts b/frontend/apps/console/src/features/flows/utils/resolveUpstreamOtpLength.ts
index 03ca0cbd6c..9608f2a73a 100644
--- a/frontend/apps/console/src/features/flows/utils/resolveUpstreamOtpLength.ts
+++ b/frontend/apps/console/src/features/flows/utils/resolveUpstreamOtpLength.ts
@@ -38,7 +38,7 @@ const isOtpGenerateNode = (node: Node): boolean => {
* @returns The configured length, or undefined.
*/
const readOtpLength = (node: Node): number | undefined => {
- const length = Number((node.data as StepData | undefined)?.properties?.['otpLength']);
+ const length = Number((node.data as StepData | undefined)?.properties?.otpLength);
if (!Number.isInteger(length) || length < MIN_OTP_LENGTH || length > MAX_OTP_LENGTH) {
return undefined;
diff --git a/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/AddAssignmentDialog.tsx b/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/AddAssignmentDialog.tsx
index 428acb31f5..9354f70913 100644
--- a/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/AddAssignmentDialog.tsx
+++ b/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/AddAssignmentDialog.tsx
@@ -3,6 +3,8 @@
import {useGetApplications} from '@thunderid/configure-applications';
import type {BasicApplication} from '@thunderid/configure-applications';
+import {useGetGroups} from '@thunderid/configure-groups';
+import type {GroupBasic} from '@thunderid/configure-groups';
import {useGetUsers} from '@thunderid/configure-users';
import {useDataGridLocaleText} from '@thunderid/hooks';
import type {User} from '@thunderid/types';
@@ -28,8 +30,6 @@ import {useState, useMemo, useCallback, type JSX, type SyntheticEvent} from 'rea
import {useTranslation} from 'react-i18next';
import useGetAgents from '../../../../agents/api/useGetAgents';
import type {BasicAgent} from '../../../../agents/models/agent';
-import useGetGroups from '../../../../groups/api/useGetGroups';
-import type {GroupBasic} from '../../../../groups/models/group';
import useGetRoleAssignments from '../../../api/useGetRoleAssignments';
import type {RoleAssignment} from '../../../models/role';
diff --git a/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/__tests__/AddAssignmentDialog.test.tsx b/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/__tests__/AddAssignmentDialog.test.tsx
index 44021ca0fa..ee9c4442f4 100644
--- a/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/__tests__/AddAssignmentDialog.test.tsx
+++ b/frontend/apps/console/src/features/roles/components/edit-role/assignments-settings/__tests__/AddAssignmentDialog.test.tsx
@@ -7,7 +7,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
import AddAssignmentDialog from '../AddAssignmentDialog';
vi.mock('@thunderid/configure-users');
-vi.mock('../../../../../groups/api/useGetGroups');
+vi.mock('@thunderid/configure-groups');
vi.mock('@thunderid/configure-applications', async (importOriginal) => ({
...(await importOriginal()),
useGetApplications: vi.fn(),
@@ -93,7 +93,7 @@ vi.mock('@wso2/oxygen-ui', async (importOriginal) => {
});
const {useGetUsers} = await import('@thunderid/configure-users');
-const {default: useGetGroups} = await import('../../../../../groups/api/useGetGroups');
+const {useGetGroups} = await import('@thunderid/configure-groups');
const {useGetApplications} = await import('@thunderid/configure-applications');
const {default: useGetRoleAssignments} = await import('../../../../api/useGetRoleAssignments');
const {useDataGridLocaleText} = await import('@thunderid/hooks');
diff --git a/frontend/apps/console/src/features/welcome/components/WayfinderConfigImport.tsx b/frontend/apps/console/src/features/welcome/components/WayfinderConfigImport.tsx
index ce06a52699..f38d4af0d6 100644
--- a/frontend/apps/console/src/features/welcome/components/WayfinderConfigImport.tsx
+++ b/frontend/apps/console/src/features/welcome/components/WayfinderConfigImport.tsx
@@ -1,14 +1,13 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
+import {useImportConfiguration, type ImportResponse} from '@thunderid/configure-import-export';
import {useConfig} from '@thunderid/contexts';
import {Box, Button, CircularProgress, Stack, Typography} from '@wso2/oxygen-ui';
import {CheckCircle, Database, RefreshCw, XCircle} from '@wso2/oxygen-ui-icons-react';
import type {JSX} from 'react';
import {useState} from 'react';
import {useTranslation} from 'react-i18next';
-import useImportConfiguration from '../../import-export/api/useImportConfiguration';
-import type {ImportResponse} from '../../import-export/models/import-configuration';
import {useGetSampleBundle} from '../api/useGetSampleBundles';
import getWayfinderConfiguredStorageKey from '../utils/getWayfinderConfiguredStorageKey';
diff --git a/frontend/apps/console/src/features/welcome/components/__tests__/WayfinderConfigImport.test.tsx b/frontend/apps/console/src/features/welcome/components/__tests__/WayfinderConfigImport.test.tsx
index 226cf6ccfa..bf1439729f 100644
--- a/frontend/apps/console/src/features/welcome/components/__tests__/WayfinderConfigImport.test.tsx
+++ b/frontend/apps/console/src/features/welcome/components/__tests__/WayfinderConfigImport.test.tsx
@@ -27,8 +27,9 @@ const {mockUseImportConfiguration, mockMutateAsync, mockUseGetSampleBundle} = vi
mockUseGetSampleBundle: vi.fn(),
}));
-vi.mock('../../../import-export/api/useImportConfiguration', () => ({
- default: mockUseImportConfiguration,
+vi.mock('@thunderid/configure-import-export', async (importOriginal) => ({
+ ...(await importOriginal()),
+ useImportConfiguration: mockUseImportConfiguration,
}));
vi.mock('../../api/useGetSampleBundles', () => ({
diff --git a/frontend/packages/configure-agent-types/src/components/edit-agent-type/schema-settings/__tests__/EditSchemaSettings.test.tsx b/frontend/packages/configure-agent-types/src/components/edit-agent-type/schema-settings/__tests__/EditSchemaSettings.test.tsx
index 669b3e8345..5a65ae544a 100644
--- a/frontend/packages/configure-agent-types/src/components/edit-agent-type/schema-settings/__tests__/EditSchemaSettings.test.tsx
+++ b/frontend/packages/configure-agent-types/src/components/edit-agent-type/schema-settings/__tests__/EditSchemaSettings.test.tsx
@@ -312,7 +312,7 @@ describe('EditSchemaSettings (agent-type)', () => {
render();
- const nameInput = screen.getByPlaceholderText(/e\.g\., email, age, address/i);
+ const nameInput = screen.getByPlaceholderText(/e\.g\., model, environment, team/i);
await user.type(nameInput, 'X');
expect(mockOnPropertiesChange).toHaveBeenCalledWith(
@@ -330,7 +330,7 @@ describe('EditSchemaSettings (agent-type)', () => {
render();
- const requiredCheckbox = screen.getByRole('checkbox', {name: /this field must be provided/i});
+ const requiredCheckbox = screen.getByRole('checkbox', {name: /agents must provide a value for this field/i});
await user.click(requiredCheckbox);
expect(mockOnPropertiesChange).toHaveBeenCalledWith(
@@ -348,7 +348,7 @@ describe('EditSchemaSettings (agent-type)', () => {
render();
- const uniqueCheckbox = screen.getByRole('checkbox', {name: /each user must have a distinct value/i});
+ const uniqueCheckbox = screen.getByRole('checkbox', {name: /each agent must have a distinct value/i});
await user.click(uniqueCheckbox);
expect(mockOnPropertiesChange).toHaveBeenCalledWith(
diff --git a/frontend/packages/configure-verifiable-presentations/.editorconfig b/frontend/packages/configure-design/.editorconfig
similarity index 100%
rename from frontend/packages/configure-verifiable-presentations/.editorconfig
rename to frontend/packages/configure-design/.editorconfig
diff --git a/frontend/packages/configure-verifiable-presentations/.gitignore b/frontend/packages/configure-design/.gitignore
similarity index 100%
rename from frontend/packages/configure-verifiable-presentations/.gitignore
rename to frontend/packages/configure-design/.gitignore
diff --git a/frontend/packages/configure-verifiable-presentations/.prettierignore b/frontend/packages/configure-design/.prettierignore
similarity index 100%
rename from frontend/packages/configure-verifiable-presentations/.prettierignore
rename to frontend/packages/configure-design/.prettierignore
diff --git a/frontend/packages/configure-verifiable-presentations/eslint.config.js b/frontend/packages/configure-design/eslint.config.js
similarity index 100%
rename from frontend/packages/configure-verifiable-presentations/eslint.config.js
rename to frontend/packages/configure-design/eslint.config.js
diff --git a/frontend/packages/configure-design/package.json b/frontend/packages/configure-design/package.json
new file mode 100644
index 0000000000..925a033081
--- /dev/null
+++ b/frontend/packages/configure-design/package.json
@@ -0,0 +1,117 @@
+{
+ "private": true,
+ "name": "@thunderid/configure-design",
+ "version": "0.0.0",
+ "description": "Design (theme/layout builder) feature for ⚡️ ThunderID applications",
+ "keywords": [
+ "thunderid",
+ "configure",
+ "design",
+ "react",
+ "feature"
+ ],
+ "homepage": "https://github.com/thunder-id/thunderid/tree/main/frontend/packages/configure-design#readme",
+ "bugs": {
+ "url": "https://github.com/thunder-id/thunderid/issues"
+ },
+ "author": "The ThunderID Authors",
+ "license": "Apache-2.0",
+ "type": "module",
+ "main": "./dist/cjs/index.cjs",
+ "module": "./dist/index.js",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js",
+ "require": "./dist/cjs/index.cjs"
+ }
+ },
+ "types": "./dist/index.d.ts",
+ "sideEffects": false,
+ "files": [
+ "dist",
+ "README.md",
+ "LICENSE"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/thunder-id/thunderid",
+ "directory": "frontend/packages/configure-design"
+ },
+ "scripts": {
+ "build": "pnpm clean:dist && rolldown -c rolldown.config.js && tsc -p tsconfig.lib.json --emitDeclarationOnly --outDir dist",
+ "clean": "pnpm clean:node_modules && pnpm clean:dist",
+ "clean:dist": "rimraf dist",
+ "clean:node_modules": "rimraf node_modules",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test": "vitest run --passWithNoTests",
+ "test:coverage": "vitest run --coverage --passWithNoTests",
+ "test:watch": "vitest",
+ "typecheck": "tsc -p tsconfig.lib.json"
+ },
+ "devDependencies": {
+ "@emotion/cache": "^11.14.0",
+ "@emotion/react": "^11.14.0",
+ "@monaco-editor/react": "catalog:",
+ "@playwright/test": "catalog:",
+ "@tanstack/react-query": "catalog:",
+ "@testing-library/jest-dom": "catalog:",
+ "@testing-library/react": "catalog:",
+ "@testing-library/user-event": "catalog:",
+ "@thunderid/components": "workspace:^",
+ "@thunderid/configure-connections": "workspace:^",
+ "@thunderid/contexts": "workspace:^",
+ "@thunderid/design": "workspace:^",
+ "@thunderid/eslint-plugin": "workspace:^",
+ "@thunderid/hooks": "workspace:^",
+ "@thunderid/logger": "workspace:^",
+ "@thunderid/prettier-config": "workspace:^",
+ "@thunderid/react": "catalog:",
+ "@thunderid/test-utils": "workspace:^",
+ "@thunderid/utils": "workspace:^",
+ "@types/node": "catalog:",
+ "@types/react": "catalog:",
+ "@types/react-dom": "catalog:",
+ "@vitest/browser-playwright": "catalog:",
+ "@vitest/coverage-istanbul": "catalog:",
+ "@wso2/oxygen-ui": "catalog:",
+ "@wso2/oxygen-ui-icons-react": "catalog:",
+ "eslint": "catalog:",
+ "prettier": "catalog:",
+ "react": "catalog:",
+ "react-dom": "catalog:",
+ "react-i18next": "catalog:",
+ "react-router": "catalog:",
+ "rimraf": "catalog:",
+ "rolldown": "catalog:",
+ "tslib": "catalog:",
+ "typescript": "catalog:",
+ "vite": "catalog:",
+ "vitest": "catalog:"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {},
+ "peerDependencies": {
+ "@emotion/cache": "^11.14.0",
+ "@emotion/react": "^11.14.0",
+ "@monaco-editor/react": "catalog:",
+ "@tanstack/react-query": "catalog:",
+ "@thunderid/components": "workspace:^",
+ "@thunderid/configure-connections": "workspace:^",
+ "@thunderid/contexts": "workspace:^",
+ "@thunderid/design": "workspace:^",
+ "@thunderid/hooks": "workspace:^",
+ "@thunderid/logger": "workspace:^",
+ "@thunderid/react": "catalog:",
+ "@thunderid/utils": "workspace:^",
+ "@wso2/oxygen-ui": "catalog:",
+ "@wso2/oxygen-ui-icons-react": "catalog:",
+ "react": "catalog:",
+ "react-dom": "catalog:",
+ "react-i18next": "catalog:",
+ "react-router": "catalog:"
+ }
+}
diff --git a/frontend/packages/configure-verifiable-presentations/prettier.config.js b/frontend/packages/configure-design/prettier.config.js
similarity index 100%
rename from frontend/packages/configure-verifiable-presentations/prettier.config.js
rename to frontend/packages/configure-design/prettier.config.js
diff --git a/frontend/packages/configure-verifiable-presentations/rolldown.config.js b/frontend/packages/configure-design/rolldown.config.js
similarity index 100%
rename from frontend/packages/configure-verifiable-presentations/rolldown.config.js
rename to frontend/packages/configure-design/rolldown.config.js
diff --git a/frontend/apps/console/src/components/GatePreview/ColorSchemeSync.tsx b/frontend/packages/configure-design/src/GatePreview/ColorSchemeSync.tsx
similarity index 100%
rename from frontend/apps/console/src/components/GatePreview/ColorSchemeSync.tsx
rename to frontend/packages/configure-design/src/GatePreview/ColorSchemeSync.tsx
diff --git a/frontend/apps/console/src/components/GatePreview/GatePreview.tsx b/frontend/packages/configure-design/src/GatePreview/GatePreview.tsx
similarity index 98%
rename from frontend/apps/console/src/components/GatePreview/GatePreview.tsx
rename to frontend/packages/configure-design/src/GatePreview/GatePreview.tsx
index ef3ec6f906..99f67abe72 100644
--- a/frontend/apps/console/src/components/GatePreview/GatePreview.tsx
+++ b/frontend/packages/configure-design/src/GatePreview/GatePreview.tsx
@@ -9,9 +9,9 @@ import {createPortal} from 'react-dom';
import {useTranslation} from 'react-i18next';
import IframeContent from './IframeContent';
import buildPreviewMock from './mocks/buildPreviewMock';
-import PreviewToolbar from '../../features/design/components/PreviewToolbar';
-import {VIEWPORT_WIDTHS, VIEWPORT_HEIGHTS} from '../../features/design/components/viewportConstants';
-import ColorSchemeOptions from '../../features/design/constants/ColorSchemeOptions';
+import PreviewToolbar from '../components/PreviewToolbar';
+import {VIEWPORT_WIDTHS, VIEWPORT_HEIGHTS} from '../components/viewportConstants';
+import ColorSchemeOptions from '../constants/ColorSchemeOptions';
// ── Constants ────────────────────────────────────────────────────────────────
diff --git a/frontend/apps/console/src/components/GatePreview/IframeContent.tsx b/frontend/packages/configure-design/src/GatePreview/IframeContent.tsx
similarity index 98%
rename from frontend/apps/console/src/components/GatePreview/IframeContent.tsx
rename to frontend/packages/configure-design/src/GatePreview/IframeContent.tsx
index 93e1ff0665..f07904d9f1 100644
--- a/frontend/apps/console/src/components/GatePreview/IframeContent.tsx
+++ b/frontend/packages/configure-design/src/GatePreview/IframeContent.tsx
@@ -22,7 +22,7 @@ import {Box, ParticleBackground, useTheme} from '@wso2/oxygen-ui';
import {useEffect, useMemo, type JSX} from 'react';
import PreviewThemeProvider from './PreviewThemeProvider';
import {resolveAnchorActionRef, resolveHoverTarget} from './richTextClickResolution';
-import ElementInspector from '../../features/design/components/layouts/ElementInspector';
+import ElementInspector from '../components/layouts/ElementInspector';
/**
* MUI's extendTheme only accepts 'light' or 'dark' for defaultColorScheme.
@@ -30,9 +30,9 @@ import ElementInspector from '../../features/design/components/layouts/ElementIn
*/
function sanitizeThemeForMui(theme: Theme): Theme {
const raw = theme as unknown as Record;
- if (raw.defaultColorScheme === 'system') {
+ if (raw['defaultColorScheme'] === 'system') {
const copy = {...raw};
- delete copy.defaultColorScheme;
+ delete copy['defaultColorScheme'];
return copy as unknown as Theme;
}
return theme;
diff --git a/frontend/apps/console/src/components/GatePreview/PreviewThemeProvider.tsx b/frontend/packages/configure-design/src/GatePreview/PreviewThemeProvider.tsx
similarity index 100%
rename from frontend/apps/console/src/components/GatePreview/PreviewThemeProvider.tsx
rename to frontend/packages/configure-design/src/GatePreview/PreviewThemeProvider.tsx
diff --git a/frontend/apps/console/src/components/GatePreview/__tests__/GatePreview.test.tsx b/frontend/packages/configure-design/src/GatePreview/__tests__/GatePreview.test.tsx
similarity index 84%
rename from frontend/apps/console/src/components/GatePreview/__tests__/GatePreview.test.tsx
rename to frontend/packages/configure-design/src/GatePreview/__tests__/GatePreview.test.tsx
index c251e2d312..8738a00dc9 100644
--- a/frontend/apps/console/src/components/GatePreview/__tests__/GatePreview.test.tsx
+++ b/frontend/packages/configure-design/src/GatePreview/__tests__/GatePreview.test.tsx
@@ -8,18 +8,24 @@ import {OxygenUIThemeProvider} from '@wso2/oxygen-ui';
import {describe, it, expect, vi, afterEach} from 'vitest';
import GatePreview from '../GatePreview';
-// Mock shared-design to stub DesignProvider (which internally needs ConfigProvider)
-vi.mock('@thunderid/design', () => ({
- DesignProvider: ({children}: {children: React.ReactNode}) => children,
- useDesign: () => ({isDesignEnabled: false, theme: undefined}),
- FlowComponentRenderer: () => ,
- AuthPageLayout: ({children}: {children: React.ReactNode}) => {children}
,
- AuthCardLayout: ({children}: {children: React.ReactNode}) => {children}
,
- FontImporter: () => null,
- getFontImportURL: () => undefined,
- AcrylicOrangeTheme: {},
- getCspNonce: () => document.querySelector('meta[property="csp-nonce"]')?.getAttribute('content') ?? undefined,
-}));
+// Mock shared-design to stub DesignProvider (which internally needs ConfigProvider). Merges over
+// the real module (rather than fully replacing it) so other exports real code in this package's
+// module graph relies on (e.g. useGetLayouts, pulled in via DesignPage/LayoutBuilderPage) stay available.
+vi.mock('@thunderid/design', async (importOriginal) => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ DesignProvider: ({children}: {children: React.ReactNode}) => children,
+ useDesign: () => ({isDesignEnabled: false, theme: undefined}),
+ FlowComponentRenderer: () => ,
+ AuthPageLayout: ({children}: {children: React.ReactNode}) => {children}
,
+ AuthCardLayout: ({children}: {children: React.ReactNode}) => {children}
,
+ FontImporter: () => null,
+ getFontImportURL: () => undefined,
+ AcrylicOrangeTheme: {},
+ getCspNonce: () => document.querySelector('meta[property="csp-nonce"]')?.getAttribute('content') ?? undefined,
+ };
+});
vi.mock('@emotion/cache', async (importActual) => {
const actual = await importActual();
diff --git a/frontend/packages/configure-design/src/GatePreview/__tests__/IframeContent.test.tsx b/frontend/packages/configure-design/src/GatePreview/__tests__/IframeContent.test.tsx
new file mode 100644
index 0000000000..bf44ce50d4
--- /dev/null
+++ b/frontend/packages/configure-design/src/GatePreview/__tests__/IframeContent.test.tsx
@@ -0,0 +1,291 @@
+// Copyright 2026 The ThunderID Authors
+// SPDX-License-Identifier: Apache-2.0
+
+import {render, cleanup} from '@testing-library/react';
+import type {Stylesheet, Theme} from '@thunderid/design';
+import {EmbeddedFlowComponentType, EmbeddedFlowEventType, type EmbeddedFlowComponent} from '@thunderid/react';
+import {OxygenUIThemeProvider} from '@wso2/oxygen-ui';
+import {afterEach, describe, it, expect, vi} from 'vitest';
+import IframeContent from '../IframeContent';
+
+// Mock shared-design to stub DesignProvider (which internally needs ConfigProvider). Merges over
+// the real module so other real exports this file's module graph relies on stay available.
+vi.mock('@thunderid/design', async (importOriginal) => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ DesignProvider: ({children}: {children: React.ReactNode}) => children,
+ useDesign: () => ({isDesignEnabled: false, theme: undefined}),
+ // Renders a rich-text link (data-action-ref) and a button (id) so hover/click
+ // resolution in IframeContent has real DOM targets to resolve against.
+ FlowComponentRenderer: ({component, onSubmit}: {component: {id?: string}; onSubmit: () => void}) => (
+
+ ),
+ AuthPageLayout: ({children}: {children: React.ReactNode}) => {children}
,
+ AuthCardLayout: ({children}: {children: React.ReactNode}) => {children}
,
+ FontImporter: () => null,
+ getFontImportURL: () => undefined,
+ getCspNonce: () => document.querySelector('meta[property="csp-nonce"]')?.getAttribute('content') ?? undefined,
+ };
+});
+
+vi.mock('@emotion/cache', async (importActual) => {
+ const actual = await importActual();
+ return {
+ ...actual,
+ default: (options: Parameters[0]) => actual.default({...options, container: document.head}),
+ };
+});
+
+vi.mock('@emotion/react', () => ({
+ CacheProvider: ({children}: {children: React.ReactNode}) => children,
+}));
+
+const mockTheme = {
+ colorSchemes: {
+ light: {palette: {background: {default: '#ffffff'}}},
+ dark: {palette: {background: {default: '#121212'}}},
+ },
+} as unknown as Theme;
+
+// `EmbeddedFlowComponent` resolves to an error type here because `@thunderid/react` has no `types`
+// condition in its `exports` map, and this package's `tsconfig.spec.json` forces `module: commonjs`
+// against a `moduleResolution: bundler` base, so the checker falls back to the untyped CJS entry point.
+// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+const mockComponent: EmbeddedFlowComponent = {id: 'comp-1'} as unknown as EmbeddedFlowComponent;
+
+function renderIframeContent(props: Partial[0]> = {}) {
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ const iframeDoc = iframe.contentDocument!;
+ iframeDoc.open();
+ iframeDoc.write('');
+ iframeDoc.close();
+
+ // IframeContent's own tree renders into the iframe document's #root (mirroring the portal
+ // GatePreview normally sets up), so DOM queries like getElementById run against iframeDoc.
+ const utils = render(
+
+
+ ,
+ {container: iframeDoc.getElementById('root')!, baseElement: iframeDoc.body},
+ );
+
+ return {...utils, iframeDoc, cleanupIframe: () => iframe.remove()};
+}
+
+afterEach(() => {
+ cleanup();
+ document.querySelectorAll('iframe').forEach((iframe) => iframe.remove());
+});
+
+describe('IframeContent', () => {
+ describe('sanitizeThemeForMui', () => {
+ it('strips defaultColorScheme "system" before it reaches downstream MUI theming', () => {
+ const themeWithSystemScheme = {
+ defaultColorScheme: 'system',
+ colorSchemes: {
+ light: {palette: {background: {default: '#ffffff'}}},
+ dark: {palette: {background: {default: '#121212'}}},
+ },
+ } as unknown as Theme;
+
+ // Rendering must not throw despite MUI's extendTheme rejecting 'system'.
+ expect(() => renderIframeContent({theme: themeWithSystemScheme})).not.toThrow();
+ });
+
+ it('renders normally when defaultColorScheme is a plain light/dark value', () => {
+ const themeWithLightScheme = {
+ defaultColorScheme: 'light',
+ colorSchemes: {
+ light: {palette: {background: {default: '#ffffff'}}},
+ dark: {palette: {background: {default: '#121212'}}},
+ },
+ } as unknown as Theme;
+
+ const {iframeDoc} = renderIframeContent({theme: themeWithLightScheme});
+ expect(iframeDoc.getElementById('root')?.children.length).toBeGreaterThan(0);
+ });
+ });
+
+ describe('stylesheet injection', () => {
+ it('injects an inline stylesheet as a