diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index 668875ca0..0f923eb38 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -4,25 +4,24 @@ import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { Actions, useStoreActions } from 'easy-peasy'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { ApplicationStore } from '@/state'; +import Field from '@/components/elements/Field'; +import { Formik, FormikHelpers } from 'formik'; +import { object, string } from 'yup'; + +interface Values { + email: string; +} export default () => { - const [ isSubmitting, setSubmitting ] = React.useState(false); - const [ email, setEmail ] = React.useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); - const handleFieldUpdate = (e: React.ChangeEvent) => setEmail(e.target.value); - - const handleSubmission = (e: React.FormEvent) => { - e.preventDefault(); - + const handleSubmission = ({ email }: Values, { setSubmitting, resetForm }: FormikHelpers) => { setSubmitting(true); clearFlashes(); requestPasswordResetEmail(email) .then(response => { - setEmail(''); + resetForm(); addFlash({ type: 'success', title: 'Success', message: response }); }) .catch(error => { @@ -33,46 +32,50 @@ export default () => { }; return ( -
-

- Request Password Reset -

- - - - -

- Enter your account email address to receive instructions on resetting your password. -

-
- -
-
- - Return to Login - -
-
-
+ + {({ isSubmitting }) => ( + + +
+ +
+
+ + Return to Login + +
+
+ )} +
); }; diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 23c75b7cb..bebc6a059 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -2,7 +2,6 @@ import React, { useRef } from 'react'; import { Link, RouteComponentProps } from 'react-router-dom'; import login, { LoginData } from '@/api/auth/login'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { ActionCreator, Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { FormikProps, withFormik } from 'formik'; @@ -12,33 +11,12 @@ import { httpErrorToHuman } from '@/api/http'; import { FlashMessage } from '@/state/flashes'; import ReCAPTCHA from 'react-google-recaptcha'; import Spinner from '@/components/elements/Spinner'; -import styled from 'styled-components'; -import { breakpoint } from 'styled-components-breakpoint'; type OwnProps = RouteComponentProps & { clearFlashes: ActionCreator; addFlash: ActionCreator; } -const Container = styled.div` - ${breakpoint('sm')` - ${tw`w-4/5 mx-auto`} - `}; - - ${breakpoint('md')` - ${tw`p-10`} - `}; - - ${breakpoint('lg')` - ${tw`w-3/5`} - `}; - - ${breakpoint('xl')` - ${tw`w-full`} - max-width: 660px; - `}; -`; - const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handleSubmit }: OwnProps & FormikProps) => { const ref = useRef(null); const { enabled: recaptchaEnabled, siteKey } = useStoreState(state => state.settings.data!.recaptcha); @@ -56,66 +34,61 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl return ( {ref.current && ref.current.render()} -

- Login to Continue -

- - - - + + + +
+ -
- - -
-
- -
- {recaptchaEnabled && - { - ref.current && ref.current.reset(); - setFieldValue('recaptchaData', token); - submitForm(); - }} - onExpired={() => setFieldValue('recaptchaData', null)} - /> - } -
- - Forgot password? - -
- - +
+
+ +
+ {recaptchaEnabled && + { + ref.current && ref.current.reset(); + setFieldValue('recaptchaData', token); + submitForm(); + }} + onExpired={() => setFieldValue('recaptchaData', null)} + /> + } +
+ + Forgot password? + +
+
); }; diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index bfdbe6782..5e291fb29 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -1,17 +1,47 @@ import React, { forwardRef } from 'react'; import { Form } from 'formik'; +import styled from 'styled-components'; +import { breakpoint } from 'styled-components-breakpoint'; +import FlashMessageRender from '@/components/FlashMessageRender'; -type Props = React.DetailedHTMLProps, HTMLFormElement>; +type Props = React.DetailedHTMLProps, HTMLFormElement> & { + title?: string; +} -export default forwardRef(({ ...props }, ref) => ( -
-
-
- +const Container = styled.div` + ${breakpoint('sm')` + ${tw`w-4/5 mx-auto`} + `}; + + ${breakpoint('md')` + ${tw`p-10`} + `}; + + ${breakpoint('lg')` + ${tw`w-3/5`} + `}; + + ${breakpoint('xl')` + ${tw`w-full`} + max-width: 700px; + `}; +`; + +export default forwardRef(({ title, ...props }, ref) => ( + + {title &&

+ {title} +

} + + +
+
+ +
+
+ {props.children} +
-
- {props.children} -
-
- + + )); diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index ec3b3ff6f..9801d9351 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -5,106 +5,110 @@ import { Link } from 'react-router-dom'; import performPasswordReset from '@/api/auth/performPasswordReset'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import Spinner from '@/components/elements/Spinner'; +import { Formik, FormikHelpers } from 'formik'; +import { object, ref, string } from 'yup'; +import Field from '@/components/elements/Field'; type Props = Readonly & {}>; -export default (props: Props) => { - const [ isLoading, setIsLoading ] = useState(false); +interface Values { + password: string; + passwordConfirmation: string; +} + +export default ({ match, history, location }: Props) => { const [ email, setEmail ] = useState(''); - const [ password, setPassword ] = useState(''); - const [ passwordConfirm, setPasswordConfirm ] = useState(''); const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); - const parsed = parse(props.location.search); + const parsed = parse(location.search); if (email.length === 0 && parsed.email) { setEmail(parsed.email as string); } - const canSubmit = () => password && email && password.length >= 8 && password === passwordConfirm; - - const submit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!password || !email || !passwordConfirm) { - return; - } - - setIsLoading(true); + const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes(); - - performPasswordReset(email, { - token: props.match.params.token, password, passwordConfirmation: passwordConfirm, - }) + performPasswordReset(email, { token: match.params.token, password, passwordConfirmation }) .then(() => { - addFlash({ type: 'success', message: 'Your password has been reset, please login to continue.' }); - props.history.push('/auth/login'); + // @ts-ignore + window.location = '/'; + return; }) .catch(error => { console.error(error); + + setSubmitting(false); addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); - }) - .then(() => setIsLoading(false)); + }); }; return ( -
-

- Reset Password -

- - - - -
- - setPassword(e.target.value)} - /> -

- Passwords must be at least 8 characters in length. -

-
-
- - setPasswordConfirm(e.target.value)} - /> -
-
- -
-
- - Return to Login - -
-
-
+ + {({ isSubmitting }) => ( + +
+ + +
+
+ +
+
+ +
+
+ +
+
+ + Return to Login + +
+
+ )} +
); }; diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index 9f2f6d64f..feb819b47 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; interface OwnProps { name: string; + light?: boolean; label?: string; description?: string; validate?: (value: any) => undefined | string | Promise; @@ -11,19 +12,19 @@ interface OwnProps { type Props = OwnProps & Omit, 'name'>; -const Field = ({ id, name, label, description, validate, className, ...props }: Props) => ( +const Field = ({ id, name, light = false, label, description, validate, className, ...props }: Props) => ( { ({ field, form: { errors, touched } }: FieldProps) => ( {label && - + }