Skip to content

Commit 7244cdb

Browse files
committed
Fix up authentication flows to use formik correctly
1 parent cb945b1 commit 7244cdb

File tree

5 files changed

+233
-222
lines changed

5 files changed

+233
-222
lines changed

resources/scripts/components/auth/ForgotPasswordContainer.tsx

Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,24 @@ import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail';
44
import { httpErrorToHuman } from '@/api/http';
55
import LoginFormContainer from '@/components/auth/LoginFormContainer';
66
import { Actions, useStoreActions } from 'easy-peasy';
7-
import FlashMessageRender from '@/components/FlashMessageRender';
87
import { ApplicationStore } from '@/state';
8+
import Field from '@/components/elements/Field';
9+
import { Formik, FormikHelpers } from 'formik';
10+
import { object, string } from 'yup';
911

10-
export default () => {
11-
const [ isSubmitting, setSubmitting ] = React.useState(false);
12-
const [ email, setEmail ] = React.useState('');
12+
interface Values {
13+
email: string;
14+
}
1315

16+
export default () => {
1417
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
1518

16-
const handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value);
17-
18-
const handleSubmission = (e: React.FormEvent<HTMLFormElement>) => {
19-
e.preventDefault();
20-
19+
const handleSubmission = ({ email }: Values, { setSubmitting, resetForm }: FormikHelpers<Values>) => {
2120
setSubmitting(true);
2221
clearFlashes();
2322
requestPasswordResetEmail(email)
2423
.then(response => {
25-
setEmail('');
24+
resetForm();
2625
addFlash({ type: 'success', title: 'Success', message: response });
2726
})
2827
.catch(error => {
@@ -33,46 +32,50 @@ export default () => {
3332
};
3433

3534
return (
36-
<div>
37-
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
38-
Request Password Reset
39-
</h2>
40-
<FlashMessageRender/>
41-
<LoginFormContainer onSubmit={handleSubmission}>
42-
<label htmlFor={'email'}>Email</label>
43-
<input
44-
id={'email'}
45-
type={'email'}
46-
required={true}
47-
className={'input'}
48-
value={email}
49-
onChange={handleFieldUpdate}
50-
autoFocus={true}
51-
/>
52-
<p className={'input-help'}>
53-
Enter your account email address to receive instructions on resetting your password.
54-
</p>
55-
<div className={'mt-6'}>
56-
<button
57-
className={'btn btn-primary btn-jumbo flex justify-center'}
58-
disabled={isSubmitting || email.length < 5}
59-
>
60-
{isSubmitting ?
61-
<div className={'spinner-circle spinner-sm spinner-white'}></div>
62-
:
63-
'Send Email'
64-
}
65-
</button>
66-
</div>
67-
<div className={'mt-6 text-center'}>
68-
<Link
69-
to={'/auth/login'}
70-
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
71-
>
72-
Return to Login
73-
</Link>
74-
</div>
75-
</LoginFormContainer>
76-
</div>
35+
<Formik
36+
onSubmit={handleSubmission}
37+
initialValues={{ email: '' }}
38+
validationSchema={object().shape({
39+
email: string().email('A valid email address must be provided to continue.')
40+
.required('A valid email address must be provided to continue.'),
41+
})}
42+
>
43+
{({ isSubmitting }) => (
44+
<LoginFormContainer
45+
title={'Request Password Reset'}
46+
className={'w-full flex'}
47+
>
48+
<Field
49+
light={true}
50+
label={'Email'}
51+
description={'Enter your account email address to receive instructions on resetting your password.'}
52+
name={'email'}
53+
type={'email'}
54+
/>
55+
<div className={'mt-6'}>
56+
<button
57+
type={'submit'}
58+
className={'btn btn-primary btn-jumbo flex justify-center'}
59+
disabled={isSubmitting}
60+
>
61+
{isSubmitting ?
62+
<div className={'spinner-circle spinner-sm spinner-white'}></div>
63+
:
64+
'Send Email'
65+
}
66+
</button>
67+
</div>
68+
<div className={'mt-6 text-center'}>
69+
<Link
70+
type={'button'}
71+
to={'/auth/login'}
72+
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
73+
>
74+
Return to Login
75+
</Link>
76+
</div>
77+
</LoginFormContainer>
78+
)}
79+
</Formik>
7780
);
7881
};

resources/scripts/components/auth/LoginContainer.tsx

Lines changed: 52 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useRef } from 'react';
22
import { Link, RouteComponentProps } from 'react-router-dom';
33
import login, { LoginData } from '@/api/auth/login';
44
import LoginFormContainer from '@/components/auth/LoginFormContainer';
5-
import FlashMessageRender from '@/components/FlashMessageRender';
65
import { ActionCreator, Actions, useStoreActions, useStoreState } from 'easy-peasy';
76
import { ApplicationStore } from '@/state';
87
import { FormikProps, withFormik } from 'formik';
@@ -12,33 +11,12 @@ import { httpErrorToHuman } from '@/api/http';
1211
import { FlashMessage } from '@/state/flashes';
1312
import ReCAPTCHA from 'react-google-recaptcha';
1413
import Spinner from '@/components/elements/Spinner';
15-
import styled from 'styled-components';
16-
import { breakpoint } from 'styled-components-breakpoint';
1714

1815
type OwnProps = RouteComponentProps & {
1916
clearFlashes: ActionCreator<void>;
2017
addFlash: ActionCreator<FlashMessage>;
2118
}
2219

23-
const Container = styled.div`
24-
${breakpoint('sm')`
25-
${tw`w-4/5 mx-auto`}
26-
`};
27-
28-
${breakpoint('md')`
29-
${tw`p-10`}
30-
`};
31-
32-
${breakpoint('lg')`
33-
${tw`w-3/5`}
34-
`};
35-
36-
${breakpoint('xl')`
37-
${tw`w-full`}
38-
max-width: 660px;
39-
`};
40-
`;
41-
4220
const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handleSubmit }: OwnProps & FormikProps<LoginData>) => {
4321
const ref = useRef<ReCAPTCHA | null>(null);
4422
const { enabled: recaptchaEnabled, siteKey } = useStoreState<ApplicationStore, any>(state => state.settings.data!.recaptcha);
@@ -56,66 +34,61 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
5634
return (
5735
<React.Fragment>
5836
{ref.current && ref.current.render()}
59-
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
60-
Login to Continue
61-
</h2>
62-
<Container>
63-
<FlashMessageRender className={'mb-2 px-1'}/>
64-
<LoginFormContainer
65-
className={'w-full flex'}
66-
onSubmit={submit}
67-
>
68-
<label htmlFor={'username'}>Username or Email</label>
37+
<LoginFormContainer
38+
title={'Login to Continue'}
39+
className={'w-full flex'}
40+
onSubmit={submit}
41+
>
42+
<label htmlFor={'username'}>Username or Email</label>
43+
<Field
44+
type={'text'}
45+
id={'username'}
46+
name={'username'}
47+
className={'input'}
48+
/>
49+
<div className={'mt-6'}>
50+
<label htmlFor={'password'}>Password</label>
6951
<Field
70-
type={'text'}
71-
id={'username'}
72-
name={'username'}
52+
type={'password'}
53+
id={'password'}
54+
name={'password'}
7355
className={'input'}
7456
/>
75-
<div className={'mt-6'}>
76-
<label htmlFor={'password'}>Password</label>
77-
<Field
78-
type={'password'}
79-
id={'password'}
80-
name={'password'}
81-
className={'input'}
82-
/>
83-
</div>
84-
<div className={'mt-6'}>
85-
<button
86-
type={'submit'}
87-
className={'btn btn-primary btn-jumbo'}
88-
>
89-
{isSubmitting ?
90-
<Spinner size={'tiny'} className={'mx-auto'}/>
91-
:
92-
'Login'
93-
}
94-
</button>
95-
</div>
96-
{recaptchaEnabled &&
97-
<ReCAPTCHA
98-
ref={ref}
99-
size={'invisible'}
100-
sitekey={siteKey || '_invalid_key'}
101-
onChange={token => {
102-
ref.current && ref.current.reset();
103-
setFieldValue('recaptchaData', token);
104-
submitForm();
105-
}}
106-
onExpired={() => setFieldValue('recaptchaData', null)}
107-
/>
108-
}
109-
<div className={'mt-6 text-center'}>
110-
<Link
111-
to={'/auth/password'}
112-
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
113-
>
114-
Forgot password?
115-
</Link>
116-
</div>
117-
</LoginFormContainer>
118-
</Container>
57+
</div>
58+
<div className={'mt-6'}>
59+
<button
60+
type={'submit'}
61+
className={'btn btn-primary btn-jumbo'}
62+
>
63+
{isSubmitting ?
64+
<Spinner size={'tiny'} className={'mx-auto'}/>
65+
:
66+
'Login'
67+
}
68+
</button>
69+
</div>
70+
{recaptchaEnabled &&
71+
<ReCAPTCHA
72+
ref={ref}
73+
size={'invisible'}
74+
sitekey={siteKey || '_invalid_key'}
75+
onChange={token => {
76+
ref.current && ref.current.reset();
77+
setFieldValue('recaptchaData', token);
78+
submitForm();
79+
}}
80+
onExpired={() => setFieldValue('recaptchaData', null)}
81+
/>
82+
}
83+
<div className={'mt-6 text-center'}>
84+
<Link
85+
to={'/auth/password'}
86+
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
87+
>
88+
Forgot password?
89+
</Link>
90+
</div>
91+
</LoginFormContainer>
11992
</React.Fragment>
12093
);
12194
};
Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,47 @@
11
import React, { forwardRef } from 'react';
22
import { Form } from 'formik';
3+
import styled from 'styled-components';
4+
import { breakpoint } from 'styled-components-breakpoint';
5+
import FlashMessageRender from '@/components/FlashMessageRender';
36

4-
type Props = React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>;
7+
type Props = React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> & {
8+
title?: string;
9+
}
510

6-
export default forwardRef<any, Props>(({ ...props }, ref) => (
7-
<Form {...props}>
8-
<div className={'md:flex w-full bg-white shadow-lg rounded-lg p-6 mx-1'}>
9-
<div className={'flex-none select-none mb-6 md:mb-0 self-center'}>
10-
<img src={'/assets/pterodactyl.svg'} className={'block w-48 md:w-64 mx-auto'}/>
11-
</div>
12-
<div className={'flex-1'}>
13-
{props.children}
11+
const Container = styled.div`
12+
${breakpoint('sm')`
13+
${tw`w-4/5 mx-auto`}
14+
`};
15+
16+
${breakpoint('md')`
17+
${tw`p-10`}
18+
`};
19+
20+
${breakpoint('lg')`
21+
${tw`w-3/5`}
22+
`};
23+
24+
${breakpoint('xl')`
25+
${tw`w-full`}
26+
max-width: 700px;
27+
`};
28+
`;
29+
30+
export default forwardRef<HTMLFormElement, Props>(({ title, ...props }, ref) => (
31+
<Container>
32+
{title && <h2 className={'text-center text-neutral-100 font-medium py-4'}>
33+
{title}
34+
</h2>}
35+
<FlashMessageRender className={'mb-2 px-1'}/>
36+
<Form {...props} ref={ref}>
37+
<div className={'md:flex w-full bg-white shadow-lg rounded-lg p-6 md:pl-0 mx-1'}>
38+
<div className={'flex-none select-none mb-6 md:mb-0 self-center'}>
39+
<img src={'/assets/pterodactyl.svg'} className={'block w-48 md:w-64 mx-auto'}/>
40+
</div>
41+
<div className={'flex-1'}>
42+
{props.children}
43+
</div>
1444
</div>
15-
</div>
16-
</Form>
45+
</Form>
46+
</Container>
1747
));

0 commit comments

Comments
 (0)