Skip to content

refactor(site): Refactor workspace schedule page #7069

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 11, 2023
Merged
Next Next commit
Add base workspace settings layout
  • Loading branch information
BrunoQuaresma committed Apr 10, 2023
commit 19306ce0eba4b0f8e694ae3a14b595279cd44317
5 changes: 4 additions & 1 deletion site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { RequireAuth } from "./components/RequireAuth/RequireAuth"
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"
import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout"
import { WorkspaceSettingsLayout } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout"

// Lazy load pages
// - Pages that are secondary, not in the main navigation or not usually accessed
Expand Down Expand Up @@ -265,7 +266,9 @@ export const AppRouter: FC = () => {
path="builds/:buildNumber"
element={<WorkspaceBuildPage />}
/>
<Route path="settings" element={<WorkspaceSettingsPage />} />
<Route path="settings" element={<WorkspaceSettingsLayout />}>
<Route index element={<WorkspaceSettingsPage />} />
</Route>
</Route>
</Route>
</Route>
Expand Down
135 changes: 135 additions & 0 deletions site/src/pages/WorkspaceSettingsPage/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { makeStyles } from "@material-ui/core/styles"
import ScheduleIcon from "@material-ui/icons/TimerOutlined"
import { Workspace } from "api/typesGenerated"
import { Stack } from "components/Stack/Stack"
import { FC, ElementType, PropsWithChildren, ReactNode } from "react"
import { Link, NavLink } from "react-router-dom"
import { combineClasses } from "util/combineClasses"
import GeneralIcon from "@material-ui/icons/SettingsOutlined"
import { Avatar } from "components/Avatar/Avatar"

const SidebarNavItem: FC<
PropsWithChildren<{ href: string; icon: ReactNode }>
> = ({ children, href, icon }) => {
const styles = useStyles()
return (
<NavLink
end
to={href}
className={({ isActive }) =>
combineClasses([
styles.sidebarNavItem,
isActive ? styles.sidebarNavItemActive : undefined,
])
}
>
<Stack alignItems="center" spacing={1.5} direction="row">
{icon}
{children}
</Stack>
</NavLink>
)
}

const SidebarNavItemIcon: React.FC<{ icon: ElementType }> = ({
icon: Icon,
}) => {
const styles = useStyles()
return <Icon className={styles.sidebarNavItemIcon} />
}

export const Sidebar: React.FC<{ workspace: Workspace }> = ({ workspace }) => {
const styles = useStyles()

return (
<nav className={styles.sidebar}>
<Stack
direction="row"
alignItems="center"
className={styles.workspaceInfo}
>
<Avatar src={workspace.template_icon} variant="square" fitImage />
<Stack spacing={0} className={styles.workspaceData}>
<Link className={styles.name} to={`/workspaces/${workspace.name}`}>
{workspace.name}
</Link>
<span className={styles.secondary}>
{workspace.template_display_name ?? workspace.template_name}
</span>
</Stack>
</Stack>

<SidebarNavItem href="" icon={<SidebarNavItemIcon icon={GeneralIcon} />}>
General
</SidebarNavItem>
<SidebarNavItem
href="schedule"
icon={<SidebarNavItemIcon icon={ScheduleIcon} />}
>
Schedule
</SidebarNavItem>
</nav>
)
}

const useStyles = makeStyles((theme) => ({
sidebar: {
width: 245,
flexShrink: 0,
},
sidebarNavItem: {
color: "inherit",
display: "block",
fontSize: 14,
textDecoration: "none",
padding: theme.spacing(1.5, 1.5, 1.5, 2),
borderRadius: theme.shape.borderRadius / 2,
transition: "background-color 0.15s ease-in-out",
marginBottom: 1,
position: "relative",

"&:hover": {
backgroundColor: theme.palette.action.hover,
},
},
sidebarNavItemActive: {
backgroundColor: theme.palette.action.hover,

"&:before": {
content: '""',
display: "block",
width: 3,
height: "100%",
position: "absolute",
left: 0,
top: 0,
backgroundColor: theme.palette.secondary.dark,
borderTopLeftRadius: theme.shape.borderRadius,
borderBottomLeftRadius: theme.shape.borderRadius,
},
},
sidebarNavItemIcon: {
width: theme.spacing(2),
height: theme.spacing(2),
},
workspaceInfo: {
marginBottom: theme.spacing(2),
},
workspaceData: {
overflow: "hidden",
},
name: {
fontWeight: 600,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
color: theme.palette.text.primary,
textDecoration: "none",
},
secondary: {
color: theme.palette.text.secondary,
fontSize: 12,
overflow: "hidden",
textOverflow: "ellipsis",
},
}))
86 changes: 86 additions & 0 deletions site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { makeStyles } from "@material-ui/core/styles"
import { Sidebar } from "./Sidebar"
import { Stack } from "components/Stack/Stack"
import { createContext, FC, Suspense, useContext } from "react"
import { Helmet } from "react-helmet-async"
import { pageTitle } from "../../util/page"
import { Loader } from "components/Loader/Loader"
import { Outlet, useParams } from "react-router-dom"
import { Margins } from "components/Margins/Margins"
import { getWorkspaceByOwnerAndName } from "api/api"
import { useQuery } from "@tanstack/react-query"

const fetchWorkspaceSettings = async (owner: string, name: string) => {
const workspace = await getWorkspaceByOwnerAndName(owner, name)

return {
workspace,
}
}

const useWorkspace = (owner: string, name: string) => {
return useQuery({
queryKey: ["workspace", name, "settings"],
queryFn: () => fetchWorkspaceSettings(owner, name),
})
}

const WorkspaceSettingsContext = createContext<
Awaited<ReturnType<typeof fetchWorkspaceSettings>> | undefined
>(undefined)

export const useWorkspaceSettingsContext = () => {
const context = useContext(WorkspaceSettingsContext)

if (!context) {
throw new Error(
"useWorkspaceSettingsContext must be used within a WorkspaceSettingsContext.Provider",
)
}

return context
}

export const WorkspaceSettingsLayout: FC = () => {
const styles = useStyles()
const { workspace: workspaceName, username } = useParams() as {
workspace: string
username: string
}
const { data: settings } = useWorkspace(username, workspaceName)

return (
<>
<Helmet>
<title>{pageTitle([workspaceName, "Settings"])}</title>
</Helmet>

{settings ? (
<WorkspaceSettingsContext.Provider value={settings}>
<Margins>
<Stack className={styles.wrapper} direction="row" spacing={10}>
<Sidebar workspace={settings.workspace} />
<Suspense fallback={<Loader />}>
<main className={styles.content}>
<Outlet />
</main>
</Suspense>
</Stack>
</Margins>
</WorkspaceSettingsContext.Provider>
) : (
<Loader />
)}
</>
)
}

const useStyles = makeStyles((theme) => ({
wrapper: {
padding: theme.spacing(6, 0),
},

content: {
width: "100%",
},
}))
42 changes: 26 additions & 16 deletions site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPageView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"
import { Loader } from "components/Loader/Loader"
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import { WorkspaceSettings, WorkspaceSettingsFormValue } from "./data"
Expand All @@ -26,22 +27,31 @@ export const WorkspaceSettingsPageView: FC<WorkspaceSettingsPageViewProps> = ({
loadingError,
}) => {
const { t } = useTranslation("workspaceSettingsPage")
const styles = useStyles()

return (
<FullPageHorizontalForm title={t("title")} onCancel={onCancel}>
<>
{loadingError && <AlertBanner error={loadingError} severity="error" />}
{isLoading && <Loader />}
{settings && (
<WorkspaceSettingsForm
error={formError}
isSubmitting={isSubmitting}
settings={settings}
onCancel={onCancel}
onSubmit={onSubmit}
/>
)}
</>
</FullPageHorizontalForm>
<>
<PageHeader className={styles.pageHeader}>
<PageHeaderTitle>{t("title")}</PageHeaderTitle>
</PageHeader>

{loadingError && <AlertBanner error={loadingError} severity="error" />}
{isLoading && <Loader />}
{settings && (
<WorkspaceSettingsForm
error={formError}
isSubmitting={isSubmitting}
settings={settings}
onCancel={onCancel}
onSubmit={onSubmit}
/>
)}
</>
)
}

const useStyles = makeStyles(() => ({
pageHeader: {
paddingTop: 0,
},
}))