diff --git a/.changeset/rare-parrots-do.md b/.changeset/rare-parrots-do.md new file mode 100644 index 00000000000..9292d1d79c1 --- /dev/null +++ b/.changeset/rare-parrots-do.md @@ -0,0 +1,14 @@ +--- +"@wso2is/console": patch +"@wso2is/myaccount": patch +"@wso2is/admin.ask-password-flow-builder.v1": patch +"@wso2is/admin.consents.v1": patch +"@wso2is/admin.flow-builder-core.v1": patch +"@wso2is/admin.registration-flow-builder.v1": patch +"@wso2is/admin.server-configurations.v1": patch +"@wso2is/common.consents.v1": patch +"@wso2is/identity-apps-core": patch +"@wso2is/i18n": patch +--- + +Add preference management management UI diff --git a/apps/console/src/configs/routes.tsx b/apps/console/src/configs/routes.tsx index aa216fd9abf..cbea53f8b75 100644 --- a/apps/console/src/configs/routes.tsx +++ b/apps/console/src/configs/routes.tsx @@ -609,6 +609,46 @@ export const getAppViewRoutes = (): RouteInterface[] => { protected: true, showOnSidePanel: false }, + { + category: "extensions:manage.sidePanel.categories.userManagement", + children: [ + { + component: lazy(() => import("@wso2is/admin.consents.v1/pages/preference-management-new")), + exact: true, + icon: { + icon: getSidePanelIcons().childIcon + }, + id: "preference-management-new", + name: "Preference Management New", + path: AppConstants.getPaths().get("PREFERENCE_MANAGEMENT_NEW"), + protected: true, + showOnSidePanel: false + }, + { + component: lazy(() => import("@wso2is/admin.consents.v1/pages/preference-management-edit")), + exact: true, + icon: { + icon: getSidePanelIcons().childIcon + }, + id: "preference-management-edit", + name: "Preference Management Edit", + path: AppConstants.getPaths().get("PREFERENCE_MANAGEMENT_EDIT"), + protected: true, + showOnSidePanel: false + } + ], + component: lazy(() => import("@wso2is/admin.consents.v1/pages/preference-management")), + exact: true, + icon: { + icon: getSidePanelIcons().consents + }, + id: "preferenceManagement", + name: "extensions:develop.sidePanel.preferenceManagement", + order: 8, + path: AppConstants.getPaths().get("PREFERENCE_MANAGEMENT"), + protected: true, + showOnSidePanel: false + }, { category: "console:develop.features.sidePanel.categories.application", children: [ @@ -626,6 +666,20 @@ export const getAppViewRoutes = (): RouteInterface[] => { protected: true, showOnSidePanel: false }, + { + component: lazy(() => + import("@wso2is/admin.connections.v1/pages/connection-test") + ), + exact: true, + icon: { + icon: getSidePanelIcons().childIcon + }, + id: "identityProviderTest", + name: "Identity Provider Test", + path: AppConstants.getPaths().get("IDP_TEST"), + protected: true, + showOnSidePanel: false + }, { component: lazy(() => import("@wso2is/admin.connections.v1/pages/connection-edit") diff --git a/apps/console/src/public/deployment.config.json b/apps/console/src/public/deployment.config.json index 9ea3f59095a..00efcee08d2 100644 --- a/apps/console/src/public/deployment.config.json +++ b/apps/console/src/public/deployment.config.json @@ -2248,6 +2248,7 @@ "attributeDialects": "v0.0.0", "attributeUpdateVerificationSettings": "v1.0.0", "branding": "v0.0.0", + "consents": "v0.0.0", "consoleSettings": "v0.0.0", "emailTemplates": "v0.0.0", "flows": "v1.0.0", @@ -2256,6 +2257,7 @@ "groups": "v0.0.0", "identityProviders": "v0.0.0", "loginAndRegistration": "v1.0.0", + "preferenceManagement": "v0.0.0", "mcpServers": "v0.0.0", "oidcScopes": "v0.0.0", "organizations": "v0.0.0", diff --git a/apps/myaccount/src/api/consents.ts b/apps/myaccount/src/api/consents.ts index a45c261f77b..f3040443352 100644 --- a/apps/myaccount/src/api/consents.ts +++ b/apps/myaccount/src/api/consents.ts @@ -370,6 +370,43 @@ export const getConsentById = ( }); }; +/** + * Creates a new user consent record via the v2 consents API. + * + * @param consentsBaseUrl - Base URL for the v2 consents endpoint. + * @param body - Consent payload with serviceId, language, and purposes. + * @returns A promise containing the created consent detail. + */ +export const postConsent = ( + consentsBaseUrl: string, + body: { + serviceId: string; + language: string; + purposes: Array<{ + id: string; + elements: Array<{ id: string }>; + }>; + } +): Promise => { + const requestConfig: AxiosRequestConfig = { + data: body, + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: "POST", + url: consentsBaseUrl + }; + + return httpClient(requestConfig) + .then((response: HttpResponse) => { + return response.data; + }) + .catch((error: HttpError) => { + return Promise.reject(error); + }); +}; + /** * Revokes a user consent record via the v2 consents API. * diff --git a/apps/myaccount/src/components/index.ts b/apps/myaccount/src/components/index.ts index d1b041ced2b..877d3661c20 100644 --- a/apps/myaccount/src/components/index.ts +++ b/apps/myaccount/src/components/index.ts @@ -23,6 +23,7 @@ export * from "./consents"; export * from "./linked-accounts"; export * from "./multi-factor-authentication"; export * from "./overview"; +export * from "./preference-management"; export * from "./policy-consent"; export * from "./profile"; export * from "./shared"; diff --git a/apps/myaccount/src/components/policy-consent/policy-consent.tsx b/apps/myaccount/src/components/policy-consent/policy-consent.tsx index c6ab2079d62..d33b0a041bc 100644 --- a/apps/myaccount/src/components/policy-consent/policy-consent.tsx +++ b/apps/myaccount/src/components/policy-consent/policy-consent.tsx @@ -88,21 +88,31 @@ export const PolicyConsent: FunctionComponent = ( ) ); - const items: PolicyConsentItemInterface[] = details.map( - (detail: PolicyConsentDetailInterface, index: number): PolicyConsentItemInterface => { + const policyDetails: PolicyConsentDetailInterface[] = details.filter( + (detail: PolicyConsentDetailInterface) => + detail.purposes?.some( + (p: ConsentedPurposeInterface) => p.type === "Policy" + ) + ); + + const items: PolicyConsentItemInterface[] = policyDetails.map( + (detail: PolicyConsentDetailInterface): PolicyConsentItemInterface => { + const matchingSummary: PolicyConsentSummaryInterface = summaries.find( + (s: PolicyConsentSummaryInterface) => s.id === detail.id + ) ?? summaries[0]; const purpose: ConsentedPurposeInterface | undefined = Array.isArray(detail.purposes) && detail.purposes.length > 0 ? detail.purposes[0] : undefined; return { - consentId: summaries[index].id, + consentId: detail.id, policyUrl: purpose?.properties?.policyUrl ?? null, purposeId: purpose?.id ?? "", purposeName: purpose?.name ?? "", - purposeVersionId: purpose?.purposeVersionId ?? "", + purposeVersionId: purpose?.versionId ?? purpose?.purposeVersionId ?? "", state: detail.state, - timestamp: summaries[index].timestamp, + timestamp: matchingSummary.timestamp, version: purpose?.version ?? null }; } @@ -192,11 +202,10 @@ export const PolicyConsent: FunctionComponent = ( onClose={ handlePolicyRevokeModalClose } type="warning" header={ t( - "myAccount:components.policyConsentManagement.modals.revokeModal.heading", - { policyName: revokingPolicyItem?.purposeName ?? "" } + "myAccount:components.policyConsentManagement.dangerZones.revoke.header" ) } content={ t( - "myAccount:components.policyConsentManagement.modals.revokeModal.message" + "myAccount:components.policyConsentManagement.dangerZones.revoke.subheader" ) } /> ); diff --git a/features/admin.flow-builder-core.v1/models/policies.ts b/apps/myaccount/src/components/preference-management/index.ts similarity index 82% rename from features/admin.flow-builder-core.v1/models/policies.ts rename to apps/myaccount/src/components/preference-management/index.ts index 880bb8347a9..655620fe45c 100644 --- a/features/admin.flow-builder-core.v1/models/policies.ts +++ b/apps/myaccount/src/components/preference-management/index.ts @@ -16,9 +16,4 @@ * under the License. */ -/** - * Represents a policy reference stored in the flow API payload. - */ -export interface PolicyConfigItemInterface { - purposeId: string; -} +export { PreferenceManagement } from "./preference-management"; diff --git a/apps/myaccount/src/components/preference-management/preference-management-list.tsx b/apps/myaccount/src/components/preference-management/preference-management-list.tsx new file mode 100644 index 00000000000..56c36ae846d --- /dev/null +++ b/apps/myaccount/src/components/preference-management/preference-management-list.tsx @@ -0,0 +1,297 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import { DangerZone, DangerZoneGroup, GenericIcon, Media } from "@wso2is/react-components"; +import { ShieldSquareCheckIcon } from "@oxygen-ui/react-icons"; +import React, { FunctionComponent, ReactElement } from "react"; +import { useTranslation } from "react-i18next"; +import { Button, Checkbox, Divider, Grid, Icon, List } from "semantic-ui-react"; +import { + PreferenceManagementElementInterface, + PreferenceManagementItemInterface +} from "../../models/consents"; +import { toSentenceCase } from "../../utils"; +import { EditSection } from "../shared"; + +/** + * Prop types for the preference management list component. + */ +interface PreferenceManagementListPropsInterface extends IdentifiableComponentInterface { + items: PreferenceManagementItemInterface[]; + activeIndexes: number[]; + deselectedElements: Map>; + onToggleDetail: (index: number) => void; + onElementToggle: (consentId: string, elementId: string) => void; + onUpdate: (consentId: string) => void; + onRevokeClick: (item: PreferenceManagementItemInterface) => void; +} + +/** + * Pure list component for preference management. + * Data fetching and state are managed by the parent. + */ +export const PreferenceManagementList: FunctionComponent = ( + props: PreferenceManagementListPropsInterface +): ReactElement => { + + const { + items, + activeIndexes, + deselectedElements, + onToggleDetail, + onElementToggle, + onUpdate, + onRevokeClick, + ["data-componentid"]: componentId + } = props; + const { t, i18n } = useTranslation(); + + const resolveStateClassname: (state: string) => string = (state: string): string => { + if (state === "ACTIVE") { + return "positive"; + } + + return ""; + }; + + const isUpdatable: (item: PreferenceManagementItemInterface) => boolean = ( + item: PreferenceManagementItemInterface + ): boolean => { + return (deselectedElements.get(item.consentId)?.size ?? 0) > 0; + }; + + const visibleElements: (item: PreferenceManagementItemInterface) => PreferenceManagementElementInterface[] = ( + item: PreferenceManagementItemInterface + ): PreferenceManagementElementInterface[] => { + return item.elements.filter( + (element: PreferenceManagementElementInterface) => element.name !== "Preference" + ); + }; + + return ( + + { + items.length > 0 && items.map((item: PreferenceManagementItemInterface, index: number) => ( + + + + + + + { item.purposeName } + +

+ + { item.state === "ACTIVE" + ? `${t( + "myAccount:components" + + ".preferenceManagement" + + ".consentedOnLabel" + )} ${new Date( + item.timestamp + ).toLocaleDateString(i18n.language)}` + : toSentenceCase(item.state) + } +

+
+
+
+ + + + + + + + + + +
+
+ { + activeIndexes.includes(index) && ( + + + { visibleElements(item).length > 0 && ( + <> + + + + { t( + "myAccount:components.preferenceManagement" + + ".elementsHeading" + ) } + + + + + + + { visibleElements(item).map( + (element: PreferenceManagementElementInterface) => ( + + + + + onElementToggle( + item.consentId, + element.id + ) + } + data-componentid={ + `${componentId}-` + + `${item.purposeId}-` + + `${element.id}-checkbox` + } + /> + + + + ) + ) } + + + + + + + + + + + ) } + + + + onRevokeClick(item) } + data-componentid={ + `${componentId}-${item.purposeId}` + + "-danger-zone-revoke" + } + /> + + + + + + ) + } +
+ )) + } +
+ ); +}; + +PreferenceManagementList.defaultProps = { + "data-componentid": "preference-management-list" +}; diff --git a/apps/myaccount/src/components/preference-management/preference-management.tsx b/apps/myaccount/src/components/preference-management/preference-management.tsx new file mode 100644 index 00000000000..ad0e263836c --- /dev/null +++ b/apps/myaccount/src/components/preference-management/preference-management.tsx @@ -0,0 +1,345 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import React, { FunctionComponent, ReactElement, useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; +import { + getConsentById, + getConsentsBySubject, + postConsent, + revokeConsentById +} from "../../api/consents"; +import { + AlertInterface, + AlertLevels +} from "../../models"; +import { + ConsentedPurposeInterface, + PreferenceManagementElementInterface, + PreferenceManagementItemInterface, + PolicyConsentDetailInterface, + PolicyConsentListResponseInterface, + PolicyConsentSummaryInterface +} from "../../models/consents"; +import { AppState } from "../../store"; +import { ModalComponent, SettingsSection } from "../shared"; +import { PreferenceManagementList } from "./preference-management-list"; + +/** + * Proptypes for the preference management component. + */ +interface PreferenceManagementComponentProps extends IdentifiableComponentInterface { + onAlertFired: (alert: AlertInterface) => void; +} + +export const PreferenceManagement: FunctionComponent = ( + props: PreferenceManagementComponentProps +): ReactElement => { + + const { onAlertFired, ["data-componentid"]: componentId } = props; + + const [ consentItems, setConsentItems ] = useState([]); + const [ activeIndexes, setActiveIndexes ] = useState([]); + const [ deselectedElements, setDeselectedElements ] = useState>>(new Map()); + const [ revokingItem, setRevokingItem ] = useState(null); + const [ isRevokeModalVisible, setRevokeModalVisible ] = useState(false); + const [ isRevoking, setIsRevoking ] = useState(false); + + const userName: string = useSelector((state: AppState) => state?.authenticationInformation?.profileInfo.userName); + const consentsBaseUrl: string = useSelector( + (state: AppState) => state?.config?.endpoints?.consentMgtV2?.consents + ); + const { t } = useTranslation(); + + const loadPreferenceManagement: () => Promise = useCallback(async (): Promise => { + if (!userName || !consentsBaseUrl) { + return; + } + + try { + const listResponse: PolicyConsentListResponseInterface = + await getConsentsBySubject(consentsBaseUrl, userName, "ACTIVE"); + const summaries: PolicyConsentSummaryInterface[] = listResponse.Consents ?? []; + + if (summaries.length === 0) { + setConsentItems([]); + setDeselectedElements(new Map()); + + return; + } + + const details: PolicyConsentDetailInterface[] = await Promise.all( + summaries.map((summary: PolicyConsentSummaryInterface) => + getConsentById(consentsBaseUrl, summary.id) + ) + ); + + const preferenceDetails: PolicyConsentDetailInterface[] = details.filter( + (detail: PolicyConsentDetailInterface) => + detail.purposes?.some( + (p: ConsentedPurposeInterface) => p.type === "Preference" + ) + ); + + const items: PreferenceManagementItemInterface[] = preferenceDetails.map( + (detail: PolicyConsentDetailInterface): PreferenceManagementItemInterface => { + const matchingSummary: PolicyConsentSummaryInterface = summaries.find( + (s: PolicyConsentSummaryInterface) => s.id === detail.id + ) ?? summaries[0]; + const purpose: ConsentedPurposeInterface | undefined = + Array.isArray(detail.purposes) && detail.purposes.length > 0 + ? detail.purposes[0] + : undefined; + + return { + consentId: detail.id, + elements: purpose?.elements ?? [], + language: detail.language, + policyUrl: purpose?.properties?.policyUrl ?? null, + purposeDescription: purpose?.name ?? "", + purposeId: purpose?.id ?? "", + purposeName: purpose?.name ?? "", + purposeVersionId: purpose?.versionId ?? purpose?.purposeVersionId ?? "", + serviceId: detail.serviceId, + state: detail.state, + timestamp: matchingSummary.timestamp, + version: purpose?.version ?? null + }; + } + ); + + setConsentItems(items); + // Reset deselected state on reload — all elements start as selected + setDeselectedElements(new Map(items.map( + (item: PreferenceManagementItemInterface) => [ item.consentId, new Set() ] + ))); + } catch { + onAlertFired({ + description: t( + "myAccount:components.preferenceManagement.notifications.fetch.genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "myAccount:components.preferenceManagement.notifications.fetch.genericError.message" + ) + }); + } + }, [ userName, consentsBaseUrl, t, onAlertFired ]); + + useEffect(() => { + loadPreferenceManagement(); + }, [ loadPreferenceManagement ]); + + const handleToggleDetail: (index: number) => void = (index: number): void => { + setActiveIndexes((prev: number[]) => + prev.includes(index) + ? prev.filter((i: number) => i !== index) + : [ ...prev, index ] + ); + }; + + const handleElementToggle: (consentId: string, elementId: string) => void = ( + consentId: string, + elementId: string + ): void => { + setDeselectedElements((prev: Map>) => { + const next: Map> = new Map(prev); + const deselected: Set = new Set(next.get(consentId) ?? []); + + if (deselected.has(elementId)) { + deselected.delete(elementId); + } else { + deselected.add(elementId); + } + next.set(consentId, deselected); + + return next; + }); + }; + + const handleUpdate: (consentId: string) => void = (consentId: string): void => { + if (!consentsBaseUrl) { + return; + } + + const item: PreferenceManagementItemInterface | undefined = consentItems.find( + (i: PreferenceManagementItemInterface) => i.consentId === consentId + ); + + if (!item) { + return; + } + + const deselected: Set = deselectedElements.get(consentId) ?? new Set(); + const visibleElements: PreferenceManagementElementInterface[] = item.elements.filter( + (e: PreferenceManagementElementInterface) => e.name !== "Preference" + ); + const allDeselected: boolean = visibleElements.every( + (e: PreferenceManagementElementInterface) => deselected.has(e.id) + ); + + if (allDeselected) { + setRevokingItem(item); + setRevokeModalVisible(true); + + return; + } + + const remainingElements: Array<{ id: string }> = item.elements + .filter((e: PreferenceManagementElementInterface) => !deselected.has(e.id)) + .map((e: PreferenceManagementElementInterface) => ({ id: e.id })); + + postConsent(consentsBaseUrl, { + language: item.language, + purposes: [ { + elements: remainingElements, + id: item.purposeId + } ], + serviceId: item.serviceId + }) + .then(async () => { + onAlertFired({ + description: t( + "myAccount:components.preferenceManagement.notifications.update.success.description" + ), + level: AlertLevels.SUCCESS, + message: t( + "myAccount:components.preferenceManagement.notifications.update.success.message" + ) + }); + await loadPreferenceManagement(); + }) + .catch(() => { + onAlertFired({ + description: t( + "myAccount:components.preferenceManagement.notifications.update.genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "myAccount:components.preferenceManagement.notifications.update.genericError.message" + ) + }); + }); + }; + + const handleRevokeClick: (item: PreferenceManagementItemInterface) => void = ( + item: PreferenceManagementItemInterface + ): void => { + setRevokingItem(item); + setRevokeModalVisible(true); + }; + + const handleRevokeModalClose: () => void = (): void => { + setRevokeModalVisible(false); + setRevokingItem(null); + }; + + const handleRevokeConfirm: () => void = (): void => { + if (isRevoking || !revokingItem || !consentsBaseUrl) { + return; + } + + setIsRevoking(true); + revokeConsentById(consentsBaseUrl, revokingItem.consentId) + .then(async () => { + onAlertFired({ + description: t( + "myAccount:components.preferenceManagement.notifications.revoke.success.description" + ), + level: AlertLevels.SUCCESS, + message: t( + "myAccount:components.preferenceManagement.notifications.revoke.success.message" + ) + }); + handleRevokeModalClose(); + await loadPreferenceManagement(); + setIsRevoking(false); + }) + .catch(() => { + onAlertFired({ + description: t( + "myAccount:components.preferenceManagement.notifications" + + ".revoke.genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "myAccount:components.preferenceManagement.notifications.revoke.genericError.message" + ) + }); + setIsRevoking(false); + }); + }; + + const revokeModal: () => ReactElement = (): ReactElement => { + return ( + + ); + }; + + return ( + <> + 0) + ? t("myAccount:sections.preferenceManagement.placeholders.emptyConsentList.heading") + : null + } + showActionBar={ + !(consentItems && consentItems.length && consentItems.length > 0) + } + > + + { revokingItem && revokeModal() } + + + ); +}; + +PreferenceManagement.defaultProps = { + "data-componentid": "preference-management" +}; diff --git a/apps/myaccount/src/layouts/dashboard.tsx b/apps/myaccount/src/layouts/dashboard.tsx index af6b7b45665..3e033647e67 100644 --- a/apps/myaccount/src/layouts/dashboard.tsx +++ b/apps/myaccount/src/layouts/dashboard.tsx @@ -28,6 +28,7 @@ import { ChildRouteInterface, RouteInterface } from "@wso2is/core/models"; + import { initializeAlertSystem } from "@wso2is/core/store"; import { RouteUtils as CommonRouteUtils, @@ -66,7 +67,8 @@ import { Header, ProtectedRoute } from "../components"; import { SystemNotificationAlert } from "../components/shared/system-notification-alert"; import { getDashboardLayoutRoutes, getEmptyPlaceholderIllustrations } from "../configs"; import { AppConstants, UIConstants } from "../constants"; -import { history, isApprovalsTabEnabled } from "../helpers"; +import { commonConfig } from "../extensions"; +import { history, isApprovalsTabEnabled, resolveUserstore } from "../helpers"; import { Application, AuthStateInterface, ConfigReducerStateInterface } from "../models"; import { AppState } from "../store"; import { toggleApplicationsPageVisibility } from "../store/actions"; @@ -115,6 +117,7 @@ export const DashboardLayout: FunctionComponent([]); const allowedScopes: string = useSelector((state: AppState) => state?.authenticationInformation?.scope); const profileDetails: AuthStateInterface = useSelector((state: AppState) => state.authenticationInformation); + const hasLocalAccount: boolean = useSelector((state: AppState) => state.authenticationInformation.hasLocalAccount); useEffect(() => { const localeCookie: string = CookieStorageUtils.getItem("ui_lang"); @@ -217,9 +220,16 @@ export const DashboardLayout: FunctionComponent = (): ReactEle const { t } = useTranslation(); const dispatch: Dispatch = useDispatch(); - const handleAlerts: (alert: AlertInterface) => void = useCallback( + const accessConfig: FeatureConfigInterface = useSelector((state: AppState) => state?.config?.ui?.features); + const allowedScopes: string = useSelector((state: AppState) => state?.authenticationInformation?.scope); + + const handleAlerts: (_alert: AlertInterface) => void = useCallback( (alert: AlertInterface): void => { dispatch(addAlert(alert)); }, @@ -49,8 +55,16 @@ const ConsentsPage: FunctionComponent = (): ReactEle description={ t("myAccount:pages.consents.subTitle") } > - + { hasRequiredScopes( + accessConfig?.security, + accessConfig?.security?.scopes?.read, + allowedScopes + ) && isFeatureEnabled( + accessConfig?.security, + AppConstants.FEATURE_DICTIONARY.get("SECURITY_CONSENTS") + ) && } + ); diff --git a/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/consent-extended-properties.tsx b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/consent-extended-properties.tsx index 4c3c56a0c1a..d3e576d4287 100644 --- a/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/consent-extended-properties.tsx +++ b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/consent-extended-properties.tsx @@ -17,114 +17,136 @@ */ import Autocomplete, { AutocompleteRenderInputParams } from "@oxygen-ui/react/Autocomplete"; -import CircularProgress from "@oxygen-ui/react/CircularProgress"; import Stack from "@oxygen-ui/react/Stack"; import TextField from "@oxygen-ui/react/TextField"; import Typography from "@oxygen-ui/react/Typography"; -import { useRequiredScopes } from "@wso2is/access-control"; import { CommonResourcePropertiesPropsInterface } from "@wso2is/admin.flow-builder-core.v1/components/resource-property-panel/resource-properties"; -import { PolicyConfigItemInterface } from "@wso2is/admin.flow-builder-core.v1/models/policies"; -import { AppState } from "@wso2is/admin.core.v1/store"; -import { - ConsentListItemInterface, - useGetPurposes -} from "@wso2is/common.consents.v1"; -import { FeatureAccessConfigInterface, IdentifiableComponentInterface } from "@wso2is/core/models"; +import { PurposeInterface } from "@wso2is/admin.flow-builder-core.v1/models/purpose"; +import { ConsentListItemInterface, useGetPurposes } from "@wso2is/common.consents.v1"; +import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement, SyntheticEvent, useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; /** - * Props interface of {@link ConsentExtendedProperties} + * Props interface for generic consent extended properties */ -type ConsentExtendedPropertiesPropsInterface = CommonResourcePropertiesPropsInterface & - IdentifiableComponentInterface; +interface ConsentExtendedPropertiesInterface extends CommonResourcePropertiesPropsInterface, + IdentifiableComponentInterface { + filter: string; + emptyMessageKey: string; + selectLabelKey: string; + onPurposesChange?: (updated: PurposeInterface[]) => void; + preserveAttributes?: boolean; +} /** - * Extended properties for the consent element. + * Generic consent extended properties component. + * Handles common logic for both preference and policy consents. * * @param props - Props injected to the component. * @returns The ConsentExtendedProperties component. */ -const ConsentExtendedProperties: FunctionComponent = ({ +const ConsentExtendedProperties: FunctionComponent = ({ "data-componentid": componentId = "consent-extended-properties", resource, - onChange -}: ConsentExtendedPropertiesPropsInterface): ReactElement => { + onChange, + filter, + emptyMessageKey, + selectLabelKey, + onPurposesChange, + preserveAttributes = false +}: ConsentExtendedPropertiesInterface): ReactElement => { const { t } = useTranslation(); - const consentsFeatureConfig: FeatureAccessConfigInterface = useSelector( - (state: AppState) => state?.config?.ui?.features?.consents + + const purposes: PurposeInterface[] = useMemo( + (): PurposeInterface[] => resource.config?.purposes ?? [], + [ resource.config?.purposes ] + ); + + const { mappedData: availableConsents, isLoading: isConsentsLoading } = useGetPurposes( + { filter, limit: 50 } ); - const hasConsentsReadPermission: boolean = useRequiredScopes(consentsFeatureConfig?.scopes?.read); + const selectedConsentOptions: ConsentListItemInterface[] = useMemo( + (): ConsentListItemInterface[] => { + if (!availableConsents) return []; - const { mappedData: allPolicies, isLoading: isPoliciesLoading } = useGetPurposes( - hasConsentsReadPermission ? { filter: "type eq Policy", limit: 50 } : null + return purposes + .map((p: PurposeInterface) => + availableConsents.find((c: ConsentListItemInterface): boolean => c.id === p.purposeId) + ) + .filter(Boolean) as ConsentListItemInterface[]; + }, + [ purposes, availableConsents ] ); - const selectedPolicies: PolicyConfigItemInterface[] = useMemo((): PolicyConfigItemInterface[] => { - return resource.config?.policies ?? []; - }, [ resource.config?.policies ]); + const handleSelectConsent = (_: SyntheticEvent, selected: ConsentListItemInterface[]): void => { + let updated: PurposeInterface[]; - const selectedPolicyOptions: ConsentListItemInterface[] = useMemo((): ConsentListItemInterface[] => { - if (!allPolicies) return []; + if (preserveAttributes) { + const existingById: Map = new Map( + purposes.map((p: PurposeInterface): [ string, PurposeInterface ] => [ p.purposeId, p ]) + ); - return selectedPolicies - .map((p: PolicyConfigItemInterface) => - allPolicies.find((policy: ConsentListItemInterface) => policy.id === p.purposeId) - ) - .filter(Boolean) as ConsentListItemInterface[]; - }, [ selectedPolicies, allPolicies ]); + updated = selected.map((c: ConsentListItemInterface): PurposeInterface => ({ + attributes: existingById.get(c.id)?.attributes ?? [], + purposeId: c.id + })); + } else { + updated = selected.map((c: ConsentListItemInterface): PurposeInterface => ({ + attributes: [], + purposeId: c.id + })); + } - const handlePolicySelectionChange = ( - _event: SyntheticEvent, - value: ConsentListItemInterface[] - ): void => { - onChange( - "config.policies", - value.map((policy: ConsentListItemInterface): PolicyConfigItemInterface => ({ purposeId: policy.id })), - resource - ); + if (onPurposesChange) { + onPurposesChange(updated); + } else { + onChange("config.purposes", updated, resource); + } }; - if (isPoliciesLoading) { + if (isConsentsLoading) { return ( - - + + + { t(emptyMessageKey) } + ); } - if (!allPolicies || allPolicies.length === 0) { + if ((availableConsents ?? []).length === 0) { return ( - - { t("consents:registrationFlow.noPolicies") } + + { t(emptyMessageKey) } ); } return ( - + option.name } isOptionEqualToValue={ ( option: ConsentListItemInterface, value: ConsentListItemInterface ): boolean => option.id === value.id } - onChange={ handlePolicySelectionChange } + onChange={ handleSelectConsent } + disabled={ isConsentsLoading } renderInput={ (params: AutocompleteRenderInputParams): ReactElement => ( ) } data-componentid={ `${componentId}-multiselect` } diff --git a/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/policy-consent-extended-properties.tsx b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/policy-consent-extended-properties.tsx new file mode 100644 index 00000000000..090dbd7611c --- /dev/null +++ b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/policy-consent-extended-properties.tsx @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Stack from "@oxygen-ui/react/Stack"; +import Typography from "@oxygen-ui/react/Typography"; +import { useRequiredScopes } from "@wso2is/access-control"; +import { + CommonResourcePropertiesPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/resource-property-panel/resource-properties"; +import { AppState } from "@wso2is/admin.core.v1/store"; +import { FeatureAccessConfigInterface, IdentifiableComponentInterface } from "@wso2is/core/models"; +import React, { FunctionComponent, ReactElement } from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; +import ConsentExtendedProperties from "./consent-extended-properties"; + +/** + * Props interface of {@link PolicyConsentExtendedProperties} + */ +type PolicyConsentExtendedPropertiesPropsInterface = CommonResourcePropertiesPropsInterface & + IdentifiableComponentInterface; + +/** + * Extended properties for the policy consent element. + * Handles permission checks for policy purposes. + * + * @param props - Props injected to the component. + * @returns The PolicyConsentExtendedProperties component. + */ +const PolicyConsentExtendedProperties: FunctionComponent = ({ + "data-componentid": componentId = "policy-consent-extended-properties", + resource, + onChange +}: PolicyConsentExtendedPropertiesPropsInterface): ReactElement => { + const { t } = useTranslation(); + const consentsFeatureConfig: FeatureAccessConfigInterface = useSelector( + (state: AppState) => state?.config?.ui?.features?.consents + ); + + const hasConsentsReadPermission: boolean = useRequiredScopes(consentsFeatureConfig?.scopes?.read); + + if (!hasConsentsReadPermission) { + return ( + + + { t("consents:registrationFlow.noPolicies") } + + + ); + } + + return ( + + ); +}; + +export default PolicyConsentExtendedProperties; diff --git a/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/preference-management-extended-properties.tsx b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/preference-management-extended-properties.tsx new file mode 100644 index 00000000000..361e8f62769 --- /dev/null +++ b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/extended-properties/preference-management-extended-properties.tsx @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Stack from "@oxygen-ui/react/Stack"; +import Typography from "@oxygen-ui/react/Typography"; +import { useRequiredScopes } from "@wso2is/access-control"; +import { + CommonResourcePropertiesPropsInterface +} from "@wso2is/admin.flow-builder-core.v1/components/resource-property-panel/resource-properties"; +import { AppState } from "@wso2is/admin.core.v1/store"; +import { FeatureAccessConfigInterface, IdentifiableComponentInterface } from "@wso2is/core/models"; +import React, { FunctionComponent, ReactElement } from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; +import ConsentExtendedProperties from "./consent-extended-properties"; + +/** + * Props interface of {@link PreferenceManagementExtendedProperties} + */ +type PreferenceManagementExtendedPropertiesPropsInterface = CommonResourcePropertiesPropsInterface & + IdentifiableComponentInterface; + +/** + * Extended properties for the preference management element. + * Handles permission checks and attribute preservation for preference purposes. + * + * @param props - Props injected to the component. + * @returns The PreferenceManagementExtendedProperties component. + */ +const PreferenceManagementExtendedProperties: FunctionComponent = ({ + "data-componentid": componentId = "preference-management-extended-properties", + resource, + onChange +}: PreferenceManagementExtendedPropertiesPropsInterface): ReactElement => { + const { t } = useTranslation(); + const consentsFeatureConfig: FeatureAccessConfigInterface = useSelector( + (state: AppState) => state?.config?.ui?.features?.consents + ); + + const hasConsentsReadPermission: boolean = useRequiredScopes(consentsFeatureConfig?.scopes?.read); + + if (!hasConsentsReadPermission) { + return ( + + + { t("consents:registrationFlow.noPreference") } + + + ); + } + + return ( + + ); +}; + +export default PreferenceManagementExtendedProperties; diff --git a/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/resource-properties.tsx b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/resource-properties.tsx index 3c18c1dac99..c2024f8588d 100644 --- a/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/resource-properties.tsx +++ b/features/admin.ask-password-flow-builder.v1/components/resource-property-panel/resource-properties.tsx @@ -29,7 +29,8 @@ import { IdentifiableComponentInterface } from "@wso2is/core/models"; import isEmpty from "lodash-es/isEmpty"; import React, { ChangeEvent, FunctionComponent, ReactElement, useMemo } from "react"; import ButtonExtendedProperties from "./extended-properties/button-extended-properties"; -import ConsentExtendedProperties from "./extended-properties/consent-extended-properties"; +import PreferenceManagementExtendedProperties from "./extended-properties/preference-management-extended-properties"; +import PolicyConsentExtendedProperties from "./extended-properties/policy-consent-extended-properties"; import FieldExtendedProperties from "./extended-properties/field-extended-properties"; import RulesProperties from "./nodes/rules-properties"; import ResourcePropertyFactory from "./resource-property-factory"; @@ -131,10 +132,24 @@ const ResourceProperties: FunctionComponent = return ( <> { renderElementId() } - + { renderElementPropertyFactory() } + + ); + } + + if (resource.type === ElementTypes.Preference) { + return ( + <> + { renderElementId() } + { renderElementPropertyFactory() } diff --git a/features/admin.ask-password-flow-builder.v1/data/elements.json b/features/admin.ask-password-flow-builder.v1/data/elements.json index f4cdead9e5d..36c3fd33c59 100644 --- a/features/admin.ask-password-flow-builder.v1/data/elements.json +++ b/features/admin.ask-password-flow-builder.v1/data/elements.json @@ -12,7 +12,7 @@ }, "config": { "description": "By continuing, you agree to the following policies.", - "policies": [] + "purposes": [] } } ] diff --git a/features/admin.ask-password-flow-builder.v1/data/steps.json b/features/admin.ask-password-flow-builder.v1/data/steps.json index 3cdb63166d0..1a1d1d0b2d2 100644 --- a/features/admin.ask-password-flow-builder.v1/data/steps.json +++ b/features/admin.ask-password-flow-builder.v1/data/steps.json @@ -19,5 +19,69 @@ "next": "" } } + }, + { + "resourceType": "STEP", + "category": "INTERFACE", + "type": "VIEW", + "variant": "PREFERENCE_MANAGEMENT", + "version": "0.1.0", + "deprecated": false, + "display": { + "label": "Preference Management View", + "image": "assets/images/icons/email-down.svg", + "showOnResourcePanel": true + }, + "config": {}, + "data": { + "components": [ + { + "id": "{{ID}}", + "category": "DISPLAY", + "type": "TYPOGRAPHY", + "variant": "H3", + "config": { + "text": "Communication Preferences" + } + }, + { + "id": "{{ID}}", + "category": "DISPLAY", + "type": "RICH_TEXT", + "config": { + "text": "

Please review your preferences to proceed.

" + } + }, + { + "id": "{{ID}}", + "category": "BLOCK", + "type": "FORM", + "config": {}, + "components": [ + { + "id": "{{ID}}", + "category": "FIELD", + "type": "PREFERENCE", + "config": { + "purposes": [] + } + }, + { + "id": "{{ID}}", + "category": "ACTION", + "type": "BUTTON", + "variant": "PRIMARY", + "config": { + "type": "submit", + "text": "Continue" + }, + "action": { + "type": "NEXT" + } + } + ] + } + ] + } } ] diff --git a/features/admin.connections.v1/api/connection-test-api.ts b/features/admin.connections.v1/api/connection-test-api.ts new file mode 100644 index 00000000000..57798906866 --- /dev/null +++ b/features/admin.connections.v1/api/connection-test-api.ts @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AsgardeoSPAClient, HttpClientInstance } from "@asgardeo/auth-react"; +import { ResourceEndpointsInterface } from "@wso2is/admin.core.v1/models/config"; +import { HttpMethods } from "@wso2is/core/models"; +import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; + +const httpClient: HttpClientInstance = AsgardeoSPAClient.getInstance() + .httpRequest.bind(AsgardeoSPAClient.getInstance()); + +interface ConnectionTestErrorResponseInterface { + description?: string; + message?: string; +} + +interface ConnectionTestMetadataInterface { + authorizationUrl?: string; + [ key: string ]: unknown; +} + +export interface ConnectionTestSessionResponseInterface { + debugId?: string; + metadata?: ConnectionTestMetadataInterface; + [ key: string ]: unknown; +} + +/** + * Starts a connection test session. + * + * @param resourceEndpoints - Connection resource endpoints. + * @param connectionId - Connection id. + * @returns Response containing test session data. + */ +export const startConnectionTestSession = ( + resourceEndpoints: ResourceEndpointsInterface, + connectionId: string +): Promise> => { + const requestConfig: AxiosRequestConfig = { + data: { + connectionId + }, + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + method: HttpMethods.POST, + url: `${ resourceEndpoints.debug }/idp` + }; + + return httpClient(requestConfig) as Promise>; +}; + +/** + * Retrieves connection test results. + * + * @param resourceEndpoints - Connection resource endpoints. + * @param sid - Debug session id. + * @returns Test result payload. + */ +export const getConnectionTestResult = ( + resourceEndpoints: ResourceEndpointsInterface, + sid: string +): Promise> => { + const requestConfig: AxiosRequestConfig = { + headers: { + "Accept": "application/json" + }, + method: HttpMethods.GET, + url: resourceEndpoints.debugResult.replace(":sid", sid) + }; + + return httpClient(requestConfig) as Promise>; +}; + +const isConnectionTestAxiosError = ( + error: unknown +): error is AxiosError => + typeof error === "object" && error !== null && (error as AxiosError).isAxiosError === true; + +/** + * Resolves a user-friendly error message from test API failures. + * + * @param error - Unknown error. + * @returns Error message if available. + */ +export const resolveConnectionTestErrorMessage = (error: unknown): string | undefined => { + if (isConnectionTestAxiosError(error)) { + return error.response?.data?.message ?? error.response?.data?.description ?? error.message; + } + + if (error instanceof Error) { + return error.message; + } + + return undefined; +}; diff --git a/features/admin.connections.v1/components/connection-test-styles.tsx b/features/admin.connections.v1/components/connection-test-styles.tsx new file mode 100644 index 00000000000..1b09e3c7fa1 --- /dev/null +++ b/features/admin.connections.v1/components/connection-test-styles.tsx @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Box from "@oxygen-ui/react/Box"; +import Card from "@oxygen-ui/react/Card"; +import { Theme, styled } from "@mui/material/styles"; + +export const StyledCodeBlockContainer: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + background: theme.palette.background.paper, + border: `1px solid ${ theme.palette.divider }`, + borderRadius: theme.shape.borderRadius, + overflowX: "auto" +})); + +export const StyledCodeBlock: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + color: theme.palette.text.primary, + fontFamily: "monospace", + fontSize: 13, + margin: 0, + padding: theme.spacing(1.5), + whiteSpace: "pre-wrap", + wordBreak: "break-word" +})); + +export const StyledResultCard: typeof Card = styled(Card)(({ theme }: { theme: Theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(3) +})); + +export const StyledTableWrapper: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + backgroundColor: theme.palette.background.paper, + border: `1px solid ${ theme.palette.divider }`, + borderRadius: theme.shape.borderRadius, + overflow: "hidden" +})); + +export const StyledTableHeaderRow: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + backgroundColor: theme.palette.action.hover, + borderBottom: `1px solid ${ theme.palette.divider }` +})); + +export const StyledTableRow: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderBottom: `1px solid ${ theme.palette.divider }` +})); + +export const StyledHeaderCell: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + borderRight: `1px solid ${ theme.palette.divider }`, + fontSize: 15, + fontWeight: 600, + padding: theme.spacing(1.25, 1), + textAlign: "left" +})); + +export const StyledMonoCell: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + borderRight: `1px solid ${ theme.palette.divider }`, + fontFamily: "monospace", + fontSize: 13, + padding: theme.spacing(1) +})); + +export const StyledValueCell: typeof Box = styled(StyledMonoCell)(({ theme }: { theme: Theme }) => ({ + color: theme.palette.text.secondary +})); + +export const StyledDiagnosticValueBlock: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + backgroundColor: theme.palette.action.hover, + border: `1px solid ${ theme.palette.divider }`, + borderRadius: theme.shape.borderRadius, + fontFamily: "monospace", + fontSize: 12, + margin: 0, + overflowX: "auto", + padding: theme.spacing(1), + whiteSpace: "pre-wrap", + wordBreak: "break-word" +})); + +export const StyledDiagnosticsScroller: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + maxHeight: theme.spacing(65), + overflowY: "auto", + scrollbarColor: `${ theme.palette.grey[400] } ${ theme.palette.action.hover }`, + scrollbarWidth: "thin", + "&::-webkit-scrollbar": { + width: 10 + }, + "&::-webkit-scrollbar-thumb": { + backgroundColor: theme.palette.grey[400], + border: `2px solid ${ theme.palette.action.hover }`, + borderRadius: 999 + }, + "&::-webkit-scrollbar-track": { + backgroundColor: theme.palette.action.hover, + borderRadius: 999 + } +})); + +export const StyledDiagnosticAccordionRow: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + borderBottom: `1px solid ${ theme.palette.divider }` +})); + +export const StyledDiagnosticAccordionTitle: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + alignItems: "center", + cursor: "pointer", + display: "flex", + flexDirection: "row", + gap: theme.spacing(1.5), + padding: theme.spacing(1.75, 1.5) +})); + +export const StyledExpandedDetailsTable: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + borderCollapse: "collapse", + width: "100%", + "& td": { + borderTop: `1px solid ${ theme.palette.divider }`, + padding: theme.spacing(1, 1.5), + verticalAlign: "top" + } +})); + +export const StyledExpandedLabelCell: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + color: theme.palette.text.secondary, + fontFamily: "monospace", + fontSize: 12, + width: 160 +})); + +export const StyledExpandedValueCell: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ + color: theme.palette.text.primary, + fontSize: 13 +})); diff --git a/features/admin.connections.v1/configs/endpoints.ts b/features/admin.connections.v1/configs/endpoints.ts index a4484863472..7d3a1a3ee90 100644 --- a/features/admin.connections.v1/configs/endpoints.ts +++ b/features/admin.connections.v1/configs/endpoints.ts @@ -37,6 +37,8 @@ export const getConnectionResourceEndpoints = (serverHost: string): ConnectionRe localAuthenticators: `${ serverHost }/api/server/v1/configs/authenticators`, multiFactorAuthenticators: `${ serverHost }/api/server/v1/identity-governance/${ CommonAuthenticatorConstants.MFA_CONNECTOR_CATEGORY_ID - }` + }`, + debug: `${ serverHost }/api/server/v1/debug`, + debugResult: `${ serverHost }/api/server/v1/debug/:sid/result` }; }; diff --git a/features/admin.connections.v1/models/connection-test.ts b/features/admin.connections.v1/models/connection-test.ts new file mode 100644 index 00000000000..c29529598e4 --- /dev/null +++ b/features/admin.connections.v1/models/connection-test.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MappedClaimInterface } from "./connection"; + +export interface ConnectionTestStepStatusInterface { + accountLinkingStatus?: string; + authenticationStatus?: string; + claimExtractionStatus?: string; + claimMappingStatus?: string; + connectionCreation?: string; +} + +export interface ConnectionTestDiagnosticLogInterface { + details?: unknown; + errorCode?: unknown; + errorDescription?: unknown; + federatedAttribute?: unknown; + message?: string; + stage?: string; + status?: string; + timestamp?: number | string; + [ key: string ]: unknown; +} + +export interface ConnectionTestResultMetadataInterface { + accountLinkingMessage?: string; + accountLinkingStatus?: string; + authenticationStatus?: string; + claimExtractionStatus?: string; + claimMappingStatus?: string; + connectionCreation?: string; + diagnostics?: ConnectionTestDiagnosticLogInterface[]; + error_code?: string; + error_description?: string; + error_details?: unknown; + idToken?: string; + mappedClaims?: MappedClaimInterface[]; + metadata?: ConnectionTestResultMetadataInterface; + stepStatus?: ConnectionTestStepStatusInterface; + steps?: ConnectionTestStepStatusInterface; + [ key: string ]: unknown; +} + +export interface ConnectionTestResultInterface { + error?: unknown; + metadata?: ConnectionTestResultMetadataInterface; + status?: string; + [ key: string ]: unknown; +} diff --git a/features/admin.connections.v1/models/connection.ts b/features/admin.connections.v1/models/connection.ts index 23b583f5839..2ed863f1e35 100644 --- a/features/admin.connections.v1/models/connection.ts +++ b/features/admin.connections.v1/models/connection.ts @@ -118,6 +118,14 @@ export interface ConnectionCommonClaimMappingInterface { claim: ConnectionClaimInterface; } +export interface MappedClaimInterface { + idpClaim?: string; + isClaim?: string; + localClaim?: string; + status?: string; + value?: unknown; +} + /** * Captures the connection claim details. */ diff --git a/features/admin.connections.v1/models/endpoints.ts b/features/admin.connections.v1/models/endpoints.ts index 39740a60a0e..a7cf33265a2 100644 --- a/features/admin.connections.v1/models/endpoints.ts +++ b/features/admin.connections.v1/models/endpoints.ts @@ -28,4 +28,6 @@ export interface ConnectionResourceEndpointsInterface { identityProviders: string; localAuthenticators: string; multiFactorAuthenticators: string; + debug: string; + debugResult: string; } diff --git a/features/admin.connections.v1/pages/connection-edit.tsx b/features/admin.connections.v1/pages/connection-edit.tsx index 9746ed671b1..8569cb22f92 100644 --- a/features/admin.connections.v1/pages/connection-edit.tsx +++ b/features/admin.connections.v1/pages/connection-edit.tsx @@ -20,6 +20,7 @@ import { FeatureAccessConfigInterface, useRequiredScopes } from "@wso2is/access- import { ApplicationTemplateConstants } from "@wso2is/admin.application-templates.v1/constants/templates"; import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants"; import { history } from "@wso2is/admin.core.v1/helpers/history"; +import useResourceEndpoints from "@wso2is/admin.core.v1/hooks/use-resource-endpoints"; import useUIConfig from "@wso2is/admin.core.v1/hooks/use-ui-configs"; import { FeatureConfigInterface } from "@wso2is/admin.core.v1/models/config"; import { AppState } from "@wso2is/admin.core.v1/store"; @@ -40,10 +41,11 @@ import { DocumentationLink, LabelWithPopup, Popup, + SecondaryButton, TabPageLayout, useDocumentation } from "@wso2is/react-components"; -import { AxiosError } from "axios"; +import { AxiosError, AxiosResponse } from "axios"; import get from "lodash-es/get"; import orderBy from "lodash-es/orderBy"; import React, { Fragment, FunctionComponent, ReactElement, ReactNode, useEffect, useRef, useState } from "react"; @@ -51,8 +53,13 @@ import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { RouteComponentProps } from "react-router"; import { Dispatch } from "redux"; -import { Label } from "semantic-ui-react"; +import { Icon, Label } from "semantic-ui-react"; import { getLocalAuthenticator, getMultiFactorAuthenticatorDetails } from "../api/authenticators"; +import { + ConnectionTestSessionResponseInterface, + resolveConnectionTestErrorMessage, + startConnectionTestSession +} from "../api/connection-test-api"; import { getConnectionDetails, getConnectionMetaData, getConnectionTemplates } from "../api/connections"; import { EditConnection } from "../components/edit/connection-edit"; import { EditMultiFactorAuthenticator } from "../components/edit/edit-multi-factor-authenticator"; @@ -92,6 +99,7 @@ const ConnectionEditPage: FunctionComponent = const { t } = useTranslation(); const { UIConfig } = useUIConfig(); + const { resourceEndpoints } = useResourceEndpoints(); const setConnectionTemplates: (templates: Record[]) => void = useSetConnectionTemplates(); const idpDescElement: React.MutableRefObject = useRef(null); @@ -119,6 +127,7 @@ const ConnectionEditPage: FunctionComponent = const [ isConnectorMetaDataFetchRequestLoading, setConnectorMetaDataFetchRequestLoading ] = useState( undefined ); + const [ isTestingConnection, setIsTestingConnection ] = useState(false); const isReadOnly: boolean = !useRequiredScopes(featureConfig?.identityProviders?.scopes?.update); const hasApplicationTemplateViewPermissions: boolean = useRequiredScopes(applicationsFeatureConfig?.scopes?.read); @@ -686,6 +695,66 @@ const ConnectionEditPage: FunctionComponent = ); }; + /** + * Handles the test connection button click event. + * Starts a debug session and redirects to the test page with the debug data. + */ + const handleTestConnection = async (): Promise => { + if (!connector?.id) { + dispatch( + addAlert({ + description: t("authenticationProvider:notifications.getIDP.genericError.description"), + level: AlertLevels.ERROR, + message: t("authenticationProvider:notifications.getIDP.error.message") + }) + ); + + return; + } + + setIsTestingConnection(true); + const authPopup: Window | null = window.open("", "_blank"); + + try { + const response: AxiosResponse = + await startConnectionTestSession(resourceEndpoints, connector.id); + + const authorizationUrl: string | undefined = response.data?.metadata?.authorizationUrl; + const debugId: string | undefined = response.data?.debugId; + + if (debugId && authorizationUrl) { + authPopup?.location.assign(authorizationUrl); + history.push({ + pathname: AppConstants.getPaths().get("IDP_TEST").replace(":id", connector.id), + state: { + debugId + } + }); + } else { + authPopup?.close(); + dispatch( + addAlert({ + description: t("authenticationProvider:notifications.getIDP.genericError.description"), + level: AlertLevels.ERROR, + message: t("authenticationProvider:notifications.getIDP.error.message") + }) + ); + } + } catch (error: unknown) { + authPopup?.close(); + dispatch( + addAlert({ + description: resolveConnectionTestErrorMessage(error) + ?? t("authenticationProvider:notifications.getIDP.genericError.description"), + level: AlertLevels.ERROR, + message: t("authenticationProvider:notifications.getIDP.genericError.message") + }) + ); + } finally { + setIsTestingConnection(false); + } + }; + return ( = imageType: "square" } } title={ resolveConnectorName(connector) } + action={ ConnectionsManagementUtils.isConnectorIdentityProvider(connector) ? ( + + + { t("authenticationProvider:connectionTest.button") } + + ) : null } contentTopMargin={ true } description={ resolveConnectorDescription(connector) } image={ resolveConnectorImage(connector) } diff --git a/features/admin.connections.v1/pages/connection-test.tsx b/features/admin.connections.v1/pages/connection-test.tsx new file mode 100644 index 00000000000..d1064300d89 --- /dev/null +++ b/features/admin.connections.v1/pages/connection-test.tsx @@ -0,0 +1,1020 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Alert from "@oxygen-ui/react/Alert"; +import AlertTitle from "@oxygen-ui/react/AlertTitle"; +import Box from "@oxygen-ui/react/Box"; +import Button from "@oxygen-ui/react/Button"; +import Card from "@oxygen-ui/react/Card"; +import CircularProgress from "@oxygen-ui/react/CircularProgress"; +import Skeleton from "@oxygen-ui/react/Skeleton"; +import Stack from "@oxygen-ui/react/Stack"; +import Tab from "@oxygen-ui/react/Tab"; +import TabPanel from "@oxygen-ui/react/TabPanel"; +import Tabs from "@oxygen-ui/react/Tabs"; +import Typography from "@oxygen-ui/react/Typography"; +import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants"; +import { history } from "@wso2is/admin.core.v1/helpers/history"; +import useResourceEndpoints from "@wso2is/admin.core.v1/hooks/use-resource-endpoints"; +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import { SessionStorageUtils } from "@wso2is/core/utils"; +import { TabPageLayout } from "@wso2is/react-components"; +import { AxiosResponse } from "axios"; +import React, { FunctionComponent, ReactElement, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { RouteComponentProps } from "react-router-dom"; +import { Accordion, Icon } from "semantic-ui-react"; +import { + ConnectionTestSessionResponseInterface, + getConnectionTestResult, + resolveConnectionTestErrorMessage, + startConnectionTestSession +} from "../api/connection-test-api"; +import { + StyledCodeBlock, + StyledCodeBlockContainer, + StyledDiagnosticAccordionRow, + StyledDiagnosticAccordionTitle, + StyledDiagnosticsScroller, + StyledDiagnosticValueBlock, + StyledExpandedDetailsTable, + StyledExpandedLabelCell, + StyledExpandedValueCell, + StyledHeaderCell, + StyledMonoCell, + StyledResultCard, + StyledTableHeaderRow, + StyledTableRow, + StyledTableWrapper, + StyledValueCell +} from "../components/connection-test-styles"; +import { MappedClaimInterface } from "../models/connection"; +import { + ConnectionTestDiagnosticLogInterface, + ConnectionTestResultInterface, + ConnectionTestResultMetadataInterface, + ConnectionTestStepStatusInterface +} from "../models/connection-test"; + +const POLL_INTERVAL_MS: number = 2000; +const POLL_MAX_ATTEMPTS: number = 30; +const DEBUG_STATUS_INCOMPLETE: string = "SUCCESS_INCOMPLETE"; +const TEST_ID: string = "idp-test-connection"; + +interface RouteParams { + tenantDomain?: string; + id?: string; +} + +interface ConnectionTestLocationStateInterface { + debugId?: string; +} + +interface ConnectionTestPagePropsInterface extends IdentifiableComponentInterface, RouteComponentProps {} + +const decodeJWT = ( + token?: string +): { header: unknown; payload: unknown; signature: string } | null => { + if (!token || typeof token !== "string" || token.split(".").length < 3) { + return null; + } + + const [ header, payload, signature ] = token.split("."); + const decode = (value: string): unknown => { + try { + let base64: string = value.replace(/-/g, "+").replace(/_/g, "/"); + + while (base64.length % 4) { + base64 += "="; + } + + return JSON.parse(atob(base64)); + } catch { + return null; + } + }; + + return { + header: decode(header), + payload: decode(payload), + signature + }; +}; + +const formatClaimValue = (value: unknown): string => { + if (value === null || value === undefined) { + return "-"; + } + if (typeof value === "object") { + return JSON.stringify(value); + } + + return String(value); +}; + +const getDiagnosticStatusIcon = (status?: string): ReactElement => { + let iconName: "check circle" | "times circle"; + let iconColor: "green" | "yellow" | "red"; + + switch (String(status || "").toUpperCase()) { + case "SUCCESS": + iconName = "check circle"; + iconColor = "green"; + break; + case "PARTIAL": + iconName = "check circle"; + iconColor = "yellow"; + break; + case "FAILED": + case "ERROR": + iconName = "times circle"; + iconColor = "red"; + break; + default: + return <>; + } + + return ( + + + + ); +}; + +const renderDiagnosticLogValue = (value: unknown): string | ReactElement => { + if (value === null || value === undefined || value === "") { + return "-"; + } + + if (typeof value === "object") { + return ( + + { JSON.stringify(value, null, 2) } + + ); + } + + return String(value); +}; + +const getExpandedDiagnosticRows = ( + log: ConnectionTestDiagnosticLogInterface +): Array<{ label: string; value: unknown }> => { + const rows: Array<{ label: string; value: unknown }> = [ + { label: "recordedAt", value: log.timestamp ? new Date(log.timestamp).toLocaleString() : null }, + { label: "resultStatus", value: log.status }, + { label: "details", value: log.details }, + { label: "errorCode", value: log.errorCode }, + { label: "federatedAttribute", value: log.federatedAttribute }, + { label: "errorDescription", value: log.errorDescription } + ]; + + return rows.filter( + (row: { label: string; value: unknown }) => + row.value !== undefined && row.value !== null && row.value !== "" + ); +}; + +/** + * Connection Test page component. + * + * @param props - Props injected to the component. + * @returns React element. + */ +const ConnectionTestPage: FunctionComponent = ( + props: ConnectionTestPagePropsInterface +): ReactElement => { + const { id: connectorId = "" } = props.match.params || {}; + const { location } = props; + const resultCacheKey: string = `idp-test-result:${ connectorId }`; + const locationState: ConnectionTestLocationStateInterface | undefined = + location?.state as ConnectionTestLocationStateInterface | undefined; + + const getCachedResult = (): ConnectionTestResultInterface | null => { + if (!connectorId) { + return null; + } + + const cachedResult: string = SessionStorageUtils.getItemFromSessionStorage(resultCacheKey); + + if (!cachedResult) { + return null; + } + + try { + return JSON.parse(cachedResult); + } catch { + SessionStorageUtils.clearItemFromSessionStorage(resultCacheKey); + + return null; + } + }; + + const { t } = useTranslation(); + const { resourceEndpoints } = useResourceEndpoints(); + + const [ debugId, setDebugId ] = useState(null); + const [ result, setResult ] = useState(() => + locationState?.debugId ? null : getCachedResult() + ); + const [ error, setError ] = useState(null); + const [ loading, setLoading ] = useState(false); + const [ hasError, setHasError ] = useState(false); + const [ hasPartial, setHasPartial ] = useState(false); + const [ activeTab, setActiveTab ] = useState(0); + const [ autoRunTriggered, setAutoRunTriggered ] = useState(false); + const [ isStatusBannerVisible, setIsStatusBannerVisible ] = useState(true); + const [ expandedDiagnosticLogs, setExpandedDiagnosticLogs ] = useState([]); + + const popupInterval: React.MutableRefObject = useRef(null); + const fetchTimer: React.MutableRefObject = useRef(null); + + const clearCachedResult = (): void => { + if (!connectorId) { + return; + } + + SessionStorageUtils.clearItemFromSessionStorage(resultCacheKey); + }; + + const cacheResult = (value: ConnectionTestResultInterface): void => { + if (!connectorId) { + return; + } + + SessionStorageUtils.setItemToSessionStorage(resultCacheKey, JSON.stringify(value)); + }; + + const clearTimers = (): void => { + if (popupInterval.current) { + clearInterval(popupInterval.current); + popupInterval.current = null; + } + if (fetchTimer.current) { + clearTimeout(fetchTimer.current); + fetchTimer.current = null; + } + }; + + useEffect(() => { + return () => clearTimers(); + }, []); + + useEffect(() => { + if (locationState?.debugId && !autoRunTriggered) { + setResult(null); + setError(null); + setHasError(false); + setHasPartial(false); + setIsStatusBannerVisible(true); + setExpandedDiagnosticLogs([]); + clearCachedResult(); + + setAutoRunTriggered(true); + setDebugId(locationState.debugId); + setLoading(true); + + fetchTimer.current = setTimeout(() => { + pollForResult(locationState.debugId); + }, POLL_INTERVAL_MS); + } + }, [ locationState, autoRunTriggered ]); + + const fetchResult = async (sid: string): Promise => { + if (!sid) { + setError(t("authenticationProvider:notifications.getIDP.genericError.description")); + + return; + } + + setLoading(true); + setError(null); + setResult(null); + setExpandedDiagnosticLogs([]); + + try { + const response: AxiosResponse = + await getConnectionTestResult(resourceEndpoints, sid); + + clearCachedResult(); + setResult(response.data); + setIsStatusBannerVisible(true); + cacheResult(response.data); + } catch (fetchError) { + setError( + resolveConnectionTestErrorMessage(fetchError) ?? + t("authenticationProvider:notifications.getIDP.genericError.description") + ); + } finally { + setLoading(false); + } + }; + + /** + * Polls the backend for test results, retrying while the session is still in progress. + * Used for the auto-run path where the popup was opened by a previous page and + * we have no direct reference to detect when it closes. + */ + const pollForResult = async (sid: string, attempt: number = 0): Promise => { + if (!sid) { + setError(t("authenticationProvider:notifications.getIDP.genericError.description")); + setLoading(false); + + return; + } + + try { + const response: AxiosResponse = + await getConnectionTestResult(resourceEndpoints, sid); + + if (response.data?.status === DEBUG_STATUS_INCOMPLETE) { + if (attempt < POLL_MAX_ATTEMPTS) { + fetchTimer.current = setTimeout(() => { + pollForResult(sid, attempt + 1); + }, POLL_INTERVAL_MS); + } else { + clearCachedResult(); + setError(t("authenticationProvider:notifications.getIDP.genericError.description")); + setIsStatusBannerVisible(true); + setLoading(false); + } + + return; + } + + clearCachedResult(); + setResult(response.data); + setIsStatusBannerVisible(true); + cacheResult(response.data); + setExpandedDiagnosticLogs([]); + setLoading(false); + } catch (fetchError) { + setError( + resolveConnectionTestErrorMessage(fetchError) ?? + t("authenticationProvider:notifications.getIDP.genericError.description") + ); + setLoading(false); + } + }; + + const handleRunTests = async (): Promise => { + clearTimers(); + setLoading(true); + setError(null); + setResult(null); + setHasError(false); + setHasPartial(false); + setIsStatusBannerVisible(true); + setExpandedDiagnosticLogs([]); + clearCachedResult(); + + if (!connectorId) { + setLoading(false); + setError(t("authenticationProvider:notifications.getIDP.genericError.description")); + + return; + } + + try { + const response: AxiosResponse = + await startConnectionTestSession(resourceEndpoints, connectorId); + + const authorizationUrl: string | undefined = response.data?.metadata?.authorizationUrl; + const newDebugId: string | undefined = response.data?.debugId; + + if (!newDebugId) { + setLoading(false); + setError(t("authenticationProvider:notifications.getIDP.genericError.description")); + + return; + } + + setDebugId(newDebugId); + + if (authorizationUrl) { + const authPopup: Window | null = window.open(authorizationUrl, "_blank"); + + popupInterval.current = setInterval(() => { + try { + if (authPopup && authPopup.closed) { + clearTimers(); + fetchTimer.current = setTimeout(() => { + fetchResult(newDebugId); + }, 1000); + } + } catch { + // ignore cross-origin access errors + } + }, 500); + + // Fallback: fetch after 30s if popup close detection fails + fetchTimer.current = setTimeout(() => { + clearTimers(); + fetchResult(newDebugId); + }, 30000); + } else { + fetchTimer.current = setTimeout(() => { + fetchResult(newDebugId); + }, POLL_INTERVAL_MS); + } + } catch (runError) { + clearTimers(); + setLoading(false); + setError( + resolveConnectionTestErrorMessage(runError) ?? + t("authenticationProvider:notifications.getIDP.genericError.description") + ); + } + }; + + useEffect(() => { + if (!result) { + return; + } + + const metadataObj: ConnectionTestResultMetadataInterface = + result?.metadata?.metadata || result?.metadata || {}; + const stepStatus: ConnectionTestStepStatusInterface = + metadataObj?.stepStatus || metadataObj?.steps || {}; + + const steps: Record = { + accountLinkingStatus: stepStatus?.accountLinkingStatus || metadataObj?.accountLinkingStatus, + authenticationStatus: stepStatus?.authenticationStatus || metadataObj?.authenticationStatus, + claimExtractionStatus: stepStatus?.claimExtractionStatus || metadataObj?.claimExtractionStatus, + claimMappingStatus: stepStatus?.claimMappingStatus || metadataObj?.claimMappingStatus, + connectionCreation: stepStatus?.connectionCreation || metadataObj?.connectionCreation + }; + + const topLevelFailure: boolean = result?.status === "FAILURE"; + + const hasStepError: boolean = Boolean( + (steps.connectionCreation && + steps.connectionCreation !== "success" && + steps.connectionCreation !== "pending") || + (steps.authenticationStatus && + steps.authenticationStatus !== "success" && + steps.authenticationStatus !== "pending") || + (steps.claimMappingStatus && + steps.claimMappingStatus !== "success" && + steps.claimMappingStatus !== "pending" && + steps.claimMappingStatus !== "partial") || + (steps.accountLinkingStatus && + steps.accountLinkingStatus !== "success" && + steps.accountLinkingStatus !== "pending") || + result?.error + ); + + const hasPartialStatus: boolean = steps.claimMappingStatus === "partial"; + + const hasErrorFields: boolean = Boolean( + metadataObj?.error_details || metadataObj?.error_description || metadataObj?.error_code + ); + + if (topLevelFailure || hasStepError || hasErrorFields) { + setHasError(true); + setHasPartial(false); + setActiveTab(2); + } else if (hasPartialStatus) { + setHasError(false); + setHasPartial(true); + setActiveTab(2); + } else { + setHasError(false); + setHasPartial(false); + } + }, [ result ]); + + const handleBackButtonClick = (): void => { + history.push(AppConstants.getPaths().get("IDP_EDIT").replace(":id", connectorId)); + }; + + const renderCodeBlock = ( + value: unknown, + options: { + color?: string; + marginBottom?: number; + wordBreak?: "break-all" | "break-word"; + } = {} + ): ReactElement => ( + + + { typeof value === "string" ? value : JSON.stringify(value, null, 2) } + + + ); + + const toggleDiagnosticLog = (index: number): void => { + setExpandedDiagnosticLogs((previous: number[]) => + previous.includes(index) + ? previous.filter((item: number) => item !== index) + : [ ...previous, index ] + ); + }; + + const renderResultsTabs = (): ReactElement => { + const tabPanes: Array<{ key: string; menuItem: string; render: () => ReactElement }> = [ + { + key: "id-token", + menuItem: t("authenticationProvider:connectionTest.tabs.idToken"), + render: (): ReactElement => { + const idToken: string | undefined = result?.metadata?.idToken; + const decoded: { header: unknown; payload: unknown; signature: string } | null = + decodeJWT(idToken); + + return ( + + + { decoded ? ( + + + { t("authenticationProvider:connectionTest.idToken.header") } + + { renderCodeBlock(decoded.header, { marginBottom: 2 }) } + + + { t("authenticationProvider:connectionTest.idToken.payload") } + + { renderCodeBlock(decoded.payload, { marginBottom: 2 }) } + + + { t("authenticationProvider:connectionTest.idToken.signature") } + + { renderCodeBlock(decoded.signature, { wordBreak: "break-all" }) } + + ) : ( + + { t("authenticationProvider:connectionTest.idToken.decodeError") } + + ) } + + + ); + } + }, + { + key: "claim-mappings", + menuItem: t("authenticationProvider:connectionTest.tabs.claimMappings"), + render: (): ReactElement => { + const claimsArray: MappedClaimInterface[] = Array.isArray(result?.metadata?.mappedClaims) + ? (result?.metadata?.mappedClaims as MappedClaimInterface[]) + : []; + const sortedClaims: MappedClaimInterface[] = [ ...claimsArray ].sort( + (a: MappedClaimInterface, b: MappedClaimInterface) => { + if (a.status === "Successful" && b.status !== "Successful") { + return -1; + } + if (a.status !== "Successful" && b.status === "Successful") { + return 1; + } + + return 0; + } + ); + + return ( + + + + + + + + { t( + "authenticationProvider:connectionTest.claimMappings.localClaimColumn" + ) } + + + { t( + "authenticationProvider:connectionTest.claimMappings.idpClaimColumn" + ) } + + + { t( + "authenticationProvider:connectionTest.claimMappings.valueColumn" + ) } + + + + + { sortedClaims.length === 0 && ( + + + { t( + "authenticationProvider:connectionTest.claimMappings.empty" + ) } + + + ) } + { sortedClaims.map((claim: MappedClaimInterface, idx: number) => ( + + + { claim.localClaim || claim.isClaim || "-" } + + + { claim.idpClaim } + + + { formatClaimValue(claim.value) } + + + )) } + + + + + + ); + } + }, + { + key: "diagnosis", + menuItem: t("authenticationProvider:connectionTest.tabs.diagnosis"), + render: (): ReactElement => { + const metadataObj: ConnectionTestResultMetadataInterface = result?.metadata || {}; + const stepStatus: ConnectionTestStepStatusInterface = metadataObj?.stepStatus || {}; + const allDiagnostics: ConnectionTestDiagnosticLogInterface[] = Array.isArray( + metadataObj?.diagnostics + ) + ? metadataObj.diagnostics + : []; + // Skip 'started' entries and claim-validation noise from diagnostics list + const diagnostics: ConnectionTestDiagnosticLogInterface[] = allDiagnostics.filter( + (log: ConnectionTestDiagnosticLogInterface) => + !(log.status === "started" || log.stage === "claimValidation") + ); + const steps: Record = { + accountLinkingStatus: + stepStatus?.accountLinkingStatus || metadataObj?.accountLinkingStatus, + authenticationStatus: + stepStatus?.authenticationStatus || metadataObj?.authenticationStatus, + claimExtractionStatus: + stepStatus?.claimExtractionStatus || metadataObj?.claimExtractionStatus, + claimMappingStatus: stepStatus?.claimMappingStatus || metadataObj?.claimMappingStatus, + connectionCreation: stepStatus?.connectionCreation || metadataObj?.connectionCreation + }; + const errorDescription: string | null = metadataObj?.error_description || null; + const errorCode: string | null = metadataObj?.error_code || null; + const topLevelStatus: string | undefined = result?.status; + + return ( + + + { !errorDescription && + !errorCode && + Object.values(steps).every((v: string | undefined) => !v) && + diagnostics.length === 0 && + topLevelStatus !== "FAILURE" && ( + + { t("authenticationProvider:connectionTest.diagnosis.noLogs") } + + ) } + + { diagnostics.length > 0 && ( + + + + { diagnostics.map( + ( + log: ConnectionTestDiagnosticLogInterface, + idx: number + ) => { + const timestamp: string = log.timestamp + ? new Date(log.timestamp).toLocaleString() + : t( + "authenticationProvider:connectionTest.diagnosis.timestampUnavailable" + ); + const isExpanded: boolean = + expandedDiagnosticLogs.includes(idx); + + return ( + + toggleDiagnosticLog(idx) } + style={ { padding: 0 } } + > + + + + + + { getDiagnosticStatusIcon( + log.status + ) } + + { log.message || + t( + "authenticationProvider:connectionTest.diagnosis.noMessage" + ) } + + + + { timestamp } + + + + + + + + { getExpandedDiagnosticRows( + log + ).map( + (row: { + label: string; + value: unknown; + }) => ( + + + { row.label }: + + + { renderDiagnosticLogValue( + row.value + ) } + + + ) + ) } + + + + + + ); + } + ) } + + + + ) } + + + ); + } + } + ]; + + return ( + + setActiveTab(value) } + > + { tabPanes.map((tabPane: { key: string; menuItem: string }) => ( + + )) } + + { tabPanes.map( + ( + tabPane: { key: string; menuItem: string; render: () => ReactElement }, + index: number + ) => ( + + { tabPane.render() } + + ) + ) } + + ); + }; + + return ( + + { t("authenticationProvider:connectionTest.rerunButton") } + + } + titleTextAlign="left" + contentTopMargin={ true } + bottomMargin={ false } + data-componentid={ `${ TEST_ID }-page-layout` } + > + { result && !error && isStatusBannerVisible && ( + + setIsStatusBannerVisible(false) } + > + + { hasError + ? t("authenticationProvider:connectionTest.status.failed") + : hasPartial + ? t("authenticationProvider:connectionTest.status.partial") + : t("authenticationProvider:connectionTest.status.passed") } + + { hasError + ? t("authenticationProvider:connectionTest.status.failedDescription") + : hasPartial + ? t("authenticationProvider:connectionTest.status.partialDescription") + : t("authenticationProvider:connectionTest.status.passedDescription") } + + + ) } + + { error && ( + + + { t("authenticationProvider:connectionTest.error.title") } + { error } + + + + + + ) } + + { !error && result && ( + + { renderResultsTabs() } + + ) } + + { !error && !result && (debugId || loading) && ( + + + + + { loading + ? t("authenticationProvider:connectionTest.loading.results") + : t("authenticationProvider:connectionTest.loading.waitingForAuth") } + + + + + + + + + + + ) } + + ); +}; + +/** + * A default export was added to support React.lazy. + * TODO: Change this to a named export once react starts supporting named exports for code splitting. + * @see {@link https://reactjs.org/docs/code-splitting.html#reactlazy} + */ +export default ConnectionTestPage; diff --git a/features/admin.consents.v1/components/consent-description-editor.tsx b/features/admin.consents.v1/components/consent-description-editor.tsx index f63c2a213e4..22f68d17e9f 100644 --- a/features/admin.consents.v1/components/consent-description-editor.tsx +++ b/features/admin.consents.v1/components/consent-description-editor.tsx @@ -404,16 +404,16 @@ const ConsentEditorToolbar: FunctionComponent string = (): string => { if (!policyUrl) { - return t("consents:wizard.create.form.description.insertPolicyLinkNoPolicyUrl"); + return t("consents:policyConsents.wizard.create.form.description.insertPolicyLinkNoPolicyUrl"); } if (!isValidPolicyUrl) { - return t("consents:wizard.create.form.description.insertPolicyLinkInvalidUrl"); + return t("consents:policyConsents.wizard.create.form.description.insertPolicyLinkInvalidUrl"); } if (!hasSelection) { - return t("consents:wizard.create.form.description.insertPolicyLinkNoSelection"); + return t("consents:policyConsents.wizard.create.form.description.insertPolicyLinkNoSelection"); } - return t("consents:wizard.create.form.description.insertPolicyLinkTooltip"); + return t("consents:policyConsents.wizard.create.form.description.insertPolicyLinkTooltip"); }; return ( @@ -480,10 +480,10 @@ const ConsentEditorToolbar: FunctionComponent - { t("consents:wizard.create.form.description.insertPolicyLinkShort") } + { t("consents:policyConsents.wizard.create.form.description.insertPolicyLinkShort") } @@ -569,6 +569,10 @@ interface ConsentDescriptionEditorProps extends IdentifiableComponentInterface { * Called when the admin links or clears an i18n key via the translation configuration card. */ onI18nKeyChange?: (key: string | null) => void; + /** + * Controls which i18n hint text to show below the editor. Defaults to "policy". + */ + variant?: "policy" | "preference"; } /** @@ -584,16 +588,45 @@ export const ConsentDescriptionEditor: FunctionComponent { const { t } = useTranslation(); const [ isI18nCardOpen, setIsI18nCardOpen ] = useState(false); const i18nButtonRef: RefObject = useRef(null); const exampleLinkText: string = - policyName || policyUrl || t("consents:form.name.placeholder"); + policyName || policyUrl || t("consents:policyConsents.form.name.placeholder"); const isValidPolicyUrl: boolean = !!policyUrl && URLUtils.isHttpsOrHttpUrl(policyUrl); + const preferencePlaceholderI18nKey: string = "consents:preferenceManagement.preview.exampleDescription"; + const policyPlaceholderI18nKey: string = "consents:policyConsents.wizard.create.preview.exampleDescription"; + + const placeholderAriaText: string = variant === "preference" + ? t(preferencePlaceholderI18nKey, { consentName: policyName }).replace(/<[^>]*>/g, "") + : t(policyPlaceholderI18nKey, { policyName: exampleLinkText }).replace(/<[^>]*>/g, ""); + + const placeholderContent: ReactElement = variant === "preference" ? ( + + { t(preferencePlaceholderI18nKey, { consentName: policyName }).replace(/<[^>]*>/g, "") } + + ) : ( + + + ] } + /> + + ); + return ( @@ -608,26 +641,8 @@ export const ConsentDescriptionEditor: FunctionComponent]*>/g, "") } - placeholder={ i18nKey ? <> : ( - - - ] } - /> - - ) } + aria-placeholder={ placeholderAriaText } + placeholder={ i18nKey ? <> : placeholderContent } /> ) } ErrorBoundary={ LexicalErrorBoundary } @@ -644,25 +659,29 @@ export const ConsentDescriptionEditor: FunctionComponent ) } - - setIsI18nCardOpen(!isI18nCardOpen) } - aria-label={ t("consents:wizard.create.form.description.configureTranslation") } - aria-pressed={ isI18nCardOpen } + { variant !== "preference" && ( + - - - + setIsI18nCardOpen(!isI18nCardOpen) } + aria-label={ + t("consents:policyConsents.wizard.create.form.description.configureTranslation") + } + aria-pressed={ isI18nCardOpen } + > + + + + ) } - { isI18nCardOpen && ( + { variant !== "preference" && isI18nCardOpen && ( ) } - { t("consents:wizard.create.form.description.labelRoleHint") } + { t(variant === "preference" + ? "consents:preferenceManagement.form.description.labelRoleHint" + : "consents:policyConsents.wizard.create.form.description.labelRoleHint") + } ); diff --git a/features/admin.consents.v1/components/consent-description-preview.tsx b/features/admin.consents.v1/components/consent-description-preview.tsx deleted file mode 100644 index c56711b401b..00000000000 --- a/features/admin.consents.v1/components/consent-description-preview.tsx +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Theme, styled } from "@mui/material/styles"; -import Box from "@oxygen-ui/react/Box"; -import Button from "@oxygen-ui/react/Button"; -import Checkbox from "@oxygen-ui/react/Checkbox"; -import FormLabel from "@oxygen-ui/react/FormLabel"; -import Typography from "@oxygen-ui/react/Typography"; -import { sanitizedHtml } from "@wso2is/common.branding.v1/utils/sanitized-html"; -import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import parse from "html-react-parser"; -import React, { FunctionComponent, ReactElement, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; - -const PreviewContainer: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ - alignItems: "center", - background: theme.palette.grey[200], - display: "flex", - flexDirection: "column", - height: "100%", - justifyContent: "center", - padding: theme.spacing(2.5, 1.5), - width: "100%" -})); - -const PREVIEW_CARD_MAX_WIDTH: string = "400px"; - -const PreviewCard: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ - background: theme.palette.background.paper, - borderRadius: theme.shape.borderRadius * 2, - boxShadow: theme.shadows[4], - maxWidth: PREVIEW_CARD_MAX_WIDTH, - padding: theme.spacing(4, 3.5, 3.5), - width: "100%" -})); - -const PreviewTitle: typeof Typography = styled(Typography)(({ theme }: { theme: Theme }) => ({ - fontSize: theme.typography.h4.fontSize, - fontWeight: 700, - lineHeight: 1.2, - marginBottom: theme.spacing(3.5), - textAlign: "center" -})); - -const PreviewHeader: typeof Typography = styled(Typography)(({ theme }: { theme: Theme }) => ({ - color: theme.palette.text.secondary, - fontSize: theme.typography.body2.fontSize, - lineHeight: 1.5, - marginBottom: theme.spacing(2.25) -})); - -const CheckboxRow: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ - alignItems: "center", - display: "flex", - gap: theme.spacing(0.5), - marginBottom: theme.spacing(3) -})); - -const RichTextDescription: typeof Box = styled(Box)(({ theme }: { theme: Theme }) => ({ - "& .rich-text-align-center": { - textAlign: "center" - }, - "& .rich-text-align-justify": { - textAlign: "justify" - }, - "& .rich-text-align-left": { - textAlign: "left" - }, - "& .rich-text-align-right": { - textAlign: "right" - }, - "& .rich-text-bold": { - fontWeight: "bold" - }, - "& .rich-text-italic": { - fontStyle: "italic" - }, - "& .rich-text-pre-wrap": { - whiteSpace: "pre-wrap" - }, - "& .rich-text-underline": { - textDecoration: "underline" - }, - "& a.rich-text-link": { - color: theme.palette.primary.main, - textDecoration: "underline" - }, - fontSize: theme.typography.body2.fontSize, - lineHeight: 1.5 -})); - -const PlaceholderText: typeof Typography = styled(Typography)(({ theme }: { theme: Theme }) => ({ - color: theme.palette.text.disabled, - fontSize: theme.typography.body2.fontSize, - fontStyle: "italic" -})); - - -/** - * Props interface for the ConsentDescriptionPreview component. - */ -interface ConsentDescriptionPreviewPropsInterface extends IdentifiableComponentInterface { - /** - * HTML description string rendered as the checkbox label. - */ - description: string; - /** - * When true, indicates this is a mandatory policy consent. - */ - mandatory?: boolean; - /** - * Optional policy name used as fallback text when description is empty. - */ - policyName?: string; -} - -/** - * Preview panel mimicking the login-flow consent page. - * - * Shows a grey background, a centred white card with a bold title, a dashed form area with - * the consent header and checkbox, and a pill-shaped gradient Continue button. - * - * A "How it maps" legend sits below the card to make the relationship between - * the Name / Description fields and the rendered checkbox label explicit without - * requiring the user to discover it through trial-and-error. - */ -export const ConsentDescriptionPreview: FunctionComponent = ({ - "data-componentid": componentId = "consent-description-preview", - description, - mandatory = false, - policyName -}: ConsentDescriptionPreviewPropsInterface): ReactElement => { - const { t } = useTranslation(); - - const [ isChecked, setIsChecked ] = useState(false); - - const i18nKeyMatch: RegExpMatchArray | null = description?.match(/^\{\{(.+)\}\}$/); - const resolvedDescription: string = i18nKeyMatch ? i18nKeyMatch[1] : description; - const sanitized: string = i18nKeyMatch ? "" : sanitizedHtml(description); - - return ( - - - - { t("consents:wizard.create.preview.pageTitle") } - - - - { t("consents:wizard.create.preview.consentHeader") } - - - - ): void => - setIsChecked(e.target.checked) - } - size="small" - sx={ { padding: 0 } } - /> - - - { sanitized ? ( - <> - { parse(sanitized) } - { mandatory && ( - - ) } - - ) : i18nKeyMatch ? ( - <> - { resolvedDescription } - { mandatory && ( - - ) } - - ) : policyName ? ( - <> - - ] } - /> - { mandatory && ( - - ) } - - ) : ( - - { t("consents:wizard.create.preview.emptyDescription") } - - ) } - - - - - - - - - - ); -}; diff --git a/features/admin.consents.v1/components/consent-i18n-configuration-card.tsx b/features/admin.consents.v1/components/consent-i18n-configuration-card.tsx index d6ff8f00a21..b8e3aee8a07 100644 --- a/features/admin.consents.v1/components/consent-i18n-configuration-card.tsx +++ b/features/admin.consents.v1/components/consent-i18n-configuration-card.tsx @@ -323,9 +323,9 @@ const ConsentI18nConfigurationCard: FunctionComponent
- { t("consents:wizard.create.form.description.i18nCard.i18nKey") } + { t("consents:policyConsents.wizard.create.form.description.i18nCard.i18nKey") }
- { t("consents:wizard.create.form.description.i18nCard.i18nKey") } + { t("consents:policyConsents.wizard.create.form.description.i18nCard.i18nKey") } { isCreationMode.current ? ( ) => { @@ -443,7 +443,7 @@ const ConsentI18nConfigurationCard: FunctionComponent - { t("consents:wizard.create.form.description.i18nCard.language") } + { t("consents:policyConsents.wizard.create.form.description.i18nCard.language") } "/> + value="<%=AuthenticationEndpointUtil.i18n(resourceBundle, "continue")%>"/>
" + value="<%=AuthenticationEndpointUtil.i18n(resourceBundle, "cancel")%>" onclick="javascript: deny(); return false;"/>
diff --git a/identity-apps-core/react-ui-core/src/components/adapters/policy-consent-field-adapter.js b/identity-apps-core/react-ui-core/src/components/adapters/policy-consent-field-adapter.js index 26732160064..6feb543498e 100644 --- a/identity-apps-core/react-ui-core/src/components/adapters/policy-consent-field-adapter.js +++ b/identity-apps-core/react-ui-core/src/components/adapters/policy-consent-field-adapter.js @@ -53,8 +53,11 @@ const ensureAfterSanitizeAttributesHookRegistered = () => { * @param {{ current: string | null }} consentRef - Ref holding the current serialized consent JSON. */ const reportPolicyState = (checkedMap, formStateHandler, purposeType, consentRef) => { - const accepted = Object.keys(checkedMap).filter((id) => checkedMap[id]); - const rejected = Object.keys(checkedMap).filter((id) => !checkedMap[id]); + const purposes = Object.entries(checkedMap).map(([ id, accepted ]) => ({ + accepted, + attributes: [], + id + })); let existingConsent = {}; @@ -64,13 +67,13 @@ const reportPolicyState = (checkedMap, formStateHandler, purposeType, consentRef if (raw) { existingConsent = JSON.parse(raw); } - } catch { + } catch (e) { existingConsent = {}; } const mergedConsent = { ...existingConsent, - [purposeType]: { accepted, rejected } + [purposeType]: { purposes } }; const serialized = JSON.stringify(mergedConsent); @@ -81,7 +84,7 @@ const reportPolicyState = (checkedMap, formStateHandler, purposeType, consentRef const PolicyConsentFieldAdapter = ({ component, formStateHandler, fieldErrorHandler }) => { - const { description, identifier = "consent_policy", policies: rawPolicies = [], + const { description, identifier = "consent_policy", purposes: rawPolicies = [], purposeType = "Policy" } = component.config; const { locale, translations } = useTranslations(); diff --git a/identity-apps-core/react-ui-core/src/components/adapters/preference-management-field-adapter.js b/identity-apps-core/react-ui-core/src/components/adapters/preference-management-field-adapter.js new file mode 100644 index 00000000000..920f82ab504 --- /dev/null +++ b/identity-apps-core/react-ui-core/src/components/adapters/preference-management-field-adapter.js @@ -0,0 +1,281 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import DOMPurify from "dompurify"; +import parse from "html-react-parser"; +import PropTypes from "prop-types"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { Checkbox, Form } from "semantic-ui-react"; +import { useTranslations } from "../../hooks/use-translations"; +import { i18nLink, resolveElementText } from "../../utils/i18n-utils"; +import "./rich-text-field-adapter.css"; + +let isAfterSanitizeHookRegistered = false; + +const ensureAfterSanitizeAttributesHookRegistered = () => { + if (isAfterSanitizeHookRegistered) { + return; + } + + DOMPurify.addHook("afterSanitizeAttributes", (node) => { + if (node.tagName === "A") { + node.setAttribute("target", "_blank"); + node.setAttribute("rel", "noopener noreferrer"); + } + }); + + isAfterSanitizeHookRegistered = true; +}; + +/** + * Resolves a purpose description to the best matching string for the current locale. + * Accepts either a plain string (optionally an i18n key placeholder) or a locale map. + * + * @param {string | Record} description - Raw description value. + * @param {string} locale - Current locale (underscore-separated, e.g. "en_US"). + * @param {Object} translations - Translations map for resolving i18n placeholders. + * @returns {string} + */ +const resolveDescription = (description, locale, translations) => { + if (!description) { + return ""; + } + + if (typeof description === "string") { + return resolveElementText(translations, description); + } + + // Locale map keyed by IETF tags (e.g. "en-US"). Normalize underscore → hyphen. + const normalizedLocale = locale ? locale.replace("_", "-") : "en-US"; + + return description[normalizedLocale] + ?? description["en-US"] + ?? Object.values(description)[0] + ?? ""; +}; + +/** + * Serializes the current per-purpose attribute selection into the shared "consent" + * form field as a JSON payload, merging with any existing consent entries for other + * purpose types (e.g. "Policy") already stored in the ref. + * + * @param {{ [purposeId: string]: { [attrId: string]: boolean } }} checkedMap + * @param {Function} formStateHandler + * @param {string} purposeType + * @param {{ current: string | null }} consentRef + */ +const reportPreferenceState = (checkedMap, formStateHandler, purposeType, consentRef) => { + const purposes = Object.entries(checkedMap).map(([ purposeId, attrMap ]) => { + const acceptedAttributes = Object.keys(attrMap) + .filter((id) => id !== "__accepted" && attrMap[id]); + + const accepted = attrMap.__accepted ?? acceptedAttributes.length > 0; + + return { + accepted, + attributes: acceptedAttributes, + id: purposeId + }; + }); + + let existingConsent = {}; + + try { + const raw = consentRef.current; + + if (raw) { + existingConsent = JSON.parse(raw); + } + } catch (e) { + existingConsent = {}; + } + + const merged = { ...existingConsent, [purposeType]: { purposes } }; + const serialized = JSON.stringify(merged); + + consentRef.current = serialized; + formStateHandler("preference", serialized); +}; + +const PreferenceManagementFieldAdapter = ({ component, formStateHandler, fieldErrorHandler }) => { + + const { + identifier = "preference_management", + purposes: rawPurposes = [], + purposeType = "Preference" + } = component.config; + + const { locale, translations } = useTranslations(); + + const purposes = useMemo( + () => rawPurposes.filter((p) => p.attributes !== undefined && p.attributes !== null), + [ rawPurposes ] + ); + + // checkedMap: { [purposeId]: { [attrId]: boolean } } + const [ checkedMap, setCheckedMap ] = useState({}); + const consentRef = useRef(null); + + useEffect(() => { + if (purposes.length === 0) { + return; + } + + const initial = {}; + + purposes.forEach((purpose) => { + initial[purpose.purposeId] = {}; + (purpose.attributes || []).forEach((attr) => { + initial[purpose.purposeId][attr.id] = + checkedMap[purpose.purposeId]?.[attr.id] ?? false; + }); + }); + + setCheckedMap(initial); + reportPreferenceState(initial, formStateHandler, purposeType, consentRef); + fieldErrorHandler(identifier, null); + }, [ purposes ]); + + const handleAttributeChange = (_e, data, purposeId, attrId) => { + const updated = { + ...checkedMap, + [purposeId]: { ...checkedMap[purposeId], [attrId]: data.checked } + }; + + setCheckedMap(updated); + reportPreferenceState(updated, formStateHandler, purposeType, consentRef); + }; + + const handleSelectAll = (_e, data, purposeId, attrIds) => { + const updatedPurpose = {}; + + attrIds.forEach((id) => { updatedPurpose[id] = data.checked; }); + + // For purposes with no attributes, track acceptance via __accepted flag + if (attrIds.length === 0) { + updatedPurpose.__accepted = data.checked; + } + + const updated = { ...checkedMap, [purposeId]: updatedPurpose }; + + setCheckedMap(updated); + reportPreferenceState(updated, formStateHandler, purposeType, consentRef); + }; + + + if (purposes.length === 0) { + return null; + } + + ensureAfterSanitizeAttributesHookRegistered(); + + return ( + + { purposes.map((purpose) => { + const description = resolveDescription(purpose.description, locale, translations); + const attrIds = (purpose.attributes || []).map((a) => a.id); + const attrMap = checkedMap[purpose.purposeId] ?? {}; + const allChecked = attrIds.length === 0 + ? attrMap.__accepted ?? false + : attrIds.every((id) => attrMap[id]); + const isIndeterminate = attrIds.length > 0 + && !allChecked + && attrIds.some((id) => attrMap[id]); + + const purposeName = resolveElementText(translations, purpose.name) || purpose.name || ""; + const resolvedTemplate = resolveElementText(translations, "{{consent.preference.exampleDescription}}") + || "I agree to receive {consentName} communications."; + const fallbackDescription = resolvedTemplate.replace("{consentName}", purposeName); + const rawDescription = description || fallbackDescription; + + const htmlWithLocalizedLinks = rawDescription.replace( + /href="([^"]*)"/g, + (_match, url) => `href="${ i18nLink(locale, url) }"` + ); + const sanitizedHtml = DOMPurify.sanitize(htmlWithLocalizedLinks, { + ADD_ATTR: [ "target", "rel" ], + ALLOWED_TAGS: [ "p", "a", "strong", "em", "u", "br", "span", "h1", "h2", "h3", "h4", "h5" ] + }); + + return ( +
+
+ handleSelectAll(_e, data, purpose.purposeId, attrIds) } + /> + +
+ { purpose.attributes.length > 0 && ( +
+ { purpose.attributes.map((attr) => ( +
+ + handleAttributeChange(_e, data, purpose.purposeId, attr.id) } + /> + +
+ )) } +
+ ) } +
+ ); + }) } +
+ ); +}; + +PreferenceManagementFieldAdapter.propTypes = { + component: PropTypes.object.isRequired, + fieldErrorHandler: PropTypes.func.isRequired, + formStateHandler: PropTypes.func.isRequired +}; + +export default PreferenceManagementFieldAdapter; diff --git a/identity-apps-core/react-ui-core/src/components/adapters/rich-text-field-adapter.css b/identity-apps-core/react-ui-core/src/components/adapters/rich-text-field-adapter.css index 9e1aad80ec7..3bddd0dc51b 100644 --- a/identity-apps-core/react-ui-core/src/components/adapters/rich-text-field-adapter.css +++ b/identity-apps-core/react-ui-core/src/components/adapters/rich-text-field-adapter.css @@ -52,3 +52,7 @@ margin: 0.5em 0; font-size: 13px; } + +.rich-text-content li.consent-purpose-item::marker { + color: #FF7300; +} diff --git a/identity-apps-core/react-ui-core/src/components/field.js b/identity-apps-core/react-ui-core/src/components/field.js index 251bd9db17d..0aa2f43a920 100644 --- a/identity-apps-core/react-ui-core/src/components/field.js +++ b/identity-apps-core/react-ui-core/src/components/field.js @@ -23,6 +23,7 @@ import ButtonFieldAdapter from "./adapters/button-field-adapter"; import CaptchaWidgetAdapter from "./adapters/captcha-widget-adapter"; import ImageFieldAdapter from "./adapters/image-field-adapter"; import InputFieldAdapter from "./adapters/input-field-adapter"; +import PreferenceManagementFieldAdapter from "./adapters/preference-management-field-adapter"; import PolicyConsentFieldAdapter from "./adapters/policy-consent-field-adapter"; import ResendButtonAdapter from "./adapters/resend-button-adapter"; import RichTextAdapter from "./adapters/rich-text-field-adapter"; @@ -76,6 +77,14 @@ const Field = ({ fieldErrorHandler={ formFieldError } /> ); + case "PREFERENCE": + return ( + + ); case "RESEND": return ; default: diff --git a/modules/core/src/helpers/__mocks__/deployment.config.json b/modules/core/src/helpers/__mocks__/deployment.config.json index 714e1106fc8..ccad8ef6843 100644 --- a/modules/core/src/helpers/__mocks__/deployment.config.json +++ b/modules/core/src/helpers/__mocks__/deployment.config.json @@ -1314,12 +1314,14 @@ "attributeDialects": "v0.0.0", "attributeUpdateVerificationSettings": "v1.0.0", "branding": "v0.0.0", + "consents": "v0.0.0", "consoleSettings": "v0.0.0", "emailTemplates": "v0.0.0", "gettingStarted": "v0.0.0", "governanceConnectors": "v0.0.0", "groups": "v0.0.0", "identityProviders": "v0.0.0", + "preferenceManagement": "v0.0.0", "organizations": "v0.0.0", "roles": "v0.0.0", "smsTemplates": "v0.0.0", diff --git a/modules/i18n/src/models/namespaces/authentication-provider-ns.ts b/modules/i18n/src/models/namespaces/authentication-provider-ns.ts index 009a943f0b9..897d486ece7 100644 --- a/modules/i18n/src/models/namespaces/authentication-provider-ns.ts +++ b/modules/i18n/src/models/namespaces/authentication-provider-ns.ts @@ -2145,6 +2145,51 @@ export interface AuthenticationProviderNS { }; }; }; + connectionTest: { + button: string; + pageTitle: string; + pageDescription: string; + backButton: string; + rerunButton: string; + tabs: { + idToken: string; + claimMappings: string; + diagnosis: string; + }; + idToken: { + header: string; + payload: string; + signature: string; + decodeError: string; + }; + claimMappings: { + localClaimColumn: string; + idpClaimColumn: string; + valueColumn: string; + empty: string; + }; + diagnosis: { + noLogs: string; + timestampUnavailable: string; + noMessage: string; + }; + status: { + passed: string; + partial: string; + failed: string; + passedDescription: string; + partialDescription: string; + failedDescription: string; + }; + error: { + title: string; + retry: string; + }; + loading: { + results: string; + waitingForAuth: string; + }; + }; wizards: { addAuthenticator: { header: string; diff --git a/modules/i18n/src/models/namespaces/common-ns.ts b/modules/i18n/src/models/namespaces/common-ns.ts index 79b121503d5..33caf456f7b 100644 --- a/modules/i18n/src/models/namespaces/common-ns.ts +++ b/modules/i18n/src/models/namespaces/common-ns.ts @@ -169,6 +169,7 @@ export interface CommonNS { completed: string; configure: string; confirm: string; + consents: string; contains: string; continue: string; copied: string; diff --git a/modules/i18n/src/models/namespaces/consents-ns.ts b/modules/i18n/src/models/namespaces/consents-ns.ts index cb470f49bb9..ea0db2f7428 100644 --- a/modules/i18n/src/models/namespaces/consents-ns.ts +++ b/modules/i18n/src/models/namespaces/consents-ns.ts @@ -16,228 +16,246 @@ * under the License. */ -export interface ConsentsNS { - form: { - createNewVersion: string; - description: { - label: string; - }; - mandatory: { - label: string; - hint: string; - linkHint: string; - }; - name: { - label: string; - placeholder: string; - error: { - duplicateName: string; - }; - }; - policyUrl: { - label: string; - hint: string; - versionHint: string; - }; - promptOnLogin: { - label: string; - hint: string; - activeHint: string; - }; - versionDropdown: { - trigger: string; - currentVersionLabel: string; - }; - versionModal: { - createNewVersion: string; - promptAtLogin: string; - promptDescription: string; +interface ConsentNotificationsNS { + create: { + error: { + conflict: { description: string; message: string }; + description: string; + message: string; + notFound: { description: string; message: string }; + serverError: { description: string; message: string }; }; + success: { description: string; message: string }; }; - list: { - emptyPlaceholder: { - addPolicy: string; - subtitle: string; - }; - emptySearchPlaceholder: { - action: string; - subtitle: string; - title: string; + delete: { + error: { + conflict: { description: string; message: string }; + description: string; + message: string; + notFound: { description: string; message: string }; + serverError: { description: string; message: string }; }; + success: { description: string; message: string }; }; - registrationFlow: { - noPolicies: string; - selectPolicies: string; + update: { + error: { + conflict: { description: string; message: string }; + description: string; + message: string; + notFound: { description: string; message: string }; + serverError: { description: string; message: string }; + }; + success: { description: string; message: string }; }; - notifications: { - create: { - error: { - conflict: { - description: string; - message: string; - }; - description: string; - message: string; - notFound: { - description: string; - message: string; - }; - serverError: { - description: string; - message: string; - }; +} + +interface ConsentDeleteConfirmationNS { + assertionHint: string; + content: string; + header: string; + message: string; + primaryAction: string; + secondaryAction: string; +} + +export interface ConsentsNS { + preferenceManagement: { + form: { + description: { + label: string; + labelRoleHint: string; }; - success: { - description: string; - message: string; + linkHint: string; + name: { + label: string; + placeholder: string; + error: { + duplicateName: string; + }; }; }; - delete: { - error: { - conflict: { - description: string; - message: string; - }; - description: string; - message: string; - notFound: { - description: string; - message: string; - }; - serverError: { - description: string; - message: string; - }; + list: { + emptyPlaceholder: { + addConsent: string; + subtitle: string; }; - success: { - description: string; - message: string; + emptySearchPlaceholder: { + action: string; + subtitle: string; + title: string; }; }; - updatePolicy: { - error: { - conflict: { - description: string; - message: string; + notifications: ConsentNotificationsNS; + pages: { + deleteConfirmation: ConsentDeleteConfirmationNS; + edit: { + backButton: string; + dangerZone: { + actionTitle: string; + header: string; + subheader: string; }; - description: string; - message: string; - notFound: { - description: string; - message: string; + title: string; + }; + list: { + actions: { + addConsent: string; }; - serverError: { - description: string; - message: string; + description: string; + heading: string; + search: { + placeholder: string; }; + title: string; }; - success: { - description: string; - message: string; + new: { + backButton: string; + title: string; }; }; + preview: { + consentHeader: string; + emptyDescription: string; + exampleDescription: string; + pageTitle: string; + }; }; - pages: { - edit: { - backButton: string; - dangerZone: { - actionTitle: string; - header: string; - subheader: string; - }; - deleteConfirmation: { - assertionHint: string; - content: string; - header: string; - message: string; - primaryAction: string; - secondaryAction: string; - }; - title: string; + policyConsents: { + form: { + createNewVersion: string; + description: { + label: string; + }; + mandatory: { + label: string; + hint: string; + linkHint: string; + }; + name: { + label: string; + placeholder: string; + error: { + duplicateName: string; + }; + }; + policyUrl: { + label: string; + hint: string; + versionHint: string; + }; + promptOnLogin: { + label: string; + hint: string; + activeHint: string; + }; + versionDropdown: { + trigger: string; + currentVersionLabel: string; + }; + versionModal: { + createNewVersion: string; + promptAtLogin: string; + promptDescription: string; + }; }; list: { - actions: { + emptyPlaceholder: { addPolicy: string; + subtitle: string; }; - deleteConfirmation: { - assertionHint: string; - header: string; - message: string; - content: string; - primaryAction: string; - secondaryAction: string; - }; - backButton: string; - description: string; - heading: string; - search: { - placeholder: string; + emptySearchPlaceholder: { + action: string; + subtitle: string; + title: string; }; - title: string; }; - new: { - backButton: string; - title: string; + notifications: ConsentNotificationsNS; + pages: { + deleteConfirmation: ConsentDeleteConfirmationNS; + edit: { + backButton: string; + dangerZone: { + actionTitle: string; + header: string; + subheader: string; + }; + title: string; + }; + list: { + actions: { + addPolicy: string; + }; + description: string; + heading: string; + search: { + placeholder: string; + }; + title: string; + }; + new: { + backButton: string; + title: string; + }; }; - }; - tabs: { - content: { label: string }; - preview: { label: string }; - }; - wizard: { - create: { - form: { - description: { - configureTranslation: string; - i18nCard: { - brandingRequired: string; - createTitle: string; - deleteError: { - description: string; - message: string; - }; - deleteSuccess: { - description: string; - message: string; - }; - deleteTooltip: string; - editTooltip: string; - i18nKey: string; - keyPlaceholder: string; - language: string; - newTooltip: string; - saveError: { - description: string; - message: string; - }; - saveSuccess: { - description: string; - message: string; + wizard: { + create: { + form: { + description: { + configureTranslation: string; + i18nCard: { + brandingRequired: string; + createTitle: string; + deleteError: { description: string; message: string }; + deleteSuccess: { description: string; message: string }; + deleteTooltip: string; + editTooltip: string; + i18nKey: string; + keyPlaceholder: string; + language: string; + newTooltip: string; + saveError: { description: string; message: string }; + saveSuccess: { description: string; message: string }; + selectKey: string; + title: string; + translationPlaceholder: string; + translationText: string; + updateTitle: string; }; - selectKey: string; - title: string; - translationPlaceholder: string; - translationText: string; - updateTitle: string; + insertPolicyLink: string; + insertPolicyLinkInvalidUrl: string; + insertPolicyLinkShort: string; + insertPolicyLinkTooltip: string; + insertPolicyLinkNoPolicyUrl: string; + insertPolicyLinkNoSelection: string; + labelRoleHint: string; }; - insertPolicyLink: string; - insertPolicyLinkInvalidUrl: string; - insertPolicyLinkShort: string; - insertPolicyLinkTooltip: string; - insertPolicyLinkNoPolicyUrl: string; - insertPolicyLinkNoSelection: string; - labelRoleHint: string; }; - }; - preview: { - allowButton: string; - appLoginMessage: string; - consentHeader: string; - denyButton: string; - emptyDescription: string; - exampleDescription: string; - pageTitle: string; + preview: { + consentHeader: string; + emptyDescription: string; + exampleDescription: string; + pageTitle: string; + updatedPolicies: string; + }; }; }; }; + registrationFlow: { + addAttribute: string; + addPurpose: string; + attributeDisplayName: string; + attributeName: string; + attributes: string; + noPreference: string; + noPolicies: string; + purposeDescription: string; + purposeLabel: string; + purposeName: string; + selectPreference: string; + selectPolicies: string; + }; + tabs: { + content: { label: string }; + preview: { label: string }; + }; } diff --git a/modules/i18n/src/models/namespaces/console-ns.ts b/modules/i18n/src/models/namespaces/console-ns.ts index ad18318e39b..af7713a8970 100644 --- a/modules/i18n/src/models/namespaces/console-ns.ts +++ b/modules/i18n/src/models/namespaces/console-ns.ts @@ -2535,10 +2535,6 @@ export interface ConsoleNS { subTitle: string; alternateSubTitle: string; }; - consents: { - title: string; - description: string; - }; applicationsEdit: { backButton: string; title: string; diff --git a/modules/i18n/src/models/namespaces/flows-ns.ts b/modules/i18n/src/models/namespaces/flows-ns.ts index 7bd59816518..dae0e97cc15 100644 --- a/modules/i18n/src/models/namespaces/flows-ns.ts +++ b/modules/i18n/src/models/namespaces/flows-ns.ts @@ -67,6 +67,12 @@ export interface flowsNS { }; placeholder: string; }; + preferenceManagement: { + emptyState: string; + }; + policyConsent: { + emptyState: string; + }; textPropertyField: { i18nCard: { chip: { @@ -293,6 +299,11 @@ export interface flowsNS { text: string; variant: string; }; + preferenceManagement: { + general: string; + purposeAttributesRequired: string; + purposesRequired: string; + }; policyConsent: { general: string; noPoliciesAvailable: string; diff --git a/modules/i18n/src/models/namespaces/governance-connectors-ns.ts b/modules/i18n/src/models/namespaces/governance-connectors-ns.ts index 43e2cefb7f2..c33443d819e 100644 --- a/modules/i18n/src/models/namespaces/governance-connectors-ns.ts +++ b/modules/i18n/src/models/namespaces/governance-connectors-ns.ts @@ -797,6 +797,11 @@ export interface governanceConnectorsNS { friendlyName: string; header: string; }; + preferenceManagement: { + description: string; + friendlyName: string; + header: string; + }; }; }; sessionManagement: { diff --git a/modules/i18n/src/models/namespaces/myaccount-ns.ts b/modules/i18n/src/models/namespaces/myaccount-ns.ts index d01cc2dd03b..773691af7a7 100644 --- a/modules/i18n/src/models/namespaces/myaccount-ns.ts +++ b/modules/i18n/src/models/namespaces/myaccount-ns.ts @@ -379,8 +379,9 @@ export interface MyAccountNS { updateConsentedClaims: Notification; }; }; - policyConsentManagement: { + preferenceManagement: { consentedOnLabel: string; + elementsHeading: string; dangerZones: { revoke: { actionTitle: string; @@ -388,10 +389,21 @@ export interface MyAccountNS { subheader: string; }; }; - modals: { - revokeModal: { - heading: string; - message: string; + notifications: { + fetch: Notification; + revoke: Notification; + update: Notification; + }; + policyUrlLabel: string; + versionLabel: string; + }; + policyConsentManagement: { + consentedOnLabel: string; + dangerZones: { + revoke: { + actionTitle: string; + header: string; + subheader: string; }; }; notifications: { @@ -1127,6 +1139,15 @@ export interface MyAccountNS { }; }; }; + preferenceManagement: { + description: string; + heading: string; + placeholders: { + emptyConsentList: { + heading: string; + }; + }; + }; policyConsentManagement: { description: string; heading: string; diff --git a/modules/i18n/src/translations/de-DE/portals/common.ts b/modules/i18n/src/translations/de-DE/portals/common.ts index e2a101edc5c..67179d3a8aa 100644 --- a/modules/i18n/src/translations/de-DE/portals/common.ts +++ b/modules/i18n/src/translations/de-DE/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { "completed": "Abgeschlossen", "configure": "Konfigurieren", "confirm": "Bestätigen", + "consents": "Einwilligungen", "contains": "enthält", "continue": "Fortsetzen", "copied": "Kopiert!", diff --git a/modules/i18n/src/translations/de-DE/portals/myaccount.ts b/modules/i18n/src/translations/de-DE/portals/myaccount.ts index e414c763245..5e4b147df82 100644 --- a/modules/i18n/src/translations/de-DE/portals/myaccount.ts +++ b/modules/i18n/src/translations/de-DE/portals/myaccount.ts @@ -364,7 +364,7 @@ export const myAccount: MyAccountNS = { "dangerZones": { "revoke": { "actionTitle": "Widerrufen", - "header": "Einwilligung widerrufen", + "header": "Präferenz widerrufen", "subheader": "Sie müssen dieser Anwendung erneut zustimmen." } }, @@ -439,6 +439,63 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "consentedOnLabel": "Akzeptiert am", + "dangerZones": { + "revoke": { + "actionTitle": "Widerrufen", + "header": "Kommunikationspräferenz widerrufen", + "subheader": "Diese Aktion entfernt Ihre Präferenz für diese Kommunikation." + } + }, + "elementsHeading": "Aktualisieren Sie Ihre Präferenzen unten. Deaktivieren Sie die Attribute, von denen Sie sich abmelden möchten, und klicken Sie auf „Aktualisieren“, um die Änderungen zu speichern, oder klicken Sie auf „Widerrufen“, um die Präferenz vollständig zu entfernen.", + "notifications": { + "fetch": { + "error": { + "description": "Beim Abrufen Ihrer Kommunikationspräferenzen ist ein Fehler aufgetreten.", + "message": "Abruf fehlgeschlagen" + }, + "genericError": { + "description": "Beim Abrufen Ihrer Kommunikationspräferenzen ist ein Fehler aufgetreten.", + "message": "Abruf fehlgeschlagen" + }, + "success": { + "description": "Ihre Kommunikationspräferenzen wurden erfolgreich abgerufen.", + "message": "Abruf erfolgreich" + } + }, + "revoke": { + "error": { + "description": "Beim Widerrufen der Kommunikationspräferenz ist ein Fehler aufgetreten.", + "message": "Widerruf fehlgeschlagen" + }, + "genericError": { + "description": "Beim Widerrufen der Kommunikationspräferenz ist ein Fehler aufgetreten.", + "message": "Widerruf fehlgeschlagen" + }, + "success": { + "description": "Die Kommunikationspräferenz wurde erfolgreich widerrufen.", + "message": "Präferenz widerrufen" + } + }, + "update": { + "error": { + "description": "Beim Aktualisieren der Kommunikationspräferenz ist ein Fehler aufgetreten.", + "message": "Aktualisierung fehlgeschlagen" + }, + "genericError": { + "description": "Beim Aktualisieren der Kommunikationspräferenz ist ein Fehler aufgetreten.", + "message": "Aktualisierung fehlgeschlagen" + }, + "success": { + "description": "Ihre Kommunikationspräferenzen wurden erfolgreich aktualisiert.", + "message": "Präferenzen aktualisiert" + } + } + }, + "policyUrlLabel": "Richtlinie anzeigen", + "versionLabel": "Version {{version}}" + }, "policyConsentManagement": { "consentedOnLabel": "Aktiv seit", "dangerZones": { @@ -448,12 +505,6 @@ export const myAccount: MyAccountNS = { "subheader": "Diese Aktion widerruft Ihre Zustimmung zu dieser Richtlinie. Möglicherweise werden Sie beim nächsten Zugriff auf den Dienst aufgefordert, erneut zuzustimmen." } }, - "modals": { - "revokeModal": { - "heading": "Zustimmung für {{policyName}} widerrufen", - "message": "Dies widerruft Ihre Zustimmung zu dieser Richtlinie. Möglicherweise werden Sie beim nächsten Zugriff auf den Dienst aufgefordert, erneut zuzustimmen. Möchten Sie fortfahren?" - } - }, "notifications": { "fetch": { "error": { @@ -1807,6 +1858,15 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "description": "Überprüfen Sie Ihre Kommunikationspräferenzen und verwalten Sie diese.", + "heading": "Kommunikationspräferenzen", + "placeholders": { + "emptyConsentList": { + "heading": "Sie haben keine Kommunikationspräferenzen festgelegt" + } + } + }, "policyConsentManagement": { "description": "Überprüfen Sie die Richtlinien, denen Sie zugestimmt haben.", "heading": "Richtlinienzustimmungen", diff --git a/modules/i18n/src/translations/en-US/portals/authentication-provider.ts b/modules/i18n/src/translations/en-US/portals/authentication-provider.ts index ba07ceed48a..f0dc6e31419 100644 --- a/modules/i18n/src/translations/en-US/portals/authentication-provider.ts +++ b/modules/i18n/src/translations/en-US/portals/authentication-provider.ts @@ -2239,6 +2239,51 @@ export const authenticationProvider:AuthenticationProviderNS = { } } }, + connectionTest: { + backButton: "Go back to Connection", + button: "Test Connection", + claimMappings: { + empty: "No claim mappings configured.", + idpClaimColumn: "IDP Claim", + localClaimColumn: "Local Claim (URI)", + valueColumn: "Value" + }, + diagnosis: { + noLogs: "No log information available.", + noMessage: "No diagnostic message provided.", + timestampUnavailable: "Timestamp unavailable" + }, + error: { + retry: "Retry", + title: "Error" + }, + idToken: { + decodeError: "Unable to decode token or not a valid JWT.", + header: "Header", + payload: "Payload", + signature: "Signature" + }, + loading: { + results: "Loading test results...", + waitingForAuth: "Waiting for authentication to complete..." + }, + pageDescription: "Results for the connection test session.", + pageTitle: "Test Results", + rerunButton: "Rerun Test", + status: { + failed: "Test Failed", + failedDescription: "Some steps failed during the connection test. Check the Diagnosis tab for details.", + partial: "Test Passed Partially", + partialDescription: "Test passed but some claims were not successfully mapped. Check the Claim Mappings tab for details.", + passed: "Test Passed", + passedDescription: "All connection test steps completed successfully." + }, + tabs: { + claimMappings: "Claim Mappings", + diagnosis: "Diagnosis", + idToken: "ID Token" + } + }, wizards: { addAuthenticator: { header: "Fill the basic information about the authenticator.", diff --git a/modules/i18n/src/translations/en-US/portals/common.ts b/modules/i18n/src/translations/en-US/portals/common.ts index 6c6a1e90c5d..d8e1a8dc3f0 100755 --- a/modules/i18n/src/translations/en-US/portals/common.ts +++ b/modules/i18n/src/translations/en-US/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { completed: "Completed", configure: "Configure", confirm: "Confirm", + consents: "Consents", contains: "Contains", continue: "Continue", copied: "Copied!", diff --git a/modules/i18n/src/translations/en-US/portals/consents.ts b/modules/i18n/src/translations/en-US/portals/consents.ts index adbc300da67..dd7fd6b74d7 100644 --- a/modules/i18n/src/translations/en-US/portals/consents.ts +++ b/modules/i18n/src/translations/en-US/portals/consents.ts @@ -19,230 +19,363 @@ import { ConsentsNS } from "../../../models"; export const consents: ConsentsNS = { - form: { - createNewVersion: "Create New Version", - description: { - label: "Description" - }, - mandatory: { - hint: "Set during policy creation. When enabled, users will not be able to continue the flow without accepting this policy.", - label: "Mandatory", - linkHint: "To add to registration flow, navigate to <0>registration flow builder." - }, - name: { - error: { - duplicateName: "A policy with this name already exists. Please use a different name." + preferenceManagement: { + form: { + description: { + label: "Description", + labelRoleHint: "Leave empty to use the default description." }, - label: "Name", - placeholder: "Privacy Policy" - }, - policyUrl: { - hint: "Provide the URL where the full policy document can be accessed. " + - "You can use placeholders like {{lang}}, {{country}}, or {{locale}} " + - "to customize the URL for different regions or languages.", - label: "Policy URL", - versionHint: "To create a new version, update the policy URL, description, or prompt settings above." - }, - promptOnLogin: { - activeHint: "Users will be prompted to review and accept this policy during login.", - hint: "When enabled, users will be prompted to review and accept this policy during login.", - label: "Prompt on Login" - }, - versionDropdown: { - currentVersionLabel: "Version {{version}} (current)", - trigger: "Version {{version}}" - }, - versionModal: { - createNewVersion: "Create New Version?", - promptAtLogin: "Prompt users at next login", - promptDescription: "Choose whether users should be prompted to accept the updated policy." - } - }, - list: { - emptyPlaceholder: { - addPolicy: "New Policy", - subtitle: "There are no policies available at the moment" - }, - emptySearchPlaceholder: { - action: "Clear search", - subtitle: "No policies found for the search query.", - title: "No results found" - } - }, - registrationFlow: { - noPolicies: "No policies available. Create policies from Policy Consents.", - selectPolicies: "Select Policies:" - }, - notifications: { - create: { - error: { - conflict: { - description: "A policy with this name already exists. Please use a different name.", - message: "Conflict" - }, - description: "Failed to create policy. Please try again.", - message: "Create Failed", - notFound: { - description: "The requested policy was not found.", - message: "Policy Not Found" + linkHint: "To include this preference in a registration flow, go to the <0>Registration Flow Builder.", + name: { + error: { + duplicateName: "A preference with this name already exists. Please use a different name." }, - serverError: { - description: "A server error occurred while creating the policy. Please try again later.", - message: "Server Error" - } + label: "Name", + placeholder: "Newsletter Subscription" + } + }, + list: { + emptyPlaceholder: { + addConsent: "New Preference", + subtitle: "There are no preferences available at the moment" }, - success: { - description: "Policy created successfully.", - message: "Policy Created" + emptySearchPlaceholder: { + action: "Clear search", + subtitle: "No preferences found for the search query.", + title: "No results found" } }, - delete: { - error: { - conflict: { - description: "This policy cannot be deleted because it is in use or is protected.", - message: "Cannot Delete" + notifications: { + create: { + error: { + conflict: { + description: "A preference with this name already exists. Please use a different name.", + message: "Conflict" + }, + description: "Failed to create preference. Please try again.", + message: "Create Failed", + notFound: { + description: "The requested preference was not found.", + message: "Not Found" + }, + serverError: { + description: "A server error occurred while creating the preference. Please try again later.", + message: "Server Error" + } }, - description: "Failed to delete policy. Please try again.", - message: "Delete Failed", - notFound: { - description: "The policy you are trying to delete was not found.", - message: "Policy Not Found" + success: { + description: "Preference created successfully.", + message: "Preference Created" + } + }, + delete: { + error: { + conflict: { + description: "This preference cannot be deleted because it is in use or is protected.", + message: "Cannot Delete" + }, + description: "Failed to delete preference. Please try again.", + message: "Delete Failed", + notFound: { + description: "The preference you are trying to delete was not found.", + message: "Not Found" + }, + serverError: { + description: "A server error occurred while deleting the preference. Please try again later.", + message: "Server Error" + } }, - serverError: { - description: "A server error occurred while deleting the policy. Please try again later.", - message: "Server Error" + success: { + description: "Preference deleted successfully.", + message: "Preference Deleted" } }, - success: { - description: "Policy deleted successfully.", - message: "Policy Deleted" + update: { + error: { + conflict: { + description: "This version already exists or conflicts with another version.", + message: "Version Conflict" + }, + description: "Preference update failed. Please try again.", + message: "Update Failed", + notFound: { + description: "The preference or version you are trying to update was not found.", + message: "Not Found" + }, + serverError: { + description: "A server error occurred while updating the preference. Please try again later.", + message: "Server Error" + } + }, + success: { + description: "Preference updated successfully.", + message: "Update Successful" + } } }, - updatePolicy: { - error: { - conflict: { - description: "This version already exists or conflicts with another version.", - message: "Version Conflict" + pages: { + deleteConfirmation: { + assertionHint: "I understand this action is permanent and cannot be undone.", + content: "This preference can only be deleted if no users have accepted it.", + header: "Delete preference?", + message: "This will permanently delete the preference. This cannot be undone.", + primaryAction: "Confirm", + secondaryAction: "Cancel" + }, + edit: { + backButton: "Back to Preference Management", + dangerZone: { + actionTitle: "Delete Preference", + header: "Delete Preference", + subheader: "Once you delete a preference, there is no going back. Please be certain." }, - description: "Policy update failed. Please try again.", - message: "Update Failed", - notFound: { - description: "The consent or version you are trying to update was not found.", - message: "Not Found" + title: "Edit Preference" + }, + list: { + actions: { + addConsent: "New Preference" }, - serverError: { - description: "A server error occurred while updating the policy. Please try again later.", - message: "Server Error" - } + description: "Manage communication preferences for your organization.", + heading: "Preference Management", + search: { + placeholder: "Search by name" + }, + title: "Preference Management" }, - success: { - description: "Policy updated successfully.", - message: "Update Successful" + new: { + backButton: "Back to Preference Management", + title: "Create Preference" } + }, + preview: { + consentHeader: "Please review your preferences to proceed.", + emptyDescription: "Write a consent name to preview.", + exampleDescription: "I agree to receive {{consentName}} communications.", + pageTitle: "Communication Preferences" } }, - pages: { - edit: { - backButton: "Back to Policies", - dangerZone: { - actionTitle: "Delete Policy", - header: "Delete Policy", - subheader: "Once you delete a policy, there is no going back. Please be certain." + policyConsents: { + form: { + createNewVersion: "Create New Version", + description: { + label: "Description" }, - deleteConfirmation: { - assertionHint: "I confirm that I want to delete this policy.", - content: "You can delete this policy only if no users have given consent to it.", - header: "Are you sure?", - message: "This action is irreversible and will permanently delete the policy.", - primaryAction: "Confirm", - secondaryAction: "Cancel" + mandatory: { + hint: "When enabled, users must accept this policy to proceed. This setting cannot be changed after creation.", + label: "Mandatory", + linkHint: "To include this consent in a registration flow, go to the <0>Registration Flow Builder." + }, + name: { + error: { + duplicateName: "A policy with this name already exists. Please use a different name." + }, + label: "Name", + placeholder: "Privacy Policy" + }, + policyUrl: { + hint: "Enter the URL of the full policy document. Use {{lang}}, {{country}}, or {{locale}} as placeholders to support multiple regions or languages.", + label: "Policy URL", + versionHint: "To create a new version, update the policy URL, description, or prompt settings above." }, - title: "Edit Policy" + promptOnLogin: { + activeHint: "Users will be prompted to review and accept this policy during login.", + hint: "When enabled, users will be prompted to review and accept this policy at each login.", + label: "Prompt on Login" + }, + versionDropdown: { + currentVersionLabel: "Version {{version}} (current)", + trigger: "Version {{version}}" + }, + versionModal: { + createNewVersion: "Save as New Version?", + promptAtLogin: "Prompt users to accept at next login", + promptDescription: "Choose whether existing users should be prompted to review and accept this updated policy at their next login." + } }, list: { - actions: { - addPolicy: "New Policy" + emptyPlaceholder: { + addPolicy: "New Policy", + subtitle: "There are no policies available at the moment" + }, + emptySearchPlaceholder: { + action: "Clear search", + subtitle: "No policies found for the search query.", + title: "No results found" + } + }, + notifications: { + create: { + error: { + conflict: { + description: "A policy with this name already exists. Please use a different name.", + message: "Conflict" + }, + description: "Failed to create policy. Please try again.", + message: "Create Failed", + notFound: { + description: "The requested policy was not found.", + message: "Not Found" + }, + serverError: { + description: "A server error occurred while creating the policy. Please try again later.", + message: "Server Error" + } + }, + success: { + description: "Policy created successfully.", + message: "Policy Created" + } + }, + delete: { + error: { + conflict: { + description: "This policy cannot be deleted because it is in use or is protected.", + message: "Cannot Delete" + }, + description: "Failed to delete policy. Please try again.", + message: "Delete Failed", + notFound: { + description: "The policy you are trying to delete was not found.", + message: "Not Found" + }, + serverError: { + description: "A server error occurred while deleting the policy. Please try again later.", + message: "Server Error" + } + }, + success: { + description: "Policy deleted successfully.", + message: "Policy Deleted" + } }, + update: { + error: { + conflict: { + description: "This version already exists or conflicts with another version.", + message: "Version Conflict" + }, + description: "Policy update failed. Please try again.", + message: "Update Failed", + notFound: { + description: "The policy or version you are trying to update was not found.", + message: "Not Found" + }, + serverError: { + description: "A server error occurred while updating the policy. Please try again later.", + message: "Server Error" + } + }, + success: { + description: "Policy updated successfully.", + message: "Update Successful" + } + } + }, + pages: { deleteConfirmation: { - assertionHint: "I confirm that I want to delete this policy.", - content: "You can delete this policy only if no users have given consent to it.", - header: "Are you sure?", - message: "This action is irreversible and will permanently delete the policy.", + assertionHint: "I understand this action is permanent and cannot be undone.", + content: "This policy can only be deleted if no users have accepted it.", + header: "Delete policy?", + message: "This will permanently delete the policy. This cannot be undone.", primaryAction: "Confirm", secondaryAction: "Cancel" }, - backButton: "Go back to login & registration", - description: "Manage and configure policy consents.", - heading: "Policy Consents", - search: { - placeholder: "Search policies by name" + edit: { + backButton: "Back to Policies", + dangerZone: { + actionTitle: "Delete Policy", + header: "Delete Policy", + subheader: "Once you delete a policy, there is no going back. Please be certain." + }, + title: "Edit Policy" + }, + list: { + actions: { + addPolicy: "New Policy" + }, + description: "Manage policy consents for your organization.", + heading: "Policy Consents", + search: { + placeholder: "Search by policy name" + }, + title: "Policy Consents" }, - title: "Policy Consents" + new: { + backButton: "Back to Policies", + title: "Create Policy" + } }, - new: { - backButton: "Back to Policies", - title: "Create Policy" + wizard: { + create: { + form: { + description: { + configureTranslation: "Configure translation", + i18nCard: { + brandingRequired: "Enable branding to configure translations.", + createTitle: "Create Translation", + deleteError: { + description: "Failed to delete the translation. Please try again.", + message: "Delete Failed" + }, + deleteSuccess: { + description: "Translation deleted successfully.", + message: "Translation Deleted" + }, + deleteTooltip: "Delete the translation for the selected key.", + editTooltip: "Edit the translation text for the selected key.", + i18nKey: "Translation Key", + keyPlaceholder: "e.g. consent.description", + language: "Language", + newTooltip: "Create a new translation key for this description.", + saveError: { + description: "Failed to save the translation. Please try again.", + message: "Save Failed" + }, + saveSuccess: { + description: "Translation saved successfully.", + message: "Translation Saved" + }, + selectKey: "Select a translation key", + title: "Configure Translation", + translationPlaceholder: "Enter the translated description text...", + translationText: "Translation Text", + updateTitle: "Update Translation" + }, + insertPolicyLink: "Wrap selected text with the policy URL as a link", + insertPolicyLinkInvalidUrl: "The Policy URL must be a valid HTTP or HTTPS URL.", + insertPolicyLinkNoPolicyUrl: "Define a Policy URL above before inserting a link.", + insertPolicyLinkNoSelection: "Select the words you want to link, then click.", + insertPolicyLinkShort: "Policy Link", + insertPolicyLinkTooltip: "Wraps the selected text with your Policy URL as a hyperlink.", + labelRoleHint: "Leave empty to use the default description. Select text and click \"Policy Link\" to hyperlink it to the policy URL." + } + }, + preview: { + consentHeader: "Please review and accept the following to continue.", + emptyDescription: "Enter a policy name above to see a preview.", + exampleDescription: "I have read and agree to the <0>{{policyName}}.", + pageTitle: "Review and Accept Policies", + updatedPolicies: "Updated policies" + } + } } }, + registrationFlow: { + addAttribute: "Add Attribute", + addPurpose: "Add Purpose", + attributeDisplayName: "Display Name", + attributeName: "Attribute Name", + attributes: "User Attributes", + noPreference: "No preferences available. Create them from Preference Management.", + noPolicies: "No policies available. Create policies from Policy Consents.", + purposeDescription: "Description", + purposeLabel: "Purpose {{index}}", + purposeName: "Purpose Name", + selectPreference: "Select Preferences:", + selectPolicies: "Select Policies:" + }, tabs: { content: { label: "Content" }, preview: { label: "Preview" } - }, - wizard: { - create: { - form: { - description: { - configureTranslation: "Configure translation", - i18nCard: { - brandingRequired: "Enable branding to configure translations.", - createTitle: "Create Translation", - deleteError: { - description: "Failed to delete the translation. Please try again.", - message: "Delete Failed" - }, - deleteSuccess: { - description: "Translation deleted successfully.", - message: "Translation Deleted" - }, - deleteTooltip: "Delete the translation for the selected key.", - editTooltip: "Edit the translation text for the selected key.", - i18nKey: "Translation Key", - keyPlaceholder: "e.g. consent.description", - language: "Language", - newTooltip: "Create a new translation key for this description.", - saveError: { - description: "Failed to save the translation. Please try again.", - message: "Save Failed" - }, - saveSuccess: { - description: "Translation saved successfully.", - message: "Translation Saved" - }, - selectKey: "Select a translation key", - title: "Configure Translation", - translationPlaceholder: "Enter the translated description text...", - translationText: "Translation Text", - updateTitle: "Update Translation" - }, - insertPolicyLink: "Wrap selected text with the policy URL as a link", - insertPolicyLinkInvalidUrl: "The Policy URL must be a valid HTTP or HTTPS URL.", - insertPolicyLinkNoPolicyUrl: "Define a Policy URL above before inserting a link.", - insertPolicyLinkNoSelection: "Select the words you want to link, then click.", - insertPolicyLinkShort: "Policy Link", - insertPolicyLinkTooltip: "Wraps the selected text with your Policy URL as a hyperlink.", - labelRoleHint: "Leave empty to use the default. " + - "Select text and click \"Policy Link\" to wrap it with the policy URL." - } - }, - preview: { - allowButton: "Confirm and Continue", - appLoginMessage: "This will be the UI you see when you log in to an application.", - consentHeader: "Please review and accept the following policies to proceed.", - denyButton: "Decline", - emptyDescription: "Write a policy name to preview.", - exampleDescription: "I have read and agree to the <0>{{policyName}}.", - pageTitle: "Policy Review Required" - } - } } }; diff --git a/modules/i18n/src/translations/en-US/portals/console.ts b/modules/i18n/src/translations/en-US/portals/console.ts index dee7d0541fc..68d85012748 100644 --- a/modules/i18n/src/translations/en-US/portals/console.ts +++ b/modules/i18n/src/translations/en-US/portals/console.ts @@ -2671,10 +2671,6 @@ export const console: ConsoleNS = { subTitle: "Create and manage your applications and configure sign-in.", title: "Applications" }, - consents: { - description: "Manage and configure user consents.", - title: "Consents" - }, applicationsEdit: { backButton: "Go back to Applications", subTitle: null, diff --git a/modules/i18n/src/translations/en-US/portals/flows.ts b/modules/i18n/src/translations/en-US/portals/flows.ts index 25bde67acd7..3c669cea9c8 100644 --- a/modules/i18n/src/translations/en-US/portals/flows.ts +++ b/modules/i18n/src/translations/en-US/portals/flows.ts @@ -59,6 +59,12 @@ export const flows: flowsNS = { }, placeholder: "Enter rich text content here..." }, + preferenceManagement: { + emptyState: "No purposes configured" + }, + policyConsent: { + emptyState: "No policies configured" + }, textPropertyField: { i18nCard: { chip: { @@ -285,6 +291,11 @@ export const flows: flowsNS = { identifier: "Phone number field must be mapped to an attribute for data collection.", label: "Phone number field must have a label to be displayed to users." }, + preferenceManagement: { + general: "Required fields are not properly configured for the communication preference.", + purposeAttributesRequired: "Purpose \"{{purposeName}}\" must have at least one attribute selected.", + purposesRequired: "At least one purpose must be added." + }, policyConsent: { general: "Required fields are not properly configured for the policy consent.", noPoliciesAvailable: "No policies are available for selection.", diff --git a/modules/i18n/src/translations/en-US/portals/governance-connectors.ts b/modules/i18n/src/translations/en-US/portals/governance-connectors.ts index b223e78953c..1696a8f610b 100644 --- a/modules/i18n/src/translations/en-US/portals/governance-connectors.ts +++ b/modules/i18n/src/translations/en-US/portals/governance-connectors.ts @@ -747,10 +747,15 @@ export const governanceConnectors: governanceConnectorsNS = { } }, consentManagement: { - categoryTitle: "Consent Management", + categoryTitle: "Consent & Preference Management", connectors: { + preferenceManagement: { + description: "Manage communication preferences for your organization.", + friendlyName: "Preference Management", + header: "Preference Management" + }, policyConsents: { - description: "Manage and configure user policy consents.", + description: "Manage policy consents for your organization.", friendlyName: "Policy Consents", header: "Policy Consents" } diff --git a/modules/i18n/src/translations/en-US/portals/myaccount.ts b/modules/i18n/src/translations/en-US/portals/myaccount.ts index fb5fff85aa7..02bb516b1e9 100644 --- a/modules/i18n/src/translations/en-US/portals/myaccount.ts +++ b/modules/i18n/src/translations/en-US/portals/myaccount.ts @@ -370,20 +370,14 @@ export const myAccount: MyAccountNS = { } }, description: "Description", - piiCategoryHeading: - "Manage consent for the collection and sharing of your personal information " + - "with the application. Uncheck the attributes that you need to revoke and press the update " + - "button to save the changes or press the revoke button to remove the consent for all the " + - "attributes.", + piiCategoryHeading: "Manage consent for the collection and sharing of your personal information with the application. Uncheck the attributes that you need to revoke and press the update button to save the changes or press the revoke button to remove the consent for all the attributes.", state: "State", version: "Version" }, modals: { consentRevokeModal: { heading: "Are you sure?", - message: - "This operation is not reversible. This will permanently revoke consent for all the " + - "attributes. Are you sure you want to proceed?", + message: "This operation is not reversible. This will permanently revoke consent for all the attributes. Are you sure you want to proceed?", warning: "Please note that you will be redirected to the login consent page" } }, @@ -447,20 +441,12 @@ export const myAccount: MyAccountNS = { } }, policyConsentManagement: { - consentedOnLabel: "Active from", + consentedOnLabel: "Accepted on", dangerZones: { revoke: { actionTitle: "Revoke", header: "Revoke Policy Consent", - subheader: "This action will revoke your consent for this policy. You may be asked to re-consent " + - "the next time you access the service." - } - }, - modals: { - revokeModal: { - heading: "Revoke consent for {{policyName}}", - message: "This will revoke your consent for this policy. You may be asked to re-consent the " + - "next time you access the service. Are you sure you want to continue?" + subheader: "This action will revoke your consent for this policy. You may be asked to re-consent the next time you access the service." } }, notifications: { @@ -496,6 +482,63 @@ export const myAccount: MyAccountNS = { policyUrlLabel: "View Policy", versionLabel: "Version {{version}}" }, + preferenceManagement: { + consentedOnLabel: "Accepted on", + elementsHeading: "Manage your communication preferences. Uncheck the attributes that you need to revoke and press the update button to save the changes or press the revoke button to remove the consent for all the attributes.", + dangerZones: { + revoke: { + actionTitle: "Revoke", + header: "Revoke Communication Preference", + subheader: "This action will revoke your consent for this communication preference." + } + }, + notifications: { + fetch: { + error: { + description: "An error occurred while retrieving your communication preferences.", + message: "Retrieval Failed" + }, + genericError: { + description: "An error occurred while retrieving your communication preferences.", + message: "Retrieval Failed" + }, + success: { + description: "Your communication preferences were retrieved successfully.", + message: "Retrieval Successful" + } + }, + revoke: { + error: { + description: "An error occurred while revoking the communication preference.", + message: "Revoke Failed" + }, + genericError: { + description: "An error occurred while revoking the communication preference.", + message: "Revoke Failed" + }, + success: { + description: "The communication preference has been revoked successfully.", + message: "Preference Revoked" + } + }, + update: { + error: { + description: "An error occurred while updating the communication preference.", + message: "Update Failed" + }, + genericError: { + description: "An error occurred while updating the communication preference.", + message: "Update Failed" + }, + success: { + description: "Your communication preferences have been updated successfully.", + message: "Preferences Updated" + } + } + }, + policyUrlLabel: "View Policy", + versionLabel: "Version {{version}}" + }, cookieConsent: { confirmButton: "Got It", content: "We use cookies to ensure that you get the best overall experience. These cookies are used to " + @@ -1753,7 +1796,7 @@ export const myAccount: MyAccountNS = { title: "Applications" }, consents: { - subTitle: "Manage the consents you have provided for applications and accepted policies", + subTitle: "View and manage consents for applications and policies you have accepted", title: "Consents" }, overview: { @@ -1850,8 +1893,7 @@ export const myAccount: MyAccountNS = { empty: "You have not granted consent to any application" }, description: - "Review the consents you have provided for each application. " + - "Also, you can revoke one or many of them as required.", + "Review consents granted to each application and revoke them as needed.", heading: "Manage Consents", placeholders: { emptyConsentList: { @@ -1859,8 +1901,17 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + description: "Review and manage your communication preferences.", + heading: "Communication Preferences", + placeholders: { + emptyConsentList: { + heading: "You have not set any communication preference" + } + } + }, policyConsentManagement: { - description: "Review the policies you have accepted.", + description: "Review the policies you have agreed to.", heading: "Policy Consents", placeholders: { emptyConsentList: { diff --git a/modules/i18n/src/translations/es-ES/portals/common.ts b/modules/i18n/src/translations/es-ES/portals/common.ts index 6e12c3cb49c..a3273b8240a 100644 --- a/modules/i18n/src/translations/es-ES/portals/common.ts +++ b/modules/i18n/src/translations/es-ES/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { completed: "TERMINADO", configure: "Configurar", confirm: "Confirmar", + consents: "Consentimientos", contains: "contiene", continue: "SEGUIR", copied: "Copiado!", diff --git a/modules/i18n/src/translations/es-ES/portals/myaccount.ts b/modules/i18n/src/translations/es-ES/portals/myaccount.ts index 1f90bdb777a..67a9c15b5d6 100644 --- a/modules/i18n/src/translations/es-ES/portals/myaccount.ts +++ b/modules/i18n/src/translations/es-ES/portals/myaccount.ts @@ -439,6 +439,63 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "consentedOnLabel": "Aceptado el", + "dangerZones": { + "revoke": { + "actionTitle": "Revocar", + "header": "Revocar preferencia de comunicación", + "subheader": "Esta acción eliminará su preferencia para esta comunicación." + } + }, + "elementsHeading": "Administre sus preferencias de comunicación a continuación. Desmarque los atributos que necesita revocar y presione el botón actualizar para guardar los cambios o presione el botón revocar para eliminar la preferencia de todos los atributos.", + "notifications": { + "fetch": { + "error": { + "description": "Se produjo un error al recuperar sus preferencias de comunicación.", + "message": "Recuperación fallida" + }, + "genericError": { + "description": "Se produjo un error al recuperar sus preferencias de comunicación.", + "message": "Recuperación fallida" + }, + "success": { + "description": "Sus preferencias de comunicación se recuperaron correctamente.", + "message": "Recuperación exitosa" + } + }, + "revoke": { + "error": { + "description": "Se produjo un error al revocar la preferencia de comunicación.", + "message": "Revocación fallida" + }, + "genericError": { + "description": "Se produjo un error al revocar la preferencia de comunicación.", + "message": "Revocación fallida" + }, + "success": { + "description": "La preferencia de comunicación se ha revocado correctamente.", + "message": "Preferencia revocada" + } + }, + "update": { + "error": { + "description": "Se produjo un error al actualizar la preferencia de comunicación.", + "message": "Actualización fallida" + }, + "genericError": { + "description": "Se produjo un error al actualizar la preferencia de comunicación.", + "message": "Actualización fallida" + }, + "success": { + "description": "Sus preferencias de comunicación se han actualizado correctamente.", + "message": "Preferencia actualizada" + } + } + }, + "policyUrlLabel": "Ver política", + "versionLabel": "Versión {{version}}" + }, "policyConsentManagement": { "consentedOnLabel": "Activo desde", "dangerZones": { @@ -448,12 +505,6 @@ export const myAccount: MyAccountNS = { "subheader": "Esta acción revocará su consentimiento para esta política. Se le puede pedir que vuelva a consentir la próxima vez que acceda al servicio." } }, - "modals": { - "revokeModal": { - "heading": "Revocar consentimiento para {{policyName}}", - "message": "Esto revocará su consentimiento para esta política. Se le puede pedir que vuelva a consentir la próxima vez que acceda al servicio. ¿Está seguro de que desea continuar?" - } - }, "notifications": { "fetch": { "error": { @@ -1806,6 +1857,15 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "description": "Revise las comunicaciones a las que ha dado su preferencia.", + "heading": "Preferencias de comunicación", + "placeholders": { + "emptyConsentList": { + "heading": "No ha aceptado ninguna preferencia de comunicación" + } + } + }, "policyConsentManagement": { "description": "Revise las políticas que ha aceptado.", "heading": "Consentimientos de política", diff --git a/modules/i18n/src/translations/fr-FR/portals/common.ts b/modules/i18n/src/translations/fr-FR/portals/common.ts index 706a0eb284c..7059429d01c 100755 --- a/modules/i18n/src/translations/fr-FR/portals/common.ts +++ b/modules/i18n/src/translations/fr-FR/portals/common.ts @@ -177,6 +177,7 @@ export const common: CommonNS = { completed: "Terminé", configure: "Configurer", confirm: "Confirmer", + consents: "Consentements", contains: "Contient", continue: "Continuer", copied: "Copié!", diff --git a/modules/i18n/src/translations/fr-FR/portals/myaccount.ts b/modules/i18n/src/translations/fr-FR/portals/myaccount.ts index e004b116a67..bab700726a3 100644 --- a/modules/i18n/src/translations/fr-FR/portals/myaccount.ts +++ b/modules/i18n/src/translations/fr-FR/portals/myaccount.ts @@ -369,25 +369,18 @@ export const myAccount: MyAccountNS = { revoke: { actionTitle: "Révoquer", header: "Révoquer le consentement", - subheader: "Vous devrez à nouveau donner votre consentement pour accéder à nouveau à " + - "cette application." + subheader: "Vous devrez à nouveau donner votre consentement pour accéder à nouveau à cette application." } }, description: "Description", - piiCategoryHeading: - "Gérez le consentement pour la collecte et le partage de vos informations personnelles " + - "avec l'application. Décochez les attributs que vous souhaitez révoquer puis validez en " + - "cliquant sur le bouton de Mettre à jour pour enregistrer les modifications ou cliquez " + - "sur le bouton de Révoquer pour supprimer le consentement pour tous les attributs.", + piiCategoryHeading: "Gérez le consentement pour la collecte et le partage de vos informations personnelles avec l'application. Décochez les attributs que vous souhaitez révoquer puis validez en cliquant sur le bouton de Mettre à jour pour enregistrer les modifications ou cliquez sur le bouton de Révoquer pour supprimer le consentement pour tous les attributs.", state: "État", version: "Version" }, modals: { consentRevokeModal: { heading: "Etes-vous sûr ?", - message: - "Cette opération est irréversible. Cela révoquera définitivement le consentement pour " + - "tous les attributs. Êtes-vous sûr de vouloir continuer ?", + message: "Cette opération est irréversible. Cela révoquera définitivement le consentement pour tous les attributs. Êtes-vous sûr de vouloir continuer ?", warning: "Veuillez noter que vous serez redirigé vers la page de recueil de consentement" } }, @@ -450,6 +443,63 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + consentedOnLabel: "Accepté le", + dangerZones: { + revoke: { + actionTitle: "Révoquer", + header: "Révoquer la préférence de communication", + subheader: "Cette action supprimera votre préférence pour cette communication." + } + }, + elementsHeading: "Mettez à jour vos préférences ci-dessous. Décochez les attributs auxquels vous souhaitez vous désabonner et appuyez sur le bouton de mise à jour pour enregistrer les modifications ou appuyez sur le bouton de révocation pour supprimer la préférence pour tous les attributs.", + notifications: { + fetch: { + error: { + description: "Une erreur s'est produite lors de la récupération de vos préférences de communication.", + message: "Échec de la récupération" + }, + genericError: { + description: "Une erreur s'est produite lors de la récupération de vos préférences de communication.", + message: "Échec de la récupération" + }, + success: { + description: "Vos préférences de communication ont été récupérées avec succès.", + message: "Récupération réussie" + } + }, + revoke: { + error: { + description: "Une erreur s'est produite lors de la révocation de la préférence de communication.", + message: "Échec de la révocation" + }, + genericError: { + description: "Une erreur s'est produite lors de la révocation de la préférence de communication.", + message: "Échec de la révocation" + }, + success: { + description: "La préférence de communication a été révoquée avec succès.", + message: "Préférence de communication révoquée" + } + }, + update: { + error: { + description: "Une erreur s'est produite lors de la mise à jour de la préférence de communication.", + message: "Échec de la mise à jour" + }, + genericError: { + description: "Une erreur s'est produite lors de la mise à jour de la préférence de communication.", + message: "Échec de la mise à jour" + }, + success: { + description: "Vos préférences de communication ont été mises à jour avec succès.", + message: "Préférence de communication mise à jour" + } + } + }, + policyUrlLabel: "Voir la politique", + versionLabel: "Version {{version}}" + }, policyConsentManagement: { consentedOnLabel: "Actif depuis", dangerZones: { @@ -459,12 +509,6 @@ export const myAccount: MyAccountNS = { subheader: "Cette action révoquera votre consentement pour cette politique. Vous pourrez être invité à donner à nouveau votre consentement la prochaine fois que vous accédez au service." } }, - modals: { - revokeModal: { - heading: "Révoquer le consentement pour {{policyName}}", - message: "Cela révoquera votre consentement pour cette politique. Vous pourrez être invité à donner à nouveau votre consentement la prochaine fois que vous accédez au service. Êtes-vous sûr de vouloir continuer ?" - } - }, notifications: { fetch: { error: { @@ -1900,6 +1944,15 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + description: "Examinez et gérez vos préférences de communication.", + heading: "Préférences de communication", + placeholders: { + emptyConsentList: { + heading: "Vous n'avez défini aucune préférence de communication" + } + } + }, policyConsentManagement: { description: "Examinez les politiques que vous avez acceptées.", heading: "Consentements de politique", diff --git a/modules/i18n/src/translations/ja-JP/portals/common.ts b/modules/i18n/src/translations/ja-JP/portals/common.ts index 243b97a7fff..af4f081f75d 100644 --- a/modules/i18n/src/translations/ja-JP/portals/common.ts +++ b/modules/i18n/src/translations/ja-JP/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { "completed": "完了しました", "configure": "構成、設定", "confirm": "確認する", + "consents": "同意", "contains": "含む", "continue": "続く", "copied": "コピーしました", diff --git a/modules/i18n/src/translations/ja-JP/portals/myaccount.ts b/modules/i18n/src/translations/ja-JP/portals/myaccount.ts index 30417139187..6a12d78890e 100644 --- a/modules/i18n/src/translations/ja-JP/portals/myaccount.ts +++ b/modules/i18n/src/translations/ja-JP/portals/myaccount.ts @@ -437,6 +437,63 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "consentedOnLabel": "承諾日", + "dangerZones": { + "revoke": { + "actionTitle": "取り消す", + "header": "マーケティング同意の取り消し", + "subheader": "このアクションにより、このマーケティングコミュニケーションへの同意が取り消されます。" + } + }, + "elementsHeading": "マーケティングコミュニケーションの同意を管理します。取り消す必要がある属性のチェックを外し、更新ボタンを押して変更を保存するか、取り消しボタンを押してすべての属性の同意を削除してください。", + "notifications": { + "fetch": { + "error": { + "description": "マーケティング同意の取得中にエラーが発生しました。", + "message": "取得に失敗しました" + }, + "genericError": { + "description": "マーケティング同意の取得中にエラーが発生しました。", + "message": "取得に失敗しました" + }, + "success": { + "description": "マーケティング同意が正常に取得されました。", + "message": "取得成功" + } + }, + "revoke": { + "error": { + "description": "マーケティング同意の取り消し中にエラーが発生しました。", + "message": "取り消しに失敗しました" + }, + "genericError": { + "description": "マーケティング同意の取り消し中にエラーが発生しました。", + "message": "取り消しに失敗しました" + }, + "success": { + "description": "マーケティング同意が正常に取り消されました。", + "message": "同意が取り消されました" + } + }, + "update": { + "error": { + "description": "マーケティング同意の更新中にエラーが発生しました。", + "message": "更新に失敗しました" + }, + "genericError": { + "description": "マーケティング同意の更新中にエラーが発生しました。", + "message": "更新に失敗しました" + }, + "success": { + "description": "マーケティング同意の設定が正常に更新されました。", + "message": "同意が更新されました" + } + } + }, + "policyUrlLabel": "ポリシーを表示", + "versionLabel": "バージョン {{version}}" + }, "policyConsentManagement": { "consentedOnLabel": "有効開始日", "dangerZones": { @@ -446,12 +503,6 @@ export const myAccount: MyAccountNS = { "subheader": "このアクションはこのポリシーに対するあなたの同意を取り消します。次回サービスにアクセスするときに、再度同意するよう求められる場合があります。" } }, - "modals": { - "revokeModal": { - "heading": "{{policyName}} の同意を取り消す", - "message": "これはこのポリシーに対するあなたの同意を取り消します。次回サービスにアクセスするときに、再度同意するよう求められる場合があります。続行してよろしいですか?" - } - }, "notifications": { "fetch": { "error": { @@ -1802,6 +1853,15 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "description": "同意したマーケティングコミュニケーションを確認します。", + "heading": "マーケティング同意", + "placeholders": { + "emptyConsentList": { + "heading": "マーケティング同意をまだ承諾していません" + } + } + }, "policyConsentManagement": { "description": "受け入れたポリシーを確認します。", "heading": "ポリシー同意", diff --git a/modules/i18n/src/translations/nl-NL/portals/common.ts b/modules/i18n/src/translations/nl-NL/portals/common.ts index b6d7a5351b0..912d274aee8 100644 --- a/modules/i18n/src/translations/nl-NL/portals/common.ts +++ b/modules/i18n/src/translations/nl-NL/portals/common.ts @@ -177,6 +177,7 @@ export const common: CommonNS = { completed: "Voltooid", configure: "Configureren", confirm: "Bevestigen", + consents: "Toestemmingen", contains: "Bevat", continue: "Doorgaan", copied: "Gekopieerd!", diff --git a/modules/i18n/src/translations/nl-NL/portals/myaccount.ts b/modules/i18n/src/translations/nl-NL/portals/myaccount.ts index 5ecbe7d30a5..772fd049eb3 100644 --- a/modules/i18n/src/translations/nl-NL/portals/myaccount.ts +++ b/modules/i18n/src/translations/nl-NL/portals/myaccount.ts @@ -433,6 +433,63 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "consentedOnLabel": "Geaccepteerd op", + "dangerZones": { + "revoke": { + "actionTitle": "Intrekken", + "header": "Communicatievoorkeur intrekken", + "subheader": "Deze actie verwijdert uw voorkeur voor deze communicatie." + } + }, + "elementsHeading": "Beheer uw communicatievoorkeuren hieronder. Schakel de attributen uit die u wilt intrekken en druk op de knop bijwerken om de wijzigingen op te slaan of druk op de knop intrekken om de voorkeur voor alle attributen te verwijderen.", + "notifications": { + "fetch": { + "error": { + "description": "Er is een fout opgetreden bij het ophalen van uw communicatievoorkeuren.", + "message": "Ophalen mislukt" + }, + "genericError": { + "description": "Er is een fout opgetreden bij het ophalen van uw communicatievoorkeuren.", + "message": "Ophalen mislukt" + }, + "success": { + "description": "Uw communicatievoorkeuren zijn succesvol opgehaald.", + "message": "Ophalen geslaagd" + } + }, + "revoke": { + "error": { + "description": "Er is een fout opgetreden bij het intrekken van de communicatievoorkeur.", + "message": "Intrekken mislukt" + }, + "genericError": { + "description": "Er is een fout opgetreden bij het intrekken van de communicatievoorkeur.", + "message": "Intrekken mislukt" + }, + "success": { + "description": "De communicatievoorkeur is succesvol ingetrokken.", + "message": "Communicatievoorkeur ingetrokken" + } + }, + "update": { + "error": { + "description": "Er is een fout opgetreden bij het bijwerken van de communicatievoorkeur.", + "message": "Bijwerken mislukt" + }, + "genericError": { + "description": "Er is een fout opgetreden bij het bijwerken van de communicatievoorkeur.", + "message": "Bijwerken mislukt" + }, + "success": { + "description": "Uw communicatievoorkeuren zijn succesvol bijgewerkt.", + "message": "Communicatievoorkeur bijgewerkt" + } + } + }, + "policyUrlLabel": "Beleid bekijken", + "versionLabel": "Versie {{version}}" + }, "policyConsentManagement": { "consentedOnLabel": "Actief vanaf", "dangerZones": { @@ -442,12 +499,6 @@ export const myAccount: MyAccountNS = { "subheader": "Deze actie trekt uw toestemming voor dit beleid in. Mogelijk wordt u gevraagd opnieuw toestemming te geven wanneer u de service de volgende keer gebruikt." } }, - "modals": { - "revokeModal": { - "heading": "Toestemming intrekken voor {{policyName}}", - "message": "Hiermee trekt u uw toestemming voor dit beleid in. Mogelijk wordt u gevraagd opnieuw toestemming te geven wanneer u de service de volgende keer gebruikt. Weet u zeker dat u wilt doorgaan?" - } - }, "notifications": { "fetch": { "error": { @@ -1797,6 +1848,15 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "description": "Bekijk en beheer uw communicatievoorkeuren.", + "heading": "Communicatievoorkeuren", + "placeholders": { + "emptyConsentList": { + "heading": "U heeft geen communicatievoorkeuren ingesteld" + } + } + }, "policyConsentManagement": { "description": "Bekijk de beleidsregels die u heeft geaccepteerd.", "heading": "Beleidstoestemmingen", diff --git a/modules/i18n/src/translations/pt-BR/portals/common.ts b/modules/i18n/src/translations/pt-BR/portals/common.ts index 6366d2c1d22..5e032b3a4a7 100755 --- a/modules/i18n/src/translations/pt-BR/portals/common.ts +++ b/modules/i18n/src/translations/pt-BR/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { completed: "Concluído", configure: "Configurar", confirm: "Confirmar", + consents: "Consentimentos", contains: "Contém", continue: "Continuar", copied: "Copiado!", diff --git a/modules/i18n/src/translations/pt-BR/portals/myaccount.ts b/modules/i18n/src/translations/pt-BR/portals/myaccount.ts index 6ce1715389f..7adeef5f049 100644 --- a/modules/i18n/src/translations/pt-BR/portals/myaccount.ts +++ b/modules/i18n/src/translations/pt-BR/portals/myaccount.ts @@ -369,10 +369,7 @@ export const myAccount: MyAccountNS = { } }, description: "Descrição", - piiCategoryHeading: "Gerencie o consentimento para a coleta e o compartilhamento de suas informações " + - "pessoais com o aplicativo. Desmarque os atributos que você precisa revogar e pressione o botão " + - "atualizar para salvar as alterações ou pressione o botão revogar para remover o consentimento " + - "para todos os atributos.", + piiCategoryHeading: "Gerencie o consentimento para a coleta e o compartilhamento de suas informações pessoais com o aplicativo. Desmarque os atributos que você precisa revogar e pressione o botão atualizar para salvar as alterações ou pressione o botão revogar para remover o consentimento para todos os atributos.", state: "Estado", version: "Versão" }, @@ -442,21 +439,70 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + consentedOnLabel: "Aceito em", + dangerZones: { + revoke: { + actionTitle: "Revogar", + header: "Revogar preferência de comunicação", + subheader: "Esta ação removerá sua preferência para esta comunicação." + } + }, + elementsHeading: "Gerencie suas preferências de comunicação abaixo. Desmarque os atributos que você precisa revogar e pressione o botão atualizar para salvar as alterações ou pressione o botão revogar para remover a preferência de todos os atributos.", + notifications: { + fetch: { + error: { + description: "Ocorreu um erro ao recuperar suas preferências de comunicação.", + message: "Falha na recuperação" + }, + genericError: { + description: "Ocorreu um erro ao recuperar suas preferências de comunicação.", + message: "Falha na recuperação" + }, + success: { + description: "Suas preferências de comunicação foram recuperadas com sucesso.", + message: "Recuperação bem-sucedida" + } + }, + revoke: { + error: { + description: "Ocorreu um erro ao revogar a preferência de comunicação.", + message: "Falha na revogação" + }, + genericError: { + description: "Ocorreu um erro ao revogar a preferência de comunicação.", + message: "Falha na revogação" + }, + success: { + description: "A preferência de comunicação foi revogada com sucesso.", + message: "Preferência de comunicação revogada" + } + }, + update: { + error: { + description: "Ocorreu um erro ao atualizar a preferência de comunicação.", + message: "Falha na atualização" + }, + genericError: { + description: "Ocorreu um erro ao atualizar a preferência de comunicação.", + message: "Falha na atualização" + }, + success: { + description: "Suas preferências de comunicação foram atualizadas com sucesso.", + message: "Preferência de comunicação atualizada" + } + } + }, + policyUrlLabel: "Ver política", + versionLabel: "Versão {{version}}" + }, policyConsentManagement: { consentedOnLabel: "Ativo desde", dangerZones: { revoke: { actionTitle: "Revogar", header: "Revogar Consentimento de Política", - subheader: "Esta ação revogará seu consentimento para esta política. Você pode ser solicitado a consentir novamente " + - "na próxima vez que acessar o serviço." - } - }, - modals: { - revokeModal: { - heading: "Revogar consentimento para {{policyName}}", - message: "Isso revogará seu consentimento para esta política. Você pode ser solicitado a consentir novamente " + - "na próxima vez que acessar o serviço. Tem certeza de que deseja continuar?" + subheader: "Esta ação revogará seu consentimento para esta política. Você pode ser solicitado a consentir novamente na próxima vez que acessar o serviço." } }, notifications: { @@ -1812,6 +1858,15 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + description: "Revise e gerencie suas preferências de comunicação.", + heading: "Preferências de comunicação", + placeholders: { + emptyConsentList: { + heading: "Você não aceitou nenhuma preferência de comunicação" + } + } + }, policyConsentManagement: { description: "Revise as políticas que você aceitou.", heading: "Consentimentos de Política", diff --git a/modules/i18n/src/translations/pt-PT/portals/common.ts b/modules/i18n/src/translations/pt-PT/portals/common.ts index 44a62cc3bb7..a0597a23c40 100755 --- a/modules/i18n/src/translations/pt-PT/portals/common.ts +++ b/modules/i18n/src/translations/pt-PT/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { completed: "Concluído", configure: "Configurar", confirm: "Confirme", + consents: "Consentimentos", contains: "Contém", continue: "Continuar", copied: "Copiado!", diff --git a/modules/i18n/src/translations/pt-PT/portals/myaccount.ts b/modules/i18n/src/translations/pt-PT/portals/myaccount.ts index e6a1988db71..17c1d29196f 100644 --- a/modules/i18n/src/translations/pt-PT/portals/myaccount.ts +++ b/modules/i18n/src/translations/pt-PT/portals/myaccount.ts @@ -371,18 +371,14 @@ export const myAccount: MyAccountNS = { } }, description: "Descrição", - piiCategoryHeading: "Gerencie o consentimento para a coleta e o compartilhamento de suas informações " + - "pessoais com o aplicativo. Desmarque os atributos que você precisa revogar e pressione o botão " + - "atualizar para salvar as alterações ou pressione o botão revogar para remover o consentimento " + - "para todos os atributos.", + piiCategoryHeading: "Gerencie o consentimento para a coleta e o compartilhamento de suas informações pessoais com o aplicativo. Desmarque os atributos que você precisa revogar e pressione o botão atualizar para salvar as alterações ou pressione o botão revogar para remover o consentimento para todos os atributos.", state: "Estado", version: "Versão" }, modals: { consentRevokeModal: { heading: "Você tem certeza?", - message: "Esta operação não é reversível. Isso revogará permanentemente o consentimento para " + - "todos os atributos. Tem certeza de que deseja continuar?", + message: "Esta operação não é reversível. Isso revogará permanentemente o consentimento para todos os atributos. Tem certeza de que deseja continuar?", warning: "Observe que você será redirecionado para a página de consentimento de login" } }, @@ -445,21 +441,70 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + consentedOnLabel: "Aceite em", + dangerZones: { + revoke: { + actionTitle: "Revogar", + header: "Revogar preferência de comunicação", + subheader: "Esta ação irá remover a sua preferência para esta comunicação." + } + }, + elementsHeading: "Gira as suas preferências de comunicação abaixo. Desmarque os atributos que pretende revogar e prima o botão atualizar para guardar as alterações ou prima o botão revogar para remover a preferência de todos os atributos.", + notifications: { + fetch: { + error: { + description: "Ocorreu um erro ao recuperar as suas preferências de comunicação.", + message: "Falha na recuperação" + }, + genericError: { + description: "Ocorreu um erro ao recuperar as suas preferências de comunicação.", + message: "Falha na recuperação" + }, + success: { + description: "As suas preferências de comunicação foram recuperadas com sucesso.", + message: "Recuperação bem-sucedida" + } + }, + revoke: { + error: { + description: "Ocorreu um erro ao revogar a preferência de comunicação.", + message: "Falha na revogação" + }, + genericError: { + description: "Ocorreu um erro ao revogar a preferência de comunicação.", + message: "Falha na revogação" + }, + success: { + description: "A preferência de comunicação foi revogada com sucesso.", + message: "Consentimento revogado" + } + }, + update: { + error: { + description: "Ocorreu um erro ao atualizar a preferência de comunicação.", + message: "Falha na atualização" + }, + genericError: { + description: "Ocorreu um erro ao atualizar a preferência de comunicação.", + message: "Falha na atualização" + }, + success: { + description: "As suas preferências de comunicação foram atualizadas com sucesso.", + message: "Consentimento atualizado" + } + } + }, + policyUrlLabel: "Ver política", + versionLabel: "Versão {{version}}" + }, policyConsentManagement: { consentedOnLabel: "Ativo desde", dangerZones: { revoke: { actionTitle: "Revogar", header: "Revogar Consentimento de Política", - subheader: "Esta ação revogará seu consentimento para esta política. Você pode ser solicitado a consentir novamente " + - "na próxima vez que acessar o serviço." - } - }, - modals: { - revokeModal: { - heading: "Revogar consentimento para {{policyName}}", - message: "Isso revogará seu consentimento para esta política. Você pode ser solicitado a consentir novamente " + - "na próxima vez que acessar o serviço. Tem certeza de que deseja continuar?" + subheader: "Esta ação revogará seu consentimento para esta política. Você pode ser solicitado a consentir novamente na próxima vez que acessar o serviço." } }, notifications: { @@ -1859,6 +1904,15 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + description: "Reveja e gira as suas preferências de comunicação.", + heading: "Preferências de comunicação", + placeholders: { + emptyConsentList: { + heading: "Não aceitou nenhuma preferência de comunicação" + } + } + }, policyConsentManagement: { description: "Revise as políticas que aceitou.", heading: "Consentimentos de Política", diff --git a/modules/i18n/src/translations/si-LK/portals/common.ts b/modules/i18n/src/translations/si-LK/portals/common.ts index c2b8072d849..8b8030a7033 100755 --- a/modules/i18n/src/translations/si-LK/portals/common.ts +++ b/modules/i18n/src/translations/si-LK/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { completed: "සම්පුර්ණ කරන ලද", configure: "වින්\u200Dයාස කරන්න", confirm: "තහවුරු කරන්න", + consents: "එකඟතාවයන්", contains: "අඩංගු වේ", continue: "පවත්වාගෙන යන්න", copied: "පිටපත් කළා!", diff --git a/modules/i18n/src/translations/si-LK/portals/myaccount.ts b/modules/i18n/src/translations/si-LK/portals/myaccount.ts index 4ced8c9ff8c..2a7ce2fe91a 100644 --- a/modules/i18n/src/translations/si-LK/portals/myaccount.ts +++ b/modules/i18n/src/translations/si-LK/portals/myaccount.ts @@ -371,19 +371,14 @@ export const myAccount: MyAccountNS = { } }, description: "සටහන", - piiCategoryHeading: - "ඇප් එක සමඟ ඔබේ පුද්ගලික තොරතුරු එකතු කිරීම සහ බෙදා ගැනීම සඳහා කැමැත්ත කළමනාකරණය කරන්න. " + - "ඔබට අවලංගු කිරීමට අවශ්‍ය ගුණාංග ඉවත් කර යාවත්කාලීන සුරැකීමට යාවත්කාලීන බොත්තම ඔබන්න. " + - "සියලු ගුණාංග සඳහා කැමැත්ත ඉවත් කිරීමට අවලංගු කිරීමේ බොත්තම ඔබන්න.", + piiCategoryHeading: "ඇප් එක සමඟ ඔබේ පුද්ගලික තොරතුරු එකතු කිරීම සහ බෙදා ගැනීම සඳහා කැමැත්ත කළමනාකරණය කරන්න. ඔබට අවලංගු කිරීමට අවශ්‍ය ගුණාංග ඉවත් කර යාවත්කාලීන සුරැකීමට යාවත්කාලීන බොත්තම ඔබන්න. සියලු ගුණාංග සඳහා කැමැත්ත ඉවත් කිරීමට අවලංගු කිරීමේ බොත්තම ඔබන්න.", state: "තත්වය", version: "පිටපත" }, modals: { consentRevokeModal: { heading: "ඔබට විශ්වාසද?", - message: - "මෙම මෙහෙයුම ආපසු හැරවිය නොහැක. මෙය සියලු ගුණාංග සඳහා වන කැමැත්ත ස්ථිරවම අවලංගු කරනු ඇත. ඔබට " + - "ඉදිරියට යාමට අවශ්‍ය බව ඔබට විශ්වාසද?", + message: "මෙම මෙහෙයුම ආපසු හැරවිය නොහැක. මෙය සියලු ගුණාංග සඳහා වන කැමැත්ත ස්ථිරවම අවලංගු කරනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍ය බව ඔබට විශ්වාසද?", warning: "ඔබ පිවිසුම් කැමැත්ත පිටුවට හරවා යවන බව කරුණාවෙන් සලකන්න" } }, @@ -446,22 +441,70 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + consentedOnLabel: "පිළිගත් දිනය", + dangerZones: { + revoke: { + actionTitle: "අවලංගු කරන්න", + header: "අලෙවිකරණ කැමැත්ත අවලංගු කරන්න", + subheader: "මෙම ක්‍රියාව ඔබේ මෙම අලෙවිකරණ සන්නිවේදනයට ඇති කැමැත්ත අවලංගු කරනු ඇත." + } + }, + elementsHeading: "ඔබේ අලෙවිකරණ සන්නිවේදන කැමැත්ත කළමනාකරණය කරන්න. ඔබ අවලංගු කළ යුතු ගුණාංග අවලංගු කර, වෙනස්කම් සුරැකීමට යාවත්කාලීන බොත්තම ඔබන්න, නැතහොත් සියලු ගුණාංග සඳහා කැමැත්ත ඉවත් කිරීමට අවලංගු කිරීමේ බොත්තම ඔබන්න.", + notifications: { + fetch: { + error: { + description: "ඔබේ අලෙවිකරණ කැමැත්ත ලබා ගැනීමේදී දෝෂයක් සිදු විය.", + message: "ලබා ගැනීම අසාර්ථක විය" + }, + genericError: { + description: "ඔබේ අලෙවිකරණ කැමැත්ත ලබා ගැනීමේදී දෝෂයක් සිදු විය.", + message: "ලබා ගැනීම අසාර්ථක විය" + }, + success: { + description: "ඔබේ අලෙවිකරණ කැමැත්ත සාර්ථකව ලබා ගන්නා ලදී.", + message: "ලබා ගැනීම සාර්ථකයි" + } + }, + revoke: { + error: { + description: "අලෙවිකරණ කැමැත්ත අවලංගු කිරීමේදී දෝෂයක් සිදු විය.", + message: "අවලංගු කිරීම අසාර්ථක විය" + }, + genericError: { + description: "අලෙවිකරණ කැමැත්ත අවලංගු කිරීමේදී දෝෂයක් සිදු විය.", + message: "අවලංගු කිරීම අසාර්ථක විය" + }, + success: { + description: "අලෙවිකරණ කැමැත්ත සාර්ථකව අවලංගු කරන ලදී.", + message: "කැමැත්ත අවලංගු කරන ලදී" + } + }, + update: { + error: { + description: "අලෙවිකරණ කැමැත්ත යාවත්කාලීන කිරීමේදී දෝෂයක් සිදු විය.", + message: "යාවත්කාලීන කිරීම අසාර්ථක විය" + }, + genericError: { + description: "අලෙවිකරණ කැමැත්ත යාවත්කාලීන කිරීමේදී දෝෂයක් සිදු විය.", + message: "යාවත්කාලීන කිරීම අසාර්ථක විය" + }, + success: { + description: "ඔබේ අලෙවිකරණ කැමැත්ත මනාපයන් සාර්ථකව යාවත්කාලීන කරන ලදී.", + message: "කැමැත්ත යාවත්කාලීන කරන ලදී" + } + } + }, + policyUrlLabel: "ප්‍රතිපත්තිය බලන්න", + versionLabel: "අනුවාදය {{version}}" + }, policyConsentManagement: { consentedOnLabel: "සිට ක්‍රියාත්මකයි", dangerZones: { revoke: { actionTitle: "අවලංගු කරන්න", header: "ප්‍රතිපත්තිය අනුමැතිය අවලංගු කරන්න", - subheader: "මෙම ක්‍රියාව ඔබගේ ඉල්ලුම අනුසාරයෙන් මෙම ප්‍රතිපත්තිය සඳහා වන අනුමැතිය අවලංගු කරනු ඇත. " + - "ඔබ ඊළඟ වතාවට සේවාවට ප්‍රවේශ වන විට නැවත අනුමැතිය දීමට ඔබෙන් ඉල්ලා සිටින්න පුළුවන්." - } - }, - modals: { - revokeModal: { - heading: "{{policyName}} සඳහා අනුමැතිය අවලංගු කරන්න", - message: "මෙය ඔබගේ {{policyName}} ප්‍රතිපත්තිය සඳහා වන අනුමැතිය අවලංගු කරනු ඇත. " + - "ඔබ ඊළඟ වතාවට සේවාවට ප්‍රවේශ වන විට නැවත අනුමැතිය දීමට ඔබෙන් ඉල්ලා සිටින්න පුළුවන්. " + - "ඔබට ඉදිරියට යාමට අවශ්‍යද?" + subheader: "මෙම ක්‍රියාව ඔබගේ ඉල්ලුම අනුසාරයෙන් මෙම ප්‍රතිපත්තිය සඳහා වන අනුමැතිය අවලංගු කරනු ඇත. ඔබ ඊළඟ වතාවට සේවාවට ප්‍රවේශ වන විට නැවත අනුමැතිය දීමට ඔබෙන් ඉල්ලා සිටින්න පුළුවන්." } }, notifications: { @@ -1860,6 +1903,15 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + description: "ඔබ කැමැත්ත ලබා දී ඇති අලෙවිකරණ සන්නිවේදන සමාලෝචනය කරන්න.", + heading: "අලෙවිකරණ කැමැත්ත", + placeholders: { + emptyConsentList: { + heading: "ඔබ කිසිදු අලෙවිකරණ කැමැත්තකට කැමැත්ත දී නොමැත" + } + } + }, policyConsentManagement: { description: "ඔබ පිළිගැනීමට ඇත්තන් ප්‍රතිපත්තිය සමාලෝචනය කරන්න.", heading: "ප්‍රතිපත්තිය අනුමැතිය", diff --git a/modules/i18n/src/translations/ta-IN/portals/common.ts b/modules/i18n/src/translations/ta-IN/portals/common.ts index 8708d47e801..794a18ad4ae 100755 --- a/modules/i18n/src/translations/ta-IN/portals/common.ts +++ b/modules/i18n/src/translations/ta-IN/portals/common.ts @@ -177,6 +177,7 @@ export const common: CommonNS = { completed: "பூரணப்படுத்தப்பட்டவை", configure: "கட்டமை", confirm: "உறுதிப்படுத்தவும்", + consents: "சம்மதங்கள்", contains: "கொண்டுள்ளது", continue: "தொடர்", copied: "நகலெடுக்கப்பட்டது!", diff --git a/modules/i18n/src/translations/ta-IN/portals/myaccount.ts b/modules/i18n/src/translations/ta-IN/portals/myaccount.ts index c8550d8c9bb..d04cb8b82f6 100644 --- a/modules/i18n/src/translations/ta-IN/portals/myaccount.ts +++ b/modules/i18n/src/translations/ta-IN/portals/myaccount.ts @@ -372,19 +372,14 @@ export const myAccount: MyAccountNS = { } }, description: "விபரம்", - piiCategoryHeading: - "உங்கள் தனிப்பட்ட தகவல்களை பயன்பாட்டுடன் சேகரிப்பதற்கும் பகிர்வதற்கும் சம்மதத்தை நிர்வகிக்கவும். " + - "மாற்றங்களைச் சேமிக்க நீங்கள் திரும்பப்பெற வேண்டிய பண்புகளைத் தேர்வுசெய்து புதுப்பிப்பு பொத்தானை " + - "அழுத்தவும் அல்லது அனைத்து பண்புகளுக்கான ஒப்புதலை நீக்க திரும்பப்பெறு பொத்தானை அழுத்தவும்.", + piiCategoryHeading: "உங்கள் தனிப்பட்ட தகவல்களை பயன்பாட்டுடன் சேகரிப்பதற்கும் பகிர்வதற்கும் சம்மதத்தை நிர்வகிக்கவும். மாற்றங்களைச் சேமிக்க நீங்கள் திரும்பப்பெற வேண்டிய பண்புகளைத் தேர்வுசெய்து புதுப்பிப்பு பொத்தானை அழுத்தவும் அல்லது அனைத்து பண்புகளுக்கான ஒப்புதலை நீக்க திரும்பப்பெறு பொத்தானை அழுத்தவும்.", state: "நிலை", version: "பதிப்பு" }, modals: { consentRevokeModal: { heading: "நீ சொல்வது உறுதியா?", - message: - "இந்த செயல்பாடு மீளக்கூடியதல்ல. இது அனைத்து பண்புகளுக்கான ஒப்புதலை நிரந்தரமாக ரத்து " + - "செய்யும். நீங்கள் நிச்சயமாக தொடர விரும்புகிறீர்களா?", + message: "இந்த செயல்பாடு மீளக்கூடியதல்ல. இது அனைத்து பண்புகளுக்கான ஒப்புதலை நிரந்தரமாக ரத்து செய்யும். நீங்கள் நிச்சயமாக தொடர விரும்புகிறீர்களா?", warning: "உள்நுழைவு ஒப்புதல் பக்கத்திற்கு நீங்கள் திருப்பி விடப்படுவீர்கள் என்பதை நினைவில் கொள்க" } }, @@ -447,6 +442,63 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + consentedOnLabel: "ஏற்றுக்கொண்ட தேதி", + dangerZones: { + revoke: { + actionTitle: "நிராகரி", + header: "சந்தைப்படுத்தல் சம்மதத்தை நிராகரி", + subheader: "இந்த செயல் இந்த சந்தைப்படுத்தல் தகவல்தொடர்புக்கான உங்கள் சம்மதத்தை நிராகரிக்கும்." + } + }, + elementsHeading: "உங்கள் சந்தைப்படுத்தல் தகவல்தொடர்பு சம்மதத்தை நிர்வகிக்கவும். நீக்க வேண்டிய பண்புகளை தேர்வு நீக்கி, மாற்றங்களை சேமிக்க புதுப்பி பொத்தானை அழுத்தவும் அல்லது அனைத்து பண்புகளுக்கும் சம்மதத்தை அகற்ற நிராகரி பொத்தானை அழுத்தவும்.", + notifications: { + fetch: { + error: { + description: "உங்கள் சந்தைப்படுத்தல் சம்மதங்களை மீட்டெடுக்கும் போது பிழை ஏற்பட்டது.", + message: "மீட்டெடுப்பு தோல்வியடைந்தது" + }, + genericError: { + description: "உங்கள் சந்தைப்படுத்தல் சம்மதங்களை மீட்டெடுக்கும் போது பிழை ஏற்பட்டது.", + message: "மீட்டெடுப்பு தோல்வியடைந்தது" + }, + success: { + description: "உங்கள் சந்தைப்படுத்தல் சம்மதங்கள் வெற்றிகரமாக மீட்டெடுக்கப்பட்டன.", + message: "மீட்டெடுப்பு வெற்றிகரமானது" + } + }, + revoke: { + error: { + description: "சந்தைப்படுத்தல் சம்மதத்தை நிராகரிக்கும் போது பிழை ஏற்பட்டது.", + message: "நிராகரிப்பு தோல்வியடைந்தது" + }, + genericError: { + description: "சந்தைப்படுத்தல் சம்மதத்தை நிராகரிக்கும் போது பிழை ஏற்பட்டது.", + message: "நிராகரிப்பு தோல்வியடைந்தது" + }, + success: { + description: "சந்தைப்படுத்தல் சம்மதம் வெற்றிகரமாக நிராகரிக்கப்பட்டது.", + message: "சம்மதம் நிராகரிக்கப்பட்டது" + } + }, + update: { + error: { + description: "சந்தைப்படுத்தல் சம்மதத்தை புதுப்பிக்கும் போது பிழை ஏற்பட்டது.", + message: "புதுப்பிப்பு தோல்வியடைந்தது" + }, + genericError: { + description: "சந்தைப்படுத்தல் சம்மதத்தை புதுப்பிக்கும் போது பிழை ஏற்பட்டது.", + message: "புதுப்பிப்பு தோல்வியடைந்தது" + }, + success: { + description: "உங்கள் சந்தைப்படுத்தல் சம்மத விருப்பங்கள் வெற்றிகரமாக புதுப்பிக்கப்பட்டன.", + message: "சம்மதம் புதுப்பிக்கப்பட்டது" + } + } + }, + policyUrlLabel: "கொள்கையை பார்க்கவும்", + versionLabel: "பதிப்பு {{version}}" + }, policyConsentManagement: { consentedOnLabel: "இதிலிருந்து செயல்பாட்டில்", dangerZones: { @@ -456,12 +508,6 @@ export const myAccount: MyAccountNS = { subheader: "இந்த செயல் இந்த கொள்கைக்கான உங்கள் சம்மதத்தை திரும்பப் பெற்றுவிடும். அடுத்த முறை நீங்கள் சேவைக்கு அணுகும்போது மீண்டும் சம்மதம் அளிக்குமாறு கேட்கப்படலாம்." } }, - modals: { - revokeModal: { - heading: "{{policyName}} க்கான சம்மதத்தை திரும்பப் பெறுக", - message: "இது இந்த கொள்கைக்கான உங்கள் சம்மதத்தை திரும்பப் பெற்றுவிடும். அடுத்த முறை நீங்கள் சேவைக்கு அணுகும்போது மீண்டும் சம்மதம் அளிக்குமாறு கேட்கப்படலாம். நீங்கள் தொடர விரும்புகிறீர்களா?" - } - }, notifications: { fetch: { error: { @@ -1874,6 +1920,15 @@ export const myAccount: MyAccountNS = { } } }, + preferenceManagement: { + description: "நீங்கள் சம்மதமளித்த சந்தைப்படுத்தல் தகவல்தொடர்புகளை மதிப்பாய்வு செய்யவும்.", + heading: "சந்தைப்படுத்தல் சம்மதங்கள்", + placeholders: { + emptyConsentList: { + heading: "நீங்கள் எந்த சந்தைப்படுத்தல் சம்மதங்களையும் ஏற்கவில்லை" + } + } + }, policyConsentManagement: { description: "நீங்கள் ஏற்றுக்கொண்ட கொள்கைகளை மதிப்பாய்வு செய்யுங்கள்.", heading: "கொள்கை சம்மதங்கள்", diff --git a/modules/i18n/src/translations/zh-CN/portals/common.ts b/modules/i18n/src/translations/zh-CN/portals/common.ts index 0995148fe06..c50a74ccd4e 100755 --- a/modules/i18n/src/translations/zh-CN/portals/common.ts +++ b/modules/i18n/src/translations/zh-CN/portals/common.ts @@ -176,6 +176,7 @@ export const common: CommonNS = { "completed": "完全的", "configure": "配置", "confirm": "确认", + "consents": "同意", "contains": "包含", "continue": "继续", "copied": "复制了", diff --git a/modules/i18n/src/translations/zh-CN/portals/myaccount.ts b/modules/i18n/src/translations/zh-CN/portals/myaccount.ts index 12772d4d6d1..7548dc0ba83 100644 --- a/modules/i18n/src/translations/zh-CN/portals/myaccount.ts +++ b/modules/i18n/src/translations/zh-CN/portals/myaccount.ts @@ -437,6 +437,63 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "consentedOnLabel": "接受于", + "dangerZones": { + "revoke": { + "actionTitle": "撤销", + "header": "撤销营销同意", + "subheader": "此操作将撤销您对此营销通信的同意。" + } + }, + "elementsHeading": "管理您的营销通信同意。取消勾选需要撤销的属性,然后按更新按钮保存更改,或按撤销按钮删除所有属性的同意。", + "notifications": { + "fetch": { + "error": { + "description": "检索您的营销同意时发生错误。", + "message": "检索失败" + }, + "genericError": { + "description": "检索您的营销同意时发生错误。", + "message": "检索失败" + }, + "success": { + "description": "您的营销同意已成功检索。", + "message": "检索成功" + } + }, + "revoke": { + "error": { + "description": "撤销营销同意时发生错误。", + "message": "撤销失败" + }, + "genericError": { + "description": "撤销营销同意时发生错误。", + "message": "撤销失败" + }, + "success": { + "description": "营销同意已成功撤销。", + "message": "同意已撤销" + } + }, + "update": { + "error": { + "description": "更新营销同意时发生错误。", + "message": "更新失败" + }, + "genericError": { + "description": "更新营销同意时发生错误。", + "message": "更新失败" + }, + "success": { + "description": "您的营销同意偏好已成功更新。", + "message": "同意已更新" + } + } + }, + "policyUrlLabel": "查看政策", + "versionLabel": "版本 {{version}}" + }, "policyConsentManagement": { "consentedOnLabel": "活跃自", "dangerZones": { @@ -446,12 +503,6 @@ export const myAccount: MyAccountNS = { "subheader": "此操作将撤销您对此政策的同意。下次您访问该服务时,可能会要求您重新同意。" } }, - "modals": { - "revokeModal": { - "heading": "撤销对 {{policyName}} 的同意", - "message": "这将撤销您对此政策的同意。下次您访问该服务时,可能会要求您重新同意。您确定要继续吗?" - } - }, "notifications": { "fetch": { "error": { @@ -1803,6 +1854,15 @@ export const myAccount: MyAccountNS = { } } }, + "preferenceManagement": { + "description": "查看您已同意的营销通信。", + "heading": "营销同意", + "placeholders": { + "emptyConsentList": { + "heading": "您尚未接受任何营销同意" + } + } + }, "policyConsentManagement": { "description": "查看您已接受的政策。", "heading": "政策同意", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b440775514b..fff5b52456a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -643,7 +643,7 @@ importers: version: 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -1020,7 +1020,7 @@ importers: version: 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -3504,10 +3504,13 @@ importers: version: 0.21.0 '@oxygen-ui/react': specifier: ^2.4.6 - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@wso2is/admin.claims.v1': + specifier: workspace:^ + version: link:../admin.claims.v1 '@wso2is/admin.core.v1': specifier: workspace:^ version: link:../admin.core.v1 @@ -3769,7 +3772,7 @@ importers: version: 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: ^2.4.5 - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@wso2is/admin.core.v1': specifier: workspace:^ version: link:../admin.core.v1 @@ -5209,7 +5212,7 @@ importers: dependencies: '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.3 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -6058,7 +6061,7 @@ importers: version: 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@wso2is/access-control': specifier: ^3.5.3 version: link:../../modules/access-control @@ -6744,7 +6747,7 @@ importers: version: 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -10484,7 +10487,7 @@ importers: version: 11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@wso2is/admin.claims.v1': specifier: ^2.30.6 version: link:../admin.claims.v1 @@ -10581,7 +10584,7 @@ importers: version: 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.3.4 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -11228,7 +11231,7 @@ importers: version: 8.28.3(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@wso2is/admin.applications.v1': specifier: ^2.42.16 version: link:../admin.applications.v1 @@ -11383,7 +11386,7 @@ importers: dependencies: '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -11754,7 +11757,7 @@ importers: version: 6.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(dayjs@1.11.20)(luxon@3.7.2)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -11876,7 +11879,7 @@ importers: version: 6.2.1(react@18.3.1) '@oxygen-ui/react': specifier: 'catalog:' - version: 2.4.6(43a10c969c8ffe9f55ba3c36a34c927d) + version: 2.4.6(6afe91b5bdd68b237ec8ccabe011f496) '@oxygen-ui/react-icons': specifier: ^2.4.6 version: 2.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) @@ -14234,18 +14237,6 @@ packages: '@mswjs/interceptors@0.12.7': resolution: {integrity: sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg==} - '@mui/base@5.0.0-alpha.108': - resolution: {integrity: sha512-KjzRUts2i/ODlMfywhFTqTzQl+Cr9nlDSZxJcnYjrbOV/iRyQNBTDoiFJt+XEdRi0fZBHnk74AFbnP56ehybsA==} - engines: {node: '>=12.0.0'} - deprecated: This package has been replaced by @base-ui/react - peerDependencies: - '@types/react': 18.0.18 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@mui/base@5.0.0-alpha.128': resolution: {integrity: sha512-wub3wxNN+hUp8hzilMlXX3sZrPo75vsy1cXEQpqdTfIFlE9HprP1jlulFiPg5tfPst2OKmygXr2hhmgvAKRrzQ==} engines: {node: '>=12.0.0'} @@ -14284,24 +14275,6 @@ packages: '@types/react': optional: true - '@mui/lab@5.0.0-alpha.110': - resolution: {integrity: sha512-SkX5QNbaWouO7BXvb8zpFzDizLt7UzgaebqKSvFJLF28OXiNDfPVCle6IIB4g7hAyb/o19Kbhxs9V+LwK5gQzA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@mui/material': ^5.13.0 - '@types/react': 18.0.18 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - '@mui/lab@5.0.0-alpha.129': resolution: {integrity: sha512-niv2mFgSTgdrRJXbWoX9pIivhe80BaFXfdWajXe1bS8VYH3Y5WyJpk8KiU3rbHyJswbFEGd8N6EBBrq11X8yMA==} engines: {node: '>=12.0.0'} @@ -14577,25 +14550,21 @@ packages: resolution: {integrity: sha512-1VS38xnAC8iH05A0nnbNn1hi9ypRnEPUfgLL3tPhAwQTWX2DQz4xR/j0NYNcCzL6yBe/JhdKlYoN/LI38lj2UA==} cpu: [arm64] os: [linux] - libc: [glibc] '@nx/nx-linux-arm64-musl@21.6.9': resolution: {integrity: sha512-PScHPs0dp+Cc17RvY4Y5wlDXT6xdDlsyhna2JLawodVCyUVArtnbF7whn/VEZKesDD/vAf1avCt4oAjuYS8VXg==} cpu: [arm64] os: [linux] - libc: [musl] '@nx/nx-linux-x64-gnu@21.6.9': resolution: {integrity: sha512-s8oX6/pLolHH3EyFJPcKITv+rzN/IZuidMCNkGfcr0jYVqrTZcJo8xUEwAQzf6u6J6urOm0bUK3BDuwJLEKESg==} cpu: [x64] os: [linux] - libc: [glibc] '@nx/nx-linux-x64-musl@21.6.9': resolution: {integrity: sha512-bojpGcscRrnet5N3waeHYnBHW0y6r5tSQ1phnwMjgoBFmWXw+0M+z/f2dfZcTtBmWc7Y/TnzaGb8EenC3a63cQ==} cpu: [x64] os: [linux] - libc: [musl] '@nx/nx-win32-arm64-msvc@21.6.9': resolution: {integrity: sha512-cS1bdMiJBs4AcykJ3+vtAdw4RkZLLfXT20o+k07dEskRFADIa5yXdOs2j0qKoe7iCiORKCH+gI/YsPHCyHfV9Q==} @@ -14687,56 +14656,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxc-parser/binding-linux-arm64-musl@0.127.0': resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxc-parser/binding-linux-riscv64-musl@0.127.0': resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxc-parser/binding-linux-s390x-gnu@0.127.0': resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxc-parser/binding-linux-x64-gnu@0.127.0': resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxc-parser/binding-linux-x64-musl@0.127.0': resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxc-parser/binding-openharmony-arm64@0.127.0': resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} @@ -14809,49 +14770,41 @@ packages: resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} cpu: [arm64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.19.1': resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==} cpu: [arm64] os: [linux] - libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==} cpu: [riscv64] os: [linux] - libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} cpu: [s390x] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.19.1': resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==} cpu: [x64] os: [linux] - libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.19.1': resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} cpu: [x64] os: [linux] - libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.19.1': resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==} @@ -14937,42 +14890,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -15203,79 +15150,66 @@ packages: resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.2': resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.2': resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.2': resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.2': resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.2': resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.2': resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.2': resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.2': resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.2': resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.2': resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.2': resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.2': resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.2': resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} @@ -15321,25 +15255,21 @@ packages: resolution: {integrity: sha512-P0QrGRPbTWu6RKWfN0bDtbnEps3rXH0MWIMreZABoUrVmNQKtXR6e73J3ub6a+di5s2+K0M2LJ9Bh2/H4UsDUA==} cpu: [arm64] os: [linux] - libc: [glibc] '@rspack/binding-linux-arm64-musl@1.7.11': resolution: {integrity: sha512-6ky7R43VMjWwmx3Yx7Jl7faLBBMAgMDt+/bN35RgwjiPgsIByz65EwytUVuW9rikB43BGHvA/eqlnjLrUzNBqw==} cpu: [arm64] os: [linux] - libc: [musl] '@rspack/binding-linux-x64-gnu@1.7.11': resolution: {integrity: sha512-cuOJMfCOvb2Wgsry5enXJ3iT1FGUjdPqtGUBVupQlEG4ntSYsQ2PtF4wIDVasR3wdxC5nQbipOrDiN/u6fYsdQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rspack/binding-linux-x64-musl@1.7.11': resolution: {integrity: sha512-CoK37hva4AmHGh3VCsQXmGr40L36m1/AdnN5LEjUX6kx5rEH7/1nEBN6Ii72pejqDVvk9anEROmPDiPw10tpFg==} cpu: [x64] os: [linux] - libc: [musl] '@rspack/binding-wasm32-wasi@1.7.11': resolution: {integrity: sha512-OtrmnPUVJMxjNa3eDMfHyPdtlLRmmp/aIm0fQHlAOATbZvlGm12q7rhPW5BXTu1yh+1rQ1/uqvz+SzKEZXuJaQ==} @@ -15793,28 +15723,24 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.5.7': resolution: {integrity: sha512-+NDhK+IFTiVK1/o7EXdCeF2hEzCiaRSrb9zD7X2Z7inwWlxAntcSuzZW7Y6BRqGQH89KA91qYgwbnjgTQ22PiQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-x64-gnu@1.5.7': resolution: {integrity: sha512-25GXpJmeFxKB+7pbY7YQLhWWjkYlR+kHz5I3j9WRl3Lp4v4UD67OGXwPe+DIcHqcouA1fhLhsgHJWtsaNOMBNg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.5.7': resolution: {integrity: sha512-0VN9Y5EAPBESmSPPsCJzplZHV26akC0sIgd3Hc/7S/1GkSMoeuVL+V9vt+F/cCuzr4VidzSkqftdP3qEIsXSpg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.5.7': resolution: {integrity: sha512-RtoNnstBwy5VloNCvmvYNApkTmuCe4sNcoYWpmY7C1+bPR+6SOo8im1G6/FpNem8AR5fcZCmXHWQ+EUmRWJyuA==} @@ -16468,49 +16394,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -22461,56 +22379,48 @@ packages: engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - libc: glibc sass-embedded-linux-arm@1.99.0: resolution: {integrity: sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - libc: glibc sass-embedded-linux-musl-arm64@1.99.0: resolution: {integrity: sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - libc: musl sass-embedded-linux-musl-arm@1.99.0: resolution: {integrity: sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - libc: musl sass-embedded-linux-musl-riscv64@1.99.0: resolution: {integrity: sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - libc: musl sass-embedded-linux-musl-x64@1.99.0: resolution: {integrity: sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - libc: musl sass-embedded-linux-riscv64@1.99.0: resolution: {integrity: sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - libc: glibc sass-embedded-linux-x64@1.99.0: resolution: {integrity: sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - libc: glibc sass-embedded-unknown-all@1.99.0: resolution: {integrity: sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==} @@ -27124,21 +27034,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@mui/base@5.0.0-alpha.108(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@emotion/is-prop-valid': 1.4.0 - '@mui/types': 7.4.12(@types/react@18.0.18) - '@mui/utils': 5.17.1(@types/react@18.0.18)(react@18.3.1) - '@popperjs/core': 2.11.8 - clsx: 1.2.1 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.3.1 - optionalDependencies: - '@types/react': 18.0.18 - '@mui/base@5.0.0-alpha.128(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.29.2 @@ -27178,24 +27073,6 @@ snapshots: optionalDependencies: '@types/react': 18.0.18 - '@mui/lab@5.0.0-alpha.110(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@mui/base': 5.0.0-alpha.108(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) - '@mui/types': 7.4.12(@types/react@18.0.18) - '@mui/utils': 5.17.1(@types/react@18.0.18)(react@18.3.1) - clsx: 1.2.1 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.3.1 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.0.18)(react@18.3.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) - '@types/react': 18.0.18 - '@mui/lab@5.0.0-alpha.129(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.29.2 @@ -27891,15 +27768,15 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@oxygen-ui/react@2.4.6(43a10c969c8ffe9f55ba3c36a34c927d)': + '@oxygen-ui/react@2.4.6(68d5af65e266984a0e5c55056d1c3c66)': dependencies: '@emotion/react': 11.14.0(@types/react@18.0.18)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) '@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) - '@mui/lab': 5.0.0-alpha.110(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/lab': 5.0.0-alpha.129(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) - '@mui/utils': 7.3.10(@types/react@18.0.18)(react@18.3.1) + '@mui/utils': 5.17.1(@types/react@18.0.18)(react@18.3.1) '@mui/x-data-grid': 6.20.4(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-date-pickers': 6.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(dayjs@1.11.20)(luxon@3.7.2)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/primitives': 2.4.6 @@ -27921,7 +27798,7 @@ snapshots: - moment-hijri - moment-jalaali - '@oxygen-ui/react@2.4.6(68d5af65e266984a0e5c55056d1c3c66)': + '@oxygen-ui/react@2.4.6(6afe91b5bdd68b237ec8ccabe011f496)': dependencies: '@emotion/react': 11.14.0(@types/react@18.0.18)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) @@ -27929,7 +27806,7 @@ snapshots: '@mui/lab': 5.0.0-alpha.129(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1) - '@mui/utils': 5.17.1(@types/react@18.0.18)(react@18.3.1) + '@mui/utils': 7.3.10(@types/react@18.0.18)(react@18.3.1) '@mui/x-data-grid': 6.20.4(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-date-pickers': 6.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(react@18.3.1))(@types/react@18.0.18)(dayjs@1.11.20)(luxon@3.7.2)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@oxygen-ui/primitives': 2.4.6