diff --git a/packages/account/package.json b/packages/account/package.json index cfb0e50f8eba..a588ce2970a8 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..d85f66b59914 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); }; @@ -26,7 +29,7 @@ export const VerifyButton = observer(() => {
- Verified + ({ 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 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 +40,6 @@ describe('DidntGetTheCodeModal', () => { beforeEach(() => { mockSetShouldShowDidntGetTheCodeModal.mockClear(); - mockSetStartTimer.mockClear(); mockSetOtpVerification.mockClear(); }); @@ -55,21 +57,19 @@ 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(); }); - it('should render setStartTimer and setShouldShowDidintGetTheCodeModal when Resend code is clicked', () => { + it('should render 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); }); 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 c2c16dba8775..0cba425020fe 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..a9e5a522c2ae 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,38 @@ 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 when send button is clicked', () => { const mockSend = jest.fn(); (useVerifyEmail as jest.Mock).mockReturnValue({ send: mockSend, }); - const mockSetStartTimer = jest.fn(); render( @@ -63,139 +72,56 @@ describe('ConfirmPhoneNumber', () => { expect(resend_button).toBeEnabled(); userEvent.click(resend_button); - expect(mockSetStartTimer).toBeCalledWith(true); 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/__test__/validation.spec.tsx b/packages/account/src/Sections/Profile/PhoneVerification/__test__/validation.spec.tsx index bf281f34f855..063634558785 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/__test__/validation.spec.tsx +++ b/packages/account/src/Sections/Profile/PhoneVerification/__test__/validation.spec.tsx @@ -1,11 +1,15 @@ import { act } from '@testing-library/react'; -import { validatePhoneNumber } from '../validation'; +import { otpRequestCountdown, validatePhoneNumber } from '../validation'; +import dayjs from 'dayjs'; describe('validatePhoneNumber', () => { - let setErrorMessage: jest.Mock; + let setErrorMessage: jest.Mock, setTitleMock: jest.Mock, setTimerMock: jest.Mock, current_time: dayjs.Dayjs; beforeEach(() => { setErrorMessage = jest.fn(); + setTitleMock = jest.fn(); + setTimerMock = jest.fn(); + current_time = dayjs(); }); it('should set an empty error message for a valid phone number', async () => { @@ -55,4 +59,22 @@ describe('validatePhoneNumber', () => { }); expect(setErrorMessage).toHaveBeenCalledWith(['Please enter a valid phone number.']); }); + + it('should set title and timer correctly if next_request is greater than 0', () => { + const nextAttemptTimestamp = current_time.add(60, 'seconds').unix(); + + otpRequestCountdown(nextAttemptTimestamp, setTitleMock, setTimerMock, current_time); + + expect(setTitleMock).toHaveBeenCalledWith(60); + expect(setTimerMock).toHaveBeenCalledWith(60); + }); + + it('should not set title and timer if next_request is less than or equal to 0', () => { + const nextAttemptTimestamp = current_time.subtract(60, 'seconds').unix(); + + otpRequestCountdown(nextAttemptTimestamp, setTitleMock, setTimerMock, current_time); + + expect(setTitleMock).not.toHaveBeenCalled(); + expect(setTimerMock).not.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..820f851c2ff6 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, + is_email_verified, + ...rest + } = useRequestPhoneNumberOTP(); const { data: account_settings } = useSettings(); const { ui } = useStore(); const { setShouldShowPhoneNumberOTP } = ui; @@ -23,19 +31,24 @@ const ConfirmPhoneNumber = observer(({ setOtpVerification }: TConfirmPhoneNumber setPhoneNumber(account_settings?.phone || ''); }, [account_settings?.phone]); + React.useEffect(() => { + if (is_email_verified) { + setOtpVerification({ show_otp_verification: true, phone_verification_type }); + setShouldShowPhoneNumberOTP(true); + } + }, [is_email_verified]); + 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..ee10b2e1577c 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,45 @@ 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; 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, + setIsButtonDisabled, + reInitializeGetSettings, phone_verification_type, setOtpVerification, }: TDidntGetTheCodeModal) => { - const { requestOnSMS, requestOnWhatsApp, ...rest } = useRequestPhoneNumberOTP(); + const { requestOnSMS, requestOnWhatsApp, is_email_verified, ...rest } = useRequestPhoneNumberOTP(); const { ui } = useStore(); const { is_mobile } = ui; + React.useEffect(() => { + //TODO: will replace error_otp_error once BE error is solved + if (is_email_verified) reInitializeGetSettings(); + }, [is_email_verified, reInitializeGetSettings]); + + const setDidntGetACodeButtonDisabled = () => { + 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 +53,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 84dbfcc29051..69fde2664243 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'; @@ -16,14 +16,13 @@ type TOTPVerification = { const OTPVerification = observer(({ phone_verification_type, setOtpVerification }: TOTPVerification) => { const { client, ui } = useStore(); - const { account_settings, email, setVerificationCode } = client; - const { phone } = account_settings; + const { setVerificationCode } = client; + 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 [is_button_disabled, setIsButtonDisabled] = React.useState(false); - const { send } = useVerifyEmail('phone_number_verification'); const { sendPhoneOTPVerification, phone_otp_error_message, @@ -35,23 +34,25 @@ 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); + }); + }, [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) { - setVerificationCode('phone_number_verification', otp); + setVerificationCode(otp, 'phone_number_verification'); 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 +75,12 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification setShouldShowPhoneNumberVerifiedModal={setShouldShowPhoneNumberVerifiedModal} /> {should_show_phone_number_otp ? ( @@ -94,7 +96,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 +105,7 @@ const OTPVerification = observer(({ phone_verification_type, setOtpVerification ]} /> @@ -124,12 +126,11 @@ 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..20fcaeeb2682 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,115 @@ 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'; +import { otpRequestCountdown } from './validation'; type TResendCodeTimer = { - resend_code_text: string; - count_from: number; + is_button_disabled: boolean; + should_show_resend_code_button: boolean; + setIsButtonDisabled: (value: boolean) => void; setShouldShowDidntGetTheCodeModal: (value: boolean) => void; - start_timer: boolean; - setStartTimer: (value: boolean) => void; + reInitializeGetSettings: () => void; }; const ResendCodeTimer = ({ - count_from = 60, - resend_code_text, + is_button_disabled, + should_show_resend_code_button, + setIsButtonDisabled, 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 [timer, setTimer] = 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(); + 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 = `${Math.round(timer / 60)}m`; + } else { + display_time = `${timer}s`; + } + should_show_resend_code_button + ? setNextOtpRequest(` in ${display_time}`) + : setNextOtpRequest(` (${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) { + otpRequestCountdown( + // @ts-expect-error this for now + account_settings.phone_number_verification.next_email_attempt, + setTitle, + setTimer, + current_time + ); + // @ts-expect-error this for now + } else if (account_settings?.phone_number_verification?.next_attempt) { + otpRequestCountdown( + // @ts-expect-error this for now + account_settings.phone_number_verification.next_attempt, + setTitle, + setTimer, + current_time + ); + } + }, [ + // @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, + current_time, + ]); 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); + setNextOtpRequest(''); } 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) { + setIsButtonDisabled(true); send(); - setTimer(count_from); - setStartTimer(true); + } else { + setShouldShowDidntGetTheCodeModal(true); } }; return ( - ); diff --git a/packages/account/src/Sections/Profile/PhoneVerification/validation.ts b/packages/account/src/Sections/Profile/PhoneVerification/validation.ts index 1b8c305b1bb7..c48036a1005e 100644 --- a/packages/account/src/Sections/Profile/PhoneVerification/validation.ts +++ b/packages/account/src/Sections/Profile/PhoneVerification/validation.ts @@ -1,6 +1,7 @@ import * as Yup from 'yup'; import { ValidationConstants } from '@deriv-com/utils'; import { localize } from '@deriv/translations'; +import dayjs from 'dayjs'; const phoneNumberSchema = Yup.string().matches( ValidationConstants.patterns.phoneNumber, @@ -15,3 +16,18 @@ export const validatePhoneNumber = (phone_number: string, setErrorMessage: (valu setErrorMessage(errors); }); }; + +export const otpRequestCountdown = ( + nextAttemptTimestamp: number, + setTitle: (title: number) => void, + setTimer: (title: number) => void, + current_time: dayjs.Dayjs +) => { + const request_in_milliseconds = dayjs(nextAttemptTimestamp * 1000); + const next_request = Math.round(request_in_milliseconds.diff(current_time) / 1000); + + if (next_request > 0) { + setTitle(next_request); + setTimer(next_request); + } +}; diff --git a/packages/core/src/App/Containers/NotificationsDialog/notifications-list.tsx b/packages/core/src/App/Containers/NotificationsDialog/notifications-list.tsx index d1a395a4b70c..bf8f9b0b6a44 100644 --- a/packages/core/src/App/Containers/NotificationsDialog/notifications-list.tsx +++ b/packages/core/src/App/Containers/NotificationsDialog/notifications-list.tsx @@ -61,7 +61,7 @@ const NotificationsList = observer(() => { {getButtonSettings(item)?.route ? ( , type: 'warning', action: { + onClick: () => { + WS.verifyEmail(email, 'phone_number_verification'); + }, route: routes.phone_verification, text: localize('Get started'), }, 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,