From 8dc26ac13e7f4dcbcc820027c1d5b91eee38b14d Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa <104334373+jim-deriv@users.noreply.github.com> Date: Thu, 16 May 2024 17:24:25 +0800 Subject: [PATCH] [WALL] Jim/WALL-4001/ Send email when forgot password is clicked (#15107) * chore: send email when forgot password is clicked * chore: add sendemail call to modal on mobile * chore: rename hook * docs: add tsdoc * chore: add sendemail to dependency array * chore: add error handling logic * chore: add error handling for sent email template * chore: remove unnecessary dependencies --- .../SentEmailContent/SentEmailContent.tsx | 39 ++++---- .../DxtradeEnterPasswordModal.tsx | 89 ++++++++++++++----- .../screens/EnterPassword/EnterPassword.tsx | 9 +- .../useSendPasswordResetEmail.spec.tsx | 42 +++++++++ .../src/hooks/useSendPasswordResetEmail.ts | 50 +++++++++++ 5 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 packages/wallets/src/hooks/__tests__/useSendPasswordResetEmail.spec.tsx create mode 100644 packages/wallets/src/hooks/useSendPasswordResetEmail.ts diff --git a/packages/wallets/src/components/SentEmailContent/SentEmailContent.tsx b/packages/wallets/src/components/SentEmailContent/SentEmailContent.tsx index e38cf2d798e7..0b0c479b953f 100644 --- a/packages/wallets/src/components/SentEmailContent/SentEmailContent.tsx +++ b/packages/wallets/src/components/SentEmailContent/SentEmailContent.tsx @@ -1,7 +1,6 @@ import React, { FC, Fragment, useEffect, useState } from 'react'; import { Trans } from 'react-i18next'; import { useCountdown } from 'usehooks-ts'; -import { useActiveWalletAccount, useSettings, useVerifyEmail } from '@deriv/api-v2'; import { DerivLightIcEmailSentIcon, DerivLightIcEmailSentPasskeyIcon, @@ -12,9 +11,10 @@ import { } from '@deriv/quill-icons'; import { PlatformDetails } from '../../features/cfd/constants'; import useDevice from '../../hooks/useDevice'; +import useSendPasswordResetEmail from '../../hooks/useSendPasswordResetEmail'; import { TPlatforms } from '../../types'; -import { platformPasswordResetRedirectLink } from '../../utils/cfd'; import { WalletButton, WalletText } from '../Base'; +import { WalletError } from '../WalletError'; import { WalletsActionScreen } from '../WalletsActionScreen'; import './SentEmailContent.scss'; @@ -22,6 +22,7 @@ type SentEmailContentProps = { description?: string; isChangePassword?: boolean; // NOTE: This prop is ONLY used for rendering different email icons between either Change Password/Forgot password email modal isInvestorPassword?: boolean; + onErrorButtonClick?: () => void; platform?: TPlatforms.All; }; @@ -57,12 +58,12 @@ const SentEmailContent: FC = ({ description, isChangePassword = false, isInvestorPassword = false, + onErrorButtonClick, platform, }) => { const [shouldShowResendEmailReasons, setShouldShowResendEmailReasons] = useState(false); const [hasCountdownStarted, setHasCountdownStarted] = useState(false); - const { data } = useSettings(); - const { mutate: verifyEmail } = useVerifyEmail(); + const { error: resetPasswordError, sendEmail } = useSendPasswordResetEmail(); const { isMobile } = useDevice(); const mt5Platform = PlatformDetails.mt5.platform; const { title } = PlatformDetails[platform ?? mt5Platform]; @@ -80,29 +81,25 @@ const SentEmailContent: FC = ({ isChangePassword || isInvestorPassword ? DerivLightIcEmailSentIcon : DerivLightIcEmailSentPasskeyIcon; const resendEmail = () => { - if (data?.email) { - verifyEmail({ - type: platform === mt5Platform ? mt5ResetType : 'trading_platform_dxtrade_password_reset', - url_parameters: { - redirect_to: platformPasswordResetRedirectLink(platform ?? mt5Platform, activeWallet?.is_virtual), - }, - verify_email: data?.email, - }); - resetCountdown(); - startCountdown(); - setHasCountdownStarted(true); - } + sendEmail({ isInvestorPassword, platform }); + resetCountdown(); + startCountdown(); + setHasCountdownStarted(true); }; useEffect(() => { if (count === 0) setHasCountdownStarted(false); }, [count]); - const { data: activeWallet } = useActiveWalletAccount(); - - const mt5ResetType = isInvestorPassword - ? 'trading_platform_investor_password_reset' - : 'trading_platform_mt5_password_reset'; + if (resetPasswordError) { + return ( + + ); + } return (
diff --git a/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx b/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx index b030baf35ca7..606c0be87325 100644 --- a/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx +++ b/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { useAccountStatus, @@ -10,6 +10,7 @@ import { SentEmailContent, WalletError } from '../../../../components'; import { ModalStepWrapper, ModalWrapper, WalletButton, WalletButtonGroup } from '../../../../components/Base'; import { useModal } from '../../../../components/ModalProvider'; import useDevice from '../../../../hooks/useDevice'; +import useSendPasswordResetEmail from '../../../../hooks/useSendPasswordResetEmail'; import { PlatformDetails } from '../../constants'; import { CFDSuccess, CreatePassword, EnterPassword } from '../../screens'; import './DxtradeEnterPasswordModal.scss'; @@ -19,9 +20,23 @@ const DxtradeEnterPasswordModal = () => { const { isMobile } = useDevice(); const [password, setPassword] = useState(''); const { data: getAccountStatus, isSuccess: accountStatusSuccess } = useAccountStatus(); - const { data: createdAccount, error, isLoading, isSuccess, mutateAsync, status } = useCreateOtherCFDAccount(); + const { + data: createdAccount, + error, + isLoading, + isSuccess: isCreateAccountSuccessful, + mutateAsync, + status, + } = useCreateOtherCFDAccount(); + const { data: dxtradeAccount, isSuccess: dxtradeAccountListSuccess } = useDxtradeAccountsList(); const { data: activeWallet } = useActiveWalletAccount(); + const { + error: resetPasswordError, + isLoading: isResetPasswordLoading, + isSuccess: isResetPasswordSuccessful, + sendEmail, + } = useSendPasswordResetEmail(); const { hide, show } = useModal(); const accountType = activeWallet?.is_virtual ? 'demo' : 'real'; const dxtradePlatform = PlatformDetails.dxtrade.platform; @@ -45,12 +60,29 @@ const DxtradeEnterPasswordModal = () => { : `Transfer funds from your ${activeWallet?.currency} Wallet to your ${PlatformDetails.dxtrade.title} account to start trading.`; }, [accountType, activeWallet?.currency, activeWallet?.display_balance]); + useEffect(() => { + if (!isResetPasswordSuccessful) return; + if (!isDxtradePasswordNotSet && isMobile) { + show( + + + + ); + } else if (!isDxtradePasswordNotSet) { + show( + + + + ); + } + }, [dxtradePlatform, hide, isDxtradePasswordNotSet, isMobile, isResetPasswordSuccessful, show]); + const dxtradeBalance = useMemo(() => { return dxtradeAccount?.find(account => account.market_type === 'all')?.display_balance; }, [dxtradeAccount]); const renderFooter = useMemo(() => { - if (isSuccess) { + if (isCreateAccountSuccessful) { if (accountType === 'demo') { return (
@@ -83,12 +115,11 @@ const DxtradeEnterPasswordModal = () => { { - show( - - - - ); + sendEmail({ + platform: dxtradePlatform, + }); }} size={isMobile ? 'lg' : 'md'} variant='outlined' @@ -125,17 +156,18 @@ const DxtradeEnterPasswordModal = () => { dxtradePlatform, hide, history, + isCreateAccountSuccessful, isDxtradePasswordNotSet, isLoading, isMobile, - isSuccess, + isResetPasswordLoading, onSubmit, password, - show, + sendEmail, ]); const successComponent = useMemo(() => { - if (isSuccess && dxtradeAccountListSuccess) { + if (isCreateAccountSuccessful && dxtradeAccountListSuccess) { return ( { ); } }, [ - isSuccess, + isCreateAccountSuccessful, dxtradeAccountListSuccess, successDescription, dxtradeBalance, @@ -160,7 +192,7 @@ const DxtradeEnterPasswordModal = () => { ]); const passwordComponent = useMemo(() => { - if (!isSuccess && accountStatusSuccess) { + if (!isCreateAccountSuccessful && accountStatusSuccess) { return isDxtradePasswordNotSet ? ( { /> ) : ( setPassword(e.target.value)} onPrimaryClick={onSubmit} - onSecondaryClick={() => - show( - - - - ) - } + onSecondaryClick={() => { + sendEmail({ + platform: dxtradePlatform, + }); + }} password={password} passwordError={error?.error?.code === 'PasswordError'} platform={dxtradePlatform} @@ -190,20 +221,32 @@ const DxtradeEnterPasswordModal = () => { ); } }, [ - isSuccess, + isCreateAccountSuccessful, accountStatusSuccess, isDxtradePasswordNotSet, isLoading, onSubmit, password, dxtradePlatform, + isResetPasswordLoading, error?.error?.code, - show, + sendEmail, ]); + if (status === 'error' && error?.error?.code !== 'PasswordError') { return ; } + if (resetPasswordError) { + return ( + + ); + } + if (isMobile) { return ( renderFooter} title={' '}> @@ -213,7 +256,7 @@ const DxtradeEnterPasswordModal = () => { ); } return ( - + {successComponent} {passwordComponent} diff --git a/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx b/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx index f89620b2922a..2b06e82fa7b5 100644 --- a/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx +++ b/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx @@ -8,6 +8,7 @@ import { CFD_PLATFORMS, MarketTypeDetails, PlatformDetails } from '../../constan import './EnterPassword.scss'; type TProps = { + isForgotPasswordLoading?: boolean; isLoading?: boolean; marketType: TMarketTypes.CreateOtherCFDAccount; onPasswordChange?: (e: React.ChangeEvent) => void; @@ -20,6 +21,7 @@ type TProps = { }; const EnterPassword: React.FC = ({ + isForgotPasswordLoading, isLoading, marketType, onPasswordChange, @@ -70,7 +72,12 @@ const EnterPassword: React.FC = ({
{isDesktop && (
- + Forgot password? ({ + ...jest.requireActual('@deriv/api-v2'), + useSettings: jest.fn(() => ({ + data: { + email: 'test@meme.com', + }, + })), + useVerifyEmail: jest.fn(() => ({ + mutate: mockMutate, + })), +})); + +const wrapper = ({ children }: PropsWithChildren) => ( + + {children} + +); + +describe('useSendPasswordResetEmail', () => { + it('should call mutate when sendEmail is called', () => { + const { result } = renderHook(() => useSendPasswordResetEmail(), { wrapper }); + result.current.sendEmail({ + isInvestorPassword: true, + platform: 'mt5', + }); + expect(mockMutate).toHaveBeenCalledWith({ + type: 'trading_platform_investor_password_reset', + url_parameters: { + redirect_to: 10, + }, + verify_email: 'test@meme.com', + }); + }); +}); diff --git a/packages/wallets/src/hooks/useSendPasswordResetEmail.ts b/packages/wallets/src/hooks/useSendPasswordResetEmail.ts new file mode 100644 index 000000000000..5924e9b7dd9a --- /dev/null +++ b/packages/wallets/src/hooks/useSendPasswordResetEmail.ts @@ -0,0 +1,50 @@ +import { useCallback } from 'react'; +import { useActiveWalletAccount, useSettings, useVerifyEmail } from '@deriv/api-v2'; +import { PlatformDetails } from '../features/cfd/constants'; +import { TPlatforms } from '../types'; +import { platformPasswordResetRedirectLink } from '../utils/cfd'; + +type TSendEmailPayload = { + isInvestorPassword?: boolean; + platform?: TPlatforms.All; +}; + +/** + * @description This hook is used to send a password reset email to the user. It handles MT5, DXTrade, and Investor password reset. + * @returns {Object} sendEmail - A function that sends a password reset email to the user. + */ +const useSendPasswordResetEmail = () => { + const { data } = useSettings(); + const { mutate: verifyEmail, ...rest } = useVerifyEmail(); + const { data: activeWallet } = useActiveWalletAccount(); + + const sendEmail = useCallback( + ({ isInvestorPassword = false, platform }: TSendEmailPayload) => { + const mt5ResetType = isInvestorPassword + ? 'trading_platform_investor_password_reset' + : 'trading_platform_mt5_password_reset'; + const mt5Platform = PlatformDetails.mt5.platform; + + if (data?.email) { + verifyEmail({ + type: platform === mt5Platform ? mt5ResetType : 'trading_platform_dxtrade_password_reset', + url_parameters: { + redirect_to: platformPasswordResetRedirectLink( + platform ?? mt5Platform, + activeWallet?.is_virtual + ), + }, + verify_email: data?.email, + }); + } + }, + [activeWallet?.is_virtual, data?.email, verifyEmail] + ); + + return { + sendEmail, + ...rest, + }; +}; + +export default useSendPasswordResetEmail;