mirror of
https://github.com/coder/coder.git
synced 2026-09-22 13:10:21 +08:00
feat: split management settings sidebar into deployment/organization sidebars (#15388)
resolves coder/internal#199 Currently the admin settings has a combined sidebar for deployment settings and organization settings, this PR separates the sidebar in 2 sidebars for deployment and organization settings. This is preparation for the redesign work of the organization settings sidebar where a dropdown will be user to select and create new organizations. see figma: https://www.figma.com/design/OR75XeUI0Z3ksqt1mHsNQw/Dashboard-v1?node-id=684-5287&m=dev This also does some of the initial redesign work for the sidebars using Tailwind. <img width="1172" alt="Screenshot 2024-11-05 at 5 28 47 PM" src="https://github.com/user-attachments/assets/5ede14fa-1da9-4e74-a967-81f4e5772c68"> <img width="1169" alt="Screenshot 2024-11-05 at 5 28 56 PM" src="https://github.com/user-attachments/assets/459339ba-f3bf-4ef2-b86a-143c922108b7">
This commit is contained in:
@@ -4,13 +4,14 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
import type { ElementType, FC, ReactNode } from "react";
|
||||
import { Link, NavLink } from "react-router-dom";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
interface SidebarProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Sidebar: FC<SidebarProps> = ({ children }) => {
|
||||
return <nav css={styles.sidebar}>{children}</nav>;
|
||||
return <nav className="w-60 flex-shrink-0">{children}</nav>;
|
||||
};
|
||||
|
||||
interface SidebarHeaderProps {
|
||||
@@ -49,6 +50,35 @@ export const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface SettingsSidebarNavItemProps {
|
||||
children?: ReactNode;
|
||||
href: string;
|
||||
end?: boolean;
|
||||
}
|
||||
|
||||
export const SettingsSidebarNavItem: FC<SettingsSidebarNavItemProps> = ({
|
||||
children,
|
||||
href,
|
||||
end,
|
||||
}) => {
|
||||
return (
|
||||
<NavLink
|
||||
end={end}
|
||||
to={href}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
"relative text-sm text-content-secondary no-underline font-medium py-2 px-3 hover:bg-surface-secondary rounded-md transition ease-in-out duration-150 ",
|
||||
{
|
||||
"font-semibold text-content-primary": isActive,
|
||||
},
|
||||
)
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
||||
interface SidebarNavItemProps {
|
||||
children?: ReactNode;
|
||||
icon: ElementType;
|
||||
@@ -78,10 +108,6 @@ export const SidebarNavItem: FC<SidebarNavItemProps> = ({
|
||||
};
|
||||
|
||||
const styles = {
|
||||
sidebar: {
|
||||
width: 245,
|
||||
flexShrink: 0,
|
||||
},
|
||||
info: (theme) => ({
|
||||
...(theme.typography.body2 as CSSObject),
|
||||
marginBottom: 16,
|
||||
|
||||
@@ -60,7 +60,6 @@ export const ThemeProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
|
||||
if (themePreference === "auto") {
|
||||
root.classList.add(preferredColorScheme);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { RequirePermission } from "contexts/auth/RequirePermission";
|
||||
import { type FC, Suspense } from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { DeploymentSidebar } from "./DeploymentSidebar";
|
||||
|
||||
const DeploymentSettingsLayout: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
|
||||
// The deployment settings page also contains users, audit logs, and groups
|
||||
// so this page must be visible if you can see any of these.
|
||||
const canViewDeploymentSettingsPage =
|
||||
permissions.viewDeploymentValues ||
|
||||
permissions.viewAllUsers ||
|
||||
permissions.viewAnyAuditLog;
|
||||
|
||||
return (
|
||||
<RequirePermission isFeatureVisible={canViewDeploymentSettingsPage}>
|
||||
<div className="px-10 max-w-screen-2xl">
|
||||
<div className="flex flex-row gap-12 py-10">
|
||||
<DeploymentSidebar />
|
||||
<main css={{ flexGrow: 1 }}>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</RequirePermission>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeploymentSettingsLayout;
|
||||
@@ -31,12 +31,11 @@ const DeploymentSettingsProvider: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
const deploymentConfigQuery = useQuery(deploymentConfig());
|
||||
|
||||
// The deployment settings page also contains users, audit logs, groups and
|
||||
// organizations, so this page must be visible if you can see any of these.
|
||||
// The deployment settings page also contains users, audit logs, and groups
|
||||
// so this page must be visible if you can see any of these.
|
||||
const canViewDeploymentSettingsPage =
|
||||
permissions.viewDeploymentValues ||
|
||||
permissions.viewAllUsers ||
|
||||
permissions.editAnyOrganization ||
|
||||
permissions.viewAnyAuditLog;
|
||||
|
||||
// Not a huge problem to unload the content in the event of an error,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import type { FC } from "react";
|
||||
import { DeploymentSidebarView } from "./DeploymentSidebarView";
|
||||
|
||||
/**
|
||||
* A sidebar for deployment settings.
|
||||
*/
|
||||
export const DeploymentSidebar: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
|
||||
return <DeploymentSidebarView permissions={permissions} />;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { MockNoPermissions, MockPermissions } from "testHelpers/entities";
|
||||
import { withDashboardProvider } from "testHelpers/storybook";
|
||||
import { DeploymentSidebarView } from "./DeploymentSidebarView";
|
||||
|
||||
const meta: Meta<typeof DeploymentSidebarView> = {
|
||||
title: "modules/management/SidebarView",
|
||||
component: DeploymentSidebarView,
|
||||
decorators: [withDashboardProvider],
|
||||
parameters: { showOrganizations: true },
|
||||
args: {
|
||||
permissions: MockPermissions,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof DeploymentSidebarView>;
|
||||
|
||||
export const NoViewUsers: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewAllUsers: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoAuditLog: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewAnyAuditLog: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoLicenses: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewAllLicenses: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoDeploymentValues: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewDeploymentValues: false,
|
||||
editDeploymentValues: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoPermissions: Story = {
|
||||
args: {
|
||||
permissions: MockNoPermissions,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { AuthorizationResponse, Organization } from "api/typesGenerated";
|
||||
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
SettingsSidebarNavItem as SidebarNavItem,
|
||||
} from "components/Sidebar/Sidebar";
|
||||
import type { Permissions } from "contexts/auth/permissions";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import type { FC } from "react";
|
||||
|
||||
export interface OrganizationWithPermissions extends Organization {
|
||||
permissions: AuthorizationResponse;
|
||||
}
|
||||
|
||||
interface DeploymentSidebarProps {
|
||||
/** Site-wide permissions. */
|
||||
permissions: Permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A combined deployment settings and organization menu.
|
||||
*/
|
||||
export const DeploymentSidebarView: FC<DeploymentSidebarProps> = ({
|
||||
permissions,
|
||||
}) => {
|
||||
const { multiple_organizations: hasPremiumLicense } = useFeatureVisibility();
|
||||
|
||||
return (
|
||||
<BaseSidebar>
|
||||
<DeploymentSettingsNavigation
|
||||
permissions={permissions}
|
||||
isPremium={hasPremiumLicense}
|
||||
/>
|
||||
</BaseSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
interface DeploymentSettingsNavigationProps {
|
||||
/** Site-wide permissions. */
|
||||
permissions: Permissions;
|
||||
isPremium: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays navigation for deployment settings. If active, highlight the main
|
||||
* menu heading.
|
||||
*
|
||||
* Menu items are shown based on the permissions. If organizations can be
|
||||
* viewed, groups are skipped since they will show under each org instead.
|
||||
*/
|
||||
const DeploymentSettingsNavigation: FC<DeploymentSettingsNavigationProps> = ({
|
||||
permissions,
|
||||
isPremium,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavItem href="general">General</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewAllLicenses && (
|
||||
<SidebarNavItem href="licenses">Licenses</SidebarNavItem>
|
||||
)}
|
||||
{permissions.editDeploymentValues && (
|
||||
<SidebarNavItem href="appearance">Appearance</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavItem href="userauth">User Authentication</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavItem href="external-auth">
|
||||
External Authentication
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
{/* Not exposing this yet since token exchange is not finished yet.
|
||||
<SidebarNavItem href="oauth2-provider/ap>
|
||||
OAuth2 Applications
|
||||
</SidebarNavItem>*/}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavItem href="network">Network</SidebarNavItem>
|
||||
)}
|
||||
{permissions.readWorkspaceProxies && (
|
||||
<SidebarNavItem href="workspace-proxies">
|
||||
Workspace Proxies
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavItem href="security">Security</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavItem href="observability">Observability</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewAllUsers && (
|
||||
<SidebarNavItem href="users">Users</SidebarNavItem>
|
||||
)}
|
||||
{permissions.viewNotificationTemplate && (
|
||||
<SidebarNavItem href="notifications">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<span>Notifications</span>
|
||||
<FeatureStageBadge contentType="beta" size="sm" />
|
||||
</div>
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
{!isPremium && <SidebarNavItem href="premium">Premium</SidebarNavItem>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+19
-26
@@ -1,28 +1,26 @@
|
||||
import type { AuthorizationResponse, Organization } from "api/typesGenerated";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { RequirePermission } from "contexts/auth/RequirePermission";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { type FC, Suspense, createContext, useContext } from "react";
|
||||
import { Outlet, useParams } from "react-router-dom";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { OrganizationSidebar } from "./OrganizationSidebar";
|
||||
|
||||
export const ManagementSettingsContext = createContext<
|
||||
ManagementSettingsValue | undefined
|
||||
export const OrganizationSettingsContext = createContext<
|
||||
OrganizationSettingsValue | undefined
|
||||
>(undefined);
|
||||
|
||||
type ManagementSettingsValue = Readonly<{
|
||||
type OrganizationSettingsValue = Readonly<{
|
||||
organizations: readonly Organization[];
|
||||
organization?: Organization;
|
||||
}>;
|
||||
|
||||
export const useManagementSettings = (): ManagementSettingsValue => {
|
||||
const context = useContext(ManagementSettingsContext);
|
||||
export const useOrganizationSettings = (): OrganizationSettingsValue => {
|
||||
const context = useContext(OrganizationSettingsContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useManagementSettings should be used inside of ManagementSettingsLayout",
|
||||
"useOrganizationSettings should be used inside of OrganizationSettingsLayout",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,20 +41,15 @@ export const canEditOrganization = (
|
||||
);
|
||||
};
|
||||
|
||||
const ManagementSettingsLayout: FC = () => {
|
||||
const OrganizationSettingsLayout: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
const { organizations } = useDashboard();
|
||||
const { organization: orgName } = useParams() as {
|
||||
organization?: string;
|
||||
};
|
||||
|
||||
// The deployment settings page also contains users, audit logs, groups and
|
||||
// organizations, so this page must be visible if you can see any of these.
|
||||
const canViewDeploymentSettingsPage =
|
||||
permissions.viewDeploymentValues ||
|
||||
permissions.viewAllUsers ||
|
||||
permissions.editAnyOrganization ||
|
||||
permissions.viewAnyAuditLog;
|
||||
const canViewOrganizationSettingsPage =
|
||||
permissions.viewDeploymentValues || permissions.editAnyOrganization;
|
||||
|
||||
const organization =
|
||||
organizations && orgName
|
||||
@@ -64,26 +57,26 @@ const ManagementSettingsLayout: FC = () => {
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<RequirePermission isFeatureVisible={canViewDeploymentSettingsPage}>
|
||||
<ManagementSettingsContext.Provider
|
||||
<RequirePermission isFeatureVisible={canViewOrganizationSettingsPage}>
|
||||
<OrganizationSettingsContext.Provider
|
||||
value={{
|
||||
organizations,
|
||||
organization,
|
||||
}}
|
||||
>
|
||||
<Margins>
|
||||
<Stack css={{ padding: "48px 0" }} direction="row" spacing={6}>
|
||||
<Sidebar />
|
||||
<div className="px-10 max-w-screen-2xl">
|
||||
<div className="flex flex-row gap-12 py-10">
|
||||
<OrganizationSidebar />
|
||||
<main css={{ flexGrow: 1 }}>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</main>
|
||||
</Stack>
|
||||
</Margins>
|
||||
</ManagementSettingsContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
</OrganizationSettingsContext.Provider>
|
||||
</RequirePermission>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagementSettingsLayout;
|
||||
export default OrganizationSettingsLayout;
|
||||
+10
-13
@@ -1,14 +1,16 @@
|
||||
import { organizationsPermissions } from "api/queries/organizations";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import {
|
||||
canEditOrganization,
|
||||
useManagementSettings,
|
||||
} from "modules/management/ManagementSettingsLayout";
|
||||
useOrganizationSettings,
|
||||
} from "modules/management/OrganizationSettingsLayout";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import { type OrganizationWithPermissions, SidebarView } from "./SidebarView";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
OrganizationSidebarView,
|
||||
type OrganizationWithPermissions,
|
||||
} from "./OrganizationSidebarView";
|
||||
|
||||
/**
|
||||
* A combined deployment settings and organization menu.
|
||||
@@ -17,10 +19,9 @@ import { type OrganizationWithPermissions, SidebarView } from "./SidebarView";
|
||||
* disabled or not licensed, this is the wrong sidebar to use. See
|
||||
* DeploySettingsPage/Sidebar instead.
|
||||
*/
|
||||
export const Sidebar: FC = () => {
|
||||
const location = useLocation();
|
||||
export const OrganizationSidebar: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
const { organizations } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const { organization: organizationName } = useParams() as {
|
||||
organization?: string;
|
||||
};
|
||||
@@ -47,11 +48,7 @@ export const Sidebar: FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<SidebarView
|
||||
// Both activeSettings and activeOrganizationName could be be falsey if
|
||||
// the user is on /organizations but has no editable organizations to
|
||||
// which we can redirect.
|
||||
activeSettings={location.pathname.startsWith("/deployment")}
|
||||
<OrganizationSidebarView
|
||||
activeOrganizationName={organizationName}
|
||||
organizations={editableOrgs}
|
||||
permissions={permissions}
|
||||
+5
-49
@@ -6,15 +6,14 @@ import {
|
||||
MockPermissions,
|
||||
} from "testHelpers/entities";
|
||||
import { withDashboardProvider } from "testHelpers/storybook";
|
||||
import { SidebarView } from "./SidebarView";
|
||||
import { OrganizationSidebarView } from "./OrganizationSidebarView";
|
||||
|
||||
const meta: Meta<typeof SidebarView> = {
|
||||
title: "modules/management/SidebarView",
|
||||
component: SidebarView,
|
||||
const meta: Meta<typeof OrganizationSidebarView> = {
|
||||
title: "modules/management/OrganizationSidebarView",
|
||||
component: OrganizationSidebarView,
|
||||
decorators: [withDashboardProvider],
|
||||
parameters: { showOrganizations: true },
|
||||
args: {
|
||||
activeSettings: true,
|
||||
activeOrganizationName: undefined,
|
||||
organizations: [
|
||||
{
|
||||
@@ -41,7 +40,7 @@ const meta: Meta<typeof SidebarView> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SidebarView>;
|
||||
type Story = StoryObj<typeof OrganizationSidebarView>;
|
||||
|
||||
export const LoadingOrganizations: Story = {
|
||||
args: {
|
||||
@@ -58,55 +57,12 @@ export const NoCreateOrg: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const NoViewUsers: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewAllUsers: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoAuditLog: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewAnyAuditLog: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoLicenses: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewAllLicenses: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoDeploymentValues: Story = {
|
||||
args: {
|
||||
permissions: {
|
||||
...MockPermissions,
|
||||
viewDeploymentValues: false,
|
||||
editDeploymentValues: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const NoPermissions: Story = {
|
||||
args: {
|
||||
permissions: MockNoPermissions,
|
||||
},
|
||||
};
|
||||
|
||||
export const NoSelected: Story = {
|
||||
args: {
|
||||
activeSettings: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const SelectedOrgNoMatch: Story = {
|
||||
args: {
|
||||
activeOrganizationName: MockOrganization.name,
|
||||
+8
-199
@@ -1,17 +1,16 @@
|
||||
import { cx } from "@emotion/css";
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import type { AuthorizationResponse, Organization } from "api/typesGenerated";
|
||||
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Sidebar as BaseSidebar } from "components/Sidebar/Sidebar";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
SettingsSidebarNavItem as SidebarNavSubItem,
|
||||
} from "components/Sidebar/Sidebar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import type { Permissions } from "contexts/auth/permissions";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { Link, NavLink } from "react-router-dom";
|
||||
|
||||
@@ -20,8 +19,6 @@ export interface OrganizationWithPermissions extends Organization {
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
/** True if a settings page is being viewed. */
|
||||
activeSettings: boolean;
|
||||
/** The active org name, if any. Overrides activeSettings. */
|
||||
activeOrganizationName: string | undefined;
|
||||
/** Organizations and their permissions or undefined if still fetching. */
|
||||
@@ -31,31 +28,17 @@ interface SidebarProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* A combined deployment settings and organization menu.
|
||||
* Organization settings left sidebar menu.
|
||||
*/
|
||||
export const SidebarView: FC<SidebarProps> = ({
|
||||
activeSettings,
|
||||
export const OrganizationSidebarView: FC<SidebarProps> = ({
|
||||
activeOrganizationName,
|
||||
organizations,
|
||||
permissions,
|
||||
}) => {
|
||||
const { showOrganizations } = useDashboard();
|
||||
const { multiple_organizations: hasPremiumLicense } = useFeatureVisibility();
|
||||
|
||||
// TODO: Do something nice to scroll to the active org.
|
||||
return (
|
||||
<BaseSidebar>
|
||||
{showOrganizations && (
|
||||
<header>
|
||||
<h2 css={styles.sidebarHeader}>Deployment</h2>
|
||||
</header>
|
||||
)}
|
||||
|
||||
<DeploymentSettingsNavigation
|
||||
active={!activeOrganizationName && activeSettings}
|
||||
permissions={permissions}
|
||||
isPremium={hasPremiumLicense}
|
||||
/>
|
||||
{showOrganizations && (
|
||||
<OrganizationsSettingsNavigation
|
||||
activeOrganizationName={activeOrganizationName}
|
||||
@@ -67,108 +50,6 @@ export const SidebarView: FC<SidebarProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface DeploymentSettingsNavigationProps {
|
||||
/** Whether a deployment setting page is being viewed. */
|
||||
active: boolean;
|
||||
/** Site-wide permissions. */
|
||||
permissions: Permissions;
|
||||
isPremium: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays navigation for deployment settings. If active, highlight the main
|
||||
* menu heading.
|
||||
*
|
||||
* Menu items are shown based on the permissions. If organizations can be
|
||||
* viewed, groups are skipped since they will show under each org instead.
|
||||
*/
|
||||
const DeploymentSettingsNavigation: FC<DeploymentSettingsNavigationProps> = ({
|
||||
active,
|
||||
permissions,
|
||||
isPremium,
|
||||
}) => {
|
||||
return (
|
||||
<div css={{ paddingBottom: 12 }}>
|
||||
<SidebarNavItem
|
||||
active={active}
|
||||
href={
|
||||
permissions.viewDeploymentValues
|
||||
? "/deployment/general"
|
||||
: "/deployment/workspace-proxies"
|
||||
}
|
||||
// 24px matches the width of the organization icons, and the component
|
||||
// is smart enough to keep the icon itself square. It looks too big if
|
||||
// it's 24x24.
|
||||
icon={<SettingsIcon css={{ width: 24, height: 20 }} />}
|
||||
>
|
||||
Deployment
|
||||
</SidebarNavItem>
|
||||
{active && (
|
||||
<Stack spacing={0.5} css={{ marginBottom: 8, marginTop: 8 }}>
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavSubItem href="general">General</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewAllLicenses && (
|
||||
<SidebarNavSubItem href="licenses">Licenses</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.editDeploymentValues && (
|
||||
<SidebarNavSubItem href="appearance">Appearance</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavSubItem href="userauth">
|
||||
User Authentication
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavSubItem href="external-auth">
|
||||
External Authentication
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{/* Not exposing this yet since token exchange is not finished yet.
|
||||
<SidebarNavSubItem href="oauth2-provider/ap>
|
||||
OAuth2 Applications
|
||||
</SidebarNavSubItem>*/}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavSubItem href="network">Network</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.readWorkspaceProxies && (
|
||||
<SidebarNavSubItem href="workspace-proxies">
|
||||
Workspace Proxies
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavSubItem href="security">Security</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewDeploymentValues && (
|
||||
<SidebarNavSubItem href="observability">
|
||||
Observability
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewAllUsers && (
|
||||
<SidebarNavSubItem href="users">Users</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewNotificationTemplate && (
|
||||
<SidebarNavSubItem href="notifications">
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<span>Notifications</span>
|
||||
<FeatureStageBadge contentType="beta" size="sm" />
|
||||
</Stack>
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{permissions.viewOrganizationIDPSyncSettings && (
|
||||
<SidebarNavSubItem href="idp-org-sync">
|
||||
IdP Organization Sync
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{!isPremium && (
|
||||
<SidebarNavSubItem href="premium">Premium</SidebarNavSubItem>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function urlForSubpage(organizationName: string, subpage = ""): string {
|
||||
return `/organizations/${organizationName}/${subpage}`;
|
||||
}
|
||||
@@ -204,18 +85,6 @@ const OrganizationsSettingsNavigation: FC<
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
css={{
|
||||
display: "flex",
|
||||
flexFlow: "row wrap",
|
||||
columnGap: "8px",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<h2 css={styles.sidebarHeader}>Organizations</h2>
|
||||
<FeatureStageBadge contentType="beta" size="sm" />
|
||||
</header>
|
||||
|
||||
{permissions.createOrganization && (
|
||||
<SidebarNavItem
|
||||
active="auto"
|
||||
@@ -270,7 +139,7 @@ const OrganizationSettingsNavigation: FC<
|
||||
{organization.display_name}
|
||||
</SidebarNavItem>
|
||||
{active && (
|
||||
<Stack spacing={0.5} css={{ marginBottom: 8, marginTop: 8 }}>
|
||||
<div className="flex flex-col gap-1 my-2 ml-11">
|
||||
{organization.permissions.editOrganization && (
|
||||
<SidebarNavSubItem end href={urlForSubpage(organization.name)}>
|
||||
Settings
|
||||
@@ -309,7 +178,7 @@ const OrganizationSettingsNavigation: FC<
|
||||
IdP Sync
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -356,42 +225,6 @@ const SidebarNavItem: FC<SidebarNavItemProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface SidebarNavSubItemProps {
|
||||
children?: ReactNode;
|
||||
href: string;
|
||||
end?: boolean;
|
||||
}
|
||||
|
||||
const SidebarNavSubItem: FC<SidebarNavSubItemProps> = ({
|
||||
children,
|
||||
href,
|
||||
end,
|
||||
}) => {
|
||||
const link = useClassName(classNames.subLink, []);
|
||||
const activeLink = useClassName(classNames.activeSubLink, []);
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
end={end}
|
||||
to={href}
|
||||
className={({ isActive }) => cx([link, isActive && activeLink])}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
sidebarHeader: {
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.1em",
|
||||
margin: 0,
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
const classNames = {
|
||||
link: (css, theme) => css`
|
||||
color: inherit;
|
||||
@@ -414,29 +247,5 @@ const classNames = {
|
||||
border-left-color: ${theme.palette.primary.main};
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
`,
|
||||
|
||||
subLink: (css, theme) => css`
|
||||
color: ${theme.palette.text.secondary};
|
||||
text-decoration: none;
|
||||
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
margin-left: 44px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
margin-bottom: 1px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
color: ${theme.palette.text.primary};
|
||||
background-color: ${theme.palette.action.hover};
|
||||
}
|
||||
`,
|
||||
|
||||
activeSubLink: (css, theme) => css`
|
||||
color: ${theme.palette.text.primary};
|
||||
font-weight: 600;
|
||||
`,
|
||||
} satisfies Record<string, ClassName>;
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import type { CustomRoleRequest } from "api/typesGenerated";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import type { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
@@ -24,7 +24,7 @@ export const CreateEditRolePage: FC = () => {
|
||||
organization: string;
|
||||
roleName: string;
|
||||
};
|
||||
const { organizations } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const organization = organizations?.find((o) => o.name === organizationName);
|
||||
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
|
||||
const createOrganizationRoleMutation = useMutation(
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
@@ -22,7 +22,7 @@ export const CustomRolesPage: FC = () => {
|
||||
const { organization: organizationName } = useParams() as {
|
||||
organization: string;
|
||||
};
|
||||
const { organizations } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const organization = organizations?.find((o) => o.name === organizationName);
|
||||
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
|
||||
const deleteRoleMutation = useMutation(
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import { type FC, useEffect } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useQuery } from "react-query";
|
||||
@@ -24,7 +24,7 @@ export const GroupsPage: FC = () => {
|
||||
organization: string;
|
||||
};
|
||||
const groupsQuery = useQuery(groupsByOrganization(organizationName));
|
||||
const { organizations } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const organization = organizations?.find((o) => o.name === organizationName);
|
||||
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Paywall } from "components/Paywall/Paywall";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import type { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useQueries } from "react-query";
|
||||
@@ -27,7 +27,7 @@ export const IdpSyncPage: FC = () => {
|
||||
};
|
||||
// IdP sync does not have its own entitlement and is based on templace_rbac
|
||||
const { template_rbac: isIdpSyncEnabled } = useFeatureVisibility();
|
||||
const { organizations } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const organization = organizations?.find((o) => o.name === organizationName);
|
||||
|
||||
const [groupIdpSyncSettingsQuery, roleIdpSyncSettingsQuery, groupsQuery] =
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
MockUser,
|
||||
} from "testHelpers/entities";
|
||||
import {
|
||||
renderWithManagementSettingsLayout,
|
||||
renderWithOrganizationSettingsLayout,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "testHelpers/renderHelpers";
|
||||
import { server } from "testHelpers/server";
|
||||
@@ -32,7 +32,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
const renderPage = async () => {
|
||||
renderWithManagementSettingsLayout(<OrganizationMembersPage />, {
|
||||
renderWithOrganizationSettingsLayout(<OrganizationMembersPage />, {
|
||||
route: `/organizations/${MockOrganization.name}/members`,
|
||||
path: "/organizations/:organization/members",
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import { type FC, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
@@ -52,7 +52,8 @@ const OrganizationMembersPage: FC = () => {
|
||||
updateOrganizationMemberRoles(queryClient, organizationName),
|
||||
);
|
||||
|
||||
const { organization } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const organization = organizations?.find((o) => o.name === organizationName);
|
||||
const permissionsQuery = useQuery(organizationPermissions(organization?.id));
|
||||
|
||||
const [memberToDelete, setMemberToDelete] =
|
||||
|
||||
@@ -3,7 +3,7 @@ import { provisionerDaemonGroups } from "api/queries/organizations";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { useManagementSettings } from "modules/management/ManagementSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import type { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useQuery } from "react-query";
|
||||
@@ -15,7 +15,7 @@ const OrganizationProvisionersPage: FC = () => {
|
||||
const { organization: organizationName } = useParams() as {
|
||||
organization: string;
|
||||
};
|
||||
const { organization } = useManagementSettings();
|
||||
const { organization } = useOrganizationSettings();
|
||||
const { entitlements } = useDashboard();
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
MockOrganization2,
|
||||
} from "testHelpers/entities";
|
||||
import {
|
||||
renderWithManagementSettingsLayout,
|
||||
renderWithOrganizationSettingsLayout,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "testHelpers/renderHelpers";
|
||||
import { server } from "testHelpers/server";
|
||||
@@ -15,7 +15,7 @@ import OrganizationSettingsPage from "./OrganizationSettingsPage";
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
const renderPage = async () => {
|
||||
renderWithManagementSettingsLayout(<OrganizationSettingsPage />, {
|
||||
renderWithOrganizationSettingsLayout(<OrganizationSettingsPage />, {
|
||||
route: "/organizations",
|
||||
path: "/organizations/:organization?",
|
||||
});
|
||||
|
||||
@@ -3,16 +3,13 @@ import {
|
||||
organizationsPermissions,
|
||||
updateOrganization,
|
||||
} from "api/queries/organizations";
|
||||
import type { Organization } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
|
||||
import {
|
||||
canEditOrganization,
|
||||
useManagementSettings,
|
||||
} from "modules/management/ManagementSettingsLayout";
|
||||
import { canEditOrganization } from "modules/management/OrganizationSettingsLayout";
|
||||
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
|
||||
import type { FC } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { Navigate, useNavigate, useParams } from "react-router-dom";
|
||||
@@ -23,7 +20,7 @@ const OrganizationSettingsPage: FC = () => {
|
||||
const { organization: organizationName } = useParams() as {
|
||||
organization?: string;
|
||||
};
|
||||
const { organizations } = useManagementSettings();
|
||||
const { organizations } = useOrganizationSettings();
|
||||
const feats = useFeatureVisibility();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
+7
-4
@@ -27,12 +27,15 @@ import WorkspacesPage from "./pages/WorkspacesPage/WorkspacesPage";
|
||||
// - Pages that are secondary, not in the main navigation or not usually accessed
|
||||
// - Pages that use heavy dependencies like charts or time libraries
|
||||
const NotFoundPage = lazy(() => import("./pages/404Page/404Page"));
|
||||
const ManagementSettingsLayout = lazy(
|
||||
() => import("./modules/management/ManagementSettingsLayout"),
|
||||
const DeploymentSettingsLayout = lazy(
|
||||
() => import("./modules/management/DeploymentSettingsLayout"),
|
||||
);
|
||||
const DeploymentSettingsProvider = lazy(
|
||||
() => import("./modules/management/DeploymentSettingsProvider"),
|
||||
);
|
||||
const OrganizationSettingsLayout = lazy(
|
||||
() => import("./modules/management/OrganizationSettingsLayout"),
|
||||
);
|
||||
const CliAuthenticationPage = lazy(
|
||||
() => import("./pages/CliAuthPage/CliAuthPage"),
|
||||
);
|
||||
@@ -414,7 +417,7 @@ export const router = createBrowserRouter(
|
||||
|
||||
<Route path="/audit" element={<AuditPage />} />
|
||||
|
||||
<Route path="/organizations" element={<ManagementSettingsLayout />}>
|
||||
<Route path="/organizations" element={<OrganizationSettingsLayout />}>
|
||||
<Route path="new" element={<CreateOrganizationPage />} />
|
||||
|
||||
{/* General settings for the default org can omit the organization name */}
|
||||
@@ -437,7 +440,7 @@ export const router = createBrowserRouter(
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
<Route path="/deployment" element={<ManagementSettingsLayout />}>
|
||||
<Route path="/deployment" element={<DeploymentSettingsLayout />}>
|
||||
<Route element={<DeploymentSettingsProvider />}>
|
||||
<Route path="general" element={<GeneralSettingsPage />} />
|
||||
<Route path="security" element={<SecuritySettingsPage />} />
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ThemeProvider } from "contexts/ThemeProvider";
|
||||
import { RequireAuth } from "contexts/auth/RequireAuth";
|
||||
import { DashboardLayout } from "modules/dashboard/DashboardLayout";
|
||||
import type { DashboardProvider } from "modules/dashboard/DashboardProvider";
|
||||
import ManagementSettingsLayout from "modules/management/ManagementSettingsLayout";
|
||||
import OrganizationSettingsLayout from "modules/management/OrganizationSettingsLayout";
|
||||
import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout";
|
||||
import { WorkspaceSettingsLayout } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout";
|
||||
import type { ReactNode } from "react";
|
||||
@@ -195,7 +195,7 @@ export function renderWithWorkspaceSettingsLayout(
|
||||
};
|
||||
}
|
||||
|
||||
export function renderWithManagementSettingsLayout(
|
||||
export function renderWithOrganizationSettingsLayout(
|
||||
element: JSX.Element,
|
||||
{
|
||||
path = "/",
|
||||
@@ -212,7 +212,7 @@ export function renderWithManagementSettingsLayout(
|
||||
element: <DashboardLayout />,
|
||||
children: [
|
||||
{
|
||||
element: <ManagementSettingsLayout />,
|
||||
element: <OrganizationSettingsLayout />,
|
||||
children: [{ element, path }, ...extraRoutes],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AuthProvider } from "contexts/auth/AuthProvider";
|
||||
import { permissionsToCheck } from "contexts/auth/permissions";
|
||||
import { DashboardContext } from "modules/dashboard/DashboardProvider";
|
||||
import { DeploymentSettingsContext } from "modules/management/DeploymentSettingsProvider";
|
||||
import { ManagementSettingsContext } from "modules/management/ManagementSettingsLayout";
|
||||
import { OrganizationSettingsContext } from "modules/management/OrganizationSettingsLayout";
|
||||
import type { FC } from "react";
|
||||
import { useQueryClient } from "react-query";
|
||||
import {
|
||||
@@ -155,7 +155,7 @@ export const withGlobalSnackbar = (Story: FC) => (
|
||||
|
||||
export const withManagementSettingsProvider = (Story: FC) => {
|
||||
return (
|
||||
<ManagementSettingsContext.Provider
|
||||
<OrganizationSettingsContext.Provider
|
||||
value={{
|
||||
organizations: [MockDefaultOrganization],
|
||||
organization: MockDefaultOrganization,
|
||||
@@ -166,6 +166,6 @@ export const withManagementSettingsProvider = (Story: FC) => {
|
||||
>
|
||||
<Story />
|
||||
</DeploymentSettingsContext.Provider>
|
||||
</ManagementSettingsContext.Provider>
|
||||
</OrganizationSettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user