236 lines
6.6 KiB
TypeScript
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>
|
|
);
|
|
}
|