diff --git a/src/core/client/admin/routeConfig.tsx b/src/core/client/admin/routeConfig.tsx index cafbc2cc4..dded23a70 100644 --- a/src/core/client/admin/routeConfig.tsx +++ b/src/core/client/admin/routeConfig.tsx @@ -16,6 +16,7 @@ import { OrganizationConfigRoute, WordListConfigRoute, } from "./routes/Configure/sections"; +import ForgotPasswordRoute from "./routes/ForgotPassword"; import InviteRoute from "./routes/Invite"; import LoginRoute from "./routes/Login"; import ModerateRoute from "./routes/Moderate"; @@ -79,5 +80,6 @@ export default makeRouteConfig( + ); diff --git a/src/core/client/admin/routes/ForgotPassword/CheckEmail.tsx b/src/core/client/admin/routes/ForgotPassword/CheckEmail.tsx new file mode 100644 index 000000000..f3984af77 --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/CheckEmail.tsx @@ -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 = ({ email }) => ( +
+
+ + } + > + + If there is an account associated with {email}, you + will receive an email with a link to create a new password. + + + +
+
+); + +export default CheckEmail; diff --git a/src/core/client/admin/routes/ForgotPassword/ForgotPassword.css b/src/core/client/admin/routes/ForgotPassword/ForgotPassword.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.css b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.css new file mode 100644 index 000000000..bd567a043 --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.css @@ -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); +} \ No newline at end of file diff --git a/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.tsx b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.tsx new file mode 100644 index 000000000..8abf89ef6 --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordContainer.tsx @@ -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(null); + return ( + +
+ + {!checkEmail ? ( + + Forgot Password? + + ) : ( + + Check Email + + )} + + + + + + Go back to sign in page + + + + + {checkEmail ? ( + + ) : ( + + )} +
+
+ ); +}; + +export default ForgotPasswordContainer; diff --git a/src/core/client/admin/routes/ForgotPassword/ForgotPasswordForm.tsx b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordForm.tsx new file mode 100644 index 000000000..fa0a3aaa7 --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordForm.tsx @@ -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 => { + 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 ( +
+
+
+ {({ handleSubmit, submitting, submitError }) => ( + + + + + Enter your email address below and we will send you a link + to reset your password. + + + {submitError && ( + + {submitError} + + )} + + {({ input, meta }) => ( + + + + Email Address + + + + + + + + )} + + + + + +
+ )} + +
+
+ ); +}; + +export default ForgotPasswordForm; diff --git a/src/core/client/admin/routes/ForgotPassword/ForgotPasswordMutation.ts b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordMutation.ts new file mode 100644 index 000000000..8e0622ac6 --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordMutation.ts @@ -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; diff --git a/src/core/client/admin/routes/ForgotPassword/ForgotPasswordRoute.tsx b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordRoute.tsx new file mode 100644 index 000000000..2ec0390fb --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/ForgotPasswordRoute.tsx @@ -0,0 +1,11 @@ +import { withRouteConfig } from "coral-framework/lib/router"; +import React from "react"; +import ForgotPasswordContainer from "./ForgotPasswordContainer"; + +const ForgotPasswordRoute: React.FunctionComponent = () => { + return ; +}; + +const enhanced = withRouteConfig({})(ForgotPasswordRoute); + +export default enhanced; diff --git a/src/core/client/admin/routes/ForgotPassword/index.ts b/src/core/client/admin/routes/ForgotPassword/index.ts new file mode 100644 index 000000000..6c34faa31 --- /dev/null +++ b/src/core/client/admin/routes/ForgotPassword/index.ts @@ -0,0 +1 @@ +export { default, default as ForgotPasswordRoute } from "./ForgotPasswordRoute"; diff --git a/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.css b/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.css new file mode 100644 index 000000000..c2b95658d --- /dev/null +++ b/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.css @@ -0,0 +1,4 @@ +.textLink { + color: var(--palette-primary-main); + text-decoration: underline; +} \ No newline at end of file diff --git a/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.tsx b/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.tsx index 17eeb62f9..bc883cf5a 100644 --- a/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.tsx +++ b/src/core/client/admin/routes/Login/views/SignIn/SignInWithEmail.tsx @@ -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 = props => { /> + + + + + Forgot your password? + + + + )} @@ -74,6 +91,7 @@ const SignInWithEmail: FunctionComponent = props => { fullWidth > email + Sign in with Email diff --git a/src/core/client/admin/test/auth/__snapshots__/signInWithEmail.spec.tsx.snap b/src/core/client/admin/test/auth/__snapshots__/signInWithEmail.spec.tsx.snap index d3f29b635..c56fdd3af 100644 --- a/src/core/client/admin/test/auth/__snapshots__/signInWithEmail.spec.tsx.snap +++ b/src/core/client/admin/test/auth/__snapshots__/signInWithEmail.spec.tsx.snap @@ -94,6 +94,21 @@ exports[`accepts correct password 1`] = ` +