Files
outline/plugins/slack/client/Settings.tsx

221 lines
6.9 KiB
TypeScript

import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import styled from "styled-components";
import { IntegrationService, IntegrationType } from "@shared/types";
import Collection from "~/models/Collection";
import Integration from "~/models/Integration";
import { ConnectedButton } from "~/scenes/Settings/components/ConnectedButton";
import SettingRow from "~/scenes/Settings/components/SettingRow";
import Flex from "~/components/Flex";
import Heading from "~/components/Heading";
import CollectionIcon from "~/components/Icons/CollectionIcon";
import List from "~/components/List";
import ListItem from "~/components/List/Item";
import Notice from "~/components/Notice";
import Scene from "~/components/Scene";
import Text from "~/components/Text";
import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import usePolicy from "~/hooks/usePolicy";
import useQuery from "~/hooks/useQuery";
import useStores from "~/hooks/useStores";
import { SlackUtils } from "../shared/SlackUtils";
import SlackIcon from "./Icon";
import SlackButton from "./components/SlackButton";
import SlackListItem from "./components/SlackListItem";
function Slack() {
const team = useCurrentTeam();
const { collections, integrations } = useStores();
const { t } = useTranslation();
const query = useQuery();
const can = usePolicy(team);
const error = query.get("error");
React.useEffect(() => {
void collections.fetchPage({
limit: 100,
});
void integrations.fetchPage({
service: IntegrationService.Slack,
limit: 100,
});
}, [collections, integrations]);
const commandIntegration = integrations.find({
type: IntegrationType.Command,
service: IntegrationService.Slack,
});
const linkedAccountIntegration = integrations.find({
type: IntegrationType.LinkedAccount,
service: IntegrationService.Slack,
});
const groupedCollections = collections.orderedData
.map<[Collection, Integration | undefined]>((collection) => {
const integration = integrations.find({
service: IntegrationService.Slack,
collectionId: collection.id,
});
return [collection, integration];
})
.sort((a) => (a[1] ? -1 : 1));
const appName = env.APP_NAME;
return (
<Scene title="Slack" icon={<SlackIcon />}>
<Heading>Slack</Heading>
{error === "access_denied" && (
<Notice>
<Trans>
Whoops, you need to accept the permissions in Slack to connect{" "}
{{ appName }} to your workspace. Try again?
</Trans>
</Notice>
)}
{error === "unauthenticated" && (
<Notice>
<Trans>
Something went wrong while authenticating your request. Please try
logging in again.
</Trans>
</Notice>
)}
<SettingRow
name="link"
label={t("Personal account")}
description={
<Trans>
Link your {{ appName }} account to Slack to enable searching and
previewing the documents you have access to, directly within chat.
</Trans>
}
>
<Flex align="flex-end" column>
{linkedAccountIntegration ? (
<ConnectedButton
onClick={linkedAccountIntegration.delete}
confirmationMessage={t(
"Disconnecting your personal account will prevent searching for documents from Slack. Are you sure?"
)}
/>
) : (
<SlackButton
redirectUri={SlackUtils.connectUrl()}
state={SlackUtils.createState(
team.id,
IntegrationType.LinkedAccount
)}
label={t("Connect")}
/>
)}
</Flex>
</SettingRow>
{can.update && (
<>
<SettingRow
name="slash"
border={false}
label={t("Slash command")}
description={
<Trans
defaults="Get rich previews of {{ appName }} links shared in Slack and use the <em>{{ command }}</em> slash command to search for documents without leaving your chat."
values={{
command: "/outline",
appName,
}}
components={{
em: <Code />,
}}
/>
}
>
<Flex align="flex-end" column>
{commandIntegration ? (
<ConnectedButton
onClick={commandIntegration.delete}
confirmationMessage={t(
"This will remove the Outline slash command from your Slack workspace. Are you sure?"
)}
/>
) : (
<SlackButton
scopes={["commands", "links:read", "links:write"]}
redirectUri={SlackUtils.connectUrl()}
state={SlackUtils.createState(
team.id,
IntegrationType.Command
)}
icon={<SlackIcon />}
/>
)}
</Flex>
</SettingRow>
<Heading as="h2">{t("Collections")}</Heading>
<Text as="p" type="secondary">
<Trans>
Connect {{ appName }} collections to Slack channels. Messages will
be automatically posted to Slack when documents are published or
updated.
</Trans>
</Text>
<List>
{groupedCollections.map(([collection, integration]) => {
if (integration) {
return (
<SlackListItem
key={integration.id}
collection={collection}
integration={
integration as Integration<IntegrationType.Post>
}
/>
);
}
return (
<ListItem
key={collection.id}
title={collection.name}
image={<CollectionIcon collection={collection} />}
actions={
<SlackButton
scopes={["incoming-webhook"]}
redirectUri={SlackUtils.connectUrl()}
state={SlackUtils.createState(
team.id,
IntegrationType.Post,
{ collectionId: collection.id }
)}
label={t("Connect")}
/>
}
/>
);
})}
</List>
</>
)}
</Scene>
);
}
const Code = styled.code`
padding: 4px 6px;
margin: 0 2px;
background: ${(props) => props.theme.codeBackground};
border-radius: 4px;
font-size: 80%;
`;
export default observer(Slack);