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 // @flow
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import SlackLogo from "../SlackLogo";
import GoogleLogo from "./GoogleLogo"; import GoogleLogo from "./GoogleLogo";
import MicrosoftLogo from "./MicrosoftLogo"; import MicrosoftLogo from "./MicrosoftLogo";
import SlackLogo from "./SlackLogo";
type Props = {| type Props = {|
providerName: string, providerName: string,

View File

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

View File

@ -1,138 +1,136 @@
// @flow // @flow
import { find } from "lodash"; import { find } from "lodash";
import { inject, observer } from "mobx-react"; import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import styled from "styled-components"; import styled from "styled-components";
import getQueryVariable from "shared/utils/getQueryVariable"; 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 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 HelpText from "components/HelpText";
import List from "components/List";
import ListItem from "components/List/Item";
import Notice from "components/Notice"; 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 SlackButton from "./components/SlackButton";
import env from "env"; import env from "env";
import useCurrentTeam from "hooks/useCurrentTeam";
import useStores from "hooks/useStores";
type Props = { function Slack() {
collections: CollectionsStore, const team = useCurrentTeam();
integrations: IntegrationsStore, const { collections, integrations } = useStores();
auth: AuthStore, const { t } = useTranslation();
}; const error = getQueryVariable("error");
@observer React.useEffect(() => {
class Slack extends React.Component<Props> { collections.fetchPage({ limit: 100 });
error: ?string; integrations.fetchPage({ limit: 100 });
}, [collections, integrations]);
componentDidMount() { const commandIntegration = find(integrations.slackIntegrations, {
this.error = getQueryVariable("error"); type: "command",
this.props.collections.fetchPage({ limit: 100 }); });
this.props.integrations.fetchPage();
}
get commandIntegration() { return (
return find(this.props.integrations.slackIntegrations, { <Scene title="Slack" icon={<SlackIcon color="currentColor" />}>
type: "command", <Heading>Slack</Heading>
}); {error === "access_denied" && (
} <Notice>
<Trans>
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>
Whoops, you need to accept the permissions in Slack to connect Whoops, you need to accept the permissions in Slack to connect
Outline to your team. Try again? Outline to your team. Try again?
</Notice> </Trans>
)} </Notice>
{this.error === "unauthenticated" && ( )}
<Notice> {error === "unauthenticated" && (
<Notice>
<Trans>
Something went wrong while authenticating your request. Please try Something went wrong while authenticating your request. Please try
logging in again? 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> </p>
Preview Outline links your team mates share and use the{" "} <p>&nbsp;</p>
<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>
<h2>Collections</h2> <h2>{t("Collections")}</h2>
<HelpText> <HelpText>
<Trans>
Connect Outline collections to Slack channels and messages will be Connect Outline collections to Slack channels and messages will be
posted in Slack when documents are published or updated. automatically posted to Slack when documents are published or updated.
</HelpText> </Trans>
</HelpText>
<List> <List>
{collections.orderedData.map((collection) => { {collections.orderedData.map((collection) => {
const integration = find(integrations.slackIntegrations, { const integration = find(integrations.slackIntegrations, {
collectionId: collection.id, 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>
);
}
if (integration) {
return ( return (
<ListItem key={collection.id}> <ListItem
<strong>{collection.name}</strong> 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 <SlackButton
scopes={["incoming-webhook"]} scopes={["incoming-webhook"]}
redirectUri={`${env.URL}/auth/slack.post`} redirectUri={`${env.URL}/auth/slack.post`}
state={collection.id} 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` const Code = styled.code`
padding: 4px 6px; padding: 4px 6px;
margin: 0 2px; margin: 0 2px;
@ -140,4 +138,4 @@ const Code = styled.code`
border-radius: 4px; border-radius: 4px;
`; `;
export default inject("collections", "integrations", "auth")(Slack); export default observer(Slack);

View File

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

View File

@ -10,7 +10,7 @@
"build:webpack": "webpack --config webpack.config.prod.js", "build:webpack": "webpack --config webpack.config.prod.js",
"build": "yarn clean && yarn build:webpack && yarn build:i18n && yarn build:server", "build": "yarn clean && yarn build:webpack && yarn build:i18n && yarn build:server",
"start": "node ./build/server/index.js", "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", "lint": "eslint app server shared",
"deploy": "git push heroku master", "deploy": "git push heroku master",
"heroku-postbuild": "yarn build && yarn db:migrate", "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 // 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 // appropriate subdomain to complete the oauth flow
if (!user) { if (!user) {
try { try {

View File

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

View File

@ -348,6 +348,7 @@
"Shared": "Shared", "Shared": "Shared",
"by {{ name }}": "by {{ name }}", "by {{ name }}": "by {{ name }}",
"Last accessed": "Last accessed", "Last accessed": "Last accessed",
"Add to Slack": "Add to Slack",
"Import started": "Import started", "Import started": "Import started",
"Export in progress…": "Export in progress…", "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.", "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>.", "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", "Shared documents": "Shared documents",
"No share links, yet.": "No share links, yet.", "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.", "Youve not starred any documents yet.": "Youve not starred any documents yet.",
"There are no templates just yet.": "There are no templates just 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.", "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.",