Add basic Formik setup and example for update password

This commit is contained in:
Dane Everitt 2019-06-22 23:25:38 -07:00
parent 403a1e79fa
commit d43b7ea5bc
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
7 changed files with 275 additions and 40 deletions

View file

@ -0,0 +1,21 @@
import http from '@/api/http';
interface Data {
current: string;
password: string;
confirmPassword: string;
}
export default ({ current, password, confirmPassword }: Data): Promise<void> => {
return new Promise((resolve, reject) => {
http.put('/account/password', {
// eslint-disable-next-line @typescript-eslint/camelcase
current_password: current,
password: password,
// eslint-disable-next-line @typescript-eslint/camelcase
password_confirmation: confirmPassword,
})
.then(() => resolve())
.catch(reject);
});
};

View file

@ -1,47 +1,81 @@
import React, { useState } from 'react';
import { State, useStoreState } from 'easy-peasy';
import { ApplicationState } from '@/state/types';
import { Form, Formik, FormikActions } from 'formik';
import Field from '@/components/elements/Field';
import * as Yup from 'yup';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
interface Values {
current: string;
password: string;
confirmPassword: string;
}
const schema = Yup.object().shape({
current: Yup.string().min(1).required('You must provide your current password.'),
password: Yup.string().min(8).required(),
confirmPassword: Yup.string().test('password', 'Password confirmation does not match the password you entered.', function (value) {
return value === this.parent.password;
}),
});
export default () => {
const [ isLoading, setIsLoading ] = useState(false);
const user = useStoreState((state: State<ApplicationState>) => state.user.data);
if (!user) {
return null;
}
const submit = (values: Values, { setSubmitting }: FormikActions<Values>) => {
setTimeout(() => setSubmitting(false), 1500);
};
return (
<form className={'m-0'}>
<label htmlFor={'current_password'} className={'input-dark-label'}>Current Password</label>
<input
id={'current_password'}
type={'password'}
className={'input-dark'}
/>
<div className={'mt-6'}>
<label htmlFor={'new_password'} className={'input-dark-label'}>New Password</label>
<input
id={'new_password'}
type={'password'}
className={'input-dark'}
/>
<p className={'input-help'}>
Your new password must be at least 8 characters in length.
</p>
</div>
<div className={'mt-6'}>
<label htmlFor={'new_password_confirm'} className={'input-dark-label'}>Confirm New Password</label>
<input
id={'new_password_confirm'}
type={'password'}
className={'input-dark'}
/>
</div>
<div className={'mt-6'}>
<button className={'btn btn-primary btn-sm'} disabled={true}>
Update Password
</button>
</div>
</form>
<React.Fragment>
<Formik
onSubmit={submit}
validationSchema={schema}
initialValues={{ current: '', password: '', confirmPassword: '' }}
>
{
({ isSubmitting, isValid }) => (
<React.Fragment>
<SpinnerOverlay large={true} visible={isSubmitting}/>
<Form className={'m-0'}>
<Field
id={'current_password'}
type={'password'}
name={'current'}
label={'Current Password'}
/>
<div className={'mt-6'}>
<Field
id={'new_password'}
type={'password'}
name={'password'}
label={'New Password'}
description={'Your new password should be at least 8 characters in length and unique to this website.'}
/>
</div>
<div className={'mt-6'}>
<Field
id={'confirm_password'}
type={'password'}
name={'confirmPassword'}
label={'Confirm New Password'}
/>
</div>
<div className={'mt-6'}>
<button className={'btn btn-primary btn-sm'} disabled={isSubmitting || !isValid}>
Update Password
</button>
</div>
</Form>
</React.Fragment>
)
}
</Formik>
</React.Fragment>
);
};

View file

@ -8,8 +8,8 @@ type Props = Readonly<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElemen
export default ({ title, borderColor, children, ...props }: Props) => (
<div {...props}>
{title && <h2 className={'text-neutral-300 mb-2 px-4'}>{title}</h2>}
<div className={classNames('bg-neutral-700 p-4 rounded shadow-lg', borderColor, {
{title && <h2 className={'text-neutral-300 mb-4 px-4'}>{title}</h2>}
<div className={classNames('bg-neutral-700 p-4 rounded shadow-lg relative', borderColor, {
'border-t-4': !!borderColor,
})}>
{children}

View file

@ -0,0 +1,41 @@
import React from 'react';
import { Field, FieldProps } from 'formik';
import classNames from 'classnames';
interface Props {
id?: string;
type: string;
name: string;
label?: string;
description?: string;
validate?: (value: any) => undefined | string | Promise<any>;
}
export default ({ id, type, name, label, description, validate }: Props) => (
<Field name={name} validate={validate}>
{
({ field, form: { errors, touched } }: FieldProps) => (
<React.Fragment>
{label &&
<label htmlFor={id} className={'input-dark-label'}>{label}</label>
}
<input
id={id}
type={type}
{...field}
className={classNames('input-dark', {
error: touched[field.name] && errors[field.name],
})}
/>
{touched[field.name] && errors[field.name] ?
<p className={'input-help'}>
{(errors[field.name] as string).charAt(0).toUpperCase() + (errors[field.name] as string).slice(1)}
</p>
:
description ? <p className={'input-help'}>{description}</p> : null
}
</React.Fragment>
)
}
</Field>
);

View file

@ -0,0 +1,14 @@
import React from 'react';
import classNames from 'classnames';
import { CSSTransition } from 'react-transition-group';
export default ({ large, visible }: { visible: boolean; large?: boolean }) => (
<CSSTransition timeout={150} classNames={'fade'} in={visible} unmountOnExit={true}>
<div
className={classNames('absolute pin-t pin-l flex items-center justify-center w-full h-full rounded')}
style={{ background: 'rgba(0, 0, 0, 0.45)' }}
>
<div className={classNames('spinner-circle spinner-white', { 'spinner-lg': large })}></div>
</div>
</CSSTransition>
);