diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.stories.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.stories.tsx new file mode 100644 index 0000000000000..60932393a7260 --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.stories.tsx @@ -0,0 +1,63 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { reactRouterParameters } from "storybook-addon-remix-react-router"; +import { MockDefaultOrganization, MockUser } from "testHelpers/entities"; +import { withAuthProvider, withDashboardProvider } from "testHelpers/storybook"; +import OrganizationSettingsPage from "./OrganizationSettingsPage"; + +const meta: Meta = { + title: "pages/OrganizationSettingsPage", + component: OrganizationSettingsPage, + decorators: [withAuthProvider, withDashboardProvider], + parameters: { + user: MockUser, + permissions: { viewDeploymentValues: true }, + queries: [ + { + key: ["organizations", [MockDefaultOrganization.id], "permissions"], + data: {}, + }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const NoRedirectableOrganizations: Story = {}; + +export const OrganizationDoesNotExist: Story = { + parameters: { + reactRouter: reactRouterParameters({ + location: { pathParams: { organization: "does-not-exist" } }, + routing: { path: "/organizations/:organization" }, + }), + }, +}; + +export const CannotEditOrganization: Story = { + parameters: { + reactRouter: reactRouterParameters({ + location: { pathParams: { organization: MockDefaultOrganization.name } }, + routing: { path: "/organizations/:organization" }, + }), + }, +}; + +export const CanEditOrganization: Story = { + parameters: { + reactRouter: reactRouterParameters({ + location: { pathParams: { organization: MockDefaultOrganization.name } }, + routing: { path: "/organizations/:organization" }, + }), + queries: [ + { + key: ["organizations", [MockDefaultOrganization.id], "permissions"], + data: { + [MockDefaultOrganization.id]: { + editOrganization: true, + }, + }, + }, + ], + }, +}; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx index e6107629920a4..8bf86e8ee6387 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.test.tsx @@ -13,7 +13,7 @@ import OrganizationSettingsPage from "./OrganizationSettingsPage"; jest.spyOn(console, "error").mockImplementation(() => {}); -const renderRootPage = async () => { +const renderPage = async () => { renderWithManagementSettingsLayout(, { route: "/organizations", path: "/organizations/:organization?", @@ -21,31 +21,7 @@ const renderRootPage = async () => { await waitForLoaderToBeRemoved(); }; -const renderPage = async (orgName: string) => { - renderWithManagementSettingsLayout(, { - route: `/organizations/${orgName}`, - path: "/organizations/:organization", - }); - await waitForLoaderToBeRemoved(); -}; - describe("OrganizationSettingsPage", () => { - it("has no organizations", async () => { - server.use( - http.get("/api/v2/organizations", () => { - return HttpResponse.json([]); - }), - http.post("/api/v2/authcheck", async () => { - return HttpResponse.json({ - [`${MockDefaultOrganization.id}.editOrganization`]: true, - viewDeploymentValues: true, - }); - }), - ); - await renderRootPage(); - await screen.findByText("No organizations found"); - }); - it("has no editable organizations", async () => { server.use( http.get("/api/v2/organizations", () => { @@ -57,7 +33,7 @@ describe("OrganizationSettingsPage", () => { }); }), ); - await renderRootPage(); + await renderPage(); await screen.findByText("No organizations found"); }); @@ -75,7 +51,7 @@ describe("OrganizationSettingsPage", () => { }); }), ); - await renderRootPage(); + await renderPage(); const form = screen.getByTestId("org-settings-form"); expect(within(form).getByRole("textbox", { name: "Name" })).toHaveValue( MockDefaultOrganization.name, @@ -94,26 +70,10 @@ describe("OrganizationSettingsPage", () => { }); }), ); - await renderRootPage(); + await renderPage(); const form = screen.getByTestId("org-settings-form"); expect(within(form).getByRole("textbox", { name: "Name" })).toHaveValue( MockOrganization2.name, ); }); - - it("cannot find organization", async () => { - server.use( - http.get("/api/v2/organizations", () => { - return HttpResponse.json([MockDefaultOrganization, MockOrganization2]); - }), - http.post("/api/v2/authcheck", async () => { - return HttpResponse.json({ - [`${MockOrganization2.id}.editOrganization`]: true, - viewDeploymentValues: true, - }); - }), - ); - await renderPage("the-endless-void"); - await screen.findByText("Organization not found"); - }); }); diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx index 0b04b3848ed92..77246d2805295 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx @@ -15,6 +15,7 @@ import { useOrganizationSettings, } from "./ManagementSettingsLayout"; import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView"; +import { OrganizationSummaryPageView } from "./OrganizationSummaryPageView"; const OrganizationSettingsPage: FC = () => { const { organization: organizationName } = useParams() as { @@ -65,12 +66,18 @@ const OrganizationSettingsPage: FC = () => { return ; } + // The user may not be able to edit this org but they can still see it because + // they can edit members, etc. In this case they will be shown a read-only + // summary page instead of the settings form. + if (!permissions[organization.id]?.editOrganization) { + return ; + } + const error = updateOrganizationMutation.error ?? deleteOrganizationMutation.error; return ( { diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.stories.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.stories.tsx index 0b01e97b67a8e..37ce1185d7dba 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.stories.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.stories.tsx @@ -10,7 +10,6 @@ const meta: Meta = { component: OrganizationSettingsPageView, args: { organization: MockOrganization, - canEdit: true, }, }; @@ -24,9 +23,3 @@ export const DefaultOrg: Story = { organization: MockDefaultOrganization, }, }; - -export const CannotEdit: Story = { - args: { - canEdit: false, - }, -}; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.tsx index 8be81243a396d..538387bcfef37 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPageView.tsx @@ -44,12 +44,11 @@ interface OrganizationSettingsPageViewProps { error: unknown; onSubmit: (values: UpdateOrganizationRequest) => Promise; onDeleteOrganization: () => void; - canEdit: boolean; } export const OrganizationSettingsPageView: FC< OrganizationSettingsPageViewProps -> = ({ organization, error, onSubmit, onDeleteOrganization, canEdit }) => { +> = ({ organization, error, onSubmit, onDeleteOrganization }) => { const form = useFormik({ initialValues: { name: organization.name, @@ -85,7 +84,7 @@ export const OrganizationSettingsPageView: FC< description="The name and description of the organization." >
@@ -117,10 +116,10 @@ export const OrganizationSettingsPageView: FC<
- {canEdit && } + - {canEdit && !organization.is_default && ( + {!organization.is_default && ( = { + title: "pages/OrganizationSummaryPageView", + component: OrganizationSummaryPageView, + args: { + organization: MockOrganization, + }, +}; + +export default meta; +type Story = StoryObj; + +export const DefaultOrg: Story = { + args: { + organization: MockDefaultOrganization, + }, +}; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx new file mode 100644 index 0000000000000..2cb7ab60c090f --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx @@ -0,0 +1,48 @@ +import type { FC } from "react"; +import type { Organization } from "api/typesGenerated"; +import { + PageHeader, + PageHeaderTitle, + PageHeaderSubtitle, +} from "components/PageHeader/PageHeader"; +import { Stack } from "components/Stack/Stack"; +import { UserAvatar } from "components/UserAvatar/UserAvatar"; + +interface OrganizationSummaryPageViewProps { + organization: Organization; +} + +export const OrganizationSummaryPageView: FC< + OrganizationSummaryPageViewProps +> = ({ organization }) => { + return ( +
+ + + +
+ + {organization.display_name || organization.name} + + {organization.description && ( + + {organization.description} + + )} +
+
+
+ You are a member of this organization. +
+ ); +};