From fcb97fa188d05caa6cb63193358d98a83ead8cdc Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Thu, 13 Jun 2024 17:21:57 +0800 Subject: [PATCH 01/10] chore: implement timer from BE --- packages/account/package.json | 1 + .../Profile/PersonalDetails/verify-button.tsx | 3 + .../didnt-get-the-code-modal.spec.tsx | 21 ++- .../__test__/otp-verification.spec.tsx | 26 ++- .../__test__/resend-code-timer.spec.tsx | 174 ++++++------------ .../confirm-phone-number.tsx | 24 ++- .../didnt-get-the-code-modal.tsx | 26 ++- .../PhoneVerification/otp-verification.tsx | 53 +++--- .../PhoneVerification/resend-code-timer.tsx | 130 +++++++++---- packages/hooks/src/useSettings.ts | 3 +- packages/hooks/src/useVerifyEmail.ts | 1 + 11 files changed, 252 insertions(+), 210 deletions(-) diff --git a/packages/account/package.json b/packages/account/package.json index 79dd6d9edd70..0dad0ac6aa59 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -39,6 +39,7 @@ "@deriv/hooks": "^1.0.0", "@deriv/integration": "1.0.0", "@deriv/quill-icons": "^1.22.10", + "dayjs": "^1.11.10", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", diff --git a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx index 1b40d03c4271..d2fe9bfa83df 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/verify-button.tsx @@ -6,6 +6,7 @@ import { useHistory } from 'react-router'; import { routes } from '@deriv/shared'; import { Popover, Text } from '@deriv/components'; import { Localize } from '@deriv/translations'; +import { useVerifyEmail } from '@deriv/hooks'; export const VerifyButton = observer(() => { const [open_popover, setOpenPopover] = React.useState(false); @@ -15,8 +16,10 @@ export const VerifyButton = observer(() => { const { phone_number_verification } = account_settings; const { verified: phone_number_verified } = phone_number_verification; const history = useHistory(); + const { send } = useVerifyEmail('phone_number_verification'); const redirectToPhoneVerification = () => { + send(); history.push(routes.phone_verification); }; diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx index 182d6603814f..036a79e7c693 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/didnt-get-the-code-modal.spec.tsx @@ -1,5 +1,5 @@ -import { render, screen } from '@testing-library/react'; import React from 'react'; +import { render, screen } from '@testing-library/react'; import DidntGetTheCodeModal from '../didnt-get-the-code-modal'; import { StoreProvider, mockStore } from '@deriv/stores'; import userEvent from '@testing-library/user-event'; @@ -11,24 +11,29 @@ jest.mock('@deriv/hooks', () => ({ useRequestPhoneNumberOTP: jest.fn(() => ({ requestOnWhatsApp: jest.fn(), requestOnSMS: jest.fn(), + email_otp_error: null, })), })); describe('DidntGetTheCodeModal', () => { const mock_store = mockStore({}); const mockSetShouldShowDidntGetTheCodeModal = jest.fn(); - const mockSetStartTimer = jest.fn(); + const mockSetTimer = jest.fn(); const mockSetOtpVerification = jest.fn(); + const mockReInitializeGetSettings = jest.fn(); + const mockSetIsButtonDisabled = jest.fn(); const resend_code_text = /Resend code/; const renderComponent = (phone_verification_type: string) => { render( @@ -37,7 +42,7 @@ describe('DidntGetTheCodeModal', () => { beforeEach(() => { mockSetShouldShowDidntGetTheCodeModal.mockClear(); - mockSetStartTimer.mockClear(); + mockSetTimer.mockClear(); mockSetOtpVerification.mockClear(); }); @@ -55,21 +60,21 @@ describe('DidntGetTheCodeModal', () => { expect(screen.getByRole('button', { name: /Send code via SMS/ })).toBeInTheDocument(); }); - it('should render setOtpVerification and setShouldShowDidintGetTheCodeModal when Change phone number is clicked, should not render setStartTimer', () => { + it('should render setOtpVerification and setShouldShowDidintGetTheCodeModal when Change phone number is clicked, should not render mockSetTimer', () => { renderComponent(VERIFICATION_SERVICES.SMS); const change_phone_number_button = screen.getByRole('button', { name: /Change phone number/ }); userEvent.click(change_phone_number_button); expect(mockSetShouldShowDidntGetTheCodeModal).toHaveBeenCalledTimes(1); expect(mockSetOtpVerification).toHaveBeenCalledTimes(1); - expect(mockSetStartTimer).not.toBeCalled(); + expect(mockSetTimer).not.toBeCalled(); }); - it('should render setStartTimer and setShouldShowDidintGetTheCodeModal when Resend code is clicked', () => { + it('should render mockSetTimer and setShouldShowDidintGetTheCodeModal when Resend code is clicked', () => { renderComponent(VERIFICATION_SERVICES.SMS); const resend_code_button = screen.getByRole('button', { name: resend_code_text }); userEvent.click(resend_code_button); expect(mockSetShouldShowDidntGetTheCodeModal).toHaveBeenCalledTimes(1); - expect(mockSetStartTimer).toHaveBeenCalledTimes(1); + expect(mockSetTimer).toBeCalled(); }); it('should render mockRequestOnSMS and setOtpVerification with phone_verification_type: sms when Resend code is clicked, phone_verification_type is sms', () => { diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx index 5390e273d728..2dc9533a90fb 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/otp-verification.spec.tsx @@ -2,14 +2,12 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { StoreProvider, mockStore } from '@deriv/stores'; import OTPVerification from '../otp-verification'; -import { useSendOTPVerificationCode, useVerifyEmail } from '@deriv/hooks'; +import { useSendOTPVerificationCode, useSettings } from '@deriv/hooks'; import userEvent from '@testing-library/user-event'; jest.mock('@deriv/hooks', () => ({ ...jest.requireActual('@deriv/hooks'), - useVerifyEmail: jest.fn(() => ({ - send: jest.fn(), - })), + useSettings: jest.fn(), useSendOTPVerificationCode: jest.fn(), })); @@ -26,7 +24,6 @@ describe('OTPVerification', () => { }); let phone_verification_type = 'sms'; const mockSetOtpVerification = jest.fn(); - const mockSend = jest.fn(); const mockSendPhoneOTPVerification = jest.fn(); const mockSetPhoneOtpErrorMessage = jest.fn(); const renderComponent = () => { @@ -40,13 +37,19 @@ describe('OTPVerification', () => { ); }; - it('should render ConfirmYourEmail in OTP Verification', () => { - (useVerifyEmail as jest.Mock).mockReturnValueOnce({ - send: mockSend, + beforeEach(() => { + (useSettings as jest.Mock).mockReturnValue({ + data: { + email: 'johndoe@regentmarkets.com', + }, + invalidate: jest.fn(() => Promise.resolve()), }); (useSendOTPVerificationCode as jest.Mock).mockReturnValue({ sendPhoneOTPVerification: jest.fn(), }); + }); + + it('should render ConfirmYourEmail in OTP Verification', () => { renderComponent(); expect(screen.getByText(/Confirm it's you/)).toBeInTheDocument(); expect(screen.getByText(/We've sent a verification code to/)).toBeInTheDocument(); @@ -56,14 +59,10 @@ describe('OTPVerification', () => { ).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: /OTP code/ })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /Resend code/ })).toBeInTheDocument(); - expect(mockSend).toHaveBeenCalled(); }); it('should render Verify your number in OTP Verification', () => { store.ui.should_show_phone_number_otp = true; - (useSendOTPVerificationCode as jest.Mock).mockReturnValue({ - sendPhoneOTPVerification: jest.fn(), - }); renderComponent(); expect(screen.getByText(/Verify your number/)).toBeInTheDocument(); expect(screen.getByText(/Enter the 6-digit code sent to you via SMS at :/)).toBeInTheDocument(); @@ -71,9 +70,6 @@ describe('OTPVerification', () => { it('should render whatsapp when phone_verification_type is whatsapp', () => { store.ui.should_show_phone_number_otp = true; - (useSendOTPVerificationCode as jest.Mock).mockReturnValue({ - sendPhoneOTPVerification: jest.fn(), - }); phone_verification_type = 'whatsapp'; renderComponent(); expect(screen.getByText(/WhatsApp/)).toBeInTheDocument(); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx index 9b835ed5cb9b..e1a12dd3540d 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/resend-code-timer.spec.tsx @@ -1,4 +1,4 @@ -import { act, render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import userEvent from '@testing-library/user-event'; import ResendCodeTimer from '../resend-code-timer'; @@ -9,6 +9,15 @@ jest.mock('@deriv/hooks', () => ({ ...jest.requireActual('@deriv/hooks'), useVerifyEmail: jest.fn(() => ({ send: jest.fn(), + is_success: false, + })), + useSettings: jest.fn(() => ({ + data: { + phone_number_verification: { + next_email_attempt: null, + next_attempt: null, + }, + }, })), })); @@ -23,38 +32,43 @@ describe('ConfirmPhoneNumber', () => { const mock_store = mockStore({}); - it('should disable button after its clicked', () => { + it('should disable button if timer value is given ', async () => { render( ); - const resend_button = screen.getByRole('button', { name: 'Resend code in 60s' }); - userEvent.click(resend_button); + await waitFor(() => { + screen.queryByRole('button', { name: 'Resend code in 59s' }); + }); - expect(resend_button).toBeDisabled(); + expect(screen.queryByRole('button', { name: 'Resend code in 59s' })).toBeDisabled; }); - it('should trigger mockSend and mockSetStartTimer when send button is clicked', () => { + it('should trigger mockSend and mockSetTimer when send button is clicked', () => { const mockSend = jest.fn(); (useVerifyEmail as jest.Mock).mockReturnValue({ send: mockSend, }); - const mockSetStartTimer = jest.fn(); + const mockSetTimer = jest.fn(); render( @@ -63,139 +77,63 @@ describe('ConfirmPhoneNumber', () => { expect(resend_button).toBeEnabled(); userEvent.click(resend_button); - expect(mockSetStartTimer).toBeCalledWith(true); + expect(mockSetTimer).toBeCalledWith(undefined); expect(mockSend).toBeCalled(); }); - it('should display correct title if value of resend_code_text is Resend code', () => { + it('should display Resend code when should_show_resend_code_button is true', () => { render( ); - const resend_button = screen.getByRole('button', { name: 'Resend code in 60s' }); + const resend_button = screen.getByRole('button', { name: 'Resend code' }); expect(resend_button).toBeInTheDocument(); }); - it('should display correct title if value of resend_code_text is Didn’t get the code?', () => { + it('should display Didn’t get the code? should_show_resend_code_button is false', () => { render( ); - const resend_button = screen.getByRole('button', { name: 'Didn’t get the code? (60s)' }); + const resend_button = screen.getByRole('button', { name: "Didn't get the code?" }); expect(resend_button).toBeInTheDocument(); }); - it('should check if title changes when timer expires and value of resend_code_text is Didn’t get the code?', () => { - render( - - - - ); - const resend_button = screen.getByRole('button', { name: 'Didn’t get the code? (6s)' }); - userEvent.click(resend_button); - - act(() => { - jest.advanceTimersByTime(6000); // Advance timers by 6 seconds - }); - - const resend_button_after = screen.getByRole('button', { name: 'Didn’t get the code?' }); - expect(resend_button_after).toBeInTheDocument(); - }); - it('should trigger setShouldShowDidntGetTheCodeModal when Didn`t get the code is clicked', () => { - const setShouldShowDidntGetTheCodeModal = jest.fn(); + const mockSetShouldShowDidntGetTheCodeModal = jest.fn(); render( ); - const resend_button_after = screen.getByRole('button', { name: 'Didn’t get the code?' }); + const resend_button_after = screen.getByRole('button', { name: "Didn't get the code?" }); userEvent.click(resend_button_after); - expect(setShouldShowDidntGetTheCodeModal).toHaveBeenCalled(); - }); - - it('should check if title displays countdown time when timer starts and value of resend_code_text is Didn’t get the code?', async () => { - render( - - - - ); - expect(screen.getByRole('button', { name: 'Didn’t get the code? (6s)' })).toBeInTheDocument(); - jest.advanceTimersByTime(3000); - const updated_resend_button = screen.getByRole('button', { name: 'Didn’t get the code? (4s)' }); - expect(updated_resend_button).toBeInTheDocument(); - }); - - it('should check if title changes when timer expires and value of resend_code_text is Resend code', () => { - render( - - - - ); - const resend_button = screen.getByRole('button', { name: 'Resend code in 6s' }); - userEvent.click(resend_button); - - act(() => { - jest.advanceTimersByTime(6000); - }); - - const resend_button_after = screen.getByRole('button', { name: 'Resend code' }); - expect(resend_button_after).toBeInTheDocument(); - }); - - it('should check if title displays countdown time when timer starts and value of resend_code_text is Resend code', async () => { - render( - - - - ); - expect(screen.getByRole('button', { name: 'Resend code in 6s' })).toBeInTheDocument(); - jest.advanceTimersByTime(3000); - const updated_resend_button = screen.getByRole('button', { name: 'Resend code in 4s' }); - expect(updated_resend_button).toBeInTheDocument(); + expect(mockSetShouldShowDidntGetTheCodeModal).toHaveBeenCalled(); }); }); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx b/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx index 437a9596bd7d..4e6ff2a67d43 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/confirm-phone-number.tsx @@ -13,8 +13,16 @@ type TConfirmPhoneNumber = { const ConfirmPhoneNumber = observer(({ setOtpVerification }: TConfirmPhoneNumber) => { const [phone_number, setPhoneNumber] = React.useState(''); - const { requestOnSMS, requestOnWhatsApp, error_message, setErrorMessage, setUsersPhoneNumber, ...rest } = - useRequestPhoneNumberOTP(); + const [phone_verification_type, setPhoneVerificationType] = React.useState(''); + const { + requestOnSMS, + requestOnWhatsApp, + error_message, + setErrorMessage, + setUsersPhoneNumber, + email_otp_error, + ...rest + } = useRequestPhoneNumberOTP(); const { data: account_settings } = useSettings(); const { ui } = useStore(); const { setShouldShowPhoneNumberOTP } = ui; @@ -23,19 +31,25 @@ const ConfirmPhoneNumber = observer(({ setOtpVerification }: TConfirmPhoneNumber setPhoneNumber(account_settings?.phone || ''); }, [account_settings?.phone]); + React.useEffect(() => { + //TODOs: this will be replace to is_email_verified when we get the BE fixed + if (email_otp_error) { + setOtpVerification({ show_otp_verification: true, phone_verification_type }); + setShouldShowPhoneNumberOTP(true); + } + }, [email_otp_error]); + const handleOnChangePhoneNumber = (e: React.ChangeEvent) => { setPhoneNumber(e.target.value); validatePhoneNumber(e.target.value, setErrorMessage); }; const handleSubmit = async (phone_verification_type: string) => { + setPhoneVerificationType(phone_verification_type); const { error } = await setUsersPhoneNumber({ phone: phone_number }); if (!error) { phone_verification_type === VERIFICATION_SERVICES.SMS ? requestOnSMS() : requestOnWhatsApp(); - //TODOs: Add an error checking from API here before setting setOtpVerification to true - setOtpVerification({ show_otp_verification: true, phone_verification_type }); - setShouldShowPhoneNumberOTP(true); } }; diff --git a/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx b/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx index 9e66577e84d2..8900dc4d66b0 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/didnt-get-the-code-modal.tsx @@ -7,33 +7,48 @@ import { useRequestPhoneNumberOTP } from '@deriv/hooks'; import { convertPhoneTypeDisplay } from 'Helpers/utils'; type TDidntGetTheCodeModal = { + phone_verification_type: string; should_show_didnt_get_the_code_modal: boolean; + setIsButtonDisabled: (value: boolean) => void; setShouldShowDidntGetTheCodeModal: (value: boolean) => void; - setStartTimer: (value: boolean) => void; - phone_verification_type: string; + setTimer: (value: number | undefined) => void; setOtpVerification: (value: { show_otp_verification: boolean; phone_verification_type: string }) => void; + reInitializeGetSettings: () => void; }; const DidntGetTheCodeModal = observer( ({ should_show_didnt_get_the_code_modal, setShouldShowDidntGetTheCodeModal, - setStartTimer, + setTimer, + setIsButtonDisabled, + reInitializeGetSettings, phone_verification_type, setOtpVerification, }: TDidntGetTheCodeModal) => { - const { requestOnSMS, requestOnWhatsApp, ...rest } = useRequestPhoneNumberOTP(); + const { requestOnSMS, requestOnWhatsApp, email_otp_error, ...rest } = useRequestPhoneNumberOTP(); const { ui } = useStore(); const { is_mobile } = ui; + React.useEffect(() => { + //TODO: will replace error_otp_error once BE error is solved + if (email_otp_error) reInitializeGetSettings(); + }, [email_otp_error, reInitializeGetSettings]); + + const setDidntGetACodeButtonDisabled = () => { + setTimer(undefined); + setIsButtonDisabled(true); + }; + const handleResendCode = () => { + setDidntGetACodeButtonDisabled(); phone_verification_type === VERIFICATION_SERVICES.SMS ? requestOnSMS() : requestOnWhatsApp(); setOtpVerification({ show_otp_verification: true, phone_verification_type }); - setStartTimer(true); setShouldShowDidntGetTheCodeModal(false); }; const handleChangeOTPVerification = () => { + setDidntGetACodeButtonDisabled(); const changed_phone_verification_type = phone_verification_type === VERIFICATION_SERVICES.SMS ? VERIFICATION_SERVICES.WHATSAPP @@ -41,7 +56,6 @@ const DidntGetTheCodeModal = observer( phone_verification_type === VERIFICATION_SERVICES.SMS ? requestOnWhatsApp() : requestOnSMS(); - setStartTimer(true); setOtpVerification({ show_otp_verification: true, phone_verification_type: changed_phone_verification_type, diff --git a/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx b/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx index 2e9b4825e64b..7f265d4d042e 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/otp-verification.tsx @@ -3,7 +3,7 @@ import PhoneVerificationCard from './phone-verification-card'; import { Text, InputGroupButton } from '@deriv-com/quill-ui'; import { Localize, localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; -import { useSendOTPVerificationCode, useVerifyEmail } from '@deriv/hooks'; +import { useSendOTPVerificationCode, useSettings } from '@deriv/hooks'; import { convertPhoneTypeDisplay } from 'Helpers/utils'; import ResendCodeTimer from './resend-code-timer'; import DidntGetTheCodeModal from './didnt-get-the-code-modal'; @@ -15,15 +15,14 @@ type TOTPVerification = { }; const OTPVerification = observer(({ phone_verification_type, setOtpVerification }: TOTPVerification) => { - const { client, ui } = useStore(); - const { account_settings, email } = client; - const { phone } = account_settings; + const { ui } = useStore(); + const { data: account_settings, invalidate } = useSettings(); const [should_show_phone_number_verified_modal, setShouldShowPhoneNumberVerifiedModal] = React.useState(false); const [should_show_didnt_get_the_code_modal, setShouldShowDidntGetTheCodeModal] = React.useState(false); - const [start_timer, setStartTimer] = React.useState(true); const [otp, setOtp] = React.useState(''); + const [timer, setTimer] = React.useState(); + const [is_button_disabled, setIsButtonDisabled] = React.useState(false); - const { send } = useVerifyEmail('phone_number_verification'); const { sendPhoneOTPVerification, phone_otp_error_message, @@ -35,23 +34,26 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification //TODO: this shall be replace by BE API call when it's ready const { should_show_phone_number_otp } = ui; + const reInitializeGetSettings = React.useCallback(() => { + invalidate('get_settings').then(() => { + setIsButtonDisabled(false); + setTimer(undefined); + }); + }, [invalidate]); + + React.useEffect(() => { + setIsButtonDisabled(true); + invalidate('get_settings').then(() => setIsButtonDisabled(false)); + }, [invalidate]); + React.useEffect(() => { if (is_phone_number_verified) { setShouldShowPhoneNumberVerifiedModal(true); } else if (is_email_verified) { localStorage.setItem('email_otp_code', otp); setOtpVerification({ show_otp_verification: false, phone_verification_type: '' }); - } else if (!should_show_phone_number_otp) { - send(); } - }, [ - should_show_phone_number_otp, - send, - is_phone_number_verified, - setShouldShowPhoneNumberVerifiedModal, - is_email_verified, - setOtpVerification, - ]); + }, [is_phone_number_verified, is_email_verified, setOtpVerification]); const handleGetOtpValue = (e: React.ChangeEvent) => { setOtp(e.target.value); @@ -74,11 +76,13 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification setShouldShowPhoneNumberVerifiedModal={setShouldShowPhoneNumberVerifiedModal} /> {should_show_phone_number_otp ? ( @@ -94,7 +98,7 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification i18n_default_text='Enter the 6-digit code sent to you via {{phone_verification_type}} at {{users_phone_number}}:' values={{ phone_verification_type: localize(convertPhoneTypeDisplay(phone_verification_type)), - users_phone_number: phone, + users_phone_number: account_settings?.phone, }} /> @@ -103,7 +107,7 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification ]} /> @@ -124,12 +128,13 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification maxLength={6} /> diff --git a/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx b/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx index e9f67fc0fef4..356fbc3d3913 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx @@ -1,64 +1,128 @@ import React from 'react'; import { Button, CaptionText } from '@deriv-com/quill-ui'; import { Localize } from '@deriv/translations'; -import { useVerifyEmail } from '@deriv/hooks'; +import { useSettings, useVerifyEmail } from '@deriv/hooks'; +import dayjs from 'dayjs'; type TResendCodeTimer = { - resend_code_text: string; - count_from: number; + timer: number | undefined; + is_button_disabled: boolean; + should_show_resend_code_button: boolean; + setIsButtonDisabled: (value: boolean) => void; + setTimer: (value: number | undefined) => void; setShouldShowDidntGetTheCodeModal: (value: boolean) => void; - start_timer: boolean; - setStartTimer: (value: boolean) => void; + reInitializeGetSettings: () => void; }; const ResendCodeTimer = ({ - count_from = 60, - resend_code_text, + timer, + is_button_disabled, + should_show_resend_code_button, + setIsButtonDisabled, + setTimer, setShouldShowDidntGetTheCodeModal, - start_timer, - setStartTimer, + reInitializeGetSettings, }: TResendCodeTimer) => { - // TODO: calculate count_from and time units(secs or mins) using timestamp once mockApi is finalised - // TODO: revist start timer logic and localizing the title - const [timer, setTimer] = React.useState(count_from); - const { send } = useVerifyEmail('phone_number_verification'); - const initial_timer_title = - resend_code_text === 'Resend code' ? `Resend code in ${timer}s` : `Didn’t get the code? (${timer}s)`; - const [timer_title, setTimerTitle] = React.useState(initial_timer_title); + const [email_otp_attempt, setEmailOtpAttempt] = React.useState(''); + const [phone_otp_attempt, setPhoneOtpAttempt] = React.useState(''); + // @ts-expect-error this for now + const { send, is_success } = useVerifyEmail('phone_number_verification'); + const { data: account_settings } = useSettings(); + const current_time = dayjs(); - const setTitle = (timer: number, text: string) => { - const title = text === 'Resend code' ? `Resend code in ${timer}s` : `Didn’t get the code? (${timer}s)`; - setTimerTitle(title); - }; + const setTitle = React.useCallback( + (timer: number) => { + let display_time: string; + if (timer > 60) { + display_time = `${timer / 60}m`; + } else { + display_time = `${timer}s`; + } + should_show_resend_code_button + ? setEmailOtpAttempt(` in ${display_time}`) + : setPhoneOtpAttempt(` (${display_time})`); + }, + [should_show_resend_code_button] + ); + + React.useEffect(() => { + if (is_success) reInitializeGetSettings(); + }, [is_success, reInitializeGetSettings]); + + React.useEffect(() => { + // @ts-expect-error this for now + if (should_show_resend_code_button && account_settings?.phone_number_verification?.next_email_attempt) { + const email_otp_request_in_miliseconds = dayjs( + // @ts-expect-error this for now + account_settings?.phone_number_verification?.next_email_attempt * 1000 + ); + const next_email_otp_request = Math.round(email_otp_request_in_miliseconds.diff(current_time) / 1000); + + if (next_email_otp_request > 0) { + setTitle(next_email_otp_request); + setTimer(next_email_otp_request); + } + // @ts-expect-error this for now + } else if (account_settings?.phone_number_verification?.next_attempt) { + const phone_otp_request_in_miliseconds = dayjs( + // @ts-expect-error this for now + account_settings?.phone_number_verification?.next_attempt * 1000 + ); + const next_phone_otp_request = Math.round(phone_otp_request_in_miliseconds.diff(current_time) / 1000); + + if (next_phone_otp_request > 0) { + setTitle(next_phone_otp_request); + setTimer(next_phone_otp_request); + } + } + }, [ + // @ts-expect-error this for now + account_settings?.phone_number_verification?.next_email_attempt, + // @ts-expect-error this for now + account_settings?.phone_number_verification?.next_attempt, + timer, + setTitle, + should_show_resend_code_button, + ]); React.useEffect(() => { let countdown: ReturnType; - if (start_timer && timer > 0) { + if (timer && timer > 0) { countdown = setInterval(() => { - setTimer(prevTime => prevTime - 1); - setTitle(timer, resend_code_text); + setTimer(timer - 1); + setTitle(timer); }, 1000); } else { - setStartTimer(false); - setTimerTitle(resend_code_text); + setEmailOtpAttempt(''); + setPhoneOtpAttempt(''); } return () => clearInterval(countdown); - }, [timer, start_timer]); + }, [timer, setTitle]); const resendCode = () => { - if (resend_code_text !== 'Resend code') { - setShouldShowDidntGetTheCodeModal(true); - } else { + if (should_show_resend_code_button) { + setTimer(undefined); + setIsButtonDisabled(true); send(); - setTimer(count_from); - setStartTimer(true); + } else { + setShouldShowDidntGetTheCodeModal(true); } }; return ( - ); diff --git a/packages/hooks/src/useSettings.ts b/packages/hooks/src/useSettings.ts index 4f173fc9ba02..4039597fa02f 100644 --- a/packages/hooks/src/useSettings.ts +++ b/packages/hooks/src/useSettings.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { useInvalidateQuery, useMutation, useQuery } from '@deriv/api'; import { useStore } from '@deriv/stores'; @@ -22,6 +22,7 @@ const useSettings = () => { data: data?.get_settings, /** Function to update user settings */ update, + invalidate, /** The mutation related information */ mutation: mutate_rest, ...rest, diff --git a/packages/hooks/src/useVerifyEmail.ts b/packages/hooks/src/useVerifyEmail.ts index 4d9961df3ac1..583ff1b7d029 100644 --- a/packages/hooks/src/useVerifyEmail.ts +++ b/packages/hooks/src/useVerifyEmail.ts @@ -34,6 +34,7 @@ const useVerifyEmail = ( return { is_loading: WS.isLoading, + is_success: WS.isSuccess, error: WS.error, data: WS.data, counter: counter.count, From dc69a85ffeddc1cd4b4f55abe80f6dd55c32d070 Mon Sep 17 00:00:00 2001 From: Tay Sui Sin Date: Fri, 14 Jun 2024 11:53:51 +0800 Subject: [PATCH 02/10] chore: use 1 useState for otp request --- .../PhoneVerification/resend-code-timer.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx b/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx index 356fbc3d3913..a1a81e0c3266 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/resend-code-timer.tsx @@ -22,8 +22,7 @@ const ResendCodeTimer = ({ setShouldShowDidntGetTheCodeModal, reInitializeGetSettings, }: TResendCodeTimer) => { - const [email_otp_attempt, setEmailOtpAttempt] = React.useState(''); - const [phone_otp_attempt, setPhoneOtpAttempt] = React.useState(''); + const [next_otp_request, setNextOtpRequest] = React.useState(''); // @ts-expect-error this for now const { send, is_success } = useVerifyEmail('phone_number_verification'); const { data: account_settings } = useSettings(); @@ -38,8 +37,8 @@ const ResendCodeTimer = ({ display_time = `${timer}s`; } should_show_resend_code_button - ? setEmailOtpAttempt(` in ${display_time}`) - : setPhoneOtpAttempt(` (${display_time})`); + ? setNextOtpRequest(` in ${display_time}`) + : setNextOtpRequest(` (${display_time})`); }, [should_show_resend_code_button] ); @@ -92,8 +91,7 @@ const ResendCodeTimer = ({ setTitle(timer); }, 1000); } else { - setEmailOtpAttempt(''); - setPhoneOtpAttempt(''); + setNextOtpRequest(''); } return () => clearInterval(countdown); @@ -113,14 +111,11 @@ const ResendCodeTimer = ({