New login screen (#1331)
* wip * feat: first draft of auth.config * chore: auth methodS * chore: styling * styling, styling, styling * feat: Auth notices * chore: Remove server-rendered pages, move shared/components -> components * lint * cleanup * cleanup * fix: Remove unused component * fix: Ensure env variables in prod too * style tweaks * fix: Entering SSO email into login form fails fix: Tweak language around guest signin
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 <Redirect to="/home" />;
|
||||
auth.logout(true);
|
||||
return null;
|
||||
});
|
||||
|
||||
export default inject("auth")(Home);
|
||||
@@ -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<Props> {
|
||||
@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<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
this.invites.push({ email: "", name: "", guest: false });
|
||||
this.invites.push({ email: "", name: "" });
|
||||
};
|
||||
|
||||
@action
|
||||
@@ -106,8 +104,8 @@ class Invite extends React.Component<Props> {
|
||||
{team.guestSignin ? (
|
||||
<HelpText>
|
||||
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.
|
||||
</HelpText>
|
||||
) : (
|
||||
<HelpText>
|
||||
@@ -116,22 +114,35 @@ class Invite extends React.Component<Props> {
|
||||
{can.update && (
|
||||
<React.Fragment>
|
||||
As an admin you can also{" "}
|
||||
<Link to="/settings/security">enable guest invites</Link>.
|
||||
<Link to="/settings/security">enable email sign-in</Link>.
|
||||
</React.Fragment>
|
||||
)}
|
||||
</HelpText>
|
||||
)}
|
||||
{team.subdomain && (
|
||||
<CopyBlock>
|
||||
Want a link to share directly with your team?
|
||||
<Flex>
|
||||
<Input type="text" value={team.url} readOnly flex />
|
||||
<Flex align="flex-end">
|
||||
<Input
|
||||
type="text"
|
||||
value={team.url}
|
||||
label="Want a link to share directly with your team?"
|
||||
readOnly
|
||||
flex
|
||||
/>
|
||||
<CopyToClipboard text={team.url} onCopy={this.handleCopy}>
|
||||
<Button type="button" neutral>
|
||||
<Button
|
||||
type="button"
|
||||
icon={<LinkIcon />}
|
||||
style={{ marginBottom: "16px" }}
|
||||
neutral
|
||||
>
|
||||
{this.linkCopied ? "Link copied" : "Copy link"}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</Flex>
|
||||
<p>
|
||||
<hr />
|
||||
</p>
|
||||
</CopyBlock>
|
||||
)}
|
||||
{this.invites.map((invite, index) => (
|
||||
@@ -159,29 +170,6 @@ class Invite extends React.Component<Props> {
|
||||
required={!!invite.email}
|
||||
flex
|
||||
/>
|
||||
{team.guestSignin && (
|
||||
<React.Fragment>
|
||||
|
||||
<Tooltip
|
||||
tooltip={
|
||||
<span>
|
||||
Guests can sign in with email and <br />do not require{" "}
|
||||
{team.signinMethods} accounts
|
||||
</span>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Guest>
|
||||
<Checkbox
|
||||
name="guest"
|
||||
label="Guest"
|
||||
onChange={ev => this.handleGuestChange(ev, index)}
|
||||
checked={invite.guest}
|
||||
/>
|
||||
</Guest>
|
||||
</Tooltip>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{index !== 0 && (
|
||||
<Remove>
|
||||
<Tooltip tooltip="Remove invite" placement="top">
|
||||
@@ -220,22 +208,8 @@ class Invite extends React.Component<Props> {
|
||||
}
|
||||
|
||||
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")`
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
56
app/scenes/Login/Notices.js
Normal file
56
app/scenes/Login/Notices.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import NoticeAlert from "components/NoticeAlert";
|
||||
|
||||
type Props = {
|
||||
notice?: string,
|
||||
};
|
||||
|
||||
export default function Notices({ notice }: Props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{notice === "google-hd" && (
|
||||
<NoticeAlert>
|
||||
Sorry, Google sign in cannot be used with a personal email. Please try
|
||||
signing in with your company GSuite account.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
{notice === "hd-not-allowed" && (
|
||||
<NoticeAlert>
|
||||
Sorry, your Google apps domain is not allowed. Please try again with
|
||||
an allowed company domain.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
{notice === "email-auth-required" && (
|
||||
<NoticeAlert>
|
||||
Your account uses email sign-in, please sign-in with email to
|
||||
continue.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
{notice === "email-auth-ratelimit" && (
|
||||
<NoticeAlert>
|
||||
An email sign-in link was recently sent, please check your inbox or
|
||||
try again in a few minutes.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
{notice === "auth-error" && (
|
||||
<NoticeAlert>
|
||||
Authentication failed - we were unable to sign you in at this time.
|
||||
Please try again.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
{notice === "expired-token" && (
|
||||
<NoticeAlert>
|
||||
Sorry, it looks like that sign-in link is no longer valid, please try
|
||||
requesting another.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
{notice === "suspended" && (
|
||||
<NoticeAlert>
|
||||
Your Outline account has been suspended. To re-activate your account,
|
||||
please contact a team admin.
|
||||
</NoticeAlert>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
147
app/scenes/Login/Service.js
Normal file
147
app/scenes/Login/Service.js
Normal file
@@ -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<Props, State> {
|
||||
state = {
|
||||
showEmailSignin: false,
|
||||
isSubmitting: false,
|
||||
email: "",
|
||||
};
|
||||
|
||||
handleChangeEmail = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
this.setState({ email: event.target.value });
|
||||
};
|
||||
|
||||
handleSubmitEmail = async (event: SyntheticEvent<HTMLFormElement>) => {
|
||||
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 (
|
||||
<Wrapper key="email">
|
||||
<Form
|
||||
method="POST"
|
||||
action="/auth/email"
|
||||
onSubmit={this.handleSubmitEmail}
|
||||
>
|
||||
{this.state.showEmailSignin ? (
|
||||
<React.Fragment>
|
||||
<InputLarge
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="me@domain.com"
|
||||
value={this.state.email}
|
||||
onChange={this.handleChangeEmail}
|
||||
disabled={this.state.isSubmitting}
|
||||
autoFocus
|
||||
required
|
||||
short
|
||||
/>
|
||||
<ButtonLarge type="submit" disabled={this.state.isSubmitting}>
|
||||
Sign In →
|
||||
</ButtonLarge>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<ButtonLarge type="submit" icon={<EmailIcon />} fullwidth>
|
||||
Continue with Email
|
||||
</ButtonLarge>
|
||||
)}
|
||||
</Form>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const icon =
|
||||
id === "slack" ? (
|
||||
<Logo>
|
||||
<SlackLogo size={16} />
|
||||
</Logo>
|
||||
) : id === "google" ? (
|
||||
<Logo>
|
||||
<GoogleLogo size={16} />
|
||||
</Logo>
|
||||
) : (
|
||||
undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper key={id}>
|
||||
<ButtonLarge
|
||||
onClick={() => (window.location.href = authUrl)}
|
||||
icon={icon}
|
||||
fullwidth
|
||||
>
|
||||
{isCreate ? "Sign up" : "Continue"} with {name}
|
||||
</ButtonLarge>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
230
app/scenes/Login/index.js
Normal file
230
app/scenes/Login/index.js
Normal file
@@ -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<Props, State> {
|
||||
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 <Redirect to="/home" />;
|
||||
}
|
||||
|
||||
// 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 href={process.env.URL}>
|
||||
<BackIcon color="currentColor" /> Back to home
|
||||
</Back>
|
||||
) : (
|
||||
<Back href="https://www.getoutline.com">
|
||||
<BackIcon color="currentColor" /> Back to website
|
||||
</Back>
|
||||
));
|
||||
|
||||
if (this.state.emailLinkSentTo) {
|
||||
return (
|
||||
<Background>
|
||||
{header}
|
||||
<Centered align="center" justify="center" column auto>
|
||||
<PageTitle title="Check your email" />
|
||||
<CheckEmailIcon size={38} color="currentColor" />
|
||||
|
||||
<Heading>Check your email</Heading>
|
||||
<Note>
|
||||
A magic sign-in link has been sent to the email{" "}
|
||||
<em>{this.state.emailLinkSentTo}</em>, no password needed.
|
||||
</Note>
|
||||
<br />
|
||||
<ButtonLarge onClick={this.handleReset} fullwidth neutral>
|
||||
Back to login
|
||||
</ButtonLarge>
|
||||
</Centered>
|
||||
</Background>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Background>
|
||||
{header}
|
||||
<Centered align="center" justify="center" column auto>
|
||||
<PageTitle title="Login" />
|
||||
<Logo>
|
||||
{process.env.TEAM_LOGO && process.env.DEPLOYMENT !== "hosted" ? (
|
||||
<TeamLogo src={process.env.TEAM_LOGO} />
|
||||
) : (
|
||||
<OutlineLogo size={38} fill="currentColor" />
|
||||
)}
|
||||
</Logo>
|
||||
|
||||
{isCreate ? (
|
||||
<Heading>Create an account</Heading>
|
||||
) : (
|
||||
<Heading>Login to {config.name || "Outline"}</Heading>
|
||||
)}
|
||||
|
||||
<Notices notice={getQueryVariable("notice")} />
|
||||
|
||||
{defaultService && (
|
||||
<React.Fragment key={defaultService.id}>
|
||||
<Service
|
||||
isCreate={isCreate}
|
||||
onEmailSuccess={this.handleEmailSuccess}
|
||||
{...defaultService}
|
||||
/>
|
||||
{hasMultipleServices && (
|
||||
<React.Fragment>
|
||||
<Note>
|
||||
You signed in with {defaultService.name} last time.
|
||||
</Note>
|
||||
<Or />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{config.services.map(service => {
|
||||
if (service.id === auth.lastSignedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Service
|
||||
key={service.id}
|
||||
isCreate={isCreate}
|
||||
onEmailSuccess={this.handleEmailSuccess}
|
||||
{...service}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Centered>
|
||||
</Background>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -60,8 +60,6 @@ class Security extends React.Component<Props> {
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
const { team } = this.props.auth;
|
||||
|
||||
return (
|
||||
<CenteredContent>
|
||||
<PageTitle title="Security" />
|
||||
@@ -72,27 +70,25 @@ class Security extends React.Component<Props> {
|
||||
</HelpText>
|
||||
|
||||
<Checkbox
|
||||
label="Allow guest invites"
|
||||
label="Allow email authentication"
|
||||
name="guestSignin"
|
||||
checked={this.guestSignin}
|
||||
onChange={this.handleChange}
|
||||
note={`When enabled guests can be invited by email address and are able to signin without ${
|
||||
team ? team.signinMethods : "SSO"
|
||||
}`}
|
||||
note="When enabled, users can sign-in using their email address"
|
||||
/>
|
||||
<Checkbox
|
||||
label="Public document sharing"
|
||||
name="sharing"
|
||||
checked={this.sharing}
|
||||
onChange={this.handleChange}
|
||||
note="When enabled documents can be shared publicly by any team member"
|
||||
note="When enabled, documents can be shared publicly on the internet by any team member"
|
||||
/>
|
||||
<Checkbox
|
||||
label="Rich service embeds"
|
||||
name="documentEmbeds"
|
||||
checked={this.documentEmbeds}
|
||||
onChange={this.handleChange}
|
||||
note="Convert links to supported services into rich embeds within your documents"
|
||||
note="Links to supported services are shown as rich embeds within your documents"
|
||||
/>
|
||||
</CenteredContent>
|
||||
);
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user