feat: OTP verify status and status messages added
This commit is contained in:
@@ -11,7 +11,13 @@
|
|||||||
"verify": {
|
"verify": {
|
||||||
"verify": "Verify",
|
"verify": "Verify",
|
||||||
"a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt": "A 4-digit verification code has been sent to your mobile number. Please enter it.",
|
"a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt": "A 4-digit verification code has been sent to your mobile number. Please enter it.",
|
||||||
"thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount": "There is no account with this number. A 4-digit verification code has been sent to this number to create a new account."
|
"thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount": "There is no account with this number. A 4-digit verification code has been sent to this number to create a new account.",
|
||||||
|
"a4digitVerificationCodeHasBeenSentToYourEmailAddressPleaseEnterIt": "A 4-digit verification code has been sent to your email address. Please enter it.",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,6 +14,11 @@
|
|||||||
"a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt": "کد تایید ۴ رقمی به شماره موبایل شما ارسال شد. لطفا آن را وارد کنید.",
|
"a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt": "کد تایید ۴ رقمی به شماره موبایل شما ارسال شد. لطفا آن را وارد کنید.",
|
||||||
"confirmAndLogin": "تایید و ورود",
|
"confirmAndLogin": "تایید و ورود",
|
||||||
"confirmAndContinue": "تایید و ادامه",
|
"confirmAndContinue": "تایید و ادامه",
|
||||||
"thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount": "حساب کاربری با این شماره وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این شماره ارسال گردید."
|
"thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount": "حساب کاربری با این شماره وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این شماره ارسال گردید.",
|
||||||
|
"a4digitVerificationCodeHasBeenSentToYourEmailAddressPleaseEnterIt": "کد تایید ۴ رقمی به شماره ایمیل شما ارسال شد. لطفا آن را وارد کنید.",
|
||||||
|
"thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount": "حساب کاربری با این ایمیل وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این ایمیل ارسال گردید.",
|
||||||
|
"theVerificationCodeIsIncorrect": "کد تایید اشتباه می باشد",
|
||||||
|
"youHaveSuccessfullyLoggedIn": "با موفقیت وارد شدید",
|
||||||
|
"youHaveSuccessfullySignedIn": "ثبت نام با موفقیت انجام شد"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/components/Toast.tsx
Normal file
23
src/components/Toast.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Alert, Snackbar, type AlertColor } from '@mui/material';
|
||||||
|
import React, { type PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
export interface ToastProps extends PropsWithChildren {
|
||||||
|
color: AlertColor | undefined;
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Toast = ({ color, open, onClose, children }: ToastProps) => {
|
||||||
|
return (
|
||||||
|
<Snackbar sx={{ minWidth: '396px' }} open={open} onClose={onClose}>
|
||||||
|
<Alert
|
||||||
|
onClose={onClose}
|
||||||
|
severity={color}
|
||||||
|
variant="filled"
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,10 +9,16 @@ import React, {
|
|||||||
import { TextField, Stack } from '@mui/material';
|
import { TextField, Stack } from '@mui/material';
|
||||||
|
|
||||||
interface DigitInputProps {
|
interface DigitInputProps {
|
||||||
|
error: boolean;
|
||||||
|
success: boolean;
|
||||||
onChange: Dispatch<SetStateAction<string[]>>;
|
onChange: Dispatch<SetStateAction<string[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DigitInput: React.FC<DigitInputProps> = ({ onChange }) => {
|
const DigitInput: React.FC<DigitInputProps> = ({
|
||||||
|
onChange,
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
}) => {
|
||||||
const [code, setCode] = useState<string[]>(['', '', '', '']);
|
const [code, setCode] = useState<string[]>(['', '', '', '']);
|
||||||
const inputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
const inputRefs = useRef<Array<HTMLInputElement | null>>([]);
|
||||||
|
|
||||||
@@ -74,6 +80,8 @@ const DigitInput: React.FC<DigitInputProps> = ({ onChange }) => {
|
|||||||
>
|
>
|
||||||
{code.map((digit, index) => (
|
{code.map((digit, index) => (
|
||||||
<TextField
|
<TextField
|
||||||
|
error={error}
|
||||||
|
color={success ? 'success' : 'primary'}
|
||||||
key={index}
|
key={index}
|
||||||
inputRef={(el) => (inputRefs.current[index] = el)}
|
inputRef={(el) => (inputRefs.current[index] = el)}
|
||||||
value={digit}
|
value={digit}
|
||||||
@@ -85,6 +93,11 @@ const DigitInput: React.FC<DigitInputProps> = ({ onChange }) => {
|
|||||||
maxLength: 1,
|
maxLength: 1,
|
||||||
sx: {
|
sx: {
|
||||||
height: '72px',
|
height: '72px',
|
||||||
|
color: error
|
||||||
|
? 'error.main'
|
||||||
|
: success
|
||||||
|
? 'success.main'
|
||||||
|
: 'text.primary',
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|||||||
@@ -2,21 +2,31 @@ import React, { useState, type JSX } from 'react';
|
|||||||
import { LoginRegisterForm } from './LoginRegiserForm';
|
import { LoginRegisterForm } from './LoginRegiserForm';
|
||||||
import type { AuthMode, AuthType } from '../types/auth-types';
|
import type { AuthMode, AuthType } from '../types/auth-types';
|
||||||
import { OtpVerifyForm } from './OtpVerifyForm';
|
import { OtpVerifyForm } from './OtpVerifyForm';
|
||||||
|
import { isNumeric } from '@/utils/regexes/isNumeric';
|
||||||
|
|
||||||
export const AuthenticationContainer = (): JSX.Element => {
|
export const AuthenticationContainer = (): JSX.Element => {
|
||||||
const [authMode, setAuthMode] = useState<AuthMode>('register');
|
const [authMode, setAuthMode] = useState<AuthMode>('login');
|
||||||
const [authType, setAuthType] = useState<AuthType>('phone');
|
const [authType, setAuthType] = useState<AuthType>('phone');
|
||||||
const [currentStep, setCurrentStep] = useState<
|
const [currentStep, setCurrentStep] = useState<
|
||||||
'emailOrPassword' | 'verify' | 'enterPassword'
|
'emailOrPassword' | 'verify' | 'enterPassword' | 'addPhoneNumber'
|
||||||
>('verify');
|
>('emailOrPassword');
|
||||||
const [loginRegisterValue, setLoginRegisterValue] =
|
const [loginRegisterValue, setLoginRegisterValue] = useState<string>('');
|
||||||
useState<string>('9152814093');
|
|
||||||
|
|
||||||
const handleLoginRegister = (value: string) => {
|
const handleLoginRegister = (value: string) => {
|
||||||
setLoginRegisterValue(value);
|
setLoginRegisterValue(value);
|
||||||
|
setAuthType(isNumeric(value) ? 'phone' : 'email');
|
||||||
setCurrentStep('verify');
|
setCurrentStep('verify');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOTPVerfied = (otpCode: string) => {
|
||||||
|
console.log(otpCode);
|
||||||
|
|
||||||
|
if (authMode === 'register' && authType === 'email') {
|
||||||
|
setAuthType('phone');
|
||||||
|
setCurrentStep('addPhoneNumber');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditValue = () => {
|
const handleEditValue = () => {
|
||||||
setCurrentStep('emailOrPassword');
|
setCurrentStep('emailOrPassword');
|
||||||
};
|
};
|
||||||
@@ -35,6 +45,7 @@ export const AuthenticationContainer = (): JSX.Element => {
|
|||||||
|
|
||||||
{currentStep === 'verify' && (
|
{currentStep === 'verify' && (
|
||||||
<OtpVerifyForm
|
<OtpVerifyForm
|
||||||
|
onOTPVerified={handleOTPVerfied}
|
||||||
onEditValue={handleEditValue}
|
onEditValue={handleEditValue}
|
||||||
authMode={authMode}
|
authMode={authMode}
|
||||||
authType={authType}
|
authType={authType}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box, Button, Typography } from '@mui/material';
|
import { Alert, Box, Button, Snackbar, Typography } from '@mui/material';
|
||||||
import { Edit2 } from 'iconsax-reactjs';
|
import { Edit2 } from 'iconsax-reactjs';
|
||||||
import DigitInput from '@/components/components/DigitsInput';
|
import DigitInput from '@/components/components/DigitsInput';
|
||||||
import type { AuthMode, AuthType } from '../types/auth-types';
|
import type { AuthMode, AuthType } from '../types/auth-types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Toast } from '@/components/Toast';
|
||||||
|
|
||||||
interface OtpVerifyFormProps {
|
interface OtpVerifyFormProps {
|
||||||
value: string;
|
value: string;
|
||||||
authType: AuthType;
|
authType: AuthType;
|
||||||
authMode: AuthMode;
|
authMode: AuthMode;
|
||||||
onEditValue: () => void;
|
onEditValue: () => void;
|
||||||
|
onOTPVerified: (otpCode: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OtpVerifyForm({
|
export function OtpVerifyForm({
|
||||||
@@ -16,9 +19,38 @@ export function OtpVerifyForm({
|
|||||||
authType,
|
authType,
|
||||||
authMode,
|
authMode,
|
||||||
onEditValue,
|
onEditValue,
|
||||||
|
onOTPVerified,
|
||||||
}: OtpVerifyFormProps) {
|
}: OtpVerifyFormProps) {
|
||||||
|
const [otpCode, setOtpCode] = useState<string>('');
|
||||||
|
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||||
|
const [verifyStatus, setVerifyStatus] = useState<
|
||||||
|
'loading' | 'success' | 'failed'
|
||||||
|
>();
|
||||||
|
const [verifyAlertOpen, setVerifyAlertOpen] = useState<boolean>(false);
|
||||||
const { t } = useTranslation('authentication');
|
const { t } = useTranslation('authentication');
|
||||||
|
|
||||||
|
const handleDigitInputChange = (value: string[]) => {
|
||||||
|
const formattedValue = value.filter((char) => char !== '').join('');
|
||||||
|
|
||||||
|
setOtpCode(formattedValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVerifyOTP = () => {
|
||||||
|
if (!otpCode || otpCode.length < 4) {
|
||||||
|
setOtpDigitInvalid(true);
|
||||||
|
} else {
|
||||||
|
setOtpDigitInvalid(false);
|
||||||
|
setVerifyStatus('loading');
|
||||||
|
// Change setTimeout to api call
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setVerifyAlertOpen(true);
|
||||||
|
setVerifyStatus('success');
|
||||||
|
onOTPVerified(otpCode);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const otpMessage = (): string => {
|
const otpMessage = (): string => {
|
||||||
if (authType === 'phone' && authMode === 'login') {
|
if (authType === 'phone' && authMode === 'login') {
|
||||||
return t(
|
return t(
|
||||||
@@ -28,6 +60,26 @@ export function OtpVerifyForm({
|
|||||||
return t(
|
return t(
|
||||||
'verify.thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount',
|
'verify.thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount',
|
||||||
);
|
);
|
||||||
|
} else if (authType === 'email' && authMode === 'login') {
|
||||||
|
return t(
|
||||||
|
'verify.a4digitVerificationCodeHasBeenSentToYourEmailAddressPleaseEnterIt',
|
||||||
|
);
|
||||||
|
} else if (authType === 'email' && authMode === 'register') {
|
||||||
|
return t(
|
||||||
|
'verify.thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyAlertMessage = (): string => {
|
||||||
|
if (verifyStatus === 'failed') {
|
||||||
|
return t('verify.theVerificationCodeIsIncorrect');
|
||||||
|
} else if (verifyStatus === 'success' && authMode === 'register') {
|
||||||
|
return t('verify.youHaveSuccessfullySignedIn');
|
||||||
|
} else if (verifyStatus === 'success' && authMode === 'login') {
|
||||||
|
return t('verify.youHaveSuccessfullyLoggedIn');
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
@@ -35,6 +87,14 @@ export function OtpVerifyForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: '100%' }}>
|
||||||
|
<Toast
|
||||||
|
open={verifyAlertOpen}
|
||||||
|
onClose={() => setVerifyAlertOpen(false)}
|
||||||
|
color={verifyStatus === 'failed' ? 'error' : 'success'}
|
||||||
|
>
|
||||||
|
{verifyAlertMessage()}
|
||||||
|
</Toast>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -62,8 +122,13 @@ export function OtpVerifyForm({
|
|||||||
{otpMessage()}
|
{otpMessage()}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<DigitInput onChange={(value) => console.log(value)} />
|
<DigitInput
|
||||||
<Button>
|
error={otpDigitInvalid || verifyStatus === 'failed'}
|
||||||
|
success={verifyStatus === 'success'}
|
||||||
|
onChange={(value) => handleDigitInputChange(value as string[])}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={handleVerifyOTP} loading={verifyStatus === 'loading'}>
|
||||||
{authMode === 'register'
|
{authMode === 'register'
|
||||||
? t('verify.confirmAndContinue')
|
? t('verify.confirmAndContinue')
|
||||||
: t('verify.confirmAndLogin')}
|
: t('verify.confirmAndLogin')}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export const PALETTE: Palette = {
|
|||||||
primary: {
|
primary: {
|
||||||
light: {
|
light: {
|
||||||
main: blue.A400,
|
main: blue.A400,
|
||||||
|
dark: blue.A700,
|
||||||
|
light: blue.A100,
|
||||||
contrastText: '#FFFFFF',
|
contrastText: '#FFFFFF',
|
||||||
},
|
},
|
||||||
// TODO
|
// TODO
|
||||||
@@ -49,9 +51,9 @@ export const PALETTE: Palette = {
|
|||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
light: {
|
light: {
|
||||||
main: '#E53935',
|
main: '#d32f2f',
|
||||||
dark: '#C62828',
|
dark: '#c62828',
|
||||||
light: '#EF5350',
|
light: '#ef5350',
|
||||||
contrastText: '#FFFFFF',
|
contrastText: '#FFFFFF',
|
||||||
},
|
},
|
||||||
// TODO
|
// TODO
|
||||||
@@ -94,9 +96,9 @@ export const PALETTE: Palette = {
|
|||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
light: {
|
light: {
|
||||||
main: '#43A047',
|
main: '#2e7d32',
|
||||||
dark: '#1B5E20',
|
dark: '#1b5e20',
|
||||||
light: '#81C784',
|
light: '#4caf50',
|
||||||
contrastText: '#FFFFFF',
|
contrastText: '#FFFFFF',
|
||||||
},
|
},
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
Reference in New Issue
Block a user