Files
ncd-fe/packages/pl-fe/src/features/auth-login/components/otp-auth-form.tsx
nicole mikołajczyk d063d09aa7 pl-fe: otp code autocomplete
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2025-10-21 15:33:17 +02:00

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 };