First, enter your Passport number and the expiry date.
- setPassportNumber(e.target.value)} />
- setExpirationDate(e.target.value)}
- renderRightIcon={() => }
- type='date'
- />
+
+
@@ -54,7 +24,7 @@ const PassportDocumentUpload = () => {
fileFormats={['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'application/pdf']}
icon={
}
maxSize={8388608}
- onFileChange={setFile}
+ onFileChange={(file: File) => setFormValues('passportCard', file)}
/>
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/index.ts
new file mode 100644
index 000000000000..4335b0f70bbd
--- /dev/null
+++ b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/index.ts
@@ -0,0 +1,4 @@
+export * from './DrivingLicenseDocumentUpload';
+export * from './IdentityCardDocumentUpload';
+export * from './NIMCSlipDocumentUpload';
+export * from './PassportDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/index.ts
new file mode 100644
index 000000000000..864046a81b14
--- /dev/null
+++ b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/index.ts
@@ -0,0 +1 @@
+export { default as useHandleManualDocumentUpload } from './useHandleManualDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/useHandleManualDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/useHandleManualDocumentUpload.tsx
new file mode 100644
index 000000000000..359ce1bb4993
--- /dev/null
+++ b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/hooks/useHandleManualDocumentUpload.tsx
@@ -0,0 +1,76 @@
+import { useCallback } from 'react';
+import { FormikValues } from 'formik';
+import { useDocumentUpload, useSettings } from '@deriv/api';
+
+const useHandleManualDocumentUpload = () => {
+ const { data: settings } = useSettings();
+ const { upload, ...rest } = useDocumentUpload();
+
+ const uploadDocument = useCallback(
+ async (formValues: FormikValues) => {
+ if (formValues.selectedManualDocument === 'passport') {
+ await upload({
+ document_id: formValues.passportNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'passport',
+ expiration_date: formValues.passportExpiryDate,
+ file: formValues.passportCard,
+ });
+ } else if (formValues.selectedManualDocument === 'identity-card') {
+ await upload({
+ document_id: formValues.identityCardNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'national_identity_card',
+ expiration_date: formValues.identityCardExpiryDate,
+ file: formValues.identityCardFront,
+ page_type: 'front',
+ });
+ await upload({
+ document_id: formValues.identityCardNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'national_identity_card',
+ expiration_date: formValues.identityCardExpiryDate,
+ file: formValues.identityCardBack,
+ page_type: 'back',
+ });
+ } else if (formValues.selectedManualDocument === 'driving-license') {
+ await upload({
+ document_id: formValues.drivingLicenceNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'driving_licence',
+ expiration_date: formValues.drivingLicenseExpiryDate,
+ file: formValues.drivingLicenseCardFront,
+ page_type: 'front',
+ });
+ await upload({
+ document_id: formValues.drivingLicenceNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'driving_licence',
+ expiration_date: formValues.drivingLicenseExpiryDate,
+ file: formValues.drivingLicenseCardBack,
+ page_type: 'back',
+ });
+ } else if (formValues.selectedManualDocument === 'nimc-slip') {
+ await upload({
+ document_id: formValues.nimcNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'nimc_slip',
+ file: formValues.nimcCardFront,
+ page_type: 'front',
+ });
+ await upload({
+ document_id: formValues.nimcNumber,
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'nimc_slip',
+ file: formValues.nimcCardBack,
+ page_type: 'back',
+ });
+ }
+ },
+ [settings, upload]
+ );
+
+ return { uploadDocument, ...rest };
+};
+
+export default useHandleManualDocumentUpload;
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts
index 5d7612fea781..2f34fe22f8c1 100644
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts
+++ b/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/index.ts
@@ -1 +1,2 @@
+export * from './hooks';
export { default as ManualDocumentUpload } from './ManualDocumentUpload';
diff --git a/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx b/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx
index 93210a9e4e69..b58ce1e618e6 100644
--- a/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx
+++ b/packages/wallets/src/features/accounts/screens/PersonalDetails/PersonalDetails.tsx
@@ -62,9 +62,15 @@ const PersonalDetails = () => {
}))}
name='wallets-personal-details__dropdown-tax-residence'
onChange={inputValue => {
- setFormValues('taxResidence', inputValue);
+ residenceList.forEach(residence => {
+ if (residence.text?.toLowerCase() === inputValue.toLowerCase()) {
+ setFormValues('taxResidence', residence.value);
+ }
+ });
+ }}
+ onSelect={selectedItem => {
+ setFormValues('taxResidence', selectedItem);
}}
- onSelect={selectedItem => setFormValues('taxResidence', selectedItem)}
value={getSettings?.tax_residence ?? formValues?.taxResidence}
variant='comboBox'
/>
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/SelfieDocumentUpload.scss b/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.scss
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/SelfieDocumentUpload.scss
rename to packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.scss
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/SelfieDocumentUpload.tsx b/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.tsx
similarity index 74%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/SelfieDocumentUpload.tsx
rename to packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.tsx
index 7a56e1479d1a..7f480dafabd3 100644
--- a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/SelfieDocumentUpload.tsx
+++ b/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/SelfieDocumentUpload.tsx
@@ -1,11 +1,13 @@
import React from 'react';
-import { Dropzone, WalletText } from '../../../../../../components';
-import useDevice from '../../../../../../hooks/useDevice';
-import SelfieIcon from '../../../../../../public/images/accounts/selfie-icon.svg';
+import { Dropzone, useFlow, WalletText } from '../../../../components';
+import useDevice from '../../../../hooks/useDevice';
+import SelfieIcon from '../../../../public/images/accounts/selfie-icon.svg';
import './SelfieDocumentUpload.scss';
const SelfieDocumentUpload = () => {
const { isDesktop } = useDevice();
+ const { setFormValues } = useFlow();
+
return (
Upload your selfie
@@ -16,6 +18,7 @@ const SelfieDocumentUpload = () => {
fileFormats='image/*'
hasFrame={isDesktop}
icon={
}
+ onFileChange={(file: File) => setFormValues('selfie', file)}
/>
Face forward and remove your glasses if necessary. Make sure your eyes are clearly visible and your face
diff --git a/packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/index.ts b/packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/index.ts
similarity index 100%
rename from packages/wallets/src/features/accounts/screens/ManualDocumentUpload/components/SelfieDocumentUpload/index.ts
rename to packages/wallets/src/features/accounts/screens/SelfieDocumentUpload/index.ts
diff --git a/packages/wallets/src/features/accounts/screens/index.ts b/packages/wallets/src/features/accounts/screens/index.ts
index 927ceaf1f1fa..eb8b644571f2 100644
--- a/packages/wallets/src/features/accounts/screens/index.ts
+++ b/packages/wallets/src/features/accounts/screens/index.ts
@@ -3,3 +3,4 @@ export * from './IDVDocumentUpload';
export * from './ManualDocumentUpload';
export * from './PersonalDetails';
export * from './ResubmitPOA';
+export * from './SelfieDocumentUpload';
diff --git a/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampDisclaimer/FiatOnRampDisclaimer.tsx b/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampDisclaimer/FiatOnRampDisclaimer.tsx
index 9704ae58fc57..d12a4c79d39a 100644
--- a/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampDisclaimer/FiatOnRampDisclaimer.tsx
+++ b/packages/wallets/src/features/cashier/modules/FiatOnRamp/components/FiatOnRampDisclaimer/FiatOnRampDisclaimer.tsx
@@ -1,5 +1,5 @@
-import React, { MouseEventHandler, useCallback } from 'react';
-import { useQuery } from '@deriv/api';
+import React, { MouseEventHandler, useCallback, useEffect } from 'react';
+import { useMutation } from '@deriv/api';
import { WalletButton, WalletText } from '../../../../../../components';
import './FiatOnRampDisclaimer.scss';
@@ -8,9 +8,7 @@ type TFiatOnRampDisclaimer = {
};
const FiatOnRampDisclaimer: React.FC = ({ handleDisclaimer }) => {
- const { data: provider, isLoading } = useQuery('service_token', {
- payload: { referrer: window.location.href, service: 'banxa' },
- });
+ const { data: provider, isLoading, mutate } = useMutation('service_token');
const redirectToBanxa = useCallback(() => {
const banxaUrl = provider?.service_token?.banxa?.url ?? '';
@@ -22,6 +20,10 @@ const FiatOnRampDisclaimer: React.FC = ({ handleDisclaime
}
}, [provider?.service_token?.banxa?.url]);
+ useEffect(() => {
+ mutate({ payload: { referrer: window.location.href, service: 'banxa' } });
+ }, [mutate]);
+
return (
diff --git a/packages/wallets/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx b/packages/wallets/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx
index 443f8506ab20..5c8e6b951e3f 100644
--- a/packages/wallets/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx
+++ b/packages/wallets/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx
@@ -3,8 +3,8 @@ import { useHistory } from 'react-router-dom';
import { useCtraderAccountsList } from '@deriv/api';
import { TradingAccountCard } from '../../../../../components';
import { WalletButton, WalletText } from '../../../../../components/Base';
-import CTrader from '../../../../../public/images/ctrader.svg';
import { useModal } from '../../../../../components/ModalProvider';
+import CTrader from '../../../../../public/images/ctrader.svg';
import { MT5TradeModal } from '../../../modals';
import './AddedCTraderAccountsList.scss';
diff --git a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx
index d8859197cb43..5fce28768ec0 100644
--- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx
+++ b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx
@@ -54,7 +54,15 @@ const AddedMT5AccountsList: React.FC = ({ account }) => {
/>
show()}
+ onClick={() =>
+ show(
+
+ )
+ }
text='Open'
/>
diff --git a/packages/wallets/src/features/cfd/flows/Verification/Verification.tsx b/packages/wallets/src/features/cfd/flows/Verification/Verification.tsx
index 9a929910975a..8b10596171f3 100644
--- a/packages/wallets/src/features/cfd/flows/Verification/Verification.tsx
+++ b/packages/wallets/src/features/cfd/flows/Verification/Verification.tsx
@@ -1,11 +1,16 @@
-import React, { FC, useMemo } from 'react';
-import { useAuthentication, usePOA, usePOI, useSettings } from '@deriv/api';
-import { ModalStepWrapper, WalletButton } from '../../../../components/Base';
+import React, { FC, useCallback, useMemo } from 'react';
+import { useDocumentUpload, usePOA, usePOI, useSettings } from '@deriv/api';
+import { ModalStepWrapper, WalletButton, WalletButtonGroup } from '../../../../components/Base';
import { FlowProvider, TFlowProviderContext } from '../../../../components/FlowProvider';
import { Loader } from '../../../../components/Loader';
import { useModal } from '../../../../components/ModalProvider';
import { THooks } from '../../../../types';
-import { ManualDocumentUpload, ResubmitPOA } from '../../../accounts/screens';
+import {
+ ManualDocumentUpload,
+ ResubmitPOA,
+ SelfieDocumentUpload,
+ useHandleManualDocumentUpload,
+} from '../../../accounts/screens';
import { IDVDocumentUpload } from '../../../accounts/screens/IDVDocumentUpload';
import { PersonalDetails } from '../../../accounts/screens/PersonalDetails';
import { MT5PasswordModal } from '../../modals';
@@ -27,6 +32,7 @@ const screens = {
onfidoScreen: ,
personalDetailsScreen: ,
poaScreen: ,
+ selfieScreen: ,
};
type TVerificationProps = {
@@ -36,19 +42,22 @@ type TVerificationProps = {
const Verification: FC = ({ selectedJurisdiction }) => {
const { data: poiStatus, isSuccess: isSuccessPOIStatus } = usePOI();
const { data: poaStatus, isSuccess: isSuccessPOAStatus } = usePOA();
- const { data: authenticationData } = useAuthentication();
- const { data: getSettings, update: updateSettings } = useSettings();
+ const { isLoading: isUploadLoading, upload } = useDocumentUpload();
+ const { isLoading: isManualUploadLoading, uploadDocument } = useHandleManualDocumentUpload();
+ const { data: settings, update: updateSettings } = useSettings();
const { getModalState, hide, show } = useModal();
const selectedMarketType = getModalState('marketType') || 'all';
const platform = getModalState('platform') || 'mt5';
+ const shouldSubmitPOA = useMemo(
+ () => !poaStatus?.has_attempted_poa || (!poaStatus?.is_pending && !poaStatus.is_verified),
+ [poaStatus]
+ );
const isLoading = useMemo(() => {
return !isSuccessPOIStatus || !isSuccessPOAStatus;
}, [isSuccessPOIStatus, isSuccessPOAStatus]);
- const hasAttemptedPOA = poaStatus?.has_attempted_poa || true;
-
const initialScreenId: keyof typeof screens = useMemo(() => {
const service = (poiStatus?.current?.service || 'manual') as keyof THooks.POI['services'];
@@ -56,22 +65,21 @@ const Verification: FC = ({ selectedJurisdiction }) => {
const serviceStatus = poiStatus.status;
if (!isSuccessPOIStatus) return 'loadingScreen';
-
if (serviceStatus === 'pending' || serviceStatus === 'verified') {
- if (authenticationData?.is_poa_needed && !hasAttemptedPOA) return 'poaScreen';
- if (!getSettings?.has_submitted_personal_details) return 'personalDetailsScreen';
+ if (shouldSubmitPOA) return 'poaScreen';
+ if (!settings?.has_submitted_personal_details) return 'personalDetailsScreen';
show();
}
if (service === 'idv') return 'idvScreen';
if (service === 'onfido') return 'onfidoScreen';
+ if (service === 'manual') return 'manualScreen';
}
return 'loadingScreen';
}, [
poiStatus,
isSuccessPOIStatus,
- authenticationData?.is_poa_needed,
- hasAttemptedPOA,
- getSettings?.has_submitted_personal_details,
+ shouldSubmitPOA,
+ settings?.has_submitted_personal_details,
show,
selectedMarketType,
platform,
@@ -87,6 +95,41 @@ const Verification: FC = ({ selectedJurisdiction }) => {
!formValues.dateOfBirth ||
!!errors.documentNumber
);
+ case 'manualScreen':
+ if (formValues.selectedManualDocument === 'driving-license') {
+ return (
+ !formValues.drivingLicenceNumber ||
+ !formValues.drivingLicenseExpiryDate ||
+ !formValues.drivingLicenseCardFront ||
+ !formValues.drivingLicenseCardBack ||
+ isManualUploadLoading
+ );
+ } else if (formValues.selectedManualDocument === 'passport') {
+ return (
+ !formValues.passportNumber ||
+ !formValues.passportExpiryDate ||
+ !formValues.passportCard ||
+ isManualUploadLoading
+ );
+ } else if (formValues.selectedManualDocument === 'identity-card') {
+ return (
+ !formValues.identityCardNumber ||
+ !formValues.identityCardExpiryDate ||
+ !formValues.identityCardFront ||
+ !formValues.identityCardBack ||
+ isManualUploadLoading
+ );
+ } else if (formValues.selectedManualDocument === 'nimc-slip') {
+ return (
+ !formValues.nimcNumber ||
+ !formValues.nimcCardFront ||
+ !formValues.nimcCardBack ||
+ isManualUploadLoading
+ );
+ }
+ return !formValues.selectedManualDocument;
+ case 'selfieScreen':
+ return !formValues.selfie;
case 'onfidoScreen':
return !formValues.hasSubmittedOnfido;
case 'personalDetailsScreen':
@@ -104,46 +147,80 @@ const Verification: FC = ({ selectedJurisdiction }) => {
}
};
- const nextFlowHandler = ({ currentScreenId, formValues, switchScreen }: TFlowProviderContext) => {
- if (['idvScreen', 'onfidoScreen', 'manualScreen'].includes(currentScreenId)) {
- if (currentScreenId === 'idvScreen') {
+ const isNextLoading = useCallback(
+ ({ currentScreenId, formValues }: TFlowProviderContext) => {
+ if (['manualScreen', 'selfieScreen'].includes(currentScreenId) && formValues.selectedManualDocument)
+ return isUploadLoading || isManualUploadLoading || isLoading;
+ return isLoading;
+ },
+ [isLoading, isManualUploadLoading, isUploadLoading]
+ );
+
+ const nextFlowHandler = useCallback(
+ async ({ currentScreenId, formValues, setFormValues, switchScreen }: TFlowProviderContext) => {
+ if (['idvScreen', 'onfidoScreen', 'selfieScreen'].includes(currentScreenId)) {
+ // API calls
+ if (currentScreenId === 'idvScreen') {
+ updateSettings({
+ date_of_birth: formValues.dateOfBirth,
+ first_name: formValues.firstName,
+ last_name: formValues.lastName,
+ });
+ } else if (currentScreenId === 'selfieScreen') {
+ await upload({
+ document_issuing_country: settings?.country_code ?? undefined,
+ document_type: 'selfie_with_id',
+ file: formValues.selfie,
+ });
+ }
+
+ // handle screen switching
+ if (shouldSubmitPOA) {
+ switchScreen('poaScreen');
+ } else if (!settings?.has_submitted_personal_details) {
+ switchScreen('personalDetailsScreen');
+ } else {
+ show();
+ }
+ } else if (currentScreenId === 'manualScreen') {
+ await uploadDocument(formValues);
+ setFormValues('selectedManualDocument', '');
+ switchScreen('selfieScreen');
+ } else if (currentScreenId === 'poaScreen') {
updateSettings({
- date_of_birth: formValues.dateOfBirth,
- first_name: formValues.firstName,
- last_name: formValues.lastName,
+ address_city: formValues.townCityLine,
+ address_line_1: formValues.firstLine,
+ address_line_2: formValues.secondLine,
+ address_postcode: formValues.zipCodeLine,
+ address_state: formValues.stateProvinceDropdownLine,
});
- } else if (currentScreenId === 'manualScreen') {
- // TODO: call the api here
- }
- if (hasAttemptedPOA) {
- switchScreen('poaScreen');
- } else if (!getSettings?.has_submitted_personal_details) {
switchScreen('personalDetailsScreen');
- } else {
+ } else if (currentScreenId === 'personalDetailsScreen') {
+ updateSettings({
+ account_opening_reason: formValues.accountOpeningReason,
+ citizen: formValues.citizenship,
+ place_of_birth: formValues.placeOfBirth,
+ tax_identification_number: formValues.taxIdentificationNumber,
+ tax_residence: formValues.taxResidence,
+ });
show();
+ } else {
+ hide();
}
- } else if (currentScreenId === 'poaScreen') {
- updateSettings({
- address_city: formValues.townCityLine,
- address_line_1: formValues.firstLine,
- address_line_2: formValues.secondLine,
- address_postcode: formValues.zipCodeLine,
- address_state: formValues.stateProvinceDropdownLine,
- });
- switchScreen('personalDetailsScreen');
- } else if (currentScreenId === 'personalDetailsScreen') {
- updateSettings({
- account_opening_reason: formValues.accountOpeningReason,
- citizen: formValues.citizenship,
- place_of_birth: formValues.placeOfBirth,
- tax_identification_number: formValues.taxIdentificationNumber,
- tax_residence: formValues.taxResidence,
- });
- show();
- } else {
- hide();
- }
- };
+ },
+ [
+ hide,
+ platform,
+ selectedMarketType,
+ settings?.country_code,
+ settings?.has_submitted_personal_details,
+ shouldSubmitPOA,
+ show,
+ updateSettings,
+ upload,
+ uploadDocument,
+ ]
+ );
return (
= ({ selectedJurisdiction }) => {
{context => {
return (
{
- return (
- nextFlowHandler(context)}
- size='lg'
- text='Next'
- />
- );
- }}
+ renderFooter={
+ context.currentScreenId === 'manualScreen' && !context.formValues.selectedManualDocument
+ ? undefined
+ : () => {
+ if (context.currentScreenId === 'manualScreen')
+ return (
+
+
+ context.setFormValues('selectedManualDocument', '')
+ }
+ size='lg'
+ text='Back'
+ variant='outlined'
+ />
+ nextFlowHandler(context)}
+ size='lg'
+ text='Next'
+ />
+
+ );
+ return (
+ nextFlowHandler(context)}
+ size='lg'
+ text='Next'
+ />
+ );
+ }
+ }
title='Add a real MT5 account'
>
{context.WalletScreen}
diff --git a/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx b/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx
index 76c0ad8d843b..c48b20fc631b 100644
--- a/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx
+++ b/packages/wallets/src/features/cfd/modals/DxtradeEnterPasswordModal/DxtradeEnterPasswordModal.tsx
@@ -46,7 +46,7 @@ const DxtradeEnterPasswordModal = () => {
const renderFooter = useMemo(() => {
if (isSuccess) {
return (
-
+
hide()} size='lg' text='Maybe later' variant='outlined' />
{
diff --git a/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx b/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx
index 0229e196d7aa..1e2dc2671268 100644
--- a/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx
+++ b/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import { useAvailableMT5Accounts } from '@deriv/api';
import { ModalStepWrapper, WalletButton } from '../../../../components/Base';
import { useModal } from '../../../../components/ModalProvider';
@@ -16,7 +16,7 @@ const JurisdictionModal = () => {
const [isDynamicLeverageVisible, setIsDynamicLeverageVisible] = useState(false);
const [isCheckBoxChecked, setIsCheckBoxChecked] = useState(false);
- const { getModalState, show } = useModal();
+ const { getModalState, setModalState, show } = useModal();
const { isLoading } = useAvailableMT5Accounts();
const { isMobile } = useDevice();
@@ -50,6 +50,10 @@ const JurisdictionModal = () => {
/>
);
+ useEffect(() => {
+ setModalState('selectedJurisdiction', selectedJurisdiction);
+ }, [selectedJurisdiction]);
+
if (isLoading) return Loading...
;
return (
diff --git a/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx b/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx
index 866723d935ba..38fe56b34a0f 100644
--- a/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx
+++ b/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx
@@ -30,7 +30,7 @@ const MT5PasswordModal: React.FC = ({ marketType, platform }) => {
const { data: mt5Accounts } = useMT5AccountsList();
const { data: availableMT5Accounts } = useAvailableMT5Accounts();
const { data: settings } = useSettings();
- const { hide, show } = useModal();
+ const { getModalState, hide, show } = useModal();
const { isMobile } = useDevice();
const hasMT5Account = mt5Accounts?.find(account => account.login);
@@ -39,6 +39,7 @@ const MT5PasswordModal: React.FC = ({ marketType, platform }) => {
marketType === 'all' && Object.keys(PlatformDetails).includes(platform)
? PlatformDetails[platform].title
: MarketTypeDetails[marketType].title;
+ const selectedJurisdiction = getModalState('selectedJurisdiction');
const onSubmit = async () => {
const accountType = marketType === 'synthetic' ? 'gaming' : marketType;
@@ -57,12 +58,18 @@ const MT5PasswordModal: React.FC = ({ marketType, platform }) => {
account_type: activeWallet?.is_virtual ? 'demo' : accountType,
address: settings?.address_line_1 || '',
city: settings?.address_city || '',
- company: 'svg',
+ company: selectedJurisdiction,
country: settings?.country_code || '',
email: settings?.email || '',
leverage: availableMT5Accounts?.find(acc => acc.market_type === marketType)?.leverage || 500,
mainPassword: password,
- ...(marketType === 'financial' && { mt5_account_type: 'financial' }),
+ ...(marketType === 'financial' &&
+ (selectedJurisdiction !== 'labuan'
+ ? { mt5_account_type: 'financial' }
+ : {
+ account_type: 'financial',
+ mt5_account_type: 'financial_stp',
+ })),
...(marketType === 'all' && { sub_account_category: 'swap_free' }),
name: settings?.first_name || '',
phone: settings?.phone || '',
@@ -86,7 +93,7 @@ const MT5PasswordModal: React.FC = ({ marketType, platform }) => {
if (isSuccess) return hide()} size='lg' text='Continue' />;
if (hasMT5Account)
return (
-
+
{
@@ -134,6 +141,7 @@ const MT5PasswordModal: React.FC = ({ marketType, platform }) => {
displayBalance={
mt5Accounts?.find(account => account.market_type === marketType)?.display_balance || ''
}
+ landingCompany={selectedJurisdiction}
marketType={marketType}
platform={platform}
renderButton={() => }
@@ -174,6 +182,7 @@ const MT5PasswordModal: React.FC = ({ marketType, platform }) => {
displayBalance={
mt5Accounts?.find(account => account.market_type === marketType)?.display_balance || ''
}
+ landingCompany={selectedJurisdiction}
marketType={marketType}
platform={platform}
renderButton={() => }
diff --git a/packages/wallets/src/features/cfd/modals/MT5TradeModal/MT5TradeModal.tsx b/packages/wallets/src/features/cfd/modals/MT5TradeModal/MT5TradeModal.tsx
index 616ef9ce3843..bae0f0b4674e 100644
--- a/packages/wallets/src/features/cfd/modals/MT5TradeModal/MT5TradeModal.tsx
+++ b/packages/wallets/src/features/cfd/modals/MT5TradeModal/MT5TradeModal.tsx
@@ -1,15 +1,16 @@
import React, { FC, useEffect } from 'react';
import { useModal } from '../../../../components/ModalProvider';
-import { TMarketTypes, TPlatforms } from '../../../../types';
+import { THooks, TMarketTypes, TPlatforms } from '../../../../types';
import { ModalTradeWrapper } from '../../components';
import { MT5TradeScreen } from '../../screens';
type TMT5TradeModalProps = {
marketType?: TMarketTypes.All;
+ mt5Account?: THooks.MT5AccountsList;
platform: TPlatforms.All;
};
-const MT5TradeModal: FC = ({ marketType, platform }) => {
+const MT5TradeModal: FC = ({ marketType, mt5Account, platform }) => {
const { setModalState } = useModal();
useEffect(() => {
setModalState('marketType', marketType);
@@ -20,7 +21,7 @@ const MT5TradeModal: FC = ({ marketType, platform }) => {
return (
-
+
);
};
diff --git a/packages/wallets/src/features/cfd/screens/CFDSuccess/CFDSuccess.tsx b/packages/wallets/src/features/cfd/screens/CFDSuccess/CFDSuccess.tsx
index ef34b0e80c02..e345da710f45 100644
--- a/packages/wallets/src/features/cfd/screens/CFDSuccess/CFDSuccess.tsx
+++ b/packages/wallets/src/features/cfd/screens/CFDSuccess/CFDSuccess.tsx
@@ -5,7 +5,7 @@ import { WalletSuccess, WalletText } from '../../../../components';
import { WalletGradientBackground } from '../../../../components/WalletGradientBackground';
import { WalletMarketCurrencyIcon } from '../../../../components/WalletMarketCurrencyIcon';
import useDevice from '../../../../hooks/useDevice';
-import { TDisplayBalance, TMarketTypes, TPlatforms } from '../../../../types';
+import { TDisplayBalance, THooks, TMarketTypes, TPlatforms } from '../../../../types';
import { MarketTypeDetails, PlatformDetails } from '../../constants';
import './CFDSuccess.scss';
@@ -15,6 +15,7 @@ type TSuccessProps = {
| TDisplayBalance.CtraderAccountsList
| TDisplayBalance.DxtradeAccountsList
| TDisplayBalance.MT5AccountsList;
+ landingCompany?: THooks.AvailableMT5Accounts['shortcode'];
marketType?: TMarketTypes.SortedMT5Accounts;
platform?: TPlatforms.All;
renderButton?: ComponentProps['renderButtons'];
@@ -24,6 +25,7 @@ type TSuccessProps = {
const CFDSuccess: React.FC = ({
description,
displayBalance,
+ landingCompany = 'svg',
marketType,
platform,
renderButton,
@@ -32,7 +34,7 @@ const CFDSuccess: React.FC = ({
const { data } = useActiveWalletAccount();
const { isDesktop } = useDevice();
const isDemo = data?.is_virtual;
- const landingCompanyName = data?.landing_company_name?.toUpperCase();
+ const landingCompanyName = landingCompany.toUpperCase();
const isMarketTypeAll = marketType === 'all';
diff --git a/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.scss b/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.scss
index 5787ac6145f5..0f50768eec6d 100644
--- a/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.scss
+++ b/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.scss
@@ -49,4 +49,26 @@
}
}
}
+
+ &__investor-password {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2.4rem;
+
+ &-fields {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1.6rem;
+ min-width: 32.8rem;
+ }
+
+ &-buttons {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1.6rem;
+ }
+ }
}
diff --git a/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.tsx b/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.tsx
index 07939608a659..c7f3e719c101 100644
--- a/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.tsx
+++ b/packages/wallets/src/features/cfd/screens/ChangePassword/ChangePassword.tsx
@@ -1,19 +1,24 @@
import React from 'react';
import { ModalStepWrapper, Tab, Tabs } from '../../../../components/Base';
+import { useModal } from '../../../../components/ModalProvider';
+import { PlatformDetails } from '../../constants';
+import MT5ChangeInvestorPasswordScreens from './MT5ChangeInvestorPasswordScreens';
import MT5ChangePasswordScreens from './MT5ChangePasswordScreens';
import './ChangePassword.scss';
const ChangePassword = () => {
+ const { getModalState } = useModal();
+ const platform = getModalState('platform') || 'mt5';
+ const platformTitle = PlatformDetails[platform].title;
return (
-
+
-
-
+
+
- {/* TODO: Add Investor Password */}
- <>>
+
diff --git a/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangeInvestorPasswordScreens.tsx b/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangeInvestorPasswordScreens.tsx
new file mode 100644
index 000000000000..31bb51ba6e27
--- /dev/null
+++ b/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangeInvestorPasswordScreens.tsx
@@ -0,0 +1,106 @@
+import React, { useState } from 'react';
+import {
+ SentEmailContent,
+ WalletButton,
+ WalletPasswordField,
+ WalletsActionScreen,
+ WalletText,
+} from '../../../../components';
+import { useModal } from '../../../../components/ModalProvider';
+import useDevice from '../../../../hooks/useDevice';
+import MT5PasswordUpdatedIcon from '../../../../public/images/ic-mt5-password-updated.svg';
+
+type TChangeInvestorPasswordScreenIndex = 'emailVerification' | 'introScreen' | 'savedScreen';
+
+const MT5ChangeInvestorPasswordScreens = () => {
+ const [currentInvestorPassword, setCurrentInvestorPassword] = useState('');
+ const [newInvestorPassword, setNewInvestorPassword] = useState('');
+
+ const [activeScreen, setActiveScreen] = useState('introScreen');
+ const handleClick = (nextScreen: TChangeInvestorPasswordScreenIndex) => setActiveScreen(nextScreen);
+
+ const { isMobile } = useDevice();
+ const { hide } = useModal();
+
+ const ChangeInvestorPasswordScreens = {
+ introScreen: {
+ bodyText: (
+ <>
+
+ Use this password to grant viewing access to another user. While they may view your trading
+ account, they will not be able to trade or take any other actions.
+
+
+ If this is the first time you try to create a password, or you have forgotten your password,
+ please reset it.
+
+ >
+ ),
+ button: (
+
+
+ setCurrentInvestorPassword(e.target.value)}
+ password={currentInvestorPassword}
+ />
+ setNewInvestorPassword(e.target.value)}
+ password={newInvestorPassword}
+ />
+
+
+ handleClick('savedScreen')}
+ size={isMobile ? 'lg' : 'md'}
+ text='Change investor password'
+ />
+ handleClick('emailVerification')}
+ size={isMobile ? 'lg' : 'md'}
+ text='Create or reset investor password'
+ variant='ghost'
+ />
+
+
+ ),
+ headingText: undefined,
+ icon: undefined,
+ },
+ savedScreen: {
+ bodyText: (
+
+ Your investor password has been changed.
+
+ ),
+ button: hide()} size='lg' text='Okay' />,
+ headingText: 'Password saved',
+ icon: ,
+ },
+ };
+
+ if (activeScreen === 'emailVerification')
+ return (
+
+
+
+ );
+
+ return (
+
+ ChangeInvestorPasswordScreens[activeScreen].button}
+ title={ChangeInvestorPasswordScreens[activeScreen].headingText}
+ />
+
+ );
+};
+
+export default MT5ChangeInvestorPasswordScreens;
diff --git a/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangePasswordScreens.tsx b/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangePasswordScreens.tsx
index 8ea8748c744d..4c38ff72f16d 100644
--- a/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangePasswordScreens.tsx
+++ b/packages/wallets/src/features/cfd/screens/ChangePassword/MT5ChangePasswordScreens.tsx
@@ -2,10 +2,15 @@ import React, { useState } from 'react';
import { SentEmailContent, WalletButton, WalletsActionScreen, WalletText } from '../../../../components';
import { useModal } from '../../../../components/ModalProvider';
import MT5PasswordIcon from '../../../../public/images/ic-mt5-password.svg';
+import { TPlatforms } from '../../../../types';
-const MT5ChangePasswordScreens = () => {
- type TChangePasswordScreenIndex = 'confirmationScreen' | 'emailVerification' | 'introScreen';
+type MT5ChangePasswordScreensProps = {
+ platform: TPlatforms.All;
+ platformTitle: string;
+};
+const MT5ChangePasswordScreens: React.FC = ({ platform, platformTitle }) => {
+ type TChangePasswordScreenIndex = 'confirmationScreen' | 'emailVerification' | 'introScreen';
const [activeScreen, setActiveScreen] = useState('introScreen');
const handleClick = (nextScreen: TChangePasswordScreenIndex) => setActiveScreen(nextScreen);
@@ -15,7 +20,7 @@ const MT5ChangePasswordScreens = () => {
confirmationScreen: {
bodyText: (
- This will change the password to all of your Deriv MT5 accounts.
+ This will change the password to all of your {platformTitle} accounts.
),
button: (
@@ -24,13 +29,13 @@ const MT5ChangePasswordScreens = () => {
handleClick('emailVerification')} size='lg' text='Confirm' />
),
- headingText: 'Confirm to change your Deriv MT5 password',
+ headingText: `Confirm to change your ${platformTitle} password`,
icon:
,
},
introScreen: {
- bodyText: 'Use this password to log in to your Deriv MT5 accounts on the desktop, web, and mobile apps.',
+ bodyText: `Use this password to log in to your ${platformTitle} accounts on the desktop, web, and mobile apps.`,
button:
handleClick('confirmationScreen')} size='lg' text='Change password' />,
- headingText: 'Deriv MT5 password',
+ headingText: `${platformTitle} password`,
icon: ,
},
};
@@ -38,7 +43,7 @@ const MT5ChangePasswordScreens = () => {
if (activeScreen === 'emailVerification')
return (
-
+
);
diff --git a/packages/wallets/src/features/cfd/screens/CreatePassword/CreatePassword.tsx b/packages/wallets/src/features/cfd/screens/CreatePassword/CreatePassword.tsx
index 9c7c88cbbd1c..96c033a63a31 100644
--- a/packages/wallets/src/features/cfd/screens/CreatePassword/CreatePassword.tsx
+++ b/packages/wallets/src/features/cfd/screens/CreatePassword/CreatePassword.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import { WalletButton, WalletPasswordField, WalletText } from '../../../../components/Base';
-import { passwordChecker } from '../../../../components/Base/WalletPasswordField/PasswordFieldUtils';
import useDevice from '../../../../hooks/useDevice';
import { TPlatforms } from '../../../../types';
+import { validPassword } from '../../../../utils/passwordUtils';
import { PlatformDetails } from '../../constants';
import './CreatePassword.scss';
@@ -26,7 +26,6 @@ const CreatePassword: React.FC = ({
const { isMobile } = useDevice();
const title = PlatformDetails[platform].title;
- const { score } = passwordChecker(password);
return (
{!isMobile && icon}
@@ -40,7 +39,7 @@ const CreatePassword: React.FC
= ({
{!isMobile && (
{
+type MT5TradeScreenProps = {
+ mt5Account?: THooks.MT5AccountsList;
+};
+
+const MT5TradeScreen: FC = ({ mt5Account }) => {
const { isDesktop } = useDevice();
const { getModalState } = useModal();
- const { data: mt5AccountsList } = useMT5AccountsList();
const { data: dxtradeAccountsList } = useDxtradeAccountsList();
const { data: ctraderAccountsList } = useCtraderAccountsList();
const { data: activeWalletData } = useActiveWalletAccount();
@@ -26,14 +29,14 @@ const MT5TradeScreen = () => {
() => ({
ctrader: ctraderAccountsList,
dxtrade: dxtradeAccountsList,
- mt5: mt5AccountsList,
+ mt5: [mt5Account],
}),
- [ctraderAccountsList, dxtradeAccountsList, mt5AccountsList]
+ [ctraderAccountsList, dxtradeAccountsList, mt5Account]
);
const details = useMemo(() => {
return platform === 'mt5'
- ? platformToAccountsListMapper.mt5?.filter(account => account.market_type === marketType)[0]
+ ? platformToAccountsListMapper.mt5?.filter(account => account?.market_type === marketType)[0]
: platformToAccountsListMapper.dxtrade?.[0];
}, [platform, marketType, platformToAccountsListMapper]);
diff --git a/packages/wallets/src/public/images/ic-mt5-password-updated.svg b/packages/wallets/src/public/images/ic-mt5-password-updated.svg
new file mode 100644
index 000000000000..afa6f50d015b
--- /dev/null
+++ b/packages/wallets/src/public/images/ic-mt5-password-updated.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/wallets/src/utils/passwordUtils.ts b/packages/wallets/src/utils/passwordUtils.ts
new file mode 100644
index 000000000000..67e411b9ec51
--- /dev/null
+++ b/packages/wallets/src/utils/passwordUtils.ts
@@ -0,0 +1,75 @@
+import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
+import { dictionary } from '@zxcvbn-ts/language-common';
+import { passwordErrorMessage, passwordRegex, passwordValues, warningMessages } from '../constants/passwordConstants';
+
+export type Score = 0 | 1 | 2 | 3 | 4;
+export type passwordKeys =
+ | 'common'
+ | 'commonNames'
+ | 'dates'
+ | 'extendedRepeat'
+ | 'keyPattern'
+ | 'namesByThemselves'
+ | 'pwned'
+ | 'recentYears'
+ | 'sequences'
+ | 'similarToCommon'
+ | 'simpleRepeat'
+ | 'straightRow'
+ | 'topHundred'
+ | 'topTen'
+ | 'userInputs'
+ | 'wordByItself';
+
+export const validPassword = (value: string) => passwordRegex.isPasswordValid.test(value);
+
+export const isPasswordValid = (password: string) => {
+ return passwordRegex.isPasswordValid.test(password) && passwordRegex.isLengthValid.test(password);
+};
+
+export const isPasswordModerate = (password: string) => {
+ const hasMoreThanOneSymbol = (password.match(/\W/g) ?? []).length > 1;
+ return (
+ isPasswordValid(password) &&
+ hasMoreThanOneSymbol &&
+ password.length >= passwordValues.minLength &&
+ password.length < passwordValues.longPassword &&
+ passwordRegex.isLengthValid
+ );
+};
+
+export const isPasswordStrong = (password: string) => {
+ const hasMoreThanOneSymbol = (password.match(/\W/g) ?? []).length > 1;
+ return (
+ isPasswordValid(password) &&
+ hasMoreThanOneSymbol &&
+ password.length >= passwordValues.longPassword &&
+ passwordRegex.isLengthValid
+ );
+};
+
+export const calculateScore = (password: string) => {
+ if (password.length === 0) return 0;
+ if (!isPasswordValid(password)) return 1;
+ if (!isPasswordStrong(password) && isPasswordValid(password) && !isPasswordModerate(password)) return 2;
+ if (!isPasswordStrong(password) && isPasswordValid(password) && isPasswordModerate(password)) return 3;
+ if (isPasswordStrong(password)) return 4;
+};
+
+export const validatePassword = (password: string) => {
+ const score = calculateScore(password);
+ let errorMessage = '';
+
+ const options = { dictionary: { ...dictionary } };
+ zxcvbnOptions.setOptions(options);
+
+ const { feedback } = zxcvbn(password);
+ if (!passwordRegex.isLengthValid.test(password)) {
+ errorMessage = passwordErrorMessage.invalidLength;
+ } else if (!isPasswordValid(password)) {
+ errorMessage = passwordErrorMessage.missingCharacter;
+ } else {
+ errorMessage = warningMessages[feedback.warning as passwordKeys] || '';
+ }
+ return { errorMessage, score };
+};