[CORL-516] forgot password flow in admin (#2558)

* add forgot password page and link to admin

* add translations

* update snaps

* fix: install patch

* fix: adapted localizations

* fix: added email type to email field

* fix: updated snapshots
This commit is contained in:
Tessa Thornton
2019-09-12 16:30:19 +00:00
committed by Wyatt Johnson
parent 2559d1c0e8
commit a89dc4675a
18 changed files with 416 additions and 47 deletions
@@ -0,0 +1,30 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent } from "react";
import Main from "coral-auth/components/Main";
import { HorizontalGutter, Typography } from "coral-ui/components";
interface Props {
email: string;
}
const CheckEmail: FunctionComponent<Props> = ({ email }) => (
<div data-testid="forgotPassword-checkEmail-container">
<Main data-testid="forgotPassword-checkEmail-main">
<HorizontalGutter size="full">
<Localized
id="forgotPassword-checkEmail-receiveEmail"
$email={email}
strong={<strong />}
>
<Typography variant="bodyCopy">
If there is an account associated with <strong>{email}</strong>, you
will receive an email with a link to create a new password.
</Typography>
</Localized>
</HorizontalGutter>
</Main>
</div>
);
export default CheckEmail;
@@ -0,0 +1,11 @@
.container {
width: 350px;
background-color: #f5f5f5;
border: 1px solid var(--palette-grey-lighter);
margin-top: 70px;
box-sizing: border-box;
}
.textLink {
color: var(--palette-primary-main);
}
@@ -0,0 +1,47 @@
import { Localized } from "fluent-react/compat";
import { Link } from "found";
import React, { FunctionComponent, useState } from "react";
import { Bar, SubBar, Title } from "coral-auth/components/Header";
import { Flex, Typography } from "coral-ui/components";
import CheckEmail from "./CheckEmail";
import ForgotPasswordForm from "./ForgotPasswordForm";
import styles from "./ForgotPasswordContainer.css";
const ForgotPasswordContainer: FunctionComponent = () => {
const [checkEmail, setCheckEmail] = useState<string | null>(null);
return (
<Flex justifyContent="center">
<div className={styles.container}>
<Bar>
{!checkEmail ? (
<Localized id="forgotPassword-forgotPasswordHeader">
<Title>Forgot Password?</Title>
</Localized>
) : (
<Localized id="forgotPassword-checkEmailHeader">
<Title>Check Email</Title>
</Localized>
)}
</Bar>
<SubBar>
<Typography variant="bodyCopy" container={Flex}>
<Localized id="forgotPassword-gotBackToSignIn">
<Link className={styles.textLink} to="/admin/login">
Go back to sign in page
</Link>
</Localized>
</Typography>
</SubBar>
{checkEmail ? (
<CheckEmail email={checkEmail} />
) : (
<ForgotPasswordForm onCheckEmail={setCheckEmail} />
)}
</div>
</Flex>
);
};
export default ForgotPasswordContainer;
@@ -0,0 +1,120 @@
import { FORM_ERROR } from "final-form";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { Field, Form } from "react-final-form";
import Main from "coral-auth/components/Main";
import { InvalidRequestError } from "coral-framework/lib/errors";
import { colorFromMeta, ValidationMessage } from "coral-framework/lib/form";
import { useMutation } from "coral-framework/lib/relay";
import {
composeValidators,
required,
validateEmail,
} from "coral-framework/lib/validation";
import {
Button,
CallOut,
FormField,
HorizontalGutter,
InputLabel,
TextField,
Typography,
} from "coral-ui/components";
import ForgotPasswordMutation from "./ForgotPasswordMutation";
interface FormProps {
email: string;
}
interface Props {
onCheckEmail: (email: string) => void;
}
const ForgotPasswordForm: FunctionComponent<Props> = props => {
const forgotPassword = useMutation(ForgotPasswordMutation);
const onSubmit = useCallback(
async (form: FormProps) => {
try {
await forgotPassword(form);
props.onCheckEmail(form.email);
} catch (error) {
if (error instanceof InvalidRequestError) {
return error.invalidArgs;
}
return { [FORM_ERROR]: error.message };
}
return;
},
[forgotPassword]
);
return (
<div data-testid="admin-forgotPassword-container">
<Main data-testid="admin-forgotPassword-main">
<Form onSubmit={onSubmit}>
{({ handleSubmit, submitting, submitError }) => (
<form autoComplete="off" onSubmit={handleSubmit}>
<HorizontalGutter size="full">
<Localized id="forgotPassword-enterEmailAndGetALink">
<Typography variant="bodyCopy">
Enter your email address below and we will send you a link
to reset your password.
</Typography>
</Localized>
{submitError && (
<CallOut color="error" fullWidth>
{submitError}
</CallOut>
)}
<Field
name="email"
validate={composeValidators(required, validateEmail)}
>
{({ input, meta }) => (
<FormField>
<Localized id="forgotPassword-emailAddressLabel">
<InputLabel htmlFor={input.name}>
Email Address
</InputLabel>
</Localized>
<Localized
id="forgotPassword-emailAddressTextField"
attrs={{ placeholder: true }}
>
<TextField
id={input.name}
placeholder="Email Address"
color={colorFromMeta(meta)}
disabled={submitting}
type="email"
fullWidth
{...input}
/>
</Localized>
<ValidationMessage meta={meta} fullWidth />
</FormField>
)}
</Field>
<Localized id="forgotPassword-sendEmailButton">
<Button
variant="filled"
color="primary"
size="large"
fullWidth
type="submit"
disabled={submitting}
>
Send Email
</Button>
</Localized>
</HorizontalGutter>
</form>
)}
</Form>
</Main>
</div>
);
};
export default ForgotPasswordForm;
@@ -0,0 +1,12 @@
import { pick } from "lodash";
import { createMutation } from "coral-framework/lib/relay";
import { forgotPassword, ForgotPasswordInput } from "coral-framework/rest";
const ForgotPasswordMutation = createMutation(
"forgotPassword",
(_, input: ForgotPasswordInput, { rest }) =>
forgotPassword(rest, pick(input, ["email"]))
);
export default ForgotPasswordMutation;
@@ -0,0 +1,11 @@
import { withRouteConfig } from "coral-framework/lib/router";
import React from "react";
import ForgotPasswordContainer from "./ForgotPasswordContainer";
const ForgotPasswordRoute: React.FunctionComponent = () => {
return <ForgotPasswordContainer />;
};
const enhanced = withRouteConfig({})(ForgotPasswordRoute);
export default enhanced;
@@ -0,0 +1 @@
export { default, default as ForgotPasswordRoute } from "./ForgotPasswordRoute";
@@ -0,0 +1,4 @@
.textLink {
color: var(--palette-primary-main);
text-decoration: underline;
}
@@ -1,4 +1,5 @@
import { Localized } from "fluent-react/compat";
import { Link } from "found";
import React, { FunctionComponent } from "react";
import { Field, Form } from "react-final-form";
@@ -13,13 +14,17 @@ import {
Button,
ButtonIcon,
CallOut,
Flex,
FormField,
HorizontalGutter,
InputLabel,
Typography,
} from "coral-ui/components";
import EmailField from "../../EmailField";
import styles from "./SignInWithEmail.css";
interface FormProps {
email: string;
password: string;
@@ -62,6 +67,18 @@ const SignInWithEmail: FunctionComponent<SignInWithEmailForm> = props => {
/>
</Localized>
<ValidationMessage meta={meta} fullWidth />
<Flex justifyContent="flex-end">
<Typography>
<Localized id="login-signIn-forgot-password">
<Link
className={styles.textLink}
to="/admin/forgot-password"
>
Forgot your password?
</Link>
</Localized>
</Typography>
</Flex>
</FormField>
)}
</Field>
@@ -74,6 +91,7 @@ const SignInWithEmail: FunctionComponent<SignInWithEmailForm> = props => {
fullWidth
>
<ButtonIcon size="md">email</ButtonIcon>
<Localized id="login-signIn-signInWithEmail">
<span>Sign in with Email</span>
</Localized>