mirror of
https://github.com/wassname/talk.git
synced 2026-09-12 13:01:11 +08:00
[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:
committed by
Wyatt Johnson
parent
2559d1c0e8
commit
a89dc4675a
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user