diff --git a/app/client/src/ce/components/WorkspaceSettingsTabs/index.tsx b/app/client/src/ce/components/WorkspaceSettingsTabs/index.tsx
new file mode 100644
index 0000000000..c8907d0105
--- /dev/null
+++ b/app/client/src/ce/components/WorkspaceSettingsTabs/index.tsx
@@ -0,0 +1,168 @@
+import React, { useCallback, useEffect, useMemo } from "react";
+import {
+ useRouteMatch,
+ Route,
+ useLocation,
+ useHistory,
+} from "react-router-dom";
+import MemberSettings from "@appsmith/pages/workspace/Members";
+import { GeneralSettings } from "pages/workspace/General";
+import { Tabs, Tab, TabsList, TabPanel } from "design-system";
+import { navigateToTab } from "@appsmith/pages/workspace/helpers";
+import styled from "styled-components";
+
+import * as Sentry from "@sentry/react";
+import { APPLICATIONS_URL } from "constants/routes/baseRoutes";
+export const SentryRoute = Sentry.withSentryRouting(Route);
+
+export const TabsWrapper = styled.div`
+ padding-top: var(--ads-v2-spaces-4);
+
+ .ads-v2-tabs {
+ height: 100%;
+ overflow: hidden;
+
+ .tab-panel {
+ height: calc(100% - 46px);
+ }
+ }
+`;
+
+interface TabProp {
+ key: string;
+ title: string;
+ count?: number;
+ panelComponent?: JSX.Element;
+}
+
+export interface WorkspaceSettingsTabsProps {
+ currentTab: string | undefined;
+ isMemberofTheWorkspace: boolean;
+ hasManageWorkspacePermissions: boolean;
+ searchValue: string;
+ setTabArrLen: (tabArrLen: number) => void;
+ workspacePermissions?: string[];
+ // EE Tab Props
+ addTabComponent?: () => TabProp;
+ eeTabRedirect?: boolean;
+}
+
+enum TABS {
+ GENERAL = "general",
+ MEMBERS = "members",
+}
+
+export const WorkspaceSettingsTabs = ({
+ addTabComponent,
+ currentTab,
+ eeTabRedirect,
+ hasManageWorkspacePermissions,
+ isMemberofTheWorkspace,
+ searchValue,
+ setTabArrLen,
+ workspacePermissions,
+}: WorkspaceSettingsTabsProps) => {
+ const { path } = useRouteMatch();
+ const location = useLocation();
+ const history = useHistory();
+
+ const shouldRedirect = useMemo(() => {
+ // If the permissions are not yet fetched, don't redirect
+ if (!workspacePermissions) {
+ return false;
+ }
+ // If user doesn't have manage workspace permissions & is on settings page, redirect to applications
+ if (currentTab === TABS.GENERAL && !hasManageWorkspacePermissions)
+ return true;
+ // If user doesn't have manage members permissions & is on members page, redirect to applications
+ if (currentTab === TABS.MEMBERS && !isMemberofTheWorkspace) return true;
+ // If the redirect flag is set to true by EE application, redirect to applications
+ if (eeTabRedirect) return true;
+ return false;
+ }, [
+ workspacePermissions,
+ isMemberofTheWorkspace,
+ hasManageWorkspacePermissions,
+ currentTab,
+ eeTabRedirect,
+ ]);
+
+ useEffect(() => {
+ if (shouldRedirect) {
+ history.replace(APPLICATIONS_URL);
+ }
+ }, [shouldRedirect]);
+
+ const GeneralSettingsComponent = (
+
+ );
+
+ const MemberSettingsComponent = (
+ (
+
+ ),
+ [location, searchValue],
+ )}
+ location={location}
+ path={`${path}/members`}
+ />
+ );
+
+ const tabArr: TabProp[] = [
+ hasManageWorkspacePermissions && {
+ key: "general",
+ title: "General Settings",
+ panelComponent: GeneralSettingsComponent,
+ },
+ isMemberofTheWorkspace && {
+ key: "members",
+ title: "Members",
+ panelComponent: MemberSettingsComponent,
+ },
+ addTabComponent && addTabComponent(),
+ ].filter(Boolean) as TabProp[];
+
+ useEffect(() => {
+ setTabArrLen(tabArr.length);
+ }, [tabArr.length, setTabArrLen]);
+
+ return (
+
+ navigateToTab(key, location, history)}
+ value={currentTab}
+ >
+
+ {tabArr.map((tab) => {
+ return (
+
+ {tab.title}
+
+ );
+ })}
+
+ {tabArr.map((tab) => {
+ return (
+
+ {tab.panelComponent}
+
+ );
+ })}
+
+
+ );
+};
diff --git a/app/client/src/ce/configs/types.ts b/app/client/src/ce/configs/types.ts
index 8c3cd53cba..50f3ff5848 100644
--- a/app/client/src/ce/configs/types.ts
+++ b/app/client/src/ce/configs/types.ts
@@ -72,11 +72,18 @@ export interface AppsmithUIConfigs {
customerPortalUrl: string;
}
+export interface DatasourceMeta {
+ configuredDatasources: number;
+ totalDatasources: number;
+}
+
// Type for one environment
export interface EnvironmentType {
id: string;
name: string;
workspaceId: string;
isDefault?: boolean;
+ isLocked: boolean; // Whether the environment is locked (disables editing and deleting of the env)
userPermissions?: string[];
+ datasourceMeta?: DatasourceMeta;
}
diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index 6f77ffceee..fb87081bca 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -508,6 +508,7 @@ export const PAGE_SERVER_UNAVAILABLE_ERROR_MESSAGES = (
export const POST = () => "Post";
export const CANCEL = () => "Cancel";
export const REMOVE = () => "Remove";
+export const CREATE = () => "Create";
// Showcase Carousel
export const NEXT = () => "NEXT";
diff --git a/app/client/src/ce/pages/Applications/ManageEnvironmentsMenu.tsx b/app/client/src/ce/pages/Applications/ManageEnvironmentsMenu.tsx
new file mode 100644
index 0000000000..92aa905e6d
--- /dev/null
+++ b/app/client/src/ce/pages/Applications/ManageEnvironmentsMenu.tsx
@@ -0,0 +1,6 @@
+export const ManageEnvironmentsMenu = ({}: {
+ workspaceId: string;
+ workspacePermissions: string[];
+}) => {
+ return null;
+};
diff --git a/app/client/src/ce/pages/Applications/WorkspaceMenu.tsx b/app/client/src/ce/pages/Applications/WorkspaceMenu.tsx
index 45962dc772..67af42bbeb 100644
--- a/app/client/src/ce/pages/Applications/WorkspaceMenu.tsx
+++ b/app/client/src/ce/pages/Applications/WorkspaceMenu.tsx
@@ -18,6 +18,7 @@ import {
DropdownOnSelectActions,
getOnSelectAction,
} from "pages/common/CustomizedDropdown/dropdownHelpers";
+import { ManageEnvironmentsMenu } from "@appsmith/pages/Applications/ManageEnvironmentsMenu";
interface WorkspaceMenuProps {
canDeleteWorkspace: boolean;
@@ -162,6 +163,10 @@ function WorkspaceMenu({
Members
)}
+
{canInviteToWorkspace && (