mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
Template delete button/kira pilot (#4992)
* removed button * ripped out delete dialog * fixed tests * added error message back * redirecting after success
This commit is contained in:
@ -5,10 +5,6 @@ import { makeStyles } from "@material-ui/core/styles"
|
||||
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
|
||||
import SettingsOutlined from "@material-ui/icons/SettingsOutlined"
|
||||
import { useMachine, useSelector } from "@xstate/react"
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
|
||||
import { DeleteButton } from "components/DropdownButton/ActionCtas"
|
||||
import { DropdownButton } from "components/DropdownButton/DropdownButton"
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderSubtitle,
|
||||
@ -22,12 +18,7 @@ import {
|
||||
Suspense,
|
||||
useContext,
|
||||
} from "react"
|
||||
import {
|
||||
Link as RouterLink,
|
||||
Navigate,
|
||||
NavLink,
|
||||
useParams,
|
||||
} from "react-router-dom"
|
||||
import { Link as RouterLink, NavLink, useParams } from "react-router-dom"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { firstLetter } from "util/firstLetter"
|
||||
import { selectPermissions } from "xServices/auth/authSelectors"
|
||||
@ -36,8 +27,8 @@ import {
|
||||
TemplateContext,
|
||||
templateMachine,
|
||||
} from "xServices/template/templateXService"
|
||||
import { Margins } from "../../components/Margins/Margins"
|
||||
import { Stack } from "../../components/Stack/Stack"
|
||||
import { Margins } from "components/Margins/Margins"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { Permissions } from "xServices/auth/authXService"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
|
||||
@ -76,11 +67,40 @@ 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<PropsWithChildren> = ({ children }) => {
|
||||
const styles = useStyles()
|
||||
const organizationId = useOrganizationId()
|
||||
const templateName = useTemplateName()
|
||||
const [templateState, templateSend] = useMachine(templateMachine, {
|
||||
const [templateState, _] = useMachine(templateMachine, {
|
||||
context: {
|
||||
templateName,
|
||||
organizationId,
|
||||
@ -103,30 +123,20 @@ export const TemplateLayout: FC<PropsWithChildren> = ({ children }) => {
|
||||
!templateDAUs ||
|
||||
!templatePermissions
|
||||
|
||||
if (templateState.matches("deleted")) {
|
||||
return <Navigate to="/templates" />
|
||||
}
|
||||
|
||||
const hasIcon = template && template.icon && template.icon !== ""
|
||||
|
||||
const createWorkspaceButton = (className?: string) => (
|
||||
<Link
|
||||
underline="none"
|
||||
component={RouterLink}
|
||||
to={`/templates/${templateName}/workspace`}
|
||||
>
|
||||
<Button
|
||||
className={className ?? ""}
|
||||
startIcon={<AddCircleOutline />}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{Language.createButton}
|
||||
</Button>
|
||||
</Link>
|
||||
)
|
||||
const generatePageHeaderActions = (): JSX.Element[] => {
|
||||
const pageActions: JSX.Element[] = []
|
||||
|
||||
const handleDeleteTemplate = () => {
|
||||
templateSend("DELETE")
|
||||
if (!isLoading && templatePermissions.canUpdateTemplate) {
|
||||
pageActions.push(<TemplateSettingsButton templateName={templateName} />)
|
||||
}
|
||||
|
||||
if (!isLoading) {
|
||||
pageActions.push(<CreateWorkspaceButton templateName={templateName} />)
|
||||
}
|
||||
|
||||
return pageActions
|
||||
}
|
||||
|
||||
return (
|
||||
@ -134,36 +144,11 @@ export const TemplateLayout: FC<PropsWithChildren> = ({ children }) => {
|
||||
<Margins>
|
||||
<PageHeader
|
||||
actions={
|
||||
isLoading ? undefined : (
|
||||
<ChooseOne>
|
||||
<Cond condition={templatePermissions.canUpdateTemplate}>
|
||||
<Link
|
||||
underline="none"
|
||||
component={RouterLink}
|
||||
to={`/templates/${template.name}/settings`}
|
||||
>
|
||||
<Button variant="outlined" startIcon={<SettingsOutlined />}>
|
||||
{Language.settingsButton}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<DropdownButton
|
||||
primaryAction={createWorkspaceButton(styles.actionButton)}
|
||||
secondaryActions={[
|
||||
{
|
||||
action: "delete",
|
||||
button: (
|
||||
<DeleteButton handleAction={handleDeleteTemplate} />
|
||||
),
|
||||
},
|
||||
]}
|
||||
canCancel={false}
|
||||
/>
|
||||
</Cond>
|
||||
|
||||
<Cond>{createWorkspaceButton()}</Cond>
|
||||
</ChooseOne>
|
||||
)
|
||||
<>
|
||||
{generatePageHeaderActions().map((action, i) => (
|
||||
<div key={i}>{action}</div>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Stack direction="row" spacing={3} className={styles.pageTitle}>
|
||||
@ -234,31 +219,12 @@ export const TemplateLayout: FC<PropsWithChildren> = ({ children }) => {
|
||||
<Suspense fallback={<Loader />}>{children}</Suspense>
|
||||
</TemplateLayoutContext.Provider>
|
||||
</Margins>
|
||||
|
||||
{!isLoading && (
|
||||
<DeleteDialog
|
||||
isOpen={templateState.matches("confirmingDelete")}
|
||||
confirmLoading={templateState.matches("deleting")}
|
||||
onConfirm={() => {
|
||||
templateSend("CONFIRM_DELETE")
|
||||
}}
|
||||
onCancel={() => {
|
||||
templateSend("CANCEL_DELETE")
|
||||
}}
|
||||
entity="template"
|
||||
name={template.name}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
actionButton: {
|
||||
border: "none",
|
||||
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
|
||||
},
|
||||
pageTitle: {
|
||||
alignItems: "center",
|
||||
},
|
||||
|
@ -1,4 +1,13 @@
|
||||
{
|
||||
"deleteSuccess": "Template successfully deleted.",
|
||||
"createdVersion": "created the version"
|
||||
"createdVersion": "created the version",
|
||||
"templateSettings": {
|
||||
"title": "Template settings",
|
||||
"dangerZone": {
|
||||
"dangerZoneHeader": "Danger Zone",
|
||||
"deleteTemplateHeader": "Delete this template",
|
||||
"deleteTemplateCaption": "Once you delete a template, there is no going back. Please be certain.",
|
||||
"deleteCta": "Delete Template"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fireEvent, screen } from "@testing-library/react"
|
||||
import { screen } from "@testing-library/react"
|
||||
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout"
|
||||
import { rest } from "msw"
|
||||
import { ResizeObserver } from "resize-observer"
|
||||
@ -42,13 +42,6 @@ describe("TemplateSummaryPage", () => {
|
||||
screen.getByText(MockWorkspaceResource.name)
|
||||
screen.queryAllByText(`${MockTemplateVersion.name}`).length
|
||||
})
|
||||
it("allows an admin to delete a template", async () => {
|
||||
renderPage()
|
||||
const dropdownButton = await screen.findByLabelText("open-dropdown")
|
||||
fireEvent.click(dropdownButton)
|
||||
const deleteButton = await screen.findByText("Delete")
|
||||
expect(deleteButton).toBeDefined()
|
||||
})
|
||||
it("does not allow a member to delete a template", () => {
|
||||
// get member-level permissions
|
||||
server.use(
|
||||
|
@ -12,7 +12,6 @@ export const TemplateSummaryPage: FC = () => {
|
||||
activeTemplateVersion,
|
||||
templateResources,
|
||||
templateVersions,
|
||||
deleteTemplateError,
|
||||
templateDAUs,
|
||||
} = context
|
||||
|
||||
@ -31,7 +30,6 @@ export const TemplateSummaryPage: FC = () => {
|
||||
templateResources={templateResources}
|
||||
templateVersions={templateVersions}
|
||||
templateDAUs={templateDAUs}
|
||||
deleteTemplateError={deleteTemplateError}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
TemplateVersion,
|
||||
WorkspaceResource,
|
||||
} from "api/typesGenerated"
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { MemoizedMarkdown } from "components/Markdown/Markdown"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable"
|
||||
@ -21,7 +20,6 @@ export interface TemplateSummaryPageViewProps {
|
||||
templateResources: WorkspaceResource[]
|
||||
templateVersions?: TemplateVersion[]
|
||||
templateDAUs?: TemplateDAUsResponse
|
||||
deleteTemplateError: Error | unknown
|
||||
}
|
||||
|
||||
export const TemplateSummaryPageView: FC<
|
||||
@ -32,15 +30,10 @@ export const TemplateSummaryPageView: FC<
|
||||
templateResources,
|
||||
templateVersions,
|
||||
templateDAUs,
|
||||
deleteTemplateError,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const readme = frontMatter(activeTemplateVersion.readme)
|
||||
|
||||
const deleteError = deleteTemplateError ? (
|
||||
<AlertBanner severity="error" error={deleteTemplateError} dismissible />
|
||||
) : null
|
||||
|
||||
const getStartedResources = (resources: WorkspaceResource[]) => {
|
||||
return resources.filter(
|
||||
(resource) => resource.workspace_transition === "start",
|
||||
@ -49,7 +42,6 @@ export const TemplateSummaryPageView: FC<
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
{deleteError}
|
||||
<TemplateStats
|
||||
template={template}
|
||||
activeVersion={activeTemplateVersion}
|
||||
|
@ -5,7 +5,6 @@ import InputAdornment from "@material-ui/core/InputAdornment"
|
||||
import Popover from "@material-ui/core/Popover"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import Typography from "@material-ui/core/Typography"
|
||||
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
|
||||
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows"
|
||||
import { FormFooter } from "components/FormFooter/FormFooter"
|
||||
@ -188,9 +187,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
there are no validation errors for that field, display helper text.
|
||||
We do not use the MUI helper-text prop because it overrides the validation error */}
|
||||
{form.values.default_ttl_ms && !form.errors.default_ttl_ms && (
|
||||
<Typography variant="subtitle2">
|
||||
{Language.ttlHelperText(form.values.default_ttl_ms)}
|
||||
</Typography>
|
||||
<span>{Language.ttlHelperText(form.values.default_ttl_ms)}</span>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
validationSchema,
|
||||
} from "./TemplateSettingsForm"
|
||||
import { TemplateSettingsPage } from "./TemplateSettingsPage"
|
||||
import { Language as ViewLanguage } from "./TemplateSettingsPageView"
|
||||
import i18next from "i18next"
|
||||
|
||||
const renderTemplateSettingsPage = async () => {
|
||||
const renderResult = renderWithAuth(<TemplateSettingsPage />, {
|
||||
@ -61,11 +61,25 @@ const fillAndSubmitForm = async ({
|
||||
|
||||
describe("TemplateSettingsPage", () => {
|
||||
it("renders", async () => {
|
||||
const { t } = i18next
|
||||
const pageTitle = t("templateSettings.title", {
|
||||
ns: "templatePage",
|
||||
})
|
||||
await renderTemplateSettingsPage()
|
||||
const element = await screen.findByText(ViewLanguage.title)
|
||||
const element = await screen.findByText(pageTitle)
|
||||
expect(element).toBeDefined()
|
||||
})
|
||||
|
||||
it("allows an admin to delete a template", async () => {
|
||||
const { t } = i18next
|
||||
await renderTemplateSettingsPage()
|
||||
const deleteCta = t("templateSettings.dangerZone.deleteCta", {
|
||||
ns: "templatePage",
|
||||
})
|
||||
const deleteButton = await screen.findByText(deleteCta)
|
||||
expect(deleteButton).toBeDefined()
|
||||
})
|
||||
|
||||
it("succeeds", async () => {
|
||||
await renderTemplateSettingsPage()
|
||||
|
||||
|
@ -28,6 +28,7 @@ export const TemplateSettingsPage: FC = () => {
|
||||
templateSettings: template,
|
||||
saveTemplateSettingsError,
|
||||
getTemplateError,
|
||||
deleteTemplateError,
|
||||
} = state.context
|
||||
|
||||
return (
|
||||
@ -41,6 +42,7 @@ export const TemplateSettingsPage: FC = () => {
|
||||
errors={{
|
||||
getTemplateError,
|
||||
saveTemplateSettingsError,
|
||||
deleteTemplateError,
|
||||
}}
|
||||
onCancel={() => {
|
||||
navigate(`/templates/${templateName}`)
|
||||
@ -48,6 +50,14 @@ 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")}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -4,19 +4,29 @@ import { FullPageForm } from "components/FullPageForm/FullPageForm"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import { ComponentProps, FC } from "react"
|
||||
import { TemplateSettingsForm } from "./TemplateSettingsForm"
|
||||
|
||||
export const Language = {
|
||||
title: "Template settings",
|
||||
}
|
||||
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"
|
||||
|
||||
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"]
|
||||
}
|
||||
@ -25,28 +35,110 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
|
||||
template,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
onConfirmDelete,
|
||||
onCancelDelete,
|
||||
isConfirmingDelete,
|
||||
isDeleting,
|
||||
isDeleted,
|
||||
isSubmitting,
|
||||
errors = {},
|
||||
initialTouched,
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
const isLoading = !template && !errors.getTemplateError
|
||||
const { t } = useTranslation("templatePage")
|
||||
|
||||
if (isDeleted) {
|
||||
return <Navigate to="/templates" />
|
||||
}
|
||||
|
||||
return (
|
||||
<FullPageForm title={Language.title} onCancel={onCancel}>
|
||||
<FullPageForm title={t("templateSettings.title")} onCancel={onCancel}>
|
||||
{Boolean(errors.getTemplateError) && (
|
||||
<AlertBanner severity="error" error={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 && (
|
||||
<TemplateSettingsForm
|
||||
initialTouched={initialTouched}
|
||||
isSubmitting={isSubmitting}
|
||||
template={template}
|
||||
onSubmit={onSubmit}
|
||||
onCancel={onCancel}
|
||||
error={errors.saveTemplateSettingsError}
|
||||
/>
|
||||
<>
|
||||
<TemplateSettingsForm
|
||||
initialTouched={initialTouched}
|
||||
isSubmitting={isSubmitting}
|
||||
template={template}
|
||||
onSubmit={onSubmit}
|
||||
onCancel={onCancel}
|
||||
error={errors.saveTemplateSettingsError}
|
||||
/>
|
||||
<Stack className={classes.dangerContainer}>
|
||||
<div className={classes.dangerHeader}>
|
||||
{t("templateSettings.dangerZone.dangerZoneHeader")}
|
||||
</div>
|
||||
|
||||
<Stack className={classes.dangerBorder}>
|
||||
<Stack spacing={0}>
|
||||
<p className={classes.deleteTemplateHeader}>
|
||||
{t("templateSettings.dangerZone.deleteTemplateHeader")}
|
||||
</p>
|
||||
<span>
|
||||
{t("templateSettings.dangerZone.deleteTemplateCaption")}
|
||||
</span>
|
||||
</Stack>
|
||||
<Button
|
||||
className={classes.deleteButton}
|
||||
onClick={onDelete}
|
||||
aria-label={t("templateSettings.dangerZone.deleteCta")}
|
||||
>
|
||||
{t("templateSettings.dangerZone.deleteCta")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<DeleteDialog
|
||||
isOpen={isConfirmingDelete}
|
||||
confirmLoading={isDeleting}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={onCancelDelete}
|
||||
entity="template"
|
||||
name={template.name}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FullPageForm>
|
||||
)
|
||||
}
|
||||
|
||||
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],
|
||||
},
|
||||
}))
|
||||
|
@ -1,22 +1,19 @@
|
||||
import { displaySuccess } from "components/GlobalSnackbar/utils"
|
||||
import { t } from "i18next"
|
||||
import { assign, createMachine } from "xstate"
|
||||
import {
|
||||
checkAuthorization,
|
||||
deleteTemplate,
|
||||
getTemplateByName,
|
||||
getTemplateDAUs,
|
||||
getTemplateVersion,
|
||||
getTemplateVersionResources,
|
||||
getTemplateVersions,
|
||||
} from "../../api/api"
|
||||
} from "api/api"
|
||||
import {
|
||||
AuthorizationResponse,
|
||||
Template,
|
||||
TemplateDAUsResponse,
|
||||
TemplateVersion,
|
||||
WorkspaceResource,
|
||||
} from "../../api/typesGenerated"
|
||||
} from "api/typesGenerated"
|
||||
|
||||
export interface TemplateContext {
|
||||
organizationId: string
|
||||
@ -27,15 +24,9 @@ export interface TemplateContext {
|
||||
templateVersions?: TemplateVersion[]
|
||||
templateDAUs?: TemplateDAUsResponse
|
||||
permissions?: AuthorizationResponse
|
||||
deleteTemplateError?: Error | unknown
|
||||
getTemplateError?: Error | unknown
|
||||
}
|
||||
|
||||
type TemplateEvent =
|
||||
| { type: "DELETE" }
|
||||
| { type: "CONFIRM_DELETE" }
|
||||
| { type: "CANCEL_DELETE" }
|
||||
|
||||
const getPermissionsToCheck = (templateId: string) => ({
|
||||
canUpdateTemplate: {
|
||||
object: {
|
||||
@ -55,7 +46,6 @@ export const templateMachine =
|
||||
tsTypes: {} as import("./templateXService.typegen").Typegen0,
|
||||
schema: {
|
||||
context: {} as TemplateContext,
|
||||
events: {} as TemplateEvent,
|
||||
services: {} as {
|
||||
getTemplate: {
|
||||
data: Template
|
||||
@ -69,9 +59,6 @@ export const templateMachine =
|
||||
getTemplateVersions: {
|
||||
data: TemplateVersion[]
|
||||
}
|
||||
deleteTemplate: {
|
||||
data: Template
|
||||
}
|
||||
getTemplateDAUs: {
|
||||
data: TemplateDAUsResponse
|
||||
}
|
||||
@ -201,11 +188,6 @@ export const templateMachine =
|
||||
},
|
||||
},
|
||||
loaded: {
|
||||
on: {
|
||||
DELETE: {
|
||||
target: "confirmingDelete",
|
||||
},
|
||||
},
|
||||
initial: "waiting",
|
||||
states: {
|
||||
refreshingTemplate: {
|
||||
@ -222,38 +204,6 @@ export const templateMachine =
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmingDelete: {
|
||||
on: {
|
||||
CONFIRM_DELETE: {
|
||||
target: "deleting",
|
||||
},
|
||||
CANCEL_DELETE: {
|
||||
target: "loaded",
|
||||
},
|
||||
},
|
||||
},
|
||||
deleting: {
|
||||
entry: "clearDeleteTemplateError",
|
||||
invoke: {
|
||||
src: "deleteTemplate",
|
||||
id: "deleteTemplate",
|
||||
onDone: [
|
||||
{
|
||||
target: "deleted",
|
||||
actions: "displayDeleteSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignDeleteTemplateError",
|
||||
target: "loaded",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
deleted: {
|
||||
type: "final",
|
||||
},
|
||||
error: {
|
||||
type: "final",
|
||||
},
|
||||
@ -284,12 +234,6 @@ export const templateMachine =
|
||||
|
||||
return getTemplateVersions(ctx.template.id)
|
||||
},
|
||||
deleteTemplate: (ctx) => {
|
||||
if (!ctx.template) {
|
||||
throw new Error("Template not loaded")
|
||||
}
|
||||
return deleteTemplate(ctx.template.id)
|
||||
},
|
||||
getTemplateDAUs: (ctx) => {
|
||||
if (!ctx.template) {
|
||||
throw new Error("Template not loaded")
|
||||
@ -327,14 +271,6 @@ export const templateMachine =
|
||||
assignPermissions: assign({
|
||||
permissions: (_, event) => event.data,
|
||||
}),
|
||||
assignDeleteTemplateError: assign({
|
||||
deleteTemplateError: (_, event) => event.data,
|
||||
}),
|
||||
clearDeleteTemplateError: assign({
|
||||
deleteTemplateError: (_) => undefined,
|
||||
}),
|
||||
displayDeleteSuccess: () =>
|
||||
displaySuccess(t("deleteSuccess", { ns: "templatePage" })),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { getTemplateByName, updateTemplateMeta } from "api/api"
|
||||
import { getTemplateByName, updateTemplateMeta, deleteTemplate } from "api/api"
|
||||
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
|
||||
import { createMachine } from "xstate"
|
||||
import { assign } from "xstate/lib/actions"
|
||||
import { displaySuccess } from "components/GlobalSnackbar/utils"
|
||||
import { t } from "i18next"
|
||||
|
||||
export const templateSettingsMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QBcwFsAOAbAhqgymMsgJYB2UsAdFgPY4TlQDEEtZYV5AbrQNadUmXASKkK1OgyYIetAMZ4S7ANoAGALqJQGWrBKl22kAA9EANgCMVAKwB2AByWATJbWWAnABYv55w-MAGhAAT0RnAGZnKgibGw9nD3M1JJsHNQiAX0zgoWw8MEJiJmpIAyZmfABBADUAUWNdfUMyYzMESx9bSK8IhwdncwcbF2dgsIRejypzX2dXf09zCLUbbNz0fNFiiSpYHG4Ktg4uMl4BKjyRQrESvYOZOUUW9S0kECbyo3f25KoHOxeDwODx9EYeNTzcYWGxqKgeGw+SJ2cx+VZebI5EBkWgQODGK4FIriSg0eiMCiNPRfVo-RBeMahRAQqhqNnuWERFFeOyedYgQnbEmlRgkqnNZS00DtSwRcz-EY2PzeeYOLk2aEIWJ2WwOIEBNIeBG8-mCm47Un7Q6U96fFptenWRJDIZy+y9AFBJkIEbRbxeWUBRwIyx2U2ba7Eu5WyDimkOhAI2yxSx+foMpWWTV2RLJgIrPoA4bh4RE24SOP2ukdHXOgJq8zuvoozWWBys9nzSydNt2NQYzFAA */
|
||||
@ -17,6 +19,7 @@ export const templateSettingsMachine =
|
||||
templateSettings?: Template
|
||||
getTemplateError?: unknown
|
||||
saveTemplateSettingsError?: unknown
|
||||
deleteTemplateError?: Error | unknown
|
||||
}
|
||||
services: {
|
||||
getTemplateSettings: {
|
||||
@ -26,7 +29,11 @@ export const templateSettingsMachine =
|
||||
data: Template
|
||||
}
|
||||
}
|
||||
events: { type: "SAVE"; templateSettings: UpdateTemplateMeta }
|
||||
events:
|
||||
| { type: "SAVE"; templateSettings: UpdateTemplateMeta }
|
||||
| { type: "DELETE" }
|
||||
| { type: "CONFIRM_DELETE" }
|
||||
| { type: "CANCEL_DELETE" }
|
||||
},
|
||||
initial: "loading",
|
||||
states: {
|
||||
@ -50,8 +57,43 @@ export const templateSettingsMachine =
|
||||
SAVE: {
|
||||
target: "saving",
|
||||
},
|
||||
DELETE: {
|
||||
target: "confirmingDelete",
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmingDelete: {
|
||||
on: {
|
||||
CONFIRM_DELETE: {
|
||||
target: "deleting",
|
||||
},
|
||||
CANCEL_DELETE: {
|
||||
target: "editing",
|
||||
},
|
||||
},
|
||||
},
|
||||
deleting: {
|
||||
entry: "clearDeleteTemplateError",
|
||||
invoke: {
|
||||
src: "deleteTemplate",
|
||||
id: "deleteTemplate",
|
||||
onDone: [
|
||||
{
|
||||
target: "deleted",
|
||||
actions: "displayDeleteSuccess",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignDeleteTemplateError",
|
||||
target: "editing",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
deleted: {
|
||||
type: "final",
|
||||
},
|
||||
saving: {
|
||||
invoke: {
|
||||
src: "saveTemplateSettings",
|
||||
@ -94,6 +136,12 @@ export const templateSettingsMachine =
|
||||
|
||||
return updateTemplateMeta(templateSettings.id, newTemplateSettings)
|
||||
},
|
||||
deleteTemplate: (ctx) => {
|
||||
if (!ctx.templateSettings) {
|
||||
throw new Error("Template not loaded")
|
||||
}
|
||||
return deleteTemplate(ctx.templateSettings.id)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
assignTemplateSettings: assign({
|
||||
@ -105,6 +153,14 @@ export const templateSettingsMachine =
|
||||
assignSaveTemplateSettingsError: assign({
|
||||
saveTemplateSettingsError: (_, { data }) => data,
|
||||
}),
|
||||
assignDeleteTemplateError: assign({
|
||||
deleteTemplateError: (_, event) => event.data,
|
||||
}),
|
||||
clearDeleteTemplateError: assign({
|
||||
deleteTemplateError: (_) => undefined,
|
||||
}),
|
||||
displayDeleteSuccess: () =>
|
||||
displaySuccess(t("deleteSuccess", { ns: "templatePage" })),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
Reference in New Issue
Block a user