feat: otp resend timer and logic added
This commit is contained in:
@@ -16,7 +16,10 @@
|
||||
"thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount": "There is no account with this email address. A 4-digit verification code has been sent to this email address to create a new account.",
|
||||
"theVerificationCodeIsIncorrect": "The verification code is incorrect.",
|
||||
"youHaveSuccessfullyLoggedIn": "You have successfully logged in",
|
||||
"youHaveSuccessfullySignedIn": "You have successfully signed in"
|
||||
"youHaveSuccessfullySignedIn": "You have successfully signed in",
|
||||
"resendCodeIn": "Resend code in",
|
||||
"moreMinute": "minute",
|
||||
"resendCode": "Resend code"
|
||||
},
|
||||
"completeSignUp": {
|
||||
"completeSignUp": "Complete Sign Up",
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
"thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount": "حساب کاربری با این ایمیل وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این ایمیل ارسال گردید.",
|
||||
"theVerificationCodeIsIncorrect": "کد تایید اشتباه می باشد",
|
||||
"youHaveSuccessfullyLoggedIn": "با موفقیت وارد شدید",
|
||||
"youHaveSuccessfullySignedIn": "ثبت نام با موفقیت انجام شد"
|
||||
"youHaveSuccessfullySignedIn": "ثبت نام با موفقیت انجام شد",
|
||||
"resendCodeIn": "ارسال مجدد کد تا",
|
||||
"moreMinute": "دقیقه دیگر",
|
||||
"resendCode": "ارسال مجدد"
|
||||
},
|
||||
"completeSignUp": {
|
||||
"completeSignUp": "تکمیل ثبت نام",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Paper } from '@mui/material';
|
||||
import React, { type PropsWithChildren } from 'react';
|
||||
|
||||
// Beacuse in the otp verify there is a element outside of the authentication card
|
||||
export const AuthenticationCard = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
bor: 2,
|
||||
p: 6,
|
||||
width: '34.5rem',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||
import { Box, Button, Paper, TextField, Typography } from '@mui/material';
|
||||
import parsePhoneNumberFromString from 'libphonenumber-js';
|
||||
import React, { useRef, useState, type Dispatch } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CountryCodeSelector } from './CountryCodeSelector';
|
||||
import { AuthenticationCard } from './AuthenticationCard';
|
||||
|
||||
export interface CompleteSignUpProps {
|
||||
email: string;
|
||||
@@ -59,7 +60,7 @@ export const CompleteSignUp = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<AuthenticationCard>
|
||||
<Typography variant="h5" sx={{ mb: 0.5 }}>
|
||||
{t('completeSignUp.completeSignUp')}
|
||||
</Typography>
|
||||
@@ -101,6 +102,6 @@ export const CompleteSignUp = ({
|
||||
<Button onClick={handleCompleteSignUp}>
|
||||
{t('verify.confirmAndContinue')}
|
||||
</Button>
|
||||
</Box>
|
||||
</AuthenticationCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Box, Button, Stack, TextField, Typography } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Paper,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useRef, useState, type Dispatch } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CountryCodeSelector } from './CountryCodeSelector';
|
||||
@@ -7,6 +14,7 @@ import { isNumeric } from '@/utils/regexes/isNumeric';
|
||||
import type { AuthMode, AuthType } from '../types/auth-types';
|
||||
import { isEmail } from '@/utils/regexes/isEmail';
|
||||
import parsePhoneNumberFromString from 'libphonenumber-js';
|
||||
import { AuthenticationCard } from './AuthenticationCard';
|
||||
|
||||
export interface LoginRegisterFormProps {
|
||||
loginRegisterValue: string;
|
||||
@@ -95,7 +103,7 @@ export function LoginRegisterForm({
|
||||
const showAdornment = authType === 'phone' && loginRegisterValue.length > 0;
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<AuthenticationCard>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="h5">{t('loginForm.title')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
@@ -136,6 +144,6 @@ export function LoginRegisterForm({
|
||||
{t('loginForm.loginWithGoogle')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</AuthenticationCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, Box, Button, Snackbar, Typography } from '@mui/material';
|
||||
import { Alert, Box, Button, Snackbar, Stack, Typography } from '@mui/material';
|
||||
import { Edit2 } from 'iconsax-reactjs';
|
||||
import DigitInput from '@/components/components/DigitsInput';
|
||||
import type { AuthMode, AuthType } from '../types/auth-types';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { AuthenticationCard } from './AuthenticationCard';
|
||||
|
||||
interface OtpVerifyFormProps {
|
||||
value: string;
|
||||
@@ -28,6 +29,42 @@ export function OtpVerifyForm({
|
||||
useState<boolean>(false);
|
||||
const [verifyAlertOpen, setVerifyAlertOpen] = useState<boolean>(false);
|
||||
const { t } = useTranslation('authentication');
|
||||
const [resendTimer, setResendTimer] = useState<number>(120);
|
||||
const [canResend, setCanResend] = useState(false);
|
||||
const [resendLoading, setResendLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
if (resendTimer > 0) {
|
||||
interval = setInterval(() => {
|
||||
setResendTimer((prev) => prev - 1);
|
||||
}, 1000);
|
||||
} else {
|
||||
setCanResend(true);
|
||||
}
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [resendTimer]);
|
||||
|
||||
const handleResendOTPCode = () => {
|
||||
setResendLoading(true);
|
||||
|
||||
// TODO: Call API here instead of settimeout
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('resended');
|
||||
|
||||
setResendTimer(120);
|
||||
setCanResend(false);
|
||||
setResendLoading(false);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
const min = Math.floor(seconds / 60);
|
||||
const sec = seconds % 60;
|
||||
return `${min}:${sec.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handleDigitInputChange = (value: string[]) => {
|
||||
const formattedValue = value.filter((char) => char !== '').join('');
|
||||
@@ -87,53 +124,76 @@ export function OtpVerifyForm({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Toast
|
||||
open={verifyAlertOpen}
|
||||
onClose={() => setVerifyAlertOpen(false)}
|
||||
color={verifyStatus === 'failed' ? 'error' : 'success'}
|
||||
>
|
||||
{verifyAlertMessage()}
|
||||
</Toast>
|
||||
<Stack alignItems="center">
|
||||
<AuthenticationCard>
|
||||
<Toast
|
||||
open={verifyAlertOpen}
|
||||
onClose={() => setVerifyAlertOpen(false)}
|
||||
color={verifyStatus === 'failed' ? 'error' : 'success'}
|
||||
>
|
||||
{verifyAlertMessage()}
|
||||
</Toast>
|
||||
|
||||
<Box
|
||||
<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={<Edit2 />}
|
||||
onClick={onEditValue}
|
||||
>
|
||||
{value}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||
{otpMessage()}
|
||||
</Typography>
|
||||
|
||||
<DigitInput
|
||||
error={otpDigitInvalid || verifyStatus === 'failed'}
|
||||
success={verifyStatus === 'success'}
|
||||
onChange={(value) => handleDigitInputChange(value as string[])}
|
||||
/>
|
||||
|
||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
||||
{authMode === 'register'
|
||||
? t('verify.confirmAndContinue')
|
||||
: t('verify.confirmAndLogin')}
|
||||
</Button>
|
||||
</AuthenticationCard>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 4,
|
||||
mb: 0.5,
|
||||
mt: 1.5,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5">{t('verify.verify')}</Typography>
|
||||
<Typography variant="body1">{t('verify.resendCodeIn')}</Typography>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="large"
|
||||
sx={{ textTransform: 'lowercase', width: 'auto' }}
|
||||
endIcon={<Edit2 />}
|
||||
onClick={onEditValue}
|
||||
variant="text"
|
||||
loading={resendLoading}
|
||||
sx={{ width: 'auto' }}
|
||||
onClick={canResend ? handleResendOTPCode : undefined}
|
||||
>
|
||||
{value}
|
||||
{canResend && t('verify.resendCode')}
|
||||
{!canResend && `${formatTime(resendTimer)} ${t('verify.moreMinute')}`}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||
{otpMessage()}
|
||||
</Typography>
|
||||
|
||||
<DigitInput
|
||||
error={otpDigitInvalid || verifyStatus === 'failed'}
|
||||
success={verifyStatus === 'success'}
|
||||
onChange={(value) => handleDigitInputChange(value as string[])}
|
||||
/>
|
||||
|
||||
<Button onClick={handleVerifyOTP} loading={verifyStatusLoading}>
|
||||
{authMode === 'register'
|
||||
? t('verify.confirmAndContinue')
|
||||
: t('verify.confirmAndLogin')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import { Edit2 } from 'iconsax-reactjs';
|
||||
import DigitInput from '@/components/components/DigitsInput';
|
||||
|
||||
interface SmsOtpProps {
|
||||
value: string;
|
||||
type: 'phone' | 'email';
|
||||
}
|
||||
|
||||
export function SmsOtpForm({ value, type }: SmsOtpProps) {
|
||||
const { t } = useTranslation('authentication');
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 4,
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5">اعتبارسنجی</Typography>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="large"
|
||||
sx={{ direction: 'auto', textTransform: 'lowercase' }}
|
||||
endIcon={<Edit2 />}
|
||||
>
|
||||
{value}
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
کد تایید ۴ رقمی به شماره موبایل شما ارسال شد. لطفا آن را وارد کنید.
|
||||
</Typography>
|
||||
<DigitInput onChange={(value) => console.log(value)} />
|
||||
<Button>{t('smsOtp.confirmAndLogin')}</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { FlexBox } from '@/components/components/common/FlexBox';
|
||||
import Logo from '@/components/Logo';
|
||||
import { Paper } from '@mui/material';
|
||||
import { SmsOtpForm } from '../components/SmsOtpForm';
|
||||
import { useState } from 'react';
|
||||
import { AuthenticationContainer } from '../components/AuthenticationContainer';
|
||||
|
||||
@@ -17,16 +16,7 @@ export function AuthenticationPage() {
|
||||
}}
|
||||
>
|
||||
<Logo />
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 'theme.xl',
|
||||
p: 6,
|
||||
width: '34.5rem',
|
||||
}}
|
||||
>
|
||||
<AuthenticationContainer />
|
||||
</Paper>
|
||||
<AuthenticationContainer />
|
||||
</FlexBox>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user