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?
-
-
+
+
-
+ }
+ style={{ marginBottom: "16px" }}
+ neutral
+ >
{this.linkCopied ? "Link copied" : "Copy link"}
+
+
+
)}
{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 (
+
+
+
+ );
+ }
+
+ 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 = ({
-
- Join now
-
+ Join now
{children}
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 (
-
-
-
-
- );
-}
-
-export default Analytics;
diff --git a/server/pages/components/Button.js b/server/pages/components/Button.js
deleted file mode 100644
index f991f804..00000000
--- a/server/pages/components/Button.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// @flow
-import styled from "styled-components";
-
-const Button = styled.a`
- border: 0;
- display: inline-flex;
- align-items: center;
- padding: 10px 20px;
- color: ${props => props.theme.white};
- background: ${props => props.theme.black};
- border-radius: 6px;
- font-weight: 600;
- height: 56px;
-`;
-
-export default Button;
diff --git a/server/pages/components/Centered.js b/server/pages/components/Centered.js
deleted file mode 100644
index 1e0ef2ad..00000000
--- a/server/pages/components/Centered.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// @flow
-import styled from "styled-components";
-
-const Centered = styled.div`
- margin: 0 auto;
- max-width: 1000px;
-`;
-
-export default Centered;
diff --git a/server/pages/components/Content.js b/server/pages/components/Content.js
deleted file mode 100644
index 9444eb3b..00000000
--- a/server/pages/components/Content.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// @flow
-import styled from "styled-components";
-
-export default styled.div`
- width: 100%;
- max-width: 1000px;
- margin: 0 auto 2em;
- font-size: 1.1em;
-
- li {
- padding: 0.2em 0;
- }
-`;
diff --git a/server/pages/components/Header.js b/server/pages/components/Header.js
deleted file mode 100644
index 4880ebaa..00000000
--- a/server/pages/components/Header.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// @flow
-import * as React from "react";
-import breakpoint from "styled-components-breakpoint";
-import styled from "styled-components";
-import Centered from "./Centered";
-
-type Props = {
- children: React.Node,
- background?: string,
-};
-
-const Header = ({ children, background }: Props) => {
- return (
-
- {children}
-
- );
-};
-
-const Wrapper = styled.div`
- width: 100%;
- padding: 8em 0 3em;
- position: relative;
-
- margin-top: -70px;
- margin-bottom: 2em;
- text-align: center;
- background: ${props => props.background || "transparent"};
- z-index: -1;
-
- &:before {
- content: "";
- position: absolute;
- top: 0;
- left: -30px;
- width: 100vw;
- height: 100%;
- background: ${props => props.background || "transparent"};
- z-index: -10;
- }
-
- p {
- font-size: 22px;
- font-weight: 500;
- color: rgba(0, 0, 0, 0.6);
- margin: 0;
- }
-
- h1 {
- font-size: 2em;
- margin: 0 0 0.1em;
- }
-
- ${breakpoint("tablet")`
- padding: 8em 3em 3em 3em;
-
- h1 {
- font-size: 4em;
- }
- `};
-`;
-
-export default Header;
diff --git a/server/pages/components/Hero.js b/server/pages/components/Hero.js
deleted file mode 100644
index b6c726c9..00000000
--- a/server/pages/components/Hero.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// @flow
-import styled from "styled-components";
-import Centered from "./Centered";
-
-const Hero = styled(Centered)`
- width: 100%;
- margin-top: 50vh;
- transform: translateY(-50%);
-
- h1 {
- font-size: 3.5em;
- line-height: 1em;
- margin-top: 0;
- }
-
- h2 {
- font-size: 2.5em;
- line-height: 1em;
- }
-`;
-
-export default Hero;
diff --git a/server/pages/components/HeroText.js b/server/pages/components/HeroText.js
deleted file mode 100644
index bb90e963..00000000
--- a/server/pages/components/HeroText.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// @flow
-import styled from "styled-components";
-
-const HeroText = styled.p`
- font-size: 22px;
- color: #666;
- font-weight: 500;
- text-align: left;
- max-width: 600px;
- margin-bottom: 1em;
-`;
-
-export default HeroText;
diff --git a/server/pages/components/Layout.js b/server/pages/components/Layout.js
deleted file mode 100644
index e9904402..00000000
--- a/server/pages/components/Layout.js
+++ /dev/null
@@ -1,81 +0,0 @@
-// @flow
-import * as React from "react";
-import { Helmet } from "react-helmet";
-import styled from "styled-components";
-import breakpoint from "styled-components-breakpoint";
-import Analytics from "./Analytics";
-import GlobalStyles from "../../../shared/styles/globals";
-import prefetchTags from "../../utils/prefetchTags";
-
-export const title = "Outline";
-export const description =
- "Your team’s knowledge base - Team wiki, documentation, playbooks, onboarding & more…";
-export const screenshotUrl = `${process.env.URL}/screenshot.png`;
-
-type Props = {
- children?: React.Node,
- sessions: Object,
- loggedIn: boolean,
-};
-
-function Layout({ children, loggedIn, sessions }: Props) {
- return (
-
-