Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: have user type name of thing to delete for extra safety#4080

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
presleyp merged 12 commits intomainfrompresleyp/destructive-dialog
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,6 +83,7 @@ export const ConfirmDialog: React.FC<React.PropsWithChildren<ConfirmDialogProps>
confirmLoading,
confirmText,
description,
disabled = false,
hideCancel,
onClose,
onConfirm,
Expand DownExpand Up@@ -122,6 +123,7 @@ export const ConfirmDialog: React.FC<React.PropsWithChildren<ConfirmDialogProps>
confirmDialog
confirmLoading={confirmLoading}
confirmText={confirmText || defaults.confirmText}
disabled={disabled}
onCancel={!hideCancel ? onClose : undefined}
onConfirm={onConfirm || onClose}
type={type}
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,12 +15,14 @@ export default {
control: "boolean",
defaultValue: true,
},
title: {
defaultValue: "Delete Something",
entity: {
defaultValue: "foo",
},
description: {
defaultValue:
"This is irreversible. To confirm, type the name of the thing you want to delete.",
name: {
defaultValue: "MyFoo",
},
info: {
defaultValue: "Here's some info about the foo so you know you're deleting the right one.",
},
},
} as ComponentMeta<typeof DeleteDialog>
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
import { screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import i18next from "i18next"
import { render } from "testHelpers/renderHelpers"
import { DeleteDialog } from "./DeleteDialog"

describe("DeleteDialog", () => {
it("disables confirm button when the text field is empty", () => {
render(
<DeleteDialog
isOpen
onConfirm={jest.fn()}
onCancel={jest.fn()}
entity="template"
name="MyTemplate"
/>,
)
const confirmButton = screen.getByRole("button", { name: "Delete" })
expect(confirmButton).toBeDisabled()
})

it("disables confirm button when the text field is filled incorrectly", async () => {
const { t } = i18next
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

❤️

render(
<DeleteDialog
isOpen
onConfirm={jest.fn()}
onCancel={jest.fn()}
entity="template"
name="MyTemplate"
/>,
)
const labelText = t("deleteDialog.confirmLabel", { ns: "common", entity: "template" })
const textField = screen.getByLabelText(labelText)
await userEvent.type(textField, "MyTemplateWrong")
const confirmButton = screen.getByRole("button", { name: "Delete" })
expect(confirmButton).toBeDisabled()
})

it("enables confirm button when the text field is filled correctly", async () => {
const { t } = i18next
render(
<DeleteDialog
isOpen
onConfirm={jest.fn()}
onCancel={jest.fn()}
entity="template"
name="MyTemplate"
/>,
)
const labelText = t("deleteDialog.confirmLabel", { ns: "common", entity: "template" })
const textField = screen.getByLabelText(labelText)
await userEvent.type(textField, "MyTemplate")
const confirmButton = screen.getByRole("button", { name: "Delete" })
expect(confirmButton).not.toBeDisabled()
})
})
83 changes: 66 additions & 17 deletionssite/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,80 @@
import React, { ReactNode } from "react"
import FormHelperText from "@material-ui/core/FormHelperText"
import makeStyles from "@material-ui/core/styles/makeStyles"
import TextField from "@material-ui/core/TextField"
import Typography from "@material-ui/core/Typography"
import { Maybe } from "components/Conditionals/Maybe"
import { Stack } from "components/Stack/Stack"
import React, { ChangeEvent, useState } from "react"
import { useTranslation } from "react-i18next"
import { ConfirmDialog } from "../ConfirmDialog/ConfirmDialog"

export interface DeleteDialogProps {
isOpen: boolean
onConfirm: () => void
onCancel: () => void
title: string
description: string | ReactNode
entity: string
name: string
info?: string
confirmLoading?: boolean
}

export const DeleteDialog: React.FC<React.PropsWithChildren<DeleteDialogProps>> = ({
isOpen,
onCancel,
onConfirm,
title,
description,
entity,
info,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What kind of info is this? A description?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yeah, it's intended to help the user identify the entity to delete, so they don't delete the wrong one by accident.@bpmct suggested this on the basis of an article about making the wrong repo private and thus losing all their github stars.

Kira-Pilot reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It's the orange text here. Note storybook isn't showing buttons; that's been going on for a while and so far doesn't mean the buttons are actually gone.
Screen Shot 2022-09-20 at 4 52 28 PM

Kira-Pilot reacted with thumbs up emoji
name,
confirmLoading,
}) => (
<ConfirmDialog
type="delete"
hideCancel={false}
open={isOpen}
title={title}
onConfirm={onConfirm}
onClose={onCancel}
description={description}
confirmLoading={confirmLoading}
/>
)
}) => {
const styles = useStyles()
const { t } = useTranslation("common")
const [nameValue, setNameValue] = useState("")
const confirmed = name === nameValue
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setNameValue(event.target.value)
}

const content = (
<>
<Typography>{t("deleteDialog.intro", { entity })}</Typography>
<Maybe condition={info !== undefined}>
<Typography className={styles.warning}>{info}</Typography>
</Maybe>
<Typography>{t("deleteDialog.confirm", { entity })}</Typography>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I love how we're not passing whole strings in so that the language will stay consistent.

<Stack spacing={1}>
<TextField
name="confirmation"
id="confirmation"
placeholder={name}
value={nameValue}
onChange={handleChange}
label={t("deleteDialog.confirmLabel", { entity })}
/>
<Maybe condition={nameValue.length > 0 && !confirmed}>
<FormHelperText error>{t("deleteDialog.incorrectName", { entity })}</FormHelperText>
</Maybe>
</Stack>
</>
)

return (
<ConfirmDialog
type="delete"
hideCancel={false}
open={isOpen}
title={t("deleteDialog.title", { entity })}
onConfirm={onConfirm}
onClose={onCancel}
description={content}
confirmLoading={confirmLoading}
disabled={!confirmed}
/>
)
}

const useStyles = makeStyles((theme) => ({
warning: {
color: theme.palette.warning.light,
},
}))
4 changes: 4 additions & 0 deletionssite/src/components/Dialogs/Dialog.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -72,6 +72,8 @@ export interface DialogActionButtonsProps {
confirmLoading?: boolean
/** Whether or not this is a confirm dialog */
confirmDialog?: boolean
/** Whether or not the submit button is disabled */
disabled?: boolean
/** Called when cancel is clicked */
onCancel?: () => void
/** Called when confirm is clicked */
Expand All@@ -94,6 +96,7 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
confirmText = "Confirm",
confirmLoading = false,
confirmDialog,
disabled = false,
onCancel,
onConfirm,
type = "info",
Expand DownExpand Up@@ -122,6 +125,7 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
onClick={onConfirm}
color={typeToColor(type)}
loading={confirmLoading}
disabled={disabled}
type="submit"
className={combineClasses({
[styles.dialogButton]: true,
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,7 +45,7 @@ export const EnterpriseSnackbar: FC<React.PropsWithChildren<EnterpriseSnackbarPr
<div className={styles.actionWrapper}>
{action}
<IconButton onClick={onClose} className={styles.iconButton}>
<CloseIcon className={styles.closeIcon} />
<CloseIcon className={styles.closeIcon}aria-label="close"/>
</IconButton>
</div>
}
Expand Down
7 changes: 7 additions & 0 deletionssite/src/i18n/en/common.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,5 +11,12 @@
"canceled": "Canceled action",
"failed": "Failed",
"queued": "Queued"
},
"deleteDialog": {
"title": "Delete {{entity}}",
"intro": "Deleting this {{entity}} is irreversible!",
"confirm": "Are you sure you want to proceed? Type the name of this {{entity}} below to confirm.",
"confirmLabel": "Name of {{entity}} to delete",
"incorrectName": "Incorrect {{entity}} name."
}
}
4 changes: 0 additions & 4 deletionssite/src/i18n/en/templatePage.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
{
"deleteDialog": {
"title": "Delete template",
"description": "Deleting a template is irreversible. Are you sure you want to proceed?"
},
"deleteSuccess": "Template successfully deleted."
}
3 changes: 1 addition & 2 deletionssite/src/i18n/en/workspacePage.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
{
"deleteDialog": {
"title": "Delete workspace",
"description": "Deleting a workspace is irreversible. Are you sure you want to proceed?"
"info": "This workspace was created {{timeAgo}}."
},
"workspaceScheduleButton": {
"schedule": "Schedule",
Expand Down
6 changes: 2 additions & 4 deletionssite/src/pages/TemplatePage/TemplatePage.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@ import { useMachine, useSelector } from "@xstate/react"
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
import { FC, useContext } from "react"
import { Helmet } from "react-helmet-async"
import { useTranslation } from "react-i18next"
import { Navigate, useParams } from "react-router-dom"
import { selectPermissions } from "xServices/auth/authSelectors"
import { XServiceContext } from "xServices/StateContext"
Expand All@@ -24,7 +23,6 @@ const useTemplateName = () => {

export const TemplatePage: FC<React.PropsWithChildren<unknown>> = () => {
const organizationId = useOrganizationId()
const { t } = useTranslation("templatePage")
const templateName = useTemplateName()
const [templateState, templateSend] = useMachine(templateMachine, {
context: {
Expand DownExpand Up@@ -77,8 +75,8 @@ export const TemplatePage: FC<React.PropsWithChildren<unknown>> = () => {
<DeleteDialog
isOpen={templateState.matches("confirmingDelete")}
confirmLoading={templateState.matches("deleting")}
title={t("deleteDialog.title")}
description={t("deleteDialog.description")}
entity="template"
name={template.name}
onConfirm={() => {
templateSend("CONFIRM_DELETE")
}}
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp