mirror of https://github.com/coder/coder.git
feat(tokens): improve delete confirmation dialog (#6651)
This commit is contained in:
parent
db40c29f26
commit
5b07f1e2a3
|
@ -6,7 +6,7 @@
|
|||
"addToken": "Add token",
|
||||
"deleteToken": {
|
||||
"delete": "Delete Token",
|
||||
"deleteCaption": "Are you sure you want to delete this token?<br/><br/><4>{{tokenId}}</4>",
|
||||
"deleteCaption": "Are you sure you want to permanently delete token <strong><4>{{tokenName}}</4></strong>?",
|
||||
"deleteSuccess": "Token has been deleted",
|
||||
"deleteFailure": "Failed to delete token"
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Stack } from "components/Stack/Stack"
|
|||
import Button from "@material-ui/core/Button"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import AddIcon from "@material-ui/icons/AddOutlined"
|
||||
import { APIKeyWithOwner } from "api/typesGenerated"
|
||||
|
||||
export const TokensPage: FC<PropsWithChildren<unknown>> = () => {
|
||||
const styles = useStyles()
|
||||
|
@ -30,9 +31,9 @@ export const TokensPage: FC<PropsWithChildren<unknown>> = () => {
|
|||
</Stack>
|
||||
)
|
||||
|
||||
const [tokenIdToDelete, setTokenIdToDelete] = useState<string | undefined>(
|
||||
undefined,
|
||||
)
|
||||
const [tokenToDelete, setTokenToDelete] = useState<
|
||||
APIKeyWithOwner | undefined
|
||||
>(undefined)
|
||||
|
||||
const {
|
||||
data: tokens,
|
||||
|
@ -60,15 +61,15 @@ export const TokensPage: FC<PropsWithChildren<unknown>> = () => {
|
|||
isLoading={isFetching}
|
||||
hasLoaded={isFetched}
|
||||
getTokensError={getTokensError}
|
||||
onDelete={(id) => {
|
||||
setTokenIdToDelete(id)
|
||||
onDelete={(token) => {
|
||||
setTokenToDelete(token)
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
<ConfirmDeleteDialog
|
||||
queryKey={queryKey}
|
||||
tokenId={tokenIdToDelete}
|
||||
setTokenId={setTokenIdToDelete}
|
||||
token={tokenToDelete}
|
||||
setToken={setTokenToDelete}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface TokensPageViewProps {
|
|||
getTokensError?: Error | unknown
|
||||
isLoading: boolean
|
||||
hasLoaded: boolean
|
||||
onDelete: (id: string) => void
|
||||
onDelete: (token: APIKeyWithOwner) => void
|
||||
deleteTokenError?: Error | unknown
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ export const TokensPageView: FC<
|
|||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onDelete(token.id)
|
||||
onDelete(token)
|
||||
}}
|
||||
size="medium"
|
||||
aria-label={t("tokenActions.deleteToken.delete")}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { Story } from "@storybook/react"
|
||||
import { MockToken } from "testHelpers/entities"
|
||||
import {
|
||||
ConfirmDeleteDialog,
|
||||
ConfirmDeleteDialogProps,
|
||||
} from "./ConfirmDeleteDialog"
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
cacheTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default {
|
||||
title: "components/ConfirmDeleteDialog",
|
||||
component: ConfirmDeleteDialog,
|
||||
}
|
||||
|
||||
const Template: Story<ConfirmDeleteDialogProps> = (
|
||||
args: ConfirmDeleteDialogProps,
|
||||
) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ConfirmDeleteDialog {...args} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
export const DeleteDialog = Template.bind({})
|
||||
DeleteDialog.args = {
|
||||
queryKey: ["tokens"],
|
||||
token: MockToken,
|
||||
setToken: () => {
|
||||
return null
|
||||
},
|
||||
}
|
|
@ -4,24 +4,28 @@ import { useTranslation, Trans } from "react-i18next"
|
|||
import { useDeleteToken } from "../hooks"
|
||||
import { displaySuccess, displayError } from "components/GlobalSnackbar/utils"
|
||||
import { getErrorMessage } from "api/errors"
|
||||
import { APIKeyWithOwner } from "api/typesGenerated"
|
||||
|
||||
export const ConfirmDeleteDialog: FC<{
|
||||
export interface ConfirmDeleteDialogProps {
|
||||
queryKey: (string | boolean)[]
|
||||
tokenId: string | undefined
|
||||
setTokenId: (arg: string | undefined) => void
|
||||
}> = ({ queryKey, tokenId, setTokenId }) => {
|
||||
const { t } = useTranslation("tokensPage")
|
||||
token: APIKeyWithOwner | undefined
|
||||
setToken: (arg: APIKeyWithOwner | undefined) => void
|
||||
}
|
||||
|
||||
export const ConfirmDeleteDialog: FC<ConfirmDeleteDialogProps> = ({
|
||||
queryKey,
|
||||
token,
|
||||
setToken,
|
||||
}) => {
|
||||
const { t } = useTranslation("tokensPage")
|
||||
const tokenName = token?.token_name
|
||||
const description = (
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="tokenActions.deleteToken.deleteCaption"
|
||||
values={{ tokenId }}
|
||||
values={{ tokenName }}
|
||||
>
|
||||
Are you sure you want to delete this token?
|
||||
<br />
|
||||
<br />
|
||||
{{ tokenId }}
|
||||
Are you sure you want to permanently delete token {{ tokenName }}?
|
||||
</Trans>
|
||||
)
|
||||
|
||||
|
@ -30,7 +34,7 @@ export const ConfirmDeleteDialog: FC<{
|
|||
|
||||
const onDeleteSuccess = () => {
|
||||
displaySuccess(t("tokenActions.deleteToken.deleteSuccess"))
|
||||
setTokenId(undefined)
|
||||
setToken(undefined)
|
||||
}
|
||||
|
||||
const onDeleteError = (error: unknown) => {
|
||||
|
@ -39,26 +43,27 @@ export const ConfirmDeleteDialog: FC<{
|
|||
t("tokenActions.deleteToken.deleteFailure"),
|
||||
)
|
||||
displayError(message)
|
||||
setTokenId(undefined)
|
||||
setToken(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
type="delete"
|
||||
title={t("tokenActions.deleteToken.delete")}
|
||||
description={description}
|
||||
open={Boolean(tokenId) || isDeleting}
|
||||
open={Boolean(token) || isDeleting}
|
||||
confirmLoading={isDeleting}
|
||||
onConfirm={() => {
|
||||
if (!tokenId) {
|
||||
if (!token) {
|
||||
return
|
||||
}
|
||||
deleteToken(tokenId, {
|
||||
deleteToken(token.id, {
|
||||
onError: onDeleteError,
|
||||
onSuccess: onDeleteSuccess,
|
||||
})
|
||||
}}
|
||||
onClose={() => {
|
||||
setTokenId(undefined)
|
||||
setToken(undefined)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -37,19 +37,22 @@ export const MockAPIKey: TypesGen.GenerateAPIKeyResponse = {
|
|||
key: "my-api-key",
|
||||
}
|
||||
|
||||
export const MockTokens: TypesGen.APIKey[] = [
|
||||
{
|
||||
id: "tBoVE3dqLl",
|
||||
user_id: "f9ee61d8-1d84-4410-ab6e-c1ec1a641e0b",
|
||||
last_used: "0001-01-01T00:00:00Z",
|
||||
expires_at: "2023-01-15T20:10:45.637438Z",
|
||||
created_at: "2022-12-16T20:10:45.637452Z",
|
||||
updated_at: "2022-12-16T20:10:45.637452Z",
|
||||
login_type: "token",
|
||||
scope: "all",
|
||||
lifetime_seconds: 2592000,
|
||||
token_name: "token-one",
|
||||
},
|
||||
export const MockToken: TypesGen.APIKeyWithOwner = {
|
||||
id: "tBoVE3dqLl",
|
||||
user_id: "f9ee61d8-1d84-4410-ab6e-c1ec1a641e0b",
|
||||
last_used: "0001-01-01T00:00:00Z",
|
||||
expires_at: "2023-01-15T20:10:45.637438Z",
|
||||
created_at: "2022-12-16T20:10:45.637452Z",
|
||||
updated_at: "2022-12-16T20:10:45.637452Z",
|
||||
login_type: "token",
|
||||
scope: "all",
|
||||
lifetime_seconds: 2592000,
|
||||
token_name: "token-one",
|
||||
username: "admin",
|
||||
}
|
||||
|
||||
export const MockTokens: TypesGen.APIKeyWithOwner[] = [
|
||||
MockToken,
|
||||
{
|
||||
id: "tBoVE3dqLl",
|
||||
user_id: "f9ee61d8-1d84-4410-ab6e-c1ec1a641e0b",
|
||||
|
@ -61,6 +64,7 @@ export const MockTokens: TypesGen.APIKey[] = [
|
|||
scope: "all",
|
||||
lifetime_seconds: 2592000,
|
||||
token_name: "token-two",
|
||||
username: "admin",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
Loading…
Reference in New Issue