fix: Add translation hooks on settings screen (#2298)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TeamIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import Heading from "components/Heading";
|
||||
@@ -14,137 +13,128 @@ import Input, { LabelText } from "components/Input";
|
||||
import Scene from "components/Scene";
|
||||
import ImageUpload from "./components/ImageUpload";
|
||||
import env from "env";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
};
|
||||
function Details() {
|
||||
const { auth, ui } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const form = useRef<?HTMLFormElement>();
|
||||
const [name, setName] = useState(team.name);
|
||||
const [subdomain, setSubdomain] = useState(team.subdomain);
|
||||
const [avatarUrl, setAvatarUrl] = useState();
|
||||
|
||||
@observer
|
||||
class Details extends React.Component<Props> {
|
||||
timeout: TimeoutID;
|
||||
form: ?HTMLFormElement;
|
||||
const handleSubmit = React.useCallback(
|
||||
async (event: ?SyntheticEvent<>) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
@observable name: string;
|
||||
@observable subdomain: ?string;
|
||||
@observable avatarUrl: ?string;
|
||||
try {
|
||||
await auth.updateTeam({
|
||||
name,
|
||||
avatarUrl,
|
||||
subdomain,
|
||||
});
|
||||
ui.showToast(t("Settings saved"), { type: "success" });
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
}
|
||||
},
|
||||
[auth, ui, name, avatarUrl, subdomain, t]
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
const { team } = this.props.auth;
|
||||
if (team) {
|
||||
this.name = team.name;
|
||||
this.subdomain = team.subdomain;
|
||||
}
|
||||
}
|
||||
const handleNameChange = React.useCallback((ev: SyntheticInputEvent<*>) => {
|
||||
setName(ev.target.value);
|
||||
}, []);
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
const handleSubdomainChange = React.useCallback(
|
||||
(ev: SyntheticInputEvent<*>) => {
|
||||
setSubdomain(ev.target.value.toLowerCase());
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
handleSubmit = async (event: ?SyntheticEvent<>) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
const handleAvatarUpload = React.useCallback(
|
||||
(avatarUrl: string) => {
|
||||
setAvatarUrl(avatarUrl);
|
||||
handleSubmit();
|
||||
},
|
||||
[handleSubmit]
|
||||
);
|
||||
|
||||
try {
|
||||
await this.props.auth.updateTeam({
|
||||
name: this.name,
|
||||
avatarUrl: this.avatarUrl,
|
||||
subdomain: this.subdomain,
|
||||
});
|
||||
this.props.ui.showToast("Settings saved", { type: "success" });
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(err.message, { type: "error" });
|
||||
}
|
||||
};
|
||||
const handleAvatarError = React.useCallback(
|
||||
(error: ?string) => {
|
||||
ui.showToast(error || t("Unable to upload new logo"));
|
||||
},
|
||||
[ui, t]
|
||||
);
|
||||
|
||||
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
||||
this.name = ev.target.value;
|
||||
};
|
||||
const isValid = form.current && form.current.checkValidity();
|
||||
|
||||
handleSubdomainChange = (ev: SyntheticInputEvent<*>) => {
|
||||
this.subdomain = ev.target.value.toLowerCase();
|
||||
};
|
||||
|
||||
handleAvatarUpload = (avatarUrl: string) => {
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.handleSubmit();
|
||||
};
|
||||
|
||||
handleAvatarError = (error: ?string) => {
|
||||
this.props.ui.showToast(error || "Unable to upload new logo");
|
||||
};
|
||||
|
||||
get isValid() {
|
||||
return this.form && this.form.checkValidity();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { team, isSaving } = this.props.auth;
|
||||
if (!team) return null;
|
||||
const avatarUrl = this.avatarUrl || team.avatarUrl;
|
||||
|
||||
return (
|
||||
<Scene title="Details" icon={<TeamIcon color="currentColor" />}>
|
||||
<Heading>Details</Heading>
|
||||
<HelpText>
|
||||
return (
|
||||
<Scene title={t("Details")} icon={<TeamIcon color="currentColor" />}>
|
||||
<Heading>{t("Details")}</Heading>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
These details affect the way that your Outline appears to everyone on
|
||||
the team.
|
||||
</HelpText>
|
||||
</Trans>
|
||||
</HelpText>
|
||||
|
||||
<ProfilePicture column>
|
||||
<LabelText>Logo</LabelText>
|
||||
<AvatarContainer>
|
||||
<ImageUpload
|
||||
onSuccess={this.handleAvatarUpload}
|
||||
onError={this.handleAvatarError}
|
||||
submitText="Crop logo"
|
||||
borderRadius={0}
|
||||
>
|
||||
<Avatar src={avatarUrl} />
|
||||
<Flex auto align="center" justify="center">
|
||||
Upload
|
||||
</Flex>
|
||||
</ImageUpload>
|
||||
</AvatarContainer>
|
||||
</ProfilePicture>
|
||||
<form onSubmit={this.handleSubmit} ref={(ref) => (this.form = ref)}>
|
||||
<Input
|
||||
label="Name"
|
||||
name="name"
|
||||
autoComplete="organization"
|
||||
value={this.name}
|
||||
onChange={this.handleNameChange}
|
||||
required
|
||||
short
|
||||
/>
|
||||
{env.SUBDOMAINS_ENABLED && (
|
||||
<>
|
||||
<Input
|
||||
label="Subdomain"
|
||||
name="subdomain"
|
||||
value={this.subdomain || ""}
|
||||
onChange={this.handleSubdomainChange}
|
||||
autoComplete="off"
|
||||
minLength={4}
|
||||
maxLength={32}
|
||||
short
|
||||
/>
|
||||
{this.subdomain && (
|
||||
<HelpText small>
|
||||
Your knowledge base will be accessible at{" "}
|
||||
<strong>{this.subdomain}.getoutline.com</strong>
|
||||
</HelpText>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Button type="submit" disabled={isSaving || !this.isValid}>
|
||||
{isSaving ? "Saving…" : "Save"}
|
||||
</Button>
|
||||
</form>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
<ProfilePicture column>
|
||||
<LabelText>{t("Logo")}</LabelText>
|
||||
<AvatarContainer>
|
||||
<ImageUpload
|
||||
onSuccess={handleAvatarUpload}
|
||||
onError={handleAvatarError}
|
||||
submitText={t("Crop logo")}
|
||||
borderRadius={0}
|
||||
>
|
||||
<Avatar src={avatarUrl} />
|
||||
<Flex auto align="center" justify="center">
|
||||
<Trans>Upload</Trans>
|
||||
</Flex>
|
||||
</ImageUpload>
|
||||
</AvatarContainer>
|
||||
</ProfilePicture>
|
||||
<form onSubmit={handleSubmit} ref={form}>
|
||||
<Input
|
||||
label={t("Name")}
|
||||
name="name"
|
||||
autoComplete="organization"
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
required
|
||||
short
|
||||
/>
|
||||
{env.SUBDOMAINS_ENABLED && (
|
||||
<>
|
||||
<Input
|
||||
label={t("Subdomain")}
|
||||
name="subdomain"
|
||||
value={subdomain || ""}
|
||||
onChange={handleSubdomainChange}
|
||||
autoComplete="off"
|
||||
minLength={4}
|
||||
maxLength={32}
|
||||
short
|
||||
/>
|
||||
{subdomain && (
|
||||
<HelpText small>
|
||||
<Trans>Your knowledge base will be accessible at</Trans>{" "}
|
||||
<strong>{subdomain}.getoutline.com</strong>
|
||||
</HelpText>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Button type="submit" disabled={auth.isSaving || !isValid}>
|
||||
{auth.isSaving ? `${t("Saving")}…` : t("Save")}
|
||||
</Button>
|
||||
</form>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
const ProfilePicture = styled(Flex)`
|
||||
@@ -186,4 +176,4 @@ const Avatar = styled.img`
|
||||
${avatarStyles};
|
||||
`;
|
||||
|
||||
export default inject("auth", "ui")(Details);
|
||||
export default observer(Details);
|
||||
|
||||
@@ -1,137 +1,140 @@
|
||||
// @flow
|
||||
import { debounce } from "lodash";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EmailIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import NotificationSettingsStore from "stores/NotificationSettingsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import Heading from "components/Heading";
|
||||
import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import Notice from "components/Notice";
|
||||
import Scene from "components/Scene";
|
||||
import Subheading from "components/Subheading";
|
||||
|
||||
import NotificationListItem from "./components/NotificationListItem";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
ui: UiStore,
|
||||
auth: AuthStore,
|
||||
notificationSettings: NotificationSettingsStore,
|
||||
};
|
||||
function Notifications() {
|
||||
const { notificationSettings, ui } = useStores();
|
||||
const user = useCurrentUser();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const options = [
|
||||
{
|
||||
event: "documents.publish",
|
||||
title: "Document published",
|
||||
description: "Receive a notification whenever a new document is published",
|
||||
},
|
||||
{
|
||||
event: "documents.update",
|
||||
title: "Document updated",
|
||||
description: "Receive a notification when a document you created is edited",
|
||||
},
|
||||
{
|
||||
event: "collections.create",
|
||||
title: "Collection created",
|
||||
description: "Receive a notification whenever a new collection is created",
|
||||
},
|
||||
{
|
||||
separator: true,
|
||||
},
|
||||
{
|
||||
event: "emails.onboarding",
|
||||
title: "Getting started",
|
||||
description:
|
||||
"Tips on getting started with Outline`s features and functionality",
|
||||
},
|
||||
{
|
||||
event: "emails.features",
|
||||
title: "New features",
|
||||
description: "Receive an email when new features of note are added",
|
||||
},
|
||||
];
|
||||
const options = [
|
||||
{
|
||||
event: "documents.publish",
|
||||
title: t("Document published"),
|
||||
description: t(
|
||||
"Receive a notification whenever a new document is published"
|
||||
),
|
||||
},
|
||||
{
|
||||
event: "documents.update",
|
||||
title: t("Document updated"),
|
||||
description: t(
|
||||
"Receive a notification when a document you created is edited"
|
||||
),
|
||||
},
|
||||
{
|
||||
event: "collections.create",
|
||||
title: t("Collection created"),
|
||||
description: t(
|
||||
"Receive a notification whenever a new collection is created"
|
||||
),
|
||||
},
|
||||
{
|
||||
separator: true,
|
||||
},
|
||||
{
|
||||
event: "emails.onboarding",
|
||||
title: t("Getting started"),
|
||||
description: t(
|
||||
"Tips on getting started with Outline`s features and functionality"
|
||||
),
|
||||
},
|
||||
{
|
||||
event: "emails.features",
|
||||
title: t("New features"),
|
||||
description: t("Receive an email when new features of note are added"),
|
||||
},
|
||||
];
|
||||
|
||||
@observer
|
||||
class Notifications extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.notificationSettings.fetchPage();
|
||||
}
|
||||
React.useEffect(() => {
|
||||
notificationSettings.fetchPage();
|
||||
}, [notificationSettings]);
|
||||
|
||||
handleChange = async (ev: SyntheticInputEvent<>) => {
|
||||
const { notificationSettings } = this.props;
|
||||
const setting = notificationSettings.getByEvent(ev.target.name);
|
||||
|
||||
if (ev.target.checked) {
|
||||
await notificationSettings.save({
|
||||
event: ev.target.name,
|
||||
});
|
||||
} else if (setting) {
|
||||
await notificationSettings.delete(setting);
|
||||
}
|
||||
|
||||
this.showSuccessMessage();
|
||||
};
|
||||
|
||||
showSuccessMessage = debounce(() => {
|
||||
this.props.ui.showToast("Notifications saved", { type: "success" });
|
||||
const showSuccessMessage = debounce(() => {
|
||||
ui.showToast(t("Notifications saved"), { type: "success" });
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
const { notificationSettings, auth } = this.props;
|
||||
const showSuccessNotice = window.location.search === "?success";
|
||||
const { user, team } = auth;
|
||||
if (!team || !user) return null;
|
||||
const handleChange = React.useCallback(
|
||||
async (ev: SyntheticInputEvent<>) => {
|
||||
const setting = notificationSettings.getByEvent(ev.target.name);
|
||||
|
||||
return (
|
||||
<Scene title="Notifications" icon={<EmailIcon color="currentColor" />}>
|
||||
{showSuccessNotice && (
|
||||
<Notice>
|
||||
if (ev.target.checked) {
|
||||
await notificationSettings.save({
|
||||
event: ev.target.name,
|
||||
});
|
||||
} else if (setting) {
|
||||
await notificationSettings.delete(setting);
|
||||
}
|
||||
|
||||
showSuccessMessage();
|
||||
},
|
||||
[notificationSettings, showSuccessMessage]
|
||||
);
|
||||
|
||||
const showSuccessNotice = window.location.search === "?success";
|
||||
|
||||
return (
|
||||
<Scene title={t("Notifications")} icon={<EmailIcon color="currentColor" />}>
|
||||
{showSuccessNotice && (
|
||||
<Notice>
|
||||
<Trans>
|
||||
Unsubscription successful. Your notification settings were updated
|
||||
</Notice>
|
||||
)}
|
||||
<Heading>Notifications</Heading>
|
||||
<HelpText>
|
||||
</Trans>
|
||||
</Notice>
|
||||
)}
|
||||
<Heading>{t("Notifications")}</Heading>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
Manage when and where you receive email notifications from Outline.
|
||||
Your email address can be updated in your SSO provider.
|
||||
</HelpText>
|
||||
</Trans>
|
||||
</HelpText>
|
||||
<Input
|
||||
type="email"
|
||||
value={user.email}
|
||||
label={t("Email address")}
|
||||
readOnly
|
||||
short
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
value={user.email}
|
||||
label="Email address"
|
||||
readOnly
|
||||
short
|
||||
/>
|
||||
<Subheading>{t("Notifications")}</Subheading>
|
||||
|
||||
<Subheading>Notifications</Subheading>
|
||||
{options.map((option, index) => {
|
||||
if (option.separator) return <Separator key={`separator-${index}`} />;
|
||||
|
||||
{options.map((option, index) => {
|
||||
if (option.separator) return <Separator key={`separator-${index}`} />;
|
||||
const setting = notificationSettings.getByEvent(option.event);
|
||||
|
||||
const setting = notificationSettings.getByEvent(option.event);
|
||||
|
||||
return (
|
||||
<NotificationListItem
|
||||
key={option.event}
|
||||
onChange={this.handleChange}
|
||||
setting={setting}
|
||||
disabled={
|
||||
(setting && setting.isSaving) || notificationSettings.isFetching
|
||||
}
|
||||
{...option}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<NotificationListItem
|
||||
key={option.event}
|
||||
onChange={handleChange}
|
||||
setting={setting}
|
||||
disabled={
|
||||
(setting && setting.isSaving) || notificationSettings.isFetching
|
||||
}
|
||||
{...option}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
const Separator = styled.hr`
|
||||
padding-bottom: 12px;
|
||||
`;
|
||||
|
||||
export default inject("notificationSettings", "auth", "ui")(Notifications);
|
||||
export default observer(Notifications);
|
||||
|
||||
@@ -1,97 +1,94 @@
|
||||
// @flow
|
||||
import { debounce } from "lodash";
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { PadlockIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import { useState } from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import Checkbox from "components/Checkbox";
|
||||
import Heading from "components/Heading";
|
||||
import HelpText from "components/HelpText";
|
||||
import Scene from "components/Scene";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
};
|
||||
function Security() {
|
||||
const { auth, ui } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const [sharing, setSharing] = useState(team.documentEmbeds);
|
||||
const [documentEmbeds, setDocumentEmbeds] = useState(team.guestSignin);
|
||||
const [guestSignin, setGuestSignin] = useState(team.sharing);
|
||||
|
||||
@observer
|
||||
class Security extends React.Component<Props> {
|
||||
form: ?HTMLFormElement;
|
||||
|
||||
@observable sharing: boolean;
|
||||
@observable documentEmbeds: boolean;
|
||||
@observable guestSignin: boolean;
|
||||
|
||||
componentDidMount() {
|
||||
const { auth } = this.props;
|
||||
if (auth.team) {
|
||||
this.documentEmbeds = auth.team.documentEmbeds;
|
||||
this.guestSignin = auth.team.guestSignin;
|
||||
this.sharing = auth.team.sharing;
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = async (ev: SyntheticInputEvent<*>) => {
|
||||
switch (ev.target.name) {
|
||||
case "sharing":
|
||||
this.sharing = ev.target.checked;
|
||||
break;
|
||||
case "documentEmbeds":
|
||||
this.documentEmbeds = ev.target.checked;
|
||||
break;
|
||||
case "guestSignin":
|
||||
this.guestSignin = ev.target.checked;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
await this.props.auth.updateTeam({
|
||||
sharing: this.sharing,
|
||||
documentEmbeds: this.documentEmbeds,
|
||||
guestSignin: this.guestSignin,
|
||||
});
|
||||
this.showSuccessMessage();
|
||||
};
|
||||
|
||||
showSuccessMessage = debounce(() => {
|
||||
this.props.ui.showToast("Settings saved", { type: "success" });
|
||||
const showSuccessMessage = debounce(() => {
|
||||
ui.showToast(t("Settings saved"), { type: "success" });
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Scene title="Security" icon={<PadlockIcon color="currentColor" />}>
|
||||
<Heading>Security</Heading>
|
||||
<HelpText>
|
||||
const handleChange = React.useCallback(
|
||||
async (ev: SyntheticInputEvent<*>) => {
|
||||
switch (ev.target.name) {
|
||||
case "sharing":
|
||||
setSharing(ev.target.checked);
|
||||
break;
|
||||
case "documentEmbeds":
|
||||
setDocumentEmbeds(ev.target.checked);
|
||||
break;
|
||||
case "guestSignin":
|
||||
setGuestSignin(ev.target.checked);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
await auth.updateTeam({
|
||||
sharing,
|
||||
documentEmbeds,
|
||||
guestSignin,
|
||||
});
|
||||
|
||||
showSuccessMessage();
|
||||
},
|
||||
[auth, sharing, documentEmbeds, guestSignin, showSuccessMessage]
|
||||
);
|
||||
|
||||
return (
|
||||
<Scene title={t("Security")} icon={<PadlockIcon color="currentColor" />}>
|
||||
<Heading>
|
||||
<Trans>Security</Trans>
|
||||
</Heading>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
Settings that impact the access, security, and content of your
|
||||
knowledge base.
|
||||
</HelpText>
|
||||
</Trans>
|
||||
</HelpText>
|
||||
|
||||
<Checkbox
|
||||
label="Allow email authentication"
|
||||
name="guestSignin"
|
||||
checked={this.guestSignin}
|
||||
onChange={this.handleChange}
|
||||
note="When enabled, users can sign-in using their email address"
|
||||
/>
|
||||
<Checkbox
|
||||
label="Public document sharing"
|
||||
name="sharing"
|
||||
checked={this.sharing}
|
||||
onChange={this.handleChange}
|
||||
note="When enabled, documents can be shared publicly on the internet by any team member"
|
||||
/>
|
||||
<Checkbox
|
||||
label="Rich service embeds"
|
||||
name="documentEmbeds"
|
||||
checked={this.documentEmbeds}
|
||||
onChange={this.handleChange}
|
||||
note="Links to supported services are shown as rich embeds within your documents"
|
||||
/>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
<Checkbox
|
||||
label={t("Allow email authentication")}
|
||||
name="guestSignin"
|
||||
checked={guestSignin}
|
||||
onChange={handleChange}
|
||||
note={t("When enabled, users can sign-in using their email address")}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("Public document sharing")}
|
||||
name="sharing"
|
||||
checked={sharing}
|
||||
onChange={handleChange}
|
||||
note={t(
|
||||
"When enabled, documents can be shared publicly on the internet by any team member"
|
||||
)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("Rich service embeds")}
|
||||
name="documentEmbeds"
|
||||
checked={documentEmbeds}
|
||||
onChange={handleChange}
|
||||
note={t(
|
||||
"Links to supported services are shown as rich embeds within your documents"
|
||||
)}
|
||||
/>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
export default inject("auth", "ui")(Security);
|
||||
export default observer(Security);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import Button from "components/Button";
|
||||
import Heading from "components/Heading";
|
||||
import HelpText from "components/HelpText";
|
||||
@@ -7,13 +8,16 @@ import Scene from "components/Scene";
|
||||
import ZapierIcon from "components/ZapierIcon";
|
||||
|
||||
function Zapier() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Scene title="Zapier" icon={<ZapierIcon color="currentColor" />}>
|
||||
<Heading>Zapier</Heading>
|
||||
<Scene title={t("Zapier")} icon={<ZapierIcon color="currentColor" />}>
|
||||
<Heading>{t("Zapier")}</Heading>
|
||||
<HelpText>
|
||||
Zapier is a platform that allows Outline to easily integrate with
|
||||
thousands of other business tools. Head over to Zapier to setup a "Zap"
|
||||
and start programmatically interacting with Outline.
|
||||
<Trans>
|
||||
Zapier is a platform that allows Outline to easily integrate with
|
||||
thousands of other business tools. Head over to Zapier to setup a
|
||||
"Zap" and start programmatically interacting with Outline.'
|
||||
</Trans>
|
||||
</HelpText>
|
||||
<p>
|
||||
<Button
|
||||
@@ -21,7 +25,7 @@ function Zapier() {
|
||||
(window.location.href = "https://zapier.com/apps/outline")
|
||||
}
|
||||
>
|
||||
Open Zapier →
|
||||
{t("Open Zapier")} →
|
||||
</Button>
|
||||
</p>
|
||||
</Scene>
|
||||
|
||||
Reference in New Issue
Block a user