refactor(site): clean up workspace and template settings layouts (#25209)

This commit is contained in:
McKayla はな
2026-07-25 11:45:22 -06:00
committed by GitHub
parent 6159eb4fc5
commit 6120fb5988
18 changed files with 355 additions and 249 deletions
@@ -32,7 +32,7 @@ export const Default: Story = {
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/organizations">Organizations</BreadcrumbLink>
<BreadcrumbLink to="/organizations">Organizations</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
@@ -3,7 +3,7 @@
* @see {@link https://ui.shadcn.com/docs/components/breadcrumb}
*/
import { MoreHorizontalIcon } from "lucide-react";
import { Slot } from "radix-ui";
import { NavLink } from "react-router";
import { cn } from "#/utils/cn";
type BreadcrumbProps = React.ComponentPropsWithRef<"nav"> & {
@@ -44,19 +44,14 @@ export const BreadcrumbItem: React.FC<React.ComponentPropsWithRef<"li">> = ({
);
};
type BreadcrumbLinkProps = React.ComponentPropsWithRef<"a"> & {
asChild?: boolean;
};
type BreadcrumbLinkProps = React.ComponentProps<typeof NavLink>;
export const BreadcrumbLink: React.FC<BreadcrumbLinkProps> = ({
asChild,
className,
...props
}) => {
const Comp = asChild ? Slot.Root : "a";
return (
<Comp
<NavLink
className={cn(
"text-content-secondary transition-colors hover:text-content-primary no-underline hover:underline",
className,
@@ -122,6 +122,7 @@ const OrganizationSettingsLayout: FC = () => {
</BreadcrumbList>
</Breadcrumb>
<div className="h-px border-none bg-border" />
<Suspense fallback={<Loader />}>
<Outlet />
</Suspense>
+16 -42
View File
@@ -1,51 +1,25 @@
import {
LockIcon,
TimerIcon as ScheduleIcon,
SettingsIcon,
CodeIcon as VariablesIcon,
} from "lucide-react";
import type { FC } from "react";
import type { Template } from "#/api/typesGenerated";
import { Avatar } from "#/components/Avatar/Avatar";
import {
Sidebar as BaseSidebar,
SidebarHeader,
SidebarNavItem,
SettingsSidebarNavItem,
} from "#/components/Sidebar/Sidebar";
import { linkToTemplate, useLinks } from "#/modules/navigation";
interface SidebarProps {
template: Template;
}
export const Sidebar: FC<SidebarProps> = ({ template }) => {
const getLink = useLinks();
export const Sidebar: React.FC = () => {
return (
<BaseSidebar>
<SidebarHeader
avatar={
<Avatar variant="icon" src={template.icon} fallback={template.name} />
}
title={template.display_name || template.name}
linkTo={getLink(
linkToTemplate(template.organization_name, template.name),
)}
subtitle={template.name}
/>
<SidebarNavItem href="" icon={SettingsIcon}>
General
</SidebarNavItem>
<SidebarNavItem href="permissions" icon={LockIcon}>
Permissions
</SidebarNavItem>
<SidebarNavItem href="variables" icon={VariablesIcon}>
Variables
</SidebarNavItem>
<SidebarNavItem href="schedule" icon={ScheduleIcon}>
Schedule
</SidebarNavItem>
<div className="flex flex-col gap-1">
<SettingsSidebarNavItem end href="">
General
</SettingsSidebarNavItem>
<SettingsSidebarNavItem href="permissions">
Permissions
</SettingsSidebarNavItem>
<SettingsSidebarNavItem href="variables">
Variables
</SettingsSidebarNavItem>
<SettingsSidebarNavItem href="schedule">
Schedule
</SettingsSidebarNavItem>
</div>
</BaseSidebar>
);
};
@@ -1,9 +1,10 @@
import type { ComponentProps, FC } from "react";
import type { Template, UpdateTemplateMeta } from "#/api/typesGenerated";
import {
PageHeader,
PageHeaderTitle,
} from "#/components/PageHeader/PageHeader";
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { TemplateSettingsForm } from "./TemplateSettingsForm";
interface TemplateSettingsPageViewProps {
@@ -32,10 +33,13 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
sharedPortControlsEnabled,
}) => {
return (
<>
<PageHeader className="pt-0">
<PageHeaderTitle>General Settings</PageHeaderTitle>
</PageHeader>
<div className="flex flex-col gap-12">
<SettingsHeader>
<SettingsHeaderTitle>General</SettingsHeaderTitle>
<SettingsHeaderDescription>
Update template metadata and workspace policies.
</SettingsHeaderDescription>
</SettingsHeader>
<TemplateSettingsForm
initialTouched={initialTouched}
@@ -48,6 +52,6 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
advancedSchedulingEnabled={advancedSchedulingEnabled}
portSharingControlsEnabled={sharedPortControlsEnabled}
/>
</>
</div>
);
};
@@ -18,10 +18,6 @@ import {
DropdownMenuTrigger,
} from "#/components/DropdownMenu/DropdownMenu";
import { EmptyState } from "#/components/EmptyState/EmptyState";
import {
PageHeader,
PageHeaderTitle,
} from "#/components/PageHeader/PageHeader";
import {
Select,
SelectContent,
@@ -29,6 +25,11 @@ import {
SelectTrigger,
SelectValue,
} from "#/components/Select/Select";
import {
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { Spinner } from "#/components/Spinner/Spinner";
import {
Table,
@@ -225,10 +226,13 @@ export const TemplatePermissionsPageView: FC<
onRemoveGroup,
}) => {
return (
<>
<PageHeader className="pt-0">
<PageHeaderTitle>Permissions</PageHeaderTitle>
</PageHeader>
<div className="flex flex-col gap-12">
<SettingsHeader>
<SettingsHeaderTitle>Permissions</SettingsHeaderTitle>
<SettingsHeaderDescription>
Manage which members and groups can use this template.
</SettingsHeaderDescription>
</SettingsHeader>
<div className="flex flex-col gap-2.5">
{canUpdatePermissions && (
@@ -265,7 +269,7 @@ export const TemplatePermissionsPageView: FC<
</TableBody>
</Table>
</div>
</>
</div>
);
};
@@ -1,9 +1,10 @@
import type { ComponentProps, FC } from "react";
import type { Template, UpdateTemplateMeta } from "#/api/typesGenerated";
import {
PageHeader,
PageHeaderTitle,
} from "#/components/PageHeader/PageHeader";
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { TemplateScheduleForm } from "./TemplateScheduleForm";
interface TemplateSchedulePageViewProps {
@@ -28,10 +29,13 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
initialTouched,
}) => {
return (
<>
<PageHeader className="pt-0">
<PageHeaderTitle>Template schedule</PageHeaderTitle>
</PageHeader>
<div className="flex flex-col gap-12">
<SettingsHeader>
<SettingsHeaderTitle>Schedule</SettingsHeaderTitle>
<SettingsHeaderDescription>
Configure workspace schedule defaults for this template.
</SettingsHeaderDescription>
</SettingsHeader>
<TemplateScheduleForm
allowAdvancedScheduling={allowAdvancedScheduling}
@@ -42,6 +46,6 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
onCancel={onCancel}
error={submitError}
/>
</>
</div>
);
};
@@ -5,8 +5,17 @@ import { checkAuthorization } from "#/api/queries/authCheck";
import { templateByName } from "#/api/queries/templates";
import type { AuthorizationResponse, Template } from "#/api/typesGenerated";
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
import { Avatar } from "#/components/Avatar/Avatar";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "#/components/Breadcrumb/Breadcrumb";
import { Loader } from "#/components/Loader/Loader";
import { Margins } from "#/components/Margins/Margins";
import { useDashboard } from "#/modules/dashboard/useDashboard";
import { pageTitle } from "#/utils/page";
import { Sidebar } from "./Sidebar";
@@ -24,11 +33,13 @@ export function useTemplateSettings() {
}
export const TemplateSettingsLayout: FC = () => {
const { showOrganizations } = useDashboard();
const { organization: organizationName = "default", template: templateName } =
useParams() as { organization?: string; template: string };
const templateQuery = useQuery(
templateByName(organizationName, templateName),
);
const permissionsQuery = useQuery({
...checkAuthorization({
checks: {
@@ -44,35 +55,87 @@ export const TemplateSettingsLayout: FC = () => {
enabled: templateQuery.isSuccess,
});
if (!(templateQuery.data && permissionsQuery.data)) {
if (!templateQuery.data || !permissionsQuery.data) {
return <Loader />;
}
const error = templateQuery.isError || permissionsQuery.isError;
const template = templateQuery.data;
return (
<>
<title>{pageTitle(templateName, "Settings")}</title>
<title>
{pageTitle(template?.display_name ?? templateName, "Template Settings")}
</title>
<Margins className="max-sm:!px-4">
<div className="flex flex-col gap-8 py-6 lg:flex-row lg:gap-20 lg:py-12">
{templateQuery.isError || permissionsQuery.isError ? (
<ErrorAlert error={templateQuery.error} />
) : (
<TemplateSettings.Provider
value={{
template: templateQuery.data,
permissions: permissionsQuery.data,
}}
>
<Sidebar template={templateQuery.data} />
<Suspense fallback={<Loader />}>
<div className="w-full min-w-0">
<Outlet />
<div>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage>Template Settings</BreadcrumbPage>
</BreadcrumbItem>
{template && (
<>
{showOrganizations && (
<>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage className="flex items-center gap-2">
<Avatar
size="sm"
fallback={
template.organization_display_name ||
template.organization_name
}
src={template.organization_icon}
/>
{template.organization_display_name}
</BreadcrumbPage>
</BreadcrumbItem>
</>
)}
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink to="..">
<BreadcrumbPage className="flex items-center gap-2">
<Avatar
variant="icon"
size="sm"
fallback={template.display_name || template.name}
src={template.icon}
/>
{template.display_name || template.name}
</BreadcrumbPage>
</BreadcrumbLink>
</BreadcrumbItem>
</>
)}
</BreadcrumbList>
</Breadcrumb>
<div className="h-px border-none bg-border" />
<section className="px-4 sm:px-6 lg:px-10 max-w-screen-2xl mx-auto">
<div className="flex flex-col gap-8 py-6 lg:flex-row lg:gap-28 lg:py-10">
{error ? (
<ErrorAlert error={error} />
) : (
<TemplateSettings.Provider
value={{
template: templateQuery.data,
permissions: permissionsQuery.data,
}}
>
<Sidebar />
<div className="grow min-w-0">
<Suspense fallback={<Loader />}>
<Outlet />
</Suspense>
</div>
</Suspense>
</TemplateSettings.Provider>
)}
</div>
</Margins>
</TemplateSettings.Provider>
)}
</div>
</section>
</div>
</>
);
};
@@ -7,9 +7,10 @@ import type {
import { Alert } from "#/components/Alert/Alert";
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
import {
PageHeader,
PageHeaderTitle,
} from "#/components/PageHeader/PageHeader";
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { TemplateVariablesForm } from "./TemplateVariablesForm";
interface TemplateVariablesPageViewProps {
@@ -45,12 +46,16 @@ export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
const hasError = Object.values(errors).some((error) => Boolean(error));
return (
<>
<PageHeader className="pt-0">
<PageHeaderTitle>Template variables</PageHeaderTitle>
</PageHeader>
<div className="flex flex-col gap-12">
<SettingsHeader>
<SettingsHeaderTitle>Variables</SettingsHeaderTitle>
<SettingsHeaderDescription>
Update the variables used by this template.
</SettingsHeaderDescription>
</SettingsHeader>
{hasError && (
<div className="flex flex-col gap-4 mb-16">
<div className="flex flex-col gap-4">
{Boolean(errors.buildError) && (
<ErrorAlert error={errors.buildError} />
)}
@@ -75,6 +80,6 @@ export const TemplateVariablesPageView: FC<TemplateVariablesPageViewProps> = ({
This template does not use managed variables.
</Alert>
)}
</>
</div>
);
};
+11 -1
View File
@@ -1,10 +1,12 @@
import { type FC, Suspense } from "react";
import { Outlet } from "react-router";
import { Avatar } from "#/components/Avatar/Avatar";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "#/components/Breadcrumb/Breadcrumb";
import { Loader } from "#/components/Loader/Loader";
import { useAuthenticated } from "#/hooks/useAuthenticated";
@@ -26,12 +28,20 @@ const Layout: FC = () => {
User Settings
</BreadcrumbPage>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage className="flex items-center gap-2">
<Avatar size="sm" fallback={me.username} src={me.avatar_url} />
{me.username}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="h-px border-none bg-border" />
<section className="px-4 sm:px-6 lg:px-10 max-w-screen-2xl mx-auto">
<div className="flex flex-col gap-8 py-6 lg:flex-row lg:gap-28 lg:py-10">
<Sidebar user={me} />
<Sidebar />
<div className="grow min-w-0">
<Suspense fallback={<Loader />}>
<Outlet />
+1 -14
View File
@@ -1,20 +1,12 @@
import type { FC } from "react";
import type { User } from "#/api/typesGenerated";
import { Avatar } from "#/components/Avatar/Avatar";
import { FeatureStageBadge } from "#/components/FeatureStageBadge/FeatureStageBadge";
import {
Sidebar as BaseSidebar,
SettingsSidebarNavItem,
SidebarHeader,
} from "#/components/Sidebar/Sidebar";
import { useDashboard } from "#/modules/dashboard/useDashboard";
import { getPrereleaseFlag } from "#/utils/buildInfo";
interface SidebarProps {
user: User;
}
export const Sidebar: FC<SidebarProps> = ({ user }) => {
export const Sidebar: React.FC = () => {
const { entitlements, experiments, buildInfo } = useDashboard();
const showSchedulePage =
entitlements.features.advanced_template_scheduling.enabled;
@@ -23,11 +15,6 @@ export const Sidebar: FC<SidebarProps> = ({ user }) => {
return (
<BaseSidebar>
<SidebarHeader
avatar={<Avatar fallback={user.username} src={user.avatar_url} />}
title={user.username}
subtitle={user.email}
/>
<div className="flex flex-col gap-1">
<SettingsSidebarNavItem href="account">Account</SettingsSidebarNavItem>
<SettingsSidebarNavItem href="appearance">
@@ -1,49 +1,30 @@
import {
SettingsIcon as GeneralIcon,
CodeIcon as ParameterIcon,
TimerIcon as ScheduleIcon,
UsersIcon as SharingIcon,
} from "lucide-react";
import { Avatar } from "#/components/Avatar/Avatar";
import {
Sidebar as BaseSidebar,
SidebarHeader,
SidebarNavItem,
SettingsSidebarNavItem,
} from "#/components/Sidebar/Sidebar";
import { useWorkspaceSettings } from "./useWorkspaceSettings";
export const Sidebar: React.FC = () => {
const { owner, workspace, permissions } = useWorkspaceSettings();
const { permissions } = useWorkspaceSettings();
return (
<BaseSidebar>
<SidebarHeader
avatar={
<Avatar
variant="icon"
src={workspace.template_icon}
fallback={workspace.name}
/>
}
title={workspace.name}
linkTo={`/@${owner}/${workspace.name}`}
subtitle={workspace.template_display_name ?? workspace.template_name}
/>
<SidebarNavItem href="" icon={GeneralIcon}>
General
</SidebarNavItem>
<SidebarNavItem href="parameters" icon={ParameterIcon}>
Parameters
</SidebarNavItem>
<SidebarNavItem href="schedule" icon={ScheduleIcon}>
Schedule
</SidebarNavItem>
{permissions?.shareWorkspace && (
<SidebarNavItem href="sharing" icon={SharingIcon}>
Sharing
</SidebarNavItem>
)}
<div className="flex flex-col gap-1">
<SettingsSidebarNavItem end href="">
General
</SettingsSidebarNavItem>
<SettingsSidebarNavItem href="parameters">
Parameters
</SettingsSidebarNavItem>
<SettingsSidebarNavItem href="schedule">
Schedule
</SettingsSidebarNavItem>
{permissions?.shareWorkspace && (
<SettingsSidebarNavItem href="sharing">
Sharing
</SettingsSidebarNavItem>
)}
</div>
</BaseSidebar>
);
};
@@ -15,6 +15,11 @@ import { ConfirmDialog } from "#/components/Dialogs/ConfirmDialog/ConfirmDialog"
import { EmptyState } from "#/components/EmptyState/EmptyState";
import { Link } from "#/components/Link/Link";
import { Loader } from "#/components/Loader/Loader";
import {
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import {
Tooltip,
TooltipContent,
@@ -263,13 +268,12 @@ const WorkspaceParametersPage: FC = () => {
}
return (
<div className="flex flex-col gap-6 max-w-screen-md">
<div className="flex flex-col gap-12">
<title>{pageTitle(workspace.name, "Parameters")}</title>
<header className="flex flex-col items-start gap-2">
<span className="flex flex-row items-center gap-2 justify-between w-full">
<span className="flex flex-row items-center gap-2">
<h1 className="text-3xl m-0">Workspace parameters</h1>
<SettingsHeader>
<SettingsHeaderTitle
tooltip={
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger asChild>
@@ -290,9 +294,14 @@ const WorkspaceParametersPage: FC = () => {
</TooltipContent>
</Tooltip>
</TooltipProvider>
</span>
</span>
</header>
}
>
Parameters
</SettingsHeaderTitle>
<SettingsHeaderDescription>
Update the build parameters for this workspace.
</SettingsHeaderDescription>
</SettingsHeader>
{Boolean(error) && <ErrorAlert error={error} />}
@@ -14,9 +14,10 @@ import { ConfirmDialog } from "#/components/Dialogs/ConfirmDialog/ConfirmDialog"
import { Link } from "#/components/Link/Link";
import { Loader } from "#/components/Loader/Loader";
import {
PageHeader,
PageHeaderTitle,
} from "#/components/PageHeader/PageHeader";
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import {
scheduleChanged,
scheduleToAutostart,
@@ -71,12 +72,15 @@ const WorkspaceSchedulePage: FC = () => {
});
return (
<>
<div className="flex flex-col gap-12">
<title>{pageTitle(workspaceName, "Schedule")}</title>
<PageHeader className="pt-0">
<PageHeaderTitle>Workspace Schedule</PageHeaderTitle>
</PageHeader>
<SettingsHeader>
<SettingsHeaderTitle>Schedule</SettingsHeaderTitle>
<SettingsHeaderDescription>
Configure when this workspace starts and stops automatically.
</SettingsHeaderDescription>
</SettingsHeader>
{error && <ErrorAlert error={error} />}
@@ -162,7 +166,7 @@ const WorkspaceSchedulePage: FC = () => {
navigate(`/@${username}/${workspaceName}`);
}}
/>
</>
</div>
);
};
@@ -6,8 +6,16 @@ import {
workspacePermissions,
} from "#/api/queries/workspaces";
import { ErrorAlert } from "#/components/Alert/ErrorAlert";
import { Avatar } from "#/components/Avatar/Avatar";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "#/components/Breadcrumb/Breadcrumb";
import { Loader } from "#/components/Loader/Loader";
import { Margins } from "#/components/Margins/Margins";
import { pageTitle } from "#/utils/page";
import { Sidebar } from "./Sidebar";
import { WorkspaceSettings } from "./useWorkspaceSettings";
@@ -30,35 +38,79 @@ export const WorkspaceSettingsLayout: FC = () => {
}
const error = workspaceQuery.error || permissionsQuery.error;
const workspace = workspaceQuery.data;
return (
<>
<title>{pageTitle(workspaceName, "Settings")}</title>
<title>{pageTitle(workspaceName, "Workspace Settings")}</title>
<Margins className="max-sm:!px-4">
<div className="flex flex-col gap-8 py-6 lg:flex-row lg:gap-20 lg:py-12">
{error ? (
<ErrorAlert error={error} />
) : (
workspaceQuery.data && (
<WorkspaceSettings.Provider
value={{
owner: username,
workspace: workspaceQuery.data,
permissions: permissionsQuery.data,
}}
>
<Sidebar />
<Suspense fallback={<Loader />}>
<div className="w-full min-w-0">
<Outlet />
<div>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbPage>Workspace Settings</BreadcrumbPage>
</BreadcrumbItem>
{workspace && (
<>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage className="flex items-center gap-2">
<Avatar
size="sm"
fallback={workspace.owner_name}
src={workspace.owner_avatar_url}
/>
{workspace.owner_name}
</BreadcrumbPage>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink to="..">
<BreadcrumbPage className="flex items-center gap-2">
<Avatar
variant="icon"
size="sm"
fallback={
workspace.template_display_name ||
workspace.template_name
}
src={workspace.template_icon}
/>
{workspace.name}
</BreadcrumbPage>
</BreadcrumbLink>
</BreadcrumbItem>
</>
)}
</BreadcrumbList>
</Breadcrumb>
<div className="h-px border-none bg-border" />
<section className="px-4 sm:px-6 lg:px-10 max-w-screen-2xl mx-auto">
<div className="flex flex-col gap-8 py-6 lg:flex-row lg:gap-28 lg:py-10">
{error ? (
<ErrorAlert error={error} />
) : (
workspaceQuery.data && (
<WorkspaceSettings.Provider
value={{
owner: username,
workspace: workspaceQuery.data,
permissions: permissionsQuery.data,
}}
>
<Sidebar />
<div className="grow min-w-0">
<Suspense fallback={<Loader />}>
<Outlet />
</Suspense>
</div>
</Suspense>
</WorkspaceSettings.Provider>
)
)}
</div>
</Margins>
</WorkspaceSettings.Provider>
)
)}
</div>
</section>
</div>
</>
);
};
@@ -1,9 +1,10 @@
import type { ComponentProps, FC } from "react";
import type { Workspace } from "#/api/typesGenerated";
import {
PageHeader,
PageHeaderTitle,
} from "#/components/PageHeader/PageHeader";
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { WorkspaceSettingsForm } from "./WorkspaceSettingsForm";
type WorkspaceSettingsPageViewProps = {
@@ -20,10 +21,13 @@ export const WorkspaceSettingsPageView: FC<WorkspaceSettingsPageViewProps> = ({
workspace,
}) => {
return (
<>
<PageHeader className="pt-0">
<PageHeaderTitle>Workspace Settings</PageHeaderTitle>
</PageHeader>
<div className="flex flex-col gap-12">
<SettingsHeader>
<SettingsHeaderTitle>General</SettingsHeaderTitle>
<SettingsHeaderDescription>
Update the name and automatic update behavior for this workspace.
</SettingsHeaderDescription>
</SettingsHeader>
<WorkspaceSettingsForm
error={error}
@@ -31,6 +35,6 @@ export const WorkspaceSettingsPageView: FC<WorkspaceSettingsPageViewProps> = ({
onCancel={onCancel}
onSubmit={onSubmit}
/>
</>
</div>
);
};
@@ -1,11 +1,9 @@
import type { FC } from "react";
import { useQuery } from "react-query";
import { checkAuthorization } from "#/api/queries/authCheck";
import { Link } from "#/components/Link/Link";
import type { WorkspacePermissions } from "#/modules/workspaces/permissions";
import { workspaceChecks } from "#/modules/workspaces/permissions";
import { useWorkspaceSharing } from "#/modules/workspaces/WorkspaceSharingForm/useWorkspaceSharing";
import { docs } from "#/utils/docs";
import { pageTitle } from "#/utils/page";
import { useWorkspaceSettings } from "../useWorkspaceSettings";
import { WorkspaceSharingPageView } from "./WorkspaceSharingPageView";
@@ -25,20 +23,9 @@ const WorkspaceSharingPage: FC = () => {
sharing.error ?? permissionsQuery.error ?? sharing.mutationError;
return (
<div className="flex flex-col gap-12">
<>
<title>{pageTitle(workspace.name, "Sharing")}</title>
<header className="flex flex-col">
<div className="flex flex-col gap-2">
<h1 className="text-3xl m-0">Workspace sharing</h1>
<p className="flex flex-row gap-1 text-sm text-content-secondary font-medium m-0">
Workspace sharing allows you to share workspaces with other users
and groups.{" "}
<Link href={docs("/user-guides/shared-workspaces")}>View docs</Link>
</p>
</div>
</header>
<WorkspaceSharingPageView
workspace={workspace}
workspaceACL={sharing.workspaceACL}
@@ -56,7 +43,7 @@ const WorkspaceSharingPage: FC = () => {
onRemoveGroup={sharing.removeGroup}
hasRemovedMember={sharing.hasRemovedMember}
/>
</div>
</>
);
};
@@ -7,9 +7,16 @@ import type {
WorkspaceRole,
WorkspaceUser,
} from "#/api/typesGenerated";
import {
SettingsHeader,
SettingsHeaderDescription,
SettingsHeaderDocsLink,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { isGroup } from "#/modules/groups";
import { AddWorkspaceUserOrGroup } from "#/modules/workspaces/WorkspaceSharingForm/AddWorkspaceUserOrGroup";
import { WorkspaceSharingForm } from "#/modules/workspaces/WorkspaceSharingForm/WorkspaceSharingForm";
import { docs } from "#/utils/docs";
interface WorkspaceSharingPageViewProps {
workspace: Workspace;
@@ -51,30 +58,45 @@ export const WorkspaceSharingPageView: FC<WorkspaceSharingPageViewProps> = ({
hasRemovedMember,
}) => {
return (
<WorkspaceSharingForm
organizationId={workspace.organization_id}
workspaceACL={workspaceACL}
canUpdatePermissions={canUpdatePermissions}
error={error}
updatingUserId={updatingUserId}
onUpdateUser={onUpdateUser}
onRemoveUser={onRemoveUser}
updatingGroupId={updatingGroupId}
onUpdateGroup={onUpdateGroup}
onRemoveGroup={onRemoveGroup}
showRestartWarning={hasRemovedMember}
addMemberForm={
<AddWorkspaceUserOrGroup
organizationID={workspace.organization_id}
workspaceACL={workspaceACL}
isLoading={isAddingUser || isAddingGroup}
onSubmit={(value, role, resetAutocomplete) =>
isGroup(value)
? onAddGroup(value, role, resetAutocomplete)
: onAddUser(value, role, resetAutocomplete)
}
/>
}
/>
<div className="flex flex-col gap-12">
<SettingsHeader
actions={
<SettingsHeaderDocsLink
href={docs("/user-guides/shared-workspaces")}
/>
}
>
<SettingsHeaderTitle>Sharing</SettingsHeaderTitle>
<SettingsHeaderDescription>
Share this workspace with other users and groups.
</SettingsHeaderDescription>
</SettingsHeader>
<WorkspaceSharingForm
organizationId={workspace.organization_id}
workspaceACL={workspaceACL}
canUpdatePermissions={canUpdatePermissions}
error={error}
updatingUserId={updatingUserId}
onUpdateUser={onUpdateUser}
onRemoveUser={onRemoveUser}
updatingGroupId={updatingGroupId}
onUpdateGroup={onUpdateGroup}
onRemoveGroup={onRemoveGroup}
showRestartWarning={hasRemovedMember}
addMemberForm={
<AddWorkspaceUserOrGroup
organizationID={workspace.organization_id}
workspaceACL={workspaceACL}
isLoading={isAddingUser || isAddingGroup}
onSubmit={(value, role, resetAutocomplete) =>
isGroup(value)
? onAddGroup(value, role, resetAutocomplete)
: onAddUser(value, role, resetAutocomplete)
}
/>
}
/>
</div>
);
};