105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|
import { Redirect } from 'react-router-dom';
|
|
|
|
import { otpVerify, verifyCredentials, switchAccount } from 'pl-fe/actions/auth';
|
|
import { BigCard } from 'pl-fe/components/big-card';
|
|
import Button from 'pl-fe/components/ui/button';
|
|
import Card, { CardBody, CardHeader, CardTitle } from 'pl-fe/components/ui/card';
|
|
import Form from 'pl-fe/components/ui/form';
|
|
import FormActions from 'pl-fe/components/ui/form-actions';
|
|
import FormGroup from 'pl-fe/components/ui/form-group';
|
|
import Input from 'pl-fe/components/ui/input';
|
|
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
|
|
|
const messages = defineMessages({
|
|
otpCodeHint: { id: 'login.fields.otp_code_hint', defaultMessage: 'Enter the two-factor code generated by your phone app or use one of your recovery codes' },
|
|
otpCodeLabel: { id: 'login.fields.otp_code_label', defaultMessage: 'Two-factor code:' },
|
|
otpLoginFail: { id: 'login.otp_log_in.fail', defaultMessage: 'Invalid code, please try again.' },
|
|
});
|
|
|
|
interface IOtpAuthForm {
|
|
mfa_token: string;
|
|
small?: boolean;
|
|
}
|
|
|
|
const OtpAuthForm: React.FC<IOtpAuthForm> = ({ mfa_token, small }) => {
|
|
const dispatch = useAppDispatch();
|
|
const intl = useIntl();
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [shouldRedirect, setShouldRedirect] = useState(false);
|
|
const [codeError, setCodeError] = useState<string | boolean>('');
|
|
|
|
const getFormData = (form: any) => Object.fromEntries(
|
|
Array.from(form).map((i: any) => [i.name, i.value]),
|
|
);
|
|
|
|
const handleSubmit = (event: React.FormEvent<Element>) => {
|
|
const { code } = getFormData(event.target);
|
|
dispatch(otpVerify(code, mfa_token)).then(({ access_token }) => {
|
|
setCodeError(false);
|
|
return dispatch(verifyCredentials(access_token as string));
|
|
}).then((account: Record<string, any>) => {
|
|
setShouldRedirect(true);
|
|
return dispatch(switchAccount(account.id));
|
|
}).catch(() => {
|
|
setIsLoading(false);
|
|
setCodeError(true);
|
|
});
|
|
setIsLoading(true);
|
|
event.preventDefault();
|
|
};
|
|
|
|
if (shouldRedirect) return <Redirect to='/' />;
|
|
|
|
const form = (
|
|
<Form onSubmit={handleSubmit}>
|
|
<FormGroup
|
|
labelText={intl.formatMessage(messages.otpCodeLabel)}
|
|
hintText={intl.formatMessage(messages.otpCodeHint)}
|
|
errors={codeError ? [intl.formatMessage(messages.otpLoginFail)] : []}
|
|
>
|
|
<Input
|
|
name='code'
|
|
type='text'
|
|
autoComplete='one-time-code'
|
|
autoFocus
|
|
required
|
|
/>
|
|
</FormGroup>
|
|
|
|
<FormActions>
|
|
<Button
|
|
theme='primary'
|
|
type='submit'
|
|
disabled={isLoading}
|
|
>
|
|
<FormattedMessage id='login.sign_in' defaultMessage='Sign in' />
|
|
</Button>
|
|
</FormActions>
|
|
</Form>
|
|
);
|
|
|
|
if (small) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle title={<FormattedMessage id='login.otp_log_in' defaultMessage='OTP Login' />} />
|
|
</CardHeader>
|
|
<CardBody>
|
|
{form}
|
|
</CardBody>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<BigCard title={<FormattedMessage id='login.otp_log_in' defaultMessage='OTP Login' />}>
|
|
{form}
|
|
</BigCard>
|
|
);
|
|
};
|
|
|
|
export { OtpAuthForm as default };
|