+ );
+};
+
+export default TenantGrid;
diff --git a/features/admin.tenants.v1/components/tenants-page-layout.scss b/features/admin.tenants.v1/components/tenants-page-layout.scss
new file mode 100644
index 00000000000..8971780b55b
--- /dev/null
+++ b/features/admin.tenants.v1/components/tenants-page-layout.scss
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+.tenants-page-actions {
+ .oxygen-icon-button {
+ margin-right: 3.5px;
+ opacity: 1;
+ }
+}
diff --git a/features/admin.tenants.v1/components/tenants-page-layout.tsx b/features/admin.tenants.v1/components/tenants-page-layout.tsx
new file mode 100644
index 00000000000..2f04c21bb0a
--- /dev/null
+++ b/features/admin.tenants.v1/components/tenants-page-layout.tsx
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2024, 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 Button from "@oxygen-ui/react/Button";
+import IconButton from "@oxygen-ui/react/IconButton";
+import Tooltip from "@oxygen-ui/react/Tooltip";
+import { GearIcon, PlusIcon } from "@oxygen-ui/react-icons";
+import { Show } from "@wso2is/access-control";
+import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants";
+import { history } from "@wso2is/admin.core.v1/helpers/history";
+import { AppState } from "@wso2is/admin.core.v1/store";
+import { FeatureAccessConfigInterface, IdentifiableComponentInterface } from "@wso2is/core/models";
+import { DocumentationLink, PageLayout, useDocumentation } from "@wso2is/react-components";
+import React, { FunctionComponent, ReactElement, useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useSelector } from "react-redux";
+import AddTenantModal from "./add-tenant/add-tenant-modal";
+import TenantGrid from "./tenant-grid";
+import useTenants from "../hooks/use-tenants";
+import "./tenants-page-layout.scss";
+
+/**
+ * Props interface of {@link TenantsPageLayout}
+ */
+type TenantsPageLayoutProps = IdentifiableComponentInterface;
+
+/**
+ * Component to wrap the page layout of the tenant listing page that can access the tenant context.
+ *
+ * @param props - Props injected to the component.
+ * @returns Tenant page layout.
+ */
+const TenantsPageLayout: FunctionComponent = ({
+ ["data-componentid"]: componentId = "tenants-page-layout"
+}: TenantsPageLayoutProps): ReactElement => {
+ const { t } = useTranslation();
+ const { getLink } = useDocumentation();
+ const { tenantList, mutateTenantList } = useTenants();
+
+ useEffect(() => {
+ mutateTenantList();
+ }, []);
+
+ const tenantFeatureConfig: FeatureAccessConfigInterface = useSelector(
+ (state: AppState) => state.config?.ui?.features?.tenants
+ );
+ const adminAdvisory: FeatureAccessConfigInterface = useSelector(
+ (state: AppState) => state.config?.ui?.features?.adminAdvisoryBanner
+ );
+ const remoteLogPublishing: FeatureAccessConfigInterface = useSelector(
+ (state: AppState) => state.config?.ui?.features?.remoteLogging
+ );
+
+ const [ addTenantModalOpen, setAddTenantModalOpen ] = useState(false);
+
+ return (
+
+ { t("tenants:subtitle") }
+
+ { t("common:learnMore") }
+
+ >)
+ }
+ data-componentid={ componentId }
+ action={
+ (
)
+ }
+ className="tenants-page"
+ >
+ setAddTenantModalOpen(true) } />
+ setAddTenantModalOpen(false) } />
+
+ );
+};
+
+export default TenantsPageLayout;
diff --git a/features/admin.branding.v1/components/custom-text/custom-text-fields.scss b/features/admin.tenants.v1/components/with-tenant-grid-placeholders.scss
similarity index 68%
rename from features/admin.branding.v1/components/custom-text/custom-text-fields.scss
rename to features/admin.tenants.v1/components/with-tenant-grid-placeholders.scss
index b7729c2d0fe..687c3b3bd10 100644
--- a/features/admin.branding.v1/components/custom-text/custom-text-fields.scss
+++ b/features/admin.tenants.v1/components/with-tenant-grid-placeholders.scss
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
+ * Copyright (c) 2024, 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
@@ -16,16 +16,16 @@
* under the License.
*/
-.reset-field-to-default-adornment {
- margin-right: 6px;
-}
-
-.branding-preference-custom-text-fields {
+.with-tenant-grid-placeholders {
+ .empty-placeholder {
+ margin: 0;
- .MuiInputBase-root {
- &.Mui-readOnly {
- cursor: default;
- background-color: #e9ecef!important;
+ .action-container .MuiStack-root {
+ min-height: 100px;
}
}
}
+
+.tenants-grid-display-count {
+ margin-bottom: var(--oxygen-spacing-2);
+}
diff --git a/features/admin.tenants.v1/components/with-tenant-grid-placeholders.tsx b/features/admin.tenants.v1/components/with-tenant-grid-placeholders.tsx
new file mode 100644
index 00000000000..36eee4d9d36
--- /dev/null
+++ b/features/admin.tenants.v1/components/with-tenant-grid-placeholders.tsx
@@ -0,0 +1,176 @@
+/**
+ * Copyright (c) 2024, 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 Button from "@oxygen-ui/react/Button";
+import Divider from "@oxygen-ui/react/Divider";
+import Grid from "@oxygen-ui/react/Grid";
+import Stack from "@oxygen-ui/react/Stack";
+import Typography from "@oxygen-ui/react/Typography";
+import { GearIcon, PlusIcon } from "@oxygen-ui/react-icons";
+import { FeatureAccessConfigInterface, Show } from "@wso2is/access-control";
+import { getEmptyPlaceholderIllustrations } from "@wso2is/admin.core.v1/configs/ui";
+import { AppConstants } from "@wso2is/admin.core.v1/constants/app-constants";
+import { history } from "@wso2is/admin.core.v1/helpers/history";
+import { AppState } from "@wso2is/admin.core.v1/store";
+import { IdentifiableComponentInterface } from "@wso2is/core/models";
+import { EmptyPlaceholder } from "@wso2is/react-components";
+import React, { FunctionComponent, PropsWithChildren, ReactElement } from "react";
+import { useTranslation } from "react-i18next";
+import { useSelector } from "react-redux";
+import TenantCard from "./tenant-card";
+import { TenantListResponse } from "../models/tenants";
+import "./with-tenant-grid-placeholders.scss";
+
+/**
+ * Props interface of {@link WithTenantGridPlaceholders}
+ */
+export type WithTenantGridPlaceholdersProps = IdentifiableComponentInterface &
+ PropsWithChildren<{
+ /**
+ * Callback to be fired on add tenant modal trigger.
+ */
+ onAddTenantModalTrigger: () => void;
+ /**
+ * Tenant list.
+ */
+ tenantList: TenantListResponse;
+ /**
+ * Flag to indicate if the tenant list is loading.
+ */
+ isTenantListLoading: boolean;
+ }>;
+
+/**
+ * HOC component to decorate the tenant grid with placeholders.
+ *
+ * @param props - Props injected to the component.
+ * @returns Decorated tenant grid component.
+ */
+const WithTenantGridPlaceholders: FunctionComponent = ({
+ tenantList,
+ isTenantListLoading,
+ children,
+ onAddTenantModalTrigger,
+ ["data-componentid"]: componentId = "with-tenant-grid-placeholders"
+}: WithTenantGridPlaceholdersProps): ReactElement => {
+ const { t } = useTranslation();
+
+ const tenantFeatureConfig: FeatureAccessConfigInterface = useSelector(
+ (state: AppState) => state.config?.ui?.features?.tenants
+ );
+
+ /**
+ * This function returns loading placeholders.
+ */
+ const renderLoadingPlaceholder = (): ReactElement => {
+ const cards: ReactElement[] = [];
+ const COUNT: number = 8;
+
+ Array.from(Array(COUNT)).map((key: number) => {
+ cards.push(
+
+
+
+ );
+ });
+
+ return <>{ cards }>;
+ };
+
+ /**
+ * Resolve the relevant placeholder.
+ *
+ * @returns React element.
+ */
+ const showPlaceholders = (): ReactElement => (
+
+
+ }
+ variant="contained"
+ color="primary"
+ autoFocus
+ onClick={ () => onAddTenantModalTrigger() }
+ >
+ { t("tenants:listing.emptyPlaceholder.actions.new.label") }
+
+
+
+ { t("tenants:listing.emptyPlaceholder.actions.divider") }
+
+ }
+ onClick={ () => history.push(AppConstants.getPaths().get("SYSTEM_SETTINGS")) }
+ >
+ { t("tenants:listing.emptyPlaceholder.actions.configure.label") }
+
+ )
+ }
+ image={ getEmptyPlaceholderIllustrations().newList }
+ imageSize="tiny"
+ subtitle={ [
+ t("tenants:listing.emptyPlaceholder.subtitles.0"),
+ t("tenants:listing.emptyPlaceholder.subtitles.1")
+ ] }
+ data-componentid={ `${componentId}-empty-placeholder` }
+ />
+ );
+
+ if (isTenantListLoading) {
+ return (
+
+ { renderLoadingPlaceholder() }
+
+ );
+ }
+
+ // Sometimes, `tenants` array is undefined but `totalResults` is available.
+ // TODO: Tracker: https://github.com/wso2/product-is/issues/21459
+ if (!tenantList?.tenants || tenantList?.totalResults <= 0) {
+ return (
+
+ { showPlaceholders() }
+
+ );
+ }
+
+ return (
+ <>
+ { !isTenantListLoading && tenantList?.tenants?.length > 0 && (
+
+ { t("tenants:listing.count", {
+ results: tenantList?.tenants?.length,
+ totalResults: tenantList?.totalResults
+ }) }
+
+ ) }
+
+ { children }
+
+ >
+ );
+};
+
+export default WithTenantGridPlaceholders;
diff --git a/features/admin.tenants.v1/configs/endpoints.ts b/features/admin.tenants.v1/configs/endpoints.ts
index 8db14caa9d2..7cbaf219177 100644
--- a/features/admin.tenants.v1/configs/endpoints.ts
+++ b/features/admin.tenants.v1/configs/endpoints.ts
@@ -30,6 +30,7 @@ export const getTenantResourceEndpoints = (
return {
tenantAssociationApi: `${ serverOrigin }/api/asgardeo/v1/tenant/me`,
tenantManagementApi: `${ serverOrigin }/api/asgardeo/v1/tenant`,
- tenantSubscriptionApi: `${ serverOrigin }${ Config.getDeploymentConfig().extensions?.subscriptionApiPath }`
+ tenantSubscriptionApi: `${ serverOrigin }${ Config.getDeploymentConfig().extensions?.subscriptionApiPath }`,
+ tenants: `${serverOrigin}/api/server/v1/tenants`
};
};
diff --git a/features/admin.tenants.v1/constants/tenant-constants.ts b/features/admin.tenants.v1/constants/tenant-constants.ts
new file mode 100644
index 00000000000..343f165ddb5
--- /dev/null
+++ b/features/admin.tenants.v1/constants/tenant-constants.ts
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+/**
+ * Constants related to the tenant management operations.
+ *
+ * @remarks
+ * This class is not meant to be instantiated. It only provides static constants.
+ *
+ * @example
+ * ```typescript
+ * const errorMessage = TenantConstants.TENANT_ACTIVATION_UPDATE_ERROR;
+ * ```
+ */
+export default class TenantConstants {
+ /**
+ * Private constructor to avoid object instantiation from outside the class.
+ */
+ private constructor() {}
+
+ public static readonly TENANT_ACTIVATION_UPDATE_INVALID_STATUS_ERROR: string =
+ "An invalid status code was received while updating the tenant activation status.";
+
+ public static readonly TENANT_ACTIVATION_UPDATE_ERROR: string =
+ "An error occurred while updating the tenant activation status.";
+
+ public static readonly TENANT_METADATA_DELETE_ERROR: string =
+ "An error occurred while deleting the tenant metadata.";
+
+ public static readonly TENANT_METADATA_DELETE_INVALID_STATUS_ERROR: string =
+ "An invalid status code was received while deleting the tenant metadata.";
+
+ public static readonly TENANT_CREATION_INVALID_STATUS_ERROR: string =
+ "An invalid status code was received while creating the tenant.";
+
+ public static readonly TENANT_CREATION_ERROR: string = "An error occurred while creating the tenant.";
+
+ public static readonly ADD_TENANT_FORM_ID: string = "add-tenant-form";
+
+ public static readonly TENANT_DOMAIN_AVAILABILITY_CHECK_INVALID_STATUS_ERROR: string =
+ "An invalid status code was received while checking the tenant domain availability.";
+
+ public static readonly TENANT_DOMAIN_AVAILABILITY_CHECK_ERROR: string =
+ "An error occurred while checking the tenant domain availability.";
+
+ public static readonly TENANT_OWNER_UPDATE_INVALID_STATUS_ERROR: string =
+ "An invalid status code was received while updating the tenant owner's details.";
+
+ public static readonly TENANT_OWNER_UPDATE_ERROR: string =
+ "An error occurred while updating the tenant owner's details.";
+
+ public static readonly FEATURE_DICTIONARY: {
+ ADD_TENANTS_FROM_DROPDOWN: string;
+ MAKING_TENANTS_DEFAULT: string;
+ MANAGING_TENANTS_FROM_DROPDOWN: string;
+ ORGANIZATIONS_QUICK_NAV_FROM_DROPDOWN: string;
+ } = {
+ ADD_TENANTS_FROM_DROPDOWN: "tenants.add.tenant.from.dropdown",
+ MAKING_TENANTS_DEFAULT: "tenants.make.default",
+ MANAGING_TENANTS_FROM_DROPDOWN: "tenants.manage.tenants.from.dropdown",
+ ORGANIZATIONS_QUICK_NAV_FROM_DROPDOWN: "tenants.organizations.quick.nav.from.dropdown"
+ };
+}
diff --git a/features/admin.tenants.v1/context/tenant-context.tsx b/features/admin.tenants.v1/context/tenant-context.tsx
new file mode 100644
index 00000000000..f335d49f21a
--- /dev/null
+++ b/features/admin.tenants.v1/context/tenant-context.tsx
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2024, 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 { UIConstants } from "@wso2is/admin.core.v1/constants/ui-constants";
+import { Context, Dispatch, createContext } from "react";
+import { Tenant, TenantListResponse } from "../models/tenants";
+
+/**
+ * Props interface of {@link TenantContext}
+ */
+export interface TenantContextProps {
+ /**
+ * Trigger a delete action on a tenant.
+ * @param tenant - Tenant to be deleted.
+ */
+ deleteTenant: (tenant: Tenant) => void;
+ /**
+ * Trigger a disable action on a tenant.
+ * @param tenant - Tenant to be disabled.
+ */
+ disableTenant: (tenant: Tenant) => void;
+ /**
+ * Trigger a enable action on a tenant.
+ * @param tenant - Tenant to be enabled.
+ */
+ enableTenant: (tenant: Tenant) => void;
+ /**
+ * Flag to indicate if the initial rendering is complete.
+ */
+ isInitialRenderingComplete: boolean;
+ /**
+ * Flag to indicate if the tenant list is loading.
+ */
+ isTenantListLoading: boolean;
+ /**
+ * Function to mutate the tenant list.
+ */
+ mutateTenantList: () => void;
+ /**
+ * Set tenant list limit from outside.
+ */
+ setTenantListLimit: Dispatch;
+ /**
+ * Tenant list response.
+ */
+ tenantList: TenantListResponse;
+ /**
+ * Tenant list limit.
+ */
+ tenantListLimit: number;
+}
+
+/**
+ * Context object for managing the Tenant context.
+ */
+const TenantContext: Context = createContext({
+ deleteTenant: () => null,
+ disableTenant: () => null,
+ enableTenant: () => null,
+ isInitialRenderingComplete: false,
+ isTenantListLoading: false,
+ mutateTenantList: () => null,
+ setTenantListLimit: () => null,
+ tenantList: null,
+ tenantListLimit: UIConstants.DEFAULT_RESOURCE_GRID_ITEM_LIMIT
+});
+
+TenantContext.displayName = "TenantContext";
+
+export default TenantContext;
diff --git a/features/admin.tenants.v1/hooks/use-tenants.ts b/features/admin.tenants.v1/hooks/use-tenants.ts
new file mode 100644
index 00000000000..31e00570de5
--- /dev/null
+++ b/features/admin.tenants.v1/hooks/use-tenants.ts
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2024, 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 { useContext } from "react";
+import TenantContext, { TenantContextProps } from "../context/tenant-context";
+
+/**
+ * Props interface of {@link useTenants}
+ */
+export type useTenantsInterface = TenantContextProps;
+
+/**
+ * Hook that provides access to the Tenant context.
+ *
+ * This hook allows components to access tenant-related data and functions
+ * provided by the {@link TenantProvider}. It returns an object containing
+ * the context values defined in {@link TenantContext}.
+ *
+ * @returns An object containing the context values of {@link TenantContext}.
+ *
+ * @throws Will throw an error if the hook is used outside of a TenantProvider.
+ *
+ * @example
+ * ```tsx
+ * const { isTenantListLoading, mutateTenantList, setTenantListLimit, tenantList } = useTenants();
+ *
+ * useEffect(() => {
+ * // Fetch tenants or perform other tenant-related operations
+ * }, []);
+ * ```
+ */
+const useTenants = (): useTenantsInterface => {
+ const context: TenantContextProps = useContext(TenantContext);
+
+ if (context === undefined) {
+ throw new Error("useTenants must be used within a TenantProvider");
+ }
+
+ return context;
+};
+
+export default useTenants;
diff --git a/features/admin.tenants.v1/models/endpoints.ts b/features/admin.tenants.v1/models/endpoints.ts
index d9e2a2a87bf..53e1828fbaa 100644
--- a/features/admin.tenants.v1/models/endpoints.ts
+++ b/features/admin.tenants.v1/models/endpoints.ts
@@ -23,4 +23,5 @@ export interface TenantResourceEndpointsInterface {
tenantAssociationApi: string;
tenantManagementApi: string;
tenantSubscriptionApi: string;
+ tenants: string;
}
diff --git a/features/admin.tenants.v1/models/system-settings/admin-advisory.ts b/features/admin.tenants.v1/models/system-settings/admin-advisory.ts
new file mode 100644
index 00000000000..819cbcd33b5
--- /dev/null
+++ b/features/admin.tenants.v1/models/system-settings/admin-advisory.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+/**
+ * Interface for the admin advisory banner configuration.
+ */
+export interface AdminAdvisoryBannerConfigurationInterface {
+ /**
+ * Text content of the banner.
+ */
+ bannerContent: string;
+ /**
+ * Is the banner enabled or not.
+ */
+ enableBanner: boolean;
+}
diff --git a/features/admin.server.v1/models/server.ts b/features/admin.tenants.v1/models/system-settings/remote-log-publishing.ts
similarity index 86%
rename from features/admin.server.v1/models/server.ts
rename to features/admin.tenants.v1/models/system-settings/remote-log-publishing.ts
index 03b9962a74e..aa63006a714 100644
--- a/features/admin.server.v1/models/server.ts
+++ b/features/admin.tenants.v1/models/system-settings/remote-log-publishing.ts
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2020-2023, WSO2 LLC. (https://www.wso2.com).
+ * Copyright (c) 2020-2024, 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
@@ -17,18 +17,16 @@
*/
/**
- * Interface for the admin advisory banner configuration.
+ * Enum for the publishing log types.
*/
-export interface AdminAdvisoryBannerConfigurationInterface {
- bannerContent: string;
- enableBanner: boolean;
-}
-
export enum LogType {
AUDIT = "audit",
CARBON = "carbon"
}
+/**
+ * Interface for the remote log publishing configuration.
+ */
export interface RemoteLogPublishingConfigurationInterface {
/**
* Destination to where the logs should be published.
diff --git a/features/admin.tenants.v1/models/system-settings/ui.ts b/features/admin.tenants.v1/models/system-settings/ui.ts
new file mode 100644
index 00000000000..5c0e738ddf5
--- /dev/null
+++ b/features/admin.tenants.v1/models/system-settings/ui.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+/**
+ * Enum for System Settings modes.
+ */
+export enum SystemSettingsModes {
+ /**
+ * Remote log publishing feature.
+ */
+ REMOTE_LOG_PUBLISHING = "remote-log-publishing",
+ /**
+ * Admin advisory banner feature.
+ */
+ ADMIN_ADVISORY_BANNER = "admin-advisory-banner"
+}
+
+/**
+ * Enum for System Settings tab IDs.
+ */
+export enum SystemSettingsTabIDs {
+ /**
+ * Remote log publishing tab ID.
+ */
+ REMOTE_LOG_PUBLISHING = 0,
+ /**
+ * Admin advisory banner tab ID.
+ */
+ ADMIN_ADVISORY_BANNER = 1
+}
diff --git a/features/admin.tenants.v1/models/tenants.ts b/features/admin.tenants.v1/models/tenants.ts
new file mode 100644
index 00000000000..69f4fecc3a9
--- /dev/null
+++ b/features/admin.tenants.v1/models/tenants.ts
@@ -0,0 +1,156 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+/**
+ * Represents a tenant.
+ */
+export interface Tenant {
+ /**
+ * The unique identifier of the tenant.
+ */
+ id: string;
+ /**
+ * The domain of the tenant.
+ */
+ domain: string;
+ /**
+ * The owners of the tenant.
+ */
+ owners: T[];
+ /**
+ * The date when the tenant was created.
+ */
+ createdDate: string;
+ /**
+ * The lifecycle status of the tenant.
+ */
+ lifecycleStatus: TenantLifecycleStatus;
+}
+
+/**
+ * Ways to configure the password of a tenant.
+ */
+export enum TenantStatus {
+ /**
+ * A valid password should be sent in the request body
+ */
+ INLINE_PASSWORD = "inline-password",
+ /**
+ * An email link will be sent to the given email address to set the password.
+ */
+ INVITE_VIA_EMAIL = "invite-via-email"
+}
+
+/**
+ * Represents a request payload to add a tenant.
+ */
+export type AddTenantRequestPayload = Pick & {
+ /**
+ * The provisioning method of the tenant.
+ */
+ provisioningMethod: TenantStatus;
+ /**
+ * Additional claims to be added to the tenant.
+ */
+ additionalClaims?: {
+ /**
+ * Claim URI. ex: http://wso2.org/claims/telephone
+ */
+ claim: string;
+ /**
+ * Value of the claim.
+ */
+ value: string;
+ }[];
+}>, "domain" | "owners">;
+
+/**
+ * Represents an owner of a tenant.
+ */
+export interface TenantOwner {
+ /**
+ * The unique identifier of the owner.
+ */
+ id: string;
+ /**
+ * The username of the owner.
+ */
+ username: string;
+ /**
+ * The password of the owner.
+ */
+ password?: string;
+ /**
+ * The email of the owner.
+ */
+ email: string;
+ /**
+ * The first name of the owner.
+ */
+ firstname: string;
+ /**
+ * The last name of the owner.
+ */
+ lastname: string;
+ /**
+ * Set of additional user details.
+ */
+ additionalClaims?: {
+ claim: string;
+ value: string;
+ }[];
+}
+
+/**
+ * Represents the lifecycle status of a tenant.
+ */
+export interface TenantLifecycleStatus {
+ /**
+ * Indicates whether the tenant is activated.
+ */
+ activated: boolean;
+}
+
+/**
+ * Represents a tenant response.
+ */
+export interface TenantListResponse {
+ /**
+ * The total number of results.
+ */
+ totalResults: number;
+ /**
+ * The index of the first result.
+ */
+ startIndex: number;
+ /**
+ * The number of results.
+ */
+ count: number;
+ /**
+ * The links.
+ */
+ links: {
+ href: string;
+ rel: "next" | "previous"
+ }[];
+ /**
+ * The tenants list.
+ */
+ tenants: Tenant[];
+}
diff --git a/features/admin.tenants.v1/package.json b/features/admin.tenants.v1/package.json
index 25ae5987b6e..d3bbbf7e6ca 100644
--- a/features/admin.tenants.v1/package.json
+++ b/features/admin.tenants.v1/package.json
@@ -24,6 +24,9 @@
"@wso2is/admin.extensions.v1": "^2.32.2",
"@wso2is/admin.feature-gate.v1": "^1.2.3",
"@wso2is/admin.organizations.v1": "^2.24.2",
+ "@wso2is/admin.server.v1": "^2.23.2",
+ "@wso2is/admin.users.v1": "^2.25.2",
+ "@wso2is/admin.validation.v1": "^2.24.2",
"@wso2is/core": "^2.2.0",
"@wso2is/dynamic-forms": "^2.2.0",
"@wso2is/form": "^2.3.0",
diff --git a/features/admin.tenants.v1/pages/edit-tenant-page.scss b/features/admin.tenants.v1/pages/edit-tenant-page.scss
new file mode 100644
index 00000000000..a28eca1d46f
--- /dev/null
+++ b/features/admin.tenants.v1/pages/edit-tenant-page.scss
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+.tenant-edit-page {
+ .page-header-wrapper {
+ .oxygen-avatar {
+ height: 80px;
+ width: 80px;
+ font-size: 3em;
+ }
+
+ .tenant-status {
+ display: flex;
+ gap: 5px;
+ flex-direction: row;
+ align-items: center;
+
+ .status-dot {
+ height: 9px;
+ width: 9px;
+ border-radius: 50%;
+
+ &.active {
+ background: var(--oxygen-palette-success-main);
+ }
+
+ &.disabled {
+ background: var(--oxygen-palette-error-main);
+ }
+ }
+ }
+ }
+}
diff --git a/features/admin.tenants.v1/pages/edit-tenant-page.tsx b/features/admin.tenants.v1/pages/edit-tenant-page.tsx
new file mode 100644
index 00000000000..33f314746ab
--- /dev/null
+++ b/features/admin.tenants.v1/pages/edit-tenant-page.tsx
@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) 2024, 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 Avatar from "@oxygen-ui/react/Avatar";
+import Tooltip from "@oxygen-ui/react/Tooltip";
+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 { IdentifiableComponentInterface } from "@wso2is/core/models";
+import { 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 useGetTenant from "../api/use-get-tenant";
+import useGetTenantOwner from "../api/use-get-tenant-owner";
+import EditTenant from "../components/edit-tenant/edit-tenant";
+import { Tenant, TenantOwner } from "../models/tenants";
+import TenantProvider from "../providers/tenant-provider";
+import "./edit-tenant-page.scss";
+
+/**
+ * Props interface of {@link EditTenantPage}
+ */
+export interface EditTenantPageProps extends IdentifiableComponentInterface {
+ match: {
+ params: {
+ id: string;
+ };
+ };
+}
+
+/**
+ * Component for editing a tenant.
+ *
+ * @param props - Props injected to the component.
+ * @returns Tenant edit page component.
+ */
+const EditTenantPage: FunctionComponent = ({
+ match,
+ ["data-componentid"]: componentId = "tenant-edit-page"
+}: EditTenantPageProps): ReactElement => {
+ const { t } = useTranslation();
+ const { id } = match.params;
+
+ const { data: tenant, isLoading: isTenantLoading, mutate: mutateTenant } = useGetTenant(id);
+ const { data: tenantOwner, mutate: mutateTenantOwner } = useGetTenantOwner(
+ tenant?.id,
+ tenant?.owners[0]?.id,
+ !!tenant
+ );
+
+ /**
+ * Merges the tenant and tenant owner data.
+ * @remarks Owner details in the tenant request is limited to just `id` & `username`.
+ * Hence we need to aggregate the owner details from the tenant owner request.
+ */
+ const mergedTenant: Tenant = useMemo(() => {
+ if (!tenant || !tenantOwner) {
+ return null;
+ }
+
+ return {
+ ...tenant,
+ owners: tenant.owners?.map((owner: TenantOwner) => {
+ if (owner.id === tenantOwner.id) {
+ return tenantOwner;
+ }
+
+ return owner;
+ })
+ };
+ }, [ tenant, tenantOwner ]);
+
+ return (
+ history.push(AppConstants.getPaths().get("TENANTS")) }
+ onTenantDisableSuccess={ (): void => {
+ mutateTenant();
+ mutateTenantOwner();
+ } }
+ onTenantEnableSuccess={ (): void => {
+ mutateTenant();
+ mutateTenantOwner();
+ } }
+ >
+
+ { tenant?.domain }
+
+
+
+
)
+ }
+ isLoading={ isTenantLoading }
+ description={
+ (
+ { t("tenants:edit.subtitle", { date: moment(tenant?.createdDate).format("MMM DD, YYYY") }) }
+ )
+ }
+ image={
+ (
+ { tenant?.domain?.charAt(0).toUpperCase() }
+ )
+ }
+ data-componentid={ `${componentId}-layout` }
+ backButton={ {
+ "data-componentid": `${componentId}-back-button`,
+ onClick: () => history.push(AppConstants.getPaths().get("TENANTS")),
+ text: t("tenants:edit.backButton")
+ } }
+ className="tenant-edit-page"
+ bottomMargin={ false }
+ >
+
+
+
+ );
+};
+
+export default EditTenantPage;
+history;
diff --git a/features/admin.tenants.v1/pages/system-settings-page.tsx b/features/admin.tenants.v1/pages/system-settings-page.tsx
new file mode 100644
index 00000000000..9a7b917c988
--- /dev/null
+++ b/features/admin.tenants.v1/pages/system-settings-page.tsx
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2024, 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 { 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 { DocumentationLink, PageLayout, useDocumentation } from "@wso2is/react-components";
+import React, { FunctionComponent, ReactElement } from "react";
+import { useTranslation } from "react-i18next";
+import SystemSettingsTabs from "../components/system-settings/system-settings-tabs";
+
+/**
+ * Props interface of {@link SystemSettingsPage}
+ */
+export type SystemSettingsPageProps = IdentifiableComponentInterface;
+
+/**
+ * Page for maintaining system wide settings applicable to all the root organizations.
+ *
+ * @param props - Props injected to the component.
+ * @returns System Settings page component.
+ */
+const SystemSettingsPage: FunctionComponent = ({
+ ["data-componentid"]: componentId = "root-organizations-page"
+}: SystemSettingsPageProps): ReactElement => {
+ const { t } = useTranslation();
+ const { getLink } = useDocumentation();
+
+ return (
+
+ { t("tenants:systemSettings.subtitle") }
+
+ { t("common:learnMore") }
+
+ >)
+ }
+ data-componentid={ `${componentId}-layout` }
+ backButton={ {
+ "data-componentid": `${componentId}-back-button`,
+ onClick: () => history.push(AppConstants.getPaths().get("TENANTS")),
+ text: t("tenants:systemSettings.backButton")
+ } }
+ className="system-settings-page"
+ >
+
+
+ );
+};
+
+export default SystemSettingsPage;
diff --git a/features/admin.tenants.v1/pages/tenants-page.tsx b/features/admin.tenants.v1/pages/tenants-page.tsx
new file mode 100644
index 00000000000..cb48a9c63b1
--- /dev/null
+++ b/features/admin.tenants.v1/pages/tenants-page.tsx
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2024, 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 } from "react";
+import TenantsPageLayout from "../components/tenants-page-layout";
+import TenantProvider from "../providers/tenant-provider";
+
+/**
+ * Props interface of {@link TenantsPage}
+ */
+type TenantsPageProps = IdentifiableComponentInterface;
+
+/**
+ * Landing page for the Tenants feature.
+ *
+ * @param props - Props injected to the component.
+ * @returns Tenant listing page component.
+ */
+const TenantsPage: FunctionComponent = ({
+ ["data-componentid"]: componentId = "tenants-page"
+}: TenantsPageProps): ReactElement => {
+ return (
+
+
+
+ );
+};
+
+export default TenantsPage;
diff --git a/features/admin.tenants.v1/providers/tenant-provider.tsx b/features/admin.tenants.v1/providers/tenant-provider.tsx
new file mode 100644
index 00000000000..99b3023d3a6
--- /dev/null
+++ b/features/admin.tenants.v1/providers/tenant-provider.tsx
@@ -0,0 +1,291 @@
+/**
+ * Copyright (c) 2024, 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 { UIConstants } from "@wso2is/admin.core.v1/constants/ui-constants";
+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 { 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";
+import useGetTenants from "../api/use-get-tenants";
+import TenantContext from "../context/tenant-context";
+import { Tenant, TenantLifecycleStatus } from "../models/tenants";
+
+/**
+ * Props interface of {@link TenantProvider}
+ */
+export interface TenantProviderProps {
+ /**
+ * Callback to be fired on successful tenant deletion.
+ */
+ onTenantDeleteSuccess?: () => void;
+ /**
+ * Callback to be fired on un-successful tenant deletion.
+ */
+ onTenantDeleteError?: () => void;
+ /**
+ * Callback to be fired on successful tenant disable.
+ */
+ onTenantDisableSuccess?: () => void;
+ /**
+ * Callback to be fired on un-successful tenant disable.
+ */
+ onTenantDisableError?: () => void;
+ /**
+ * Callback to be fired on successful tenant enable.
+ */
+ onTenantEnableSuccess?: () => void;
+ /**
+ * Callback to be fired on un-successful tenant enable.
+ */
+ onTenantEnableError?: () => void;
+};
+
+/**
+ * This component provides tenant-related context to its children.
+ *
+ * @param props - Props injected to the component.
+ * @returns The TenantProvider component.
+ */
+const TenantProvider = ({
+ children,
+ onTenantDeleteError,
+ onTenantDeleteSuccess,
+ onTenantDisableError,
+ onTenantDisableSuccess,
+ onTenantEnableError,
+ onTenantEnableSuccess
+}: PropsWithChildren): ReactElement => {
+ const { t } = useTranslation();
+ const dispatch: Dispatch = useDispatch();
+
+ 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 [ deletingTenant, setDeletingTenant ] = useState(null);
+ const [ disablingTenant, setDisablingTenant ] = useState(null);
+ const [ isTenantDeleteRequestLoading, setIsTenantDeleteRequestLoading ] = useState(false);
+ const [ isTenantStatusUpdateRequestLoading, setIsTenantStatusUpdateRequestLoading ] = useState(false);
+
+ const { data: tenantList, isLoading: isTenantListLoading, mutate: mutateTenantList } = useGetTenants({
+ limit: tenantListLimit,
+ offset: 0,
+ sortBy: "domainName",
+ sortOrder: "asc"
+ });
+
+ useEffect(() => {
+ if (tenantList && tenantList?.tenants?.length > 0) {
+ setIsInitialRenderingComplete(true);
+ }
+ }, [ tenantList ]);
+
+ /**
+ * Handles the tenant deletion after the user confirms the action.
+ * @param tenant - Tenant to be deleted.
+ */
+ const handleTenantDeletionConfirmation = (tenant: Tenant): void => {
+ setIsTenantDeleteRequestLoading(true);
+
+ deleteTenantMetadata(tenant.id)
+ .then(() => {
+ dispatch(
+ addAlert({
+ description: t("tenants:editTenant.notifications.deleteTenantMeta.success.description", {
+ tenantDomain: tenant.domain
+ }),
+ level: AlertLevels.SUCCESS,
+ message: t("tenants:editTenant.notifications.deleteTenantMeta.success.message")
+ })
+ );
+
+ mutateTenantList();
+ onTenantDeleteSuccess && onTenantDeleteSuccess();
+ })
+ .catch(() => {
+ dispatch(
+ addAlert({
+ description: t("tenants:editTenant.notifications.deleteTenantMeta.error.description", {
+ tenantDomain: tenant.domain
+ }),
+ level: AlertLevels.ERROR,
+ message: t("tenants:editTenant.notifications.deleteTenantMeta.error.message")
+ })
+ );
+
+ onTenantDeleteError && onTenantDeleteError();
+ })
+ .finally(() => {
+ setShowDeleteConfirmationModal(false);
+ setIsTenantDeleteRequestLoading(false);
+ });
+ };
+
+ /**
+ * Handles the tenant status update after the user confirms the action.
+ * @param tenant - Tenant to be enabled/disabled.
+ */
+ const handleTenantStatusUpdateConfirmation = (tenant: Tenant): void => {
+ setIsTenantStatusUpdateRequestLoading(true);
+
+ const newLifecycleStatus: TenantLifecycleStatus = {
+ activated: !tenant?.lifecycleStatus?.activated
+ };
+
+ updateTenantActivationStatus(tenant.id, newLifecycleStatus)
+ .then(() => {
+ dispatch(
+ addAlert({
+ description: t("tenants:editTenant.notifications.updateTenantStatus.success.description", {
+ operation: newLifecycleStatus.activated ? t("common:enabled") : t("common:disabled"),
+ tenantDomain: tenant.domain
+ }),
+ level: AlertLevels.SUCCESS,
+ message: t("tenants:editTenant.notifications.updateTenantStatus.success.message", {
+ operation: newLifecycleStatus.activated ? t("common:enabled") : t("common:disabled")
+ })
+ })
+ );
+
+ mutateTenantList();
+
+ if (newLifecycleStatus.activated) {
+ onTenantEnableSuccess && onTenantEnableSuccess();
+ } else {
+ onTenantDisableSuccess && onTenantDisableSuccess();
+ }
+ })
+ .catch(() => {
+ dispatch(
+ addAlert({
+ description: t("tenants:editTenant.notifications.updateTenantStatus.error.description", {
+ operation: newLifecycleStatus.activated ? t("common:enable") : t("common:disable"),
+ tenantDomain: tenant.domain
+ }),
+ level: AlertLevels.ERROR,
+ message: t("tenants:editTenant.notifications.updateTenantStatus.error.message", {
+ operation: newLifecycleStatus.activated ? t("common:enable") : t("common:disable")
+ })
+ })
+ );
+
+ if (newLifecycleStatus.activated) {
+ onTenantEnableError && onTenantEnableError();
+ } else {
+ onTenantDisableError && onTenantDisableError();
+ }
+ })
+ .finally(() => {
+ setShowDisableConfirmationModal(false);
+ setIsTenantStatusUpdateRequestLoading(false);
+ });
+ };
+
+ return (
+ {
+ setShowDeleteConfirmationModal(true);
+ setDeletingTenant(tenant);
+ },
+ disableTenant: (tenant: Tenant): void => {
+ setShowDisableConfirmationModal(true);
+ setDisablingTenant(tenant);
+ },
+ enableTenant: (tenant: Tenant): void => {
+ handleTenantStatusUpdateConfirmation(tenant);
+ },
+ isInitialRenderingComplete,
+ isTenantListLoading,
+ mutateTenantList,
+ setTenantListLimit,
+ tenantList,
+ tenantListLimit
+ } }
+ >
+ { children }
+ { showDeleteConfirmationModal && (
+ setShowDeleteConfirmationModal(false) }
+ type="negative"
+ open={ showDeleteConfirmationModal }
+ assertionHint={ t("tenants:confirmationModals.deleteTenant.assertionHint") }
+ assertionType="checkbox"
+ primaryAction={ t("tenants:confirmationModals.deleteTenant.primaryAction") }
+ secondaryAction={ t("tenants:confirmationModals.deleteTenant.secondaryAction") }
+ onSecondaryActionClick={ (): void => setShowDeleteConfirmationModal(false) }
+ onPrimaryActionClick={ (): void => handleTenantDeletionConfirmation(deletingTenant) }
+ closeOnDimmerClick={ false }
+ >
+
+ { t("tenants:confirmationModals.deleteTenant.header") }
+
+
+ { t("tenants:confirmationModals.deleteTenant.message") }
+
+
+ { t("tenants:confirmationModals.deleteTenant.content") }
+
+
+ ) }
+ { showDisableConfirmationModal && (
+ setShowDisableConfirmationModal(false) }
+ type="negative"
+ open={ showDisableConfirmationModal }
+ assertionHint={ t("tenants:confirmationModals.disableTenant.assertionHint") }
+ assertionType="checkbox"
+ primaryAction={ t("tenants:confirmationModals.disableTenant.primaryAction") }
+ secondaryAction={ t("tenants:confirmationModals.disableTenant.secondaryAction") }
+ onSecondaryActionClick={ (): void => setShowDisableConfirmationModal(false) }
+ onPrimaryActionClick={ (): void => handleTenantStatusUpdateConfirmation(disablingTenant) }
+ closeOnDimmerClick={ false }
+ >
+
+ { t("tenants:confirmationModals.disableTenant.header") }
+
+
+ { t("tenants:confirmationModals.disableTenant.message") }
+
+
+ { t("tenants:confirmationModals.disableTenant.content") }
+
+
+ ) }
+
+ );
+};
+
+export default TenantProvider;
diff --git a/features/admin.users.v1/components/wizard/steps/add-consumer-user.tsx b/features/admin.users.v1/components/wizard/steps/add-consumer-user.tsx
index 8743699bc09..d375dbea389 100644
--- a/features/admin.users.v1/components/wizard/steps/add-consumer-user.tsx
+++ b/features/admin.users.v1/components/wizard/steps/add-consumer-user.tsx
@@ -37,6 +37,7 @@ import {
Message
} from "semantic-ui-react";
import { UserManagementConstants } from "../../../constants";
+import getUsertoreUsernameValidationPattern from "../../../utils/get-usertore-usernam-validation-pattern";
/**
* Proptypes for the add consumer user component.
@@ -88,7 +89,6 @@ export const AddConsumerUser: React.FunctionComponent = (
"username.validations.regExViolation");
const USERNAME_HAS_INVALID_CHARS_ERROR_MESSAGE: string = t("user:forms.addUserForm." +
"inputs.username.validations.invalidCharacters");
- const USERNAME_JAVA_REGEX: string = "UsernameJavaRegEx";
const USERNAME_HAS_INVALID_SYMBOLS_ERROR_MESSAGE: string = t("extensions:manage.features.user.addUser.validation." +
"usernameSymbols");
const USERNAME_HAS_INVALID_SPECIAL_SYMBOLS_ERROR_MESSAGE: string = t("extensions:manage.features.user.addUser." +
@@ -356,27 +356,6 @@ export const AddConsumerUser: React.FunctionComponent = (
}
};
- /**
- * The following function validates user name against the user store regEx.
- */
- const validateUserNamePattern = async (): Promise => {
- try {
- let regex: string = await SharedUserStoreUtils.getUserStoreRegEx(
- userstoresConfig.primaryUserstoreName, USERNAME_JAVA_REGEX);
-
- if (!regex.startsWith("^")) {
- regex = "^" + regex;
- }
- if (!regex.endsWith("$")) {
- regex = regex + "$";
- }
-
- return regex;
- } catch (error) {
- return Promise.reject("");
- }
- };
-
/**
* The modal to add new user.
*/
@@ -412,7 +391,7 @@ export const AddConsumerUser: React.FunctionComponent = (
return;
}
- const userRegex: string = await validateUserNamePattern();
+ const userRegex: string = await getUsertoreUsernameValidationPattern();
if (!SharedUserStoreUtils.validateInputAgainstRegEx(value, userRegex) ||
!FormValidation.email(value)) {
@@ -532,7 +511,7 @@ export const AddConsumerUser: React.FunctionComponent = (
) }
validation={ async (value: string, validation: Validation) => {
- const userRegex: string = await validateUserNamePattern();
+ const userRegex: string = await getUsertoreUsernameValidationPattern();
// Check username validity against userstore regex.
if (value && (
diff --git a/features/admin.users.v1/constants/user-management-constants.ts b/features/admin.users.v1/constants/user-management-constants.ts
index 905ea537ce4..9908f4cb2e0 100644
--- a/features/admin.users.v1/constants/user-management-constants.ts
+++ b/features/admin.users.v1/constants/user-management-constants.ts
@@ -154,6 +154,8 @@ export class UserManagementConstants {
public static readonly MANAGED_BY_PARENT_TEXT: string = "Parent Organization";
public static readonly GLOBE: string = "globe";
+
+ public static readonly USERNAME_JAVA_REGEX: string = "UsernameJavaRegEx";
}
/**
diff --git a/features/admin.users.v1/utils/get-usertore-usernam-validation-pattern.ts b/features/admin.users.v1/utils/get-usertore-usernam-validation-pattern.ts
new file mode 100644
index 00000000000..8ae014487ee
--- /dev/null
+++ b/features/admin.users.v1/utils/get-usertore-usernam-validation-pattern.ts
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2024, 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 { SharedUserStoreUtils } from "@wso2is/admin.core.v1/utils";
+import { userstoresConfig } from "@wso2is/admin.extensions.v1/configs/userstores";
+import { UserManagementConstants } from "../constants/user-management-constants";
+
+/**
+ * Retrieves the username validation pattern from the primary user store.
+ *
+ * @remarks
+ * This function fetches the username regular expression from the primary user store
+ * and ensures that it starts with `^` and ends with `$` to match the entire string.
+ *
+ * @returns A promise that resolves to the validated username regular expression.
+ * @throws Will reject the promise with an empty string if an error occurs.
+ */
+const getUsertoreUsernameValidationPattern = async (): Promise => {
+ try {
+ let regex: string = await SharedUserStoreUtils.getUserStoreRegEx(
+ userstoresConfig.primaryUserstoreName, UserManagementConstants.USERNAME_JAVA_REGEX);
+
+ if (!regex.startsWith("^")) {
+ regex = "^" + regex;
+ }
+ if (!regex.endsWith("$")) {
+ regex = regex + "$";
+ }
+
+ return regex;
+ } catch (error) {
+ return Promise.reject("");
+ }
+};
+
+export default getUsertoreUsernameValidationPattern;
diff --git a/modules/form/src/components/adapters/select-field-adapter.tsx b/modules/form/src/components/adapters/select-field-adapter.tsx
index bfdd920cf3d..801ddc30783 100644
--- a/modules/form/src/components/adapters/select-field-adapter.tsx
+++ b/modules/form/src/components/adapters/select-field-adapter.tsx
@@ -23,7 +23,6 @@ import Select, { SelectProps } from "@oxygen-ui/react/Select";
import { IdentifiableComponentInterface } from "@wso2is/core/models";
import React, { ChangeEvent, FunctionComponent, ReactElement, ReactNode } from "react";
import { FieldRenderProps } from "react-final-form";
-import "./text-field-adapter.scss";
/**
* Interface for the DropDownItem.
diff --git a/modules/form/src/components/adapters/text-field-adapter.tsx b/modules/form/src/components/adapters/text-field-adapter.tsx
index 17564d718e5..96540072ca8 100644
--- a/modules/form/src/components/adapters/text-field-adapter.tsx
+++ b/modules/form/src/components/adapters/text-field-adapter.tsx
@@ -21,7 +21,6 @@ import FormHelperText from "@oxygen-ui/react/FormHelperText";
import TextField from "@oxygen-ui/react/TextField";
import React, { FunctionComponent, ReactElement } from "react";
import { FieldRenderProps } from "react-final-form";
-import "./text-field-adapter.scss";
/**
* Props for the TextFieldAdapter component.
@@ -68,7 +67,6 @@ const TextFieldAdapter: FunctionComponent = (
return (
<>
value ? undefined : "This field is required";
+ * const isEmail = (value: string) => /\S+@\S+\.\S+/.test(value) ? undefined : "Invalid email address";
+ * const validate = composeValidators(isRequired, isEmail);
+ * const error = validate("example@domain.com"); // undefined
+ * const error2 = validate(""); // "This field is required"
+ * ```
+ */
+const composeValidators = (...validators: Array<(value: any) => string | undefined | any>) => (
+ value: any
+): string | undefined => validators.reduce((error: any, validator: any) => error || validator(value), undefined);
+
+export default composeValidators;
diff --git a/modules/form/src/utils/index.ts b/modules/form/src/utils/index.ts
index 608e8ac8f66..a853862922a 100644
--- a/modules/form/src/utils/index.ts
+++ b/modules/form/src/utils/index.ts
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2019, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
+ * Copyright (c) 2019-2024, 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
@@ -16,4 +16,5 @@
* under the License.
*/
+export { default as composeValidators } from "./compose-validators";
export * from "./validate";
diff --git a/modules/i18n/src/constants.ts b/modules/i18n/src/constants.ts
index 2a7991d045a..14eb29b2fe4 100644
--- a/modules/i18n/src/constants.ts
+++ b/modules/i18n/src/constants.ts
@@ -320,4 +320,9 @@ export class I18nModuleConstants {
* @default
*/
public static readonly ACTIONS_NAMESPACE: string = "actions";
+
+ /**
+ * Tenants namespace.
+ */
+ public static readonly TENANTS_NAMESPACE: string = "tenants";
}
diff --git a/modules/i18n/src/models/namespaces/console-ns.ts b/modules/i18n/src/models/namespaces/console-ns.ts
index 8d8def0023f..9cd72c9f82c 100644
--- a/modules/i18n/src/models/namespaces/console-ns.ts
+++ b/modules/i18n/src/models/namespaces/console-ns.ts
@@ -352,6 +352,7 @@ export interface ConsoleNS {
userAttributesAndStores: string;
userManagement: string;
branding: string;
+ tenants: string;
};
};
develop: {
diff --git a/modules/i18n/src/models/namespaces/index.ts b/modules/i18n/src/models/namespaces/index.ts
index 5b9b0d30d71..e4e7ee823f6 100644
--- a/modules/i18n/src/models/namespaces/index.ts
+++ b/modules/i18n/src/models/namespaces/index.ts
@@ -61,3 +61,4 @@ export * from "./api-resources-ns";
export * from "./ai-ns";
export * from "./impersonation-ns";
export * from "./actions-ns";
+export * from "./tenants-ns";
diff --git a/modules/i18n/src/models/namespaces/server-configs-ns.ts b/modules/i18n/src/models/namespaces/server-configs-ns.ts
index 350c0a15a27..4a674379316 100644
--- a/modules/i18n/src/models/namespaces/server-configs-ns.ts
+++ b/modules/i18n/src/models/namespaces/server-configs-ns.ts
@@ -99,6 +99,7 @@ export interface serverConfigsNS {
};
pageHeading: string;
pageSubheading: string;
+ title: string;
};
manageNotificationSendingInternally: {
title: string;
diff --git a/modules/i18n/src/models/namespaces/tenants-ns.ts b/modules/i18n/src/models/namespaces/tenants-ns.ts
new file mode 100644
index 00000000000..67ea0ced0d9
--- /dev/null
+++ b/modules/i18n/src/models/namespaces/tenants-ns.ts
@@ -0,0 +1,299 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+export interface TenantsNS {
+ actions: {
+ newTenant: {
+ title: string;
+ };
+ systemSettings: {
+ tooltip: string;
+ };
+ };
+ addTenant: {
+ actions: {
+ cancel: {
+ label: string;
+ };
+ save: {
+ label: string;
+ };
+ };
+ form: {
+ adminDetails: {
+ title: string;
+ };
+ };
+ notifications: {
+ addTenant: {
+ error: {
+ description: string;
+ message: string;
+ };
+ success: {
+ description: string;
+ message: string;
+ };
+ };
+ };
+ subtitle: string;
+ title: string;
+ };
+ common: {
+ form: {
+ fields: {
+ alphanumericUsername: {
+ label: string;
+ placeholder: string;
+ validations: {
+ usernameHint: string;
+ usernameSpecialCharHint: string;
+ };
+ };
+ domain: {
+ helperText: string;
+ label: string;
+ placeholder: string;
+ validations: {
+ domainUnavailable: string;
+ required: string;
+ };
+ };
+ email: {
+ label: string;
+ placeholder: string;
+ validations: {
+ required: string;
+ };
+ };
+ emailUsername: {
+ helperTextAlt: string;
+ label: string;
+ placeholder: string;
+ };
+ firstname: {
+ label: string;
+ placeholder: string;
+ validations: {
+ required: string;
+ };
+ };
+ id: {
+ helperText: string;
+ label: string;
+ placeholder: string;
+ };
+ lastname: {
+ label: string;
+ placeholder: string;
+ validations: {
+ required: string;
+ };
+ };
+ password: {
+ actions: {
+ generate: {
+ label: string;
+ };
+ };
+ label: string;
+ placeholder: string;
+ validations: {
+ criteria: {
+ consecutiveCharacters: string;
+ lowerCase: string;
+ passwordCase: string;
+ passwordLength: string;
+ passwordNumeric: string;
+ specialCharacter: string;
+ uniqueCharacters: string;
+ upperCase: string;
+ };
+ required: string;
+ };
+ };
+ username: {
+ helperText: string;
+ label: string;
+ placeholder: string;
+ validations: {
+ regExViolation: string;
+ required: string;
+ usernameLength: string;
+ usernameSpecialCharSymbols: string;
+ usernameSymbols: string;
+ };
+ };
+ };
+ };
+ };
+ confirmationModals: {
+ deleteTenant: {
+ assertionHint: string;
+ content: string;
+ header: string;
+ message: string;
+ primaryAction: string;
+ secondaryAction: string;
+ };
+ disableTenant: {
+ assertionHint: string;
+ content: string;
+ header: string;
+ message: string;
+ primaryAction: string;
+ secondaryAction: string;
+ };
+ },
+ edit: {
+ backButton: string;
+ subtitle: string;
+ };
+ editTenant: {
+ actions: {
+ save: {
+ label: string;
+ };
+ };
+ dangerZoneGroup: {
+ delete: {
+ actionTitle: string;
+ header: string;
+ subheader: string;
+ };
+ disable: {
+ actionTitle: string;
+ header: string;
+ subheader: string;
+ };
+ header: string;
+ };
+ disabledDisclaimer: {
+ actions: {
+ enable: {
+ label: string;
+ };
+ };
+ content: string;
+ title: string;
+ };
+ notifications: {
+ deleteTenantMeta: {
+ error: {
+ description: string;
+ message: string;
+ };
+ success: {
+ description: string;
+ message: string;
+ };
+ };
+ updateTenant: {
+ error: {
+ description: string;
+ message: string;
+ };
+ success: {
+ description: string;
+ message: string;
+ };
+ };
+ updateTenantStatus: {
+ error: {
+ description: string;
+ message: string;
+ };
+ success: {
+ description: string;
+ message: string;
+ };
+ };
+ };
+ };
+ listing: {
+ count: string;
+ emptyPlaceholder: {
+ actions: {
+ configure: {
+ label: string;
+ };
+ divider: string;
+ new: {
+ label: string;
+ };
+ };
+ subtitles: {
+ 0: string;
+ 1: string;
+ };
+ title: string;
+ };
+ item: {
+ actions: {
+ delete: {
+ label: string;
+ };
+ edit: {
+ label: string;
+ };
+ goToConsole: {
+ label: string;
+ };
+ more: {
+ label: string;
+ };
+ };
+ meta: {
+ createdOn: {
+ label: string;
+ };
+ owner: {
+ label: string;
+ };
+ };
+ };
+ };
+ status: {
+ activate: string;
+ activated: string;
+ deActivate: string;
+ deActivated: string;
+ };
+ subtitle: string;
+ systemSettings: {
+ actions: {
+ newTenant: {
+ title: string;
+ };
+ systemSettings: {
+ tooltip: string;
+ };
+ };
+ backButton: string;
+ subtitle: string;
+ title: string;
+ };
+ tenantDropdown: {
+ options: {
+ manage: {
+ label: string;
+ };
+ };
+ };
+ title: string;
+}
diff --git a/modules/i18n/src/translations/en-US/meta.ts b/modules/i18n/src/translations/en-US/meta.ts
index 09c972fd620..e4103740538 100644
--- a/modules/i18n/src/translations/en-US/meta.ts
+++ b/modules/i18n/src/translations/en-US/meta.ts
@@ -67,6 +67,7 @@ export const meta: LocaleMeta = {
I18nModuleConstants.IDP_NAMESPACE,
I18nModuleConstants.API_RESOURCES_NAMESPACE,
I18nModuleConstants.AI_NAMESPACE,
- I18nModuleConstants.ACTIONS_NAMESPACE
+ I18nModuleConstants.ACTIONS_NAMESPACE,
+ I18nModuleConstants.TENANTS_NAMESPACE
]
};
diff --git a/modules/i18n/src/translations/en-US/portals/console.ts b/modules/i18n/src/translations/en-US/portals/console.ts
index fa592df32a6..6e45bb64e15 100644
--- a/modules/i18n/src/translations/en-US/portals/console.ts
+++ b/modules/i18n/src/translations/en-US/portals/console.ts
@@ -456,7 +456,8 @@ export const console: ConsoleNS = {
},
userAttributesAndStores: "User Attributes & Stores",
userManagement: "User Management",
- branding: "Branding"
+ branding: "Branding",
+ tenants: "Root Organizations"
},
validations: {
inSecureURL: {
diff --git a/modules/i18n/src/translations/en-US/portals/index.ts b/modules/i18n/src/translations/en-US/portals/index.ts
index a2f8e96dbf0..ea845df05d8 100644
--- a/modules/i18n/src/translations/en-US/portals/index.ts
+++ b/modules/i18n/src/translations/en-US/portals/index.ts
@@ -61,3 +61,4 @@ export * from "./template-core";
export * from "./application-templates";
export * from "./impersonation";
export * from "./actions";
+export * from "./tenants";
diff --git a/modules/i18n/src/translations/en-US/portals/organizations.ts b/modules/i18n/src/translations/en-US/portals/organizations.ts
index 9e9704b845e..e989f1f12e5 100644
--- a/modules/i18n/src/translations/en-US/portals/organizations.ts
+++ b/modules/i18n/src/translations/en-US/portals/organizations.ts
@@ -311,7 +311,7 @@ export const organizations: organizationsNS = {
},
subOrganizations: "Organizations",
switchButton: "Switch to Organization",
- switchLabel: "Organization"
+ switchLabel: "Root Organization"
},
title: "Organizations",
unshareApplicationInfo: "This will allow you to prevent sharing this application with any of the " +
diff --git a/modules/i18n/src/translations/en-US/portals/pages.ts b/modules/i18n/src/translations/en-US/portals/pages.ts
index c50a7a8870b..70e437ce75f 100644
--- a/modules/i18n/src/translations/en-US/portals/pages.ts
+++ b/modules/i18n/src/translations/en-US/portals/pages.ts
@@ -17,6 +17,9 @@
*/
import { pagesNS } from "../../../models";
+/**
+ * @deprecated Add the relevant page title and description in the respective feature's i18n file.
+ */
export const pages: pagesNS = {
actions: {
subTitle: "Create actions to extend login & registration flows.",
diff --git a/modules/i18n/src/translations/en-US/portals/server-configs.ts b/modules/i18n/src/translations/en-US/portals/server-configs.ts
index 83408a80727..eac17145a43 100644
--- a/modules/i18n/src/translations/en-US/portals/server-configs.ts
+++ b/modules/i18n/src/translations/en-US/portals/server-configs.ts
@@ -17,6 +17,8 @@
*/
import { serverConfigsNS } from "../../../models";
+/* eslint-disable max-len */
+/* eslint-disable sort-keys */
export const serverConfigs: serverConfigsNS = {
adminAdvisory: {
configurationEditSection: {
@@ -96,7 +98,8 @@ export const serverConfigs: serverConfigsNS = {
}
},
pageHeading: "Admin Advisory Banner",
- pageSubheading: "Configure the admin advisory banner to be displayed on the login page."
+ pageSubheading: "Configure the admin advisory banner to be displayed on the login page.",
+ title: "Admin Advisory Banner"
},
manageNotificationSendingInternally: {
title: "Internal Notification Sending",
diff --git a/modules/i18n/src/translations/en-US/portals/tenants.ts b/modules/i18n/src/translations/en-US/portals/tenants.ts
new file mode 100644
index 00000000000..c0e29cf9941
--- /dev/null
+++ b/modules/i18n/src/translations/en-US/portals/tenants.ts
@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2024, 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.
+ */
+
+/* eslint-disable max-len */
+
+import { TenantsNS } from "../../../models/namespaces/tenants-ns";
+
+export const tenants: TenantsNS = {
+ actions: {
+ newTenant: {
+ title: "New Root Organization"
+ },
+ systemSettings: {
+ tooltip: "Configure System Settings"
+ }
+ },
+ addTenant: {
+ actions: {
+ cancel: {
+ label: "Cancel"
+ },
+ save: {
+ label: "Create"
+ }
+ },
+ form: {
+ adminDetails: {
+ title: "Admin Details"
+ }
+ },
+ notifications: {
+ addTenant: {
+ error: {
+ description: "An error occurred while creating the organization.",
+ message: "Couldn't create the organization"
+ },
+ success: {
+ description: "Successfully created the root organization.",
+ message: "Organization created"
+ }
+ }
+ },
+ subtitle: "Add a new root organization to share the server in an isolated manner.",
+ title: "Create a Root Organization"
+ },
+ common: {
+ form: {
+ fields: {
+ alphanumericUsername: {
+ label: "Username",
+ placeholder: "Enter the username",
+ validations: {
+ usernameHint: "Must be an alphanumeric (a-z, A-Z, 0-9) string between {{minLength}} to {{maxLength}} characters including at least one letter.",
+ usernameSpecialCharHint: "Must be {{minLength}} to {{maxLength}} characters long, including at least one letter, and may contain a combination of the following characters: a-z, A-Z, 0-9, !@#$&'+\\=^.{|}~-."
+ }
+ },
+ domain: {
+ helperText: "Enter a unique domain name for your organization. The domain name should be in the format of <1>abc.com1>.",
+ label: "Domain",
+ placeholder: "Enter a Domain name",
+ validations: {
+ domainUnavailable: "A domain with the same name already exists.",
+ required: "A domain name is required."
+ }
+ },
+ email: {
+ label: "Email",
+ placeholder: "Enter the admin’s email address.",
+ validations: {
+ required: "Email is required."
+ }
+ },
+ emailUsername: {
+ helperTextAlt: "Enter a valid email address to be used as the username.",
+ label: "Username (Email)",
+ placeholder: "Enter an email address or a username to be used as the username."
+ },
+ firstname: {
+ label: "First Name",
+ placeholder: "Enter the admin’s first name.",
+ validations: {
+ required: "First name is required."
+ }
+ },
+ id: {
+ helperText: "Choose a unique username for the administrator.",
+ label: "Tenant ID",
+ placeholder: "Enter a Tenant ID."
+ },
+ lastname: {
+ label: "Last Name",
+ placeholder: "Enter the admin’s last name.",
+ validations: {
+ required: "Last name is required."
+ }
+ },
+ password: {
+ actions: {
+ generate: {
+ label: "Generate"
+ }
+ },
+ label: "Password",
+ placeholder: "Enter a password for the administrator.",
+ validations: {
+ criteria: {
+ consecutiveCharacters: "No more than {{repeatedChr}} repeated character(s)",
+ lowerCase: "At least {{minLowerCase}} lowercase letter(s)",
+ passwordCase: "At least {{minUpperCase}} uppercase and {{minLowerCase}} lowercase letters",
+ passwordLength: "Must be between {{min}} and {{max}} characters",
+ passwordNumeric: "At least {{min}} number(s)",
+ specialCharacter: "At least {{specialChr}} special character(s)",
+ uniqueCharacters: "At least {{uniqueChr}} unique character(s)",
+ upperCase: "At least {{minUpperCase}} uppercase letter(s)"
+ },
+ required: "Password is required."
+ }
+ },
+ username: {
+ helperText: "Choose a unique username for the administrator.",
+ label: "Username",
+ placeholder: "Enter a username for the administrator.",
+ validations: {
+ regExViolation: "Please enter a valid username.",
+ required: "Username is required.",
+ usernameLength: "The username length should be between {{minLength}} and {{maxLength}}.",
+ usernameSpecialCharSymbols: "Please choose a valid username that adheres to the given guidelines.",
+ usernameSymbols: "The username should consist of alphanumeric characters (a-z, A-Z, 0-9) and must include at least one letter."
+ }
+ }
+ }
+ }
+ },
+ confirmationModals: {
+ deleteTenant: {
+ assertionHint: "Please confirm your action.",
+ content: "If you delete this organization, users will not be able to use the services offered by the organization. Please proceed with caution.",
+ header: "Are you sure?",
+ message: "This action is irreversible and will permanently delete the organization.",
+ primaryAction: "Confirm",
+ secondaryAction: "Cancel"
+ },
+ disableTenant: {
+ assertionHint: "Please confirm your action.",
+ content: "If you disable this organization, users will not be able to use the services offered by the organization until it is enabled again. Please proceed with caution.",
+ header: "Are you sure?",
+ message: "This action will temporarily disable the organization.",
+ primaryAction: "Confirm",
+ secondaryAction: "Cancel"
+ }
+ },
+ edit: {
+ backButton: "Go back to Root Organizations",
+ subtitle: "Crated on {{date}}"
+ },
+ editTenant: {
+ actions: {
+ save: {
+ label: "Update"
+ }
+ },
+ dangerZoneGroup: {
+ delete: {
+ actionTitle: "Delete",
+ header: "Delete Organization",
+ subheader: "Once you delete a root organization, there is no going back. Please proceed with caution."
+ },
+ disable: {
+ actionTitle: "Disable",
+ header: "DisableDisable Organization",
+ subheader: "Users will not be able to log in to any application or perform any relevant tasks of the organization until it is enabled again."
+ },
+ header: "Danger Zone"
+ },
+ disabledDisclaimer: {
+ actions: {
+ enable: {
+ label: "Enable"
+ }
+ },
+ content: "This organization is currently in a <1>disabled1> state. Users will not be able to log in to any of the applications or perform any relevant tasks of the organization until it is enabled again.",
+ title: "Warning"
+ },
+ notifications: {
+ deleteTenantMeta: {
+ error: {
+ description: "An error occurred while deleting the {{tenantDomain}} organization.",
+ message: "Couldn't delete"
+ },
+ success: {
+ description: "Successfully deleted the {{tenantDomain}} organization.",
+ message: "Organization deleted"
+ }
+ },
+ updateTenant: {
+ error: {
+ description: "An error occurred while trying to update the organization",
+ message: "Couldn't update the organization"
+ },
+ success: {
+ description: "Successfully updated the organization.",
+ message: "Organization updated"
+ }
+ },
+ updateTenantStatus: {
+ error: {
+ description: "An error occurred while trying to {{operation}} the {{tenantDomain}} organization",
+ message: "Couldn't {{operation}} the organization"
+ },
+ success: {
+ description: "Successfully {{operation}} the {{tenantDomain}} organization.",
+ message: "Organization {{operation}}"
+ }
+ }
+ }
+ },
+ listing: {
+ count: "Showing {{results}} out of {{totalResults}}",
+ emptyPlaceholder: {
+ actions: {
+ configure: {
+ label: "Configure System Settings"
+ },
+ divider: "OR",
+ new: {
+ label: "New Root Organization"
+ }
+ },
+ subtitles: {
+ 0: "There are no root organizations available at the moment.",
+ 1: "Start with creating your first root organization. or configure system settings that applies to all the root organizations."
+ },
+ title: "No groups assigned to the role."
+ },
+ item: {
+ actions: {
+ delete: {
+ label: "Delete"
+ },
+ edit: {
+ label: "Edit"
+ },
+ goToConsole: {
+ label: "Go to Console"
+ },
+ more: {
+ label: "More"
+ }
+ },
+ meta: {
+ createdOn: {
+ label: "Created on <1>{{date}}1>"
+ },
+ owner: {
+ label: "Owned by <1>{{owner}}1>"
+ }
+ }
+ }
+ },
+ status: {
+ activate: "Enable",
+ activated: "Enabled",
+ deActivate: "Disable",
+ deActivated: "Disabled"
+ },
+ subtitle: "Configure and extend you server by creating new root level organizations in your workspace.",
+ systemSettings: {
+ actions: {
+ newTenant: {
+ title: "New Root Organization"
+ },
+ systemSettings: {
+ tooltip: "Configure System Settings"
+ }
+ },
+ backButton: "Go back to Root Organizations",
+ subtitle: "Configure and manage global configurations that applies across the system. .",
+ title: "System Settings"
+ },
+ tenantDropdown: {
+ options: {
+ manage: {
+ label: "Manage Root Organizations"
+ }
+ }
+ },
+ title: "Root Organizations"
+};
diff --git a/modules/react-components/src/layouts/default.tsx b/modules/react-components/src/layouts/default.tsx
deleted file mode 100644
index a19ec20e595..00000000000
--- a/modules/react-components/src/layouts/default.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * Copyright (c) 2019, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
- *
- * 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 classNames from "classnames";
-import React, { FunctionComponent, PropsWithChildren, ReactElement, ReactNode } from "react";
-import { Container } from "semantic-ui-react";
-import { BaseLayout, BaseLayoutInterface } from "./base";
-
-/**
- * Default layout Prop types.
- */
-export interface DefaultLayoutPropsInterface extends BaseLayoutInterface {
- /**
- * App footer component.
- */
- footer?: ReactNode;
- /**
- * Extra CSS classes.
- */
- className?: string;
- /**
- * Is layout fluid.
- */
- fluid?: boolean;
- /**
- * App header component.
- */
- header?: ReactNode;
- /**
- * Content spacing.
- */
- desktopContentTopSpacing?: number;
- /**
- * height of the footer.
- */
- footerHeight: number;
- /**
- * Height of the header.
- */
- headerHeight: number;
-}
-
-/**
- * Default layout.
- *
- * @param props - Props injected to the component.
- * @returns Default Layout component.
- */
-export const DefaultLayout: FunctionComponent> = (
- props: PropsWithChildren
-): ReactElement => {
-
- const {
- alert,
- children,
- className,
- desktopContentTopSpacing,
- footer,
- footerHeight,
- fluid,
- header,
- headerHeight,
- topLoadingBar
- } = props;
-
- const classes = classNames(
- "layout",
- "default-layout",
- {
- [ "fluid-default-layout" ]: fluid
- },
- className
- );
-
- const mainLayoutStyles = {
- paddingBottom: `${ footerHeight }px`,
- paddingTop: `${ headerHeight }px`
- };
-
- const mainContentStyle = {
- minHeight: `calc(100vh - ${ headerHeight + footerHeight }px`,
- paddingTop: `${ desktopContentTopSpacing }px`
- };
-
- return (
-
-
- { header }
-