Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WALL] george / WALL-4850 / Password change and account creation success message are not shown when changing password #17318

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ import PasswordMeter from './PasswordMeter';
import PasswordViewerIcon from './PasswordViewerIcon';
import './WalletPasswordField.scss';

export const validatePassword = (
password: string,
mt5Policy: boolean,
localize: ReturnType<typeof useTranslations>['localize']
) => {
type TValidatePasswordProps = {
isMT5PasswordNotSet?: boolean;
localize: ReturnType<typeof useTranslations>['localize'];
mt5Policy: boolean;
password: string;
};

export const validatePassword = (values: TValidatePasswordProps) => {
const { isMT5PasswordNotSet, localize, mt5Policy, password } = values;
const score = mt5Policy ? calculateScoreMT5(password) : calculateScoreCFD(password);
let validationErrorMessage = '';

Expand All @@ -38,7 +42,9 @@ export const validatePassword = (
} else {
cfdSchema(localize).validateSync(password);
}
validationErrorMessage = getWarningMessages(localize)[feedback.warning as passwordKeys] ?? '';
validationErrorMessage = isMT5PasswordNotSet
? getWarningMessages(localize)[feedback.warning as passwordKeys] ?? ''
: '';
} catch (err) {
if (err instanceof ValidationError) {
validationErrorMessage = err?.message;
Expand All @@ -50,6 +56,7 @@ export const validatePassword = (

const WalletPasswordField: React.FC<WalletPasswordFieldProps> = ({
autoComplete,
isMT5PasswordNotSet,
label,
mt5Policy = false,
name = 'walletPasswordField',
Expand All @@ -67,8 +74,8 @@ const WalletPasswordField: React.FC<WalletPasswordFieldProps> = ({
const [errorMessage, setErrorMessage] = useState('');

const { score, validationErrorMessage } = useMemo(
() => validatePassword(password, mt5Policy, localize),
[password, mt5Policy, localize]
() => validatePassword({ isMT5PasswordNotSet, localize, mt5Policy, password }),
[password, mt5Policy, localize, isMT5PasswordNotSet]
);
const passwordValidation = mt5Policy ? !validPasswordMT5(password) : !validPassword(password);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Loader } from '@deriv-com/ui';
import { WalletTextFieldProps } from '../WalletTextField/WalletTextField';

export interface WalletPasswordFieldProps extends WalletTextFieldProps {
isMT5PasswordNotSet?: boolean;
mt5Policy?: boolean; // This prop is used to utilize the new password validation for MT5.
password: string;
passwordError?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type TModalContext = {
type TModalOptions = {
defaultRootId?: 'wallets_modal_root' | 'wallets_modal_show_header_root';
rootRef?: React.RefObject<HTMLElement>;
shouldCloseOnClickOutside?: boolean;
shouldHideDerivAppHeader?: boolean;
};

Expand Down Expand Up @@ -84,7 +85,10 @@ const ModalProvider = ({ children }: React.PropsWithChildren<unknown>) => {
}));
};

useOnClickOutside(modalRef, isDesktop ? hide : () => undefined);
const onClickOutsideHandler = () =>
modalOptions?.shouldCloseOnClickOutside === false || !isDesktop ? () => undefined : hide;

useOnClickOutside(modalRef, onClickOutsideHandler);

const modalRootRef = useMemo(() => {
// if they specify their own root, prioritize this first
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { FC } from 'react';
import { DerivLightMt5SuccessPasswordResetIcon } from '@deriv/quill-icons';
import { useTranslations } from '@deriv-com/translations';
import { ActionScreen, Button, useDevice } from '@deriv-com/ui';
import { PlatformDetails } from '../../features/cfd/constants';
import { ModalStepWrapper, ModalWrapper } from '../Base';
import './WalletsChangeMT5Password.scss';

type TWalletSuccessChangeMT5Password = {
onClick: () => void;
};

const WalletSuccessChangeMT5Password: FC<TWalletSuccessChangeMT5Password> = ({ onClick }) => {
const { isDesktop } = useDevice();
const { localize } = useTranslations();

const title = localize('Success');
const description = localize(
'You have a new {{title}} password to log in to your {{title}} accounts on the web and mobile apps.',
{ title: PlatformDetails.mt5.title }
);
const renderButtons = (
<Button isFullWidth={!isDesktop} onClick={onClick} size='lg' textSize='sm'>
{localize('Next')}
</Button>
);

if (isDesktop) {
return (
<ModalWrapper hideCloseButton shouldPreventCloseOnEscape>
<div className='wallets-change-mt5-password'>
<ActionScreen
actionButtons={renderButtons}
description={description}
descriptionSize='sm'
icon={<DerivLightMt5SuccessPasswordResetIcon height={100} width={100} />}
title={title}
/>
</div>
</ModalWrapper>
);
}

return (
<ModalStepWrapper
renderFooter={() => renderButtons}
shouldHideFooter={isDesktop}
shouldHideHeader
shouldPreventCloseOnEscape
>
<div className='wallets-change-mt5-password'>
<ActionScreen
description={description}
descriptionSize='sm'
icon={<DerivLightMt5SuccessPasswordResetIcon height={100} width={100} />}
title={title}
/>
</div>
</ModalStepWrapper>
);
};

export default WalletSuccessChangeMT5Password;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.wallets-change-mt5-password {
width: 44rem;
padding: 2.4rem;
display: flex;
flex-direction: column;
gap: 2.4rem;
background-color: var(--general-main-1, #ffffff);
border-radius: 0.8rem;

@include mobile-or-tablet-screen {
width: 100%;
padding-inline: 1.6rem;
gap: 1.6rem;
margin: 0 auto;
max-width: 60rem;
}

&__footer {
display: flex;
justify-content: flex-end;
gap: 0.8rem;

@include mobile-or-tablet-screen {
width: 100%;
justify-content: center;
margin: 0 auto;
max-width: 60rem;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as WalletSuccessChangeMT5Password } from './WalletSuccessChangeMT5Password';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Button, Loader, useDevice } from '@deriv-com/ui';
import { SentEmailContent, WalletError } from '../../../../components';
import { ModalStepWrapper, ModalWrapper } from '../../../../components/Base';
import { useModal } from '../../../../components/ModalProvider';
import { WalletSuccessChangeMT5Password } from '../../../../components/WalletsChangeMT5Password';
import { THooks, TMarketTypes, TPlatforms } from '../../../../types';
import { platformPasswordResetRedirectLink } from '../../../../utils/cfd';
import { validPassword, validPasswordMT5 } from '../../../../utils/password-validation';
Expand Down Expand Up @@ -59,13 +60,14 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
status: emailVerificationStatus,
} = useVerifyEmail();
const { isDesktop } = useDevice();
const { getModalState, hide } = useModal();
const { getModalState, hide, setModalOptions } = useModal();
const { data: settingsData } = useSettings();
const { localize } = useTranslations();

const { email } = settingsData;

const [password, setPassword] = useState('');
const [isPasswordChanged, setIsPasswordChanged] = useState(false);

const isMT5PasswordNotSet = accountStatusData?.is_mt5_password_not_set;
const isDemo = activeWalletData?.is_virtual;
Expand All @@ -79,22 +81,10 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
(createMT5AccountError?.error?.code === 'InvalidTradingPlatformPasswordFormat' ||
createMT5AccountError?.error?.code === 'IncorrectMT5PasswordFormat');

const onSubmit = useCallback(async () => {
// ====== Create MT5 Account ======
// In order to create account, we need to set a password through trading_platform_password_change endpoint first,
// then only mt5_create_account can be called, otherwise it will response an error for password required.
// =================================

const createMT5Account = useCallback(() => {
const accountType = marketType === MARKET_TYPE.SYNTHETIC ? 'gaming' : marketType;
const categoryAccountType = isDemo ? 'demo' : accountType;

if (isMT5PasswordNotSet) {
await tradingPasswordChangeMutateAsync({
new_password: password,
platform: mt5Platform,
});
}

createMT5AccountMutate({
payload: {
account_type: categoryAccountType,
Expand Down Expand Up @@ -129,10 +119,10 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
availableMT5AccountsData,
createMT5AccountMutate,
isDemo,
isMT5PasswordNotSet,
marketType,
mt5Platform,
password,
product,
selectedJurisdiction,
settingsData?.address_city,
settingsData?.address_line_1,
settingsData?.address_postcode,
Expand All @@ -141,11 +131,24 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
settingsData?.email,
settingsData?.first_name,
settingsData?.phone,
tradingPasswordChangeMutateAsync,
selectedJurisdiction,
product,
]);

const onSubmit = useCallback(async () => {
// ====== Create MT5 Account ======
// In order to create account, we need to set a password through trading_platform_password_change endpoint first,
// then only mt5_create_account can be called, otherwise it will response an error for password required.
// =================================

if (isMT5PasswordNotSet) {
await tradingPasswordChangeMutateAsync({
new_password: password,
platform: mt5Platform,
});
}

createMT5Account();
}, [createMT5Account, isMT5PasswordNotSet, mt5Platform, password, tradingPasswordChangeMutateAsync]);

const sendEmailVerification = useCallback(() => {
if (email) {
emailVerificationMutate({
Expand All @@ -164,10 +167,13 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
new_password: newPassword,
old_password: currentPassword,
platform: mt5Platform,
}).then(() => {
setPassword(newPassword);
setModalOptions(prev => ({ ...prev, shouldCloseOnClickOutside: false }));
setIsPasswordChanged(true);
});
setPassword(newPassword);
},
[mt5Platform, tradingPasswordChangeMutateAsync]
[mt5Platform, setModalOptions, tradingPasswordChangeMutateAsync]
);

const renderTitle = useCallback(() => {
Expand Down Expand Up @@ -286,6 +292,7 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
return (
<EnterPassword
isLoading={tradingPlatformPasswordChangeLoading || createMT5AccountLoading}
isMT5PasswordNotSet={isMT5PasswordNotSet}
isTncChecked={isTncChecked}
isVirtual={activeWalletData?.is_virtual}
marketType={marketType}
Expand Down Expand Up @@ -346,6 +353,18 @@ const MT5PasswordModal: React.FC<TProps> = ({ isVirtual, marketType, platform, p
);
}

if (isPasswordChanged) {
return (
<WalletSuccessChangeMT5Password
onClick={() => {
createMT5Account();
setIsPasswordChanged(false);
setModalOptions(prev => ({ ...prev, shouldCloseOnClickOutside: true }));
}}
/>
);
}

if (createMT5AccountSuccess && !isMT5PasswordNotSet) {
return (
<MT5AccountAdded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { Localize, useTranslations } from '@deriv-com/translations';
import { Button, Text, useDevice } from '@deriv-com/ui';
import { WalletPasswordFieldLazy } from '../../../../components/Base';
import { THooks, TMarketTypes, TPlatforms } from '../../../../types';
import { validPassword, validPasswordMT5 } from '../../../../utils/password-validation';
import { CFDPasswordModalTnc } from '../../components/CFDPasswordModalTnc';
import { CFD_PLATFORMS, getMarketTypeDetails, PlatformDetails, PRODUCT } from '../../constants';
import './EnterPassword.scss';

type TProps = {
isForgotPasswordLoading?: boolean;
isLoading?: boolean;
isMT5PasswordNotSet?: boolean;
isTncChecked?: boolean;
isVirtual?: boolean;
marketType: TMarketTypes.CreateOtherCFDAccount;
Expand All @@ -30,6 +30,7 @@ type TProps = {
const EnterPassword: React.FC<TProps> = ({
isForgotPasswordLoading,
isLoading,
isMT5PasswordNotSet,
isTncChecked = true,
isVirtual,
marketType,
Expand All @@ -48,8 +49,6 @@ const EnterPassword: React.FC<TProps> = ({
const { localize } = useTranslations();
const { data } = useActiveWalletAccount();

const isMT5 = platform === CFD_PLATFORMS.MT5;
const disableButton = isMT5 ? !validPasswordMT5(password) : !validPassword(password);
const accountType = data?.is_virtual ? localize('Demo') : localize('Real');
const title = PlatformDetails[platform].title;
const marketTypeTitle =
Expand All @@ -60,6 +59,7 @@ const EnterPassword: React.FC<TProps> = ({
'Hint: You may have entered your Deriv password, which is different from your {{title}} password.',
{ title }
);
const isAddAccountBtnDisabled = !password || isLoading || !isTncChecked;

useEffect(() => {
if (passwordError) {
Expand Down Expand Up @@ -89,6 +89,7 @@ const EnterPassword: React.FC<TProps> = ({
/>
</Text>
<WalletPasswordFieldLazy
isMT5PasswordNotSet={isMT5PasswordNotSet}
label={localize('{{title}} password', { title })}
onChange={onPasswordChange}
password={password}
Expand Down Expand Up @@ -122,7 +123,7 @@ const EnterPassword: React.FC<TProps> = ({
<Localize i18n_default_text='Forgot password?' />
</Button>
<Button
disabled={!password || isLoading || disableButton || !isTncChecked}
disabled={isAddAccountBtnDisabled}
isLoading={isLoading}
onClick={onPrimaryClick}
size='lg'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ describe('EnterPassword', () => {
});

const title = `Enter your ${PlatformDetails.mt5.title} password`;
const shortPassword = 'abcd';
const validPassword = 'Abcd1234!';

const defaultProps = {
Expand Down Expand Up @@ -76,12 +75,6 @@ describe('EnterPassword', () => {
expect(defaultProps.onPrimaryClick).toHaveBeenCalled();
});

it('disables the "Add account" button when password is invalid', () => {
renderComponent({ password: shortPassword });
const addAccountButton = screen.getByRole('button', { name: 'Add account' });
expect(addAccountButton).toBeDisabled();
});

it('shows password error hints when passwordError is true', () => {
renderComponent({ passwordError: true });
expect(
Expand Down
Loading