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:
@ -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,
|
||||||
|
@ -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";
|
||||||
|
@ -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> </p>
|
||||||
<Code>/outline</Code> slash command in Slack to search for documents
|
|
||||||
in your team’s 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> </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);
|
||||||
|
@ -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;
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
"You’ve not starred any documents yet.": "You’ve not starred any documents yet.",
|
"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.",
|
"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.",
|
||||||
|
Reference in New Issue
Block a user