From 7221e51b96826b9e0a5d37af250959d712275fff Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Apr 2021 18:46:58 -0700 Subject: [PATCH] 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 --- app/routes/settings.js | 4 +- app/scenes/Settings/Details.js | 13 +- app/scenes/Settings/Export.js | 73 ---------- app/scenes/Settings/Groups.js | 161 +++++++++------------ app/scenes/Settings/ImportExport.js | 18 ++- app/scenes/Settings/Notifications.js | 14 +- app/scenes/Settings/People.js | 14 +- app/scenes/Settings/Profile.js | 13 +- app/scenes/Settings/Security.js | 13 +- app/scenes/Settings/Shares.js | 12 +- app/scenes/Settings/Tokens.js | 14 +- app/scenes/Settings/Zapier.js | 12 +- app/scenes/Settings/index.js | 3 - shared/i18n/locales/en_US/translation.json | 4 + 14 files changed, 129 insertions(+), 239 deletions(-) delete mode 100644 app/scenes/Settings/Export.js delete mode 100644 app/scenes/Settings/index.js diff --git a/app/routes/settings.js b/app/routes/settings.js index 483a5e2e..79e178c4 100644 --- a/app/routes/settings.js +++ b/app/routes/settings.js @@ -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 ( - + diff --git a/app/scenes/Settings/Details.js b/app/scenes/Settings/Details.js index fa70e18b..6d21df94 100644 --- a/app/scenes/Settings/Details.js +++ b/app/scenes/Settings/Details.js @@ -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 { const avatarUrl = this.avatarUrl || team.avatarUrl; return ( - - -

Details

+ }> + Details These details affect the way that your Outline appears to everyone on the team. @@ -143,7 +142,7 @@ class Details extends React.Component { {isSaving ? "Saving…" : "Save"} -
+ ); } } diff --git a/app/scenes/Settings/Export.js b/app/scenes/Settings/Export.js deleted file mode 100644 index b19b1e72..00000000 --- a/app/scenes/Settings/Export.js +++ /dev/null @@ -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 { - @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 ( - - -

Export Data

- - Exporting your team’s documents may take a little time depending on - the size of your knowledge base. Consider exporting a single document - or collection instead. - - - Still want to export everything in your wiki? We’ll put together a zip - file of your collections and documents in Markdown format and email it - to {auth.user.email}. - - -
- ); - } -} - -export default inject("auth", "ui", "collections")(Export); diff --git a/app/scenes/Settings/Groups.js b/app/scenes/Settings/Groups.js index 3cc60729..ff5cdef8 100644 --- a/app/scenes/Settings/Groups.js +++ b/app/scenes/Settings/Groups.js @@ -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 { - @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 ( - - -

Groups

- + return ( + }> + {t("Groups")} + + Groups can be used to organize and manage the people on your team. - + + - {can.createGroup && ( - - )} - - - - All Groups - - - - - {groups.orderedData.map((group) => ( - ( - - )} - showFacepile - /> - ))} - - - {showEmpty && No groups to see here.} - {showLoading && } - - } + neutral > - - -
- ); - } + {`${t("New group")}…`} + + )} + + {t("All groups")} + {t("No groups have been created yet")}} + fetch={groups.fetchPage} + renderItem={(item) => ( + ( + + )} + showFacepile + /> + )} + /> + + + + + + ); } -export default inject("auth", "groups", "policies")(Groups); +export default observer(Groups); diff --git a/app/scenes/Settings/ImportExport.js b/app/scenes/Settings/ImportExport.js index 50e965de..f8188dde 100644 --- a/app/scenes/Settings/ImportExport.js +++ b/app/scenes/Settings/ImportExport.js @@ -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 ( - - -

{t("Import")}

+ } + > + {t("Import")} It is possible to import a zip file of folders and Markdown files @@ -176,7 +178,7 @@ function ImportExport() { )} -

{t("Export")}

+ {t("Export")} -
+ ); } diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js index d655fae9..d9d490af 100644 --- a/app/scenes/Settings/Notifications.js +++ b/app/scenes/Settings/Notifications.js @@ -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 { if (!team || !user) return null; return ( - + }> {showSuccessNotice && ( Unsubscription successful. Your notification settings were updated )} - - -

Notifications

- + Notifications 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 { /> ); })} -
+ ); } } diff --git a/app/scenes/Settings/People.js b/app/scenes/Settings/People.js index 6a1f2ed0..25d7f406 100644 --- a/app/scenes/Settings/People.js +++ b/app/scenes/Settings/People.js @@ -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 { const { counts } = this.props.users; return ( - - -

{t("People")}

+ }> + {t("People")} Everyone that has signed into Outline appears here. It’s possible @@ -151,7 +149,7 @@ class People extends React.Component { )} -
+ ); } } diff --git a/app/scenes/Settings/Profile.js b/app/scenes/Settings/Profile.js index 3734f02a..4efedaae 100644 --- a/app/scenes/Settings/Profile.js +++ b/app/scenes/Settings/Profile.js @@ -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 { const avatarUrl = this.avatarUrl || user.avatarUrl; return ( - - -

{t("Profile")}

+ }> + {t("Profile")} {t("Photo")} @@ -168,7 +167,7 @@ class Profile extends React.Component { {this.showDeleteModal && ( )} -
+ ); } } diff --git a/app/scenes/Settings/Security.js b/app/scenes/Settings/Security.js index 0e663825..5f19bfce 100644 --- a/app/scenes/Settings/Security.js +++ b/app/scenes/Settings/Security.js @@ -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 { render() { return ( - - -

Security

+ }> + Security Settings that impact the access, security, and content of your knowledge base. @@ -90,7 +89,7 @@ class Security extends React.Component { onChange={this.handleChange} note="Links to supported services are shown as rich embeds within your documents" /> -
+ ); } } diff --git a/app/scenes/Settings/Shares.js b/app/scenes/Settings/Shares.js index b606d56b..68f90e9e 100644 --- a/app/scenes/Settings/Shares.js +++ b/app/scenes/Settings/Shares.js @@ -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 ( - - -

{t("Share Links")}

+ }> + {t("Share Links")} Documents that have been shared are listed below. Anyone that has the @@ -49,7 +49,7 @@ function Shares() { fetch={shares.fetchPage} renderItem={(item) => } /> -
+ ); } diff --git a/app/scenes/Settings/Tokens.js b/app/scenes/Settings/Tokens.js index 8f826786..09b2a48b 100644 --- a/app/scenes/Settings/Tokens.js +++ b/app/scenes/Settings/Tokens.js @@ -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 { const hasApiKeys = apiKeys.orderedData.length > 0; return ( - - -

API Tokens

- + }> + API Tokens 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 { disabled={apiKeys.isSaving} /> -
+ ); } } diff --git a/app/scenes/Settings/Zapier.js b/app/scenes/Settings/Zapier.js index 076d1d84..6899375c 100644 --- a/app/scenes/Settings/Zapier.js +++ b/app/scenes/Settings/Zapier.js @@ -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 ( - - -

Zapier

+ }> + Zapier 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 →

-
+ ); } diff --git a/app/scenes/Settings/index.js b/app/scenes/Settings/index.js deleted file mode 100644 index c8f34287..00000000 --- a/app/scenes/Settings/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow -import Profile from "./Profile"; -export default Profile; diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 78ad5947..b953aeca 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -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.",