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}) => ( +
+ + Rich text link + + +
+ ), + 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