diff --git a/site/src/components/Sidebar/Sidebar.tsx b/site/src/components/Sidebar/Sidebar.tsx index 2ff051ee17080..b719bb28e83de 100644 --- a/site/src/components/Sidebar/Sidebar.tsx +++ b/site/src/components/Sidebar/Sidebar.tsx @@ -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 = ({ children }) => { - return ; + return ; }; interface SidebarHeaderProps { @@ -49,6 +50,35 @@ export const SidebarHeader: FC = ({ ); }; +interface SettingsSidebarNavItemProps { + children?: ReactNode; + href: string; + end?: boolean; +} + +export const SettingsSidebarNavItem: FC = ({ + children, + href, + end, +}) => { + return ( + + 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} + + ); +}; + interface SidebarNavItemProps { children?: ReactNode; icon: ElementType; @@ -78,10 +108,6 @@ export const SidebarNavItem: FC = ({ }; const styles = { - sidebar: { - width: 245, - flexShrink: 0, - }, info: (theme) => ({ ...(theme.typography.body2 as CSSObject), marginBottom: 16, diff --git a/site/src/contexts/ThemeProvider.tsx b/site/src/contexts/ThemeProvider.tsx index fd953d17e23f0..8367e96e3cc64 100644 --- a/site/src/contexts/ThemeProvider.tsx +++ b/site/src/contexts/ThemeProvider.tsx @@ -60,7 +60,6 @@ export const ThemeProvider: FC = ({ children }) => { useEffect(() => { const root = document.documentElement; - if (themePreference === "auto") { root.classList.add(preferredColorScheme); } else { diff --git a/site/src/modules/management/DeploymentSettingsLayout.tsx b/site/src/modules/management/DeploymentSettingsLayout.tsx new file mode 100644 index 0000000000000..f2988e5824698 --- /dev/null +++ b/site/src/modules/management/DeploymentSettingsLayout.tsx @@ -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 ( + +
+
+ +
+ }> + + +
+
+
+
+ ); +}; + +export default DeploymentSettingsLayout; diff --git a/site/src/modules/management/DeploymentSettingsProvider.tsx b/site/src/modules/management/DeploymentSettingsProvider.tsx index c9f6cd5f4a8ce..633c67d67fe44 100644 --- a/site/src/modules/management/DeploymentSettingsProvider.tsx +++ b/site/src/modules/management/DeploymentSettingsProvider.tsx @@ -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, diff --git a/site/src/modules/management/DeploymentSidebar.tsx b/site/src/modules/management/DeploymentSidebar.tsx new file mode 100644 index 0000000000000..1153ab226bda2 --- /dev/null +++ b/site/src/modules/management/DeploymentSidebar.tsx @@ -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 ; +}; diff --git a/site/src/modules/management/DeploymentSidebarView.stories.tsx b/site/src/modules/management/DeploymentSidebarView.stories.tsx new file mode 100644 index 0000000000000..443a5cfd417ce --- /dev/null +++ b/site/src/modules/management/DeploymentSidebarView.stories.tsx @@ -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 = { + title: "modules/management/SidebarView", + component: DeploymentSidebarView, + decorators: [withDashboardProvider], + parameters: { showOrganizations: true }, + args: { + permissions: MockPermissions, + }, +}; + +export default meta; +type Story = StoryObj; + +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, + }, +}; diff --git a/site/src/modules/management/DeploymentSidebarView.tsx b/site/src/modules/management/DeploymentSidebarView.tsx new file mode 100644 index 0000000000000..ecd8f2e774fcd --- /dev/null +++ b/site/src/modules/management/DeploymentSidebarView.tsx @@ -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 = ({ + permissions, +}) => { + const { multiple_organizations: hasPremiumLicense } = useFeatureVisibility(); + + return ( + + + + ); +}; + +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 = ({ + permissions, + isPremium, +}) => { + return ( +
+
+ {permissions.viewDeploymentValues && ( + General + )} + {permissions.viewAllLicenses && ( + Licenses + )} + {permissions.editDeploymentValues && ( + Appearance + )} + {permissions.viewDeploymentValues && ( + User Authentication + )} + {permissions.viewDeploymentValues && ( + + External Authentication + + )} + {/* Not exposing this yet since token exchange is not finished yet. + Network + )} + {permissions.readWorkspaceProxies && ( + + Workspace Proxies + + )} + {permissions.viewDeploymentValues && ( + Security + )} + {permissions.viewDeploymentValues && ( + Observability + )} + {permissions.viewAllUsers && ( + Users + )} + {permissions.viewNotificationTemplate && ( + +
+ Notifications + +
+
+ )} + {!isPremium && Premium} +
+
+ ); +}; diff --git a/site/src/modules/management/ManagementSettingsLayout.tsx b/site/src/modules/management/OrganizationSettingsLayout.tsx similarity index 55% rename from site/src/modules/management/ManagementSettingsLayout.tsx rename to site/src/modules/management/OrganizationSettingsLayout.tsx index 0cb313f0e53b9..ed987910edafd 100644 --- a/site/src/modules/management/ManagementSettingsLayout.tsx +++ b/site/src/modules/management/OrganizationSettingsLayout.tsx @@ -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 ( - - + - - - +
+
+
}>
- - - +
+
+
); }; -export default ManagementSettingsLayout; +export default OrganizationSettingsLayout; diff --git a/site/src/modules/management/Sidebar.tsx b/site/src/modules/management/OrganizationSidebar.tsx similarity index 69% rename from site/src/modules/management/Sidebar.tsx rename to site/src/modules/management/OrganizationSidebar.tsx index a2560fe5d6515..902085052e289 100644 --- a/site/src/modules/management/Sidebar.tsx +++ b/site/src/modules/management/OrganizationSidebar.tsx @@ -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 ( - = { - title: "modules/management/SidebarView", - component: SidebarView, +const meta: Meta = { + title: "modules/management/OrganizationSidebarView", + component: OrganizationSidebarView, decorators: [withDashboardProvider], parameters: { showOrganizations: true }, args: { - activeSettings: true, activeOrganizationName: undefined, organizations: [ { @@ -41,7 +40,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; 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, diff --git a/site/src/modules/management/SidebarView.tsx b/site/src/modules/management/OrganizationSidebarView.tsx similarity index 52% rename from site/src/modules/management/SidebarView.tsx rename to site/src/modules/management/OrganizationSidebarView.tsx index d63dcca388b8e..f662fa9afceb4 100644 --- a/site/src/modules/management/SidebarView.tsx +++ b/site/src/modules/management/OrganizationSidebarView.tsx @@ -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 = ({ - activeSettings, +export const OrganizationSidebarView: FC = ({ activeOrganizationName, organizations, permissions, }) => { const { showOrganizations } = useDashboard(); - const { multiple_organizations: hasPremiumLicense } = useFeatureVisibility(); - // TODO: Do something nice to scroll to the active org. return ( - {showOrganizations && ( -
-

Deployment

-
- )} - - {showOrganizations && ( = ({ ); }; -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 = ({ - active, - permissions, - isPremium, -}) => { - return ( -
- } - > - Deployment - - {active && ( - - {permissions.viewDeploymentValues && ( - General - )} - {permissions.viewAllLicenses && ( - Licenses - )} - {permissions.editDeploymentValues && ( - Appearance - )} - {permissions.viewDeploymentValues && ( - - User Authentication - - )} - {permissions.viewDeploymentValues && ( - - External Authentication - - )} - {/* Not exposing this yet since token exchange is not finished yet. - Network - )} - {permissions.readWorkspaceProxies && ( - - Workspace Proxies - - )} - {permissions.viewDeploymentValues && ( - Security - )} - {permissions.viewDeploymentValues && ( - - Observability - - )} - {permissions.viewAllUsers && ( - Users - )} - {permissions.viewNotificationTemplate && ( - - - Notifications - - - - )} - {permissions.viewOrganizationIDPSyncSettings && ( - - IdP Organization Sync - - )} - {!isPremium && ( - Premium - )} - - )} -
- ); -}; - function urlForSubpage(organizationName: string, subpage = ""): string { return `/organizations/${organizationName}/${subpage}`; } @@ -204,18 +85,6 @@ const OrganizationsSettingsNavigation: FC< return ( <> -
-

Organizations

- -
- {permissions.createOrganization && ( {active && ( - +
{organization.permissions.editOrganization && ( Settings @@ -309,7 +178,7 @@ const OrganizationSettingsNavigation: FC< IdP Sync )} - +
)} ); @@ -356,42 +225,6 @@ const SidebarNavItem: FC = ({ ); }; -interface SidebarNavSubItemProps { - children?: ReactNode; - href: string; - end?: boolean; -} - -const SidebarNavSubItem: FC = ({ - children, - href, - end, -}) => { - const link = useClassName(classNames.subLink, []); - const activeLink = useClassName(classNames.activeSubLink, []); - - return ( - cx([link, isActive && activeLink])} - > - {children} - - ); -}; - -const styles = { - sidebarHeader: { - textTransform: "uppercase", - letterSpacing: "0.1em", - margin: 0, - fontSize: 11, - fontWeight: 500, - paddingBottom: 4, - }, -} satisfies Record>; - 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; diff --git a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePage.tsx b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePage.tsx index e770a400af2a7..9bb27679689fa 100644 --- a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePage.tsx +++ b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePage.tsx @@ -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( diff --git a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage.tsx b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage.tsx index 34c33083a76be..905e67ebd26e3 100644 --- a/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage.tsx +++ b/site/src/pages/ManagementSettingsPage/CustomRolesPage/CustomRolesPage.tsx @@ -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( diff --git a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx index 5f9df03f825e0..0e31af80e359a 100644 --- a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPage.tsx @@ -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)); diff --git a/site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPage.tsx b/site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPage.tsx index ef432e8b0d6d6..fd1375405fda4 100644 --- a/site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPage.tsx +++ b/site/src/pages/ManagementSettingsPage/IdpSyncPage/IdpSyncPage.tsx @@ -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] = diff --git a/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.test.tsx b/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.test.tsx index e029f9ed35359..0c9c7d44bd15a 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.test.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.test.tsx @@ -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(, { + renderWithOrganizationSettingsLayout(, { route: `/organizations/${MockOrganization.name}/members`, path: "/organizations/:organization/members", }); diff --git a/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.tsx index 067fb9fdfe3f8..ac90365ea4d43 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationMembersPage.tsx @@ -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] = diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx index 5c288ec1aabe1..8d97a49e78bd1 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx @@ -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"])); diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx index b8e720fc5cd09..2978702ab9651 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx @@ -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(, { + renderWithOrganizationSettingsLayout(, { route: "/organizations", path: "/organizations/:organization?", }); diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx index 2b4eb18a9a524..9b80db4503f44 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx @@ -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(); diff --git a/site/src/router.tsx b/site/src/router.tsx index 0c9ffb38e411b..907c550757fc5 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -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( } /> - }> + }> } /> {/* General settings for the default org can omit the organization name */} @@ -437,7 +440,7 @@ export const router = createBrowserRouter( - }> + }> }> } /> } /> diff --git a/site/src/testHelpers/renderHelpers.tsx b/site/src/testHelpers/renderHelpers.tsx index 46ae893927801..330919c7ef7f6 100644 --- a/site/src/testHelpers/renderHelpers.tsx +++ b/site/src/testHelpers/renderHelpers.tsx @@ -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: , children: [ { - element: , + element: , children: [{ element, path }, ...extraRoutes], }, ], diff --git a/site/src/testHelpers/storybook.tsx b/site/src/testHelpers/storybook.tsx index 514d34e0265e8..f1bdc8fadd0f0 100644 --- a/site/src/testHelpers/storybook.tsx +++ b/site/src/testHelpers/storybook.tsx @@ -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 ( - { > - + ); };