chore: Normalize "new" actions in settings (#2226)
* fix: Unauthorized request to views.list from shared documents * Bump dep styled-components * chore: Normalize 'new' actions in settings area to top right chore: Add translation hooks to API tokens screen chore: Move API tokens loading to paginated list
This commit is contained in:
parent
d85592b5f3
commit
2c39cd6496
|
@ -0,0 +1,65 @@
|
|||
// @flow
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {|
|
||||
onSubmit: () => void,
|
||||
|};
|
||||
|
||||
function APITokenNew({ onSubmit }: Props) {
|
||||
const [name, setName] = React.useState("");
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const { apiKeys, ui } = useStores();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = React.useCallback(async () => {
|
||||
setIsSaving(true);
|
||||
|
||||
try {
|
||||
await apiKeys.create({ name });
|
||||
ui.showToast(t("API token created", { type: "success" }));
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [t, ui, name, onSubmit, apiKeys]);
|
||||
|
||||
const handleNameChange = React.useCallback((event) => {
|
||||
setName(event.target.value);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
Name your token something that will help you to remember it's use in
|
||||
the future, for example "local development", "production", or
|
||||
"continuous integration".
|
||||
</Trans>
|
||||
</HelpText>
|
||||
<Flex>
|
||||
<Input
|
||||
type="text"
|
||||
label="Name"
|
||||
onChange={handleNameChange}
|
||||
value={name}
|
||||
required
|
||||
autoFocus
|
||||
flex
|
||||
/>
|
||||
</Flex>
|
||||
<Button type="submit" disabled={isSaving || !name}>
|
||||
{isSaving ? "Creating…" : "Create"}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default APITokenNew;
|
|
@ -4,6 +4,7 @@ import { PlusIcon, GroupIcon } from "outline-icons";
|
|||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import GroupNew from "scenes/GroupNew";
|
||||
import { Action } from "components/Actions";
|
||||
import Button from "components/Button";
|
||||
import Empty from "components/Empty";
|
||||
import GroupListItem from "components/GroupListItem";
|
||||
|
@ -33,25 +34,31 @@ function Groups() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<Scene title={t("Groups")} icon={<GroupIcon color="currentColor" />}>
|
||||
<Scene
|
||||
title={t("Groups")}
|
||||
icon={<GroupIcon color="currentColor" />}
|
||||
actions={
|
||||
<>
|
||||
{can.createGroup && (
|
||||
<Action>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleNewGroupModalOpen}
|
||||
icon={<PlusIcon />}
|
||||
>
|
||||
{`${t("New group")}…`}
|
||||
</Button>
|
||||
</Action>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Heading>{t("Groups")}</Heading>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
Groups can be used to organize and manage the people on your team.
|
||||
</Trans>
|
||||
</HelpText>
|
||||
|
||||
{can.createGroup && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleNewGroupModalOpen}
|
||||
icon={<PlusIcon />}
|
||||
neutral
|
||||
>
|
||||
{`${t("New group")}…`}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Subheading>{t("All groups")}</Subheading>
|
||||
<PaginatedList
|
||||
items={groups.orderedData}
|
||||
|
|
|
@ -1,89 +1,86 @@
|
|||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { CodeIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import ApiKeysStore from "stores/ApiKeysStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import APITokenNew from "scenes/APITokenNew";
|
||||
import { Action } from "components/Actions";
|
||||
import Button from "components/Button";
|
||||
import Heading from "components/Heading";
|
||||
import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import List from "components/List";
|
||||
import Modal from "components/Modal";
|
||||
import PaginatedList from "components/PaginatedList";
|
||||
import Scene from "components/Scene";
|
||||
import Subheading from "components/Subheading";
|
||||
import TokenListItem from "./components/TokenListItem";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
apiKeys: ApiKeysStore,
|
||||
ui: UiStore,
|
||||
};
|
||||
function Tokens() {
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const { apiKeys, policies } = useStores();
|
||||
const [newModalOpen, setNewModalOpen] = React.useState(false);
|
||||
const can = policies.abilities(team.id);
|
||||
|
||||
@observer
|
||||
class Tokens extends React.Component<Props> {
|
||||
@observable name: string = "";
|
||||
const handleNewModalOpen = React.useCallback(() => {
|
||||
setNewModalOpen(true);
|
||||
}, []);
|
||||
|
||||
componentDidMount() {
|
||||
this.props.apiKeys.fetchPage({ limit: 100 });
|
||||
}
|
||||
const handleNewModalClose = React.useCallback(() => {
|
||||
setNewModalOpen(false);
|
||||
}, []);
|
||||
|
||||
handleUpdate = (ev: SyntheticInputEvent<*>) => {
|
||||
this.name = ev.target.value;
|
||||
};
|
||||
|
||||
handleSubmit = async (ev: SyntheticEvent<>) => {
|
||||
try {
|
||||
ev.preventDefault();
|
||||
await this.props.apiKeys.create({ name: this.name });
|
||||
this.name = "";
|
||||
} catch (error) {
|
||||
this.props.ui.showToast(error.message, { type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { apiKeys } = this.props;
|
||||
const hasApiKeys = apiKeys.orderedData.length > 0;
|
||||
|
||||
return (
|
||||
<Scene title="API Tokens" icon={<CodeIcon color="currentColor" />}>
|
||||
<Heading>API Tokens</Heading>
|
||||
<HelpText>
|
||||
You can create an unlimited amount of personal tokens to authenticate
|
||||
with the API. For more details about the API take a look at the{" "}
|
||||
<a href="https://www.getoutline.com/developers">
|
||||
developer documentation
|
||||
</a>
|
||||
.
|
||||
</HelpText>
|
||||
|
||||
{hasApiKeys && (
|
||||
<List>
|
||||
{apiKeys.orderedData.map((token) => (
|
||||
<TokenListItem
|
||||
key={token.id}
|
||||
token={token}
|
||||
onDelete={token.delete}
|
||||
return (
|
||||
<Scene
|
||||
title={t("API Tokens")}
|
||||
icon={<CodeIcon color="currentColor" />}
|
||||
actions={
|
||||
<>
|
||||
{can.createApiKey && (
|
||||
<Action>
|
||||
<Button
|
||||
type="submit"
|
||||
value={`${t("New token")}…`}
|
||||
onClick={handleNewModalOpen}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</Action>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Heading>{t("API Tokens")}</Heading>
|
||||
<HelpText>
|
||||
<Trans
|
||||
defaults="You can create an unlimited amount of personal tokens to authenticate
|
||||
with the API. Tokens have the same permissions as your user account.
|
||||
For more details see the <em>developer documentation</em>."
|
||||
components={{
|
||||
em: (
|
||||
<a href="https://www.getoutline.com/developers" target="_blank" />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</HelpText>
|
||||
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<Input
|
||||
onChange={this.handleUpdate}
|
||||
placeholder="Token label (eg. development)"
|
||||
value={this.name}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
value="Create Token"
|
||||
disabled={apiKeys.isSaving}
|
||||
/>
|
||||
</form>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
<PaginatedList
|
||||
fetch={apiKeys.fetchPage}
|
||||
items={apiKeys.orderedData}
|
||||
heading={<Subheading sticky>{t("Tokens")}</Subheading>}
|
||||
renderItem={(token) => (
|
||||
<TokenListItem key={token.id} token={token} onDelete={token.delete} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={t("Create a token")}
|
||||
onRequestClose={handleNewModalClose}
|
||||
isOpen={newModalOpen}
|
||||
>
|
||||
<APITokenNew onSubmit={handleNewModalClose} />
|
||||
</Modal>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
export default inject("apiKeys", "ui")(Tokens);
|
||||
export default observer(Tokens);
|
||||
|
|
|
@ -4,17 +4,20 @@ import ApiKey from "models/ApiKey";
|
|||
import Button from "components/Button";
|
||||
import ListItem from "components/List/Item";
|
||||
|
||||
type Props = {
|
||||
type Props = {|
|
||||
token: ApiKey,
|
||||
onDelete: (tokenId: string) => Promise<void>,
|
||||
};
|
||||
|};
|
||||
|
||||
const TokenListItem = ({ token, onDelete }: Props) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={token.id}
|
||||
title={token.name}
|
||||
subtitle={<code>{token.secret}</code>}
|
||||
title={
|
||||
<>
|
||||
{token.name} – <code>{token.secret}</code>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<Button onClick={() => onDelete(token.id)} neutral>
|
||||
Revoke
|
||||
|
|
|
@ -216,6 +216,8 @@
|
|||
"Revoke invite": "Revoke invite",
|
||||
"Activate account": "Activate account",
|
||||
"Suspend account": "Suspend account",
|
||||
"API token created": "API token created",
|
||||
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
|
||||
"Documents": "Documents",
|
||||
"The document archive is empty at the moment.": "The document archive is empty at the moment.",
|
||||
"Search in collection": "Search in collection",
|
||||
|
@ -386,8 +388,8 @@
|
|||
"Active": "Active",
|
||||
"Everyone": "Everyone",
|
||||
"Admins": "Admins",
|
||||
"Groups can be used to organize and manage the people on your team.": "Groups can be used to organize and manage the people on your team.",
|
||||
"New group": "New group",
|
||||
"Groups can be used to organize and manage the people on your team.": "Groups can be used to organize and manage the people on your team.",
|
||||
"All groups": "All groups",
|
||||
"No groups have been created yet": "No groups have been created yet",
|
||||
"Import started": "Import started",
|
||||
|
@ -428,6 +430,10 @@
|
|||
"Connect Outline collections to Slack channels and messages will be automatically posted to Slack when documents are published or updated.": "Connect Outline collections to Slack channels and messages will be automatically posted to Slack when documents are published or updated.",
|
||||
"Connected to the <em>{{ channelName }}</em> channel": "Connected to the <em>{{ channelName }}</em> channel",
|
||||
"Connect": "Connect",
|
||||
"New token": "New token",
|
||||
"You can create an unlimited amount of personal tokens to authenticate\n with the API. Tokens have the same permissions as your user account.\n For more details see the <em>developer documentation</em>.": "You can create an unlimited amount of personal tokens to authenticate\n with the API. Tokens have the same permissions as your user account.\n For more details see the <em>developer documentation</em>.",
|
||||
"Tokens": "Tokens",
|
||||
"Create a token": "Create a token",
|
||||
"You’ve not starred any documents yet.": "You’ve not starred any documents yet.",
|
||||
"There are no templates just yet.": "There are no templates just yet.",
|
||||
"You can create templates to help your team create consistent and accurate documentation.": "You can create templates to help your team create consistent and accurate documentation.",
|
||||
|
|
Reference in New Issue