diff --git a/.changeset/fast-nails-nail.md b/.changeset/fast-nails-nail.md new file mode 100644 index 00000000000..ac771b35bfb --- /dev/null +++ b/.changeset/fast-nails-nail.md @@ -0,0 +1,9 @@ +--- +"@wso2is/admin.tenants.v1": patch +"@wso2is/react-components": patch +"@wso2is/i18n": patch +--- + +Remove `Go to Console` link from root organization actions and show a URL instead + +**Reason**: There are issues in having a `Go to Console` link and we can't properly workaround this without first resolving https://github.com/wso2/product-is/issues/22615 diff --git a/features/admin.console-settings.v1/pages/console-settings-page.scss b/features/admin.console-settings.v1/pages/console-settings-page.scss index e24024b5446..4c35a374a64 100644 --- a/features/admin.console-settings.v1/pages/console-settings-page.scss +++ b/features/admin.console-settings.v1/pages/console-settings-page.scss @@ -6,10 +6,12 @@ align-items: center; flex-wrap: nowrap; gap: 15px; - + .MuiInputBase-root { min-width: 400px; - + + // TODO: Fix this from the `@oxygen-ui/react` side and remove. + // Tracker: https://github.com/wso2/oxygen-ui/issues/331 &.Mui-readOnly { cursor: default; background-color: #e9ecef!important; diff --git a/features/admin.organization-discovery.v1/components/edit-organization-discovery-domains.scss b/features/admin.organization-discovery.v1/components/edit-organization-discovery-domains.scss index 585be0e2259..0152c2d0bdd 100644 --- a/features/admin.organization-discovery.v1/components/edit-organization-discovery-domains.scss +++ b/features/admin.organization-discovery.v1/components/edit-organization-discovery-domains.scss @@ -1,10 +1,12 @@ .edit-organization-discovery-domains-form { - // TODO: Fix this from `@oxygen-ui` side. + // TODO: Fix this from the `@oxygen-ui/react` side and remove. + // Tracker: https://github.com/wso2/oxygen-ui/issues/331 .Mui-readOnly { background-color: #e9ecef; } // TODO: Fix this from `@oxygen-ui` side. + // Tracker: https://github.com/wso2/oxygen-ui/issues/332 .MuiFormHelperText-root { margin-left: 0; } diff --git a/features/admin.sms-providers.v1/pages/sms-providers.scss b/features/admin.sms-providers.v1/pages/sms-providers.scss index 22df6dbae8d..02f630b6354 100644 --- a/features/admin.sms-providers.v1/pages/sms-providers.scss +++ b/features/admin.sms-providers.v1/pages/sms-providers.scss @@ -19,6 +19,8 @@ .sms-provider-config-form { .MuiInputBase-root { + // TODO: Fix this from the `@oxygen-ui/react` side and remove. + // Tracker: https://github.com/wso2/oxygen-ui/issues/331 &.Mui-readOnly { cursor: default; background-color: #e9ecef!important; diff --git a/features/admin.tenants.v1/components/edit-tenant/edit-tenant.tsx b/features/admin.tenants.v1/components/edit-tenant/edit-tenant.tsx index 567cc6844d9..989396329ff 100644 --- a/features/admin.tenants.v1/components/edit-tenant/edit-tenant.tsx +++ b/features/admin.tenants.v1/components/edit-tenant/edit-tenant.tsx @@ -56,6 +56,8 @@ const EditTenant: FunctionComponent = ({ }: EditTenantProps): ReactElement => { const { t } = useTranslation(); + const isActivated: boolean = tenant?.lifecycleStatus?.activated; + const isTenantDeletionEnabled: boolean = useSelector((state: AppState) => { return !state?.config?.ui?.features?.tenants?.disabledFeatures?.includes( TenantConstants.FEATURE_DICTIONARY.TENANT_DELETION @@ -97,26 +99,28 @@ const EditTenant: FunctionComponent = ({ > - - { tenant?.lifecycleStatus?.activated && ( - disableTenant(tenant) } - /> - ) } - { isTenantDeletionEnabled && ( - deleteTenant(tenant) } - /> - ) } - + { (isActivated || isTenantDeletionEnabled) && ( + + { isActivated && ( + disableTenant(tenant) } + /> + ) } + { isTenantDeletionEnabled && ( + deleteTenant(tenant) } + /> + ) } + + ) } ); }; diff --git a/features/admin.tenants.v1/components/tenant-card.tsx b/features/admin.tenants.v1/components/tenant-card.tsx index 3d627145788..a660b0c233a 100644 --- a/features/admin.tenants.v1/components/tenant-card.tsx +++ b/features/admin.tenants.v1/components/tenant-card.tsx @@ -29,7 +29,6 @@ import Stack from "@oxygen-ui/react/Stack"; import Tooltip from "@oxygen-ui/react/Tooltip"; import Typography from "@oxygen-ui/react/Typography/Typography"; import { - ArrowUpRightFromSquareIcon, BanIcon, CircleCheckFilledIcon, EllipsisVerticalIcon, @@ -77,7 +76,7 @@ const TenantCard: FunctionComponent = ({ isLoading, tenant }: T ); }); - const { deleteTenant, disableTenant, enableTenant, navigateToTenantConsole } = useTenants(); + const { deleteTenant, disableTenant, enableTenant } = useTenants(); const [ anchorEl, setAnchorEl ] = useState(null); @@ -202,18 +201,6 @@ const TenantCard: FunctionComponent = ({ isLoading, tenant }: T onClose={ handleClose } className="tenant-card-footer-dropdown" > - { - navigateToTenantConsole(tenant); - handleClose(); - } } - > - - - - { t("tenants:listing.item.actions.goToConsole.label") } - void; - /** - * Navigate to the tenant console. - * @param tenant - Tenant to navigate to. - */ - navigateToTenantConsole: (tenant: Tenant) => void; /** * The search query. */ @@ -96,7 +91,6 @@ const TenantContext: Context = createContext null, - navigateToTenantConsole: () => null, searchQuery: "", searchQueryClearTrigger: false, setSearchQuery: () => null, diff --git a/features/admin.tenants.v1/hooks/use-tenant-console-url.ts b/features/admin.tenants.v1/hooks/use-tenant-console-url.ts new file mode 100644 index 00000000000..4930a1fa950 --- /dev/null +++ b/features/admin.tenants.v1/hooks/use-tenant-console-url.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2025, 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 { AppState } from "@wso2is/admin.core.v1/store"; +import { useSelector } from "react-redux"; +import { Tenant } from "../models/tenants"; + +/** + * Hook to generate the console URL for a given tenant. + * + * This hook generates the console URL for a specified tenant by replacing the + * tenant domain in the client host URL. It uses the `clientHost` value from + * the Redux store and replaces the existing tenant domain with the provided + * tenant's domain. + * + * @param tenant - The tenant object containing the domain to be used in the URL. + * @returns The console URL for the specified tenant, or `undefined` if the tenant domain is not provided. + * + * @example + * ```tsx + * const tenant: Tenant = { domain: "example.com" }; + * const consoleURL = useTenantConsoleURL(tenant); + * console.log(consoleURL); // Outputs the console URL for the specified tenant + * ``` + */ +const useTenantConsoleURL = (tenant: Tenant): string | undefined => { + const clientHost: string = useSelector((state: AppState) => state.config?.deployment?.clientHost); + const tenantPrefix: string = useSelector((state: AppState) => state.config?.deployment?.tenantPrefix); + + if (!tenant?.domain) { + return undefined; + } + + return clientHost.replace(new RegExp(`/${tenantPrefix}/[^/]+/`), `/${tenantPrefix}/${tenant.domain}/`); +}; + +export default useTenantConsoleURL; diff --git a/features/admin.tenants.v1/pages/edit-tenant-page.scss b/features/admin.tenants.v1/pages/edit-tenant-page.scss index a28eca1d46f..bcd14c13cde 100644 --- a/features/admin.tenants.v1/pages/edit-tenant-page.scss +++ b/features/admin.tenants.v1/pages/edit-tenant-page.scss @@ -45,4 +45,41 @@ } } } + + .console-url-copy-field { + .MuiInputBase-root { + min-width: 400px; + + // TODO: Fix this from the `@oxygen-ui/react` side and remove. + // Tracker: https://github.com/wso2/oxygen-ui/issues/331 + &.Mui-readOnly { + cursor: default; + background-color: #e9ecef!important; + } + } + } +} + +@media (min-width: 675px) { + .tenant-edit-page { + .console-url-copy-field { + display: flex; + flex-direction: row; + align-content: center; + justify-content: center; + align-items: center; + flex-wrap: nowrap; + gap: 15px; + } + } +} + +@media (max-width: 675px) { + .tenant-edit-page { + .console-url-copy-field { + .MuiInputBase-root { + min-width: auto; + } + } + } } diff --git a/features/admin.tenants.v1/pages/edit-tenant-page.tsx b/features/admin.tenants.v1/pages/edit-tenant-page.tsx index 33f314746ab..651eb6e4873 100644 --- a/features/admin.tenants.v1/pages/edit-tenant-page.tsx +++ b/features/admin.tenants.v1/pages/edit-tenant-page.tsx @@ -17,19 +17,25 @@ */ import Avatar from "@oxygen-ui/react/Avatar"; +import Box from "@oxygen-ui/react/Box"; +import IconButton from "@oxygen-ui/react/IconButton"; +import TextField from "@oxygen-ui/react/TextField"; import Tooltip from "@oxygen-ui/react/Tooltip"; import Typography from "@oxygen-ui/react/Typography"; +import { CopyIcon } from "@oxygen-ui/react-icons"; import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants"; import { history } from "@wso2is/admin.core.v1/helpers/history"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; -import { PageLayout } from "@wso2is/react-components"; +import { CommonUtils } from "@wso2is/core/utils"; +import { Hint, PageLayout } from "@wso2is/react-components"; import classNames from "classnames"; import moment from "moment"; import React, { FunctionComponent, ReactElement, useMemo } from "react"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import useGetTenant from "../api/use-get-tenant"; import useGetTenantOwner from "../api/use-get-tenant-owner"; import EditTenant from "../components/edit-tenant/edit-tenant"; +import useTenantConsoleURL from "../hooks/use-tenant-console-url"; import { Tenant, TenantOwner } from "../models/tenants"; import TenantProvider from "../providers/tenant-provider"; import "./edit-tenant-page.scss"; @@ -64,6 +70,7 @@ const EditTenantPage: FunctionComponent = ({ tenant?.owners[0]?.id, !!tenant ); + const consoleURL: string | undefined = useTenantConsoleURL(tenant); /** * Merges the tenant and tenant owner data. @@ -100,6 +107,7 @@ const EditTenantPage: FunctionComponent = ({ } } > @@ -144,6 +152,44 @@ const EditTenantPage: FunctionComponent = ({ } } className="tenant-edit-page" bottomMargin={ false } + action={ + ( + { t("tenants:edit.consoleURL.label") } + + + If you try to login to { tenant?.domain } organization's + Console using the same browser, you will have to logout from this active + session first. + + + ) + } + id="filled-start-adornment" + InputProps={ { + endAdornment: ( + +
+ { + await CommonUtils.copyTextToClipboard(consoleURL); + } } + edge="end" + > + + +
+
+ ), + readOnly: true + } } + value={ consoleURL } + className="console-url-copy-field" + />) + } >
@@ -152,4 +198,3 @@ const EditTenantPage: FunctionComponent = ({ }; export default EditTenantPage; -history; diff --git a/features/admin.tenants.v1/providers/tenant-provider.tsx b/features/admin.tenants.v1/providers/tenant-provider.tsx index aab0d9c417a..ff5adf975d6 100644 --- a/features/admin.tenants.v1/providers/tenant-provider.tsx +++ b/features/admin.tenants.v1/providers/tenant-provider.tsx @@ -16,17 +16,13 @@ * under the License. */ -import { useAuthContext } from "@asgardeo/auth-react"; -import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants"; import { UIConstants } from "@wso2is/admin.core.v1/constants/ui-constants"; -import { history } from "@wso2is/admin.core.v1/helpers/history"; -import { AppState } from "@wso2is/admin.core.v1/store"; import { AlertLevels } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; import { ConfirmationModal } from "@wso2is/react-components"; import React, { PropsWithChildren, ReactElement, useEffect, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; import { Dispatch } from "redux"; import deleteTenantMetadata from "../api/delete-tenant-metadata"; import updateTenantActivationStatus from "../api/update-tenant-activation-status"; @@ -81,20 +77,13 @@ const TenantProvider = ({ }: PropsWithChildren): ReactElement => { const { t } = useTranslation(); const dispatch: Dispatch = useDispatch(); - const { updateConfig } = useAuthContext(); - - const clientHost: string = useSelector((state: AppState) => state.config?.deployment?.clientHost); const [ isInitialRenderingComplete, setIsInitialRenderingComplete ] = useState(false); const [ tenantListLimit, setTenantListLimit ] = useState(UIConstants.DEFAULT_RESOURCE_GRID_ITEM_LIMIT); const [ showDeleteConfirmationModal, setShowDeleteConfirmationModal ] = useState(false); const [ showDisableConfirmationModal, setShowDisableConfirmationModal ] = useState(false); - const [ showTenantConsoleNavigationConfirmationModal, setShowTenantConsoleNavigationConfirmationModal ] = useState< - boolean - >(false); const [ deletingTenant, setDeletingTenant ] = useState(null); const [ disablingTenant, setDisablingTenant ] = useState(null); - const [ consoleNavigatingTenant, setConsoleNavigatingTenant ] = useState(null); const [ isTenantDeleteRequestLoading, setIsTenantDeleteRequestLoading ] = useState(false); const [ isTenantStatusUpdateRequestLoading, setIsTenantStatusUpdateRequestLoading ] = useState(false); const [ searchQuery, setSearchQuery ] = useState(""); @@ -215,22 +204,6 @@ const TenantProvider = ({ }); }; - /** - * Handles the tenant console navigation after the user confirms the action. - * @param tenant - Tenant to navigate to. - */ - const handleTenantConsoleNavigation = async (tenant: Tenant): Promise => { - const consoleURL: string = clientHost.replace(/\/t\/[^/]+\//, `/t/${tenant.domain}/`); - - setShowTenantConsoleNavigationConfirmationModal(false); - - await updateConfig({ - signOutRedirectURL: consoleURL - }); - - history.push(AppConstants.getAppLogoutPath()); - }; - return ( { - setShowTenantConsoleNavigationConfirmationModal(true); - setConsoleNavigatingTenant(tenant); - }, searchQuery, searchQueryClearTrigger, setSearchQuery, @@ -322,41 +291,6 @@ const TenantProvider = ({ ) } - { showTenantConsoleNavigationConfirmationModal && ( - setShowTenantConsoleNavigationConfirmationModal(false) } - type="warning" - open={ showTenantConsoleNavigationConfirmationModal } - assertionHint={ t("tenants:confirmationModals.navigatingToTenantConsole.assertionHint") } - assertionType="checkbox" - primaryAction={ t("tenants:confirmationModals.navigatingToTenantConsole.primaryAction") } - secondaryAction={ t("tenants:confirmationModals.navigatingToTenantConsole.secondaryAction") } - onSecondaryActionClick={ (): void => setShowTenantConsoleNavigationConfirmationModal(false) } - onPrimaryActionClick={ async () => await handleTenantConsoleNavigation(consoleNavigatingTenant) } - closeOnDimmerClick={ false } - > - - { t("tenants:confirmationModals.navigatingToTenantConsole.header") } - - - { t("tenants:confirmationModals.navigatingToTenantConsole.message") } - - - - If you continue navigating to the { consoleNavigatingTenant?.domain } - Console, you will be logged out from the current session. - - - - ) } ); }; diff --git a/modules/i18n/src/models/namespaces/tenants-ns.ts b/modules/i18n/src/models/namespaces/tenants-ns.ts index ad9059477e2..f3cf035f56f 100644 --- a/modules/i18n/src/models/namespaces/tenants-ns.ts +++ b/modules/i18n/src/models/namespaces/tenants-ns.ts @@ -163,17 +163,13 @@ export interface TenantsNS { primaryAction: string; secondaryAction: string; }; - navigatingToTenantConsole: { - assertionHint: string; - content: string; - header: string; - message: string; - primaryAction: string; - secondaryAction: string; - }; }, edit: { backButton: string; + consoleURL: { + label: string; + hint: string; + }; subtitle: string; }; editTenant: { @@ -296,9 +292,6 @@ export interface TenantsNS { edit: { label: string; }; - goToConsole: { - label: string; - }; more: { label: string; }; diff --git a/modules/i18n/src/translations/en-US/portals/tenants.ts b/modules/i18n/src/translations/en-US/portals/tenants.ts index 06aeafee40f..d10cc74e87b 100644 --- a/modules/i18n/src/translations/en-US/portals/tenants.ts +++ b/modules/i18n/src/translations/en-US/portals/tenants.ts @@ -166,18 +166,14 @@ export const tenants: TenantsNS = { message: "This action will temporarily disable the organization.", primaryAction: "Confirm", secondaryAction: "Cancel" - }, - navigatingToTenantConsole: { - assertionHint: "Please confirm your action.", - content: "If you continue navigating to the <1>{{domain}} Console, you will be logged out from the current session.", - header: "Confirmation", - message: "Navigating to the Console will require you to re-login.", - primaryAction: "Logout & Go to Console", - secondaryAction: "Cancel" } }, edit: { backButton: "Go back to Root Organizations", + consoleURL: { + hint: "If you try to login to <1>{{domain}} organization's Console using the same browser, you will have to logout from this active session first.", + label: "Console URL" + }, subtitle: "Crated on {{date}}" }, editTenant: { @@ -300,9 +296,6 @@ export const tenants: TenantsNS = { edit: { label: "Edit" }, - goToConsole: { - label: "Go to Console" - }, more: { label: "More" } diff --git a/modules/react-components/src/components/page-header/page-header.tsx b/modules/react-components/src/components/page-header/page-header.tsx index 3b0da4b4f39..f0ac53e3003 100644 --- a/modules/react-components/src/components/page-header/page-header.tsx +++ b/modules/react-components/src/components/page-header/page-header.tsx @@ -16,6 +16,7 @@ * under the License. */ +import Box from "@oxygen-ui/react/Box"; import { IdentifiableComponentInterface, LoadableComponentInterface, @@ -61,6 +62,12 @@ export interface PageHeaderPropsInterface extends LoadableComponentInterface, Te * Description. */ description?: ReactNode; + /** + * Flag to enable the legacy semantic-ui grid system for the page header. + * @deprecated This prop is deprecated and will be removed in the next major release. + * Use the new flex mode (default) instead. + */ + legacyGrid?: boolean; /** * Flag to determine whether max width should be added to page header text content. */ @@ -125,32 +132,31 @@ export interface BackButtonInterface extends TestableComponentInterface, Identif * @returns the page header component */ export const PageHeader: React.FunctionComponent = ( - props: PageHeaderPropsInterface -): ReactElement => { - - const { + { action, - actionColumnWidth, + actionColumnWidth = 6, alertBanner, backButton, - bottomMargin, + bottomMargin = true, className, description, - headingColumnWidth, + // TODO: Remove the default value once the existing components are updated. + legacyGrid = true, + headingColumnWidth = 10, image, isLoading, - imageSpaced, + imageSpaced = "right", loadingStateOptions, - showBottomDivider, + showBottomDivider = false, title, - titleAs, + titleAs = "h1", titleTextAlign, pageHeaderMaxWidth, truncateContent, - [ "data-componentid" ]: componentId, - [ "data-testid" ]: testId - } = props; - + [ "data-componentid" ]: componentId = "page-header", + [ "data-testid" ]: testId = "page-header" + }: PageHeaderPropsInterface +): ReactElement => { const wrapperClasses = classNames( "page-header-wrapper", { @@ -275,6 +281,47 @@ export const PageHeader: React.FunctionComponent = ( ); + /** + * Resolves the content rendering logic. + * @returns Header content as a react component. + */ + const renderContent = (): ReactElement => { + if (!action) { + return headingContent; + } + + if (!legacyGrid) { + return ( + + { headingContent } + { action } + + ); + } + + return ( + + + + { headingContent } + + + { action &&
{ action }
} +
+
+
+ ); + }; + return ( (title || description) ? ( @@ -307,22 +354,7 @@ export const PageHeader: React.FunctionComponent = ( ) } { alertBanner } - { - action - ? ( - - - - { headingContent } - - - { action &&
{ action }
} -
-
-
- ) - : headingContent - } + { renderContent() } { bottomMargin &&