fix: Updated design of api tokens to clarify, closes #3422

This commit is contained in:
Tom Moor
2022-04-25 21:34:18 -07:00
parent e49897ab5a
commit 2a11a23d5b
5 changed files with 91 additions and 15 deletions

View File

@@ -5,7 +5,7 @@ type Props = {
text: string;
children?: React.ReactElement;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
onCopy: () => void;
onCopy?: () => void;
};
class CopyToClipboard extends React.PureComponent<Props> {
@@ -17,9 +17,8 @@ class CopyToClipboard extends React.PureComponent<Props> {
debug: process.env.NODE_ENV !== "production",
format: "text/plain",
});
if (onCopy) {
onCopy();
}
onCopy?.();
if (elem && elem.props && typeof elem.props.onClick === "function") {
elem.props.onClick(ev);

View File

@@ -61,7 +61,7 @@ function Tokens() {
items={apiKeys.orderedData}
heading={<Subheading sticky>{t("Tokens")}</Subheading>}
renderItem={(token: ApiKey) => (
<TokenListItem key={token.id} token={token} onDelete={token.delete} />
<TokenListItem key={token.id} token={token} />
)}
/>
<Modal

View File

@@ -1,26 +1,65 @@
import { CopyIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import ApiKey from "~/models/ApiKey";
import Button from "~/components/Button";
import CopyToClipboard from "~/components/CopyToClipboard";
import ListItem from "~/components/List/Item";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import TokenRevokeDialog from "./TokenRevokeDialog";
type Props = {
token: ApiKey;
onDelete: (tokenId: string) => Promise<void>;
};
const TokenListItem = ({ token, onDelete }: Props) => {
const TokenListItem = ({ token }: Props) => {
const { t } = useTranslation();
const { showToast } = useToasts();
const { dialogs } = useStores();
const [linkCopied, setLinkCopied] = React.useState<boolean>(false);
React.useEffect(() => {
if (linkCopied) {
setTimeout(() => {
setLinkCopied(false);
}, 3000);
}
}, [linkCopied]);
const handleCopy = React.useCallback(() => {
setLinkCopied(true);
showToast(t("API token copied to clipboard"), {
type: "success",
});
}, [showToast, t]);
const showRevokeConfirmation = React.useCallback(() => {
dialogs.openModal({
title: t("Revoke token"),
isCentered: true,
content: (
<TokenRevokeDialog onSubmit={dialogs.closeAllModals} token={token} />
),
});
}, [t, dialogs, token]);
return (
<ListItem
key={token.id}
title={
<>
{token.name} <code>{token.secret}</code>
</>
}
title={token.name}
subtitle={<code>{token.secret}</code>}
actions={
<Button onClick={() => onDelete(token.id)} neutral>
Revoke
</Button>
<>
<CopyToClipboard text={token.secret} onCopy={handleCopy}>
<Button type="button" icon={<CopyIcon />} neutral borderOnHover>
{linkCopied ? t("Copied") : t("Copy")}
</Button>
</CopyToClipboard>
<Button onClick={showRevokeConfirmation} neutral>
Revoke
</Button>
</>
}
/>
);

View File

@@ -0,0 +1,31 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import ApiKey from "~/models/ApiKey";
import ConfirmationDialog from "~/components/ConfirmationDialog";
type Props = {
token: ApiKey;
onSubmit: () => void;
};
export default function TokenRevokeDialog({ token, onSubmit }: Props) {
const { t } = useTranslation();
const handleSubmit = async () => {
await token.delete();
onSubmit();
};
return (
<ConfirmationDialog
onSubmit={handleSubmit}
submitText={t("Revoke")}
savingText={`${t("Revoking")}`}
danger
>
{t("Are you sure you want to revoke the {{ tokenName }} token?", {
tokenName: token.name,
})}
</ConfirmationDialog>
);
}

View File

@@ -569,6 +569,13 @@
"Document published": "Document published",
"Document updated": "Document updated",
"Disconnect": "Disconnect",
"API token copied to clipboard": "API token copied to clipboard",
"Revoke token": "Revoke token",
"Copied": "Copied",
"Copy": "Copy",
"Revoke": "Revoke",
"Revoking": "Revoking",
"Are you sure you want to revoke the {{ tokenName }} token?": "Are you sure you want to revoke the {{ tokenName }} token?",
"Active": "Active",
"Everyone": "Everyone",
"Admins": "Admins",