diff --git a/README.md b/README.md index 2f5c80e8..2e65a06c 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,6 @@ Backend is driven by [Koa](http://koajs.com/) (API, web server), [Sequelize](htt - `server/commands` - Domain logic, currently being refactored from /models - `server/emails` - React rendered email templates - `server/models` - Database models -- `server/pages` - Server-side rendered public pages - `server/policies` - Authorization logic - `server/presenters` - API responses for database models - `server/test` - Test helps and support diff --git a/app/components/Actions.js b/app/components/Actions.js index 920f3899..2905cd03 100644 --- a/app/components/Actions.js +++ b/app/components/Actions.js @@ -1,7 +1,7 @@ // @flow import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; export const Action = styled(Flex)` justify-content: center; diff --git a/app/components/Alert.js b/app/components/Alert.js deleted file mode 100644 index 629a3c22..00000000 --- a/app/components/Alert.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow -import * as React from "react"; -import { observer } from "mobx-react"; -import Flex from "shared/components/Flex"; -import styled from "styled-components"; - -type Props = { - children: React.Node, - type?: "info" | "success" | "warning" | "danger" | "offline", -}; - -@observer -class Alert extends React.Component { - defaultProps = { - type: "info", - }; - - render() { - return ( - - {this.props.children} - - ); - } -} - -const Container = styled(Flex)` - height: $headerHeight; - color: ${props => props.theme.white}; - font-size: 14px; - line-height: 1; - - background-color: ${({ theme, type }) => theme.color[type]}; -`; - -export default Alert; diff --git a/app/components/Authenticated.js b/app/components/Authenticated.js index a81a8200..a4fc3fd4 100644 --- a/app/components/Authenticated.js +++ b/app/components/Authenticated.js @@ -1,6 +1,7 @@ // @flow import * as React from "react"; import { observer, inject } from "mobx-react"; +import { Redirect } from "react-router-dom"; import AuthStore from "stores/AuthStore"; import LoadingIndicator from "components/LoadingIndicator"; import { isCustomSubdomain } from "shared/utils/domains"; @@ -35,7 +36,7 @@ const Authenticated = observer(({ auth, children }: Props) => { } auth.logout(true); - return null; + return ; }); export default inject("auth")(Authenticated); diff --git a/shared/components/Branding.js b/app/components/Branding.js similarity index 100% rename from shared/components/Branding.js rename to app/components/Branding.js diff --git a/shared/components/Breadcrumb.js b/app/components/Breadcrumb.js similarity index 98% rename from shared/components/Breadcrumb.js rename to app/components/Breadcrumb.js index baae01d7..d89ac620 100644 --- a/shared/components/Breadcrumb.js +++ b/app/components/Breadcrumb.js @@ -9,7 +9,7 @@ import { PadlockIcon, GoToIcon, MoreIcon } from "outline-icons"; import Document from "models/Document"; import CollectionsStore from "stores/CollectionsStore"; import { collectionUrl } from "utils/routeHelpers"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import BreadcrumbMenu from "./BreadcrumbMenu"; import CollectionIcon from "components/CollectionIcon"; diff --git a/shared/components/BreadcrumbMenu.js b/app/components/BreadcrumbMenu.js similarity index 100% rename from shared/components/BreadcrumbMenu.js rename to app/components/BreadcrumbMenu.js diff --git a/app/components/Button.js b/app/components/Button.js index 45a7e128..43c1b995 100644 --- a/app/components/Button.js +++ b/app/components/Button.js @@ -5,7 +5,8 @@ import { darken, lighten } from "polished"; import { ExpandedIcon } from "outline-icons"; const RealButton = styled.button` - display: inline-block; + display: ${props => (props.fullwidth ? "block" : "inline-block")}; + width: ${props => (props.fullwidth ? "100%" : "auto")}; margin: 0; padding: 0; border: 0; @@ -126,6 +127,7 @@ export type Props = { children?: React.Node, innerRef?: React.ElementRef, disclosure?: boolean, + fullwidth?: boolean, borderOnHover?: boolean, }; diff --git a/app/components/ButtonLarge.js b/app/components/ButtonLarge.js new file mode 100644 index 00000000..f3611bbd --- /dev/null +++ b/app/components/ButtonLarge.js @@ -0,0 +1,13 @@ +// @flow +import styled from "styled-components"; +import Button, { Inner } from "./Button"; + +const ButtonLarge = styled(Button)` + height: 40px; + + ${Inner} { + padding: 4px 16px; + } +`; + +export default ButtonLarge; diff --git a/app/components/DocumentHistory/DocumentHistory.js b/app/components/DocumentHistory/DocumentHistory.js index 47f2cfc7..bb4af4d4 100644 --- a/app/components/DocumentHistory/DocumentHistory.js +++ b/app/components/DocumentHistory/DocumentHistory.js @@ -11,7 +11,7 @@ import { DEFAULT_PAGINATION_LIMIT } from "stores/BaseStore"; import DocumentsStore from "stores/DocumentsStore"; import RevisionsStore from "stores/RevisionsStore"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { ListPlaceholder } from "components/LoadingPlaceholder"; import Revision from "./components/Revision"; import { documentHistoryUrl } from "utils/routeHelpers"; diff --git a/app/components/DocumentHistory/components/Revision.js b/app/components/DocumentHistory/components/Revision.js index 30ae6c5a..da5778c4 100644 --- a/app/components/DocumentHistory/components/Revision.js +++ b/app/components/DocumentHistory/components/Revision.js @@ -5,8 +5,8 @@ import styled, { withTheme } from "styled-components"; import format from "date-fns/format"; import { MoreIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; -import Time from "shared/components/Time"; +import Flex from "components/Flex"; +import Time from "components/Time"; import Avatar from "components/Avatar"; import RevisionMenu from "menus/RevisionMenu"; import Document from "models/Document"; diff --git a/app/components/DocumentPreview/DocumentPreview.js b/app/components/DocumentPreview/DocumentPreview.js index 48321d48..917c3b42 100644 --- a/app/components/DocumentPreview/DocumentPreview.js +++ b/app/components/DocumentPreview/DocumentPreview.js @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { Link } from "react-router-dom"; import { StarredIcon } from "outline-icons"; import styled, { withTheme } from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Badge from "components/Badge"; import Tooltip from "components/Tooltip"; import Highlight from "components/Highlight"; diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js index 7a60cab3..a8addbf9 100644 --- a/app/components/DropdownMenu/DropdownMenu.js +++ b/app/components/DropdownMenu/DropdownMenu.js @@ -7,7 +7,7 @@ import { PortalWithState } from "react-portal"; import { MoreIcon } from "outline-icons"; import { rgba } from "polished"; import styled from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { fadeAndScaleIn } from "shared/styles/animations"; import NudeButton from "components/NudeButton"; diff --git a/app/components/Facepile.js b/app/components/Facepile.js index 9bd9970f..d2f1d6e2 100644 --- a/app/components/Facepile.js +++ b/app/components/Facepile.js @@ -2,7 +2,7 @@ import * as React from "react"; import { observer, inject } from "mobx-react"; import styled, { withTheme } from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Avatar from "components/Avatar"; import User from "models/User"; diff --git a/shared/components/Flex.js b/app/components/Flex.js similarity index 100% rename from shared/components/Flex.js rename to app/components/Flex.js diff --git a/shared/components/GithubLogo.js b/app/components/GithubLogo.js similarity index 100% rename from shared/components/GithubLogo.js rename to app/components/GithubLogo.js diff --git a/shared/components/GoogleLogo.js b/app/components/GoogleLogo.js similarity index 100% rename from shared/components/GoogleLogo.js rename to app/components/GoogleLogo.js diff --git a/app/components/GroupListItem.js b/app/components/GroupListItem.js index be616c47..d1255d11 100644 --- a/app/components/GroupListItem.js +++ b/app/components/GroupListItem.js @@ -5,7 +5,7 @@ import { observable } from "mobx"; import { observer, inject } from "mobx-react"; import { MAX_AVATAR_DISPLAY } from "shared/constants"; import Modal from "components/Modal"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Facepile from "components/Facepile"; import GroupMembers from "scenes/GroupMembers"; import ListItem from "components/List/Item"; diff --git a/app/components/IconPicker.js b/app/components/IconPicker.js index ccb367fa..c01e4c31 100644 --- a/app/components/IconPicker.js +++ b/app/components/IconPicker.js @@ -27,7 +27,7 @@ import styled from "styled-components"; import { LabelText } from "components/Input"; import { DropdownMenu } from "components/DropdownMenu"; import NudeButton from "components/NudeButton"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; export const icons = { collection: { diff --git a/app/components/Input.js b/app/components/Input.js index ad59d6ab..18ea9be9 100644 --- a/app/components/Input.js +++ b/app/components/Input.js @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { observable } from "mobx"; import styled from "styled-components"; import VisuallyHidden from "components/VisuallyHidden"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; const RealTextarea = styled.textarea` border: 0; diff --git a/app/components/InputLarge.js b/app/components/InputLarge.js new file mode 100644 index 00000000..9df392c0 --- /dev/null +++ b/app/components/InputLarge.js @@ -0,0 +1,13 @@ +// @flow +import styled from "styled-components"; +import Input from "./Input"; + +const InputLarge = styled(Input)` + height: 40px; + + input { + height: 40px; + } +`; + +export default InputLarge; diff --git a/app/components/Labeled.js b/app/components/Labeled.js index 4fda039b..2737e34a 100644 --- a/app/components/Labeled.js +++ b/app/components/Labeled.js @@ -1,7 +1,7 @@ // @flow import * as React from "react"; import { observer } from "mobx-react"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import styled from "styled-components"; type Props = { diff --git a/app/components/Layout.js b/app/components/Layout.js index 8349be3d..d5403e21 100644 --- a/app/components/Layout.js +++ b/app/components/Layout.js @@ -8,7 +8,7 @@ import { observable } from "mobx"; import { observer, inject } from "mobx-react"; import keydown from "react-keydown"; import Analytics from "components/Analytics"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { homeUrl, searchUrl, diff --git a/app/components/List/Item.js b/app/components/List/Item.js index e4104447..bada3769 100644 --- a/app/components/List/Item.js +++ b/app/components/List/Item.js @@ -1,7 +1,7 @@ // @flow import * as React from "react"; import styled from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { image?: React.Node, diff --git a/app/components/List/Placeholder.js b/app/components/List/Placeholder.js index de1fbcb0..f0f86147 100644 --- a/app/components/List/Placeholder.js +++ b/app/components/List/Placeholder.js @@ -4,7 +4,7 @@ import { times } from "lodash"; import styled from "styled-components"; import Mask from "components/Mask"; import Fade from "components/Fade"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { count?: number, diff --git a/app/components/LoadingPlaceholder/ListPlaceholder.js b/app/components/LoadingPlaceholder/ListPlaceholder.js index 586cdfd1..7dff75aa 100644 --- a/app/components/LoadingPlaceholder/ListPlaceholder.js +++ b/app/components/LoadingPlaceholder/ListPlaceholder.js @@ -4,7 +4,7 @@ import { times } from "lodash"; import styled from "styled-components"; import Mask from "components/Mask"; import Fade from "components/Fade"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { count?: number, diff --git a/app/components/LoadingPlaceholder/LoadingPlaceholder.js b/app/components/LoadingPlaceholder/LoadingPlaceholder.js index 75e0fc85..7afb5ea3 100644 --- a/app/components/LoadingPlaceholder/LoadingPlaceholder.js +++ b/app/components/LoadingPlaceholder/LoadingPlaceholder.js @@ -3,7 +3,7 @@ import * as React from "react"; import styled from "styled-components"; import Mask from "components/Mask"; import Fade from "components/Fade"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; export default function LoadingPlaceholder(props: Object) { return ( diff --git a/app/components/Mask.js b/app/components/Mask.js index cc36ee9c..e005d0c6 100644 --- a/app/components/Mask.js +++ b/app/components/Mask.js @@ -3,7 +3,7 @@ import * as React from "react"; import styled from "styled-components"; import { pulsate } from "shared/styles/animations"; import { randomInteger } from "shared/random"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { header?: boolean, diff --git a/app/components/Modal.js b/app/components/Modal.js index 3828393a..bedad776 100644 --- a/app/components/Modal.js +++ b/app/components/Modal.js @@ -8,7 +8,7 @@ import { transparentize } from "polished"; import { CloseIcon, BackIcon } from "outline-icons"; import NudeButton from "components/NudeButton"; import { fadeAndScaleIn } from "shared/styles/animations"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; ReactModal.setAppElement("#root"); diff --git a/app/components/Notice.js b/app/components/Notice.js new file mode 100644 index 00000000..53e14174 --- /dev/null +++ b/app/components/Notice.js @@ -0,0 +1,12 @@ +// @flow +import styled from "styled-components"; + +const Notice = styled.p` + background: ${props => props.theme.sidebarBackground}; + color: ${props => props.theme.sidebarText}; + padding: 10px 12px; + border-radius: 4px; + position: relative; +`; + +export default Notice; diff --git a/app/components/NoticeAlert.js b/app/components/NoticeAlert.js new file mode 100644 index 00000000..92efbed9 --- /dev/null +++ b/app/components/NoticeAlert.js @@ -0,0 +1,24 @@ +// @flow +import * as React from "react"; +import Notice from "components/Notice"; + +export default function AlertNotice({ children }: { children: React.Node }) { + return ( + + + + {" "} + {children} + + ); +} diff --git a/shared/components/OutlineLogo.js b/app/components/OutlineLogo.js similarity index 100% rename from shared/components/OutlineLogo.js rename to app/components/OutlineLogo.js diff --git a/app/components/PathToDocument.js b/app/components/PathToDocument.js index ad1c3c62..f7c2caec 100644 --- a/app/components/PathToDocument.js +++ b/app/components/PathToDocument.js @@ -3,7 +3,7 @@ import * as React from "react"; import { observer } from "mobx-react"; import styled from "styled-components"; import { GoToIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Document from "models/Document"; import Collection from "models/Collection"; diff --git a/app/components/PublishingInfo.js b/app/components/PublishingInfo.js index ce9ef0ca..0c66f95f 100644 --- a/app/components/PublishingInfo.js +++ b/app/components/PublishingInfo.js @@ -3,9 +3,9 @@ import * as React from "react"; import { inject, observer } from "mobx-react"; import styled from "styled-components"; import Document from "models/Document"; -import Flex from "shared/components/Flex"; -import Time from "shared/components/Time"; -import Breadcrumb from "shared/components/Breadcrumb"; +import Flex from "components/Flex"; +import Time from "components/Time"; +import Breadcrumb from "components/Breadcrumb"; import CollectionsStore from "stores/CollectionsStore"; import AuthStore from "stores/AuthStore"; diff --git a/app/components/Sidebar/Main.js b/app/components/Sidebar/Main.js index 88be199a..9306ca2d 100644 --- a/app/components/Sidebar/Main.js +++ b/app/components/Sidebar/Main.js @@ -12,7 +12,7 @@ import { PlusIcon, } from "outline-icons"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Modal from "components/Modal"; import Invite from "scenes/Invite"; import AccountMenu from "menus/AccountMenu"; diff --git a/app/components/Sidebar/Settings.js b/app/components/Sidebar/Settings.js index 3e064039..09bf725f 100644 --- a/app/components/Sidebar/Settings.js +++ b/app/components/Sidebar/Settings.js @@ -19,7 +19,7 @@ import { import ZapierIcon from "./icons/Zapier"; import SlackIcon from "./icons/Slack"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Sidebar from "./Sidebar"; import Scrollable from "components/Scrollable"; import Section from "./components/Section"; @@ -54,7 +54,7 @@ class SettingsSidebar extends React.Component { - Return to App + Return to App } teamName={team.name} diff --git a/app/components/Sidebar/Sidebar.js b/app/components/Sidebar/Sidebar.js index 17394ac0..c013036f 100644 --- a/app/components/Sidebar/Sidebar.js +++ b/app/components/Sidebar/Sidebar.js @@ -7,7 +7,7 @@ import breakpoint from "styled-components-breakpoint"; import { observer, inject } from "mobx-react"; import { CloseIcon, MenuIcon } from "outline-icons"; import Fade from "components/Fade"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import UiStore from "stores/UiStore"; let firstRender = true; diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js index 3c311ed3..3354eddf 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.js @@ -11,7 +11,7 @@ import SidebarLink from "./SidebarLink"; import DocumentLink from "./DocumentLink"; import CollectionIcon from "components/CollectionIcon"; import DropToImport from "components/DropToImport"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { collection: Collection, diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.js index 771e23c5..00ad9117 100644 --- a/app/components/Sidebar/components/Collections.js +++ b/app/components/Sidebar/components/Collections.js @@ -3,7 +3,7 @@ import * as React from "react"; import { observer, inject } from "mobx-react"; import { withRouter, type RouterHistory } from "react-router-dom"; import keydown from "react-keydown"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { PlusIcon } from "outline-icons"; import { newDocumentUrl } from "utils/routeHelpers"; diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js index 4cc216fc..97c79332 100644 --- a/app/components/Sidebar/components/DocumentLink.js +++ b/app/components/Sidebar/components/DocumentLink.js @@ -10,7 +10,7 @@ import DropToImport from "components/DropToImport"; import Fade from "components/Fade"; import Collection from "models/Collection"; import DocumentsStore from "stores/DocumentsStore"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { type NavigationNode } from "types"; type Props = { diff --git a/app/components/Sidebar/components/Header.js b/app/components/Sidebar/components/Header.js index ef95382f..52c9e58e 100644 --- a/app/components/Sidebar/components/Header.js +++ b/app/components/Sidebar/components/Header.js @@ -1,5 +1,5 @@ // @flow -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import styled from "styled-components"; const Header = styled(Flex)` diff --git a/app/components/Sidebar/components/HeaderBlock.js b/app/components/Sidebar/components/HeaderBlock.js index 01f3807e..7f5636d9 100644 --- a/app/components/Sidebar/components/HeaderBlock.js +++ b/app/components/Sidebar/components/HeaderBlock.js @@ -2,8 +2,8 @@ import * as React from "react"; import styled, { withTheme } from "styled-components"; import { ExpandedIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; -import TeamLogo from "shared/components/TeamLogo"; +import Flex from "components/Flex"; +import TeamLogo from "components/TeamLogo"; type Props = { teamName: string, diff --git a/app/components/Sidebar/components/Section.js b/app/components/Sidebar/components/Section.js index 9ece925a..31d0c807 100644 --- a/app/components/Sidebar/components/Section.js +++ b/app/components/Sidebar/components/Section.js @@ -1,6 +1,6 @@ // @flow import styled from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; const Section = styled(Flex)` position: relative; diff --git a/app/components/Sidebar/components/SidebarLink.js b/app/components/Sidebar/components/SidebarLink.js index 34654400..84f7c16f 100644 --- a/app/components/Sidebar/components/SidebarLink.js +++ b/app/components/Sidebar/components/SidebarLink.js @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { withRouter, NavLink } from "react-router-dom"; import { CollapsedIcon } from "outline-icons"; import styled, { withTheme } from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { to?: string | Object, diff --git a/shared/components/SlackLogo.js b/app/components/SlackLogo.js similarity index 100% rename from shared/components/SlackLogo.js rename to app/components/SlackLogo.js diff --git a/shared/components/TeamLogo.js b/app/components/TeamLogo.js similarity index 75% rename from shared/components/TeamLogo.js rename to app/components/TeamLogo.js index cc898040..549e44e5 100644 --- a/shared/components/TeamLogo.js +++ b/app/components/TeamLogo.js @@ -2,10 +2,10 @@ import styled from "styled-components"; const TeamLogo = styled.img` - width: 38px; + width: auto; height: 38px; border-radius: 4px; - background: ${props => props.theme.white}; + background: ${props => props.theme.background}; border: 1px solid ${props => props.theme.divider}; `; diff --git a/shared/components/Time.js b/app/components/Time.js similarity index 100% rename from shared/components/Time.js rename to app/components/Time.js diff --git a/app/menus/AccountMenu.js b/app/menus/AccountMenu.js index e5777f98..d9dce563 100644 --- a/app/menus/AccountMenu.js +++ b/app/menus/AccountMenu.js @@ -7,7 +7,7 @@ import { SunIcon, MoonIcon } from "outline-icons"; import styled from "styled-components"; import UiStore from "stores/UiStore"; import AuthStore from "stores/AuthStore"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import Modal from "components/Modal"; import KeyboardShortcuts from "scenes/KeyboardShortcuts"; diff --git a/app/routes.js b/app/routes.js index 6f74ba2a..a9fe169c 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,7 +1,7 @@ // @flow import * as React from "react"; import { Switch, Route, Redirect } from "react-router-dom"; -import Home from "scenes/Home"; +import Login from "scenes/Login"; import Dashboard from "scenes/Dashboard"; import Starred from "scenes/Starred"; import Drafts from "scenes/Drafts"; @@ -38,7 +38,8 @@ const RedirectDocument = ({ match }: { match: Object }) => ( export default function Routes() { return ( - + + diff --git a/app/scenes/Collection.js b/app/scenes/Collection.js index 0b6b8445..e22155c2 100644 --- a/app/scenes/Collection.js +++ b/app/scenes/Collection.js @@ -30,7 +30,7 @@ import HelpText from "components/HelpText"; import DocumentList from "components/DocumentList"; import Subheading from "components/Subheading"; import PageTitle from "components/PageTitle"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Modal from "components/Modal"; import CollectionMembers from "scenes/CollectionMembers"; import Tabs from "components/Tabs"; diff --git a/app/scenes/CollectionDelete.js b/app/scenes/CollectionDelete.js index 4c79a63c..c1c7f838 100644 --- a/app/scenes/CollectionDelete.js +++ b/app/scenes/CollectionDelete.js @@ -5,7 +5,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import { homeUrl } from "utils/routeHelpers"; import Button from "components/Button"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Collection from "models/Collection"; import CollectionsStore from "stores/CollectionsStore"; diff --git a/app/scenes/CollectionEdit.js b/app/scenes/CollectionEdit.js index 23068b5a..f78f0bfb 100644 --- a/app/scenes/CollectionEdit.js +++ b/app/scenes/CollectionEdit.js @@ -6,7 +6,7 @@ import Input from "components/Input"; import InputRich from "components/InputRich"; import Button from "components/Button"; import Switch from "components/Switch"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import IconPicker from "components/IconPicker"; import Collection from "models/Collection"; diff --git a/app/scenes/CollectionExport.js b/app/scenes/CollectionExport.js index 1ef04c32..c949d601 100644 --- a/app/scenes/CollectionExport.js +++ b/app/scenes/CollectionExport.js @@ -3,7 +3,7 @@ import * as React from "react"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import Button from "components/Button"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Collection from "models/Collection"; import AuthStore from "stores/AuthStore"; diff --git a/app/scenes/CollectionMembers/AddGroupsToCollection.js b/app/scenes/CollectionMembers/AddGroupsToCollection.js index 30d55811..ef7b9f6c 100644 --- a/app/scenes/CollectionMembers/AddGroupsToCollection.js +++ b/app/scenes/CollectionMembers/AddGroupsToCollection.js @@ -5,7 +5,7 @@ import { inject, observer } from "mobx-react"; import { observable } from "mobx"; import { debounce } from "lodash"; import Button from "components/Button"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Input from "components/Input"; import Modal from "components/Modal"; diff --git a/app/scenes/CollectionMembers/AddPeopleToCollection.js b/app/scenes/CollectionMembers/AddPeopleToCollection.js index c53499fd..8e15b008 100644 --- a/app/scenes/CollectionMembers/AddPeopleToCollection.js +++ b/app/scenes/CollectionMembers/AddPeopleToCollection.js @@ -3,7 +3,7 @@ import * as React from "react"; import { inject, observer } from "mobx-react"; import { observable } from "mobx"; import { debounce } from "lodash"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Input from "components/Input"; import Modal from "components/Modal"; diff --git a/app/scenes/CollectionMembers/CollectionMembers.js b/app/scenes/CollectionMembers/CollectionMembers.js index 2ca673a2..e5b08373 100644 --- a/app/scenes/CollectionMembers/CollectionMembers.js +++ b/app/scenes/CollectionMembers/CollectionMembers.js @@ -4,7 +4,7 @@ import { observable } from "mobx"; import styled from "styled-components"; import { inject, observer } from "mobx-react"; import { PlusIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Subheading from "components/Subheading"; import Button from "components/Button"; diff --git a/app/scenes/CollectionMembers/components/MemberListItem.js b/app/scenes/CollectionMembers/components/MemberListItem.js index 26815839..37b31d75 100644 --- a/app/scenes/CollectionMembers/components/MemberListItem.js +++ b/app/scenes/CollectionMembers/components/MemberListItem.js @@ -2,8 +2,8 @@ import * as React from "react"; import styled from "styled-components"; import Avatar from "components/Avatar"; -import Flex from "shared/components/Flex"; -import Time from "shared/components/Time"; +import Flex from "components/Flex"; +import Time from "components/Time"; import Badge from "components/Badge"; import Button from "components/Button"; import InputSelect from "components/InputSelect"; diff --git a/app/scenes/CollectionMembers/components/UserListItem.js b/app/scenes/CollectionMembers/components/UserListItem.js index 19663088..c064ab42 100644 --- a/app/scenes/CollectionMembers/components/UserListItem.js +++ b/app/scenes/CollectionMembers/components/UserListItem.js @@ -1,7 +1,7 @@ // @flow import * as React from "react"; import { PlusIcon } from "outline-icons"; -import Time from "shared/components/Time"; +import Time from "components/Time"; import Avatar from "components/Avatar"; import Button from "components/Button"; import Badge from "components/Badge"; diff --git a/app/scenes/CollectionNew.js b/app/scenes/CollectionNew.js index ed628595..4241447a 100644 --- a/app/scenes/CollectionNew.js +++ b/app/scenes/CollectionNew.js @@ -10,7 +10,7 @@ import Input from "components/Input"; import InputRich from "components/InputRich"; import IconPicker, { icons } from "components/IconPicker"; import HelpText from "components/HelpText"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Collection from "models/Collection"; import CollectionsStore from "stores/CollectionsStore"; diff --git a/app/scenes/Document/components/Container.js b/app/scenes/Document/components/Container.js index a248aa0d..b27159eb 100644 --- a/app/scenes/Document/components/Container.js +++ b/app/scenes/Document/components/Container.js @@ -1,6 +1,6 @@ // @flow import styled from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; const Container = styled(Flex)` position: relative; diff --git a/app/scenes/Document/components/Document.js b/app/scenes/Document/components/Document.js index 7fc5509c..a562feeb 100644 --- a/app/scenes/Document/components/Document.js +++ b/app/scenes/Document/components/Document.js @@ -8,7 +8,7 @@ import { observer, inject } from "mobx-react"; import { Prompt, Route, withRouter } from "react-router-dom"; import type { Location, RouterHistory } from "react-router-dom"; import keydown from "react-keydown"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import { collectionUrl, documentMoveUrl, @@ -29,9 +29,9 @@ import MarkAsViewed from "./MarkAsViewed"; import ErrorBoundary from "components/ErrorBoundary"; import LoadingIndicator from "components/LoadingIndicator"; import PageTitle from "components/PageTitle"; -import Branding from "shared/components/Branding"; -import Notice from "shared/components/Notice"; -import Time from "shared/components/Time"; +import Branding from "components/Branding"; +import Notice from "components/Notice"; +import Time from "components/Time"; import UiStore from "stores/UiStore"; import AuthStore from "stores/AuthStore"; diff --git a/app/scenes/Document/components/DocumentMove.js b/app/scenes/Document/components/DocumentMove.js index 22df2a86..57022869 100644 --- a/app/scenes/Document/components/DocumentMove.js +++ b/app/scenes/Document/components/DocumentMove.js @@ -12,7 +12,7 @@ import Modal from "components/Modal"; import Input from "components/Input"; import Labeled from "components/Labeled"; import PathToDocument from "components/PathToDocument"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Document from "models/Document"; import DocumentsStore from "stores/DocumentsStore"; diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js index bb8141f1..955d72b0 100644 --- a/app/scenes/Document/components/Editor.js +++ b/app/scenes/Document/components/Editor.js @@ -5,7 +5,7 @@ import Textarea from "react-autosize-textarea"; import { observer } from "mobx-react"; import Editor from "components/Editor"; import ClickablePadding from "components/ClickablePadding"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import parseTitle from "shared/utils/parseTitle"; import Document from "models/Document"; import DocumentMeta from "./DocumentMeta"; diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js index d53a7631..1b8d84ab 100644 --- a/app/scenes/Document/components/Header.js +++ b/app/scenes/Document/components/Header.js @@ -13,8 +13,8 @@ import AuthStore from "stores/AuthStore"; import { documentEditUrl } from "utils/routeHelpers"; import { meta } from "utils/keyboard"; -import Flex from "shared/components/Flex"; -import Breadcrumb, { Slash } from "shared/components/Breadcrumb"; +import Flex from "components/Flex"; +import Breadcrumb, { Slash } from "components/Breadcrumb"; import DocumentMenu from "menus/DocumentMenu"; import NewChildDocumentMenu from "menus/NewChildDocumentMenu"; import DocumentShare from "scenes/DocumentShare"; diff --git a/app/scenes/DocumentDelete.js b/app/scenes/DocumentDelete.js index 2ba61e0b..594c85af 100644 --- a/app/scenes/DocumentDelete.js +++ b/app/scenes/DocumentDelete.js @@ -4,7 +4,7 @@ import { withRouter, type RouterHistory } from "react-router-dom"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import Button from "components/Button"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Document from "models/Document"; import DocumentsStore from "stores/DocumentsStore"; diff --git a/app/scenes/DocumentNew.js b/app/scenes/DocumentNew.js index 1bbe6b08..1664147c 100644 --- a/app/scenes/DocumentNew.js +++ b/app/scenes/DocumentNew.js @@ -2,7 +2,7 @@ import * as React from "react"; import { inject } from "mobx-react"; import type { RouterHistory, Location } from "react-router-dom"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import CenteredContent from "components/CenteredContent"; import LoadingPlaceholder from "components/LoadingPlaceholder"; import DocumentsStore from "stores/DocumentsStore"; diff --git a/app/scenes/GroupDelete.js b/app/scenes/GroupDelete.js index ff9aa6c9..83ec112a 100644 --- a/app/scenes/GroupDelete.js +++ b/app/scenes/GroupDelete.js @@ -5,7 +5,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import { groupSettings } from "shared/utils/routeHelpers"; import Button from "components/Button"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Group from "models/Group"; import UiStore from "stores/UiStore"; diff --git a/app/scenes/GroupEdit.js b/app/scenes/GroupEdit.js index 7efb4de1..9f3c40aa 100644 --- a/app/scenes/GroupEdit.js +++ b/app/scenes/GroupEdit.js @@ -6,7 +6,7 @@ import { inject, observer } from "mobx-react"; import Button from "components/Button"; import Input from "components/Input"; import HelpText from "components/HelpText"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Group from "models/Group"; import UiStore from "stores/UiStore"; diff --git a/app/scenes/GroupMembers/AddPeopleToGroup.js b/app/scenes/GroupMembers/AddPeopleToGroup.js index dc274bba..f0f36a0f 100644 --- a/app/scenes/GroupMembers/AddPeopleToGroup.js +++ b/app/scenes/GroupMembers/AddPeopleToGroup.js @@ -3,7 +3,7 @@ import * as React from "react"; import { inject, observer } from "mobx-react"; import { observable } from "mobx"; import { debounce } from "lodash"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Input from "components/Input"; import Modal from "components/Modal"; diff --git a/app/scenes/GroupMembers/GroupMembers.js b/app/scenes/GroupMembers/GroupMembers.js index cd8d9147..1b052601 100644 --- a/app/scenes/GroupMembers/GroupMembers.js +++ b/app/scenes/GroupMembers/GroupMembers.js @@ -3,7 +3,7 @@ import * as React from "react"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import { PlusIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Empty from "components/Empty"; import HelpText from "components/HelpText"; import Subheading from "components/Subheading"; diff --git a/app/scenes/GroupMembers/components/GroupMemberListItem.js b/app/scenes/GroupMembers/components/GroupMemberListItem.js index 623671a3..8e3471d8 100644 --- a/app/scenes/GroupMembers/components/GroupMemberListItem.js +++ b/app/scenes/GroupMembers/components/GroupMemberListItem.js @@ -1,8 +1,8 @@ // @flow import * as React from "react"; import Avatar from "components/Avatar"; -import Flex from "shared/components/Flex"; -import Time from "shared/components/Time"; +import Flex from "components/Flex"; +import Time from "components/Time"; import Badge from "components/Badge"; import Button from "components/Button"; import ListItem from "components/List/Item"; diff --git a/app/scenes/GroupMembers/components/UserListItem.js b/app/scenes/GroupMembers/components/UserListItem.js index cf7202c3..e99a4027 100644 --- a/app/scenes/GroupMembers/components/UserListItem.js +++ b/app/scenes/GroupMembers/components/UserListItem.js @@ -1,7 +1,7 @@ // @flow import * as React from "react"; import { PlusIcon } from "outline-icons"; -import Time from "shared/components/Time"; +import Time from "components/Time"; import Avatar from "components/Avatar"; import Button from "components/Button"; import Badge from "components/Badge"; diff --git a/app/scenes/GroupNew.js b/app/scenes/GroupNew.js index f7f7fefc..ddf01f44 100644 --- a/app/scenes/GroupNew.js +++ b/app/scenes/GroupNew.js @@ -8,7 +8,7 @@ import Input from "components/Input"; import HelpText from "components/HelpText"; import Modal from "components/Modal"; import GroupMembers from "scenes/GroupMembers"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Group from "models/Group"; import GroupsStore from "stores/GroupsStore"; diff --git a/app/scenes/Home.js b/app/scenes/Home.js deleted file mode 100644 index 1f0b3f7e..00000000 --- a/app/scenes/Home.js +++ /dev/null @@ -1,17 +0,0 @@ -// @flow -import * as React from "react"; -import { observer, inject } from "mobx-react"; -import { Redirect } from "react-router-dom"; -import AuthStore from "stores/AuthStore"; - -type Props = { - auth: AuthStore, -}; - -const Home = observer(({ auth }: Props) => { - if (auth.authenticated) return ; - auth.logout(true); - return null; -}); - -export default inject("auth")(Home); diff --git a/app/scenes/Invite.js b/app/scenes/Invite.js index e7aaebc4..e9ee4464 100644 --- a/app/scenes/Invite.js +++ b/app/scenes/Invite.js @@ -3,13 +3,12 @@ import * as React from "react"; import { Link, withRouter, type RouterHistory } from "react-router-dom"; import { observable, action } from "mobx"; import { inject, observer } from "mobx-react"; -import { CloseIcon } from "outline-icons"; +import { LinkIcon, CloseIcon } from "outline-icons"; import styled from "styled-components"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Button from "components/Button"; import Input from "components/Input"; import CopyToClipboard from "components/CopyToClipboard"; -import Checkbox from "components/Checkbox"; import HelpText from "components/HelpText"; import Tooltip from "components/Tooltip"; import NudeButton from "components/NudeButton"; @@ -33,7 +32,6 @@ type Props = { type InviteRequest = { email: string, name: string, - guest: boolean, }; @observer @@ -42,9 +40,9 @@ class Invite extends React.Component { @observable linkCopied: boolean = false; @observable invites: InviteRequest[] = [ - { email: "", name: "", guest: false }, - { email: "", name: "", guest: false }, - { email: "", name: "", guest: false }, + { email: "", name: "" }, + { email: "", name: "" }, + { email: "", name: "" }, ]; handleSubmit = async (ev: SyntheticEvent<>) => { @@ -80,7 +78,7 @@ class Invite extends React.Component { ); } - this.invites.push({ email: "", name: "", guest: false }); + this.invites.push({ email: "", name: "" }); }; @action @@ -106,8 +104,8 @@ class Invite extends React.Component { {team.guestSignin ? ( Invite team members or guests to join your knowledge base. Team - members can sign in with {team.signinMethods} and guests can use - their email address. + members can sign in with {team.signinMethods} or use their email + address. ) : ( @@ -116,22 +114,35 @@ class Invite extends React.Component { {can.update && ( As an admin you can also{" "} - enable guest invites. + enable email sign-in. )} )} {team.subdomain && ( - Want a link to share directly with your team? - -    + +    - +

+


+

)} {this.invites.map((invite, index) => ( @@ -159,29 +170,6 @@ class Invite extends React.Component { required={!!invite.email} flex /> - {team.guestSignin && ( - -    - - Guests can sign in with email and
do not require{" "} - {team.signinMethods} accounts - - } - placement="top" - > - - this.handleGuestChange(ev, index)} - checked={invite.guest} - /> - -
-
- )} {index !== 0 && ( @@ -220,22 +208,8 @@ class Invite extends React.Component { } const CopyBlock = styled("div")` + margin: 2em 0; font-size: 14px; - background: ${props => props.theme.secondaryBackground}; - padding: 8px 16px 4px; - border-radius: 8px; - margin-bottom: 24px; - - input { - background: ${props => props.theme.background}; - border-radius: 4px; - } -`; - -const Guest = styled("div")` - padding-top: 4px; - margin: 0 4px 16px; - align-self: flex-end; `; const Remove = styled("div")` diff --git a/app/scenes/KeyboardShortcuts.js b/app/scenes/KeyboardShortcuts.js index 86248f41..ca853ec8 100644 --- a/app/scenes/KeyboardShortcuts.js +++ b/app/scenes/KeyboardShortcuts.js @@ -2,7 +2,7 @@ import * as React from "react"; import styled from "styled-components"; import Key from "components/Key"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import { meta } from "utils/keyboard"; diff --git a/server/pages/components/AuthNotices.js b/app/scenes/Login/Notices.js similarity index 66% rename from server/pages/components/AuthNotices.js rename to app/scenes/Login/Notices.js index 6e032e6e..2b30f9ad 100644 --- a/server/pages/components/AuthNotices.js +++ b/app/scenes/Login/Notices.js @@ -1,61 +1,55 @@ // @flow import * as React from "react"; -import Notice from "../../../shared/components/Notice"; +import NoticeAlert from "components/NoticeAlert"; type Props = { notice?: string, }; -export default function AuthNotices({ notice }: Props) { +export default function Notices({ notice }: Props) { return ( - {notice === "guest-success" && ( - - A magic sign-in link has been sent to your email address, no password - needed. - - )} {notice === "google-hd" && ( - + Sorry, Google sign in cannot be used with a personal email. Please try - signing in with your company Google account. - + signing in with your company GSuite account. + )} {notice === "hd-not-allowed" && ( - + Sorry, your Google apps domain is not allowed. Please try again with an allowed company domain. - + )} {notice === "email-auth-required" && ( - + Your account uses email sign-in, please sign-in with email to continue. - + )} {notice === "email-auth-ratelimit" && ( - - An email sign-in link was recently sent, please check your inbox and + + An email sign-in link was recently sent, please check your inbox or try again in a few minutes. - + )} {notice === "auth-error" && ( - + Authentication failed - we were unable to sign you in at this time. Please try again. - + )} {notice === "expired-token" && ( - + Sorry, it looks like that sign-in link is no longer valid, please try requesting another. - + )} {notice === "suspended" && ( - + Your Outline account has been suspended. To re-activate your account, please contact a team admin. - + )} ); diff --git a/app/scenes/Login/Service.js b/app/scenes/Login/Service.js new file mode 100644 index 00000000..75fb4088 --- /dev/null +++ b/app/scenes/Login/Service.js @@ -0,0 +1,147 @@ +// @flow +import * as React from "react"; +import styled from "styled-components"; +import { EmailIcon } from "outline-icons"; +import { client } from "utils/ApiClient"; +import ButtonLarge from "components/ButtonLarge"; +import SlackLogo from "components/SlackLogo"; +import GoogleLogo from "components/GoogleLogo"; +import InputLarge from "components/InputLarge"; + +type Props = { + id: string, + name: string, + authUrl: string, + isCreate: boolean, + onEmailSuccess: (email: string) => void, +}; + +type State = { + showEmailSignin: boolean, + isSubmitting: boolean, + email: string, +}; + +class Service extends React.Component { + state = { + showEmailSignin: false, + isSubmitting: false, + email: "", + }; + + handleChangeEmail = (event: SyntheticInputEvent) => { + this.setState({ email: event.target.value }); + }; + + handleSubmitEmail = async (event: SyntheticEvent) => { + event.preventDefault(); + + if (this.state.showEmailSignin && this.state.email) { + this.setState({ isSubmitting: true }); + + try { + const response = await client.post(event.currentTarget.action, { + email: this.state.email, + }); + if (response.redirect) { + window.location.href = response.redirect; + } else { + this.props.onEmailSuccess(this.state.email); + } + } finally { + this.setState({ isSubmitting: false }); + } + } else { + this.setState({ showEmailSignin: true }); + } + }; + + render() { + const { isCreate, id, name, authUrl } = this.props; + + if (id === "email") { + if (isCreate) { + return null; + } + + return ( + +
+ {this.state.showEmailSignin ? ( + + + + Sign In → + + + ) : ( + } fullwidth> + Continue with Email + + )} +
+
+ ); + } + + const icon = + id === "slack" ? ( + + + + ) : id === "google" ? ( + + + + ) : ( + undefined + ); + + return ( + + (window.location.href = authUrl)} + icon={icon} + fullwidth + > + {isCreate ? "Sign up" : "Continue"} with {name} + + + ); + } +} + +const Logo = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; +`; + +const Wrapper = styled.div` + margin-bottom: 1em; + width: 100%; +`; + +const Form = styled.form` + width: 100%; + display: flex; + justify-content: space-between; +`; + +export default Service; diff --git a/app/scenes/Login/index.js b/app/scenes/Login/index.js new file mode 100644 index 00000000..5e7e0dc7 --- /dev/null +++ b/app/scenes/Login/index.js @@ -0,0 +1,230 @@ +// @flow +import * as React from "react"; +import styled from "styled-components"; +import { BackIcon, EmailIcon } from "outline-icons"; +import { observer, inject } from "mobx-react"; +import { Redirect } from "react-router-dom"; +import { find } from "lodash"; +import Flex from "components/Flex"; +import TeamLogo from "components/TeamLogo"; +import OutlineLogo from "components/OutlineLogo"; +import Heading from "components/Heading"; +import PageTitle from "components/PageTitle"; +import ButtonLarge from "components/ButtonLarge"; +import HelpText from "components/HelpText"; +import Fade from "components/Fade"; +import Service from "./Service"; +import Notices from "./Notices"; +import AuthStore from "stores/AuthStore"; +import getQueryVariable from "shared/utils/getQueryVariable"; + +type Props = { + auth: AuthStore, + location: Object, +}; + +type State = { + emailLinkSentTo: string, +}; + +@observer +class Login extends React.Component { + state = { + emailLinkSentTo: "", + }; + + handleReset = () => { + this.setState({ emailLinkSentTo: "" }); + }; + + handleEmailSuccess = email => { + this.setState({ emailLinkSentTo: email }); + }; + + render() { + const { auth, location } = this.props; + const { config } = auth; + const isCreate = location.pathname === "/create"; + + if (auth.authenticated) { + return ; + } + + // we're counting on the config request being fast + if (!config) { + return null; + } + + const hasMultipleServices = config.services.length > 1; + const defaultService = find( + config.services, + service => service.id === auth.lastSignedIn + ); + + const header = + process.env.DEPLOYMENT === "hosted" && + (config.hostname ? ( + + Back to home + + ) : ( + + Back to website + + )); + + if (this.state.emailLinkSentTo) { + return ( + + {header} + + + + + Check your email + + A magic sign-in link has been sent to the email{" "} + {this.state.emailLinkSentTo}, no password needed. + +
+ + Back to login + +
+
+ ); + } + + return ( + + {header} + + + + {process.env.TEAM_LOGO && process.env.DEPLOYMENT !== "hosted" ? ( + + ) : ( + + )} + + + {isCreate ? ( + Create an account + ) : ( + Login to {config.name || "Outline"} + )} + + + + {defaultService && ( + + + {hasMultipleServices && ( + + + You signed in with {defaultService.name} last time. + + + + )} + + )} + + {config.services.map(service => { + if (service.id === auth.lastSignedIn) { + return null; + } + + return ( + + ); + })} + + + ); + } +} + +const CheckEmailIcon = styled(EmailIcon)` + margin-bottom: -1.5em; +`; + +const Background = styled(Fade)` + width: 100vw; + height: 100vh; + background: ${props => props.theme.background}; + display: flex; +`; + +const Logo = styled.div` + margin-bottom: -1.5em; + height: 38px; +`; + +const Note = styled(HelpText)` + text-align: center; + font-size: 14px; + + em { + font-style: normal; + font-weight: 500; + } +`; + +const Back = styled.a` + display: flex; + align-items: center; + color: inherit; + padding: 32px; + font-weight: 500; + position: absolute; + + svg { + transition: transform 100ms ease-in-out; + } + + &:hover { + svg { + transform: translateX(-4px); + } + } +`; + +const Or = styled.hr` + margin: 1em 0; + position: relative; + width: 100%; + + &:after { + content: "Or"; + display: block; + position: absolute; + left: 50%; + transform: translate3d(-50%, -50%, 0); + text-transform: uppercase; + font-size: 11px; + color: ${props => props.theme.textSecondary}; + background: ${props => props.theme.background}; + border-radius: 2px; + padding: 0 4px; + } +`; + +const Centered = styled(Flex)` + user-select: none; + width: 90vw; + height: 100%; + max-width: 320px; + margin: 0 auto; +`; + +export default inject("auth")(Login); diff --git a/app/scenes/Search/Search.js b/app/scenes/Search/Search.js index 84b73862..e32a72ce 100644 --- a/app/scenes/Search/Search.js +++ b/app/scenes/Search/Search.js @@ -19,7 +19,7 @@ import UsersStore from "stores/UsersStore"; import { newDocumentUrl, searchUrl } from "utils/routeHelpers"; import { meta } from "utils/keyboard"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Button from "components/Button"; import Empty from "components/Empty"; import Fade from "components/Fade"; diff --git a/app/scenes/Search/components/FilterOption.js b/app/scenes/Search/components/FilterOption.js index f6725b61..898b4df9 100644 --- a/app/scenes/Search/components/FilterOption.js +++ b/app/scenes/Search/components/FilterOption.js @@ -3,7 +3,7 @@ import * as React from "react"; import { CheckmarkIcon } from "outline-icons"; import styled from "styled-components"; import HelpText from "components/HelpText"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { label: string, diff --git a/app/scenes/Search/components/SearchField.js b/app/scenes/Search/components/SearchField.js index 6bed3cfb..91fedd19 100644 --- a/app/scenes/Search/components/SearchField.js +++ b/app/scenes/Search/components/SearchField.js @@ -2,7 +2,7 @@ import * as React from "react"; import styled, { withTheme } from "styled-components"; import { SearchIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { onChange: string => void, diff --git a/app/scenes/Settings/Details.js b/app/scenes/Settings/Details.js index 120e1e66..9120049f 100644 --- a/app/scenes/Settings/Details.js +++ b/app/scenes/Settings/Details.js @@ -12,7 +12,7 @@ import Button from "components/Button"; import CenteredContent from "components/CenteredContent"; import PageTitle from "components/PageTitle"; import HelpText from "components/HelpText"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { auth: AuthStore, diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js index 94ba1a2c..19110d35 100644 --- a/app/scenes/Settings/Notifications.js +++ b/app/scenes/Settings/Notifications.js @@ -9,7 +9,7 @@ import HelpText from "components/HelpText"; import Input from "components/Input"; import Subheading from "components/Subheading"; import NotificationListItem from "./components/NotificationListItem"; -import Notice from "shared/components/Notice"; +import Notice from "components/Notice"; import UiStore from "stores/UiStore"; import AuthStore from "stores/AuthStore"; diff --git a/app/scenes/Settings/Profile.js b/app/scenes/Settings/Profile.js index 71ad9276..6e32626c 100644 --- a/app/scenes/Settings/Profile.js +++ b/app/scenes/Settings/Profile.js @@ -12,7 +12,7 @@ import Button from "components/Button"; import CenteredContent from "components/CenteredContent"; import PageTitle from "components/PageTitle"; import UserDelete from "scenes/UserDelete"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; type Props = { auth: AuthStore, diff --git a/app/scenes/Settings/Security.js b/app/scenes/Settings/Security.js index b4c1bcc1..7d4a6447 100644 --- a/app/scenes/Settings/Security.js +++ b/app/scenes/Settings/Security.js @@ -60,8 +60,6 @@ class Security extends React.Component { }, 500); render() { - const { team } = this.props.auth; - return ( @@ -72,27 +70,25 @@ class Security extends React.Component { ); diff --git a/app/scenes/Settings/Slack.js b/app/scenes/Settings/Slack.js index cdbe05b5..81dbd715 100644 --- a/app/scenes/Settings/Slack.js +++ b/app/scenes/Settings/Slack.js @@ -12,7 +12,7 @@ import SlackButton from "./components/SlackButton"; import CollectionsStore from "stores/CollectionsStore"; import IntegrationsStore from "stores/IntegrationsStore"; import AuthStore from "stores/AuthStore"; -import Notice from "shared/components/Notice"; +import Notice from "components/Notice"; import getQueryVariable from "shared/utils/getQueryVariable"; type Props = { diff --git a/app/scenes/Settings/components/EventListItem.js b/app/scenes/Settings/components/EventListItem.js index 08abbffb..068e3fde 100644 --- a/app/scenes/Settings/components/EventListItem.js +++ b/app/scenes/Settings/components/EventListItem.js @@ -3,7 +3,7 @@ import * as React from "react"; import { Link } from "react-router-dom"; import { capitalize } from "lodash"; import styled from "styled-components"; -import Time from "shared/components/Time"; +import Time from "components/Time"; import ListItem from "components/List/Item"; import Avatar from "components/Avatar"; import Event from "models/Event"; diff --git a/app/scenes/Settings/components/ImageUpload.js b/app/scenes/Settings/components/ImageUpload.js index ac64d195..423eee56 100644 --- a/app/scenes/Settings/components/ImageUpload.js +++ b/app/scenes/Settings/components/ImageUpload.js @@ -5,7 +5,7 @@ import { observer, inject } from "mobx-react"; import styled from "styled-components"; import Dropzone from "react-dropzone"; import LoadingIndicator from "components/LoadingIndicator"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import Modal from "components/Modal"; import Button from "components/Button"; import AvatarEditor from "react-avatar-editor"; diff --git a/app/scenes/Settings/components/ShareListItem.js b/app/scenes/Settings/components/ShareListItem.js index 1f054aec..466d3dcd 100644 --- a/app/scenes/Settings/components/ShareListItem.js +++ b/app/scenes/Settings/components/ShareListItem.js @@ -2,7 +2,7 @@ import * as React from "react"; import ShareMenu from "menus/ShareMenu"; import ListItem from "components/List/Item"; -import Time from "shared/components/Time"; +import Time from "components/Time"; import Share from "models/Share"; type Props = { diff --git a/app/scenes/Settings/components/SlackButton.js b/app/scenes/Settings/components/SlackButton.js index d471f2a9..7333b122 100644 --- a/app/scenes/Settings/components/SlackButton.js +++ b/app/scenes/Settings/components/SlackButton.js @@ -2,7 +2,7 @@ import * as React from "react"; import styled from "styled-components"; import { slackAuth } from "shared/utils/routeHelpers"; -import SlackLogo from "shared/components/SlackLogo"; +import SlackLogo from "components/SlackLogo"; import Button from "components/Button"; type Props = { diff --git a/app/scenes/Settings/components/UserListItem.js b/app/scenes/Settings/components/UserListItem.js index a049e8c8..485bc21c 100644 --- a/app/scenes/Settings/components/UserListItem.js +++ b/app/scenes/Settings/components/UserListItem.js @@ -8,7 +8,7 @@ import Avatar from "components/Avatar"; import Badge from "components/Badge"; import UserProfile from "scenes/UserProfile"; import ListItem from "components/List/Item"; -import Time from "shared/components/Time"; +import Time from "components/Time"; import User from "models/User"; type Props = { diff --git a/app/scenes/UserDelete.js b/app/scenes/UserDelete.js index 7309b81e..d09b49cc 100644 --- a/app/scenes/UserDelete.js +++ b/app/scenes/UserDelete.js @@ -3,7 +3,7 @@ import * as React from "react"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import Button from "components/Button"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Modal from "components/Modal"; import AuthStore from "stores/AuthStore"; diff --git a/app/scenes/UserProfile.js b/app/scenes/UserProfile.js index 7ce72313..fcbae252 100644 --- a/app/scenes/UserProfile.js +++ b/app/scenes/UserProfile.js @@ -5,7 +5,7 @@ import distanceInWordsToNow from "date-fns/distance_in_words_to_now"; import { inject, observer } from "mobx-react"; import { withRouter, type RouterHistory } from "react-router-dom"; import { EditIcon } from "outline-icons"; -import Flex from "shared/components/Flex"; +import Flex from "components/Flex"; import HelpText from "components/HelpText"; import Modal from "components/Modal"; import Button from "components/Button"; diff --git a/app/stores/AuthStore.js b/app/stores/AuthStore.js index a0e9afdb..4d950c52 100644 --- a/app/stores/AuthStore.js +++ b/app/stores/AuthStore.js @@ -10,13 +10,27 @@ import Team from "models/Team"; const AUTH_STORE = "AUTH_STORE"; +type Service = { + id: string, + name: string, + authUrl: string, +}; + +type Config = { + name?: string, + hostname?: string, + services: Service[], +}; + export default class AuthStore { @observable user: ?User; @observable team: ?Team; @observable token: ?string; + @observable lastSignedIn: ?string; @observable isSaving: boolean = false; @observable isSuspended: boolean = false; @observable suspendedContactEmail: ?string; + @observable config: ?Config; rootStore: RootStore; constructor(rootStore: RootStore) { @@ -32,8 +46,12 @@ export default class AuthStore { this.user = new User(data.user); this.team = new Team(data.team); this.token = getCookie("accessToken"); + this.lastSignedIn = getCookie("lastSignedIn"); + setImmediate(() => this.fetchConfig()); - if (this.token) setImmediate(() => this.fetch()); + if (this.token) { + setImmediate(() => this.fetch()); + } autorun(() => { try { @@ -63,6 +81,13 @@ export default class AuthStore { }); } + @action + fetchConfig = async () => { + const res = await client.post("/auth.config"); + invariant(res && res.data, "Config not available"); + this.config = res.data; + }; + @action fetch = async () => { try { @@ -158,10 +183,16 @@ export default class AuthStore { }) ); + this.token = null; + // if this logout was forced from an authenticated route then // save the current path so we can go back there once signed in if (savePath) { - setCookie("postLoginRedirectPath", window.location.pathname); + const pathName = window.location.pathname; + + if (pathName !== "/" && pathName !== "/create") { + setCookie("postLoginRedirectPath", pathName); + } } // remove authentication token itself @@ -178,8 +209,5 @@ export default class AuthStore { }); this.team = null; } - - // add a timestamp to force reload from server - window.location.href = `${BASE_URL}?done=${new Date().getTime()}`; }; } diff --git a/app/utils/ApiClient.js b/app/utils/ApiClient.js index b156ec14..44b18b8a 100644 --- a/app/utils/ApiClient.js +++ b/app/utils/ApiClient.js @@ -34,6 +34,7 @@ class ApiClient { ) => { let body; let modifiedPath; + let urlToFetch; if (method === "GET") { if (data) { @@ -45,6 +46,12 @@ class ApiClient { body = data ? JSON.stringify(data) : undefined; } + if (path.match(/^http/)) { + urlToFetch = modifiedPath || path; + } else { + urlToFetch = this.baseUrl + (modifiedPath || path); + } + // Construct headers const headers = new Headers({ Accept: "application/json", @@ -60,7 +67,7 @@ class ApiClient { let response; try { - response = await fetch(this.baseUrl + (modifiedPath || path), { + response = await fetch(urlToFetch, { method, body, headers, diff --git a/flow-typed/npm/styled-components-grid_vx.x.x.js b/flow-typed/npm/styled-components-grid_vx.x.x.js deleted file mode 100644 index 67cf5e5c..00000000 --- a/flow-typed/npm/styled-components-grid_vx.x.x.js +++ /dev/null @@ -1,144 +0,0 @@ -// flow-typed signature: d7c6f4d4223c61be85becba99f8e5712 -// flow-typed version: <>/styled-components-grid_v^1.0.0-preview.15/flow_v0.71.0 - -/** - * This is an autogenerated libdef stub for: - * - * 'styled-components-grid' - * - * Fill this stub out by replacing all the `any` types. - * - * Once filled out, we encourage you to share your work with the - * community by sending a pull request to: - * https://github.com/flowtype/flow-typed - */ - -declare module 'styled-components-grid' { - declare module.exports: any; -} - -/** - * We include stubs for each file inside this npm package in case you need to - * require those files directly. Feel free to delete any files that aren't - * needed. - */ -declare module 'styled-components-grid/dist/cjs/components/Grid' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/cjs/components/GridUnit' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/cjs/components/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/cjs/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/cjs/mixins/grid' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/cjs/mixins/gridUnit' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/cjs/mixins/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/components/Grid' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/components/GridUnit' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/components/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/mixins/grid' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/mixins/gridUnit' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/es/mixins/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/example/index' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/styled-components-grid' { - declare module.exports: any; -} - -declare module 'styled-components-grid/dist/styled-components-grid.min' { - declare module.exports: any; -} - -// Filename aliases -declare module 'styled-components-grid/dist/cjs/components/Grid.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/components/Grid'>; -} -declare module 'styled-components-grid/dist/cjs/components/GridUnit.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/components/GridUnit'>; -} -declare module 'styled-components-grid/dist/cjs/components/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/components/index'>; -} -declare module 'styled-components-grid/dist/cjs/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/index'>; -} -declare module 'styled-components-grid/dist/cjs/mixins/grid.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/mixins/grid'>; -} -declare module 'styled-components-grid/dist/cjs/mixins/gridUnit.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/mixins/gridUnit'>; -} -declare module 'styled-components-grid/dist/cjs/mixins/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/cjs/mixins/index'>; -} -declare module 'styled-components-grid/dist/es/components/Grid.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/components/Grid'>; -} -declare module 'styled-components-grid/dist/es/components/GridUnit.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/components/GridUnit'>; -} -declare module 'styled-components-grid/dist/es/components/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/components/index'>; -} -declare module 'styled-components-grid/dist/es/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/index'>; -} -declare module 'styled-components-grid/dist/es/mixins/grid.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/mixins/grid'>; -} -declare module 'styled-components-grid/dist/es/mixins/gridUnit.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/mixins/gridUnit'>; -} -declare module 'styled-components-grid/dist/es/mixins/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/es/mixins/index'>; -} -declare module 'styled-components-grid/dist/example/index.js' { - declare module.exports: $Exports<'styled-components-grid/dist/example/index'>; -} -declare module 'styled-components-grid/dist/styled-components-grid.js' { - declare module.exports: $Exports<'styled-components-grid/dist/styled-components-grid'>; -} -declare module 'styled-components-grid/dist/styled-components-grid.min.js' { - declare module.exports: $Exports<'styled-components-grid/dist/styled-components-grid.min'>; -} diff --git a/index.js b/index.js index 60a39906..00d10400 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,12 @@ // @flow -require('./init'); +require("./init"); if ( !process.env.SECRET_KEY || - process.env.SECRET_KEY === 'generate_a_new_key' + process.env.SECRET_KEY === "generate_a_new_key" ) { console.error( - 'The SECRET_KEY env variable must be set with the output of `openssl rand -hex 32`' + "The SECRET_KEY env variable must be set with the output of `openssl rand -hex 32`" ); // $FlowFixMe process.exit(1); @@ -14,11 +14,11 @@ if ( if (process.env.AWS_ACCESS_KEY_ID) { [ - 'AWS_REGION', - 'AWS_SECRET_ACCESS_KEY', - 'AWS_S3_UPLOAD_BUCKET_URL', - 'AWS_S3_UPLOAD_BUCKET_NAME', - 'AWS_S3_UPLOAD_MAX_SIZE', + "AWS_REGION", + "AWS_SECRET_ACCESS_KEY", + "AWS_S3_UPLOAD_BUCKET_URL", + "AWS_S3_UPLOAD_BUCKET_NAME", + "AWS_S3_UPLOAD_MAX_SIZE", ].forEach(key => { if (!process.env[key]) { console.error(`The ${key} env variable must be set when using AWS`); @@ -40,7 +40,7 @@ if (process.env.SLACK_KEY) { if (!process.env.URL) { console.error( - 'The URL env variable must be set to the externally accessible URL, e.g (https://www.getoutline.com)' + "The URL env variable must be set to the externally accessible URL, e.g (https://www.getoutline.com)" ); // $FlowFixMe process.exit(1); @@ -48,7 +48,7 @@ if (!process.env.URL) { if (!process.env.DATABASE_URL) { console.error( - 'The DATABASE_URL env variable must be set to the location of your postgres server, including authentication and port' + "The DATABASE_URL env variable must be set to the location of your postgres server, including authentication and port" ); // $FlowFixMe process.exit(1); @@ -56,7 +56,7 @@ if (!process.env.DATABASE_URL) { if (!process.env.REDIS_URL) { console.error( - 'The REDIS_URL env variable must be set to the location of your redis server, including authentication and port' + "The REDIS_URL env variable must be set to the location of your redis server, including authentication and port" ); // $FlowFixMe process.exit(1); @@ -64,17 +64,17 @@ if (!process.env.REDIS_URL) { if (!process.env.WEBSOCKETS_ENABLED) { console.log( - 'WARNING: Websockets are disabled. Set WEBSOCKETS_ENABLED env variable to true to enable' + "WARNING: Websockets are disabled. Set WEBSOCKETS_ENABLED env variable to true to enable" ); } -if (process.env.NODE_ENV === 'production') { - console.log('\n\x1b[33m%s\x1b[0m', 'Running Outline in production mode.'); -} else if (process.env.NODE_ENV === 'development') { +if (process.env.NODE_ENV === "production") { + console.log("\n\x1b[33m%s\x1b[0m", "Running Outline in production mode."); +} else if (process.env.NODE_ENV === "development") { console.log( - '\n\x1b[33m%s\x1b[0m', + "\n\x1b[33m%s\x1b[0m", 'Running Outline in development mode with hot reloading. To run Outline in production mode set the NODE_ENV env variable to "production"' ); } -require('./server'); +require("./server"); diff --git a/init.js b/init.js index d0fe5e0f..f828b21c 100644 --- a/init.js +++ b/init.js @@ -1,4 +1,4 @@ // @flow -require('babel-core/register'); -require('babel-polyfill'); -require('dotenv').config({ silent: true }); +require("babel-core/register"); +require("babel-polyfill"); +require("dotenv").config({ silent: true }); diff --git a/package.json b/package.json index c2c831de..b4a1700a 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,6 @@ "string-replace-to-array": "^1.0.3", "styled-components": "^5.0.0", "styled-components-breakpoint": "^2.1.1", - "styled-components-grid": "^2.2.1", "styled-normalize": "^8.0.4", "tiny-cookie": "^2.3.1", "tmp": "0.0.33", diff --git a/public/screenshot.png b/public/screenshot.png deleted file mode 100644 index 85a4e122..00000000 Binary files a/public/screenshot.png and /dev/null differ diff --git a/public/screenshot@2x.png b/public/screenshot@2x.png deleted file mode 100644 index 80eed4fd..00000000 Binary files a/public/screenshot@2x.png and /dev/null differ diff --git a/server/api/auth.js b/server/api/auth.js index f168bc03..e4285f62 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -1,11 +1,105 @@ // @flow import Router from "koa-router"; +import { reject } from "lodash"; import auth from "../middlewares/authentication"; import { presentUser, presentTeam, presentPolicies } from "../presenters"; import { Team } from "../models"; +import { signin } from "../../shared/utils/routeHelpers"; +import { parseDomain, isCustomSubdomain } from "../../shared/utils/domains"; const router = new Router(); +let services = []; + +if (process.env.GOOGLE_CLIENT_ID) { + services.push({ + id: "google", + name: "Google", + authUrl: signin("google"), + }); +} + +if (process.env.SLACK_KEY) { + services.push({ + id: "slack", + name: "Slack", + authUrl: signin("slack"), + }); +} + +services.push({ + id: "email", + name: "Email", + authUrl: "", +}); + +function filterServices(team) { + let output = services; + + if (team && !team.googleId) { + output = reject(output, service => service.id === "google"); + } + if (team && !team.slackId) { + output = reject(output, service => service.id === "slack"); + } + if (!team || !team.guestSignin) { + output = reject(output, service => service.id === "email"); + } + + return output; +} + +router.post("auth.config", async ctx => { + // If self hosted AND there is only one team then that team becomes the + // brand for the knowledgebase and it's guest signin option is used for the + // root login page. + if (process.env.DEPLOYMENT !== "hosted") { + const teams = await Team.findAll(); + + if (teams.length === 1) { + const team = teams[0]; + ctx.body = { + data: { + name: team.name, + services: filterServices(team), + }, + }; + return; + } + } + + // If subdomain signin page then we return minimal team details to allow + // for a custom screen showing only relevant signin options for that team. + if ( + process.env.SUBDOMAINS_ENABLED === "true" && + isCustomSubdomain(ctx.request.hostname) + ) { + const domain = parseDomain(ctx.request.hostname); + const subdomain = domain ? domain.subdomain : undefined; + const team = await Team.findOne({ + where: { subdomain }, + }); + + if (team) { + ctx.body = { + data: { + name: team.name, + hostname: ctx.request.hostname, + services: filterServices(team), + }, + }; + return; + } + } + + // Otherwise, we're requesting from the standard root signin page + ctx.body = { + data: { + services: filterServices(), + }, + }; +}); + router.post("auth.info", auth(), async ctx => { const user = ctx.state.user; const team = await Team.findByPk(user.teamId); diff --git a/server/auth/email.js b/server/auth/email.js index d40dc1f0..0a54fe33 100644 --- a/server/auth/email.js +++ b/server/auth/email.js @@ -30,7 +30,10 @@ router.post("email", async ctx => { // signin then just forward them directly to that service's // login page if (user.service && user.service !== "email") { - return ctx.redirect(`${team.url}/auth/${user.service}`); + ctx.body = { + redirect: `${team.url}/auth/${user.service}`, + }; + return; } if (!team.guestSignin) { @@ -55,12 +58,12 @@ router.post("email", async ctx => { user.lastSigninEmailSentAt = new Date(); await user.save(); - - // respond with success regardless of whether an email was sent - ctx.redirect(`${team.url}?notice=guest-success`); - } else { - ctx.redirect(`${process.env.URL}?notice=guest-success`); } + + // respond with success regardless of whether an email was sent + ctx.body = { + success: true, + }; }); router.get("email.callback", auth({ required: false }), async ctx => { diff --git a/server/commands/userInviter.js b/server/commands/userInviter.js index 22d39434..603cd122 100644 --- a/server/commands/userInviter.js +++ b/server/commands/userInviter.js @@ -4,7 +4,7 @@ import { User, Event, Team } from "../models"; import mailer from "../mailer"; import { sequelize } from "../sequelize"; -type Invite = { name: string, email: string, guest: boolean }; +type Invite = { name: string, email: string }; export default async function userInviter({ user, @@ -77,7 +77,6 @@ export default async function userInviter({ await mailer.invite({ to: invite.email, name: invite.name, - guest: invite.guest, actorName: user.name, actorEmail: user.email, teamName: team.name, diff --git a/server/emails/InviteEmail.js b/server/emails/InviteEmail.js index c034cb3e..b0bf30d9 100644 --- a/server/emails/InviteEmail.js +++ b/server/emails/InviteEmail.js @@ -10,7 +10,6 @@ import EmptySpace from "./components/EmptySpace"; export type Props = { name: string, - guest: boolean, actorName: string, actorEmail: string, teamName: string, @@ -22,7 +21,6 @@ export const inviteEmailText = ({ actorName, actorEmail, teamUrl, - guest, }: Props) => ` Join ${teamName} on Outline @@ -30,7 +28,7 @@ ${actorName} (${ actorEmail }) has invited you to join Outline, a place for your team to build and share knowledge. -Join now: ${teamUrl}${guest ? "?guest=true" : ""} +Join now: ${teamUrl} `; export const InviteEmail = ({ @@ -38,7 +36,6 @@ export const InviteEmail = ({ actorName, actorEmail, teamUrl, - guest, }: Props) => { return ( @@ -52,9 +49,7 @@ export const InviteEmail = ({

- +

diff --git a/server/pages/Home.js b/server/pages/Home.js deleted file mode 100644 index 789963f2..00000000 --- a/server/pages/Home.js +++ /dev/null @@ -1,50 +0,0 @@ -// @flow -import * as React from "react"; -import { Helmet } from "react-helmet"; -import styled from "styled-components"; -import Grid from "styled-components-grid"; -import AuthNotices from "./components/AuthNotices"; -import Hero from "./components/Hero"; -import HeroText from "./components/HeroText"; -import SigninButtons from "./components/SigninButtons"; -import Branding from "../../shared/components/Branding"; -import { githubUrl } from "../../shared/utils/routeHelpers"; - -type Props = { - notice?: "google-hd" | "auth-error" | "hd-not-allowed", - lastSignedIn: string, - googleSigninEnabled: boolean, - slackSigninEnabled: boolean, -}; - -function Home(props: Props) { - return ( - - - Outline - Team wiki & knowledge base - - - - - {process.env.TEAM_LOGO && } -

Our team’s knowledge base

- - Team wiki, documentation, meeting notes, playbooks, onboarding, work - logs, brainstorming, & more… - -

- -

-
-
- -
- ); -} - -const Logo = styled.img` - height: 60px; - border-radius: 4px; -`; - -export default Home; diff --git a/server/pages/SubdomainSignin.js b/server/pages/SubdomainSignin.js deleted file mode 100644 index 90038606..00000000 --- a/server/pages/SubdomainSignin.js +++ /dev/null @@ -1,131 +0,0 @@ -// @flow -import * as React from "react"; -import styled from "styled-components"; -import Grid from "styled-components-grid"; -import Hero from "./components/Hero"; -import HeroText from "./components/HeroText"; -import Button from "./components/Button"; -import SigninButtons from "./components/SigninButtons"; -import AuthNotices from "./components/AuthNotices"; -import Centered from "./components/Centered"; -import PageTitle from "./components/PageTitle"; -import { Team } from "../models"; - -type Props = { - team: Team, - guest?: boolean, - notice?: "google-hd" | "auth-error" | "hd-not-allowed" | "guest-success", - lastSignedIn: string, - googleSigninEnabled: boolean, - slackSigninEnabled: boolean, - hostname: string, -}; - -function SubdomainSignin({ - team, - guest, - lastSignedIn, - notice, - googleSigninEnabled, - slackSigninEnabled, - hostname, -}: Props) { - googleSigninEnabled = !!team.googleId && googleSigninEnabled; - slackSigninEnabled = !!team.slackId && slackSigninEnabled; - - const guestSigninEnabled = team.guestSignin; - const guestSigninForm = ( -
-
- {" "} - - -
- ); - - // only show the "last signed in" hint if there is more than one option available - const signinHint = - googleSigninEnabled && slackSigninEnabled ? lastSignedIn : undefined; - - return ( - - - - -

{lastSignedIn ? "Welcome back," : "Hey there,"}

- - {guest && guestSigninEnabled ? ( - - - Sign in with your email address to continue to {team.name}. - {hostname} - - {guestSigninForm} -
- - Have a team account? Sign in with SSO… -

- -

-
- ) : ( - - - Sign in with your team account to continue to {team.name}. - {hostname} - -

- -

- - {guestSigninEnabled && ( - - Have a guest account? Sign in with email… - {guestSigninForm} - - )} -
- )} -
-
- -

- Trying to create or sign in to a different team?{" "} - Head to the homepage. -

-
-
- ); -} - -const EmailInput = styled.input` - padding: 12px; - border-radius: 4px; - border: 1px solid #999; - min-width: 217px; - height: 56px; -`; - -const Subdomain = styled.span` - display: block; - font-weight: 500; - font-size: 16px; - margin-top: 0; -`; - -const Alternative = styled(Centered)` - padding: 2em 0; - text-align: center; -`; - -export default SubdomainSignin; diff --git a/server/pages/components/Analytics.js b/server/pages/components/Analytics.js deleted file mode 100644 index 552f0295..00000000 --- a/server/pages/components/Analytics.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow -import * as React from "react"; - -function Analytics() { - if (!process.env.GOOGLE_ANALYTICS_ID) return null; - - return ( - -