chore: Slack integration screen improvements (#2049)

* feat: Add collection iconography and colors to Slack settings page
fix: Use standardized list components
fix: Slack icon size
chore: Convert to translation strings

* fix: Missing translation, convert to Scene
This commit is contained in:
Tom Moor
2021-04-18 18:34:49 -07:00
committed by GitHub
parent bf668d6347
commit fa52bc5afd
11 changed files with 130 additions and 127 deletions

View File

@ -1,9 +1,9 @@
// @flow
import * as React from "react";
import styled from "styled-components";
import SlackLogo from "../SlackLogo";
import GoogleLogo from "./GoogleLogo";
import MicrosoftLogo from "./MicrosoftLogo";
import SlackLogo from "./SlackLogo";
type Props = {|
providerName: string,

View File

@ -19,14 +19,14 @@ import styled from "styled-components";
import Flex from "components/Flex";
import Scrollable from "components/Scrollable";
import SlackIcon from "components/SlackIcon";
import ZapierIcon from "components/ZapierIcon";
import Sidebar from "./Sidebar";
import Header from "./components/Header";
import Section from "./components/Section";
import SidebarLink from "./components/SidebarLink";
import TeamButton from "./components/TeamButton";
import Version from "./components/Version";
import SlackIcon from "./icons/Slack";
import ZapierIcon from "./icons/Zapier";
import env from "env";
import useCurrentTeam from "hooks/useCurrentTeam";
import useStores from "hooks/useStores";

View File

@ -1,138 +1,136 @@
// @flow
import { find } from "lodash";
import { inject, observer } from "mobx-react";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import styled from "styled-components";
import getQueryVariable from "shared/utils/getQueryVariable";
import AuthStore from "stores/AuthStore";
import CollectionsStore from "stores/CollectionsStore";
import IntegrationsStore from "stores/IntegrationsStore";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import CollectionIcon from "components/CollectionIcon";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import List from "components/List";
import ListItem from "components/List/Item";
import Notice from "components/Notice";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import SlackIcon from "components/SlackIcon";
import SlackButton from "./components/SlackButton";
import env from "env";
import useCurrentTeam from "hooks/useCurrentTeam";
import useStores from "hooks/useStores";
type Props = {
collections: CollectionsStore,
integrations: IntegrationsStore,
auth: AuthStore,
};
function Slack() {
const team = useCurrentTeam();
const { collections, integrations } = useStores();
const { t } = useTranslation();
const error = getQueryVariable("error");
@observer
class Slack extends React.Component<Props> {
error: ?string;
React.useEffect(() => {
collections.fetchPage({ limit: 100 });
integrations.fetchPage({ limit: 100 });
}, [collections, integrations]);
componentDidMount() {
this.error = getQueryVariable("error");
this.props.collections.fetchPage({ limit: 100 });
this.props.integrations.fetchPage();
}
const commandIntegration = find(integrations.slackIntegrations, {
type: "command",
});
get commandIntegration() {
return find(this.props.integrations.slackIntegrations, {
type: "command",
});
}
render() {
const { collections, integrations, auth } = this.props;
const teamId = auth.team ? auth.team.id : "";
return (
<CenteredContent>
<PageTitle title="Slack" />
<h1>Slack</h1>
{this.error === "access_denied" && (
<Notice>
return (
<Scene title="Slack" icon={<SlackIcon color="currentColor" />}>
<Heading>Slack</Heading>
{error === "access_denied" && (
<Notice>
<Trans>
Whoops, you need to accept the permissions in Slack to connect
Outline to your team. Try again?
</Notice>
)}
{this.error === "unauthenticated" && (
<Notice>
</Trans>
</Notice>
)}
{error === "unauthenticated" && (
<Notice>
<Trans>
Something went wrong while authenticating your request. Please try
logging in again?
</Notice>
</Trans>
</Notice>
)}
<HelpText>
<Trans
defaults="Get rich previews of Outline links shared in Slack and use the <em>{{ command }}</em> slash command to search for documents without leaving your chat."
values={{ command: "/outline" }}
components={{ em: <Code /> }}
/>
</HelpText>
<p>
{commandIntegration ? (
<Button onClick={commandIntegration.delete}>{t("Disconnect")}</Button>
) : (
<SlackButton
scopes={["commands", "links:read", "links:write"]}
redirectUri={`${env.URL}/auth/slack.commands`}
state={team.id}
/>
)}
<HelpText>
Preview Outline links your team mates share and use the{" "}
<Code>/outline</Code> slash command in Slack to search for documents
in your teams wiki.
</HelpText>
<p>
{this.commandIntegration ? (
<Button onClick={this.commandIntegration.delete}>Disconnect</Button>
) : (
<SlackButton
scopes={["commands", "links:read", "links:write"]}
redirectUri={`${env.URL}/auth/slack.commands`}
state={teamId}
/>
)}
</p>
<p>&nbsp;</p>
</p>
<p>&nbsp;</p>
<h2>Collections</h2>
<HelpText>
<h2>{t("Collections")}</h2>
<HelpText>
<Trans>
Connect Outline collections to Slack channels and messages will be
posted in Slack when documents are published or updated.
</HelpText>
automatically posted to Slack when documents are published or updated.
</Trans>
</HelpText>
<List>
{collections.orderedData.map((collection) => {
const integration = find(integrations.slackIntegrations, {
collectionId: collection.id,
});
if (integration) {
return (
<ListItem key={integration.id}>
<span>
<strong>{collection.name}</strong> posting activity to the{" "}
<strong>{integration.settings.channel}</strong> Slack
channel
</span>
<Button onClick={integration.delete}>Disconnect</Button>
</ListItem>
);
}
<List>
{collections.orderedData.map((collection) => {
const integration = find(integrations.slackIntegrations, {
collectionId: collection.id,
});
if (integration) {
return (
<ListItem key={collection.id}>
<strong>{collection.name}</strong>
<ListItem
key={integration.id}
title={collection.name}
image={<CollectionIcon collection={collection} />}
subtitle={
<Trans
defaults={`Connected to the <em>{{ channelName }}</em> channel`}
values={{ channelName: integration.settings.channel }}
components={{ em: <strong /> }}
/>
}
actions={
<Button onClick={integration.delete}>
{t("Disconnect")}
</Button>
}
/>
);
}
return (
<ListItem
key={collection.id}
title={collection.name}
image={<CollectionIcon collection={collection} />}
actions={
<SlackButton
scopes={["incoming-webhook"]}
redirectUri={`${env.URL}/auth/slack.post`}
state={collection.id}
label="Connect"
label={t("Connect")}
/>
</ListItem>
);
})}
</List>
</CenteredContent>
);
}
}
/>
);
})}
</List>
</Scene>
);
}
const List = styled.ol`
list-style: none;
margin: 8px 0;
padding: 0;
`;
const ListItem = styled.li`
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #eaebea;
`;
const Code = styled.code`
padding: 4px 6px;
margin: 0 2px;
@ -140,4 +138,4 @@ const Code = styled.code`
border-radius: 4px;
`;
export default inject("collections", "integrations", "auth")(Slack);
export default observer(Slack);

View File

@ -1,19 +1,20 @@
// @flow
import * as React from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { slackAuth } from "shared/utils/routeHelpers";
import Button from "components/Button";
import SlackLogo from "components/SlackLogo";
import SlackIcon from "components/SlackIcon";
import env from "env";
type Props = {
type Props = {|
scopes?: string[],
redirectUri: string,
state?: string,
label?: string,
};
|};
function SlackButton({ state = "", scopes, redirectUri, label }: Props) {
const { t } = useTranslation();
const handleClick = () =>
(window.location.href = slackAuth(
state,
@ -25,22 +26,12 @@ function SlackButton({ state = "", scopes, redirectUri, label }: Props) {
return (
<Button
onClick={handleClick}
icon={<SpacedSlackLogo size={24} fill="#000" />}
icon={<SlackIcon fill="currentColor" />}
neutral
>
{label ? (
label
) : (
<span>
Add to <strong>Slack</strong>
</span>
)}
{label || t("Add to Slack")}
</Button>
);
}
const SpacedSlackLogo = styled(SlackLogo)`
padding-right: 4px;
`;
export default SlackButton;

View File

@ -10,7 +10,7 @@
"build:webpack": "webpack --config webpack.config.prod.js",
"build": "yarn clean && yarn build:webpack && yarn build:i18n && yarn build:server",
"start": "node ./build/server/index.js",
"dev": "nodemon --exec \"yarn build:server && yarn build:i18n && node build/server/index.js\" -e js --ignore build/ --ignore app/",
"dev": "nodemon --exec \"yarn build:server && yarn build:i18n && node --inspect=0.0.0.0 build/server/index.js\" -e js --ignore build/ --ignore app/",
"lint": "eslint app server shared",
"deploy": "git push heroku master",
"heroku-postbuild": "yarn build && yarn db:migrate",

View File

@ -149,7 +149,7 @@ if (SLACK_CLIENT_ID) {
}
// this code block accounts for the root domain being unable to
// access authentcation for subdomains. We must forward to the
// access authentication for subdomains. We must forward to the
// appropriate subdomain to complete the oauth flow
if (!user) {
try {

View File

@ -10,8 +10,14 @@ import { sendEmail } from "../mailer";
import { DataTypes, sequelize, encryptedFields, Op } from "../sequelize";
import { DEFAULT_AVATAR_HOST } from "../utils/avatars";
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
import UserAuthentication from "./UserAuthentication";
import { Star, Team, Collection, NotificationSetting, ApiKey } from ".";
import {
UserAuthentication,
Star,
Team,
Collection,
NotificationSetting,
ApiKey,
} from ".";
const User = sequelize.define(
"user",

View File

@ -348,6 +348,7 @@
"Shared": "Shared",
"by {{ name }}": "by {{ name }}",
"Last accessed": "Last accessed",
"Add to Slack": "Add to Slack",
"Import started": "Import started",
"Export in progress…": "Export in progress…",
"It is possible to import a zip file of folders and Markdown files previously exported from an Outline instance. Support will soon be added for importing from other services.": "It is possible to import a zip file of folders and Markdown files previously exported from an Outline instance. Support will soon be added for importing from other services.",
@ -384,6 +385,13 @@
"You can globally enable and disable public document sharing in the <em>security settings</em>.": "You can globally enable and disable public document sharing in the <em>security settings</em>.",
"Shared documents": "Shared documents",
"No share links, yet.": "No share links, yet.",
"Whoops, you need to accept the permissions in Slack to connect Outline to your team. Try again?": "Whoops, you need to accept the permissions in Slack to connect Outline to your team. Try again?",
"Something went wrong while authenticating your request. Please try logging in again?": "Something went wrong while authenticating your request. Please try logging in again?",
"Get rich previews of Outline links shared in Slack and use the <em>{{ command }}</em> slash command to search for documents without leaving your chat.": "Get rich previews of Outline links shared in Slack and use the <em>{{ command }}</em> slash command to search for documents without leaving your chat.",
"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",
"Disconnect": "Disconnect",
"Connect": "Connect",
"Youve not starred any documents yet.": "Youve 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.",