Lance Pioch a0f5ef13d6
Show login failure message (#781)
* Show login failure message

* Update resources/scripts/components/auth/LoginContainer.tsx

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>

---------

Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-12-07 22:03:56 -05:00

135 lines
5.0 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import login from '@/api/auth/login';
import LoginFormContainer from '@/components/auth/LoginFormContainer';
import { useStoreState } from 'easy-peasy';
import { Formik, FormikHelpers } from 'formik';
import { object, string } from 'yup';
import { useTranslation } from 'react-i18next';
import Field from '@/components/elements/Field';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import Turnstile, { useTurnstile } from 'react-turnstile';
import useFlash from '@/plugins/useFlash';
interface Values {
username: string;
password: string;
}
const LoginContainer = ({ history }: RouteComponentProps) => {
const { t } = useTranslation(['auth', 'strings']);
const turnstile = useTurnstile();
const [token, setToken] = useState('');
const { clearFlashes, clearAndAddHttpError, addError } = useFlash();
const { enabled: recaptchaEnabled, siteKey } = useStoreState((state) => state.settings.data!.recaptcha);
useEffect(() => {
clearFlashes();
}, []);
const onSubmit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes();
if (recaptchaEnabled && !token) {
addError({ message: 'No captcha token found.' });
setSubmitting(false);
return;
}
login({ ...values, recaptchaData: token })
.then((response) => {
if (response.complete) {
// @ts-expect-error this is valid
window.location = response.intended || '/';
return;
}
history.replace('/auth/login/checkpoint', { token: response.confirmationToken });
})
.catch((error) => {
setSubmitting(false);
addError({ message: 'Invalid login, please try again.' });
setToken('');
if (turnstile) {
turnstile.reset();
}
setSubmitting(false);
clearAndAddHttpError({ error });
});
};
return (
<Formik
onSubmit={onSubmit}
initialValues={{ username: '', password: '' }}
validationSchema={object().shape({
username: string().required(t('login.required.username_or_email')),
password: string().required(t('login.required.password')),
})}
>
{({ isSubmitting, setSubmitting }) => (
<LoginFormContainer title={t('login.title')} css={tw`w-full flex`}>
<Field
light
type={'text'}
label={t('user_identifier', { ns: 'strings' })}
name={'username'}
disabled={isSubmitting}
/>
<div css={tw`mt-6`}>
<Field
light
type={'password'}
label={t('password', { ns: 'strings' })}
name={'password'}
disabled={isSubmitting}
/>
</div>
{recaptchaEnabled && (
<Turnstile
sitekey={siteKey || '_invalid_key'}
className='mt-6 flex justify-center'
retry='never'
onVerify={(token) => {
setToken(token);
}}
onError={(error) => {
console.error('Error verifying captcha: ' + error);
addError({ message: 'Error verifying captcha: ' + error });
setSubmitting(false);
setToken('');
}}
onExpire={() => {
setSubmitting(false);
setToken('');
}}
/>
)}
<div css={tw`mt-6`}>
<Button type={'submit'} size={'xlarge'} isLoading={isSubmitting} disabled={isSubmitting}>
{t('login.button')}
</Button>
</div>
<div css={tw`mt-6 text-center`}>
<Link
to={'/auth/password'}
css={tw`text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600`}
>
{t('forgot_password.label')}
</Link>
</div>
</LoginFormContainer>
)}
</Formik>
);
};
export default LoginContainer;