diff --git a/site/src/components/Breadcrumb/Breadcrumb.stories.tsx b/site/src/components/Breadcrumb/Breadcrumb.stories.tsx index 7ece0cd119..01b772389e 100644 --- a/site/src/components/Breadcrumb/Breadcrumb.stories.tsx +++ b/site/src/components/Breadcrumb/Breadcrumb.stories.tsx @@ -32,7 +32,7 @@ export const Default: Story = { - Organizations + Organizations diff --git a/site/src/components/Breadcrumb/Breadcrumb.tsx b/site/src/components/Breadcrumb/Breadcrumb.tsx index 667c301ea3..cdc39f682a 100644 --- a/site/src/components/Breadcrumb/Breadcrumb.tsx +++ b/site/src/components/Breadcrumb/Breadcrumb.tsx @@ -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> = ({ ); }; -type BreadcrumbLinkProps = React.ComponentPropsWithRef<"a"> & { - asChild?: boolean; -}; +type BreadcrumbLinkProps = React.ComponentProps; export const BreadcrumbLink: React.FC = ({ - asChild, className, ...props }) => { - const Comp = asChild ? Slot.Root : "a"; - return ( - {
+ }> diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index f02cae4e69..edf59478a6 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -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 = ({ template }) => { - const getLink = useLinks(); +export const Sidebar: React.FC = () => { return ( - - } - title={template.display_name || template.name} - linkTo={getLink( - linkToTemplate(template.organization_name, template.name), - )} - subtitle={template.name} - /> - - - General - - - Permissions - - - Variables - - - Schedule - +
+ + General + + + Permissions + + + Variables + + + Schedule + +
); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.tsx index 08c6037e57..5c17fa6fd4 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.tsx @@ -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 = ({ sharedPortControlsEnabled, }) => { return ( - <> - - General Settings - +
+ + General + + Update template metadata and workspace policies. + + = ({ advancedSchedulingEnabled={advancedSchedulingEnabled} portSharingControlsEnabled={sharedPortControlsEnabled} /> - +
); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx index 44ef8328ec..4d4ba40459 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx @@ -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 ( - <> - - Permissions - +
+ + Permissions + + Manage which members and groups can use this template. + +
{canUpdatePermissions && ( @@ -265,7 +269,7 @@ export const TemplatePermissionsPageView: FC<
- +
); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePageView.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePageView.tsx index 38fc63aa92..9eac09838e 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePageView.tsx @@ -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 = ({ initialTouched, }) => { return ( - <> - - Template schedule - +
+ + Schedule + + Configure workspace schedule defaults for this template. + + = ({ onCancel={onCancel} error={submitError} /> - +
); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx index 8cce82d23b..4dc5f042b7 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSettingsLayout.tsx @@ -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 ; } + const error = templateQuery.isError || permissionsQuery.isError; + const template = templateQuery.data; + return ( <> - {pageTitle(templateName, "Settings")} + + {pageTitle(template?.display_name ?? templateName, "Template Settings")} + - -
- {templateQuery.isError || permissionsQuery.isError ? ( - - ) : ( - - - }> -
- +
+ + + + Template Settings + + {template && ( + <> + {showOrganizations && ( + <> + + + + + {template.organization_display_name} + + + + )} + + + + + + {template.display_name || template.name} + + + + + )} + + +
+ +
+
+ {error ? ( + + ) : ( + + +
+ }> + +
- -
- )} -
- + + )} +
+ +
); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPageView.tsx index cb6f30825f..c549fb9bab 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPageView.tsx @@ -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 = ({ const hasError = Object.values(errors).some((error) => Boolean(error)); return ( - <> - - Template variables - +
+ + Variables + + Update the variables used by this template. + + + {hasError && ( -
+
{Boolean(errors.buildError) && ( )} @@ -75,6 +80,6 @@ export const TemplateVariablesPageView: FC = ({ This template does not use managed variables. )} - +
); }; diff --git a/site/src/pages/UserSettingsPage/Layout.tsx b/site/src/pages/UserSettingsPage/Layout.tsx index 5146207f8f..6fc0828972 100644 --- a/site/src/pages/UserSettingsPage/Layout.tsx +++ b/site/src/pages/UserSettingsPage/Layout.tsx @@ -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 + + + + + {me.username} + +
+
- +
}> diff --git a/site/src/pages/UserSettingsPage/Sidebar.tsx b/site/src/pages/UserSettingsPage/Sidebar.tsx index a96541a3fb..8fe704c903 100644 --- a/site/src/pages/UserSettingsPage/Sidebar.tsx +++ b/site/src/pages/UserSettingsPage/Sidebar.tsx @@ -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 = ({ 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 = ({ user }) => { return ( - } - title={user.username} - subtitle={user.email} - />
Account diff --git a/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx b/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx index bc401d95cf..18a800f58d 100644 --- a/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx +++ b/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx @@ -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 ( - - } - title={workspace.name} - linkTo={`/@${owner}/${workspace.name}`} - subtitle={workspace.template_display_name ?? workspace.template_name} - /> - - - General - - - Parameters - - - Schedule - - {permissions?.shareWorkspace && ( - - Sharing - - )} +
+ + General + + + Parameters + + + Schedule + + {permissions?.shareWorkspace && ( + + Sharing + + )} +
); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx index 6ab7ad9a72..26275c5aaf 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.tsx @@ -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 ( -
+
{pageTitle(workspace.name, "Parameters")} -
- - -

Workspace parameters

+ + @@ -290,9 +294,14 @@ const WorkspaceParametersPage: FC = () => { -
-
-
+ } + > + Parameters + + + Update the build parameters for this workspace. + + {Boolean(error) && } diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index de9486683c..5ef8395049 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -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 ( - <> +
{pageTitle(workspaceName, "Schedule")} - - Workspace Schedule - + + Schedule + + Configure when this workspace starts and stops automatically. + + {error && } @@ -162,7 +166,7 @@ const WorkspaceSchedulePage: FC = () => { navigate(`/@${username}/${workspaceName}`); }} /> - +
); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx index 31d351e475..efe7aa2aea 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx @@ -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 ( <> - {pageTitle(workspaceName, "Settings")} + {pageTitle(workspaceName, "Workspace Settings")} - -
- {error ? ( - - ) : ( - workspaceQuery.data && ( - - - }> -
- +
+ + + + Workspace Settings + + {workspace && ( + <> + + + + + {workspace.owner_name} + + + + + + + + {workspace.name} + + + + + )} + + +
+ +
+
+ {error ? ( + + ) : ( + workspaceQuery.data && ( + + +
+ }> + +
- -
- ) - )} -
- + + ) + )} +
+
+
); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx index 9b9b143914..d975606a90 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx @@ -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 = ({ workspace, }) => { return ( - <> - - Workspace Settings - +
+ + General + + Update the name and automatic update behavior for this workspace. + + = ({ onCancel={onCancel} onSubmit={onSubmit} /> - +
); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx index a2ea892477..8c3b608ce1 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPage.tsx @@ -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 ( -
+ <> {pageTitle(workspace.name, "Sharing")} -
-
-

Workspace sharing

-

- Workspace sharing allows you to share workspaces with other users - and groups.{" "} - View docs -

-
-
- { onRemoveGroup={sharing.removeGroup} hasRemovedMember={sharing.hasRemovedMember} /> -
+ ); }; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPageView.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPageView.tsx index 32f82daaa6..6329d2f73c 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPageView.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSharingPage/WorkspaceSharingPageView.tsx @@ -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 = ({ hasRemovedMember, }) => { return ( - - isGroup(value) - ? onAddGroup(value, role, resetAutocomplete) - : onAddUser(value, role, resetAutocomplete) - } - /> - } - /> +
+ + } + > + Sharing + + Share this workspace with other users and groups. + + + + + isGroup(value) + ? onAddGroup(value, role, resetAutocomplete) + : onAddUser(value, role, resetAutocomplete) + } + /> + } + /> +
); };