From fc5d441712bf2b23e68c3e67b80b748bc1334634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=85=D9=87=D8=B1=D8=B2=D8=A7=D8=AF=20=D9=82=D8=AF=D8=B1?= =?UTF-8?q?=D8=AA=DB=8C?= Date: Sun, 27 Jul 2025 15:05:46 +0330 Subject: [PATCH] feat: OTP verify status and status messages added --- public/locales/en/authentication.json | 8 ++- public/locales/fa/authentication.json | 7 +- src/components/Toast.tsx | 23 ++++++ src/components/components/DigitsInput.tsx | 15 +++- .../components/AuthenticationContainer.tsx | 21 ++++-- .../components/OtpVerifyForm.tsx | 71 ++++++++++++++++++- src/theme/colors.ts | 14 ++-- 7 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 src/components/Toast.tsx diff --git a/public/locales/en/authentication.json b/public/locales/en/authentication.json index 8cda9bb..18287d4 100644 --- a/public/locales/en/authentication.json +++ b/public/locales/en/authentication.json @@ -11,7 +11,13 @@ "verify": { "verify": "Verify", "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" } } } + \ No newline at end of file diff --git a/public/locales/fa/authentication.json b/public/locales/fa/authentication.json index e1dbe20..72a9536 100644 --- a/public/locales/fa/authentication.json +++ b/public/locales/fa/authentication.json @@ -14,6 +14,11 @@ "a4DigitVerificationCodeHasBeenSentToYourBobileNumberPleaseEnterIt": "کد تایید ۴ رقمی به شماره موبایل شما ارسال شد. لطفا آن را وارد کنید.", "confirmAndLogin": "تایید و ورود", "confirmAndContinue": "تایید و ادامه", - "thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount": "حساب کاربری با این شماره وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این شماره ارسال گردید." + "thereIsNoAccountWithThisNumberA4DigitVerificationCodeHasBeenSentToThisNumberToCreateANewAccount": "حساب کاربری با این شماره وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این شماره ارسال گردید.", + "a4digitVerificationCodeHasBeenSentToYourEmailAddressPleaseEnterIt": "کد تایید ۴ رقمی به شماره ایمیل شما ارسال شد. لطفا آن را وارد کنید.", + "thereIsNoAccountWithThisEmailAddressA4DigitVerificationCodeHasBeenSentToThisEmailAddressToCreateANewAccount": "حساب کاربری با این ایمیل وجود ندارد. برای ساخت حساب جدید، کد تایید ۴ رقمی برای این ایمیل ارسال گردید.", + "theVerificationCodeIsIncorrect": "کد تایید اشتباه می باشد", + "youHaveSuccessfullyLoggedIn": "با موفقیت وارد شدید", + "youHaveSuccessfullySignedIn": "ثبت نام با موفقیت انجام شد" } } diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx new file mode 100644 index 0000000..947194f --- /dev/null +++ b/src/components/Toast.tsx @@ -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 ( + + + {children} + + + ); +}; diff --git a/src/components/components/DigitsInput.tsx b/src/components/components/DigitsInput.tsx index de4f054..90d8b36 100644 --- a/src/components/components/DigitsInput.tsx +++ b/src/components/components/DigitsInput.tsx @@ -9,10 +9,16 @@ import React, { import { TextField, Stack } from '@mui/material'; interface DigitInputProps { + error: boolean; + success: boolean; onChange: Dispatch>; } -const DigitInput: React.FC = ({ onChange }) => { +const DigitInput: React.FC = ({ + onChange, + error, + success, +}) => { const [code, setCode] = useState(['', '', '', '']); const inputRefs = useRef>([]); @@ -74,6 +80,8 @@ const DigitInput: React.FC = ({ onChange }) => { > {code.map((digit, index) => ( (inputRefs.current[index] = el)} value={digit} @@ -85,6 +93,11 @@ const DigitInput: React.FC = ({ onChange }) => { maxLength: 1, sx: { height: '72px', + color: error + ? 'error.main' + : success + ? 'success.main' + : 'text.primary', }, style: { textAlign: 'center', diff --git a/src/features/authentication/components/AuthenticationContainer.tsx b/src/features/authentication/components/AuthenticationContainer.tsx index 8b8ed9c..5148e47 100644 --- a/src/features/authentication/components/AuthenticationContainer.tsx +++ b/src/features/authentication/components/AuthenticationContainer.tsx @@ -2,21 +2,31 @@ import React, { useState, type JSX } from 'react'; import { LoginRegisterForm } from './LoginRegiserForm'; import type { AuthMode, AuthType } from '../types/auth-types'; import { OtpVerifyForm } from './OtpVerifyForm'; +import { isNumeric } from '@/utils/regexes/isNumeric'; export const AuthenticationContainer = (): JSX.Element => { - const [authMode, setAuthMode] = useState('register'); + const [authMode, setAuthMode] = useState('login'); const [authType, setAuthType] = useState('phone'); const [currentStep, setCurrentStep] = useState< - 'emailOrPassword' | 'verify' | 'enterPassword' - >('verify'); - const [loginRegisterValue, setLoginRegisterValue] = - useState('9152814093'); + 'emailOrPassword' | 'verify' | 'enterPassword' | 'addPhoneNumber' + >('emailOrPassword'); + const [loginRegisterValue, setLoginRegisterValue] = useState(''); const handleLoginRegister = (value: string) => { setLoginRegisterValue(value); + setAuthType(isNumeric(value) ? 'phone' : 'email'); setCurrentStep('verify'); }; + const handleOTPVerfied = (otpCode: string) => { + console.log(otpCode); + + if (authMode === 'register' && authType === 'email') { + setAuthType('phone'); + setCurrentStep('addPhoneNumber'); + } + }; + const handleEditValue = () => { setCurrentStep('emailOrPassword'); }; @@ -35,6 +45,7 @@ export const AuthenticationContainer = (): JSX.Element => { {currentStep === 'verify' && ( void; + onOTPVerified: (otpCode: string) => void; } export function OtpVerifyForm({ @@ -16,9 +19,38 @@ export function OtpVerifyForm({ authType, authMode, onEditValue, + onOTPVerified, }: OtpVerifyFormProps) { + const [otpCode, setOtpCode] = useState(''); + const [otpDigitInvalid, setOtpDigitInvalid] = useState(false); + const [verifyStatus, setVerifyStatus] = useState< + 'loading' | 'success' | 'failed' + >(); + const [verifyAlertOpen, setVerifyAlertOpen] = useState(false); 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 => { if (authType === 'phone' && authMode === 'login') { return t( @@ -28,6 +60,26 @@ export function OtpVerifyForm({ 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 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 ''; @@ -35,6 +87,14 @@ export function OtpVerifyForm({ return ( + setVerifyAlertOpen(false)} + color={verifyStatus === 'failed' ? 'error' : 'success'} + > + {verifyAlertMessage()} + + - console.log(value)} /> -