Skip to content

Commit f864b72

Browse files
committed
Get formik used on login form
1 parent d9d4c05 commit f864b72

File tree

3 files changed

+96
-77
lines changed

3 files changed

+96
-77
lines changed
Lines changed: 86 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,114 @@
1-
import React, { useState } from 'react';
1+
import React from 'react';
22
import { Link, RouteComponentProps } from 'react-router-dom';
33
import login from '@/api/auth/login';
4-
import { httpErrorToHuman } from '@/api/http';
54
import LoginFormContainer from '@/components/auth/LoginFormContainer';
65
import FlashMessageRender from '@/components/FlashMessageRender';
76
import { Actions, useStoreActions } from 'easy-peasy';
87
import { ApplicationStore } from '@/state';
8+
import { FormikProps, withFormik } from 'formik';
9+
import { object, string } from 'yup';
10+
import Field from '@/components/elements/Field';
11+
import { httpErrorToHuman } from '@/api/http';
912

10-
export default ({ history }: RouteComponentProps) => {
11-
const [ username, setUsername ] = useState('');
12-
const [ password, setPassword ] = useState('');
13-
const [ isLoading, setLoading ] = useState(false);
13+
interface Values {
14+
username: string;
15+
password: string;
16+
}
1417

15-
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
18+
type OwnProps = RouteComponentProps & {
19+
clearFlashes: any;
20+
addFlash: any;
21+
}
22+
23+
const LoginContainer = ({ isSubmitting }: OwnProps & FormikProps<Values>) => (
24+
<React.Fragment>
25+
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
26+
Login to Continue
27+
</h2>
28+
<FlashMessageRender className={'mb-2'}/>
29+
<LoginFormContainer>
30+
<label htmlFor={'username'}>Username or Email</label>
31+
<Field
32+
type={'text'}
33+
id={'username'}
34+
name={'username'}
35+
className={'input'}
36+
/>
37+
<div className={'mt-6'}>
38+
<label htmlFor={'password'}>Password</label>
39+
<Field
40+
type={'password'}
41+
id={'password'}
42+
name={'password'}
43+
className={'input'}
44+
/>
45+
</div>
46+
<div className={'mt-6'}>
47+
<button
48+
type={'submit'}
49+
className={'btn btn-primary btn-jumbo'}
50+
>
51+
{isSubmitting ?
52+
<span className={'spinner white'}>&nbsp;</span>
53+
:
54+
'Login'
55+
}
56+
</button>
57+
</div>
58+
<div className={'mt-6 text-center'}>
59+
<Link
60+
to={'/auth/password'}
61+
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
62+
>
63+
Forgot password?
64+
</Link>
65+
</div>
66+
</LoginFormContainer>
67+
</React.Fragment>
68+
);
69+
70+
const EnhancedForm = withFormik<OwnProps, Values>({
71+
displayName: 'LoginContainerForm',
1672

17-
const submit = (e: React.FormEvent<HTMLFormElement>) => {
18-
e.preventDefault();
73+
mapPropsToValues: (props) => ({
74+
username: '',
75+
password: '',
76+
}),
1977

20-
setLoading(true);
21-
clearFlashes();
78+
validationSchema: () => object().shape({
79+
username: string().required('A username or email must be provided.'),
80+
password: string().required('Please enter your account password.'),
81+
}),
2282

23-
login(username!, password!)
83+
handleSubmit: ({ username, password }, { props, setSubmitting }) => {
84+
props.clearFlashes();
85+
login(username, password)
2486
.then(response => {
2587
if (response.complete) {
2688
// @ts-ignore
2789
window.location = response.intended || '/';
2890
return;
2991
}
3092

31-
history.replace('/auth/login/checkpoint', { token: response.confirmationToken });
93+
props.history.replace('/auth/login/checkpoint', { token: response.confirmationToken });
3294
})
3395
.catch(error => {
3496
console.error(error);
3597

36-
setLoading(false);
37-
addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
98+
setSubmitting(false);
99+
props.addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
38100
});
39-
};
101+
},
102+
})(LoginContainer);
40103

41-
const canSubmit = () => username && password && username.length > 0 && password.length > 0;
104+
export default (props: RouteComponentProps) => {
105+
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
42106

43107
return (
44-
<React.Fragment>
45-
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
46-
Login to Continue
47-
</h2>
48-
<FlashMessageRender/>
49-
<LoginFormContainer onSubmit={submit}>
50-
<label htmlFor={'username'}>Username or Email</label>
51-
<input
52-
id={'username'}
53-
autoFocus={true}
54-
required={true}
55-
className={'input'}
56-
onChange={e => setUsername(e.target.value)}
57-
disabled={isLoading}
58-
/>
59-
<div className={'mt-6'}>
60-
<label htmlFor={'password'}>Password</label>
61-
<input
62-
id={'password'}
63-
required={true}
64-
type={'password'}
65-
className={'input'}
66-
onChange={e => setPassword(e.target.value)}
67-
disabled={isLoading}
68-
/>
69-
</div>
70-
<div className={'mt-6'}>
71-
<button
72-
type={'submit'}
73-
className={'btn btn-primary btn-jumbo'}
74-
disabled={isLoading || !canSubmit()}
75-
>
76-
{isLoading ?
77-
<span className={'spinner white'}>&nbsp;</span>
78-
:
79-
'Login'
80-
}
81-
</button>
82-
</div>
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>
92-
</React.Fragment>
108+
<EnhancedForm
109+
{...props}
110+
addFlash={addFlash}
111+
clearFlashes={clearFlashes}
112+
/>
93113
);
94114
};

resources/scripts/components/auth/LoginFormContainer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
2+
import { Form } from 'formik';
23

34
export default ({ className, ...props }: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>) => (
4-
<form
5+
<Form
56
className={'flex items-center justify-center login-box'}
67
{...props}
78
style={{
@@ -14,5 +15,5 @@ export default ({ className, ...props }: React.DetailedHTMLProps<React.FormHTMLA
1415
<div className={'flex-1'}>
1516
{props.children}
1617
</div>
17-
</form>
18+
</Form>
1819
);

resources/scripts/components/elements/Field.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ import React from 'react';
22
import { Field, FieldProps } from 'formik';
33
import classNames from 'classnames';
44

5-
interface Props {
6-
id?: string;
7-
type: string;
5+
interface OwnProps {
86
name: string;
97
label?: string;
108
description?: string;
11-
autoFocus?: boolean;
129
validate?: (value: any) => undefined | string | Promise<any>;
1310
}
1411

15-
export default ({ id, type, name, label, description, autoFocus, validate }: Props) => (
12+
type Props = OwnProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'>;
13+
14+
export default ({ id, name, label, description, validate, className, ...props }: Props) => (
1615
<Field name={name} validate={validate}>
1716
{
1817
({ field, form: { errors, touched } }: FieldProps) => (
@@ -22,15 +21,14 @@ export default ({ id, type, name, label, description, autoFocus, validate }: Pro
2221
}
2322
<input
2423
id={id}
25-
type={type}
2624
{...field}
27-
autoFocus={autoFocus}
28-
className={classNames('input-dark', {
25+
{...props}
26+
className={classNames((className || 'input-dark'), {
2927
error: touched[field.name] && errors[field.name],
3028
})}
3129
/>
3230
{touched[field.name] && errors[field.name] ?
33-
<p className={'input-help'}>
31+
<p className={'input-help error'}>
3432
{(errors[field.name] as string).charAt(0).toUpperCase() + (errors[field.name] as string).slice(1)}
3533
</p>
3634
:

0 commit comments

Comments
 (0)