Files
Account/src/features/authentication/components/AuthenticationSteps/OtpVerifyForm.tsx

236 lines
6.6 KiB
TypeScript

import { useTranslation } from 'react-i18next';
import { Box, Button, Stack, Typography } from '@mui/material';
import { Edit2 } from 'iconsax-react';
import DigitInput from '@/components/DigitsInput';
import type { AuthMode, AuthType } from '../../types/authTypes';
import { useEffect, useState } from 'react';
import { AuthenticationCard } from '../AuthenticationCard';
import type { LoginRequest, LoginResult } from '../../types/userTypes';
import {
loginOrSignUpWithOtp,
sendEmailOtp,
sendSmsOtp,
} from '../../api/authorizationAPI';
import type { CountryCode, GUID } from '@/types/commonTypes';
import { Icon, useToast } from '@rkheftan/harmony-ui';
import { useApi } from '@/hooks/useApi';
import { generateTokenWithOtp } from '../../api/identityAPI';
import { useAuth } from '@/hooks/useAuth';
interface OtpVerifyFormProps {
value: string;
countryCode: CountryCode;
authType: AuthType;
authMode: AuthMode;
onEditValue: () => void;
onOTPVerified: (loginResult: LoginResult) => void;
authReturnUrl: string;
}
export function OtpVerifyForm({
value,
countryCode,
authType,
authMode,
onEditValue,
onOTPVerified,
authReturnUrl,
}: OtpVerifyFormProps) {
const [otpCode, setOtpCode] = useState<string>('');
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
const [isStatusSuccess, setIsStatusSuccess] = useState<boolean>();
const { t } = useTranslation('authentication');
const [resendTimer, setResendTimer] = useState<number>(120);
const [canResend, setCanResend] = useState(false);
const toast = useToast();
const { loading: smsResendLoading, execute: smsResendCall } =
useApi(sendSmsOtp);
const { loading: emailResendLoading, execute: emailResendCall } =
useApi(sendEmailOtp);
const { loading: loginSignUpLoading, execute: loginSignUpCall } =
useApi(loginOrSignUpWithOtp);
const auth = useAuth();
useEffect(() => {
let interval: NodeJS.Timeout;
if (resendTimer > 0) {
interval = setInterval(() => {
setResendTimer((prev) => prev - 1);
}, 1000);
} else {
setCanResend(true);
}
return () => clearInterval(interval);
}, [resendTimer]);
const handleResendOTPCode = async () => {
if (authType === 'phone') {
await smsResendCall({ phoneNumber: countryCode + value });
} else {
await emailResendCall({ email: value });
}
setResendTimer(120);
setCanResend(false);
};
const formatTime = (seconds: number) => {
const min = Math.floor(seconds / 60);
const sec = seconds % 60;
return `${min}:${sec.toString().padStart(2, '0')}`;
};
const handleVerifyOTP = () => {
if (!otpCode || otpCode.length < 4) {
setOtpDigitInvalid(true);
} else {
handleLoginOrSignUp();
}
};
const handleLoginOrSignUp = async () => {
setOtpDigitInvalid(false);
const loginRequest: LoginRequest = {
otpCode: otpCode,
phoneNumber: authType === 'phone' ? countryCode + value : undefined,
email: authType === 'email' ? value : undefined,
returnUrl: authReturnUrl,
};
const res = await loginSignUpCall(loginRequest);
if (!res) {
return;
}
if (res.success) {
setIsStatusSuccess(true);
const tokenRes = await generateTokenWithOtp({
email: loginRequest.email,
phonenumber: loginRequest.phoneNumber,
otp: loginRequest.otpCode,
});
auth.login({
...tokenRes.data,
});
onOTPVerified(res);
toast({
message:
authMode === 'login'
? t('verify.youHaveSuccessfullyLoggedIn')
: t('verify.youHaveSuccessfullySignedIn'),
severity: 'success',
});
} else {
setIsStatusSuccess(false);
toast({
message: res.message,
severity: 'error',
});
}
};
const otpMessageFn = (): string => {
if (authType === 'phone' && authMode === 'login') {
return t(
'verify.a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt',
);
} else if (authType === 'phone' && authMode === 'register') {
return t(
'verify.thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount',
);
} else if (authType === 'email' && authMode === 'login') {
return t(
'verify.a4digitVerificationCodeHasBeenSentToYourEmailAddressPleaseEnterIt',
);
} else if (authType === 'email' && authMode === 'register') {
return t(
'verify.thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount',
);
}
return '';
};
const otpMessage = otpMessageFn();
return (
<Stack alignItems="center">
<AuthenticationCard>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
mb: 0.5,
}}
>
<Typography variant="h5">{t('verify.verify')}</Typography>
<Button
variant="outlined"
size="large"
sx={{ textTransform: 'lowercase', width: 'auto' }}
endIcon={<Icon Component={Edit2} />}
onClick={onEditValue}
>
{authType === 'phone' ? countryCode + value : value}
</Button>
</Box>
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
{otpMessage}
</Typography>
<DigitInput
error={otpDigitInvalid || isStatusSuccess === false}
success={isStatusSuccess === true}
onChange={(value) => setOtpCode(value)}
/>
<Button onClick={handleVerifyOTP} loading={loginSignUpLoading}>
{authMode === 'register'
? t('verify.confirmAndContinue')
: t('verify.confirmAndLogin')}
</Button>
</AuthenticationCard>
<Stack
direction="row"
sx={{
justifyContent: 'center',
alignItems: 'center',
mt: 2.5,
}}
>
<Typography variant="body1">{t('verify.resendCodeIn')}</Typography>
<Typography
variant="body1"
sx={{ color: 'primary.main', marginInlineStart: 1 }}
>
{!canResend && `${formatTime(resendTimer)} ${t('verify.moreMinute')}`}
</Typography>
{canResend && (
<Button
variant="text"
loading={smsResendLoading || emailResendLoading}
sx={{ width: 'auto', p: 0 }}
onClick={canResend ? handleResendOTPCode : undefined}
>
{t('verify.resendCode')}
</Button>
)}
</Stack>
</Stack>
);
}