chore: Move settings screens to `Scene` component (#2092)

* chore: Convert groups and people settings screens to Scene/functional

* chore: ImportExport to Scene component

* Remaining settings scenes
This commit is contained in:
Tom Moor 2021-04-27 18:46:58 -07:00 committed by GitHub
parent b89f4c36f4
commit 7221e51b96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 129 additions and 239 deletions

View File

@ -1,12 +1,12 @@
// @flow
import * as React from "react";
import { Switch } from "react-router-dom";
import Settings from "scenes/Settings";
import Details from "scenes/Settings/Details";
import Groups from "scenes/Settings/Groups";
import ImportExport from "scenes/Settings/ImportExport";
import Notifications from "scenes/Settings/Notifications";
import People from "scenes/Settings/People";
import Profile from "scenes/Settings/Profile";
import Security from "scenes/Settings/Security";
import Shares from "scenes/Settings/Shares";
import Slack from "scenes/Settings/Slack";
@ -17,7 +17,7 @@ import Route from "components/ProfiledRoute";
export default function SettingsRoutes() {
return (
<Switch>
<Route exact path="/settings" component={Settings} />
<Route exact path="/settings" component={Profile} />
<Route exact path="/settings/details" component={Details} />
<Route exact path="/settings/security" component={Security} />
<Route exact path="/settings/people" component={People} />

View File

@ -1,17 +1,17 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { TeamIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import AuthStore from "stores/AuthStore";
import UiStore from "stores/UiStore";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Flex from "components/Flex";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import Input, { LabelText } from "components/Input";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import ImageUpload from "./components/ImageUpload";
import env from "env";
@ -85,9 +85,8 @@ class Details extends React.Component<Props> {
const avatarUrl = this.avatarUrl || team.avatarUrl;
return (
<CenteredContent>
<PageTitle title="Details" />
<h1>Details</h1>
<Scene title="Details" icon={<TeamIcon color="currentColor" />}>
<Heading>Details</Heading>
<HelpText>
These details affect the way that your Outline appears to everyone on
the team.
@ -143,7 +142,7 @@ class Details extends React.Component<Props> {
{isSaving ? "Saving…" : "Save"}
</Button>
</form>
</CenteredContent>
</Scene>
);
}
}

View File

@ -1,73 +0,0 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import * as React from "react";
import AuthStore from "stores/AuthStore";
import CollectionsStore from "stores/CollectionsStore";
import UiStore from "stores/UiStore";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import HelpText from "components/HelpText";
import PageTitle from "components/PageTitle";
type Props = {
auth: AuthStore,
collections: CollectionsStore,
ui: UiStore,
};
@observer
class Export extends React.Component<Props> {
@observable isLoading: boolean = false;
@observable isExporting: boolean = false;
handleSubmit = async (ev: SyntheticEvent<>) => {
ev.preventDefault();
this.isLoading = true;
try {
await this.props.collections.export();
this.isExporting = true;
this.props.ui.showToast("Export in progress…", { type: "info" });
} finally {
this.isLoading = false;
}
};
render() {
const { auth } = this.props;
if (!auth.user) return null;
return (
<CenteredContent>
<PageTitle title="Export Data" />
<h1>Export Data</h1>
<HelpText>
Exporting your teams documents may take a little time depending on
the size of your knowledge base. Consider exporting a single document
or collection instead.
</HelpText>
<HelpText>
Still want to export everything in your wiki? Well put together a zip
file of your collections and documents in Markdown format and email it
to <strong>{auth.user.email}</strong>.
</HelpText>
<Button
type="submit"
onClick={this.handleSubmit}
disabled={this.isLoading || this.isExporting}
primary
>
{this.isExporting
? "Export Requested"
: this.isLoading
? "Requesting Export…"
: "Export All Data"}
</Button>
</CenteredContent>
);
}
}
export default inject("auth", "ui", "collections")(Export);

View File

@ -1,114 +1,83 @@
// @flow
import invariant from "invariant";
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { PlusIcon } from "outline-icons";
import { observer } from "mobx-react";
import { PlusIcon, GroupIcon } from "outline-icons";
import * as React from "react";
import { type Match } from "react-router-dom";
import AuthStore from "stores/AuthStore";
import GroupsStore from "stores/GroupsStore";
import PoliciesStore from "stores/PoliciesStore";
import { useTranslation, Trans } from "react-i18next";
import GroupNew from "scenes/GroupNew";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Empty from "components/Empty";
import GroupListItem from "components/GroupListItem";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import List from "components/List";
import { ListPlaceholder } from "components/LoadingPlaceholder";
import Modal from "components/Modal";
import PageTitle from "components/PageTitle";
import Tab from "components/Tab";
import Tabs from "components/Tabs";
import PaginatedList from "components/PaginatedList";
import Scene from "components/Scene";
import Subheading from "components/Subheading";
import useCurrentTeam from "hooks/useCurrentTeam";
import useStores from "hooks/useStores";
import GroupMenu from "menus/GroupMenu";
type Props = {
auth: AuthStore,
groups: GroupsStore,
policies: PoliciesStore,
match: Match,
};
function Groups() {
const { t } = useTranslation();
const { policies, groups } = useStores();
const team = useCurrentTeam();
const can = policies.abilities(team.id);
const [newGroupModalOpen, setNewGroupModalOpen] = React.useState(false);
@observer
class Groups extends React.Component<Props> {
@observable newGroupModalOpen: boolean = false;
const handleNewGroupModalOpen = React.useCallback(() => {
setNewGroupModalOpen(true);
}, []);
componentDidMount() {
this.props.groups.fetchPage({ limit: 100 });
}
const handleNewGroupModalClose = React.useCallback(() => {
setNewGroupModalOpen(false);
}, []);
handleNewGroupModalOpen = () => {
this.newGroupModalOpen = true;
};
handleNewGroupModalClose = () => {
this.newGroupModalOpen = false;
};
render() {
const { auth, policies, groups } = this.props;
const currentUser = auth.user;
const team = auth.team;
invariant(currentUser, "User should exist");
invariant(team, "Team should exist");
const showLoading = groups.isFetching && !groups.orderedData.length;
const showEmpty = groups.isLoaded && !groups.orderedData.length;
const can = policies.abilities(team.id);
return (
<CenteredContent>
<PageTitle title="People" />
<h1>Groups</h1>
<HelpText>
return (
<Scene title={t("Groups")} icon={<GroupIcon color="currentColor" />}>
<Heading>{t("Groups")}</Heading>
<HelpText>
<Trans>
Groups can be used to organize and manage the people on your team.
</HelpText>
</Trans>
</HelpText>
{can.createGroup && (
<Button
type="button"
onClick={this.handleNewGroupModalOpen}
icon={<PlusIcon />}
neutral
>
New group
</Button>
)}
<Tabs>
<Tab to="/settings/groups" exact>
All Groups
</Tab>
</Tabs>
<List>
{groups.orderedData.map((group) => (
<GroupListItem
key={group.id}
group={group}
renderActions={({ openMembersModal }) => (
<GroupMenu group={group} onMembers={openMembersModal} />
)}
showFacepile
/>
))}
</List>
{showEmpty && <Empty>No groups to see here.</Empty>}
{showLoading && <ListPlaceholder count={5} />}
<Modal
title="Create a group"
onRequestClose={this.handleNewGroupModalClose}
isOpen={this.newGroupModalOpen}
{can.createGroup && (
<Button
type="button"
onClick={handleNewGroupModalOpen}
icon={<PlusIcon />}
neutral
>
<GroupNew onSubmit={this.handleNewGroupModalClose} />
</Modal>
</CenteredContent>
);
}
{`${t("New group")}`}
</Button>
)}
<Subheading>{t("All groups")}</Subheading>
<PaginatedList
items={groups.orderedData}
empty={<Empty>{t("No groups have been created yet")}</Empty>}
fetch={groups.fetchPage}
renderItem={(item) => (
<GroupListItem
key={item.id}
group={item}
renderActions={({ openMembersModal }) => (
<GroupMenu group={item} onMembers={openMembersModal} />
)}
showFacepile
/>
)}
/>
<Modal
title={t("Create a group")}
onRequestClose={handleNewGroupModalClose}
isOpen={newGroupModalOpen}
>
<GroupNew onSubmit={handleNewGroupModalClose} />
</Modal>
</Scene>
);
}
export default inject("auth", "groups", "policies")(Groups);
export default observer(Groups);

View File

@ -1,17 +1,17 @@
// @flow
import invariant from "invariant";
import { observer } from "mobx-react";
import { CollectionIcon } from "outline-icons";
import { CollectionIcon, DocumentIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import { parseOutlineExport } from "shared/utils/zip";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import Notice from "components/Notice";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import useCurrentUser from "hooks/useCurrentUser";
import useStores from "hooks/useStores";
import getDataTransferFiles from "utils/getDataTransferFiles";
@ -107,9 +107,11 @@ function ImportExport() {
const isImportable = hasCollections && hasDocuments;
return (
<CenteredContent>
<PageTitle title={`${t("Import")} / ${t("Export")}`} />
<h1>{t("Import")}</h1>
<Scene
title={`${t("Import")} / ${t("Export")}`}
icon={<DocumentIcon color="currentColor" />}
>
<Heading>{t("Import")}</Heading>
<HelpText>
<Trans>
It is possible to import a zip file of folders and Markdown files
@ -176,7 +178,7 @@ function ImportExport() {
</Button>
)}
<h1>{t("Export")}</h1>
<Heading>{t("Export")}</Heading>
<HelpText>
<Trans
defaults="A full export might take some time, consider exporting a single document or collection if possible. Well put together a zip of all your documents in Markdown format and email it to <em>{{ userEmail }}</em>."
@ -196,7 +198,7 @@ function ImportExport() {
? `${t("Requesting Export")}`
: t("Export Data")}
</Button>
</CenteredContent>
</Scene>
);
}

View File

@ -1,16 +1,17 @@
// @flow
import { debounce } from "lodash";
import { observer, inject } from "mobx-react";
import { EmailIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import AuthStore from "stores/AuthStore";
import NotificationSettingsStore from "stores/NotificationSettingsStore";
import UiStore from "stores/UiStore";
import CenteredContent from "components/CenteredContent";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import Input from "components/Input";
import Notice from "components/Notice";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import Subheading from "components/Subheading";
import NotificationListItem from "./components/NotificationListItem";
@ -85,16 +86,13 @@ class Notifications extends React.Component<Props> {
if (!team || !user) return null;
return (
<CenteredContent>
<Scene title="Notifications" icon={<EmailIcon color="currentColor" />}>
{showSuccessNotice && (
<Notice>
Unsubscription successful. Your notification settings were updated
</Notice>
)}
<PageTitle title="Notifications" />
<h1>Notifications</h1>
<Heading>Notifications</Heading>
<HelpText>
Manage when and where you receive email notifications from Outline.
Your email address can be updated in your SSO provider.
@ -127,7 +125,7 @@ class Notifications extends React.Component<Props> {
/>
);
})}
</CenteredContent>
</Scene>
);
}
}

View File

@ -2,7 +2,7 @@
import invariant from "invariant";
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { PlusIcon } from "outline-icons";
import { PlusIcon, UserIcon } from "outline-icons";
import * as React from "react";
import { withTranslation, type TFunction, Trans } from "react-i18next";
import { type Match } from "react-router-dom";
@ -12,15 +12,14 @@ import UsersStore from "stores/UsersStore";
import Invite from "scenes/Invite";
import Bubble from "components/Bubble";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Empty from "components/Empty";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import Modal from "components/Modal";
import PageTitle from "components/PageTitle";
import PaginatedList from "components/PaginatedList";
import Scene from "components/Scene";
import Tab from "components/Tab";
import Tabs, { Separator } from "components/Tabs";
import UserListItem from "./components/UserListItem";
type Props = {
@ -79,9 +78,8 @@ class People extends React.Component<Props> {
const { counts } = this.props.users;
return (
<CenteredContent>
<PageTitle title={t("People")} />
<h1>{t("People")}</h1>
<Scene title={t("People")} icon={<UserIcon color="currentColor" />}>
<Heading>{t("People")}</Heading>
<HelpText>
<Trans>
Everyone that has signed into Outline appears here. Its possible
@ -151,7 +149,7 @@ class People extends React.Component<Props> {
<Invite onSubmit={this.handleInviteModalClose} />
</Modal>
)}
</CenteredContent>
</Scene>
);
}
}

View File

@ -1,21 +1,21 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { ProfileIcon } from "outline-icons";
import * as React from "react";
import { Trans, withTranslation, type TFunction } from "react-i18next";
import styled from "styled-components";
import { languageOptions } from "shared/i18n";
import AuthStore from "stores/AuthStore";
import UiStore from "stores/UiStore";
import UserDelete from "scenes/UserDelete";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Flex from "components/Flex";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import Input, { LabelText } from "components/Input";
import InputSelect from "components/InputSelect";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import ImageUpload from "./components/ImageUpload";
type Props = {
@ -99,9 +99,8 @@ class Profile extends React.Component<Props> {
const avatarUrl = this.avatarUrl || user.avatarUrl;
return (
<CenteredContent>
<PageTitle title={t("Profile")} />
<h1>{t("Profile")}</h1>
<Scene title={t("Profile")} icon={<ProfileIcon color="currentColor" />}>
<Heading>{t("Profile")}</Heading>
<ProfilePicture column>
<LabelText>{t("Photo")}</LabelText>
<AvatarContainer>
@ -168,7 +167,7 @@ class Profile extends React.Component<Props> {
{this.showDeleteModal && (
<UserDelete onRequestClose={this.toggleDeleteAccount} />
)}
</CenteredContent>
</Scene>
);
}
}

View File

@ -2,14 +2,14 @@
import { debounce } from "lodash";
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { PadlockIcon } from "outline-icons";
import * as React from "react";
import AuthStore from "stores/AuthStore";
import UiStore from "stores/UiStore";
import CenteredContent from "components/CenteredContent";
import Checkbox from "components/Checkbox";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
type Props = {
auth: AuthStore,
@ -61,9 +61,8 @@ class Security extends React.Component<Props> {
render() {
return (
<CenteredContent>
<PageTitle title="Security" />
<h1>Security</h1>
<Scene title="Security" icon={<PadlockIcon color="currentColor" />}>
<Heading>Security</Heading>
<HelpText>
Settings that impact the access, security, and content of your
knowledge base.
@ -90,7 +89,7 @@ class Security extends React.Component<Props> {
onChange={this.handleChange}
note="Links to supported services are shown as rich embeds within your documents"
/>
</CenteredContent>
</Scene>
);
}
}

View File

@ -1,13 +1,14 @@
// @flow
import { observer } from "mobx-react";
import { LinkIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { Link } from "react-router-dom";
import CenteredContent from "components/CenteredContent";
import Empty from "components/Empty";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import PageTitle from "components/PageTitle";
import PaginatedList from "components/PaginatedList";
import Scene from "components/Scene";
import Subheading from "components/Subheading";
import ShareListItem from "./components/ShareListItem";
import useCurrentTeam from "hooks/useCurrentTeam";
@ -21,9 +22,8 @@ function Shares() {
const can = policies.abilities(team.id);
return (
<CenteredContent>
<PageTitle title={t("Share Links")} />
<h1>{t("Share Links")}</h1>
<Scene title={t("Share Links")} icon={<LinkIcon color="currentColor" />}>
<Heading>{t("Share Links")}</Heading>
<HelpText>
<Trans>
Documents that have been shared are listed below. Anyone that has the
@ -49,7 +49,7 @@ function Shares() {
fetch={shares.fetchPage}
renderItem={(item) => <ShareListItem key={item.id} share={item} />}
/>
</CenteredContent>
</Scene>
);
}

View File

@ -1,16 +1,16 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { CodeIcon } from "outline-icons";
import * as React from "react";
import ApiKeysStore from "stores/ApiKeysStore";
import UiStore from "stores/UiStore";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import Input from "components/Input";
import List from "components/List";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import TokenListItem from "./components/TokenListItem";
type Props = {
@ -45,10 +45,8 @@ class Tokens extends React.Component<Props> {
const hasApiKeys = apiKeys.orderedData.length > 0;
return (
<CenteredContent>
<PageTitle title="API Tokens" />
<h1>API Tokens</h1>
<Scene title="API Tokens" icon={<CodeIcon color="currentColor" />}>
<Heading>API Tokens</Heading>
<HelpText>
You can create an unlimited amount of personal tokens to authenticate
with the API. For more details about the API take a look at the{" "}
@ -83,7 +81,7 @@ class Tokens extends React.Component<Props> {
disabled={apiKeys.isSaving}
/>
</form>
</CenteredContent>
</Scene>
);
}
}

View File

@ -1,15 +1,15 @@
// @flow
import * as React from "react";
import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import Heading from "components/Heading";
import HelpText from "components/HelpText";
import PageTitle from "components/PageTitle";
import Scene from "components/Scene";
import ZapierIcon from "components/ZapierIcon";
function Zapier() {
return (
<CenteredContent>
<PageTitle title="Zapier" />
<h1>Zapier</h1>
<Scene title="Zapier" icon={<ZapierIcon color="currentColor" />}>
<Heading>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"
@ -24,7 +24,7 @@ function Zapier() {
Open Zapier
</Button>
</p>
</CenteredContent>
</Scene>
);
}

View File

@ -1,3 +0,0 @@
// @flow
import Profile from "./Profile";
export default Profile;

View File

@ -361,6 +361,10 @@
"by {{ name }}": "by {{ name }}",
"Last accessed": "Last accessed",
"Add to Slack": "Add to Slack",
"Groups can be used to organize and manage the people on your team.": "Groups can be used to organize and manage the people on your team.",
"New group": "New group",
"All groups": "All groups",
"No groups have been created yet": "No groups have been created yet",
"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.",