refactor(site): Refactor template settings (#6239)

This commit is contained in:
Bruno Quaresma
2023-02-17 13:27:35 +00:00
committed by GitHub
parent e161c45b47
commit 7a864bdb28
12 changed files with 549 additions and 272 deletions
@@ -0,0 +1,129 @@
import { makeStyles } from "@material-ui/core/styles"
import {
FormFooterProps as BaseFormFooterProps,
FormFooter as BaseFormFooter,
} from "components/FormFooter/FormFooter"
import { Stack } from "components/Stack/Stack"
import { FC, HTMLProps, PropsWithChildren } from "react"
export const HorizontalForm: FC<
PropsWithChildren & HTMLProps<HTMLFormElement>
> = ({ children, ...formProps }) => {
const styles = useStyles()
return (
<form {...formProps}>
<Stack direction="column" spacing={10} className={styles.formSections}>
{children}
</Stack>
</form>
)
}
export const FormSection: FC<
PropsWithChildren & { title: string; description: string | JSX.Element }
> = ({ children, title, description }) => {
const styles = useStyles()
return (
<div className={styles.formSection}>
<div className={styles.formSectionInfo}>
<h2 className={styles.formSectionInfoTitle}>{title}</h2>
<div className={styles.formSectionInfoDescription}>{description}</div>
</div>
{children}
</div>
)
}
export const FormFields: FC<PropsWithChildren> = ({ children }) => {
const styles = useStyles()
return (
<Stack direction="column" className={styles.formSectionFields}>
{children}
</Stack>
)
}
export const FormFooter: FC<BaseFormFooterProps> = (props) => {
const formFooterStyles = useFormFooterStyles()
return (
<BaseFormFooter
{...props}
styles={{ ...formFooterStyles, ...props.styles }}
/>
)
}
const useStyles = makeStyles((theme) => ({
formSections: {
[theme.breakpoints.down("sm")]: {
gap: theme.spacing(8),
},
},
formSection: {
display: "flex",
alignItems: "flex-start",
gap: theme.spacing(15),
[theme.breakpoints.down("sm")]: {
flexDirection: "column",
gap: theme.spacing(2),
},
},
formSectionInfo: {
width: 312,
flexShrink: 0,
position: "sticky",
top: theme.spacing(3),
[theme.breakpoints.down("sm")]: {
width: "100%",
position: "initial",
},
},
formSectionInfoTitle: {
fontSize: 20,
color: theme.palette.text.primary,
fontWeight: 400,
margin: 0,
marginBottom: theme.spacing(1),
},
formSectionInfoDescription: {
fontSize: 14,
color: theme.palette.text.secondary,
lineHeight: "160%",
margin: 0,
},
formSectionFields: {
width: "100%",
},
}))
const useFormFooterStyles = makeStyles((theme) => ({
button: {
minWidth: theme.spacing(23),
[theme.breakpoints.down("sm")]: {
width: "100%",
},
},
footer: {
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
flexDirection: "row-reverse",
gap: theme.spacing(2),
[theme.breakpoints.down("sm")]: {
flexDirection: "column",
gap: theme.spacing(1),
},
},
}))
@@ -1,22 +1,8 @@
import Button from "@material-ui/core/Button"
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
import SettingsOutlined from "@material-ui/icons/SettingsOutlined"
import { useMachine } from "@xstate/react"
import {
PageHeader,
PageHeaderSubtitle,
PageHeaderTitle,
} from "components/PageHeader/PageHeader"
import { useOrganizationId } from "hooks/useOrganizationId"
import { createContext, FC, Suspense, useContext } from "react"
import {
Link as RouterLink,
NavLink,
Outlet,
useParams,
} from "react-router-dom"
import { NavLink, Outlet, useNavigate, useParams } from "react-router-dom"
import { combineClasses } from "util/combineClasses"
import {
TemplateContext,
@@ -27,14 +13,7 @@ import { Stack } from "components/Stack/Stack"
import { Permissions } from "xServices/auth/authXService"
import { Loader } from "components/Loader/Loader"
import { usePermissions } from "hooks/usePermissions"
import { Avatar } from "components/Avatar/Avatar"
const Language = {
settingsButton: "Settings",
editButton: "Edit",
createButton: "Create workspace",
noDescription: "",
}
import { TemplatePageHeader } from "./TemplatePageHeader"
const useTemplateName = () => {
const { template } = useParams()
@@ -65,38 +44,10 @@ export const useTemplateLayoutContext = (): TemplateLayoutContextValue => {
return context
}
const TemplateSettingsButton: FC<{ templateName: string }> = ({
templateName,
}) => (
<Link
underline="none"
component={RouterLink}
to={`/templates/${templateName}/settings`}
>
<Button variant="outlined" startIcon={<SettingsOutlined />}>
{Language.settingsButton}
</Button>
</Link>
)
const CreateWorkspaceButton: FC<{
templateName: string
className?: string
}> = ({ templateName, className }) => (
<Link
underline="none"
component={RouterLink}
to={`/templates/${templateName}/workspace`}
>
<Button className={className ?? ""} startIcon={<AddCircleOutline />}>
{Language.createButton}
</Button>
</Link>
)
export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
children = <Outlet />,
}) => {
const navigate = useNavigate()
const styles = useStyles()
const organizationId = useOrganizationId()
const templateName = useTemplateName()
@@ -108,58 +59,20 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
})
const { template, permissions: templatePermissions } = templateState.context
const permissions = usePermissions()
const hasIcon = template && template.icon && template.icon !== ""
if (!template) {
if (!template || !templatePermissions) {
return <Loader />
}
const generatePageHeaderActions = (): JSX.Element[] => {
const pageActions: JSX.Element[] = []
if (templatePermissions?.canUpdateTemplate) {
pageActions.push(<TemplateSettingsButton templateName={template.name} />)
}
pageActions.push(<CreateWorkspaceButton templateName={template.name} />)
return pageActions
}
return (
<>
<Margins>
<PageHeader
actions={
<>
{generatePageHeaderActions().map((action, i) => (
<div key={i}>{action}</div>
))}
</>
}
>
<Stack direction="row" spacing={3} className={styles.pageTitle}>
{hasIcon ? (
<Avatar size="xl" src={template.icon} variant="square" fitImage />
) : (
<Avatar size="xl">{template.name}</Avatar>
)}
<div>
<PageHeaderTitle>
{template.display_name.length > 0
? template.display_name
: template.name}
</PageHeaderTitle>
<PageHeaderSubtitle condensed>
{template.description === ""
? Language.noDescription
: template.description}
</PageHeaderSubtitle>
</div>
</Stack>
</PageHeader>
</Margins>
<TemplatePageHeader
template={template}
permissions={templatePermissions}
onDeleteTemplate={() => {
navigate("/templates")
}}
/>
<div className={styles.tabs}>
<Margins>
@@ -204,17 +117,6 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
export const useStyles = makeStyles((theme) => {
return {
pageTitle: {
alignItems: "center",
},
iconWrapper: {
width: theme.spacing(6),
height: theme.spacing(6),
"& img": {
width: "100%",
},
},
tabs: {
borderBottom: `1px solid ${theme.palette.divider}`,
marginBottom: theme.spacing(5),
@@ -0,0 +1,35 @@
import { ComponentMeta, Story } from "@storybook/react"
import { MockTemplate } from "testHelpers/entities"
import {
TemplatePageHeader,
TemplatePageHeaderProps,
} from "./TemplatePageHeader"
export default {
title: "Components/TemplatePageHeader",
component: TemplatePageHeader,
argTypes: {
template: {
defaultValue: MockTemplate,
},
permissions: {
defaultValue: {
canUpdateTemplate: true,
},
},
},
} as ComponentMeta<typeof TemplatePageHeader>
const Template: Story<TemplatePageHeaderProps> = (args) => (
<TemplatePageHeader {...args} />
)
export const CanUpdate = Template.bind({})
CanUpdate.args = {}
export const CanNotUpdate = Template.bind({})
CanNotUpdate.args = {
permissions: {
canUpdateTemplate: false,
},
}
@@ -0,0 +1,119 @@
import Button from "@material-ui/core/Button"
import DeleteOutlined from "@material-ui/icons/DeleteOutlined"
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
import SettingsOutlined from "@material-ui/icons/SettingsOutlined"
import { AuthorizationResponse, Template } from "api/typesGenerated"
import { Avatar } from "components/Avatar/Avatar"
import { Maybe } from "components/Conditionals/Maybe"
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
import {
PageHeader,
PageHeaderTitle,
PageHeaderSubtitle,
} from "components/PageHeader/PageHeader"
import { Stack } from "components/Stack/Stack"
import { FC } from "react"
import { Link as RouterLink } from "react-router-dom"
import { useDeleteTemplate } from "./deleteTemplate"
import { Margins } from "components/Margins/Margins"
const Language = {
editButton: "Edit",
settingsButton: "Settings",
createButton: "Create workspace",
deleteButton: "Delete",
}
const TemplateSettingsButton: FC<{ templateName: string }> = ({
templateName,
}) => (
<Button
component={RouterLink}
to={`/templates/${templateName}/settings`}
startIcon={<SettingsOutlined />}
>
{Language.settingsButton}
</Button>
)
const CreateWorkspaceButton: FC<{
templateName: string
className?: string
}> = ({ templateName }) => (
<Button
startIcon={<AddCircleOutline />}
component={RouterLink}
to={`/templates/${templateName}/workspace`}
>
{Language.createButton}
</Button>
)
const DeleteTemplateButton: FC<{ onClick: () => void }> = ({ onClick }) => (
<Button startIcon={<DeleteOutlined />} onClick={onClick}>
{Language.deleteButton}
</Button>
)
export type TemplatePageHeaderProps = {
template: Template
permissions: AuthorizationResponse
onDeleteTemplate: () => void
}
export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
template,
permissions,
onDeleteTemplate,
}) => {
const hasIcon = template.icon && template.icon !== ""
const deleteTemplate = useDeleteTemplate(template, onDeleteTemplate)
return (
<Margins>
<PageHeader
actions={
<>
<Maybe condition={permissions.canUpdateTemplate}>
<DeleteTemplateButton
onClick={deleteTemplate.openDeleteConfirmation}
/>
<TemplateSettingsButton templateName={template.name} />
</Maybe>
<CreateWorkspaceButton templateName={template.name} />
</>
}
>
<Stack direction="row" spacing={3} alignItems="center">
{hasIcon ? (
<Avatar size="xl" src={template.icon} variant="square" fitImage />
) : (
<Avatar size="xl">{template.name}</Avatar>
)}
<div>
<PageHeaderTitle>
{template.display_name.length > 0
? template.display_name
: template.name}
</PageHeaderTitle>
{template.description !== "" && (
<PageHeaderSubtitle condensed>
{template.description}
</PageHeaderSubtitle>
)}
</div>
</Stack>
</PageHeader>
<DeleteDialog
isOpen={deleteTemplate.isDeleteDialogOpen}
confirmLoading={deleteTemplate.state.status === "deleting"}
onConfirm={deleteTemplate.confirmDelete}
onCancel={deleteTemplate.cancelDeleteConfirmation}
entity="template"
name={template.name}
/>
</Margins>
)
}
@@ -0,0 +1,48 @@
import { act, renderHook, waitFor } from "@testing-library/react"
import { MockTemplate } from "testHelpers/entities"
import { useDeleteTemplate } from "./deleteTemplate"
import * as API from "api/api"
test("delete dialog starts closed", () => {
const { result } = renderHook(() =>
useDeleteTemplate(MockTemplate, jest.fn()),
)
expect(result.current.isDeleteDialogOpen).toBeFalsy()
})
test("confirm template deletion", async () => {
const onDeleteTemplate = jest.fn()
const { result } = renderHook(() =>
useDeleteTemplate(MockTemplate, onDeleteTemplate),
)
//Open delete confirmation
act(() => {
result.current.openDeleteConfirmation()
})
expect(result.current.isDeleteDialogOpen).toBeTruthy()
// Confirm delete
jest.spyOn(API, "deleteTemplate")
await act(async () => result.current.confirmDelete())
await waitFor(() => expect(API.deleteTemplate).toBeCalledTimes(1))
expect(onDeleteTemplate).toBeCalledTimes(1)
})
test("cancel template deletion", () => {
const { result } = renderHook(() =>
useDeleteTemplate(MockTemplate, jest.fn()),
)
//Open delete confirmation
act(() => {
result.current.openDeleteConfirmation()
})
expect(result.current.isDeleteDialogOpen).toBeTruthy()
// Cancel deletion
act(() => {
result.current.cancelDeleteConfirmation()
})
expect(result.current.isDeleteDialogOpen).toBeFalsy()
})
@@ -0,0 +1,36 @@
import { deleteTemplate } from "api/api"
import { Template } from "api/typesGenerated"
import { useState } from "react"
type DeleteTemplateState =
| { status: "idle" }
| { status: "confirming" }
| { status: "deleting" }
export const useDeleteTemplate = (template: Template, onDelete: () => void) => {
const [state, setState] = useState<DeleteTemplateState>({ status: "idle" })
const isDeleteDialogOpen =
state.status === "confirming" || state.status === "deleting"
const openDeleteConfirmation = () => {
setState({ status: "confirming" })
}
const cancelDeleteConfirmation = () => {
setState({ status: "idle" })
}
const confirmDelete = async () => {
setState({ status: "deleting" })
await deleteTemplate(template.id)
onDelete()
}
return {
state,
isDeleteDialogOpen,
openDeleteConfirmation,
cancelDeleteConfirmation,
confirmDelete,
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"generalInfo": {
"title": "General info",
"description": "The name is used to identify the template in URLs and the API. It must be unique."
},
"displayInfo": {
"title": "Display info",
"description": "Give your template a friendly name, description, and icon."
},
"schedule": {
"title": "Schedule",
"description": "Define when workspaces created from this template automatically stop."
},
"operations": {
"title": "Operations",
"description": "Regulate actions allowed on workspaces created from this template."
},
"parameters": {
"title": "Template variables",
"description": "These variables are provided by your template's Terraform configuration."
},
"fields": {
"name": "Name",
"displayName": "Display name",
"description": "Description",
"icon": "Icon",
"autoStop": "Auto-stop default",
"allowUsersToCancel": "Allow users to cancel in-progress workspace jobs"
},
"helperText": {
"autoStop": "Time in hours",
"allowUsersToCancel": "If checked, users may be able to corrupt their workspace."
},
"upload": {
"removeTitle": "Remove file",
"title": "Upload template"
},
"tooltip": {
"allowUsersToCancel": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases."
}
}
+16 -5
View File
@@ -15,10 +15,21 @@
"ttlHelperText_other": "Workspaces created from this template will default to stopping after {{count}} hours.",
"allowUserCancelWorkspaceJobsLabel": "Allow users to cancel in-progress workspace jobs.",
"allowUserCancelWorkspaceJobsNotice": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases.",
"dangerZone": {
"dangerZoneHeader": "Danger Zone",
"deleteTemplateHeader": "Delete this template",
"deleteTemplateCaption": "Do you want to permanently delete this template?",
"deleteCta": "Delete Template"
"allowUsersCancelHelperText": "If checked, users may be able to corrupt their workspace.",
"generalInfo": {
"title": "General info",
"description": "The name is used to identify the template in URLs and the API. It must be unique within your organization."
},
"displayInfo": {
"title": "Display info",
"description": "Give your template a friendly name, description, and icon."
},
"schedule": {
"title": "Schedule",
"description": "Define when workspaces created from this template automatically stop."
},
"operations": {
"title": "Operations",
"description": "Regulate actions allowed on workspaces created from this template."
}
}
@@ -1,10 +1,5 @@
import Box from "@material-ui/core/Box"
import Checkbox from "@material-ui/core/Checkbox"
import Typography from "@material-ui/core/Typography"
import TextField from "@material-ui/core/TextField"
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
import { FormFooter } from "components/FormFooter/FormFooter"
import { Stack } from "components/Stack/Stack"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC } from "react"
import {
@@ -18,6 +13,16 @@ import i18next from "i18next"
import { useTranslation } from "react-i18next"
import { Maybe } from "components/Conditionals/Maybe"
import { LazyIconField } from "components/IconField/LazyIconField"
import {
FormFields,
FormSection,
HorizontalForm,
FormFooter,
} from "components/HorizontalForm/HorizontalForm"
import { Stack } from "components/Stack/Stack"
import Checkbox from "@material-ui/core/Checkbox"
import { HelpTooltip, HelpTooltipText } from "components/Tooltips/HelpTooltip"
import { makeStyles } from "@material-ui/core/styles"
const TTLHelperText = ({ ttl }: { ttl?: number }) => {
const { t } = useTranslation("templateSettingsPage")
@@ -101,48 +106,69 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
})
const getFieldHelpers = getFormHelpers<UpdateTemplateMeta>(form, error)
const { t } = useTranslation("templateSettingsPage")
const styles = useStyles()
return (
<form onSubmit={form.handleSubmit} aria-label={t("formAriaLabel")}>
<Stack>
<TextField
{...getFieldHelpers("name")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
autoFocus
fullWidth
label={t("nameLabel")}
variant="outlined"
/>
<HorizontalForm
onSubmit={form.handleSubmit}
aria-label={t("formAriaLabel")}
>
<FormSection
title={t("generalInfo.title")}
description={t("generalInfo.description")}
>
<FormFields>
<TextField
{...getFieldHelpers("name")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
autoFocus
fullWidth
label={t("nameLabel")}
variant="outlined"
/>
</FormFields>
</FormSection>
<TextField
{...getFieldHelpers("display_name")}
disabled={isSubmitting}
fullWidth
label={t("displayNameLabel")}
variant="outlined"
/>
<FormSection
title={t("displayInfo.title")}
description={t("displayInfo.description")}
>
<FormFields>
<TextField
{...getFieldHelpers("display_name")}
disabled={isSubmitting}
fullWidth
label={t("displayNameLabel")}
variant="outlined"
/>
<TextField
{...getFieldHelpers("description")}
multiline
disabled={isSubmitting}
fullWidth
label={t("descriptionLabel")}
variant="outlined"
rows={2}
/>
<TextField
{...getFieldHelpers("description")}
multiline
disabled={isSubmitting}
fullWidth
label={t("descriptionLabel")}
variant="outlined"
rows={2}
/>
<LazyIconField
{...getFieldHelpers("icon")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
fullWidth
label={t("form.fields.icon")}
variant="outlined"
onPickEmoji={(value) => form.setFieldValue("icon", value)}
/>
<LazyIconField
{...getFieldHelpers("icon")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
fullWidth
label={t("iconLabel")}
variant="outlined"
onPickEmoji={(value) => form.setFieldValue("icon", value)}
/>
</FormFields>
</FormSection>
<FormSection
title={t("schedule.title")}
description={t("schedule.description")}
>
<TextField
{...getFieldHelpers(
"default_ttl_ms",
@@ -155,30 +181,59 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
variant="outlined"
type="number"
/>
</FormSection>
<Box display="flex">
<div>
{/*"getFieldHelpers" can't be used as it requires "helperText" property to be present.*/}
<FormSection
title={t("operations.title")}
description={t("operations.description")}
>
<label htmlFor="allow_user_cancel_workspace_jobs">
<Stack direction="row" spacing={1}>
<Checkbox
color="primary"
id="allow_user_cancel_workspace_jobs"
name="allow_user_cancel_workspace_jobs"
disabled={isSubmitting}
checked={form.values.allow_user_cancel_workspace_jobs}
onChange={form.handleChange}
/>
</div>
<Box>
<Typography variant="h6" style={{ fontSize: 14 }}>
{t("allowUserCancelWorkspaceJobsLabel")}
</Typography>
<Typography variant="caption" color="textSecondary">
{t("allowUserCancelWorkspaceJobsNotice")}
</Typography>
</Box>
</Box>
</Stack>
<Stack direction="column" spacing={0.5}>
<Stack
direction="row"
alignItems="center"
spacing={0.5}
className={styles.optionText}
>
{t("allowUserCancelWorkspaceJobsLabel")}
<HelpTooltip>
<HelpTooltipText>
{t("allowUserCancelWorkspaceJobsNotice")}
</HelpTooltipText>
</HelpTooltip>
</Stack>
<span className={styles.optionHelperText}>
{t("allowUsersCancelHelperText")}
</span>
</Stack>
</Stack>
</label>
</FormSection>
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
</form>
</HorizontalForm>
)
}
const useStyles = makeStyles((theme) => ({
optionText: {
fontSize: theme.spacing(2),
color: theme.palette.text.primary,
},
optionHelperText: {
fontSize: theme.spacing(1.5),
color: theme.palette.text.secondary,
},
}))
@@ -88,16 +88,6 @@ describe("TemplateSettingsPage", () => {
expect(element).toBeDefined()
})
it("allows an admin to delete a template", async () => {
const { t } = i18next
await renderTemplateSettingsPage()
const deleteCta = t("dangerZone.deleteCta", {
ns: "templateSettingsPage",
})
const deleteButton = await screen.findByText(deleteCta)
expect(deleteButton).toBeDefined()
})
it("succeeds", async () => {
await renderTemplateSettingsPage()
@@ -26,7 +26,6 @@ export const TemplateSettingsPage: FC = () => {
templateSettings: template,
saveTemplateSettingsError,
getTemplateError,
deleteTemplateError,
} = state.context
return (
@@ -40,7 +39,6 @@ export const TemplateSettingsPage: FC = () => {
errors={{
getTemplateError,
saveTemplateSettingsError,
deleteTemplateError,
}}
onCancel={() => {
navigate(`/templates/${templateName}`)
@@ -48,14 +46,6 @@ export const TemplateSettingsPage: FC = () => {
onSubmit={(templateSettings) => {
send({ type: "SAVE", templateSettings })
}}
onDelete={() => {
send("DELETE")
}}
onConfirmDelete={() => send("CONFIRM_DELETE")}
onCancelDelete={() => send("CANCEL_DELETE")}
isConfirmingDelete={state.matches("confirmingDelete")}
isDeleting={state.matches("deleting")}
isDeleted={state.matches("deleted")}
/>
</>
)
@@ -1,32 +1,21 @@
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { FullPageForm } from "components/FullPageForm/FullPageForm"
import { Loader } from "components/Loader/Loader"
import { ComponentProps, FC } from "react"
import { TemplateSettingsForm } from "./TemplateSettingsForm"
import { Stack } from "components/Stack/Stack"
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
import { makeStyles } from "@material-ui/core/styles"
import { colors } from "theme/colors"
import Button from "@material-ui/core/Button"
import { useTranslation } from "react-i18next"
import { Navigate } from "react-router-dom"
import { FullPageHorizontalForm } from "components/FullPageForm/FullPageHorizontalForm"
export interface TemplateSettingsPageViewProps {
template?: Template
onSubmit: (data: UpdateTemplateMeta) => void
onCancel: () => void
onDelete: () => void
onConfirmDelete: () => void
onCancelDelete: () => void
isConfirmingDelete: boolean
isDeleting: boolean
isDeleted: boolean
isSubmitting: boolean
errors?: {
getTemplateError?: unknown
saveTemplateSettingsError?: unknown
deleteTemplateError?: unknown
}
initialTouched?: ComponentProps<typeof TemplateSettingsForm>["initialTouched"]
}
@@ -35,12 +24,6 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
template,
onCancel,
onSubmit,
onDelete,
onConfirmDelete,
onCancelDelete,
isConfirmingDelete,
isDeleting,
isDeleted,
isSubmitting,
errors = {},
initialTouched,
@@ -49,22 +32,13 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
const isLoading = !template && !errors.getTemplateError
const { t } = useTranslation("templateSettingsPage")
if (isDeleted) {
return <Navigate to="/templates" />
}
return (
<FullPageForm title={t("title")}>
<FullPageHorizontalForm title={t("title")} onCancel={onCancel}>
{Boolean(errors.getTemplateError) && (
<Stack className={classes.errorContainer}>
<AlertBanner severity="error" error={errors.getTemplateError} />
</Stack>
)}
{Boolean(errors.deleteTemplateError) && (
<Stack className={classes.errorContainer}>
<AlertBanner severity="error" error={errors.deleteTemplateError} />
</Stack>
)}
{isLoading && <Loader />}
{template && (
<>
@@ -76,39 +50,9 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
onCancel={onCancel}
error={errors.saveTemplateSettingsError}
/>
<Stack className={classes.dangerContainer}>
<div className={classes.dangerHeader}>
{t("dangerZone.dangerZoneHeader")}
</div>
<Stack className={classes.dangerBorder}>
<Stack spacing={0}>
<p className={classes.deleteTemplateHeader}>
{t("dangerZone.deleteTemplateHeader")}
</p>
<span>{t("dangerZone.deleteTemplateCaption")}</span>
</Stack>
<Button
className={classes.deleteButton}
onClick={onDelete}
aria-label={t("dangerZone.deleteCta")}
>
{t("dangerZone.deleteCta")}
</Button>
</Stack>
</Stack>
<DeleteDialog
isOpen={isConfirmingDelete}
confirmLoading={isDeleting}
onConfirm={onConfirmDelete}
onCancel={onCancelDelete}
entity="template"
name={template.name}
/>
</>
)}
</FullPageForm>
</FullPageHorizontalForm>
)
}
@@ -116,27 +60,4 @@ const useStyles = makeStyles((theme) => ({
errorContainer: {
marginBottom: theme.spacing(2),
},
dangerContainer: {
marginTop: theme.spacing(4),
},
dangerHeader: {
fontSize: theme.typography.h5.fontSize,
color: theme.palette.text.secondary,
},
dangerBorder: {
border: `1px solid ${colors.red[13]}`,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(2),
"& p": {
marginTop: "0px",
},
},
deleteTemplateHeader: {
fontSize: theme.typography.h6.fontSize,
fontWeight: "bold",
},
deleteButton: {
color: colors.red[8],
},
}))