From 614596d6e79d6b4495976fea32ba1e254d67a120 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Fri, 25 Oct 2024 16:34:09 +0800 Subject: [PATCH 1/2] =?UTF-8?q?Revert=20"[WALL]=20aum=20/=20WALL-[4916=20/?= =?UTF-8?q?=204925]=20/=20wallets-implement-default-jurisdict=E2=80=A6"=20?= =?UTF-8?q?(#17324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7b49beb1e663070414523defa8ad506a762dcb7a. --- .../__tests__/useSortedMT5Accounts.spec.ts | 296 ------------------ .../api-v2/src/hooks/useSortedMT5Accounts.ts | 85 +++-- .../CFDPasswordModalTnc.scss | 9 + .../CFDPasswordModalTnc.tsx | 57 ++++ .../__tests__/CFDPasswordModalTnc.spec.tsx} | 33 +- .../components/CFDPasswordModalTnc/index.ts | 1 + .../CFDPlatformsListAccounts.tsx | 13 +- .../ClientVerificationStatusBadge.scss | 9 - .../ClientVerificationStatusBadge.tsx | 76 ----- .../ClientVerificationBadge/index.ts | 1 - .../PlatformStatusBadge.tsx | 4 +- .../src/features/cfd/components/index.ts | 1 - .../wallets/src/features/cfd/constants.tsx | 18 +- .../AddedMT5AccountsList.scss | 3 - .../AddedMT5AccountsList.tsx | 189 ++++++++--- .../__tests__/AddedMT5AccountsList.spec.tsx | 258 ++++++--------- .../__tests__/useAddedMT5Account.spec.ts | 111 ------- .../MT5/AddedMT5AccountsList/hooks/index.ts | 1 - .../hooks/useAddedMT5Account.ts | 61 ---- .../AvailableMT5AccountsList.tsx | 68 +++- .../__test__/AvailableMT5AcountsList.spec.tsx | 121 +++---- .../ClientVerificationModal.scss | 22 -- .../ClientVerificationModal.tsx | 46 --- .../DocumentsList/DocumentsList.scss | 7 - .../DocumentsList/DocumentsList.tsx | 62 ---- .../__tests__/DocumentsList.spec.tsx | 172 ---------- .../components/DocumentTile/DocumentTile.scss | 28 -- .../components/DocumentTile/DocumentTile.tsx | 30 -- .../components/DocumentTile/index.ts | 1 - .../DocumentsList/components/index.ts | 1 - .../components/DocumentsList/index.ts | 1 - .../components/index.ts | 1 - .../modals/ClientVerificationModal/index.ts | 1 - .../JurisdictionModal/JurisdictionModal.tsx | 19 +- .../MT5AccountAdded/MT5AccountAdded.tsx | 4 +- .../MT5PasswordModal/MT5PasswordModal.tsx | 65 ++-- .../modals/MT5TradeModal/MT5TradeModal.tsx | 5 +- .../wallets/src/features/cfd/modals/index.ts | 1 - .../CreatePasswordMT5/CreatePasswordMT5.tsx | 21 +- .../screens/EnterPassword/EnterPassword.tsx | 18 +- .../__test__/EnterPassword.spec.tsx | 41 +-- .../screens/MT5TradeScreen/MT5TradeScreen.tsx | 3 +- .../MT5LicenceMessage/MT5LicenceMessage.scss | 5 - .../MT5LicenceMessage/MT5LicenceMessage.tsx | 53 ---- .../__tests__/MT5LicenceMessage.spec.tsx | 48 --- .../components/MT5LicenceMessage/index.ts | 1 - .../MT5PasswordModalTnc.scss | 5 - .../MT5PasswordModalTnc.tsx | 44 --- .../components/MT5PasswordModalTnc/index.ts | 1 - .../features/cfd/screens/components/index.ts | 2 - packages/wallets/src/features/cfd/types.ts | 23 -- .../wallets/src/features/cfd/utils/index.ts | 1 - .../wallets/src/features/cfd/utils/utils.ts | 28 -- 53 files changed, 590 insertions(+), 1585 deletions(-) delete mode 100644 packages/api-v2/src/hooks/__tests__/useSortedMT5Accounts.spec.ts create mode 100644 packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.scss create mode 100644 packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.tsx rename packages/wallets/src/features/cfd/{screens/components/MT5PasswordModalTnc/__tests__/MT5PasswordModalTnc.spec.tsx => components/CFDPasswordModalTnc/__tests__/CFDPasswordModalTnc.spec.tsx} (54%) create mode 100644 packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/index.ts delete mode 100644 packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.scss delete mode 100644 packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.tsx delete mode 100644 packages/wallets/src/features/cfd/components/ClientVerificationBadge/index.ts delete mode 100644 packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/__tests__/useAddedMT5Account.spec.ts delete mode 100644 packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/index.ts delete mode 100644 packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/useAddedMT5Account.ts delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.scss delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.tsx delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.scss delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.tsx delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/__tests__/DocumentsList.spec.tsx delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.scss delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.tsx delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/index.ts delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/index.ts delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/index.ts delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/index.ts delete mode 100644 packages/wallets/src/features/cfd/modals/ClientVerificationModal/index.ts delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.scss delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.tsx delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/__tests__/MT5LicenceMessage.spec.tsx delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/index.ts delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.scss delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.tsx delete mode 100644 packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/index.ts delete mode 100644 packages/wallets/src/features/cfd/screens/components/index.ts delete mode 100644 packages/wallets/src/features/cfd/types.ts delete mode 100644 packages/wallets/src/features/cfd/utils/index.ts delete mode 100644 packages/wallets/src/features/cfd/utils/utils.ts diff --git a/packages/api-v2/src/hooks/__tests__/useSortedMT5Accounts.spec.ts b/packages/api-v2/src/hooks/__tests__/useSortedMT5Accounts.spec.ts deleted file mode 100644 index c556c4b76780..000000000000 --- a/packages/api-v2/src/hooks/__tests__/useSortedMT5Accounts.spec.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import useActiveAccount from '../useActiveAccount'; -import useAvailableMT5Accounts from '../useAvailableMT5Accounts'; -import useIsEuRegion from '../useIsEuRegion'; -import useMT5AccountsList from '../useMT5AccountsList'; -import useSortedMT5Accounts from '../useSortedMT5Accounts'; -import { cleanup } from '@testing-library/react'; - -jest.mock('../useActiveAccount', () => jest.fn()); -jest.mock('../useAvailableMT5Accounts', () => jest.fn()); -jest.mock('../useIsEuRegion', () => jest.fn()); -jest.mock('../useMT5AccountsList', () => jest.fn()); - -const mockMT5NonEUAvailableAccounts = [ - { - is_default_jurisdiction: 'false', - product: 'standard', - shortcode: 'svg', - }, - { - is_default_jurisdiction: 'false', - product: 'financial', - shortcode: 'svg', - }, - { - is_default_jurisdiction: 'true', - product: 'financial', - shortcode: 'vanuatu', - }, - { - is_default_jurisdiction: 'true', - product: 'stp', - shortcode: 'vanuatu', - }, - { - is_default_jurisdiction: 'true', - product: 'standard', - shortcode: 'vanuatu', - }, - { - is_default_jurisdiction: 'true', - product: 'zero_spread', - shortcode: 'bvi', - }, - { - is_default_jurisdiction: 'true', - product: 'swap_free', - shortcode: 'svg', - }, -]; - -const mockMT5NonEUAddedAccounts = [ - { - is_virtual: false, - landing_company_short: 'vanuatu', - product: 'standard', - }, - { - is_virtual: false, - landing_company_short: 'vanuatu', - product: 'financial', - }, - { - is_virtual: false, - landing_company_short: 'bvi', - product: 'zero_spread', - }, -]; - -const mockMT5EUAvailableAccounts = [ - { - is_default_jurisdiction: 'true', - product: 'financial', - shortcode: 'maltainvest', - }, -]; - -const mockMT5EUAddedAccounts = [ - { - is_virtual: false, - landing_company_short: 'maltainvest', - product: 'financial', - }, -]; - -describe('useSortedMT5Accounts', () => { - beforeEach(() => { - (useActiveAccount as jest.Mock).mockReturnValue({ - data: { is_virtual: false }, - }); - (useIsEuRegion as jest.Mock).mockReturnValue({ - isEUCountry: false, - }); - }); - afterEach(cleanup); - - it('returns non-eu available accounts with default jurisdiction', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5NonEUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: [], - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data).toEqual([ - { - is_added: false, - is_default_jurisdiction: 'true', - product: 'standard', - shortcode: 'vanuatu', - }, - { - is_added: false, - is_default_jurisdiction: 'true', - product: 'financial', - shortcode: 'vanuatu', - }, - { - is_added: false, - is_default_jurisdiction: 'true', - product: 'swap_free', - shortcode: 'svg', - }, - { - is_added: false, - is_default_jurisdiction: 'true', - product: 'zero_spread', - shortcode: 'bvi', - }, - ]); - }); - - it('returns eu available accounts with default jurisdiction', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5EUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: [], - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data).toEqual([ - { - is_added: false, - is_default_jurisdiction: 'true', - product: 'financial', - shortcode: 'maltainvest', - }, - ]); - }); - - it('returns list of non-eu added and available accounts after some accounts are created', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5NonEUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: mockMT5NonEUAddedAccounts, - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data).toEqual([ - { - is_added: true, - is_virtual: false, - landing_company_short: 'vanuatu', - product: 'standard', - }, - { - is_added: true, - is_virtual: false, - landing_company_short: 'vanuatu', - product: 'financial', - }, - { - is_added: false, - is_default_jurisdiction: 'true', - product: 'swap_free', - shortcode: 'svg', - }, - { - is_added: true, - is_virtual: false, - landing_company_short: 'bvi', - product: 'zero_spread', - }, - ]); - }); - - it('returns list of eu added and available accounts after some accounts are created', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5EUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: mockMT5EUAddedAccounts, - }); - (useIsEuRegion as jest.Mock).mockReturnValue({ - isEUCountry: true, - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data).toEqual([ - { - is_added: true, - is_virtual: false, - landing_company_short: 'maltainvest', - product: 'financial', - }, - ]); - }); - - it('returns sorted non-eu accounts list in the correct order', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5NonEUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: [], - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data?.map(account => account.product)).toStrictEqual([ - 'standard', - 'financial', - 'swap_free', - 'zero_spread', - ]); - }); - - it('filters-out available MT5 financial stp account disabling clients to create it', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5NonEUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: [], - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data).not.toContain({ - is_added: false, - is_default_jurisdiction: 'true', - product: 'stp', - shortcode: 'vanuatu', - }); - }); - - it('all available MT5 accounts are created', () => { - (useAvailableMT5Accounts as jest.Mock).mockReturnValue({ - data: mockMT5NonEUAvailableAccounts, - }); - (useMT5AccountsList as jest.Mock).mockReturnValue({ - data: [ - ...mockMT5NonEUAddedAccounts, - { - is_virtual: false, - landing_company_short: 'svg', - product: 'swap_free', - }, - ], - }); - - const { result } = renderHook(() => useSortedMT5Accounts()); - - expect(result.current.data).toEqual([ - { - is_added: true, - is_virtual: false, - landing_company_short: 'vanuatu', - product: 'standard', - }, - { - is_added: true, - is_virtual: false, - landing_company_short: 'vanuatu', - product: 'financial', - }, - { - is_added: true, - is_virtual: false, - landing_company_short: 'svg', - product: 'swap_free', - }, - { - is_added: true, - is_virtual: false, - landing_company_short: 'bvi', - product: 'zero_spread', - }, - ]); - }); -}); diff --git a/packages/api-v2/src/hooks/useSortedMT5Accounts.ts b/packages/api-v2/src/hooks/useSortedMT5Accounts.ts index e85841d39c02..7db93f7d010f 100644 --- a/packages/api-v2/src/hooks/useSortedMT5Accounts.ts +++ b/packages/api-v2/src/hooks/useSortedMT5Accounts.ts @@ -28,42 +28,79 @@ const useSortedMT5Accounts = (regulation?: string) => { : account.landing_company_short !== 'maltainvest') ); - const available_accounts = filtered_available_accounts.filter(available => { - return ( - !filtered_mt5_accounts.find(added => added.product === available.product) && - // @ts-expect-error type for is_default_jurisdiction is unavailable in mt5_login_list and trading_platform_available_accounts - available.is_default_jurisdiction === 'true' && - /* - TODO: remove this check to filter out the creation of `stp` accounts in phase 2 of default jurisdiction - when this product type is separated from `financial` type. - */ - // @ts-expect-error type `stp` is unavailable in the type for trading_platform_available_accounts - available.product !== 'stp' - ); - }); + return filtered_available_accounts?.map(available_account => { + const created_account = filtered_mt5_accounts?.find(account => { + return ( + available_account.market_type === account.market_type && + available_account.shortcode === account.landing_company_short + ); + }); - const combined_accounts = [ - ...available_accounts.map(account => ({ ...account, is_added: false })), - ...filtered_mt5_accounts.map(account => ({ ...account, is_added: true })), - ]; - return combined_accounts; + if (created_account) + return { + ...created_account, + /** Determine if the account is added or not */ + is_added: true, + } as const; + + return { + ...available_account, + /** Determine if the account is added or not */ + is_added: false, + } as const; + }); }, [activeAccount?.is_virtual, all_available_mt5_accounts, isEU, mt5_accounts]); + // // Reduce out the added and non added accounts to make sure only one of each market_type is shown for not added + const filtered_data = useMemo(() => { + if (!modified_data) return; + + const added_accounts = modified_data.filter(account => account.is_added); + const non_added_accounts = modified_data.filter(account => !account.is_added); + + const filtered_non_added_accounts = non_added_accounts.reduce((acc, account) => { + const { market_type, product } = account; + const key = product === 'zero_spread' ? `${market_type}_${product}` : market_type; + + const existing_account = acc.find(acc_account => + acc_account.product === 'zero_spread' + ? `${acc_account.market_type}_${acc_account.product}` === key + : acc_account.market_type === key + ); + const added_account = added_accounts.find(acc_account => + acc_account.product === 'zero_spread' + ? `${acc_account.market_type}_${acc_account.product}` === key + : acc_account.market_type === key + ); + if (existing_account || added_account) return acc; + + return [...acc, account]; + }, [] as typeof non_added_accounts); + + return [...added_accounts, ...filtered_non_added_accounts]; + }, [modified_data]); + + // Sort the data by market_type and product to make sure the order is 'synthetic', 'financial', 'swap_free' and 'zero_spread' const sorted_data = useMemo(() => { - const sorting_order = ['standard', 'financial', 'stp', 'swap_free', 'zero_spread']; + const sorting_order = ['synthetic', 'financial', 'swap_free', 'zero_spread']; - if (!modified_data) return; + if (!filtered_data) return; const sorted_data = sorting_order.reduce((acc, sort_order) => { - const accounts = modified_data.filter(account => account.product === sort_order); + const accounts = filtered_data.filter(account => { + if (account.market_type === 'all') { + return account.product === sort_order; + } + return account.market_type === sort_order; + }); if (!accounts.length) return acc; return [...acc, ...accounts]; - }, [] as typeof modified_data); + }, [] as typeof filtered_data); return sorted_data; - }, [modified_data]); + }, [filtered_data]); - const areAllAccountsCreated = modified_data?.length === all_available_mt5_accounts?.length; + const areAllAccountsCreated = sorted_data?.length === all_available_mt5_accounts?.length; return { data: sorted_data, diff --git a/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.scss b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.scss new file mode 100644 index 000000000000..5f1047baca7e --- /dev/null +++ b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.scss @@ -0,0 +1,9 @@ +.wallets-cfd-modal-tnc { + display: flex; + flex-direction: column; + gap: 1.6rem; + + @include mobile-or-tablet-screen { + margin-top: auto; + } +} diff --git a/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.tsx b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.tsx new file mode 100644 index 000000000000..ed0ccb3c891c --- /dev/null +++ b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/CFDPasswordModalTnc.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { Localize, useTranslations } from '@deriv-com/translations'; +import { Checkbox, InlineMessage, Text, useDevice } from '@deriv-com/ui'; +import { WalletLink } from '../../../../components/Base'; +import { useModal } from '../../../../components/ModalProvider'; +import { THooks, TPlatforms } from '../../../../types'; +import { companyNamesAndUrls, getMarketTypeDetails, PlatformDetails } from '../../constants'; +import './CFDPasswordModalTnc.scss'; + +export type TCFDPasswordModalTncProps = { + checked: boolean; + onChange: () => void; + platform: TPlatforms.All; + product?: THooks.AvailableMT5Accounts['product']; +}; + +const CFDPasswordModalTnc = ({ checked, onChange, platform, product }: TCFDPasswordModalTncProps) => { + const { isDesktop } = useDevice(); + const { getModalState } = useModal(); + const { localize } = useTranslations(); + const selectedJurisdiction = getModalState('selectedJurisdiction'); + const selectedCompany = companyNamesAndUrls[selectedJurisdiction as keyof typeof companyNamesAndUrls]; + const platformTitle = PlatformDetails[platform].title; + const productTitle = getMarketTypeDetails(localize, product).all.title; + + return ( +
+ + + + + + + ]} + i18n_default_text='I confirm and accept {{company}}’s <0>terms and conditions' + values={{ + company: selectedCompany.name, + }} + /> + + } + name='zerospread-checkbox' + onChange={onChange} + /> +
+ ); +}; + +export default CFDPasswordModalTnc; diff --git a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/__tests__/MT5PasswordModalTnc.spec.tsx b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/__tests__/CFDPasswordModalTnc.spec.tsx similarity index 54% rename from packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/__tests__/MT5PasswordModalTnc.spec.tsx rename to packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/__tests__/CFDPasswordModalTnc.spec.tsx index 35e05b9cb1ac..820b654032c3 100644 --- a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/__tests__/MT5PasswordModalTnc.spec.tsx +++ b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/__tests__/CFDPasswordModalTnc.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import MT5PasswordModalTnc, { type TMT5PasswordModalTncProps } from '../MT5PasswordModalTnc'; +import CFDPasswordModalTnc, { type TCFDPasswordModalTncProps } from '../CFDPasswordModalTnc'; jest.mock('@deriv-com/ui', () => ({ Checkbox: jest.fn(({ checked, label, onChange }) => ( @@ -18,44 +18,53 @@ jest.mock('@deriv-com/ui', () => ({ useDevice: jest.fn(() => ({ isDesktop: true })), })); -jest.mock('../../../../../../components/ModalProvider', () => ({ +jest.mock('../../../../../components/ModalProvider', () => ({ useModal: jest.fn(() => ({ getModalState: jest.fn(() => 'bvi'), })), })); -jest.mock('../../../../../../components/Base/WalletLink', () => ({ +jest.mock('../../../../../components/Base/WalletLink', () => ({ WalletLink: ({ children }: { children: React.ReactNode }) => {children}, })); const mockOnChange = jest.fn(); -describe('MT5PasswordModalTnc', () => { - const defaultProps: TMT5PasswordModalTncProps = { +describe('CFDPasswordModalTnc', () => { + const defaultProps: TCFDPasswordModalTncProps = { checked: false, onChange: mockOnChange, + platform: 'mt5', + product: 'zero_spread', }; it('renders correctly', () => { - render(); - expect(screen.getByTestId('dt_wallets_mt5_tnc_checkbox')).toBeInTheDocument(); + render(); + expect(screen.getByTestId('dt_wallets_tnc_checkbox')).toBeInTheDocument(); + expect(screen.getByTestId('dt_wallets_tnc_inline_message')).toBeInTheDocument(); }); it('displays correct text content', () => { - render(); - expect(screen.getByText("I confirm and accept Deriv (BVI) Ltd's")).toBeInTheDocument(); + render(); + expect(screen.getByText(/You are adding your Deriv MT5/i)).toBeInTheDocument(); + expect(screen.getByText(/I confirm and accept/i)).toBeInTheDocument(); }); it('handles checkbox change', () => { - render(); - const checkbox = screen.getByTestId('dt_wallets_mt5_tnc_checkbox'); + render(); + const checkbox = screen.getByTestId('dt_wallets_tnc_checkbox'); fireEvent.click(checkbox); expect(mockOnChange).toHaveBeenCalledTimes(1); }); it('renders the terms and conditions link', () => { - render(); + render(); const link = screen.getByText('terms and conditions'); expect(link).toHaveAttribute('href', 'https://example.com'); }); + + it('uses the correct platform and product titles', () => { + render(); + expect(screen.getByText(/MT5.*Zero Spread/)).toBeInTheDocument(); + }); }); diff --git a/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/index.ts b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/index.ts new file mode 100644 index 000000000000..bea48cf4ec04 --- /dev/null +++ b/packages/wallets/src/features/cfd/components/CFDPasswordModalTnc/index.ts @@ -0,0 +1 @@ +export { default as CFDPasswordModalTnc } from './CFDPasswordModalTnc'; diff --git a/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx b/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx index 0a3ea97a7b3c..68f97577d784 100644 --- a/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx +++ b/packages/wallets/src/features/cfd/components/CFDPlatformsListAccounts/CFDPlatformsListAccounts.tsx @@ -9,7 +9,6 @@ import { AvailableDxtradeAccountsList, AvailableMT5AccountsList, } from '../../flows'; -import { TAddedMT5Account, TAvailableMT5Account } from '../../types'; import './CFDPlatformsListAccounts.scss'; const CFDPlatformsListAccounts: React.FC = () => { @@ -56,18 +55,10 @@ const CFDPlatformsListAccounts: React.FC = () => {
{mt5AccountsList?.map((account, index) => { if (account.is_added) - return ( - - ); + return ; return ( - + ); })} {!isRestricted && ( diff --git a/packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.scss b/packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.scss deleted file mode 100644 index 101e4280e4a1..000000000000 --- a/packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.scss +++ /dev/null @@ -1,9 +0,0 @@ -.wallets-client-verification-badge { - margin-inline-end: auto; - - &__content { - &--underlined { - text-decoration: underline; - } - } -} diff --git a/packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.tsx b/packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.tsx deleted file mode 100644 index 6eea858836d2..000000000000 --- a/packages/wallets/src/features/cfd/components/ClientVerificationBadge/ClientVerificationStatusBadge.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { - LabelPairedCircleCheckCaptionBoldIcon, - LabelPairedCircleExclamationCaptionBoldIcon, - LabelPairedClockThreeCaptionBoldIcon, - LabelPairedTriangleExclamationCaptionBoldIcon, -} from '@deriv/quill-icons'; -import { useTranslations } from '@deriv-com/translations'; -import { Badge, Text, useDevice } from '@deriv-com/ui'; -import { TTranslations } from '../../../../types'; -import './ClientVerificationStatusBadge.scss'; - -type TBadgeColor = React.ComponentProps['color']; - -const getBadgeVariations = (localize: TTranslations['localize']) => { - return { - failed: { - color: 'danger-secondary', - content: localize('Failed'), - icon: , - }, - in_review: { - color: 'warning-secondary', - content: localize('In review'), - icon: , - }, - needs_verification: { - color: 'blue-secondary', - content: localize('Needs verification'), - icon: , - }, - verified: { - color: 'success-secondary', - content: localize('Verified'), - icon: , - }, - }; -}; - -type TClientVerificationBadgeProps = { - onClick?: VoidFunction; - variant: keyof ReturnType; -}; - -const ClientVerificationStatusBadge: React.FC = ({ onClick, variant }) => { - const { localize } = useTranslations(); - const { isDesktop } = useDevice(); - const { color, content, icon } = getBadgeVariations(localize)[variant]; - return ( - { - if (onClick) { - e.stopPropagation(); - onClick(); - } - }} - > - - {content} - - - ); -}; - -export default ClientVerificationStatusBadge; diff --git a/packages/wallets/src/features/cfd/components/ClientVerificationBadge/index.ts b/packages/wallets/src/features/cfd/components/ClientVerificationBadge/index.ts deleted file mode 100644 index 7c8dfffb6e93..000000000000 --- a/packages/wallets/src/features/cfd/components/ClientVerificationBadge/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ClientVerificationStatusBadge } from './ClientVerificationStatusBadge'; diff --git a/packages/wallets/src/features/cfd/components/PlatformStatusBadge/PlatformStatusBadge.tsx b/packages/wallets/src/features/cfd/components/PlatformStatusBadge/PlatformStatusBadge.tsx index eabf13023033..61faecd6b7b0 100644 --- a/packages/wallets/src/features/cfd/components/PlatformStatusBadge/PlatformStatusBadge.tsx +++ b/packages/wallets/src/features/cfd/components/PlatformStatusBadge/PlatformStatusBadge.tsx @@ -3,15 +3,15 @@ import { useTradingPlatformStatus } from '@deriv/api-v2'; import { LegacyWarningIcon } from '@deriv/quill-icons'; import { useTranslations } from '@deriv-com/translations'; import { Badge, Text } from '@deriv-com/ui'; +import { THooks } from '../../../../types'; import type { TAccount } from '../../../cashier/modules/Transfer/types'; import { MT5_ACCOUNT_STATUS, TRADING_PLATFORM_STATUS } from '../../constants'; -import { TAddedMT5Account } from '../../types'; type TProps = { badgeSize: ComponentProps['badgeSize']; cashierAccount?: TAccount; className?: ComponentProps['className']; - mt5Account?: TAddedMT5Account; + mt5Account?: THooks.MT5AccountsList; }; const PlatformStatusBadge: React.FC = ({ badgeSize, cashierAccount, className, mt5Account }) => { diff --git a/packages/wallets/src/features/cfd/components/index.ts b/packages/wallets/src/features/cfd/components/index.ts index 04b24a2fe7fc..ac62e79079c5 100644 --- a/packages/wallets/src/features/cfd/components/index.ts +++ b/packages/wallets/src/features/cfd/components/index.ts @@ -1,5 +1,4 @@ export * from './CFDPlatformsListAccounts'; -export * from './ClientVerificationBadge'; export * from './CompareAccountsCarousel'; export * from './ModalTradeWrapper'; export * from './PlatformStatusBadge'; diff --git a/packages/wallets/src/features/cfd/constants.tsx b/packages/wallets/src/features/cfd/constants.tsx index 07a36a720225..bd51f9972fba 100644 --- a/packages/wallets/src/features/cfd/constants.tsx +++ b/packages/wallets/src/features/cfd/constants.tsx @@ -185,24 +185,12 @@ export const MT5_ACCOUNT_STATUS = { FAILED: 'failed', MIGRATED_WITH_POSITION: 'migrated_with_position', MIGRATED_WITHOUT_POSITION: 'migrated_without_position', - PENDING: 'pending', - UNAVAILABLE: 'unavailable', - UNDER_MAINTENANCE: 'under_maintenance', - // TODO: remove all the statuses below once the KYC statuses are consolidated by BE - // eslint-disable-next-line sort-keys - POA_FAILED: 'poa_failed', - POA_OUTDATED: 'poa_outdated', - PROOF_FAILED: 'proof_failed', - - // eslint-disable-next-line sort-keys NEEDS_VERIFICATION: 'needs_verification', - POA_REQUIRED: 'poa_required', - - // eslint-disable-next-line sort-keys + PENDING: 'pending', POA_PENDING: 'poa_pending', - VERIFICATION_PENDING: 'verification_pending', - // eslint-disable-next-line sort-keys POA_VERIFIED: 'poa_verified', + UNAVAILABLE: 'unavailable', + UNDER_MAINTENANCE: 'under_maintenance', } as const; /** diff --git a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.scss b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.scss index 80a851dae2be..b2a54c389c1a 100644 --- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.scss +++ b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.scss @@ -94,8 +94,5 @@ &--pending { opacity: 0.5; } - &--disabled { - opacity: 0.48; - } } } 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 dab700346633..d6d4f84ae9ba 100644 --- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx +++ b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx @@ -1,67 +1,161 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import classNames from 'classnames'; +import { useJurisdictionStatus, useTradingPlatformStatus } from '@deriv/api-v2'; import { LabelPairedChevronLeftCaptionRegularIcon, LabelPairedChevronRightCaptionRegularIcon, + LabelPairedCircleExclamationLgBoldIcon, + LabelPairedTriangleExclamationMdBoldIcon, } from '@deriv/quill-icons'; -import { useTranslations } from '@deriv-com/translations'; -import { Text } from '@deriv-com/ui'; +import { Localize, useTranslations } from '@deriv-com/translations'; +import { InlineMessage, Text } from '@deriv-com/ui'; import { WalletDisabledAccountModal, WalletStatusBadge } from '../../../../../components'; import { useModal } from '../../../../../components/ModalProvider'; import { TradingAccountCard } from '../../../../../components/TradingAccountCard'; import useIsRtl from '../../../../../hooks/useIsRtl'; -import { ClientVerificationStatusBadge, PlatformStatusBadge } from '../../../components'; -import { MARKET_TYPE, PlatformDetails } from '../../../constants'; -import { ClientVerificationModal, MT5TradeModal, TradingPlatformStatusModal } from '../../../modals'; -import { TAddedMT5Account } from '../../../types'; -import { useAddedMT5Account } from './hooks'; +import { THooks } from '../../../../../types'; +import { PlatformStatusBadge } from '../../../components/PlatformStatusBadge'; +import { + getMarketTypeDetails, + JURISDICTION, + MARKET_TYPE, + MT5_ACCOUNT_STATUS, + PlatformDetails, + TRADING_PLATFORM_STATUS, +} from '../../../constants'; +import { MT5TradeModal, TradingPlatformStatusModal, VerificationFailedModal } from '../../../modals'; import './AddedMT5AccountsList.scss'; type TProps = { - account: TAddedMT5Account; + account: THooks.MT5AccountsList; +}; + +type TTradingAccountJurisdictionStatusInfoProps = { + isAccountDisabled?: boolean; + isJurisdictionFailure?: boolean; + isJurisdictionPending?: boolean; + selectedJurisdiction: THooks.MT5AccountsList['landing_company_short']; +}; + +const TradingAccountJurisdictionStatusInfo: React.FC = ({ + isAccountDisabled, + isJurisdictionFailure, + isJurisdictionPending, + selectedJurisdiction, +}) => { + const { show } = useModal(); + if (isAccountDisabled) { + return ; + } + if (isJurisdictionPending) { + return ( + + } + > + + + + + ); + } + + if (isJurisdictionFailure) { + return ( + + } + > + + + show(, { + defaultRootId: 'wallets_modal_root', + }) + } + />, + ]} + i18n_default_text='Verification failed <0>Why?' + /> + + + ); + } + + return null; }; const AddedMT5AccountsList: React.FC = ({ account }) => { + const [shouldShowDisabledAccountModal, setShouldShowDisabledAccountModal] = useState(false); + const { getVerificationStatus } = useJurisdictionStatus(); const { localize } = useTranslations(); const isRtl = useIsRtl(); - const { accountDetails, isAccountDisabled, isServerMaintenance, kycStatus, showMT5TradeModal, showPlatformStatus } = - useAddedMT5Account(account); - + const jurisdictionStatus = useMemo( + () => getVerificationStatus(account.landing_company_short || JURISDICTION.SVG, account.status), + [account.landing_company_short, account.status, getVerificationStatus] + ); + const { title } = getMarketTypeDetails(localize, account.product)[account.market_type ?? MARKET_TYPE.ALL]; const { show } = useModal(); - const [showDisabledAccountModal, setShowDisabledAccountModal] = useState(false); + const { getPlatformStatus } = useTradingPlatformStatus(); + const platformStatus = getPlatformStatus(account.platform); + + const hasPlatformStatus = + account.status === TRADING_PLATFORM_STATUS.UNAVAILABLE || + account.status === MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE || + platformStatus === TRADING_PLATFORM_STATUS.MAINTENANCE; + + const isServerMaintenance = + platformStatus === TRADING_PLATFORM_STATUS.MAINTENANCE || + account.status === MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE; + const showPlatformStatus = hasPlatformStatus && !(jurisdictionStatus.is_pending || jurisdictionStatus.is_failed); + // @ts-expect-error The enabled property exists, but the api-types are invalid + const isAccountDisabled = !account?.rights?.enabled; + const shouldShowBalance = !(jurisdictionStatus.is_failed || jurisdictionStatus.is_pending) && !isAccountDisabled; return ( <> { if (isAccountDisabled) { - setShowDisabledAccountModal(true); - return; + return setShouldShowDisabledAccountModal(true); } - - if (showPlatformStatus) { + if (hasPlatformStatus) return show(, { defaultRootId: 'wallets_modal_root', }); - } - - if (showMT5TradeModal) { - return show( - , - { defaultRootId: 'wallets_modal_root' } - ); + if (platformStatus === TRADING_PLATFORM_STATUS.ACTIVE) { + return jurisdictionStatus.is_failed + ? show(, { + defaultRootId: 'wallets_modal_root', + }) + : show( + + ); } }} > - {accountDetails.icon} + {getMarketTypeDetails(localize, account.product)[account.market_type || MARKET_TYPE.ALL].icon} = ({ account }) => { })} >
- {accountDetails.title} + {title}
- {!isAccountDisabled && !kycStatus && ( + {shouldShowBalance && ( {account.display_balance} )} + {account.display_login} - {!isAccountDisabled && kycStatus && ( - - show(, { - defaultRootId: 'wallets_modal_root', - }) - } - variant={kycStatus} - /> - )} - {isAccountDisabled && } +
{showPlatformStatus ? ( @@ -105,7 +194,7 @@ const AddedMT5AccountsList: React.FC = ({ account }) => { mt5Account={account} /> ) : ( -
+
{isRtl ? ( ) : ( @@ -118,8 +207,8 @@ const AddedMT5AccountsList: React.FC = ({ account }) => { setShowDisabledAccountModal(false)} + isVisible={shouldShowDisabledAccountModal} + onClose={() => setShouldShowDisabledAccountModal(false)} /> ); diff --git a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/__tests__/AddedMT5AccountsList.spec.tsx b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/__tests__/AddedMT5AccountsList.spec.tsx index 1b5f66f84d52..7157576d8451 100644 --- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/__tests__/AddedMT5AccountsList.spec.tsx +++ b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/__tests__/AddedMT5AccountsList.spec.tsx @@ -1,220 +1,160 @@ import React from 'react'; +import { useJurisdictionStatus, useTradingPlatformStatus } from '@deriv/api-v2'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { ModalProvider } from '../../../../../../components/ModalProvider'; -import { PlatformDetails } from '../../../../constants'; +import { useModal } from '../../../../../../components/ModalProvider'; +import { MT5TradeModal, TradingPlatformStatusModal, VerificationFailedModal } from '../../../../modals'; import AddedMT5AccountsList from '../AddedMT5AccountsList'; -import { useAddedMT5Account } from '../hooks'; -// mock function to check if correct props are passed to the modal components -const mockPropsFn = jest.fn(); - -jest.mock('../hooks', () => ({ - useAddedMT5Account: jest.fn(), -})); - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useHistory: jest.fn(() => ({ - push: jest.fn(), - })), -})); - -jest.mock('../../../../components', () => ({ - ...jest.requireActual('../../../../components'), - ClientVerificationStatusBadge: jest.fn(props => { - mockPropsFn(props.variant); - return ( -
{ - e.stopPropagation(); - props.onClick(); - }} - > - ClientVerificationStatusBadge -
- ); - }), - PlatformStatusBadge: jest.fn(props => { - mockPropsFn(props); - return
PlatformStatusBadge
; - }), -})); - -jest.mock('../../../../modals', () => ({ - ...jest.requireActual('../../../../modals'), - ClientVerificationModal: jest.fn(props => { - mockPropsFn(props); - return
ClientVerificationModal
; - }), - MT5TradeModal: jest.fn(props => { - mockPropsFn(props); - return
MT5TradeModal
; - }), - TradingPlatformStatusModal: jest.fn(props => { - mockPropsFn(props); - return
TradingPlatformStatusModal
; - }), +jest.mock('@deriv/api-v2', () => ({ + useJurisdictionStatus: jest.fn(), + useTradingPlatformStatus: jest.fn(), })); -jest.mock('../../../../../../components', () => ({ - ...jest.requireActual('../../../../../../components'), - WalletDisabledAccountModal: jest.fn(props => { - mockPropsFn(props); - return
WalletDisabledAccountModal
; - }), - WalletStatusBadge: jest.fn(props => { - mockPropsFn(props); - return
WalletStatusBadge
; - }), +jest.mock('../../../../../../components/ModalProvider', () => ({ + useModal: jest.fn(), })); -const mockAccount = { - display_balance: 'USD 1000.00', - display_login: '12345678', - landing_company_short: 'svg', - market_type: 'financial', - platform: 'mt5', - product: 'financial', - status: 'active', -}; - -const mockUseAddedMT5AccountData = { - accountDetails: { - icon: ( - <> - icon-{mockAccount.platform}-{mockAccount.product} - - ), - title: 'Financial', - }, - isServerMaintenance: false, - showClientVerificationModal: false, - showMT5TradeModal: true, - showPlatformStatus: false, -}; - -const wrapper: React.FC = ({ children }) => ( - <> - {children} - -); - describe('AddedMT5AccountsList', () => { - // const mockShow = jest.fn(); + const mockAccount = { + display_balance: 'USD 1000.00', + display_login: '12345678', + landing_company_short: 'svg', + market_type: 'financial', + platform: 'mt5', + product: 'standard', + rights: { enabled: true }, + status: 'active', + }; + + const mockShow = jest.fn(); - beforeAll(() => { - const modalRoot = document.createElement('div'); - modalRoot.setAttribute('id', 'wallets_modal_root'); - document.body.appendChild(modalRoot); - }); beforeEach(() => { - (useAddedMT5Account as jest.Mock).mockReturnValue(mockUseAddedMT5AccountData); + (useJurisdictionStatus as jest.Mock).mockReturnValue({ + getVerificationStatus: jest.fn().mockReturnValue({ is_failed: false, is_pending: false }), + }); + (useTradingPlatformStatus as jest.Mock).mockReturnValue({ + getPlatformStatus: jest.fn().mockReturnValue('active'), + }); + (useModal as jest.Mock).mockReturnValue({ show: mockShow }); }); - it('displays added mt5 account with correct account details', () => { + it('renders added mt5 accounts list with correct account details', () => { // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(, { wrapper }); + render(); - expect(screen.getByText('icon-mt5-financial')).toBeInTheDocument(); expect(screen.getByText('Financial')).toBeInTheDocument(); expect(screen.getByText('USD 1000.00')).toBeInTheDocument(); expect(screen.getByText('12345678')).toBeInTheDocument(); }); - it('displays correct variant of ClientVerificationStatusBadge and renders modal with ClientVerificationModal when clicked on it', async () => { - (useAddedMT5Account as jest.Mock).mockReturnValue({ - ...mockUseAddedMT5AccountData, - kycStatus: 'mockKycStatus', - }); - + it('shows MT5TradeModal when list is clicked and status is active', async () => { // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(, { wrapper }); - - const badge = screen.getByText('ClientVerificationStatusBadge'); + render(); - expect(badge).toBeInTheDocument(); - expect(mockPropsFn).toBeCalledWith('mockKycStatus'); - - userEvent.click(badge); + userEvent.click(screen.getByTestId('dt_wallets_trading_account_card')); await waitFor(() => { - expect(screen.getByText('ClientVerificationModal')).toBeInTheDocument(); + expect(mockShow).toHaveBeenCalledWith( + // @ts-expect-error - since this is a mock, we only need partial properties of the account + + ); }); }); - it('shows the disabled badge when the account MT5 account is disabled', () => { - (useAddedMT5Account as jest.Mock).mockReturnValue({ - ...mockUseAddedMT5AccountData, - isAccountDisabled: true, - isServerMaintenance: true, - showPlatformStatus: true, + it('shows TradingPlatformStatusModal when platform is under maintenance', async () => { + (useTradingPlatformStatus as jest.Mock).mockReturnValue({ + getPlatformStatus: jest.fn().mockReturnValue('maintenance'), }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(, { wrapper }); + render(); - expect(screen.getByText('WalletStatusBadge')).toBeInTheDocument(); - expect(mockPropsFn).toBeCalledWith({ - badgeSize: 'md', - padding: 'tight', - status: 'disabled', + userEvent.click(screen.getByTestId('dt_wallets_trading_account_card')); + + await waitFor(() => { + expect(mockShow).toHaveBeenCalledWith(, { + defaultRootId: 'wallets_modal_root', + }); }); }); - it('shows MT5TradeModal when list is clicked and status is active', async () => { + it('shows VerificationFailedModal when verification has failed', async () => { + (useJurisdictionStatus as jest.Mock).mockReturnValue({ + getVerificationStatus: jest.fn().mockReturnValue({ is_failed: true, is_pending: false }), + }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(, { wrapper }); + render(); userEvent.click(screen.getByTestId('dt_wallets_trading_account_card')); await waitFor(() => { - expect(screen.getByText('MT5TradeModal')).toBeInTheDocument(); - expect(mockPropsFn).toBeCalledWith({ - marketType: mockAccount.market_type, - mt5Account: mockAccount, - platform: PlatformDetails.mt5.platform, + expect(mockShow).toHaveBeenCalledWith(, { + defaultRootId: 'wallets_modal_root', }); }); }); - it('shows TradingPlatformStatusModal when platform is under maintenance', async () => { - (useAddedMT5Account as jest.Mock).mockReturnValue({ - ...mockUseAddedMT5AccountData, - isServerMaintenance: true, - showPlatformStatus: true, + it('displays pending verification message when status is pending', () => { + (useJurisdictionStatus as jest.Mock).mockReturnValue({ + getVerificationStatus: jest.fn().mockReturnValue({ is_failed: false, is_pending: true }), }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(, { wrapper }); + render(); - userEvent.click(screen.getByTestId('dt_wallets_trading_account_card')); + expect(screen.getByText('Pending verification')).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByText('TradingPlatformStatusModal')).toBeInTheDocument(); - expect(mockPropsFn).toBeCalledWith({ - isServerMaintenance: true, - }); + it('displays verification failed message when verification has failed', () => { + (useJurisdictionStatus as jest.Mock).mockReturnValue({ + getVerificationStatus: jest.fn().mockReturnValue({ is_failed: true, is_pending: false }), }); + // @ts-expect-error - since this is a mock, we only need partial properties of the account + render(); + + expect(screen.getByText('Verification failed')).toBeInTheDocument(); + expect(screen.getByText('Why?')).toBeInTheDocument(); }); - it('shows the WalletDisabledAccountModal when a disabled account MT5 account is clicked', async () => { - (useAddedMT5Account as jest.Mock).mockReturnValue({ - ...mockUseAddedMT5AccountData, - isAccountDisabled: true, - isServerMaintenance: true, - showPlatformStatus: true, + it('displays VerificationFailedModal when "Why?" link is clicked', async () => { + (useJurisdictionStatus as jest.Mock).mockReturnValue({ + getVerificationStatus: jest.fn().mockReturnValue({ is_failed: true, is_pending: false }), }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(, { wrapper }); + render(); - await waitFor(() => { - userEvent.click(screen.getByText('WalletStatusBadge')); - }); + const link = screen.getByText('Why?'); + userEvent.click(link); await waitFor(() => { - expect(screen.getByText('WalletDisabledAccountModal')).toBeInTheDocument(); + expect(mockShow).toHaveBeenCalledWith(, { + defaultRootId: 'wallets_modal_root', + }); }); }); + + it('shows WalletStatusBadge when account is disabled', () => { + // @ts-expect-error - since this is a mock, we only need partial properties of the account + render(); + + expect(screen.getByText('Disabled')).toBeInTheDocument(); + }); + + it('opens WalletDisabledAccountModal when disabled account card is clicked', async () => { + // @ts-expect-error - since this is a mock, we only need partial properties of the account + render(); + + const card = screen.getByTestId('dt_wallets_trading_account_card'); + await userEvent.click(card); + + expect(screen.getByText('Contact us via live chat for more details.')).toBeInTheDocument(); + + const closeButton = screen.getByTestId('dt-close-icon'); + expect(closeButton).toBeInTheDocument(); + await userEvent.click(closeButton); + + expect(screen.queryByText('Contact us via live chat for more details.')).not.toBeInTheDocument(); + }); }); diff --git a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/__tests__/useAddedMT5Account.spec.ts b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/__tests__/useAddedMT5Account.spec.ts deleted file mode 100644 index 0d7af35c6215..000000000000 --- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/__tests__/useAddedMT5Account.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { useTradingPlatformStatus } from '@deriv/api-v2'; -import { cleanup } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; -import { getMarketTypeDetails } from '../../../../../constants'; -import { TAddedMT5Account } from '../../../../../types'; -import useAddedMT5Account from '../useAddedMT5Account'; - -jest.mock('@deriv/api-v2', () => ({ - ...jest.requireActual('@deriv/api-v2'), - useTradingPlatformStatus: jest.fn(), -})); - -jest.mock('../../../../../constants', () => ({ - ...jest.requireActual('../../../../../constants'), - getMarketTypeDetails: jest.fn(), -})); - -const mockAccount = { - market_type: 'financial', - product: 'financial', - status: '', -} as TAddedMT5Account; - -describe('useAddedMT5Account', () => { - beforeEach(() => { - (useTradingPlatformStatus as jest.Mock).mockReturnValue({ - getPlatformStatus: jest.fn(), - }); - }); - afterEach(cleanup); - - it('provides correct account details based on the market type', () => { - (getMarketTypeDetails as jest.Mock).mockReturnValue({ financial: 'mock-account-details' }); - - const { result } = renderHook(() => useAddedMT5Account(mockAccount)); - - expect(result.current.accountDetails).toEqual('mock-account-details'); - }); - - it('isServerMaintenance is `true` when trading platform status is `maintenance`', () => { - (useTradingPlatformStatus as jest.Mock).mockReturnValue({ - getPlatformStatus: jest.fn(() => 'maintenance'), - }); - - const { result } = renderHook(() => useAddedMT5Account(mockAccount)); - - expect(result.current.isServerMaintenance).toEqual(true); - }); - - it('isServerMaintenance is `true` when account status is `under_maintenance`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'under_maintenance' })); - - expect(result.current.isServerMaintenance).toEqual(true); - }); - - it('kycStatus is `failed` when status received for account is `proof_failed`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'proof_failed' })); - - expect(result.current.kycStatus).toEqual('failed'); - }); - - it('kycStatus is `failed` when status received for account is `poa_failed`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'poa_failed' })); - - expect(result.current.kycStatus).toEqual('failed'); - }); - - it('kycStatus is `in_review` when status received for account is `verification_pending`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'verification_pending' })); - - expect(result.current.kycStatus).toEqual('in_review'); - }); - - it('kycStatus is `needs_verification` when status received for account is `needs_verification`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'needs_verification' })); - - expect(result.current.kycStatus).toEqual('needs_verification'); - }); - - it('showMT5TradeModal is `true` when platform status is `active`', () => { - (useTradingPlatformStatus as jest.Mock).mockReturnValue({ - getPlatformStatus: jest.fn(() => 'active'), - }); - - const { result } = renderHook(() => useAddedMT5Account(mockAccount)); - - expect(result.current.showMT5TradeModal).toEqual(true); - }); - - it('showPlatformStatus is `true` when account status is `unavailable`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'unavailable' })); - - expect(result.current.showPlatformStatus).toEqual(true); - }); - - it('showPlatformStatus is `true` when account status is `under_maintenance`', () => { - const { result } = renderHook(() => useAddedMT5Account({ ...mockAccount, status: 'under_maintenance' })); - - expect(result.current.showPlatformStatus).toEqual(true); - }); - - it('showPlatformStatus is `true` when trading platform status is `maintenance`', () => { - (useTradingPlatformStatus as jest.Mock).mockReturnValue({ - getPlatformStatus: jest.fn(() => 'maintenance'), - }); - - const { result } = renderHook(() => useAddedMT5Account(mockAccount)); - - expect(result.current.showPlatformStatus).toEqual(true); - }); -}); diff --git a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/index.ts b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/index.ts deleted file mode 100644 index 1ac54db629bf..000000000000 --- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as useAddedMT5Account } from './useAddedMT5Account'; diff --git a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/useAddedMT5Account.ts b/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/useAddedMT5Account.ts deleted file mode 100644 index e02189eab358..000000000000 --- a/packages/wallets/src/features/cfd/flows/MT5/AddedMT5AccountsList/hooks/useAddedMT5Account.ts +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useMemo } from 'react'; -import { useTradingPlatformStatus } from '@deriv/api-v2'; -import { useTranslations } from '@deriv-com/translations'; -import { ClientVerificationStatusBadge } from '../../../../components'; -import { getMarketTypeDetails, MARKET_TYPE, MT5_ACCOUNT_STATUS, TRADING_PLATFORM_STATUS } from '../../../../constants'; -import { TAddedMT5Account } from '../../../../types'; - -type TBadgeVariations = Partial['variant']> | undefined; - -const getClientKycStatus = (status: TAddedMT5Account['status']): TBadgeVariations => { - switch (status) { - case MT5_ACCOUNT_STATUS.POA_FAILED: - case MT5_ACCOUNT_STATUS.PROOF_FAILED: - return 'failed'; - case MT5_ACCOUNT_STATUS.VERIFICATION_PENDING: - case MT5_ACCOUNT_STATUS.POA_PENDING: - return 'in_review'; - case MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION: - case MT5_ACCOUNT_STATUS.POA_REQUIRED: - return 'needs_verification'; - default: - } -}; - -const useAddedMT5Account = (account: TAddedMT5Account) => { - const { localize } = useTranslations(); - - // @ts-expect-error The enabled property exists, but the api-types are invalid - const isAccountDisabled = !account.rights?.enabled; - - const accountDetails = useMemo( - () => getMarketTypeDetails(localize, account.product)[account.market_type ?? MARKET_TYPE.ALL], - [account.market_type, account.product, localize] - ); - - const { getPlatformStatus } = useTradingPlatformStatus(); - const platformStatus = getPlatformStatus(account.platform); - const kycStatus = getClientKycStatus(account.status); - - const isServerMaintenance = - platformStatus === TRADING_PLATFORM_STATUS.MAINTENANCE || - account.status === MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE; - - const showPlatformStatus = - account.status === MT5_ACCOUNT_STATUS.UNAVAILABLE || - account.status === MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE || - platformStatus === TRADING_PLATFORM_STATUS.MAINTENANCE; - - const showMT5TradeModal = platformStatus === TRADING_PLATFORM_STATUS.ACTIVE; - - return { - accountDetails, - isAccountDisabled, - isServerMaintenance, - kycStatus, - showMT5TradeModal, - showPlatformStatus, - }; -}; - -export default useAddedMT5Account; diff --git a/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/AvailableMT5AccountsList.tsx b/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/AvailableMT5AccountsList.tsx index 3d6c53ed996e..310168f8c1a3 100644 --- a/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/AvailableMT5AccountsList.tsx +++ b/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/AvailableMT5AccountsList.tsx @@ -1,22 +1,25 @@ -import React, { useCallback } from 'react'; +import React, { lazy, Suspense, useCallback, useEffect, useState } from 'react'; import { useActiveWalletAccount, useMT5AccountsList, useTradingPlatformStatus } from '@deriv/api-v2'; import { LabelPairedChevronLeftCaptionRegularIcon, LabelPairedChevronRightCaptionRegularIcon, } from '@deriv/quill-icons'; import { Localize, useTranslations } from '@deriv-com/translations'; -import { Text } from '@deriv-com/ui'; +import { Loader, Text } from '@deriv-com/ui'; import { TradingAccountCard } from '../../../../../components'; import { useModal } from '../../../../../components/ModalProvider'; import useIsRtl from '../../../../../hooks/useIsRtl'; +import { THooks } from '../../../../../types'; import { getMarketTypeDetails, MARKET_TYPE, PRODUCT, TRADING_PLATFORM_STATUS } from '../../../constants'; -import { ClientVerificationModal, MT5PasswordModal, TradingPlatformStatusModal } from '../../../modals'; -import { TAvailableMT5Account } from '../../../types'; -import { getClientVerification } from '../../../utils'; +import { JurisdictionModal, MT5PasswordModal, TradingPlatformStatusModal } from '../../../modals'; import './AvailableMT5AccountsList.scss'; +const LazyVerification = lazy( + () => import(/* webpackChunkName: "wallets-client-verification" */ '../../ClientVerification/ClientVerification') +); + type TProps = { - account: TAvailableMT5Account; + account: THooks.AvailableMT5Accounts; }; const AvailableMT5AccountsList: React.FC = ({ account }) => { @@ -28,11 +31,10 @@ const AvailableMT5AccountsList: React.FC = ({ account }) => { const { description, title } = getMarketTypeDetails(localize, account.product)[ account.market_type || MARKET_TYPE.ALL ]; + const [showMt5PasswordModal, setShowMt5PasswordModal] = useState(false); const { data: mt5Accounts } = useMT5AccountsList(); const platformStatus = getPlatformStatus(account.platform); const hasUnavailableAccount = mt5Accounts?.some(account => account.status === 'unavailable'); - const isVirtual = activeWallet?.is_virtual; - const { hasClientKycStatus } = getClientVerification(account); const onButtonClick = useCallback(() => { if (hasUnavailableAccount) return show(); @@ -44,16 +46,58 @@ const AvailableMT5AccountsList: React.FC = ({ account }) => { return show(); case TRADING_PLATFORM_STATUS.ACTIVE: default: - if (!isVirtual && hasClientKycStatus) { - show(); + if (activeWallet?.is_virtual || account.product === PRODUCT.SWAPFREE) { + show( + + ); + } else if (account.product === PRODUCT.ZEROSPREAD) { + show( + }> + { + setShowMt5PasswordModal(true); + }} + selectedJurisdiction={account.shortcode} + /> + + ); } else { - show(); + show(); } setModalState('marketType', account.market_type); setModalState('selectedJurisdiction', account.shortcode); break; } - }, [hasUnavailableAccount, show, platformStatus, isVirtual, hasClientKycStatus, setModalState, account]); + }, [ + hasUnavailableAccount, + show, + platformStatus, + account.platform, + account.market_type, + account.product, + account.shortcode, + activeWallet?.is_virtual, + setModalState, + ]); + + useEffect(() => { + if (showMt5PasswordModal) { + show( + + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showMt5PasswordModal]); return ( diff --git a/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/__test__/AvailableMT5AcountsList.spec.tsx b/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/__test__/AvailableMT5AcountsList.spec.tsx index 8b6ef7708732..c9756151deeb 100644 --- a/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/__test__/AvailableMT5AcountsList.spec.tsx +++ b/packages/wallets/src/features/cfd/flows/MT5/AvailableMT5AccountsList/__test__/AvailableMT5AcountsList.spec.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { useActiveWalletAccount, useMT5AccountsList, useTradingPlatformStatus } from '@deriv/api-v2'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useModal } from '../../../../../../components/ModalProvider'; -import { ClientVerificationModal, MT5PasswordModal, TradingPlatformStatusModal } from '../../../../modals'; +import { JurisdictionModal, MT5PasswordModal, TradingPlatformStatusModal } from '../../../../modals'; import AvailableMT5AccountsList from '../AvailableMT5AccountsList'; jest.mock('@deriv/api-v2', () => ({ @@ -42,38 +42,16 @@ describe('AvailableMT5AccountsList', () => { }); }); - const nonRegulatedAccount = { + const defaultAccount = { market_type: 'synthetic', platform: 'mt5', product: 'swap_free', shortcode: 'svg', }; - const regulatedVerifiedAccount = { - client_kyc_status: { - poi_status: 'verified', - valid_tin: 1, - }, - market_type: 'synthetic', - platform: 'mt5', - product: 'swap_free', - shortcode: 'svg', - }; - - const regulatedUnverifiedAccount = { - client_kyc_status: { - poi_status: 'none', - valid_tin: 0, - }, - market_type: 'synthetic', - platform: 'mt5', - product: 'financial', - shortcode: 'bvi', - }; - it('renders default content for available mt5 account', () => { // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); expect(screen.getByTestId('dt_wallets_trading_account_card')).toBeInTheDocument(); expect(screen.getByText('Standard')).toBeInTheDocument(); @@ -81,13 +59,14 @@ describe('AvailableMT5AccountsList', () => { it('handles button click when platform status is active for real wallet account', () => { // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); - // @ts-expect-error - since this is a mock, we only need partial properties of the account - expect(mockShow).toHaveBeenCalledWith(); + expect(mockShow).toHaveBeenCalledWith( + + ); expect(mockSetModalState).toHaveBeenCalledWith('marketType', 'synthetic'); expect(mockSetModalState).toHaveBeenCalledWith('selectedJurisdiction', 'svg'); }); @@ -98,7 +77,7 @@ describe('AvailableMT5AccountsList', () => { }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); @@ -111,7 +90,7 @@ describe('AvailableMT5AccountsList', () => { getPlatformStatus: jest.fn(() => 'unavailable'), }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); @@ -119,12 +98,25 @@ describe('AvailableMT5AccountsList', () => { expect(mockShow).toHaveBeenCalledWith(); }); + it('shows JurisdictionModal by default when account is undefined', () => { + (useActiveWalletAccount as jest.Mock).mockReturnValue({ + data: undefined, + }); + // @ts-expect-error - since this is a mock, we only need partial properties of the account + render(); + + const button = screen.getByTestId('dt_wallets_trading_account_card'); + userEvent.click(button); + + expect(mockShow).toHaveBeenCalledWith(); + }); + it('shows TradingPlatformStatusModal with isServerMaintenance when platform status is maintenance', () => { (useTradingPlatformStatus as jest.Mock).mockReturnValue({ getPlatformStatus: jest.fn(() => 'maintenance'), }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); @@ -132,49 +124,70 @@ describe('AvailableMT5AccountsList', () => { expect(mockShow).toHaveBeenCalledWith(); }); - it('shows MT5PasswordModal for non-regulated real accounts if client is verified', () => { - (useActiveWalletAccount as jest.Mock).mockReturnValue({ - data: undefined, - }); + it('shows JurisdictionModal when product is neither swap-free nor zero-spread', () => { + const nonSwapAccount = { ...defaultAccount, product: 'ctrader' }; // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); - // @ts-expect-error - since this is a mock, we only need partial properties of the account - expect(mockShow).toHaveBeenCalledWith(); + expect(mockShow).toHaveBeenCalledWith(); }); - it('shows ClientVerificationModal for regulated real accounts if client is unverified', () => { - (useActiveWalletAccount as jest.Mock).mockReturnValue({ - data: { - is_virtual: false, - }, - }); + it('shows ClientVerification when product is zero-spread', async () => { + const zeroSpreadAccount = { ...defaultAccount, product: 'zero_spread' }; // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); + expect(screen.getByText('NEW')).toBeInTheDocument(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); - // @ts-expect-error - since this is a mock, we only need partial properties of the account - expect(mockShow).toHaveBeenCalledWith(); + await waitFor(() => { + expect(mockShow).toHaveBeenCalled(); + }); }); - it('shows MT5PasswordModal for demo accounts for verified clients', () => { + it('handles virtual wallet accounts correctly', () => { (useActiveWalletAccount as jest.Mock).mockReturnValue({ - data: { - is_virtual: true, - }, + data: { is_virtual: true }, }); // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); + render(); const button = screen.getByTestId('dt_wallets_trading_account_card'); userEvent.click(button); + expect(mockShow).toHaveBeenCalledWith( + + ); + }); + + it('shows MT5PasswordModal after ClientVerification completion', async () => { + const zeroSpreadAccount = { ...defaultAccount, product: 'zero_spread' }; // @ts-expect-error - since this is a mock, we only need partial properties of the account - expect(mockShow).toHaveBeenCalledWith(); + render(); + + const button = screen.getByTestId('dt_wallets_trading_account_card'); + userEvent.click(button); + + await waitFor(() => { + expect(mockShow).toHaveBeenCalled(); + }); + + const lastCall = mockShow.mock.calls[mockShow.mock.calls.length - 1][0]; + // eslint-disable-next-line testing-library/no-node-access + const { onCompletion } = lastCall.props.children.props; //required to access the function of lazy-loaded ClientVerification + + act(() => { + onCompletion(); + }); + + await waitFor(() => { + expect(mockShow).toHaveBeenCalledWith( + + ); + }); }); }); diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.scss b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.scss deleted file mode 100644 index 305e021bca3e..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.scss +++ /dev/null @@ -1,22 +0,0 @@ -.wallets-client-verification-modal { - width: 100%; - min-width: 44rem; - height: 100%; - padding: 2.4rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 2.4rem; - - &__description { - max-width: 36rem; - - @include mobile-or-tablet-screen { - max-width: 100%; - } - } - - @include mobile-or-tablet-screen { - min-width: 100%; - } -} diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.tsx b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.tsx deleted file mode 100644 index 856da8dee040..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/ClientVerificationModal.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { DerivLightUploadPoiIcon } from '@deriv/quill-icons'; -import { Localize, useTranslations } from '@deriv-com/translations'; -import { Text, useDevice } from '@deriv-com/ui'; -import { ModalStepWrapper } from '../../../../components'; -import { getMarketTypeDetails, MARKET_TYPE } from '../../constants'; -import { TModifiedMT5Account } from '../../types'; -import { DocumentsList } from './components'; -import './ClientVerificationModal.scss'; - -type TClientVerificationModal = { - account: TModifiedMT5Account; -}; - -const ClientVerificationModal: React.FC = ({ account }) => { - const { localize } = useTranslations(); - const { isMobile } = useDevice(); - const { title } = getMarketTypeDetails(localize, account.product)[account.market_type || MARKET_TYPE.ALL]; - - return ( - -
- - - {account.is_added ? ( - - ) : ( - - )} - - -
-
- ); -}; - -export default ClientVerificationModal; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.scss b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.scss deleted file mode 100644 index 264c1f00b394..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.scss +++ /dev/null @@ -1,7 +0,0 @@ -.wallets-documents-list { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - gap: 1.6rem; -} diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.tsx b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.tsx deleted file mode 100644 index 44d2f2f4cf98..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/DocumentsList.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { useHistory } from 'react-router-dom'; -import { TModifiedMT5Account } from 'src/features/cfd/types'; -import { useTranslations } from '@deriv-com/translations'; -import { ClientVerificationStatusBadge } from '../../../../components'; -import { getClientVerification } from '../../../../utils'; -import { DocumentTile } from './components'; -import './DocumentsList.scss'; - -type TDocumentsListProps = { - account: TModifiedMT5Account; -}; - -type TStatusBadgeProps = Record; - -const statusBadge: TStatusBadgeProps = { - expired: , - none: <>, - pending: , - rejected: , - suspected: , - verified: , -}; - -const DocumentsList: React.FC = ({ account }) => { - const history = useHistory(); - const { localize } = useTranslations(); - const { hasPoaStatus, hasPoiStatus, hasTinStatus, isPoaRequired, isPoiRequired, isTinRequired, statuses } = - getClientVerification(account); - - return ( -
- {hasPoiStatus && ( - history.push('/account/proof-of-identity')} - title={localize('Proof of identity')} - /> - )} - {hasPoaStatus && ( - history.push('/account/proof-of-address')} - title={localize('Proof of address')} - /> - )} - {hasTinStatus && isTinRequired && ( - history.push('/account/personal-details')} - title={localize('Personal details')} - /> - )} -
- ); -}; - -export default DocumentsList; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/__tests__/DocumentsList.spec.tsx b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/__tests__/DocumentsList.spec.tsx deleted file mode 100644 index cec52fd1c3f7..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/__tests__/DocumentsList.spec.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React from 'react'; -import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import DocumentsList from '../DocumentsList'; - -const mockHistoryPush = jest.fn(); - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useHistory: jest.fn(() => ({ - push: mockHistoryPush, - })), -})); - -jest.mock('../../../../../components', () => ({ - ...jest.requireActual('../../../../../components'), - ClientVerificationStatusBadge: jest.fn(({ variant }) => variant), -})); - -jest.mock('../components', () => ({ - ...jest.requireActual('../components'), - DocumentTile: jest.fn(({ badge, isDisabled, onClick, title }) => ( - - )), -})); - -describe('', () => { - it('poi tile is not rendered', () => { - render( - - ); - - expect(screen.queryByText('Proof of identity')).not.toBeInTheDocument(); - }); - - it('poi tile is not rendered', () => { - render( - - ); - - expect(screen.queryByText('Proof of address')).not.toBeInTheDocument(); - }); - - it('personal details tile is not rendered', () => { - render( - - ); - - expect(screen.queryByText('Personal details')).not.toBeInTheDocument(); - }); - - it('on click poi tile redirects to correct page', async () => { - render( - - ); - - const poiTile = screen.getByText('Proof of identity'); - userEvent.click(poiTile); - - await waitFor(() => { - expect(mockHistoryPush).toBeCalledWith('/account/proof-of-identity'); - }); - }); - - it('on click poa tile redirects to correct page', async () => { - render( - - ); - - const poaTile = screen.getByText('Proof of address'); - userEvent.click(poaTile); - - await waitFor(() => { - expect(mockHistoryPush).toBeCalledWith('/account/proof-of-address'); - }); - }); - - it('on click personal details tile redirects to correct page', async () => { - render( - - ); - - const personalDetailsTile = screen.getByText('Personal details'); - userEvent.click(personalDetailsTile); - - await waitFor(() => { - expect(mockHistoryPush).toBeCalledWith('/account/personal-details'); - }); - }); - - it('renders poi tile with correct badge', () => { - render( - - ); - - const poiTile = screen.getByText('Proof of identity'); - - expect(within(poiTile).getByText('verified')).toBeInTheDocument; - }); - - it('renders poa tile with correct badge', () => { - render( - - ); - - const poaTile = screen.getByText('Proof of address'); - - expect(within(poaTile).getByText('verified')).toBeInTheDocument; - }); -}); diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.scss b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.scss deleted file mode 100644 index e1b8aab8fd48..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.scss +++ /dev/null @@ -1,28 +0,0 @@ -.wallets-document-tile { - width: 100%; - height: 5.6rem; - padding-inline: 1.6rem; - border: none; - border-radius: 8px; - background: #f6f7f8; - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - - &:disabled { - cursor: not-allowed; - } - - &__status { - display: flex; - align-items: center; - gap: 0.8rem; - } - - &__chevron { - &--disabled { - fill: #d6d6d6; - } - } -} diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.tsx b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.tsx deleted file mode 100644 index 1a3395392faf..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/DocumentTile.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { LabelPairedChevronRightMdRegularIcon } from '@deriv/quill-icons'; -import { Text } from '@deriv-com/ui'; -import './DocumentTile.scss'; - -type TDocumentTileProps = { - badge?: JSX.Element; - disabled?: boolean; - onClick: VoidFunction; - title: string; -}; - -const DocumentTile: React.FC = ({ badge, disabled, onClick, title }) => { - return ( - - ); -}; - -export default DocumentTile; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/index.ts b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/index.ts deleted file mode 100644 index e6b33e8515a5..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/DocumentTile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as DocumentTile } from './DocumentTile'; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/index.ts b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/index.ts deleted file mode 100644 index 76c67643d998..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DocumentTile'; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/index.ts b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/index.ts deleted file mode 100644 index d23ca16a13b6..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/DocumentsList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as DocumentsList } from './DocumentsList'; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/index.ts b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/index.ts deleted file mode 100644 index a711e93c8860..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DocumentsList'; diff --git a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/index.ts b/packages/wallets/src/features/cfd/modals/ClientVerificationModal/index.ts deleted file mode 100644 index 7625bdeff6b4..000000000000 --- a/packages/wallets/src/features/cfd/modals/ClientVerificationModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ClientVerificationModal } from './ClientVerificationModal'; diff --git a/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx b/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx index 1698de810892..e32bbbd5f544 100644 --- a/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx +++ b/packages/wallets/src/features/cfd/modals/JurisdictionModal/JurisdictionModal.tsx @@ -5,8 +5,10 @@ import { Button, Loader, useDevice } from '@deriv-com/ui'; import { ModalStepWrapper } from '../../../../components/Base'; import { useModal } from '../../../../components/ModalProvider'; import { DynamicLeverageContext } from '../../components/DynamicLeverageContext'; +import { PlatformDetails } from '../../constants'; import { DynamicLeverageScreen, DynamicLeverageTitle } from '../../screens/DynamicLeverage'; import { JurisdictionScreen } from '../../screens/Jurisdiction'; +import { MT5PasswordModal } from '..'; import './JurisdictionModal.scss'; const LazyVerification = lazy( @@ -21,19 +23,32 @@ const JurisdictionModal = () => { const [isDynamicLeverageVisible, setIsDynamicLeverageVisible] = useState(false); const [isCheckBoxChecked, setIsCheckBoxChecked] = useState(false); - const { setModalState, show } = useModal(); + const { getModalState, setModalState, show } = useModal(); const { isLoading } = useAvailableMT5Accounts(); const { isDesktop } = useDevice(); const { localize } = useTranslations(); + const marketType = getModalState('marketType') ?? 'all'; + const platform = getModalState('platform') ?? PlatformDetails.mt5.platform; + const toggleDynamicLeverage = useCallback(() => { setIsDynamicLeverageVisible(!isDynamicLeverageVisible); }, [isDynamicLeverageVisible, setIsDynamicLeverageVisible]); const JurisdictionFlow = () => { + const [showMt5PasswordModal, setShowMt5PasswordModal] = useState(false); + if (selectedJurisdiction === 'svg' || showMt5PasswordModal) { + return ; + } + return ( }> - + { + setShowMt5PasswordModal(true); + }} + selectedJurisdiction={selectedJurisdiction} + /> ); }; diff --git a/packages/wallets/src/features/cfd/modals/MT5AccountAdded/MT5AccountAdded.tsx b/packages/wallets/src/features/cfd/modals/MT5AccountAdded/MT5AccountAdded.tsx index 2552375f2083..076b42a78b02 100644 --- a/packages/wallets/src/features/cfd/modals/MT5AccountAdded/MT5AccountAdded.tsx +++ b/packages/wallets/src/features/cfd/modals/MT5AccountAdded/MT5AccountAdded.tsx @@ -31,8 +31,8 @@ const MT5AccountAdded: FC = ({ account, marketType, platform, product }) const history = useHistory(); const { isDesktop } = useDevice(); - const { localize } = useTranslations(); const { getModalState, hide } = useModal(); + const { localize } = useTranslations(); const addedAccount = mt5Accounts?.find(acc => acc.login === account?.login); @@ -97,7 +97,7 @@ const MT5AccountAdded: FC = ({ account, marketType, platform, product })
); }, - [hide, buttonSize, history, addedAccount?.loginid] + [hide, isDesktop, history, addedAccount?.loginid] ); const renderSuccessDescription = useMemo(() => { diff --git a/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx b/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx index 25cdb52233fd..1624b1d04d7d 100644 --- a/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx +++ b/packages/wallets/src/features/cfd/modals/MT5PasswordModal/MT5PasswordModal.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useAccountStatus, + useActiveWalletAccount, useAvailableMT5Accounts, useCreateMT5Account, useMT5AccountsList, @@ -9,23 +10,25 @@ import { useVerifyEmail, } from '@deriv/api-v2'; import { Localize, useTranslations } from '@deriv-com/translations'; -import { Button, Loader, useDevice } from '@deriv-com/ui'; +import { Button, useDevice, Loader } from '@deriv-com/ui'; import { SentEmailContent, WalletError } from '../../../../components'; import { ModalStepWrapper, ModalWrapper } from '../../../../components/Base'; import { useModal } from '../../../../components/ModalProvider'; +import { THooks, TMarketTypes, TPlatforms } from '../../../../types'; import { platformPasswordResetRedirectLink } from '../../../../utils/cfd'; import { validPassword, validPasswordMT5 } from '../../../../utils/password-validation'; -import { CFD_PLATFORMS, JURISDICTION, MARKET_TYPE, PlatformDetails } from '../../constants'; +import { CFD_PLATFORMS, JURISDICTION, MARKET_TYPE, PlatformDetails, PRODUCT } from '../../constants'; import { CreatePassword, CreatePasswordMT5, EnterPassword, MT5ResetPasswordModal } from '../../screens'; -import { TAvailableMT5Account } from '../../types'; import MT5AccountAdded from '../MT5AccountAdded/MT5AccountAdded'; import { PasswordLimitExceededModal } from '../PasswordLimitExceededModal'; import { MT5PasswordModalFooter, SuccessModalFooter } from './MT5PasswordModalFooters'; import './MT5PasswordModal.scss'; type TProps = { - account: TAvailableMT5Account; isVirtual?: boolean; + marketType: TMarketTypes.SortedMT5Accounts; + platform: TPlatforms.All; + product?: THooks.AvailableMT5Accounts['product']; }; export type TPlatformPasswordChange = { @@ -33,11 +36,8 @@ export type TPlatformPasswordChange = { newPassword: string; }; -const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { - const [isTncChecked, setIsTncChecked] = useState( - // tnc is automatically checked for real SVG accounts and all demo accounts - (account as TAvailableMT5Account).shortcode === JURISDICTION.SVG || isVirtual - ); +const MT5PasswordModal: React.FC = ({ isVirtual, marketType, platform, product }) => { + const [isTncChecked, setIsTncChecked] = useState(!(product === PRODUCT.ZEROSPREAD && !isVirtual)); const { data: createMT5AccountData, error: createMT5AccountError, @@ -52,6 +52,7 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { mutateAsync: tradingPasswordChangeMutateAsync, } = useTradingPlatformPasswordChange(); const { data: accountStatusData, isLoading: accountStatusLoading } = useAccountStatus(); + const { data: activeWalletData } = useActiveWalletAccount(); const { data: availableMT5AccountsData } = useAvailableMT5Accounts(); const { error: emailVerificationError, @@ -68,14 +69,11 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { const [password, setPassword] = useState(''); - const marketType = account.market_type ?? 'synthetic'; - const platform = account.platform; - const product = account.product; - const isMT5PasswordNotSet = accountStatusData?.is_mt5_password_not_set; const hasMT5Account = mt5AccountsData?.find(account => account.login); + const isDemo = activeWalletData?.is_virtual; const { platform: mt5Platform, title: mt5Title } = PlatformDetails.mt5; - const selectedJurisdiction = isVirtual ? JURISDICTION.SVG : getModalState('selectedJurisdiction'); + const selectedJurisdiction = isDemo ? JURISDICTION.SVG : getModalState('selectedJurisdiction'); const isLoading = accountStatusLoading || createMT5AccountLoading || tradingPlatformPasswordChangeLoading; @@ -91,7 +89,7 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { // ================================= const accountType = marketType === MARKET_TYPE.SYNTHETIC ? 'gaming' : marketType; - const categoryAccountType = isVirtual ? 'demo' : accountType; + const categoryAccountType = isDemo ? 'demo' : accountType; if (isMT5PasswordNotSet) { await tradingPasswordChangeMutateAsync({ @@ -109,7 +107,7 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { email: settingsData?.email ?? '', leverage: availableMT5AccountsData?.find(acc => acc.market_type === marketType)?.leverage ?? 500, mainPassword: password, - ...(selectedJurisdiction && !isVirtual ? { company: selectedJurisdiction } : {}), + ...(selectedJurisdiction && !isDemo ? { company: selectedJurisdiction } : {}), ...(marketType === MARKET_TYPE.FINANCIAL && { mt5_account_type: MARKET_TYPE.FINANCIAL }), ...(selectedJurisdiction && (selectedJurisdiction !== JURISDICTION.LABUAN @@ -133,7 +131,7 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { }, [ availableMT5AccountsData, createMT5AccountMutate, - isVirtual, + isDemo, isMT5PasswordNotSet, marketType, mt5Platform, @@ -156,12 +154,12 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { emailVerificationMutate({ type: 'trading_platform_mt5_password_reset', url_parameters: { - redirect_to: platformPasswordResetRedirectLink(CFD_PLATFORMS.MT5, isVirtual), + redirect_to: platformPasswordResetRedirectLink(CFD_PLATFORMS.MT5, activeWalletData?.is_virtual), }, verify_email: email, }); } - }, [email, emailVerificationMutate, isVirtual]); + }, [activeWalletData?.is_virtual, email, emailVerificationMutate]); const onSubmitPasswordChange = useCallback( ({ currentPassword, newPassword }: TPlatformPasswordChange) => { @@ -177,7 +175,7 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { const renderTitle = useCallback(() => { const accountAction = hasMT5Account ? localize('Add') : localize('Create'); - const accountType = isVirtual ? localize('demo') : localize('real'); + const accountType = isDemo ? localize('demo') : localize('real'); return updateMT5Password ? localize('{{mt5Title}} latest password requirements', { mt5Title }) @@ -186,13 +184,13 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { accountType, mt5Title, }); - }, [hasMT5Account, isVirtual, localize, mt5Title, updateMT5Password]); + }, [hasMT5Account, isDemo, localize, mt5Title, updateMT5Password]); const renderFooter = useCallback(() => { if (createMT5AccountSuccess) return (
- +
); @@ -237,7 +235,7 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { }, [ createMT5AccountLoading, createMT5AccountSuccess, - isVirtual, + isDemo, isDesktop, isMT5PasswordNotSet, mt5Title, @@ -265,7 +263,6 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { if (isMT5PasswordNotSet && platform === CFD_PLATFORMS.MT5) return ( = ({ account, isVirtual = false }) => { }} password={password} platform={mt5Platform} + product={product} /> ); @@ -291,10 +289,9 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { return ( setPassword(e.target.value)} @@ -310,18 +307,18 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { ); }, [ isMT5PasswordNotSet, - platform, tradingPlatformPasswordChangeLoading, createMT5AccountLoading, + isTncChecked, onSubmit, password, mt5Platform, - account, - isTncChecked, - isVirtual, - product, updateMT5Password, tradingPasswordChangeError, + platform, + isVirtual, + product, + activeWalletData?.is_virtual, onSubmitPasswordChange, marketType, localize, @@ -331,10 +328,6 @@ const MT5PasswordModal: React.FC = ({ account, isVirtual = false }) => { isLoading, ]); - if (accountStatusLoading) { - return ; - } - if (emailVerificationStatus === 'error') { return ( = ({ account, isVirtual = false }) => { ); } - if (createMT5AccountSuccess) { + if (createMT5AccountSuccess && !isMT5PasswordNotSet) { return ( void; password: string; platform: TPlatforms.All; + product?: THooks.AvailableMT5Accounts['product']; }; const CreatePasswordMT5: React.FC = ({ - account, isLoading, isTncChecked, isVirtual, @@ -32,6 +30,7 @@ const CreatePasswordMT5: React.FC = ({ onTncChange, password, platform, + product, }) => { const { isDesktop } = useDevice(); const { localize } = useTranslations(); @@ -62,9 +61,13 @@ const CreatePasswordMT5: React.FC = ({ onChange={onPasswordChange} password={password} /> - {!isVirtual && } - {!isVirtual && account.shortcode !== 'svg' && ( - + {product === PRODUCT.ZEROSPREAD && !isVirtual && ( + )}
diff --git a/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx b/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx index 2c270fbd048d..d48df9ae70e7 100644 --- a/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx +++ b/packages/wallets/src/features/cfd/screens/EnterPassword/EnterPassword.tsx @@ -5,14 +5,11 @@ 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 { CFD_PLATFORMS, getMarketTypeDetails, JURISDICTION, PlatformDetails } from '../../constants'; -import { TAvailableMT5Account } from '../../types'; -import { MT5LicenceMessage, MT5PasswordModalTnc } from '../components'; +import { CFDPasswordModalTnc } from '../../components/CFDPasswordModalTnc'; +import { CFD_PLATFORMS, getMarketTypeDetails, PlatformDetails, PRODUCT } from '../../constants'; import './EnterPassword.scss'; -// Note: this component requires a proper refactor to remove props for keys available under the `account` prop type TProps = { - account?: TAvailableMT5Account; isForgotPasswordLoading?: boolean; isLoading?: boolean; isTncChecked?: boolean; @@ -31,7 +28,6 @@ type TProps = { }; const EnterPassword: React.FC = ({ - account, isForgotPasswordLoading, isLoading, isTncChecked = true, @@ -104,9 +100,13 @@ const EnterPassword: React.FC = ({ {passwordErrorHints} )} - {account && !isVirtual && } - {account && account.shortcode !== JURISDICTION.SVG && platform === CFD_PLATFORMS.MT5 && !isVirtual && ( - onTncChange?.()} /> + {product === PRODUCT.ZEROSPREAD && !isVirtual && ( + onTncChange?.()} + platform={platform} + product={product} + /> )}
{isDesktop && ( diff --git a/packages/wallets/src/features/cfd/screens/EnterPassword/__test__/EnterPassword.spec.tsx b/packages/wallets/src/features/cfd/screens/EnterPassword/__test__/EnterPassword.spec.tsx index c5fe094d0dda..fbdb92674752 100644 --- a/packages/wallets/src/features/cfd/screens/EnterPassword/__test__/EnterPassword.spec.tsx +++ b/packages/wallets/src/features/cfd/screens/EnterPassword/__test__/EnterPassword.spec.tsx @@ -1,18 +1,12 @@ import React from 'react'; import { useActiveWalletAccount } from '@deriv/api-v2'; -import { cleanup, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MARKET_TYPE, PlatformDetails } from '../../../constants'; import EnterPassword from '../EnterPassword'; jest.mock('@deriv/api-v2'); -jest.mock('../../components', () => ({ - ...jest.requireActual('../../components'), - MT5LicenceMessage: jest.fn(() =>
MT5LicenceMessage
), - MT5PasswordModalTnc: jest.fn(() =>
MT5PasswordModalTnc
), -})); - describe('EnterPassword', () => { const mockUseActiveWalletAccount = useActiveWalletAccount as jest.Mock; @@ -20,8 +14,6 @@ describe('EnterPassword', () => { mockUseActiveWalletAccount.mockReturnValue({ data: { is_virtual: false } }); }); - afterEach(cleanup); - const title = `Enter your ${PlatformDetails.mt5.title} password`; const shortPassword = 'abcd'; const validPassword = 'Abcd1234!'; @@ -90,12 +82,6 @@ describe('EnterPassword', () => { expect(addAccountButton).toBeDisabled(); }); - it('disables the "Add account" button when tnc is not checked', () => { - renderComponent({ isTncChecked: false }); - const addAccountButton = screen.getByRole('button', { name: 'Add account' }); - expect(addAccountButton).toBeDisabled(); - }); - it('shows password error hints when passwordError is true', () => { renderComponent({ passwordError: true }); expect( @@ -104,29 +90,4 @@ describe('EnterPassword', () => { ) ).toBeInTheDocument(); }); - - it('shows the mt5 licence message component for real MT5 accounts', () => { - renderComponent({ account: { shortcode: 'svg' } }); - - expect(screen.getByText('MT5LicenceMessage')).toBeInTheDocument(); - }); - - it('hides the mt5 licence message for virtual accounts', () => { - mockUseActiveWalletAccount.mockReturnValue({ data: { is_virtual: true } }); - renderComponent(); - - expect(screen.queryByText('MT5LicenceMessage')).not.toBeInTheDocument(); - }); - - it('shows the mt5 tnc checkbox for regulated real accounts', () => { - renderComponent({ account: { shortcode: 'bvi' } }); - - expect(screen.getByText('MT5PasswordModalTnc')).toBeInTheDocument(); - }); - - it('hides the mt5 tnc checkbox for non-regulated real accounts', () => { - renderComponent({ account: { shortcode: 'svg' } }); - - expect(screen.queryByText('MT5PasswordModalTnc')).not.toBeInTheDocument(); - }); }); diff --git a/packages/wallets/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx b/packages/wallets/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx index f5efb6cc09e9..9c03bccd69c2 100644 --- a/packages/wallets/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx +++ b/packages/wallets/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx @@ -8,7 +8,6 @@ import { WalletBadge, WalletListCardBadge } from '../../../../components'; import { useModal } from '../../../../components/ModalProvider'; import { THooks } from '../../../../types'; import { CFD_PLATFORMS, getMarketTypeDetails, getServiceMaintenanceMessages, PlatformDetails } from '../../constants'; -import { TAddedMT5Account } from '../../types'; import MT5DesktopRedirectOption from './MT5TradeLink/MT5DesktopRedirectOption'; import MT5MobileRedirectOption from './MT5TradeLink/MT5MobileRedirectOption'; import { MT5TradeDetailsItem } from './MT5TradeDetailsItem'; @@ -16,7 +15,7 @@ import { MT5TradeLink } from './MT5TradeLink'; import './MT5TradeScreen.scss'; type MT5TradeScreenProps = { - mt5Account?: TAddedMT5Account; + mt5Account?: THooks.MT5AccountsList; }; const MT5TradeScreen: FC = ({ mt5Account }) => { diff --git a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.scss b/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.scss deleted file mode 100644 index 10eefbe6dcd5..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.scss +++ /dev/null @@ -1,5 +0,0 @@ -.wallets-mt5-licence-message { - @include mobile-or-tablet-screen { - margin-top: auto; - } -} diff --git a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.tsx b/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.tsx deleted file mode 100644 index 8b68d3233535..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/MT5LicenceMessage.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { Localize, useTranslations } from '@deriv-com/translations'; -import { InlineMessage, Text, useDevice } from '@deriv-com/ui'; -import { getMarketTypeDetails, JURISDICTION, MARKET_TYPE, PlatformDetails } from '../../../constants'; -import { TAvailableMT5Account } from '../../../types'; -import './MT5LicenceMessage.scss'; - -type TMT5LicenseMessageProps = { - account: TAvailableMT5Account; -}; - -const MT5LicenseMessage: React.FC = ({ account }) => { - const { isDesktop } = useDevice(); - const { localize } = useTranslations(); - const isSvg = account.shortcode === JURISDICTION.SVG; - - return ( - - - {isSvg ? ( - // TODO: remove this hardcoded logic for the company number for SVG once BE provides company_number key for non-regulated accounts - - ) : ( - - )} - - - ); -}; - -export default MT5LicenseMessage; diff --git a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/__tests__/MT5LicenceMessage.spec.tsx b/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/__tests__/MT5LicenceMessage.spec.tsx deleted file mode 100644 index d0d40201f9ca..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/__tests__/MT5LicenceMessage.spec.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import MT5LicenseMessage from '../MT5LicenceMessage'; - -jest.mock('@deriv-com/ui', () => ({ - ...jest.requireActual('@deriv-com/ui'), - useDevice: jest.fn(() => ({ isDesktop: false })), -})); - -const mockRegulatedAccount = { - licence_number: 'mock_licence_number', - market_type: 'financial', - name: 'mock_company_name', - product: 'financial', - regulatory_authority: 'mock_regulatory_authority', - shortcode: 'bvi', -}; - -const mockNonRegulatedAccount = { - market_type: 'all', - name: 'mock_company_name', - product: 'swap_free', - shortcode: 'svg', -}; - -describe('', () => { - it('displays correct message for regulated account', () => { - // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); - - expect( - screen.getByText( - 'You are adding your Deriv MT5 Financial account under mock_company_name, regulated by the mock_regulatory_authority (licence no. mock_licence_number).' - ) - ); - }); - - it('displays correct message for non-regulated account', () => { - // @ts-expect-error - since this is a mock, we only need partial properties of the account - render(); - - expect( - screen.getByText( - 'You are adding your Deriv MT5 Swap-Free account under mock_company_name (company no. 273 LLC 2020).' - ) - ); - }); -}); diff --git a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/index.ts b/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/index.ts deleted file mode 100644 index c939c664e13b..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5LicenceMessage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MT5LicenceMessage } from './MT5LicenceMessage'; diff --git a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.scss b/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.scss deleted file mode 100644 index b6fb09376e7e..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.scss +++ /dev/null @@ -1,5 +0,0 @@ -.wallets-mt5-modal-tnc { - display: flex; - flex-direction: column; - gap: 1.6rem; -} diff --git a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.tsx b/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.tsx deleted file mode 100644 index 507b9bb26e54..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/MT5PasswordModalTnc.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { Localize } from '@deriv-com/translations'; -import { Checkbox, Text, useDevice } from '@deriv-com/ui'; -import { WalletLink } from '../../../../../components/Base'; -import { useModal } from '../../../../../components/ModalProvider'; -import { companyNamesAndUrls } from '../../../constants'; -import './MT5PasswordModalTnc.scss'; - -export type TMT5PasswordModalTncProps = { - checked: boolean; - onChange: () => void; -}; - -const MT5PasswordModalTnc = ({ checked, onChange }: TMT5PasswordModalTncProps) => { - const { isDesktop } = useDevice(); - const { getModalState } = useModal(); - const selectedJurisdiction = getModalState('selectedJurisdiction'); - // TODO: replace the company name with the information provided by the trading_platform_account_available API's BE response - const selectedCompany = companyNamesAndUrls[selectedJurisdiction as keyof typeof companyNamesAndUrls]; - - return ( -
- - ]} - i18n_default_text="I confirm and accept {{company}}'s <0>terms and conditions" - values={{ - company: selectedCompany.name, - }} - /> - - } - name='mt5-tnc-checkbox' - onChange={onChange} - /> -
- ); -}; - -export default MT5PasswordModalTnc; diff --git a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/index.ts b/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/index.ts deleted file mode 100644 index fe47194df6f4..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/MT5PasswordModalTnc/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as MT5PasswordModalTnc } from './MT5PasswordModalTnc'; diff --git a/packages/wallets/src/features/cfd/screens/components/index.ts b/packages/wallets/src/features/cfd/screens/components/index.ts deleted file mode 100644 index 14f20a9a7f16..000000000000 --- a/packages/wallets/src/features/cfd/screens/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './MT5LicenceMessage'; -export * from './MT5PasswordModalTnc'; diff --git a/packages/wallets/src/features/cfd/types.ts b/packages/wallets/src/features/cfd/types.ts deleted file mode 100644 index 32aaf0d18d0d..000000000000 --- a/packages/wallets/src/features/cfd/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable camelcase */ -/* - TODO: Remove these types once API types for client_kyc_status is available for mt5_login_list and trading_platform_available_accounts from BE -*/ -import { THooks } from '../../types'; - -type TStatuses = 'expired' | 'none' | 'pending' | 'rejected' | 'suspected' | 'verified'; - -export type TModifiedMT5Account = THooks.SortedMT5Accounts & { - client_kyc_status: { - poa_status: TStatuses; - poi_status: TStatuses; - valid_tin: 0 | 1; - }; - licence_number: string; - regulatory_authority: string; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ObjectWithKeyInUnion = T extends any ? (K extends keyof T ? T : never) : never; - -export type TAvailableMT5Account = ObjectWithKeyInUnion; -export type TAddedMT5Account = ObjectWithKeyInUnion; diff --git a/packages/wallets/src/features/cfd/utils/index.ts b/packages/wallets/src/features/cfd/utils/index.ts deleted file mode 100644 index 04bca77e0dec..000000000000 --- a/packages/wallets/src/features/cfd/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './utils'; diff --git a/packages/wallets/src/features/cfd/utils/utils.ts b/packages/wallets/src/features/cfd/utils/utils.ts deleted file mode 100644 index e650e20011e0..000000000000 --- a/packages/wallets/src/features/cfd/utils/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TAddedMT5Account, TAvailableMT5Account } from '../types'; - -const requiredDocumentStatuses = ['expired', 'none', 'rejected', 'suspected']; - -export const getClientVerification = (account: TAddedMT5Account | TAvailableMT5Account) => { - const hasClientKycStatus = 'client_kyc_status' in account; - const documentStatuses = account.client_kyc_status; - - const hasPoiStatus = hasClientKycStatus && 'poi_status' in documentStatuses; - const hasPoaStatus = hasClientKycStatus && 'poa_status' in documentStatuses; - const hasTinStatus = hasClientKycStatus && 'valid_tin' in documentStatuses; - - const isPoiRequired = hasPoiStatus && requiredDocumentStatuses.includes(documentStatuses.poi_status); - const isPoaRequired = hasPoaStatus && requiredDocumentStatuses.includes(documentStatuses.poa_status); - const isTinRequired = hasTinStatus && !documentStatuses.valid_tin; - - return { - hasClientKycStatus, - hasPoaStatus, - hasPoiStatus, - hasTinStatus, - isPoaRequired, - isPoiRequired, - isTinRequired, - isVerificationRequired: isPoiRequired || isPoaRequired || isTinRequired, - statuses: documentStatuses, - }; -}; From 6fb892bf15a306e0002bb734de12ad39bbbad213 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Fri, 25 Oct 2024 16:34:53 +0800 Subject: [PATCH 2/2] Revert "[COJ][TRAH-3427]/Dhruv/amina mt5 defaulting jurisdiction (#16540)" (#17325) This reverts commit b723364548e952632128c1b11b57762489fce727. --- package-lock.json | 9 +- packages/account/build/webpack.config.js | 1 + packages/account/package.json | 2 +- .../__tests__/needs-review-spec.tsx | 51 ++ .../poa/status/needs-review/index.ts | 3 + .../poa/status/needs-review/needs-review.tsx | 37 ++ .../submitted/__tests__/submitted.spec.tsx | 8 +- .../poa/status/submitted/submitted.tsx | 22 +- .../poi-poa-docs-submitted.tsx | 73 +++ .../__tests__/idv-submit-complete.spec.tsx | 28 +- .../idv-submit-complete.tsx | 10 +- .../status/unsupported/detail-component.tsx | 4 +- .../poi/status/unsupported/unsupported.tsx | 1 - .../__tests__/upload-complete.spec.tsx | 24 +- .../upload-complete/upload-complete.tsx | 23 +- .../__tests__/route-button.spec.tsx | 31 -- .../src/Components/route-button/index.ts | 3 - .../Components/route-button/route-button.tsx | 20 - .../__test__/get-status-badge-config.spec.tsx | 121 ++++- .../Configs/get-mt5-status-badge-config.tsx | 57 --- .../src/Configs/get-status-badge-config.tsx | 98 +++- .../Verification/Helpers/verification.js | 9 +- .../proof-of-address-container.tsx | 3 +- .../proof-of-identity-container-for-mt5.jsx | 78 +++ .../proof-of-identity-submission-for-mt5.jsx | 193 ++++++++ packages/account/src/Styles/account.scss | 5 + packages/appstore/package.json | 3 +- .../components/cfds-listing/cfds-listing.scss | 227 ++++++++- .../src/components/cfds-listing/index.tsx | 105 ++-- .../__tests__/trading-app-card.spec.tsx | 4 +- .../containers/trading-app-card.tsx | 59 +-- .../__tests__/real-account-switcher.spec.tsx | 6 +- .../real/real-account-switcher.tsx | 32 +- .../failed-verification-modal.scss | 39 ++ .../failed-verification-modal.tsx | 150 ++++++ .../failed-veriification-modal/index.ts | 3 + .../src/components/modals/modal-manager.tsx | 45 +- .../verification-docs-list-modal/ListItem.tsx | 97 ---- .../__tests__/Listitem.spec.tsx | 117 ----- .../verification-docs-list-modal.spec.tsx | 132 ----- .../verification-docs-list-modal/index.ts | 3 - .../verification-docs-list-modal.scss | 55 --- .../verification-docs-list-modal.tsx | 107 ----- packages/appstore/src/types/common.types.ts | 2 +- packages/bot-web-ui/package.json | 2 +- .../form-wrappers/upgraded-qs-v2/stepper.scss | 13 +- .../strategy-template-picker.scss | 2 +- .../cashier-locked/cashier-locked.tsx | 4 +- .../account-transfer-form.tsx | 4 +- .../src/stores/account-transfer-store.ts | 1 - packages/cfd/build/webpack.config.js | 2 + .../src/Components/__tests__/cfd-poa.spec.tsx | 151 ++++++ .../src/Components/__tests__/cfd-poi.spec.js | 106 ++++ .../cfd/src/Components/cfd-account-card.tsx | 8 +- packages/cfd/src/Components/cfd-poa.tsx | 58 +++ packages/cfd/src/Components/cfd-poi.tsx | 49 ++ packages/cfd/src/Components/props.types.ts | 35 +- .../jurisdiction-bvi-contents.ts | 101 ++++ .../jurisdiction-contents.ts | 22 + .../jurisdiction-labuan-contents.ts | 76 +++ .../jurisdiction-svg-contents.ts | 135 ++++++ .../jurisdiction-vanuatu-contents.ts | 101 ++++ .../jurisdiction-verification-contents.ts | 45 ++ .../jurisdiction_maltainvest_contents.ts | 96 ++++ ...-financial-stp-real-account-signup.spec.js | 301 ++++++++++++ .../__tests__/cfd-password-modal.spec.js | 12 +- .../__tests__/dmt5-trade-modal.spec.tsx | 4 +- .../cfd-compare-accounts-card.spec.tsx | 15 + .../cfd-compare-accounts-description.spec.tsx | 189 ++++---- ...d-compare-accounts-platform-label.spec.tsx | 7 +- .../cfd-compare-accounts-title-icon.spec.tsx | 173 ++++--- .../__tests__/cfd-compare-accounts.spec.tsx | 1 + ...cfd-instruments-label-highlighted.spec.tsx | 27 +- .../instruments-icon-with-label.spec.tsx | 1 + .../cfd-compare-accounts-card.tsx | 7 + .../cfd-compare-accounts-description.tsx | 59 ++- .../cfd-compare-accounts-platform-label.tsx | 2 - .../cfd-compare-accounts-title-icon.tsx | 17 +- .../cfd-compare-accounts.scss | 29 +- .../cfd-compare-accounts.tsx | 31 +- .../cfd-instruments-label-highlighted.tsx | 33 +- .../instruments-icon-with-label.tsx | 21 +- .../src/Containers/cfd-dbvi-onboarding.tsx | 165 +++++++ .../cfd-enter-password-modal-title.tsx | 49 -- .../cfd-financial-stp-real-account-signup.tsx | 229 +++++++++ .../src/Containers/cfd-password-change.tsx | 3 - .../Containers/cfd-password-modal-info.tsx | 46 +- .../Containers/cfd-password-modal-title.tsx | 68 +++ .../src/Containers/cfd-password-modal-tnc.tsx | 51 +- .../cfd/src/Containers/cfd-password-modal.tsx | 451 ++++++++++++------ .../Containers/cfd-password-success-icon.tsx | 61 --- .../cfd-personal-details-container.tsx | 124 +++++ .../cfd/src/Containers/dmt5-trade-modal.tsx | 13 +- .../src/Containers/jurisdiction-checkbox.tsx | 48 -- .../__test__/jurisdiction-card-back.spec.tsx | 95 ++++ .../__test__/jurisdiction-card-front.spec.tsx | 94 ++++ .../jurisdiction-card-section.spec.tsx | 128 +++++ .../__test__/jurisdiction-card.spec.tsx | 241 ++++++++++ ...urisdiction-clickable-description.spec.tsx | 54 +++ .../jurisdiction-modal-checkbox.spec.tsx | 94 ++++ .../jurisdiction-modal-content.spec.tsx | 394 +++++++++++++++ .../jurisdiction-modal-foot-note.spec.tsx | 231 +++++++++ .../jurisdiction-modal-title.spec.tsx | 75 +++ .../__test__/jurisdiction-modal.spec.tsx | 116 +++++ .../jurisdiction-title-indicator.spec.tsx | 426 +++++++++++++++++ .../jurisdiction-card-back.tsx | 73 +++ .../jurisdiction-card-front.tsx | 82 ++++ .../jurisdiction-card-section.tsx | 47 ++ .../jurisdiction-modal/jurisdiction-card.tsx | 79 +++ .../jurisdiction-clickable-description.tsx | 33 ++ .../jurisdiction-modal-checkbox.tsx | 66 +++ .../jurisdiction-modal-content-wrapper.tsx | 251 ++++++++++ .../jurisdiction-modal-content.tsx | 82 ++++ .../jurisdiction-modal-foot-note.tsx | 101 ++++ .../jurisdiction-modal-title.tsx | 50 ++ .../jurisdiction-modal/jurisdiction-modal.tsx | 98 ++++ .../jurisdiction-title-indicator.tsx | 105 ++++ .../__tests__/mt5-create-password.spec.tsx | 174 ------- .../mt5-create-password.scss | 128 ----- .../mt5-create-password.tsx | 152 ------ packages/cfd/src/Containers/props.types.ts | 37 +- packages/cfd/src/Helpers/cfd-config.ts | 11 +- .../src/Helpers/compare-accounts-config.ts | 282 +++++++---- .../cfd/src/Stores/Modules/CFD/cfd-store.js | 41 +- packages/cfd/src/sass/cfd-dashboard.scss | 197 ++++++++ packages/cfd/src/sass/cfd.scss | 54 ++- packages/cfd/src/types/cfd-store.types.ts | 10 +- .../cfd-compare-accounts-carousel.scss | 3 +- .../components/src/components/icon/icons.js | 4 - .../src/components/icon/mt5/ic-mt5-failed.svg | 1 - .../components/icon/mt5/ic-mt5-pending.svg | 1 - .../components/icon/mt5/ic-mt5-success.svg | 1 - .../icon/mt5/ic-mt5-verification.svg | 1 - .../components/status-badge/status-badge.scss | 60 +-- .../components/status-badge/status-badge.tsx | 6 +- packages/core/package.json | 2 +- .../curreny-selection-modal.tsx | 12 +- packages/core/src/Constants/cfd-text.js | 2 +- packages/core/src/Stores/client-store.js | 13 +- packages/core/src/Stores/traders-hub-store.js | 156 +++--- packages/core/src/Stores/ui-store.js | 7 + .../__tests__/useGetMFAccountStatus.spec.tsx | 21 +- .../hooks/src/__tests__/useGetStatus.spec.tsx | 190 -------- .../useIsSelectedMT5AccountCreated.spec.tsx | 118 ----- .../src/__tests__/useMFAccountStatus.spec.tsx | 14 +- packages/hooks/src/index.ts | 2 - packages/hooks/src/useGetMFAccountStatus.ts | 45 +- packages/hooks/src/useGetStatus.ts | 31 -- .../src/useIsSelectedMT5AccountCreated.ts | 62 --- packages/hooks/src/useMFAccountStatus.ts | 12 +- packages/shared/package.json | 2 +- packages/shared/src/styles/constants.scss | 6 - packages/shared/src/styles/themes.scss | 10 - packages/shared/src/utils/cfd/cfd.ts | 35 +- .../src/utils/constants/auth-status-codes.ts | 6 - .../utils/constants/jurisdictions-config.ts | 12 +- .../utils/constants/mt5-login-list-status.ts | 2 - packages/shared/src/utils/types.ts | 6 +- packages/stores/src/mockStore.ts | 9 +- packages/stores/types.ts | 57 +-- packages/trader/package.json | 2 +- packages/wallets/package.json | 2 +- 162 files changed, 7527 insertions(+), 2835 deletions(-) create mode 100644 packages/account/src/Components/poa/status/needs-review/__tests__/needs-review-spec.tsx create mode 100644 packages/account/src/Components/poa/status/needs-review/index.ts create mode 100644 packages/account/src/Components/poa/status/needs-review/needs-review.tsx create mode 100644 packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx delete mode 100644 packages/account/src/Components/route-button/__tests__/route-button.spec.tsx delete mode 100644 packages/account/src/Components/route-button/index.ts delete mode 100644 packages/account/src/Components/route-button/route-button.tsx delete mode 100644 packages/account/src/Configs/get-mt5-status-badge-config.tsx create mode 100644 packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx create mode 100644 packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx create mode 100644 packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.scss create mode 100644 packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx create mode 100644 packages/appstore/src/components/modals/failed-veriification-modal/index.ts delete mode 100644 packages/appstore/src/components/modals/verification-docs-list-modal/ListItem.tsx delete mode 100644 packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/Listitem.spec.tsx delete mode 100644 packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/verification-docs-list-modal.spec.tsx delete mode 100644 packages/appstore/src/components/modals/verification-docs-list-modal/index.ts delete mode 100644 packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.scss delete mode 100644 packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.tsx create mode 100644 packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx create mode 100644 packages/cfd/src/Components/__tests__/cfd-poi.spec.js create mode 100644 packages/cfd/src/Components/cfd-poa.tsx create mode 100644 packages/cfd/src/Components/cfd-poi.tsx create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-bvi-contents.ts create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-contents.ts create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-labuan-contents.ts create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-svg-contents.ts create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-vanuatu-contents.ts create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-verification-contents.ts create mode 100644 packages/cfd/src/Constants/jurisdiction-contents/jurisdiction_maltainvest_contents.ts create mode 100644 packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js create mode 100644 packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx delete mode 100644 packages/cfd/src/Containers/cfd-enter-password-modal-title.tsx create mode 100644 packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx create mode 100644 packages/cfd/src/Containers/cfd-password-modal-title.tsx delete mode 100644 packages/cfd/src/Containers/cfd-password-success-icon.tsx create mode 100644 packages/cfd/src/Containers/cfd-personal-details-container.tsx delete mode 100644 packages/cfd/src/Containers/jurisdiction-checkbox.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-back.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-front.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-section.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-clickable-description.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-checkbox.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-title-indicator.spec.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-back.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-front.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-section.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-clickable-description.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-checkbox.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-foot-note.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx create mode 100644 packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx delete mode 100644 packages/cfd/src/Containers/mt5-create-password/__tests__/mt5-create-password.spec.tsx delete mode 100644 packages/cfd/src/Containers/mt5-create-password/mt5-create-password.scss delete mode 100644 packages/cfd/src/Containers/mt5-create-password/mt5-create-password.tsx delete mode 100644 packages/components/src/components/icon/mt5/ic-mt5-failed.svg delete mode 100644 packages/components/src/components/icon/mt5/ic-mt5-pending.svg delete mode 100644 packages/components/src/components/icon/mt5/ic-mt5-success.svg delete mode 100644 packages/components/src/components/icon/mt5/ic-mt5-verification.svg delete mode 100644 packages/hooks/src/__tests__/useGetStatus.spec.tsx delete mode 100644 packages/hooks/src/__tests__/useIsSelectedMT5AccountCreated.spec.tsx delete mode 100644 packages/hooks/src/useGetStatus.ts delete mode 100644 packages/hooks/src/useIsSelectedMT5AccountCreated.ts diff --git a/package-lock.json b/package-lock.json index e578496e4a5e..38afcb649f3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@deriv/deriv-charts": "^2.5.1", "@deriv/js-interpreter": "^3.0.0", "@deriv/quill-design": "^1.3.2", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@lottiefiles/dotlottie-react": "0.7.2", "@sendbird/chat": "^4.9.7", "@simplewebauthn/browser": "^8.3.4", @@ -3456,9 +3456,10 @@ } }, "node_modules/@deriv/quill-icons": { - "version": "1.23.14", - "resolved": "https://registry.npmjs.org/@deriv/quill-icons/-/quill-icons-1.23.14.tgz", - "integrity": "sha512-I9rajWDuN6Yoqj5atWS1djVFuAQDRV7U5ZOencpYNc8JBrv96eWq+eVNHk1eeSy5QSIyxhh6aKnz59V3ViVBnA==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@deriv/quill-icons/-/quill-icons-1.23.3.tgz", + "integrity": "sha512-xCjtZuFC6EAz0vFljzIkHZfLoKH0YTO9IuollwTCjj8tLY/3Lam998MvPO4zEFU6el1A6/rLjqiqlQXciSKT9w==", + "license": "ISC", "peerDependencies": { "react": ">= 16", "react-dom": ">= 16" diff --git a/packages/account/build/webpack.config.js b/packages/account/build/webpack.config.js index ff63ee2d0335..3089aefb44f4 100644 --- a/packages/account/build/webpack.config.js +++ b/packages/account/build/webpack.config.js @@ -16,6 +16,7 @@ module.exports = function (env) { 'financial-details-config': 'Configs/financial-details-config', 'get-status-badge-config': 'Configs/get-status-badge-config', 'personal-details-config': 'Configs/personal-details-config', + 'poi-poa-docs-submitted': 'Components/poi-poa-docs-submitted/poi-poa-docs-submitted', 'risk-tolerance-warning-modal': 'Components/trading-assessment/risk-tolerance-warning-modal', 'sent-email-modal': 'Components/sent-email-modal', 'terms-of-use-config': 'Configs/terms-of-use-config', diff --git a/packages/account/package.json b/packages/account/package.json index 90fd5114d0cc..0a2212b102db 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -38,7 +38,7 @@ "@deriv-com/quill-ui": "1.18.0", "@deriv/components": "^1.0.0", "@deriv/hooks": "^1.0.0", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", diff --git a/packages/account/src/Components/poa/status/needs-review/__tests__/needs-review-spec.tsx b/packages/account/src/Components/poa/status/needs-review/__tests__/needs-review-spec.tsx new file mode 100644 index 000000000000..83c9c33e28f7 --- /dev/null +++ b/packages/account/src/Components/poa/status/needs-review/__tests__/needs-review-spec.tsx @@ -0,0 +1,51 @@ +import { render, screen } from '@testing-library/react'; + +import { BrowserRouter } from 'react-router-dom'; +import { Button } from '@deriv/components'; +import { NeedsReview } from '../needs-review'; +import React from 'react'; + +jest.mock('Components/poa/continue-trading-button/continue-trading-button', () => ({ + ContinueTradingButton: jest.fn(() =>
ContinueTradingButton
), +})); + +const mock_redirection_btn = ; + +describe('', () => { + it('should render NeedsReview component if it does not need poi', () => { + render( + + + + ); + + expect(screen.getByText('Your proof of address was submitted successfully')).toBeInTheDocument(); + expect(screen.getByText('Your document is being reviewed, please check back in 1-3 days.')).toBeInTheDocument(); + expect(screen.getByText('ContinueTradingButton')).toBeInTheDocument(); + }); + + it('should render NeedsReview component if it does not need poi and is_description_enabled', () => { + render( + + + + ); + + expect(screen.getByText('Your proof of address was submitted successfully')).toBeInTheDocument(); + expect(screen.getByText('Your document is being reviewed, please check back in 1-3 days.')).toBeInTheDocument(); + expect(screen.queryByText('ContinueTradingButton')).not.toBeInTheDocument(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('should render NeedsReview component if it needs poi', () => { + render( + + + + ); + + expect(screen.getByText('Your proof of address was submitted successfully')).toBeInTheDocument(); + expect(screen.getByText('Your document is being reviewed, please check back in 1-3 days.')).toBeInTheDocument(); + expect(screen.getByText('You must also submit a proof of identity.')).toBeInTheDocument(); + }); +}); diff --git a/packages/account/src/Components/poa/status/needs-review/index.ts b/packages/account/src/Components/poa/status/needs-review/index.ts new file mode 100644 index 000000000000..d3c9442101b5 --- /dev/null +++ b/packages/account/src/Components/poa/status/needs-review/index.ts @@ -0,0 +1,3 @@ +import { NeedsReview as PoaNeedsReview } from './needs-review'; + +export default PoaNeedsReview; diff --git a/packages/account/src/Components/poa/status/needs-review/needs-review.tsx b/packages/account/src/Components/poa/status/needs-review/needs-review.tsx new file mode 100644 index 000000000000..c29d2d77019c --- /dev/null +++ b/packages/account/src/Components/poa/status/needs-review/needs-review.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import { isNavigationFromP2P, isNavigationFromDerivGO } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import ContinueTradingButton from '../../continue-trading-button'; +import IconMessageContent from '../../../icon-message-content'; +import PoiButton from '../../../poi/poi-button'; +import { TPoaStatusProps } from '../../../../Types'; + +export const NeedsReview = ({ needs_poi, redirect_button }: TPoaStatusProps) => { + const message = localize('Your proof of address was submitted successfully'); + const is_redirected_from_platform = isNavigationFromP2P() || isNavigationFromDerivGO(); + if (!needs_poi) { + return ( + } + > + {redirect_button || (!is_redirected_from_platform && )} + + ); + } + return ( + }> +
+ + {localize('Your document is being reviewed, please check back in 1-3 days.')} + + + {localize('You must also submit a proof of identity.')} + +
+ +
+ ); +}; diff --git a/packages/account/src/Components/poa/status/submitted/__tests__/submitted.spec.tsx b/packages/account/src/Components/poa/status/submitted/__tests__/submitted.spec.tsx index be5fa726a9dd..193f59793311 100644 --- a/packages/account/src/Components/poa/status/submitted/__tests__/submitted.spec.tsx +++ b/packages/account/src/Components/poa/status/submitted/__tests__/submitted.spec.tsx @@ -14,23 +14,23 @@ describe('', () => { it('should render the Submitted component', () => { renderWithRouter(); - expect(screen.getByText('Review in progress')).toBeInTheDocument(); + expect(screen.getByText('Your documents were submitted successfully')).toBeInTheDocument(); }); it('should show submit_poi message if needs_poi is true', () => { renderWithRouter(); - expect(screen.getByText('To start trading, you also need to verify your identity.')).toBeInTheDocument(); + expect(screen.getByText('You must also submit a proof of identity.')).toBeInTheDocument(); }); it('should show review message if needs_poi is true', () => { renderWithRouter(); expect( - screen.getByText('Your proof of address is under review. We’ll get back to you in 1–3 working days.') + screen.getByText('We’ll review your documents and notify you of its status within 1 to 3 days.') ).toBeInTheDocument(); }); it('should show ContinueTradingButton if no props are passed', () => { renderWithRouter(); - expect(screen.getByText("Return to Trader's Hub")).toBeInTheDocument(); + expect(screen.getByTestId('continue-trading-button')).toBeInTheDocument(); }); }); diff --git a/packages/account/src/Components/poa/status/submitted/submitted.tsx b/packages/account/src/Components/poa/status/submitted/submitted.tsx index 0c7ffbf61825..24d3d187b0aa 100644 --- a/packages/account/src/Components/poa/status/submitted/submitted.tsx +++ b/packages/account/src/Components/poa/status/submitted/submitted.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { Icon, Text } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { isNavigationFromP2P, isNavigationFromDerivGO, routes } from '@deriv/shared'; +import { isNavigationFromP2P, isNavigationFromDerivGO } from '@deriv/shared'; +import ContinueTradingButton from '../../continue-trading-button'; import IconMessageContent from '../../../icon-message-content'; -import RouteButton from '../../../route-button'; +import PoiButton from '../../../poi/poi-button'; import { TPoaStatusProps } from '../../../../Types'; export const Submitted = ({ needs_poi, redirect_button }: TPoaStatusProps) => { - const message = localize('Review in progress'); + const message = localize('Your documents were submitted successfully'); const is_redirected_from_platform = isNavigationFromP2P() || isNavigationFromDerivGO(); if (needs_poi) { return ( @@ -15,15 +16,13 @@ export const Submitted = ({ needs_poi, redirect_button }: TPoaStatusProps) => { }>
- {localize( - 'Your proof of address is under review. We’ll get back to you in 1–3 working days.' - )} + {localize('We’ll review your documents and notify you of its status within 1 to 3 days.')} - {localize('To start trading, you also need to verify your identity.')} + {localize('You must also submit a proof of identity.')}
- +
); @@ -32,13 +31,10 @@ export const Submitted = ({ needs_poi, redirect_button }: TPoaStatusProps) => {
} > - {redirect_button || - (!is_redirected_from_platform && ( - - ))} + {redirect_button || (!is_redirected_from_platform && )}
); diff --git a/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx b/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx new file mode 100644 index 000000000000..32dcdbf868b8 --- /dev/null +++ b/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Button, Icon, Loading } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { getAuthenticationStatusInfo, Jurisdiction } from '@deriv/shared'; +import IconMessageContent from 'Components/icon-message-content'; +import { GetAccountStatus } from '@deriv/api-types'; + +type TPoiPoaDocsSubmitted = { + account_status: GetAccountStatus; + onClickOK: () => void; + jurisdiction_selected_shortcode: string; + has_created_account_for_selected_jurisdiction: boolean; + openPasswordModal: () => void; + updateAccountStatus: () => Promise; +}; + +const PoiPoaDocsSubmitted = ({ + account_status, + jurisdiction_selected_shortcode, + onClickOK, + updateAccountStatus, + has_created_account_for_selected_jurisdiction, + openPasswordModal, +}: TPoiPoaDocsSubmitted) => { + const [is_loading, setIsLoading] = React.useState(false); + + React.useEffect(() => { + const fetchAccountStatus = async () => { + await updateAccountStatus(); + setIsLoading(false); + }; + setIsLoading(true); + fetchAccountStatus(); + + return () => setIsLoading(false); + }, [updateAccountStatus]); + + const onSubmit = () => { + onClickOK(); + if (!has_created_account_for_selected_jurisdiction) { + openPasswordModal(); + } + }; + + const getDescription = () => { + const { manual_status, poi_verified_for_maltainvest, poi_verified_for_bvi_labuan_vanuatu, poa_pending } = + getAuthenticationStatusInfo(account_status); + const is_maltainvest_selected = jurisdiction_selected_shortcode === Jurisdiction.MALTA_INVEST; + if ( + (is_maltainvest_selected && poi_verified_for_maltainvest && poa_pending) || + (!is_maltainvest_selected && poi_verified_for_bvi_labuan_vanuatu && poa_pending) || + manual_status === 'pending' + ) { + return localize('We’ll review your documents and notify you of its status within 1 - 3 working days.'); + } + return localize('We’ll review your documents and notify you of its status within 5 minutes.'); + }; + + return is_loading ? ( + + ) : ( + } + className='poi-poa-submitted' + > + ; - const needs_poa_extra_submit_message = /To start trading, you also need to verify your address./i; + const needs_poa_extra_submit_message = /you must also submit a proof of address./i; const renderWithRouter = (component: React.ReactElement) => render({component}); it('should render component for manual upload', () => { renderWithRouter(); + expect(screen.getByText(successful_upload_message)).toBeInTheDocument(); expect(screen.getByText(poi_under_review_message_for_manual)).toBeInTheDocument(); expect(screen.getByTestId(/dt_mocked_icon/i)).toBeInTheDocument(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); - it('should render component for idv and onfido upload', () => { - renderWithRouter(); + it('should render component for manual upload', () => { + renderWithRouter(); expect(screen.getByText(successful_upload_message)).toBeInTheDocument(); - expect(screen.getByText(poi_under_review_message)).toBeInTheDocument(); + expect(screen.getByText(poi_under_review_message_for_manual)).toBeInTheDocument(); expect(screen.getByTestId(/dt_mocked_icon/i)).toBeInTheDocument(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); it('should not show redirect_button if it redirect_button passed and is_from_external is true, but needs_poa is false', () => { renderWithRouter(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); @@ -55,17 +56,18 @@ describe('', () => { it('should show needs_poa review message and extra submission message, and poa_buttons', () => { renderWithRouter(); + expect(screen.getByTestId('dt_poa_button')).toBeInTheDocument(); expect(screen.getByText(poi_under_review_message)).toBeInTheDocument(); expect(screen.getByText(needs_poa_extra_submit_message)).toBeInTheDocument(); expect(screen.getByRole('button')).toBeInTheDocument(); - expect(screen.getByRole('button')).toHaveTextContent('Lorem Ipsom'); }); it('should show needs_poa review message and extra submission message, and poa_buttons but redirect_button will not display', () => { renderWithRouter(); + expect(screen.getByTestId('dt_poa_button')).toBeInTheDocument(); expect(screen.getByText(poi_under_review_message)).toBeInTheDocument(); expect(screen.getByText(needs_poa_extra_submit_message)).toBeInTheDocument(); - expect(screen.getByText('Next')).toBeInTheDocument(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); }); diff --git a/packages/account/src/Components/poi/status/upload-complete/upload-complete.tsx b/packages/account/src/Components/poi/status/upload-complete/upload-complete.tsx index d4ad9d9bf8a3..e0ed002c5e78 100644 --- a/packages/account/src/Components/poi/status/upload-complete/upload-complete.tsx +++ b/packages/account/src/Components/poi/status/upload-complete/upload-complete.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Icon, Text } from '@deriv/components'; -import { isNavigationFromP2P, isNavigationFromDerivGO, routes } from '@deriv/shared'; -import { useTranslations } from '@deriv-com/translations'; +import { isNavigationFromP2P, isNavigationFromDerivGO } from '@deriv/shared'; +import { localize } from '@deriv/translations'; import PoaButton from '../../../poa/poa-button'; -import RouteButton from '../../../route-button'; +import { ContinueTradingButton } from '../../../poa/continue-trading-button/continue-trading-button'; import IconMessageContent from '../../../icon-message-content/icon-message-content'; import { TPOIStatus } from 'Types'; @@ -13,22 +13,17 @@ export const UploadComplete = ({ is_from_external, is_manual_upload = false, }: TPOIStatus) => { - const { localize } = useTranslations(); - const message = localize('Review in progress'); + const message = localize('Your documents were submitted successfully'); const description = is_manual_upload - ? localize('Your proof of identity is under review. We’ll get back to you within 1–3 working days.') - : localize('Your proof of identity is under review. We’ll get back to you within 5 minutes.'); + ? localize('We’ll review your documents and notify you of its status within 1 - 3 working days.') + : localize('We’ll review your documents and notify you of its status within 5 minutes.'); const is_redirected_from_platform = isNavigationFromP2P() || isNavigationFromDerivGO(); if (!needs_poa) { return ( }> - {!is_from_external && - (redirect_button || - (!is_redirected_from_platform && ( - - )))} + {!is_from_external && (redirect_button || (!is_redirected_from_platform && ))} ); } @@ -40,10 +35,10 @@ export const UploadComplete = ({ {description} - {localize('To start trading, you also need to verify your address.')} + {localize('You must also submit a proof of address.')} - + {!is_from_external && redirect_button} diff --git a/packages/account/src/Components/route-button/__tests__/route-button.spec.tsx b/packages/account/src/Components/route-button/__tests__/route-button.spec.tsx deleted file mode 100644 index 2327a4502171..000000000000 --- a/packages/account/src/Components/route-button/__tests__/route-button.spec.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { createBrowserHistory } from 'history'; -import { Router } from 'react-router'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { routes } from '@deriv/shared'; -import { RouteButton } from '../route-button'; - -describe('', () => { - const history = createBrowserHistory(); - const renderWithRouter = (component: React.ReactElement) => { - return render({component}); - }; - - it('should render RouteButton component', () => { - renderWithRouter(); - screen.getByRole('link', { - name: 'Test Route Button', - }); - expect(screen.getByText('Test Route Button')).toBeInTheDocument(); - }); - - it('should navigate to route on clicking the text', () => { - renderWithRouter(); - const route_btn_text = screen.getByRole('link', { - name: 'Test Route Button', - }); - userEvent.click(route_btn_text); - expect(history.location.pathname).toBe(routes.traders_hub); - }); -}); diff --git a/packages/account/src/Components/route-button/index.ts b/packages/account/src/Components/route-button/index.ts deleted file mode 100644 index 1e15db9c2c50..000000000000 --- a/packages/account/src/Components/route-button/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { RouteButton } from './route-button'; - -export default RouteButton; diff --git a/packages/account/src/Components/route-button/route-button.tsx b/packages/account/src/Components/route-button/route-button.tsx deleted file mode 100644 index f71e808cd1ad..000000000000 --- a/packages/account/src/Components/route-button/route-button.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import clsx from 'clsx'; -import { ButtonLink, Text } from '@deriv/components'; - -type TRouteButtonProps = { route: string; button_label: string; className?: string }; - -/** - * Renders a button that redirects to the trading platform - * @name RouteButton - * @param route - Route to redirect to - * @param button_label - Text to be displayed on the button - * @param className - Styles to be applied to the button - * @returns React Element - */ -export const RouteButton = ({ button_label, className, route }: TRouteButtonProps) => ( - - - {button_label} - - -); diff --git a/packages/account/src/Configs/__test__/get-status-badge-config.spec.tsx b/packages/account/src/Configs/__test__/get-status-badge-config.spec.tsx index 6d857a07de92..a474bb660098 100644 --- a/packages/account/src/Configs/__test__/get-status-badge-config.spec.tsx +++ b/packages/account/src/Configs/__test__/get-status-badge-config.spec.tsx @@ -1,15 +1,31 @@ import React from 'react'; import { BrowserRouter } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import getStatusBadgeConfig from '../get-status-badge-config'; -import { ACCOUNT_BADGE_STATUS, TAccountBadgeStatus } from '@deriv/shared'; +import getStatusBadgeConfig from 'Configs/get-status-badge-config'; +import { AUTH_STATUS_CODES, MT5_ACCOUNT_STATUS, routes } from '@deriv/shared'; +import { TMT5AccountStatus } from 'Types'; describe('getStatusBadgeConfig', () => { - let account_status: TAccountBadgeStatus; + let account_status: TMT5AccountStatus; + const openFailedVerificationModal = jest.fn(); + const setIsVerificationModalVisible = jest.fn(); + const selected_account_type = 'test type'; - const renderCheck = (account_status: Parameters[0]) => { - const badge = getStatusBadgeConfig(account_status); + const renderCheck = ( + account_status: Parameters[0], + openFailedVerificationModal: Parameters[1], + selected_account_type: Parameters[2], + setIsVerificationModalVisible?: Parameters[3], + user_account_status?: Parameters[4] + ) => { + const badge = getStatusBadgeConfig( + account_status, + openFailedVerificationModal, + selected_account_type, + setIsVerificationModalVisible, + user_account_status + ); render(
{badge.text}
@@ -19,35 +35,92 @@ describe('getStatusBadgeConfig', () => { }; it('should render pending status', () => { - account_status = ACCOUNT_BADGE_STATUS.PENDING; + account_status = MT5_ACCOUNT_STATUS.PENDING; + + renderCheck(account_status, openFailedVerificationModal, selected_account_type); + + expect(screen.getByText('Pending verification')).toBeInTheDocument(); + expect(screen.getByText('IcAlertWarning')).toBeInTheDocument(); + }); + + it('should render failed status and trigger "Why?"', () => { + account_status = MT5_ACCOUNT_STATUS.FAILED; + + renderCheck(account_status, openFailedVerificationModal, selected_account_type); + + expect(screen.getByText('Verification failed.')).toBeInTheDocument(); + expect(screen.getByText('IcRedWarning')).toBeInTheDocument(); + + fireEvent.click(screen.getByText('Why?')); + expect(openFailedVerificationModal).toBeCalledWith(selected_account_type); + }); + + it('should render needs_verification status and redirect to identity by default', () => { + account_status = MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION; - renderCheck(account_status); + renderCheck(account_status, openFailedVerificationModal, selected_account_type, setIsVerificationModalVisible); - expect(screen.getByText('In review')).toBeInTheDocument(); - expect(screen.getByText('IcMt5Pending')).toBeInTheDocument(); + expect(screen.getByText(/Needs verification./)); + expect(screen.getByText('IcAlertInfo')); + + const btn = screen.getByText('Verify now'); + expect(btn).toBeInTheDocument(); + expect(btn.hasAttribute('href')); + expect(btn.hasAttribute(routes.proof_of_identity)); + userEvent.click(btn); + expect(setIsVerificationModalVisible).toBeCalled(); + }); + + it('should render migrated_with_position status', () => { + account_status = MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION; + + renderCheck(account_status, openFailedVerificationModal, selected_account_type); + + expect(screen.getByText('No new positions')).toBeInTheDocument(); + expect(screen.getByText('IcAlertWarning')).toBeInTheDocument(); }); - it('should render failed status', () => { - account_status = ACCOUNT_BADGE_STATUS.FAILED; + it('should render migrated_without_position status', () => { + account_status = MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION; - renderCheck(account_status); - const failed_text = screen.getByText('Failed'); + renderCheck(account_status, openFailedVerificationModal, selected_account_type); - expect(failed_text).toBeInTheDocument(); - expect(screen.getByText('IcMt5Failed')).toBeInTheDocument(); + expect(screen.getByText('Account closed')).toBeInTheDocument(); + expect(screen.getByText('IcAlertWarning')).toBeInTheDocument(); + }); + + it('should render need_verification status and redirect to POA when POI is verified', () => { + account_status = MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION; + + renderCheck(account_status, openFailedVerificationModal, selected_account_type, undefined, { + poi_status: AUTH_STATUS_CODES.VERIFIED, + poa_status: AUTH_STATUS_CODES.NONE, + }); - userEvent.click(failed_text); + expect(screen.getByText('Needs verification.')); + expect(screen.getByText('IcAlertInfo')); + + const btn = screen.getByRole('link', { name: 'Verify now' }); + expect(btn).toBeInTheDocument(); + expect(btn.hasAttribute('href')); + expect(btn.hasAttribute(routes.proof_of_address)); }); - it('should render needs_verification status', () => { - account_status = ACCOUNT_BADGE_STATUS.NEEDS_VERIFICATION; + it('should render need_verification status and redirect to POI when POI status is not verified and POA status is not verified', () => { + account_status = MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION; + + renderCheck(account_status, openFailedVerificationModal, selected_account_type, undefined, { + poi_status: AUTH_STATUS_CODES.NONE, + poa_status: AUTH_STATUS_CODES.NONE, + }); - renderCheck(account_status); - const needs_verification_text = screen.getByText('Needs Verification'); + expect(screen.getByText('Needs verification.')); + expect(screen.getByText('IcAlertInfo')); - expect(needs_verification_text).toBeInTheDocument(); - expect(screen.getByText('IcMt5Verification')); + const btn = screen.getByRole('link', { name: 'Verify now' }); - userEvent.click(needs_verification_text); + expect(btn).toBeInTheDocument(); + expect(btn.hasAttribute('href')); + expect(btn.hasAttribute(routes.proof_of_identity)); }); }); diff --git a/packages/account/src/Configs/get-mt5-status-badge-config.tsx b/packages/account/src/Configs/get-mt5-status-badge-config.tsx deleted file mode 100644 index 78f4a90fac79..000000000000 --- a/packages/account/src/Configs/get-mt5-status-badge-config.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { Text } from '@deriv/components'; -import { MT5_ACCOUNT_STATUS, TRADING_PLATFORM_STATUS } from '@deriv/shared'; -import { Localize } from '@deriv-com/translations'; -import { TMT5AccountStatus } from '../Types/common.type'; - -const getMT5StatusBadgeConfig = (mt5_account_status: TMT5AccountStatus) => { - const BadgeTextComponent = ; - - switch (mt5_account_status) { - case MT5_ACCOUNT_STATUS.PENDING: - return { - text: , - icon: 'IcMt5Pending', - }; - case MT5_ACCOUNT_STATUS.FAILED: - return { - text: , - icon: 'IcMt5Failed', - icon_size: '18', - }; - case MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION: { - return { - text: , - icon: 'IcMt5Verification', - icon_size: '18', - }; - } - case MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION: - return { - text: , - icon: 'IcAlertWarning', - }; - case MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION: - return { - text: , - icon: 'IcAlertWarning', - }; - case MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE: - return { - text: , - icon: 'IcAlertWarning', - }; - case TRADING_PLATFORM_STATUS.UNAVAILABLE: - return { - text: , - icon: 'IcAlertWarning', - }; - default: - return { - text: '', - icon: '', - }; - } -}; - -export default getMT5StatusBadgeConfig; diff --git a/packages/account/src/Configs/get-status-badge-config.tsx b/packages/account/src/Configs/get-status-badge-config.tsx index cce0f1fcb352..1b2d2c24df5b 100644 --- a/packages/account/src/Configs/get-status-badge-config.tsx +++ b/packages/account/src/Configs/get-status-badge-config.tsx @@ -1,27 +1,95 @@ import React from 'react'; -import { ACCOUNT_BADGE_STATUS, TAccountBadgeStatus } from '@deriv/shared'; -import { Localize } from '@deriv-com/translations'; +import { Text } from '@deriv/components'; +import { AUTH_STATUS_CODES, MT5_ACCOUNT_STATUS, TRADING_PLATFORM_STATUS, routes } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { TAuthStatusCodes, TMT5AccountStatus } from '../Types/common.type'; +import { Link } from 'react-router-dom'; -const getStatusBadgeConfig = (account_status: TAccountBadgeStatus) => { - switch (account_status) { - case ACCOUNT_BADGE_STATUS.PENDING: +const getStatusBadgeConfig = ( + mt5_account_status: TMT5AccountStatus, + openFailedVerificationModal?: (selected_account_type: string) => void, + selected_account_type?: string, + setIsVerificationModalVisible?: (value: boolean) => void, + user_account_status?: { poi_status: TAuthStatusCodes; poa_status: TAuthStatusCodes } +) => { + const BadgeTextComponent = ; + + switch (mt5_account_status) { + case MT5_ACCOUNT_STATUS.PENDING: return { - text: , - icon: 'IcMt5Pending', + text: ( + ]} + /> + ), + icon: 'IcAlertWarning', }; - case ACCOUNT_BADGE_STATUS.FAILED: + case MT5_ACCOUNT_STATUS.FAILED: return { - text: , - icon: 'IcMt5Failed', - icon_size: '18', + text: ( + , + { + openFailedVerificationModal?.(selected_account_type ?? ''); + }} + />, + ]} + /> + ), + icon: 'IcRedWarning', }; - case ACCOUNT_BADGE_STATUS.NEEDS_VERIFICATION: { + case MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION: { + const redirect_url = + user_account_status?.poi_status === AUTH_STATUS_CODES.NONE + ? routes.proof_of_identity + : routes.proof_of_address; return { - text: , - icon: 'IcMt5Verification', - icon_size: '18', + text: ( + , + setIsVerificationModalVisible ? ( + setIsVerificationModalVisible?.(true)} + /> + ) : ( + + ), + ]} + /> + ), + icon: 'IcAlertInfo', }; } + case MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION: + return { + text: , + icon: 'IcAlertWarning', + }; + case MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION: + return { + text: , + icon: 'IcAlertWarning', + }; + case MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE: + return { + text: , + icon: 'IcAlertWarning', + }; + case TRADING_PLATFORM_STATUS.UNAVAILABLE: + return { + text: , + icon: 'IcAlertWarning', + }; default: return { text: '', diff --git a/packages/account/src/Sections/Verification/Helpers/verification.js b/packages/account/src/Sections/Verification/Helpers/verification.js index ac904c1a36a0..624e7ecde65b 100644 --- a/packages/account/src/Sections/Verification/Helpers/verification.js +++ b/packages/account/src/Sections/Verification/Helpers/verification.js @@ -21,16 +21,13 @@ export const populateVerificationStatus = account_status => { status => status === 'poa_authenticated_with_idv_photo' ); const poa_expiring_soon = account_status.status.some(status => status === 'poa_expiring_soon'); - const poi_acknowledged = ['pending', 'verified'].includes(identity_status); + const has_poa = !(document && document.status === 'none'); const has_poi = !(identity && identity.status === 'none'); const has_poinc = !(income && income.status === 'none'); const has_submitted_poa = document_status === 'pending' && !allow_poa_resubmission; - const needs_poa = - !(has_submitted_poa || document_status === 'verified') || - (needs_verification.length && needs_verification.includes('document')); - const needs_poi = - !poi_acknowledged || (Boolean(needs_verification.length) && needs_verification.includes('identity')); + const needs_poa = Boolean(needs_verification.length) && needs_verification.includes('document'); + const needs_poi = Boolean(needs_verification.length) && needs_verification.includes('identity'); const needs_poinc = needs_verification.length && needs_verification.includes('income'); const { idv, onfido, manual } = identity.services; diff --git a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx index eabc1e251a13..ba307e6b35df 100644 --- a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx +++ b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-container.tsx @@ -5,6 +5,7 @@ import { WS, getPlatformRedirect, platforms, routes, AUTH_STATUS_CODES } from '@ import { observer, useStore } from '@deriv/stores'; import { Localize } from '@deriv/translations'; import Expired from '../../../Components/poa/status/expired'; +import NeedsReview from '../../../Components/poa/status/needs-review'; import NotRequired from '../../../Components/poa/status/not-required'; import ProofOfAddressForm from './proof-of-address-form'; import Submitted from '../../../Components/poa/status/submitted'; @@ -197,7 +198,7 @@ const ProofOfAddressContainer = observer(({ onSubmit }: TProofOfAddressContainer case AUTH_STATUS_CODES.NONE: return ; case AUTH_STATUS_CODES.PENDING: - return ; + return ; case AUTH_STATUS_CODES.VERIFIED: return ; case AUTH_STATUS_CODES.EXPIRED: diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx new file mode 100644 index 000000000000..d4178c30f96d --- /dev/null +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Loading } from '@deriv/components'; +import { WS } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { useStore, observer } from '@deriv/stores'; +import ErrorMessage from '../../../Components/error-component'; +import IconWithMessage from '../../../Components/icon-with-message'; +import POISubmissionForMT5 from './proof-of-identity-submission-for-mt5.jsx'; +import { service_code } from './proof-of-identity-utils'; +import { populateVerificationStatus } from '../Helpers/verification'; + +const ProofOfIdentityContainerForMt5 = observer(({ onStateChange, citizen_data }) => { + const [api_error, setAPIError] = React.useState(); + const [residence_list, setResidenceList] = React.useState(); + const [is_status_loading, setStatusLoading] = React.useState(true); + + const { client } = useStore(); + const { account_status, fetchResidenceList, is_switching, is_virtual } = client; + + React.useEffect(() => { + // only re-mount logic when switching is done + if (!is_switching) { + WS.authorized.getAccountStatus().then(response_account_status => { + if (response_account_status.error) { + setAPIError(response_account_status.error); + setStatusLoading(false); + return; + } + fetchResidenceList().then(response_residence_list => { + if (response_residence_list.error) { + setAPIError(response_residence_list.error); + } else { + setResidenceList(response_residence_list.residence_list); + } + setStatusLoading(false); + }); + }); + } + }, [fetchResidenceList, is_switching]); + + if (is_status_loading || is_switching) { + return ; + } else if (is_virtual) { + return ( + + ); + } else if (api_error) { + return ; + } + + const verification_status = populateVerificationStatus(account_status); + const { idv, identity_last_attempt, is_idv_disallowed, onfido } = verification_status; + + const poi_resubmission_cases = ['rejected', 'suspected', 'expired']; + + const has_idv_error = + identity_last_attempt?.service && service_code.idv && poi_resubmission_cases.includes(idv.status); + + return ( + + ); +}); + +export default ProofOfIdentityContainerForMt5; diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx new file mode 100644 index 000000000000..2d5756e1e23d --- /dev/null +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx @@ -0,0 +1,193 @@ +import React from 'react'; +import { AutoHeightWrapper } from '@deriv/components'; +import { useDevice } from '@deriv-com/ui'; +import { + WS, + isVerificationServiceSupported, + formatIDVFormValues, + formatIDVError, + isIDVReportNotAvailable, + getIDVNotApplicableOption, +} from '@deriv/shared'; +import { useStore, observer } from '@deriv/stores'; +import Unsupported from '../../../Components/poi/status/unsupported'; +import OnfidoUpload from './onfido-sdk-view-container'; +import { identity_status_codes, submission_status_code, service_code } from './proof-of-identity-utils'; +import IdvFailed from '../../../Components/poi/idv-status/idv-failed'; +import { IdvDocSubmitOnSignup } from '../../../Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup'; +import { makeSettingsRequest } from '../../../Helpers/utils'; + +const POISubmissionForMT5 = observer( + ({ + idv, + is_idv_disallowed, + onfido, + onStateChange, + citizen_data, + is_from_external, + residence_list, + identity_last_attempt, + }) => { + const { isDesktop } = useDevice(); + const [submission_status, setSubmissionStatus] = React.useState(); // submitting + const [submission_service, setSubmissionService] = React.useState(); + const [idv_mismatch_status, setIdvMismatchStatus] = React.useState(null); + + const { client, notifications, traders_hub } = useStore(); + const { account_settings, getChangeableFields, account_status } = client; + const { refreshNotifications } = notifications; + const { is_eu_user } = traders_hub; + const is_report_not_available = isIDVReportNotAvailable(idv); + const IDV_NOT_APPLICABLE_OPTION = React.useMemo(() => getIDVNotApplicableOption(), []); + const shouldSkipIdv = document_id => document_id === IDV_NOT_APPLICABLE_OPTION.id; + + const attempts = account_status?.authentication?.attempts; + + const { service } = attempts?.latest ?? {}; + const { submissions_left: idv_submissions_left, last_rejected, status } = idv; + const { submissions_left: onfido_submissions_left } = onfido; + + const is_idv_supported = + service === service_code.idv || isVerificationServiceSupported(residence_list, account_settings, 'idv'); + const is_onfido_supported = + service === service_code.onfido || + (account_settings?.citizen !== 'ng' && + isVerificationServiceSupported(residence_list, account_settings, 'onfido')); + + React.useEffect(() => { + if (citizen_data) { + if (is_idv_supported && Number(idv_submissions_left) > 0 && !is_idv_disallowed && !is_eu_user) { + setSubmissionService(service_code.idv); + if ( + [ + identity_status_codes.rejected, + identity_status_codes.suspected, + identity_status_codes.expired, + ].includes(status) + ) { + setIdvMismatchStatus(formatIDVError(last_rejected, status, undefined, is_report_not_available)); + } + } else if (onfido_submissions_left && is_onfido_supported) { + setSubmissionService(service_code.onfido); + } else { + setSubmissionService(service_code.manual); + } + setSubmissionStatus(submission_status_code.submitting); + } + }, [citizen_data]); + + const handleSelectionNext = () => { + setSubmissionService(service_code.manual); + }; + + const handlePOIComplete = () => { + if (onStateChange && typeof onStateChange === 'function') { + onStateChange(identity_status_codes.pending); + } + WS.authorized.getAccountStatus().then(() => { + refreshNotifications(); + }); + }; + + const handleIdvSubmit = async (values, { setSubmitting, setErrors }) => { + if (shouldSkipIdv(values?.document_type?.id)) { + handleSelectionNext?.(true); + return; + } + setSubmitting(true); + + const request = makeSettingsRequest(values, [...getChangeableFields()]); + + const data = await WS.setSettings(request); + + if (data.error) { + setErrors({ error_message: data.error.message }); + setSubmitting(false); + return; + } + const get_settings = WS.authorized.storage.getSettings(); + + if (get_settings.error) { + setErrors({ error_message: get_settings.error.message }); + setSubmitting(false); + return; + } + + const submit_data = { + identity_verification_document_add: 1, + ...formatIDVFormValues(values, citizen_data.value), + }; + + WS.send(submit_data).then(response => { + setSubmitting(false); + if (response.error) { + setErrors({ error_message: response.error.message }); + return; + } + handlePOIComplete(); + }); + }; + + if (submission_status === submission_status_code.submitting) { + switch (submission_service) { + case service_code.idv: + return idv_mismatch_status ? ( + + ) : ( + + ); + case service_code.onfido: { + const country_code = citizen_data.value; + const doc_obj = citizen_data.identity.services.onfido.documents_supported; + const documents_supported = Object.keys(doc_obj).map(d => doc_obj[d].display_name); + + return ( + + {({ setRef, height }) => ( +
+ +
+ )} +
+ ); + } + case service_code.manual: + return ( + + ); + default: + return null; + } + } else { + return null; + } + } +); + +export default POISubmissionForMT5; diff --git a/packages/account/src/Styles/account.scss b/packages/account/src/Styles/account.scss index d045ff565293..96717d7db005 100644 --- a/packages/account/src/Styles/account.scss +++ b/packages/account/src/Styles/account.scss @@ -325,6 +325,11 @@ $MIN_HEIGHT_FLOATING: calc( max-width: 100%; width: 100%; } + .cfd-personal-details-modal__form & { + margin: unset; + max-width: unset; + } + &--phone-verification-livechat-link { color: var(--text-general); } diff --git a/packages/appstore/package.json b/packages/appstore/package.json index 11c9062b693d..ddd5228877f8 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -37,7 +37,6 @@ "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", "@deriv/hooks": "^1.0.0", - "@deriv/quill-icons": "1.23.14", "@deriv/wallets": "^1.0.0", "classnames": "^2.2.6", "mobx": "^6.6.1", @@ -90,4 +89,4 @@ "webpack-bundle-analyzer": "^4.3.0", "webpack-cli": "^4.7.2" } -} +} \ No newline at end of file diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index 1db53c07acbb..b495d57780c5 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -1200,6 +1200,51 @@ } } +.cfd-personal-details-modal { + grid-template-rows: 8rem minmax(10rem, 1fr); + overflow-y: scroll; + height: 100%; + + &__body { + height: calc(100% - 5rem); + } + + &__heading-container { + padding-top: 2.4rem; + } + + &__form { + height: 100%; + display: flex; + flex-direction: column; + .dc-modal-footer { + width: 100%; + padding: 1.6rem 2.4rem; + bottom: 0; + right: 0; + display: flex; + justify-content: flex-end; + } + + .dc-autocomplete { + margin-bottom: 3.6rem; + } + + .details-form__description { + margin-inline: 1.6rem; + } + } + + &__inline-message { + width: 45%; + margin: 1.2rem auto; + + @include mobile-or-tablet-screen { + width: 95%; + } + } +} + .dc-modal { &__container { min-width: initial; @@ -1227,6 +1272,178 @@ } } +.cfd-proof-of-identity { + height: 100%; + overflow: auto; + + &__fields { + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + + @include mobile-or-tablet-screen { + display: unset; + position: relative; + } + + .proof-of-identity { + &:is(span) { + width: unset; + height: unset; + } + + &__footer { + width: 45%; + display: inline-flex; + justify-content: flex-end; + height: unset; + position: unset; + bottom: unset; + left: unset; + padding: unset; + z-index: unset; + border-radius: unset; + border-top: unset; + background-color: unset; + align-items: unset; + flex-direction: unset; + + @include mobile-or-tablet-screen { + width: 95%; + margin-top: 8px; + } + + &-alert { + margin-right: unset; + } + } + + &__container { + display: flex; + flex-direction: column; + align-items: center; + width: 45%; + justify-content: unset; + + @include mobile-or-tablet-screen { + width: 100%; + padding: 0 1.2rem; + overflow-y: unset; + justify-content: unset; + } + + .icon { + width: 12.8rem; + height: 12.8rem; + margin: 1.6rem 0 2.4rem; + } + .dc-input__bottom-label { + margin: unset; + } + .btm-spacer { + margin-bottom: 1.6rem; + } + .top-spacer { + margin-top: 1.6rem; + } + + .proof-of-identity__footer { + // for cases when __footer is a child of proof-of-identity__container: + width: 100%; + margin-bottom: 8.6em; + + span.dc-text.dc-btn__text { + display: inline-flex; + align-items: center; + } + .back-btn { + margin-right: unset; + + &-icon { + margin-right: 0.8rem; + } + } + } + } + + &__header { + margin: 0 0 1.6rem; + } + &__country-text { + text-align: center; + margin-bottom: 1.6rem; + } + &__inner-container { + display: unset; + flex-direction: unset; + justify-content: unset; + align-items: unset; + width: 100%; + } + &__image { + width: 100%; + max-width: unset; + border-radius: unset; + object-fit: unset; + + &-container { + width: unset; + height: unset; + padding: unset; + border-radius: unset; + background-color: unset; + } + } + &__fieldset { + width: 100%; + + @include mobile-or-tablet-screen { + margin: 0 0 1.8rem; + } + + &-container { + display: unset; + flex-direction: unset; + justify-content: unset; + align-items: unset; + } + &-input { + width: unset; + } + } + &__sample-container { + margin-top: 2.4rem; + margin-left: unset; + width: unset; + } + + &__submit-button { + margin-left: 0.8rem; + @include mobile-or-tablet-screen { + margin-right: unset; + } + } + } + .dc-themed-scrollbars { + height: 100%; + } + } + & .details-form { + display: grid; + grid-template-rows: minmax(10rem, 1fr) 8.2rem; + height: 100%; + position: relative; + + @include mobile-or-tablet-screen { + max-height: calc(100% - 1rem); + } + } + @include mobile-or-tablet-screen { + overflow: hidden; + } +} + .cfd-change-password { &__description { margin-bottom: 2.4rem; @@ -1412,7 +1629,14 @@ white-space: nowrap; } } - +.cfd-personal-details-form-error { + @include typeface(--paragraph-left-normal-red); + @include desktop-screen { + white-space: nowrap; + display: flex; + align-items: flex-end; + } +} .cfd-accounts-compare-modal { display: flex; flex-flow: column nowrap; @@ -1860,7 +2084,6 @@ flex-direction: row; } } - &__create-password { padding: 0 1.2rem; diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index f0c8fe8171ae..899b77f67fc0 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -4,6 +4,8 @@ import { Loading, Text } from '@deriv/components'; import { CFD_PLATFORMS, formatMoney, + getAuthenticationStatusInfo, + Jurisdiction, MT5_ACCOUNT_STATUS, TRADING_PLATFORM_STATUS, makeLazyLoader, @@ -65,41 +67,49 @@ const CFDsListing = observer(() => { no_MF_account, toggleAccountTransferModal, is_demo, + openFailedVerificationModal, showTopUpModal, no_CR_account, setSelectedAccount, CFDs_restricted_countries, financial_restricted_countries, - getDefaultJurisdiction, } = traders_hub; + const { setAccountType, toggleCTraderTransferModal, setAccountUnavailableModal, setServerMaintenanceModal, setProduct, - setJurisdictionSelectedShortcode, } = cfd; const { + account_status, is_landing_company_loaded, is_populating_mt5_account_list, real_account_creation_unlock_date, ctrader_total_balance, - updateMT5AccountDetails, } = client; const { setAppstorePlatform } = common; - const { openDerivRealAccountNeededModal, setShouldShowCooldownModal } = ui; + const { openDerivRealAccountNeededModal, setShouldShowCooldownModal, setIsMT5VerificationFailedModal } = ui; const has_no_real_account = !has_any_real_account; const accounts_sub_text = !is_eu_user || is_demo_low_risk ? localize('Compare accounts') : localize('Account Information'); + const { + poi_pending_for_bvi_labuan_vanuatu, + poi_resubmit_for_bvi_labuan_vanuatu, + poa_resubmit_for_labuan, + is_idv_revoked, + } = getAuthenticationStatusInfo(account_status); + const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ featureFlag: 'ce_tradershub_dashboard_tracking', defaultValue: false, }); const { has_svg_accounts_to_migrate } = useMT5SVGEligibleToMigrate(); + const getAuthStatus = (status_list: boolean[]) => status_list.some(status => status); const { getPlatformStatus } = useTradingPlatformStatus(); @@ -132,23 +142,61 @@ const CFDsListing = observer(() => { return MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE; } else if (current_acc_status === 'unavailable') { return TRADING_PLATFORM_STATUS.UNAVAILABLE; - } else if (jurisdiction) { - switch (current_acc_status) { - case 'proof_failed': - case 'poa_failed': - case 'poa_outdated': - return MT5_ACCOUNT_STATUS.FAILED; - case 'verification_pending': - case 'poa_pending': - return MT5_ACCOUNT_STATUS.PENDING; - case 'needs_verification': - case 'poa_required': - return MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION; - case 'migrated_with_position': - return MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION; - case 'migrated_without_position': - return MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION; + } + + if (jurisdiction) { + switch (jurisdiction) { + case Jurisdiction.BVI: { + if ( + getAuthStatus([ + is_idv_revoked, + poi_resubmit_for_bvi_labuan_vanuatu, + current_acc_status === 'proof_failed', + ]) + ) { + return MT5_ACCOUNT_STATUS.FAILED; + } else if ( + getAuthStatus([ + poi_pending_for_bvi_labuan_vanuatu, + current_acc_status === 'verification_pending', + ]) + ) { + return MT5_ACCOUNT_STATUS.PENDING; + } + return null; + } + case Jurisdiction.LABUAN: { + if ( + getAuthStatus([ + poa_resubmit_for_labuan, + is_idv_revoked, + poi_resubmit_for_bvi_labuan_vanuatu, + current_acc_status === 'proof_failed', + ]) + ) { + return MT5_ACCOUNT_STATUS.FAILED; + } else if ( + getAuthStatus([ + poi_pending_for_bvi_labuan_vanuatu, + current_acc_status === 'verification_pending', + ]) + ) { + return MT5_ACCOUNT_STATUS.PENDING; + } + return null; + } default: + if (current_acc_status === 'proof_failed') { + return MT5_ACCOUNT_STATUS.FAILED; + } else if (current_acc_status === 'verification_pending') { + return MT5_ACCOUNT_STATUS.PENDING; + } else if (current_acc_status === 'needs_verification') { + return MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION; + } else if (current_acc_status === 'migrated_with_position') { + return MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION; + } else if (current_acc_status === 'migrated_without_position') { + return MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION; + } return null; } } @@ -186,7 +234,6 @@ const CFDsListing = observer(() => { setPerformanceValue('switch_from_demo_to_real_time'); setPerformanceValue('switch_from_real_to_demo_time'); } - updateMT5AccountDetails(); }, [is_landing_company_loaded, is_populating_mt5_account_list]); return ( @@ -213,19 +260,19 @@ const CFDsListing = observer(() => { {has_svg_accounts_to_migrate && is_landing_company_loaded && } {is_landing_company_loaded && !is_populating_mt5_account_list ? ( - {/* MT5 */} {combined_cfd_mt5_accounts.map((existing_account, index: number) => { const list_size = combined_cfd_mt5_accounts.length; const track_account_subtitle = existing_account.tracking_name ?? ''; const has_mt5_account_status = - existing_account?.status || hasMaintenanceStatus + existing_account?.status || is_idv_revoked || hasMaintenanceStatus ? getMT5AccountAuthStatus( existing_account?.status, existing_account?.landing_company_short ) : ''; + return ( { }); setProduct(existing_account.product); setAppstorePlatform(existing_account.platform); - setJurisdictionSelectedShortcode(getDefaultJurisdiction()); getTradingPlatformStatus(existing_account.platform); } } else if (existing_account.action_type === 'multi-action') { @@ -304,6 +350,12 @@ const CFDsListing = observer(() => { }); } + if (has_mt5_account_status === MT5_ACCOUNT_STATUS.FAILED && is_eu_user) { + setIsMT5VerificationFailedModal(true); + openFailedVerificationModal(existing_account); + return; + } + startTrade(existing_account.platform, existing_account); } } @@ -314,8 +366,8 @@ const CFDsListing = observer(() => { category: selected_account_type, type: existing_account.market_type, jurisdiction: existing_account.landing_company_short, - product: existing_account.product, }} + openFailedVerificationModal={openFailedVerificationModal} market_type={existing_account?.market_type} /> ); @@ -324,8 +376,6 @@ const CFDsListing = observer(() => { ) : ( )} - - {/* cTrader */} {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && (
@@ -445,7 +495,6 @@ const CFDsListing = observer(() => {
- {/* dxtrade */} {is_landing_company_loaded ? ( available_dxtrade_accounts?.map(account => { const existing_accounts = getExistingAccounts(account.platform, account.market_type); diff --git a/packages/appstore/src/components/containers/__tests__/trading-app-card.spec.tsx b/packages/appstore/src/components/containers/__tests__/trading-app-card.spec.tsx index 3db1b1fe0c2b..dacafe3bec8a 100644 --- a/packages/appstore/src/components/containers/__tests__/trading-app-card.spec.tsx +++ b/packages/appstore/src/components/containers/__tests__/trading-app-card.spec.tsx @@ -36,7 +36,7 @@ describe('', () => { type: 'synthetic', jurisdiction: 'svg', }, - openVerificationDocsListModal: jest.fn(), + openFailedVerificationModal: jest.fn(), market_type: 'synthetic', }; @@ -124,7 +124,7 @@ describe('', () => { }; renderComponent({ props: new_mock_props }); - expect(screen.queryByText(/In review/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Pending verification/)).not.toBeInTheDocument(); expect(screen.queryByText(/No new positions/)).not.toBeInTheDocument(); expect(screen.queryByText(/Account closed/)).not.toBeInTheDocument(); }); diff --git a/packages/appstore/src/components/containers/trading-app-card.tsx b/packages/appstore/src/components/containers/trading-app-card.tsx index 36ab12828091..9997d49af52b 100644 --- a/packages/appstore/src/components/containers/trading-app-card.tsx +++ b/packages/appstore/src/components/containers/trading-app-card.tsx @@ -1,6 +1,6 @@ import React from 'react'; import classNames from 'classnames'; -import getMT5StatusBadgeConfig from '@deriv/account/src/Configs/get-mt5-status-badge-config'; +import getStatusBadgeConfig from '@deriv/account/src/Configs/get-status-badge-config'; import { Text, StatusBadge } from '@deriv/components'; import { Localize } from '@deriv/translations'; import { Analytics } from '@deriv-com/analytics'; @@ -42,32 +42,22 @@ const TradingAppCard = ({ short_code_and_region, mt5_acc_auth_status, selected_mt5_jurisdiction, + openFailedVerificationModal, market_type, is_new = false, }: Actions & BrandConfig & AvailableAccount & TDetailsOfEachMT5Loginid) => { const { common, traders_hub, + ui, modules: { cfd }, + client, } = useStore(); - - const { - is_eu_user, - is_demo_low_risk, - content_flag, - is_real, - selected_account_type, - setVerificationModalOpen, - getMT5AccountKYCStatus, - } = traders_hub; + const { setIsVerificationModalVisible } = ui; + const { is_eu_user, is_demo_low_risk, content_flag, is_real, selected_account_type } = traders_hub; const { current_language, setAppstorePlatform } = common; - const { - is_account_being_created, - setAccountUnavailableModal, - setServerMaintenanceModal, - setJurisdictionSelectedShortcode, - setProduct, - } = cfd; + const { is_account_being_created, setAccountUnavailableModal, setServerMaintenanceModal } = cfd; + const { account_status: { authentication } = {} } = client; const [is_traders_dashboard_tracking_enabled] = useGrowthbookGetFeatureValue({ featureFlag: 'ce_tradershub_dashboard_tracking', @@ -86,8 +76,6 @@ const TradingAppCard = ({ link_to: '', }; - const { text: badge_text, icon: badge_icon, icon_size: badge_size } = getMT5StatusBadgeConfig(mt5_acc_auth_status); - const getAppDescription = () => { if (is_existing_real_ctrader_account) return ''; if (platform === CFD_PLATFORMS.DXTRADE) { @@ -101,6 +89,14 @@ const TradingAppCard = ({ return app_desc; }; + const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig( + mt5_acc_auth_status, + openFailedVerificationModal, + selected_mt5_jurisdiction, + setIsVerificationModalVisible, + { poi_status: authentication?.identity?.status, poa_status: authentication?.document?.status } + ); + const handleStatusBadgeClick = (mt5_acc_auth_status: string) => { switch (mt5_acc_auth_status) { case MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION: @@ -110,15 +106,6 @@ const TradingAppCard = ({ return setServerMaintenanceModal(true); case TRADING_PLATFORM_STATUS.UNAVAILABLE: return setAccountUnavailableModal(true); - case MT5_ACCOUNT_STATUS.PENDING: - case MT5_ACCOUNT_STATUS.FAILED: - case MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION: { - setJurisdictionSelectedShortcode(selected_mt5_jurisdiction?.jurisdiction ?? ''); - setProduct(selected_mt5_jurisdiction?.product ?? ''); - getMT5AccountKYCStatus(); - return setVerificationModalOpen(true); - } - default: } }; @@ -159,11 +146,11 @@ const TradingAppCard = ({ window.open(getStaticUrl(`trade-types/options/digital-options/up-and-down/`)); else; }; - const is_mt5_maintainance_status = [ - TRADING_PLATFORM_STATUS.UNAVAILABLE, - MT5_ACCOUNT_STATUS.UNDER_MAINTENANCE, - ].includes(mt5_acc_auth_status); + const migration_status = + mt5_acc_auth_status === MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION || + mt5_acc_auth_status === MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION; + const is_disabled = !!(mt5_acc_auth_status && !migration_status) && !is_eu_user; const platform_name = is_account_being_created ? name : sub_title ?? name; const is_existing_real_ctrader_account = @@ -238,7 +225,6 @@ const TradingAppCard = ({ account_status={mt5_acc_auth_status} icon={badge_icon} text={badge_text} - icon_size={badge_size} onClick={() => { setAppstorePlatform(platform); handleStatusBadgeClick(mt5_acc_auth_status); @@ -259,7 +245,10 @@ const TradingAppCard = ({ onAction={onAction} is_external={is_external} new_tab={new_tab} - is_buttons_disabled={is_mt5_maintainance_status} + is_buttons_disabled={ + //For MF, we enable the button even if account is not authenticated. Rest of jurisdictions, disable the button for pending, failed and needs verification + is_disabled + } is_account_being_created={!!is_account_being_created} is_real={is_real} /> diff --git a/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx b/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx index d016b9f2f7ed..fc01a34382dc 100644 --- a/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx +++ b/packages/appstore/src/components/currency-switcher-card/real/__tests__/real-account-switcher.spec.tsx @@ -18,7 +18,7 @@ jest.mock('../real-account-card', () => ({ jest.mock('@deriv/account', () => ({ __esModule: true, getStatusBadgeConfig: () => ({ - text: 'In review', + text: 'Pending verification', icon: 'pending', }), })); @@ -61,8 +61,8 @@ describe('RealAccountSwitcher', () => { const { container } = render(, { wrapper }); expect(container).toBeInTheDocument(); - expect(screen.getByText('In review')).toBeInTheDocument(); - expect(screen.getByText('In review')).toHaveClass( + expect(screen.getByText('Pending verification')).toBeInTheDocument(); + expect(screen.getByText('Pending verification')).toHaveClass( 'switcher-status-badge__container switcher-status-badge__container--pending' ); }); diff --git a/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx b/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx index 8300758aa0a1..d88817f35b29 100644 --- a/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx +++ b/packages/appstore/src/components/currency-switcher-card/real/real-account-switcher.tsx @@ -11,24 +11,22 @@ import { useMFAccountStatus } from '@deriv/hooks'; const AccountNeedsVerification = observer(() => { const mf_account_status = useMFAccountStatus(); - const { client, traders_hub, common } = useStore(); - const { account_list, loginid } = client; - const { openModal, setVerificationModalOpen } = traders_hub; - const { setAppstorePlatform } = common; + const { client, traders_hub } = useStore(); + const { account_list, loginid, account_status } = client; + const { openModal, openFailedVerificationModal } = traders_hub; const account = account_list?.find((acc: { loginid?: string }) => loginid === acc?.loginid); const icon_title = account?.title; - const onClickBanner = () => { - setAppstorePlatform(''); - setVerificationModalOpen(true); - }; + const { authentication } = account_status || {}; - const { - text: badge_text, - icon: badge_icon, - icon_size: badge_icon_size, - } = getStatusBadgeConfig(mf_account_status, onClickBanner); + const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig( + mf_account_status, + openFailedVerificationModal, + 'multipliers', + undefined, + { poi_status: authentication?.identity?.status, poa_status: authentication?.document?.status } + ); return ( { return openModal('currency_selection'); }} > - + ); }); diff --git a/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.scss b/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.scss new file mode 100644 index 000000000000..7debd13c6be5 --- /dev/null +++ b/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.scss @@ -0,0 +1,39 @@ +.failed-verification-modal { + @include mobile { + .dc-dialog__dialog { + max-width: 32rem; + } + .dc-dialog__footer { + .dc-btn { + flex: 1; + margin-bottom: 0; + } + + .dc-dialog__button { + max-width: fit-content; + .dc-text.dc-btn__text { + font-size: 1.2rem; + } + } + } + } + &__failed_list { + padding: 1.2rem 2.4rem; + &-item { + display: list-item; + list-style-type: disc; + &:first-child { + margin-bottom: 0.8rem; + } + } + } +} + +.dc-modal__container_failed-verification-modal { + .dc-modal-body { + padding: 0 2.4rem; + } + .dc-modal-footer { + padding: 2.4rem; + } +} diff --git a/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx b/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx new file mode 100644 index 000000000000..d359e637109b --- /dev/null +++ b/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { useStores } from 'Stores'; +import { useHistory } from 'react-router-dom'; +import { localize, Localize } from '@deriv/translations'; +import { Text, Dialog } from '@deriv/components'; +import { isMobile, getAuthenticationStatusInfo, routes, Jurisdiction } from '@deriv/shared'; +import './failed-verification-modal.scss'; + +type TFailedVerificationModal = { + should_resubmit_poi: boolean; + should_resubmit_poa: boolean; + from_account: string; + is_from_multipliers: boolean; + has_mf_mt5_account: boolean; +}; + +const FailedVerificationModalContent = ({ + should_resubmit_poi, + should_resubmit_poa, + from_account, + is_from_multipliers, + has_mf_mt5_account, +}: TFailedVerificationModal) => { + return ( + + + + +
+ {should_resubmit_poi && ( + + + + )} + {should_resubmit_poa && ( + + + + )} +
+ {!is_from_multipliers && has_mf_mt5_account ? ( + + + + ) : ( + + + + )} +
+ ); +}; + +const FailedVerificationModal = () => { + const { + traders_hub, + ui, + modules: { cfd }, + client, + } = useStores(); + const { + is_failed_verification_modal_visible, + mt5_existing_account, + toggleFailedVerificationModalVisibility, + open_failed_verification_for, + startTrade, + } = traders_hub; + const { account_status } = client; + const { toggleCFDVerificationModal, current_list } = cfd; + const { disableApp, enableApp, is_mt5_verification_failed_modal, setIsMT5VerificationFailedModal } = ui; + const is_from_multipliers = open_failed_verification_for === 'multipliers'; + const has_mf_mt5_account = Object.keys(current_list) + .map(key => current_list[key]) + .some(account => account.landing_company_short === Jurisdiction.MALTA_INVEST); + + const { poi_resubmit_for_maltainvest, poi_resubmit_for_bvi_labuan_vanuatu, need_poa_resubmission } = + getAuthenticationStatusInfo(account_status); + const history = useHistory(); + + const closeModal = () => { + setIsMT5VerificationFailedModal(false); + if (is_mt5_verification_failed_modal) { + toggleFailedVerificationModalVisibility(); + startTrade(mt5_existing_account.platform, mt5_existing_account); + return; + } + toggleFailedVerificationModalVisibility(); + }; + + const onConfirmModal = () => { + setIsMT5VerificationFailedModal(false); + toggleFailedVerificationModalVisibility(); + if (is_from_multipliers) { + if (should_resubmit_poi()) { + history.push(routes.proof_of_identity); + } else { + history.push(routes.proof_of_address); + } + } else { + toggleCFDVerificationModal(); + } + }; + + const should_resubmit_poi = () => { + if (is_from_multipliers || open_failed_verification_for === 'maltainvest') { + return poi_resubmit_for_maltainvest; + } + return poi_resubmit_for_bvi_labuan_vanuatu; + }; + const should_resubmit_poa = need_poa_resubmission; + const from_account_label = is_from_multipliers ? localize('Multipliers') : localize('MT5'); + + return ( + + + + ); +}; + +export default observer(FailedVerificationModal); diff --git a/packages/appstore/src/components/modals/failed-veriification-modal/index.ts b/packages/appstore/src/components/modals/failed-veriification-modal/index.ts new file mode 100644 index 000000000000..073d4fdd1689 --- /dev/null +++ b/packages/appstore/src/components/modals/failed-veriification-modal/index.ts @@ -0,0 +1,3 @@ +import FailedVerificationModal from './failed-verification-modal'; + +export default FailedVerificationModal; diff --git a/packages/appstore/src/components/modals/modal-manager.tsx b/packages/appstore/src/components/modals/modal-manager.tsx index 1e5b1177cbe8..e3234b47759e 100644 --- a/packages/appstore/src/components/modals/modal-manager.tsx +++ b/packages/appstore/src/components/modals/modal-manager.tsx @@ -1,17 +1,19 @@ import React from 'react'; +import { useLocation } from 'react-router-dom'; import { observer } from 'mobx-react-lite'; import { useWalletMigration } from '@deriv/hooks'; import { makeLazyLoader, moduleLoader } from '@deriv/shared'; import { Loading } from '@deriv/components'; import { TTradingPlatformAvailableAccount } from './account-type-modal/types'; import { useStores } from 'Stores'; +import { TOpenAccountTransferMeta } from 'Types'; import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import CFDResetPasswordModal from '@deriv/cfd/src/Containers/cfd-reset-password-modal'; -const VerificationDocsListModal = makeLazyLoader( +const FailedVerificationModal = makeLazyLoader( () => moduleLoader( - () => import(/* webpackChunkName: "modal_verification-docs-list-modal" */ './verification-docs-list-modal') + () => import(/* webpackChunkName: "modal_failed-veriification-modal" */ './failed-veriification-modal') ), () => )(); @@ -59,6 +61,17 @@ const CFDServerErrorDialog = makeLazyLoader( () => )(); +const JurisdictionModal = makeLazyLoader( + () => + moduleLoader( + () => + import( + /* webpackChunkName: "modal_cfd_jurisdiction-modal" */ '@deriv/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal' + ) + ), + () => +)(); + const CFDPasswordModal = makeLazyLoader( () => moduleLoader( @@ -92,6 +105,17 @@ const MT5AccountUnavailableModal = makeLazyLoader( () => )(); +const CFDDbviOnBoarding = makeLazyLoader( + () => + moduleLoader( + () => + import( + /* webpackChunkName: "modal_cfd_cfd-dbvi-onboarding" */ '@deriv/cfd/src/Containers/cfd-dbvi-onboarding' + ) + ), + () => +)(); + const CFDTopUpDemoModal = makeLazyLoader( () => moduleLoader( @@ -179,12 +203,16 @@ const ModalManager = () => { const { platform } = common; const { current_list, + enableCFDPasswordModal, is_mt5_trade_modal_visible, + setAccountType, toggleMT5TradeModal, getRealSyntheticAccountsExistingData, getRealFinancialAccountsExistingData, getRealSwapfreeAccountsExistingData, has_cfd_error, + is_jurisdiction_modal_visible, + is_cfd_verification_modal_visible, mt5_migration_error, is_mt5_password_invalid_format_modal_visible, is_cfd_password_modal_enabled, @@ -209,7 +237,7 @@ const ModalManager = () => { is_account_transfer_modal_open, toggleAccountTransferModal, is_real_wallets_upgrade_on, - is_verification_docs_list_modal_visible, + is_failed_verification_modal_visible, is_regulators_compare_modal_visible, is_wallet_migration_failed, is_setup_real_account_or_go_to_demo_modal_visible, @@ -248,6 +276,11 @@ const ModalManager = () => { })); }; + const openRealPasswordModal = (account_type: TOpenAccountTransferMeta) => { + setAccountType(account_type); + enableCFDPasswordModal(); + }; + const existing_accounts_data = (acc_type: TTradingPlatformAvailableAccount['market_type'] | 'synthetic') => { const current_list_keys = Object.keys(current_list); const should_be_enabled = (list_item: TCurrentList) => @@ -281,8 +314,10 @@ const ModalManager = () => { {is_server_maintenance_modal_visible && } {is_account_unavailable_modal_visible && } + {is_jurisdiction_modal_visible && } {should_show_cfd_password_modal && } - {/* a new condition for this hotfix needs to be found */} + {is_cfd_verification_modal_visible && } + {is_ctrader_transfer_modal_visible && } {has_cfd_error && } {(is_top_up_virtual_open || is_top_up_virtual_success) && } @@ -328,7 +363,7 @@ const ModalManager = () => { toggleModal={toggleAccountTransferModal} /> )} - {is_verification_docs_list_modal_visible && } + {is_failed_verification_modal_visible && } {is_wallet_migration_failed && } {(is_eligible || is_real_wallets_upgrade_on || is_in_progress) && } diff --git a/packages/appstore/src/components/modals/verification-docs-list-modal/ListItem.tsx b/packages/appstore/src/components/modals/verification-docs-list-modal/ListItem.tsx deleted file mode 100644 index 965045991544..000000000000 --- a/packages/appstore/src/components/modals/verification-docs-list-modal/ListItem.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { observer, useStore } from '@deriv/stores'; -import { LabelPairedChevronRightMdRegularIcon } from '@deriv/quill-icons'; -import { useHistory } from 'react-router-dom'; -import { Localize } from '@deriv/translations'; -import { Text, StatusBadge } from '@deriv/components'; -import { AUTH_STATUS_CODES } from '@deriv/shared'; -import './verification-docs-list-modal.scss'; -import { useDevice } from '@deriv-com/ui'; -import classNames from 'classnames'; - -type TListItemProps = { - id: string; - text: string; - status?: string | number; - route: string; -}; - -type TAuthStatusCodes = typeof AUTH_STATUS_CODES[keyof typeof AUTH_STATUS_CODES]; - -const getBadgeStatus = (status: TAuthStatusCodes) => { - switch (status) { - case AUTH_STATUS_CODES.VERIFIED: - return { - text: , - icon: 'IcMt5Success', - icon_size: '18', - }; - case AUTH_STATUS_CODES.PENDING: - return { - text: , - icon: 'IcMt5Pending', - icon_size: '12', - }; - case AUTH_STATUS_CODES.REJECTED: - case AUTH_STATUS_CODES.SUSPECTED: - return { - text: , - icon: 'IcMt5Failed', - icon_size: '18', - }; - default: - return { - text: '', - icon: '', - }; - } -}; - -const ListItem = observer(({ id, text, status, route }: TListItemProps) => { - const { text: badge_text, icon: badge_icon, icon_size: badge_size } = getBadgeStatus(status); - const { traders_hub } = useStore(); - const { isMobile } = useDevice(); - const { setVerificationModalOpen } = traders_hub; - const history = useHistory(); - const is_document_verified = status === AUTH_STATUS_CODES.VERIFIED; - - const onClickItem = () => { - if (is_document_verified) { - return; - } - history.push(route); - setVerificationModalOpen(false); - }; - - return ( -
- - - - {status === AUTH_STATUS_CODES.NONE || (id === 'tax' && status === 0) ? ( - - ) : ( -
- - -
- )} -
- ); -}); - -export default ListItem; diff --git a/packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/Listitem.spec.tsx b/packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/Listitem.spec.tsx deleted file mode 100644 index 322b561415f3..000000000000 --- a/packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/Listitem.spec.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import ListItem from '../ListItem'; -import { StoreProvider, mockStore } from '@deriv/stores'; -import { useDevice } from '@deriv-com/ui'; -import { useGetStatus, useIsSelectedMT5AccountCreated } from '@deriv/hooks'; -import { AUTH_STATUS_CODES } from '@deriv/shared'; - -jest.mock('@deriv-com/ui', () => ({ - ...jest.requireActual('@deriv-com/ui'), - useDevice: jest.fn(), -})); - -jest.mock('@deriv/hooks', () => ({ - ...jest.requireActual('@deriv/hooks'), - useGetStatus: jest.fn(), - useIsSelectedMT5AccountCreated: jest.fn(), -})); - -jest.mock('@deriv/quill-icons', () => ({ - ...jest.requireActual('@deriv/quill-icons'), - DerivLightUploadPoiIcon: () =>
DerivLightUploadPoiIcon
, - LabelPairedChevronRightMdRegularIcon: () =>
LabelPairedChevronRightMdRegularIcon
, - DerivLightWaitingPoaIcon: () =>
DerivLightWaitingPoaIcon
, -})); - -jest.mock('@deriv/components', () => ({ - ...jest.requireActual('@deriv/components'), - StatusBadge: () =>
StatusBadge
, -})); - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - AUTH_STATUS_CODES: { - VERIFIED: 'verified', - PENDING: 'pending', - REJECTED: 'rejected', - }, -})); - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useHistory: () => ({ - push: jest.fn(), - location: { search: 'test' }, - }), -})); - -describe('', () => { - const defaultStore = mockStore({ - traders_hub: { - setVerificationModalOpen: jest.fn(), - }, - common: { - platform: 'mt5', - }, - }); - - const renderComponent = (props: { id: string; text: string; status: string; route: string }) => { - render( - - - - ); - }; - - beforeEach(() => { - jest.clearAllMocks(); - (useDevice as jest.Mock).mockReturnValue({ isMobile: false }); - (useGetStatus as jest.Mock).mockReturnValue({ - client_kyc_status: { poi_status: 'verified', poa_status: 'verified', valid_tin: 'verified' }, - }); - (useIsSelectedMT5AccountCreated as jest.Mock).mockReturnValue({ is_selected_MT5_account_created: true }); - }); - - it('should render verified status', () => { - const props = { - id: 'identity', - text: 'Verified', - status: AUTH_STATUS_CODES.VERIFIED, - route: '/proof_of_identity', - }; - renderComponent(props); - - expect(screen.getByText('Verified')).toBeInTheDocument(); - expect(screen.getByText('StatusBadge')).toBeInTheDocument(); - expect(screen.getByText('LabelPairedChevronRightMdRegularIcon')).toBeInTheDocument(); - }); - - it('should render pending status', () => { - const props = { - id: 'identity', - text: 'In review', - status: AUTH_STATUS_CODES.PENDING, - route: '/proof_of_identity', - }; - renderComponent(props); - - expect(screen.getByText('In review')).toBeInTheDocument(); - expect(screen.getByText('StatusBadge')).toBeInTheDocument(); - expect(screen.getByText('LabelPairedChevronRightMdRegularIcon')).toBeInTheDocument(); - }); - - it('should render with Failed status', () => { - const props = { - id: 'identity', - text: 'Failed', - status: AUTH_STATUS_CODES.REJECTED, - route: '/proof_of_identity', - }; - renderComponent(props); - - expect(screen.getByText('Failed')).toBeInTheDocument(); - expect(screen.getByText('StatusBadge')).toBeInTheDocument(); - expect(screen.getByText('LabelPairedChevronRightMdRegularIcon')).toBeInTheDocument(); - }); -}); diff --git a/packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/verification-docs-list-modal.spec.tsx b/packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/verification-docs-list-modal.spec.tsx deleted file mode 100644 index 287ed7e658a7..000000000000 --- a/packages/appstore/src/components/modals/verification-docs-list-modal/__tests__/verification-docs-list-modal.spec.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { mockStore, StoreProvider } from '@deriv/stores'; -import { useDevice } from '@deriv-com/ui'; -import { useGetStatus, useIsSelectedMT5AccountCreated } from '@deriv/hooks'; -import VerificationDocsListModal from '../verification-docs-list-modal'; - -jest.mock('@deriv-com/ui', () => ({ - ...jest.requireActual('@deriv-com/ui'), - useDevice: jest.fn(() => ({ isMobile: false })), -})); - -jest.mock('@deriv/hooks', () => ({ - ...jest.requireActual('@deriv/hooks'), - useGetStatus: jest.fn(() => ({ - client_kyc_status: { poi_status: 'rejected', poa_status: 'verified', valid_tin: 1 }, - })), - useIsSelectedMT5AccountCreated: jest.fn(() => ({ is_selected_MT5_account_created: false })), -})); - -jest.mock('@deriv/quill-icons', () => ({ - ...jest.requireActual('@deriv/quill-icons'), - LabelPairedChevronRightCaptionBoldIcon: () =>
LabelPairedChevronRightCaptionBoldIcon
, - DerivLightUserVerificationIcon: () =>
DerivLightUserVerificationIcon
, -})); - -jest.mock('@deriv/components', () => ({ - ...jest.requireActual('@deriv/components'), - StatusBadge: () =>
StatusBadge
, -})); - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - CFD_PLATFORMS: { MT5: 'mt5' }, -})); - -describe('', () => { - const defaultstore = mockStore({ - traders_hub: { - is_verification_docs_list_modal_visible: true, - setVerificationModalOpen: jest.fn(), - }, - common: { - platform: 'mt5', - }, - }); - const renderComponent = ({ store = defaultstore }) => { - render( - - - - ); - }; - let modal_root_el: HTMLDivElement; - - beforeAll(() => { - modal_root_el = document.createElement('div'); - modal_root_el.setAttribute('id', 'modal_root'); - document.body.appendChild(modal_root_el); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterAll(() => { - document.body.removeChild(modal_root_el); - }); - - it('should render the modal with details when mt5 account is created', () => { - (useIsSelectedMT5AccountCreated as jest.Mock).mockReturnValue({ is_selected_MT5_account_created: true }); - (useDevice as jest.Mock).mockReturnValueOnce({ isMobile: false }); - (useGetStatus as jest.Mock).mockReturnValueOnce({ - client_kyc_status: { - poi_status: 'none', - poa_status: 'verified', - valid_tin: 1, - }, - }); - renderComponent({}); - expect(screen.getByText('Verification required')).toBeInTheDocument(); - expect(screen.getByText('Your account needs verification.')).toBeInTheDocument(); - expect(screen.getByText('Proof of identity')).toBeInTheDocument(); - expect(screen.getByText('Proof of address')).toBeInTheDocument(); - expect(screen.queryByText('Additional information')).not.toBeInTheDocument(); - }); - - it('should render the modal with details when mt5 account is not created', () => { - (useIsSelectedMT5AccountCreated as jest.Mock).mockReturnValue({ is_selected_MT5_account_created: false }); - (useGetStatus as jest.Mock).mockReturnValueOnce({ - client_kyc_status: { - poi_status: 'pending', - poa_status: 'none', - valid_tin: 0, - }, - }); - renderComponent({}); - expect(screen.getByText('DerivLightUserVerificationIcon')).toBeInTheDocument(); - expect(screen.getByText('Complete your profile')).toBeInTheDocument(); - expect( - screen.getByText('Confirm your details to open the account. After verification, you can begin trading.') - ).toBeInTheDocument(); - expect(screen.getByText('Proof of identity')).toBeInTheDocument(); - expect(screen.getByText('Proof of address')).toBeInTheDocument(); - expect(screen.queryByText('Additional information')).toBeInTheDocument(); - }); - it('should render the modal with details when platform is not mt5', () => { - (useIsSelectedMT5AccountCreated as jest.Mock).mockReturnValue({ is_selected_MT5_account_created: false }); - (useGetStatus as jest.Mock).mockReturnValueOnce({ - client_kyc_status: { - poi_status: 'pending', - poa_status: 'none', - }, - }); - const mock_store = mockStore({ - traders_hub: { - is_verification_docs_list_modal_visible: true, - setVerificationModalOpen: jest.fn(), - }, - common: { - platform: '', - }, - }); - renderComponent({ store: mock_store }); - expect(screen.getByText('DerivLightUserVerificationIcon')).toBeInTheDocument(); - expect(screen.getByText('Verification required')).toBeInTheDocument(); - expect(screen.getByText('Your account needs verification.')).toBeInTheDocument(); - expect(screen.getByText('Proof of identity')).toBeInTheDocument(); - expect(screen.getByText('Proof of address')).toBeInTheDocument(); - expect(screen.queryByText('Additional information ')).not.toBeInTheDocument(); - }); -}); diff --git a/packages/appstore/src/components/modals/verification-docs-list-modal/index.ts b/packages/appstore/src/components/modals/verification-docs-list-modal/index.ts deleted file mode 100644 index d508b6390de5..000000000000 --- a/packages/appstore/src/components/modals/verification-docs-list-modal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import VerificationDocsListModal from './verification-docs-list-modal'; - -export default VerificationDocsListModal; diff --git a/packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.scss b/packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.scss deleted file mode 100644 index 458fbf1a79e5..000000000000 --- a/packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.scss +++ /dev/null @@ -1,55 +0,0 @@ -.verification-docs-list-modal { - &__content { - display: flex; - flex-direction: column; - padding: 2.4rem; - justify-content: center; - align-items: center; - &-list { - width: 100%; - &-item { - cursor: pointer; - display: flex; - min-height: 5.6rem; - align-items: center; - border-radius: 2 * $BORDER_RADIUS; - background: var(--icon-grey-background); - padding: 0.8rem 1.6rem; - justify-content: space-between; - margin: 1.6rem auto; - - &:hover { - background: var(--state-normal); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); - transition-delay: 0.1s; - } - - &--disabled { - cursor: not-allowed; - pointer-events: none; - } - } - } - } - &__card { - display: flex; - gap: 0.8rem; - align-items: center; - &--icon { - width: 2.4rem; - height: 2.4rem; - } - } - &__status-badge { - text-decoration: none; - } -} - -.dc-modal__container_verification-docs-list-modal { - .dc-modal-body { - padding: 0 2.4rem; - } - .dc-modal-footer { - padding: 2.4rem; - } -} diff --git a/packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.tsx b/packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.tsx deleted file mode 100644 index a71d9553b677..000000000000 --- a/packages/appstore/src/components/modals/verification-docs-list-modal/verification-docs-list-modal.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { Suspense } from 'react'; -import { useDevice } from '@deriv-com/ui'; -import { DerivLightUserVerificationIcon } from '@deriv/quill-icons'; -import { observer, useStore } from '@deriv/stores'; -import { localize, Localize } from '@deriv/translations'; -import { Text, Modal, UILoader, MobileDialog } from '@deriv/components'; -import { routes, CFD_PLATFORMS, AUTH_STATUS_CODES } from '@deriv/shared'; -import { useGetStatus, useIsSelectedMT5AccountCreated } from '@deriv/hooks'; -import ListItem from './ListItem'; -import './verification-docs-list-modal.scss'; - -type TItems = { - id: string; - text: string; - status: string | number; - route: string; -}; - -const VerificationDocsListModalContent = observer(() => { - const { - common: { platform }, - } = useStore(); - const { isMobile } = useDevice(); - const { client_kyc_status } = useGetStatus(); - const { is_selected_MT5_account_created } = useIsSelectedMT5AccountCreated(); - if (!client_kyc_status) return null; - const { poi_status, poa_status, valid_tin } = client_kyc_status; - - const items: TItems[] = [ - poi_status && { - id: 'identity', - text: 'Proof of identity', - status: poi_status, - route: routes.proof_of_identity, - }, - poa_status && { - id: 'address', - text: 'Proof of address', - status: poa_status, - route: routes.proof_of_address, - }, - valid_tin === 0 && { - id: 'tax', - text: 'Additional information', - status: valid_tin, - route: routes.personal_details, - }, - ].filter(Boolean) as TItems[]; - return ( -
- - - {platform === CFD_PLATFORMS.MT5 && !is_selected_MT5_account_created ? ( - - ) : ( - - )} - -
- {items.map(item => ( - - ))} -
-
- ); -}); - -const VerificationDocsListModal = observer(() => { - const { traders_hub, common } = useStore(); - const { is_verification_docs_list_modal_visible, setVerificationModalOpen } = traders_hub; - const { platform } = common; - const { is_selected_MT5_account_created } = useIsSelectedMT5AccountCreated(); - const { isMobile } = useDevice(); - const getTitle = () => - platform === CFD_PLATFORMS.MT5 && !is_selected_MT5_account_created ? ( - - ) : ( - - ); - return ( - }> - {!isMobile ? ( - setVerificationModalOpen(false)} - title={getTitle()} - width='44rem' - should_header_stick_body={false} - has_close_icon - > - - - ) : ( - setVerificationModalOpen(false)} - > - - - )} - - ); -}); - -export default VerificationDocsListModal; diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts index 40d00326a0b5..4813d0bb1cd4 100644 --- a/packages/appstore/src/types/common.types.ts +++ b/packages/appstore/src/types/common.types.ts @@ -60,10 +60,10 @@ export type TDetailsOfEachMT5Loginid = DetailsOfEachMT5Loginid & { selected_mt5_jurisdiction?: TOpenAccountTransferMeta & TJurisdictionData & { platform?: string; - product?: string; }; platform?: TPlatform; product?: 'swap_free' | 'zero_spread' | 'derivx' | 'ctrader'; + openFailedVerificationModal?: (from_account: string) => void; market_type: NonNullable | TMarketType; client_kyc_status?: { poa_status: string; diff --git a/packages/bot-web-ui/package.json b/packages/bot-web-ui/package.json index 95cce2b76a28..07e04b3a2f70 100644 --- a/packages/bot-web-ui/package.json +++ b/packages/bot-web-ui/package.json @@ -79,7 +79,7 @@ "@deriv/components": "^1.0.0", "@deriv/deriv-charts": "^2.5.1", "@deriv/hooks": "^1.0.0", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", diff --git a/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/stepper.scss b/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/stepper.scss index 1c9fe1cddf67..3204c86d6757 100644 --- a/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/stepper.scss +++ b/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/stepper.scss @@ -3,11 +3,9 @@ } @mixin margin-top-for-size($size) { - margin-top: calculate-margin-top( - var(--component-stepper-node-size-#{$size}), + margin-top: calculate-margin-top(var(--component-stepper-node-size-#{$size}), var(--component-stepper-spacing-3xs), - var(--semantic-typography-body-#{$size}-bold-default-lineHeight) - ) !important; + var(--semantic-typography-body-#{$size}-bold-default-lineHeight)) !important; } .quill-steps-container { @@ -65,6 +63,7 @@ } &__label { + &--disabled { color: var(--component-stepper-node-border-color-incomplete) !important; } @@ -92,11 +91,11 @@ } &--size-sm { - height: calc((var(--component-stepper-track-height-sm) + var(--component-stepper-track-height-sm)) * 2); + height: calc((var(--component-stepper-track-height-sm) + var(--component-stepper-track-height-sm)) * 2) } &--size-md { - height: calc(var(--component-stepper-track-height-md) + var(--component-stepper-track-height-md)); + height: calc(var(--component-stepper-track-height-md) + var(--component-stepper-track-height-md)) } } -} +} \ No newline at end of file diff --git a/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/strategy-template-picker.scss b/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/strategy-template-picker.scss index 5bf7d60a3e0b..56de73442b34 100644 --- a/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/strategy-template-picker.scss +++ b/packages/bot-web-ui/src/pages/bot-builder/quick-strategy/form-wrappers/upgraded-qs-v2/strategy-template-picker.scss @@ -185,4 +185,4 @@ right: 2.4rem; } } -} +} \ No newline at end of file diff --git a/packages/cashier/src/components/cashier-locked/cashier-locked.tsx b/packages/cashier/src/components/cashier-locked/cashier-locked.tsx index d99a4d376f0d..6df39d881f7a 100644 --- a/packages/cashier/src/components/cashier-locked/cashier-locked.tsx +++ b/packages/cashier/src/components/cashier-locked/cashier-locked.tsx @@ -11,7 +11,7 @@ import { } from '@deriv/hooks'; import getMessage from './cashier-locked-provider'; import './cashier-locked.scss'; -import { ACCOUNT_BADGE_STATUS } from '@deriv/shared'; +import { MT5_ACCOUNT_STATUS } from '@deriv/shared'; const CashierLocked = observer(() => { const { @@ -48,7 +48,7 @@ const CashierLocked = observer(() => { is_deposit_locked, is_withdrawal_locked, is_identity_verification_needed, - is_pending_verification: mf_account_status === ACCOUNT_BADGE_STATUS.PENDING, + is_pending_verification: mf_account_status === MT5_ACCOUNT_STATUS.PENDING, is_duplicate_dob_phone, is_account_to_be_closed_by_residence, account_time_of_closure, diff --git a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx index cd11bfbd12be..998d5f64a415 100644 --- a/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx +++ b/packages/cashier/src/pages/account-transfer/account-transfer-form/account-transfer-form.tsx @@ -58,7 +58,7 @@ const AccountOption = ({ if (is_pending_verification) { return ( - + ); } else if (is_verification_needed) { @@ -70,7 +70,7 @@ const AccountOption = ({ } else if (is_verification_failed) { return ( - + ); } else if (is_server_maintenance) { diff --git a/packages/cashier/src/stores/account-transfer-store.ts b/packages/cashier/src/stores/account-transfer-store.ts index 946c78b42a29..071a2b74d9a5 100644 --- a/packages/cashier/src/stores/account-transfer-store.ts +++ b/packages/cashier/src/stores/account-transfer-store.ts @@ -393,7 +393,6 @@ export default class AccountTransferStore { platform: account.account_type, is_eu: this.root_store.client.is_eu, product: account.product, - shortcode: account.landing_company_short, })} ${this.root_store.client.is_eu ? '' : non_eu_accounts}` : `${cfd_text_display} ${ getCFDAccountDisplay({ diff --git a/packages/cfd/build/webpack.config.js b/packages/cfd/build/webpack.config.js index 8bd4952fe5ab..bb568b4fcb1a 100644 --- a/packages/cfd/build/webpack.config.js +++ b/packages/cfd/build/webpack.config.js @@ -10,7 +10,9 @@ module.exports = function (env) { entry: { cfd: path.resolve(__dirname, '../src', 'index.tsx'), CFDStore: 'Stores/Modules/CFD/cfd-store', + JurisdictionModal: 'Containers/jurisdiction-modal/jurisdiction-modal.tsx', CFDPasswordModal: 'Containers/cfd-password-modal.tsx', + CFDDbviOnBoarding: 'Containers/cfd-dbvi-onboarding.tsx', CFDResetPasswordModal: 'Containers/cfd-reset-password-modal.tsx', CFDServerErrorDialog: 'Containers/cfd-server-error-dialog.tsx', CFDTopUpDemoModal: 'Containers/cfd-top-up-demo-modal.tsx', diff --git a/packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx b/packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx new file mode 100644 index 000000000000..76ca9e8038ad --- /dev/null +++ b/packages/cfd/src/Components/__tests__/cfd-poa.spec.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { mockStore } from '@deriv/stores'; +import CFDPOA from '../cfd-poa'; +import CFDProviders from '../../cfd-providers'; + +const mock_kyc_auth_status_response = { + kyc_auth_status: { + address: { + supported_documents: ['utility_bill', 'affidavit', 'poa_others'], + }, + }, + isLoading: false, + isSuccess: false, +}; + +jest.mock('@deriv/account/src/Components/forms/personal-details-form.jsx', () => + jest.fn(() =>
PersonalDetailsForm
) +); +jest.mock('@deriv/account/src/Components/poa/common-mistakes/common-mistake-examples', () => + jest.fn(() =>
CommonMistakeExamples
) +); + +jest.mock('@deriv/account/src/hooks', () => ({ + useKycAuthStatus: jest.fn(() => mock_kyc_auth_status_response), +})); + +jest.mock('@deriv/account/src/Components/leave-confirm', () => jest.fn(() =>
LeaveConfirm
)); +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + validPostCode: jest.fn(() => true), + validLength: jest.fn(() => true), + validLetterSymbol: jest.fn(() => true), + validAddress: jest.fn(() => ({ + is_ok: true, + })), + WS: { + authorized: { + storage: { + getSettings: jest.fn().mockResolvedValue({ + get_settings: { + address_line_1: 'test address_line_1', + address_line_2: 'test address_line_2', + address_city: 'test address_city', + address_state: 'test address_state', + address_postcode: 'test address_postcode', + }, + }), + getAccountStatus: jest.fn().mockResolvedValue({ + get_account_status: { + authentication: { + document: { + status: 'none', + }, + identity: { + status: 'none', + }, + }, + }, + }), + }, + }, + setSettings: jest.fn(() => Promise.resolve({ error: '' })), + wait: jest.fn(() => Promise.resolve([])), + getSocket: jest.fn().mockReturnValue({}), + }, +})); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useFileUploader: jest.fn(() => ({ + upload: jest.fn(), + })), +})); + +describe('', () => { + const mock_props: React.ComponentProps = { + index: 0, + onSave: jest.fn(), + onSubmit: jest.fn(), + }; + const mock_store = mockStore({ + client: { + account_settings: { + address_line_1: 'test address_line_1', + address_line_2: 'test address_line_2', + address_city: 'test address_city', + address_state: 'test address_state', + address_postcode: 'test address_postcode', + country_code: 'in', + }, + fetchResidenceList: jest.fn(() => Promise.resolve('')), + getChangeableFields: jest.fn(() => []), + }, + }); + + it('should render CFDPOA and trigger buttons', async () => { + render( + + + + + + ); + + expect(await screen.findByText('PersonalDetailsForm')).toBeInTheDocument(); + + const button = screen.getByRole('button'); + expect(button).toHaveTextContent('Continue'); + expect(button).toBeDisabled(); + + const uploader = screen.getByTestId('dt_file_upload_input'); + const file = new File(['test file'], 'test_file.png', { type: 'image/png' }); + const dt_document_type = screen.getByRole('textbox', { name: /Type of document/ }); + fireEvent.change(dt_document_type, { target: { value: 'utility_bill' } }); + await waitFor(() => { + userEvent.upload(uploader, file); + }); + + expect(button).toBeEnabled(); + + userEvent.click(button); + + await waitFor(() => { + expect(mock_props.onSave).toHaveBeenCalled(); + expect(mock_props.onSubmit).toHaveBeenCalled(); + }); + }); + + it('should render duplicate document error message if has_submitted_duplicate_poa is true ', async () => { + const setHasSubmittedDuplicatePOA = jest.fn(); + jest.spyOn(React, 'useState').mockImplementationOnce(() => [true, setHasSubmittedDuplicatePOA]); + + render( + + + + + + ); + expect(screen.getByText(/we could not verify your proof of address/i)).toBeInTheDocument(); + expect(screen.getByText(/proof of address documents upload failed/i)).toBeInTheDocument(); + expect(screen.getByText(/try again/i)).toBeInTheDocument(); + const submit_btn = screen.getByRole('button', { name: 'Try again' }); + + userEvent.click(submit_btn); + expect(setHasSubmittedDuplicatePOA).toHaveBeenCalledWith(false); + }); +}); diff --git a/packages/cfd/src/Components/__tests__/cfd-poi.spec.js b/packages/cfd/src/Components/__tests__/cfd-poi.spec.js new file mode 100644 index 000000000000..cb081f2cb04a --- /dev/null +++ b/packages/cfd/src/Components/__tests__/cfd-poi.spec.js @@ -0,0 +1,106 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import CFDPOI from '../cfd-poi'; +import CFDProviders from '../../cfd-providers'; +import { mockStore } from '@deriv/stores'; + +jest.mock('@deriv/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx', () => + jest.fn(() =>
ProofOfIdentityContainerForMt5
) +); + +describe('', () => { + let props; + let mockRootStore; + + beforeEach(() => { + mockRootStore = { + client: { + account_status: { + authentication: { + attempts: { count: 0, history: {}, latest: null }, + identity: { + services: { idv: {}, manual: {}, onfido: {} }, + status: 'none', + }, + document: { + status: 'none', + }, + needs_verification: [], + ownership: { requests: [], status: 'none' }, + }, + currency_config: { + USD: { + is_deposit_suspended: 0, + is_withdrawal_suspended: 0, + }, + }, + prompt_client_to_authenticate: 0, + risk_classification: 'low', + status: [ + 'allow_document_upload', + 'crs_tin_information', + 'deposit_attempt', + 'financial_information_not_complete', + 'trading_experience_not_complete', + ], + }, + is_switching: false, + is_virtual: false, + should_allow_authentication: true, + fetchResidenceList: jest.fn(), + }, + common: { + routeBackInApp: jest.fn(), + app_routing_history: [ + { + action: 'POP', + hash: '#real', + pathname: '/mt5', + search: '', + state: undefined, + }, + { + action: 'PUSH', + hash: '', + pathname: '/', + search: '', + state: undefined, + }, + ], + }, + notifications: { + refreshNotifications: jest.fn(), + }, + }; + + props = { + addNotificationByKey: jest.fn(), + authentication_status: { + document_status: '', + identity_status: '', + }, + form_error: undefined, + height: 'auto', + index: 1, + is_loading: false, + is_switching: false, + is_virtual: false, + is_eu_user: false, + onCancel: jest.fn(), + onSave: jest.fn(), + onSubmit: jest.fn(), + removeNotificationByKey: jest.fn(), + removeNotificationMessage: jest.fn(), + value: { + poi_state: 'unknown', + }, + }; + }); + + it('should render ProofOfIdentityContainerForMt5', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + expect(screen.getByText('ProofOfIdentityContainerForMt5')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Components/cfd-account-card.tsx b/packages/cfd/src/Components/cfd-account-card.tsx index 0aa68ef8bb61..d4a669254c27 100644 --- a/packages/cfd/src/Components/cfd-account-card.tsx +++ b/packages/cfd/src/Components/cfd-account-card.tsx @@ -21,7 +21,7 @@ import { TCFDAccountCard, TCFDAccountCardActionProps, TTradingPlatformAccounts, - TModifiedTradingPlatformAvailableAccount, + TTradingPlatformAvailableAccount, } from './props.types'; import { CFD_PLATFORMS, CATEGORY, MARKET_TYPE } from '../Helpers/cfd-config'; import { TMarketTypeSynthetic, TAccountCategory } from '../types/market-type.types'; @@ -177,6 +177,7 @@ const CFDAccountCardComponent = observer( setAccountType, setJurisdictionSelectedShortcode, setMT5TradeAccount, + toggleCFDVerificationModal, toggleMT5TradeModal, } = useCfdStore(); @@ -188,10 +189,10 @@ const CFDAccountCardComponent = observer( platform === CFD_PLATFORMS.MT5 && (type.category === CATEGORY.DEMO ? isEligibleForMoreDemoMt5Svg( - type.type as TModifiedTradingPlatformAvailableAccount['market_type'] | TMarketTypeSynthetic + type.type as TTradingPlatformAvailableAccount['market_type'] | TMarketTypeSynthetic ) && !!existing_data : isEligibleForMoreRealMt5( - type.type as TModifiedTradingPlatformAvailableAccount['market_type'] | TMarketTypeSynthetic + type.type as TTradingPlatformAvailableAccount['market_type'] | TMarketTypeSynthetic ) && !!existing_data); const platform_icon = show_eu_related_content && platform === CFD_PLATFORMS.MT5 ? CFD_PLATFORMS.CFD : type.type; @@ -271,6 +272,7 @@ const CFDAccountCardComponent = observer( }); setJurisdictionSelectedShortcode(landing_company_short); updateAccountStatus(); + toggleCFDVerificationModal(); }} primary large diff --git a/packages/cfd/src/Components/cfd-poa.tsx b/packages/cfd/src/Components/cfd-poa.tsx new file mode 100644 index 000000000000..30e9a0de87f2 --- /dev/null +++ b/packages/cfd/src/Components/cfd-poa.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { FormikValues } from 'formik/dist/types'; +import ProofOfAddressForm from '@deriv/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form'; +import IconMessageContent from '@deriv/account/src/Components/icon-message-content'; +import { Localize } from '@deriv/translations'; +import { Button, Text, Icon } from '@deriv/components'; + +type TCFDPOA = { + index: number; + onSave: (index: number, values: FormikValues) => void; + onSubmit: (index: number, values: FormikValues) => void; +}; + +const CFDPOA = ({ index, onSave, onSubmit }: TCFDPOA) => { + const [has_submitted_duplicate_poa, setHasSubmittedDuplicatePOA] = React.useState(false); + const onSubmitForCFDModal = (values: FormikValues, has_submitted_duplicate_poa = false) => { + if (has_submitted_duplicate_poa) { + setHasSubmittedDuplicatePOA(has_submitted_duplicate_poa); + } else { + onSave(index, values); + onSubmit(index, values); + } + }; + + return ( + + {has_submitted_duplicate_poa ? ( + } + text={} + icon={} + > + + + ) : ( +
+ +
+ )} +
+ ); +}; + +export default CFDPOA; diff --git a/packages/cfd/src/Components/cfd-poi.tsx b/packages/cfd/src/Components/cfd-poi.tsx new file mode 100644 index 000000000000..67ced113919b --- /dev/null +++ b/packages/cfd/src/Components/cfd-poi.tsx @@ -0,0 +1,49 @@ +// @ts-expect-error remove this line when ProofOfIdentityContainerForMt5 is converted to TS +import ProofOfIdentityContainerForMt5 from '@deriv/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx'; +import React from 'react'; +import { useStore, observer } from '@deriv/stores'; +import type { TCoreStores } from '@deriv/stores/types'; + +type TCFDValue = { + poi_state: string; +}; + +type TFormValues = { + poi_state?: string; +}; + +export type TCFDPOIProps = { + index: number; + onSubmit: (index: number, value: TCFDValue) => void; + value: TCFDValue; + addNotificationMessageByKey: TCoreStores['notifications']['addNotificationMessageByKey']; + height: string; + onSave: (index: number, values: TFormValues) => void; + removeNotificationByKey: TCoreStores['notifications']['removeNotificationByKey']; + removeNotificationMessage: TCoreStores['notifications']['removeNotificationMessage']; + jurisdiction_selected_shortcode: string; +}; + +const CFDPOI = observer(({ index, onSave, onSubmit, ...props }: TCFDPOIProps) => { + const { client } = useStore(); + const { account_settings, residence_list } = client; + + const [poi_state, setPOIState] = React.useState('none'); + const citizen = account_settings?.citizen || account_settings?.country_code; + const citizen_data = residence_list?.find(item => item.value === citizen); + + const onStateChange = (status: string) => { + setPOIState(status); + onSave(index, { poi_state: status }); + onSubmit(index, { poi_state }); + }; + return ( + onStateChange(status)} + citizen_data={citizen_data} + /> + ); +}); + +export default CFDPOI; diff --git a/packages/cfd/src/Components/props.types.ts b/packages/cfd/src/Components/props.types.ts index 0f354d3c513b..e35eb267cf47 100644 --- a/packages/cfd/src/Components/props.types.ts +++ b/packages/cfd/src/Components/props.types.ts @@ -1,5 +1,4 @@ import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; -import { TAdditionalDetailsOfEachMT5Loginid, TTradingPlatformAvailableAccount } from '@deriv/stores/types'; import { TCFDPasswordReset } from '../Containers/props.types'; @@ -9,7 +8,7 @@ export type TCFDPlatform = 'dxtrade' | 'mt5' | 'ctrader'; export type TCFDsPlatformType = 'dxtrade' | 'mt5' | 'ctrader' | ''; -export type TProducts = 'swap_free' | 'zero_spread' | 'ctrader' | 'derivx' | 'financial' | 'standard' | 'stp'; +export type TProducts = 'swap_free' | 'zero_spread' | 'ctrader' | 'derivx'; export type TShortcode = DetailsOfEachMT5Loginid['landing_company_short']; @@ -76,11 +75,31 @@ export type TCFDAccountCardActionProps = { setShouldShowCooldownModal: (value: boolean) => void; }; +export type TTradingPlatformAvailableAccount = { + market_type: 'financial' | 'gaming' | 'all'; + name: string; + requirements: { + after_first_deposit: { + financial_assessment: string[]; + }; + compliance: { + mt5: string[]; + tax_information: string[]; + }; + signup: string[]; + }; + shortcode?: TShortcode; + sub_account_type: string; + account_type?: 'real' | 'demo'; + landing_company_short?: TShortcode; + max_count?: number; + available_count?: number; +}; + export type TModifiedTradingPlatformAvailableAccount = Omit & { platform?: 'mt5' | 'dxtrade' | 'ctrader'; market_type: TTradingPlatformAvailableAccount['market_type'] | 'synthetic'; - account_type?: 'real' | 'demo'; - landing_company_short?: TShortcode; + product?: TProducts; }; export type TCardFlipStatus = { @@ -245,8 +264,7 @@ export type TInstrumentsIcon = { | 'Cryptocurrencies' | 'ETF'; text: string; - id?: string; - highlighted?: boolean; + highlighted: boolean; className?: string; is_asterisk?: boolean; }; @@ -261,7 +279,7 @@ export type TJurisdictionData = { jurisdiction?: 'bvi' | 'labuan' | 'svg' | 'vanuatu' | 'maltainvest' | 'malta'; }; -export type TDetailsOfEachMT5Loginid = TAdditionalDetailsOfEachMT5Loginid & { +export type TDetailsOfEachMT5Loginid = DetailsOfEachMT5Loginid & { display_login?: string; white_label_links?: { webtrader_url: string; @@ -275,6 +293,7 @@ export type TDetailsOfEachMT5Loginid = TAdditionalDetailsOfEachMT5Loginid & { selected_mt5_jurisdiction?: TOpenAccountTransferMeta & TJurisdictionData & { platform?: string; - product?: string; }; + + openFailedVerificationModal?: (from_account: string) => void; }; diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-bvi-contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-bvi-contents.ts new file mode 100644 index 000000000000..323315b0bec4 --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-bvi-contents.ts @@ -0,0 +1,101 @@ +import { localize } from '@deriv/translations'; +import { TJurisdictionCardItems, TJurisdictionCardParams } from 'Components/props.types'; + +export const getJurisdictionBviContents = ({ + toggleDynamicLeverage, +}: TJurisdictionCardParams): TJurisdictionCardItems => ({ + is_over_header_available: false, + header: localize('British Virgin Islands'), + synthetic_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex (standard), stock indices, commodities, cryptocurrencies, stocks, ETFs, synthetic indices, basket indices and derived FX' + ), + title_indicators: { + type: 'displayText', + display_text: localize('210+'), + display_text_skin_color: 'red-darker', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('British Virgin Islands Financial Services Commission (License no. SIBA/L/18/1114)'), + }, + ], + financial_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs' + ), + title_indicators: { + type: 'displayText', + display_text: localize('170+'), + display_text_skin_color: 'red-light', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + clickable_description: [ + { + type: 'link', + text: localize('Dynamic Leverage'), + onClick: toggleDynamicLeverage, + }, + ], + }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + title_indicators: { + type: 'displayText', + display_text: localize('0.2 pips'), + display_text_skin_color: 'violet-dark', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('British Virgin Islands Financial Services Commission (License no. SIBA/L/18/1114)'), + }, + ], + synthetic_verification_docs: ['document_number', 'name_and_address'], + financial_verification_docs: ['document_number', 'name_and_address'], +}); diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-contents.ts new file mode 100644 index 000000000000..8f4e92bb6c48 --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-contents.ts @@ -0,0 +1,22 @@ +import { TJurisdictionCardItems, TJurisdictionCardParams } from 'Components/props.types'; +import { getJurisdictionBviContents } from './jurisdiction-bvi-contents'; +import { getJurisdictionLabuanContents } from './jurisdiction-labuan-contents'; +import { getJurisdictionSvgContents } from './jurisdiction-svg-contents'; +import { getJurisdictionVanuatuContents } from './jurisdiction-vanuatu-contents'; +import { getJurisdictionMaltainvestContents } from './jurisdiction_maltainvest_contents'; + +type TJurisdictionContent = { + svg: TJurisdictionCardItems; + vanuatu: TJurisdictionCardItems; + labuan: TJurisdictionCardItems; + maltainvest: TJurisdictionCardItems; + bvi: TJurisdictionCardItems; +}; + +export const getJurisdictionContents = (params: TJurisdictionCardParams): TJurisdictionContent => ({ + svg: getJurisdictionSvgContents(params), + bvi: getJurisdictionBviContents(params), + vanuatu: getJurisdictionVanuatuContents(params), + labuan: getJurisdictionLabuanContents(), + maltainvest: getJurisdictionMaltainvestContents(), +}); diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-labuan-contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-labuan-contents.ts new file mode 100644 index 000000000000..8ba60e64f76a --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-labuan-contents.ts @@ -0,0 +1,76 @@ +import { localize } from '@deriv/translations'; +import { TJurisdictionCardItems } from 'Components/props.types'; + +export const getJurisdictionLabuanContents = (): TJurisdictionCardItems => ({ + over_header: localize('Straight-through processing'), + is_over_header_available: true, + header: localize('Labuan'), + synthetic_contents: [ + { key: 'assets', title: localize('Assets'), description: localize('Forex and Cryptocurrencies') }, + { key: 'leverage', title: localize('Leverage') }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Labuan Financial Services Authority (licence no. MB/18/0024)'), + }, + ], + financial_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize('Forex (standard/exotic) and cryptocurrencies'), + title_indicators: { + type: 'displayText', + display_text: localize('80+'), + display_text_skin_color: 'red-dark', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:100'), + display_text_skin_color: 'yellow-dark', + }, + }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + title_indicators: { + type: 'displayText', + display_text: localize('0.6 pips'), + display_text_skin_color: 'violet-dark', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Labuan Financial Services Authority (licence no. MB/18/0024)'), + }, + ], + synthetic_verification_docs: ['document_number', 'name_and_address'], + financial_verification_docs: ['document_number', 'name_and_address'], +}); diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-svg-contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-svg-contents.ts new file mode 100644 index 000000000000..4fc1d769b1b4 --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-svg-contents.ts @@ -0,0 +1,135 @@ +import { localize } from '@deriv/translations'; +import { TJurisdictionCardItems, TJurisdictionCardParams } from 'Components/props.types'; + +export const getJurisdictionSvgContents = ({ + toggleDynamicLeverage, +}: TJurisdictionCardParams): TJurisdictionCardItems => ({ + is_over_header_available: false, + header: localize('St. Vincent & Grenadines'), + synthetic_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex (standard), stock indices, commodities, cryptocurrencies, stocks, ETFs, synthetic indices, basket indices and derived FX' + ), + title_indicators: { + type: 'displayText', + display_text: localize('210+'), + display_text_skin_color: 'red-darker', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + description: localize( + 'You will need to submit proof of identity and address once you reach certain thresholds.' + ), + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Deriv (SVG) LLC (company no. 273 LLC 2020)'), + }, + ], + financial_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs' + ), + title_indicators: { + type: 'displayText', + display_text: localize('170+'), + display_text_skin_color: 'red-light', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + clickable_description: [ + { + type: 'link', + text: localize('Dynamic Leverage'), + onClick: toggleDynamicLeverage, + }, + ], + }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + title_indicators: { + type: 'displayText', + display_text: localize('0.2 pips'), + display_text_skin_color: 'violet-dark', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + description: localize( + 'You will need to submit proof of identity and address once you reach certain thresholds.' + ), + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Deriv (SVG) LLC (company no. 273 LLC 2020)'), + }, + ], + swapfree_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex, stocks, stock indices, commodities, cryptocurrencies, ETFs and synthetic indices' + ), + title_indicators: { + type: 'displayText', + display_text: localize('40+'), + display_text_skin_color: 'red-darker', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + description: localize( + 'You will need to submit proof of identity and address once you reach certain thresholds.' + ), + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Deriv (SVG) LLC (company no. 273 LLC 2020)'), + }, + ], + synthetic_verification_docs: [], + financial_verification_docs: [], +}); diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-vanuatu-contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-vanuatu-contents.ts new file mode 100644 index 000000000000..ff107e5edd2b --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-vanuatu-contents.ts @@ -0,0 +1,101 @@ +import { localize } from '@deriv/translations'; +import { TJurisdictionCardItems, TJurisdictionCardParams } from 'Components/props.types'; + +export const getJurisdictionVanuatuContents = ({ + toggleDynamicLeverage, +}: TJurisdictionCardParams): TJurisdictionCardItems => ({ + is_over_header_available: false, + header: localize('Vanuatu'), + synthetic_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex (standard), stock indices, commodities, cryptocurrencies, stocks, ETFs, synthetic indices, basket indices and derived FX' + ), + title_indicators: { + type: 'displayText', + display_text: localize('210+'), + display_text_skin_color: 'red-darker', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Vanuatu Financial Services Commission'), + }, + ], + financial_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize( + 'Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs' + ), + title_indicators: { + type: 'displayText', + display_text: localize('170+'), + display_text_skin_color: 'red-light', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:1000'), + display_text_skin_color: 'yellow-light', + }, + clickable_description: [ + { + type: 'link', + text: localize('Dynamic Leverage'), + onClick: toggleDynamicLeverage, + }, + ], + }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + title_indicators: { + type: 'displayText', + display_text: localize('0.2 pips'), + display_text_skin_color: 'violet-dark', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Vanuatu Financial Services Commission'), + }, + ], + synthetic_verification_docs: ['document_number', 'name_and_address'], + financial_verification_docs: ['document_number', 'name_and_address'], +}); diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-verification-contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-verification-contents.ts new file mode 100644 index 000000000000..3bad30e99bc6 --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction-verification-contents.ts @@ -0,0 +1,45 @@ +import { localize } from '@deriv/translations'; +import { TJurisdictionVerificationItems, TJurisdictionVerificationStatus } from 'Components/props.types'; + +type TJurisdictionVerificationContents = { + short_description: string; + required_verification_docs: TJurisdictionVerificationItems; + status_references: Array; +}; + +export const jurisdictionVerificationContents = (): TJurisdictionVerificationContents => ({ + short_description: localize('We need you to submit these in order to get this account:'), + required_verification_docs: { + document_number: { + icon: 'IcDocumentNumberVerification', + text: localize("Document number (e.g. identity card, passport, driver's license)"), + }, + selfie: { + icon: 'IcSelfieVerification', + text: localize('A selfie of yourself.'), + }, + identity_document: { + icon: 'IcIdentityDocumentVerification', + text: localize("A copy of your identity document (e.g. identity card, passport, driver's license)"), + }, + name_and_address: { + icon: 'IcNameAndAddressVerification', + text: localize( + 'A recent utility bill (e.g. electricity, water or gas) or recent bank statement or government-issued letter with your name and address.' + ), + }, + }, + status_references: [ + { + icon: 'IcVerificationStatusYellow', + text: localize('Verification in review.'), + color: 'yellow', + }, + { + icon: 'IcVerificationStatusRed', + text: localize('Verification failed. Resubmit your details.'), + color: 'red', + }, + { icon: 'IcVerificationStatusGreen', text: localize('Verification successful.'), color: 'green' }, + ], +}); diff --git a/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction_maltainvest_contents.ts b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction_maltainvest_contents.ts new file mode 100644 index 000000000000..38c0e38e6810 --- /dev/null +++ b/packages/cfd/src/Constants/jurisdiction-contents/jurisdiction_maltainvest_contents.ts @@ -0,0 +1,96 @@ +import { localize } from '@deriv/translations'; +import { TJurisdictionCardItems } from 'Components/props.types'; + +export const getJurisdictionMaltainvestContents = (): TJurisdictionCardItems => ({ + is_over_header_available: false, + header: localize('Malta'), + synthetic_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize('Synthetics, Forex, Stocks, Stock indices, Commodities, and Cryptocurrencies'), + title_indicators: { + type: 'displayText', + display_text: localize('140+'), + display_text_skin_color: 'red-light', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:30'), + display_text_skin_color: 'brown-dark', + }, + }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + title_indicators: { + type: 'displayText', + display_text: localize('0.5 pips'), + display_text_skin_color: 'violet-dark', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Malta Financial Services Authority (MFSA) (licence no. IS/70156)'), + }, + ], + financial_contents: [ + { + key: 'assets', + title: localize('Assets'), + description: localize('Forex, stocks, stock indices, commodities, cryptocurrencies and synthetic indices.'), + title_indicators: { + type: 'displayText', + display_text: localize('140+'), + display_text_skin_color: 'red-light', + }, + }, + { + key: 'leverage', + title: localize('Leverage'), + title_indicators: { + type: 'displayText', + display_text: localize('1:30'), + display_text_skin_color: 'brown-dark', + }, + }, + { + key: 'spreadsFrom', + title: localize('Spreads from'), + title_indicators: { + type: 'displayText', + display_text: localize('0.5 pips'), + display_text_skin_color: 'violet-dark', + }, + }, + { + key: 'verifications', + title: localize('Verifications'), + title_indicators: { type: 'displayIcons' }, + clickable_description: [ + { type: 'link', text: localize('Learn more') }, + { type: 'text', text: localize('about required verifications.') }, + ], + }, + { + key: 'regulator', + title: localize('Regulator/EDR'), + description: localize('Malta Financial Services Authority (MFSA) (licence no. IS/70156)'), + }, + ], + synthetic_verification_docs: ['selfie', 'identity_document', 'name_and_address'], + financial_verification_docs: ['selfie', 'identity_document', 'name_and_address'], +}); diff --git a/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js b/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js new file mode 100644 index 000000000000..d98092f8f6e8 --- /dev/null +++ b/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js @@ -0,0 +1,301 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CFDFinancialStpRealAccountSignup from '../cfd-financial-stp-real-account-signup'; +import CFDProviders from '../../cfd-providers'; +import { mockStore } from '@deriv/stores'; +import { getAuthenticationStatusInfo } from '@deriv/shared'; + +jest.mock('@deriv/account', () => ({ + ...jest.requireActual('@deriv/account'), + FormSubHeader: () =>
FormSubHeader
, +})); + +const MockComponent = ({ prevStep, nextStep }) => ( +
+ + +
+); + +jest.mock('../../Components/cfd-poa', () => + jest.fn(({ onCancel, onSubmit }) => ( +
+ CFDPOA + +
+ )) +); + +jest.mock('../../Components/cfd-poi', () => + jest.fn(({ onCancel, onSubmit }) => ( +
+ CFDPOI + +
+ )) +); + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + getAuthenticationStatusInfo: jest.fn().mockReturnValue({}), +})); + +const getByTextFn = (text, should_be) => { + if (should_be) { + expect(screen.getByText(text)).toBeInTheDocument(); + } else { + expect(screen.queryByText(text)).not.toBeInTheDocument(); + } +}; + +const testAllStepsFn = (steps, step_no) => { + steps.map((step, index) => { + if (index === step_no) { + getByTextFn(step.body, true); + } else { + getByTextFn(step.body, false); + } + }); +}; + +const steps = [ + { + body: 'CFDPOI', + }, + { + body: 'CFDPOA', + }, +]; + +describe('', () => { + let modal_root_el; + + beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); + }); + + afterAll(() => { + document.body.removeChild(modal_root_el); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const verified_jurisdiction = { + bvi: 1, + labuan: 1, + maltainvest: 0, + svg: 1, + vanuatu: 0, + virtual: 0, + }; + + let mockRootStore = { + notifications: { + addNotificationByKey: jest.fn(), + refreshNotifications: jest.fn(), + removeNotificationByKey: jest.fn(), + removeNotificationMessage: jest.fn(), + }, + client: { + account_settings: { + account_opening_reason: '', + address_city: 'MUDGEERABA', + address_line_1: "29 Ross Street, .'", + address_line_2: ".'", + address_postcode: '111', + address_state: '', + allow_copiers: 0, + citizen: '', + client_tnc_status: 'Version 4.2.0 2020-08-07', + country: 'Singapore', + country_code: 'sg', + date_of_birth: 984960000, + email: 'mock@gmail.com', + email_consent: 1, + feature_flag: { + wallet: 0, + }, + first_name: 'mahdiyeh', + has_secret_answer: 1, + immutable_fields: ['residence'], + is_authenticated_payment_agent: 0, + last_name: 'am', + non_pep_declaration: 1, + phone: '+651213456', + place_of_birth: null, + preferred_language: 'EN', + request_professional_status: 0, + residence: 'Singapore', + salutation: '', + tax_identification_number: null, + tax_residence: null, + user_hash: '823341c18bfccb391b6bb5d77ab7e6a83991f82669c1ba4e5b01dbd2fd71c7fe', + }, + account_status: { + authentication: { + document: { + verified_jurisdiction, + status: 'none', + }, + attempts: { + latest: { + service: 'idv', + }, + }, + }, + }, + authentication_status: { + document_status: 'none', + identity_status: 'none', + }, + email: 'mock@gmail.com', + is_fully_authenticated: true, + landing_company: { + config: { + tax_details_required: 1, + tin_format: ['^\\d{15}$'], + tin_format_description: '999999999999999', + }, + dxtrade_financial_company: {}, + dxtrade_gaming_company: {}, + financial_company: {}, + gaming_company: {}, + id: 'id', + minimum_age: 18, + mt_financial_company: {}, + mt_gaming_company: {}, + name: 'Indonesia', + virtual_company: 'virtual', + }, + residence_list: [ + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '93', + text: 'Afghanistan', + value: 'af', + }, + ], + fetchStatesList: jest.fn(), + states_list: { + text: 'Central Singapore', + value: '01', + }, + }, + modules: { + cfd: { + storeProofOfAddress: jest.fn(), + }, + }, + jurisdiction_selected_shortcode: 'svg', + }; + + it('should render CFDFinancialStpRealAccountSignup component', () => { + render(, { + wrapper: ({ children }) => {children}, + }); + + expect(screen.getByTestId('dt_cfd_financial_stp_modal_body')).toBeInTheDocument(); + }); + + it('should render properly for the first step content', () => { + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_bvi_labuan_vanuatu: true }); + render(, { + wrapper: ({ children }) => {children}, + }); + + testAllStepsFn(steps, 0); + }); + + it('should render properly for the second step content', () => { + getAuthenticationStatusInfo.mockReturnValueOnce({ poa_resubmit_for_labuan: true }); + const { getByTestId } = render(, { + wrapper: ({ children }) => {children}, + }); + + testAllStepsFn(steps, 1); + }); + + it('should check for POI status when Jurisdiction is Vanuatu or maltainvest', () => { + const new_mock_store = { + ...mockRootStore, + modules: { + ...mockRootStore.modules, + cfd: { + ...mockRootStore.modules.cfd, + jurisdiction_selected_shortcode: 'vanuatu', + }, + }, + }; + + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_bvi_labuan_vanuatu: true }); + + render(, { + wrapper: ({ children }) => {children}, + }); + testAllStepsFn(steps, 0); + }); + + it('should check for POA status when Jurisdiction is Labuan and resubmit status is set to true', () => { + const verified_jurisdiction = { + bvi: 1, + labuan: 0, + maltainvest: 0, + svg: 1, + vanuatu: 0, + virtual: 0, + }; + const new_mock_store = { + ...mockRootStore, + client: { + ...mockRootStore.client, + account_status: { + authentication: { + document: { + verified_jurisdiction, + status: 'none', + }, + attempts: { + latest: { + service: 'idv', + }, + }, + }, + }, + }, + modules: { + ...mockRootStore.modules, + cfd: { + ...mockRootStore.modules.cfd, + jurisdiction_selected_shortcode: 'labuan', + }, + }, + }; + + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_maltainvest: true }); + + render(, { + wrapper: ({ children }) => {children}, + }); + testAllStepsFn(steps, 1); + }); +}); diff --git a/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js b/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js index 2b7b4ddf1262..622f292a67c5 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js +++ b/packages/cfd/src/Containers/__tests__/cfd-password-modal.spec.js @@ -114,18 +114,14 @@ describe('', () => { jest.clearAllMocks(); }); - it('should render create Password modal when valid conditions are met for dxtrade account', async () => { + it('should render create Password modal when valid conditions are met', async () => { const store = mockStore(mockRootStore); - const props = { - ...mock_props, - platform: 'dxtrade', - }; store.client.account_status = { status: ['mt5_password_not_set', 'dxtrade_password_not_set'] }; render( - + , { wrapper: ({ children }) => {children}, @@ -325,7 +321,7 @@ describe('', () => { } ); - fireEvent.click(await screen.findByRole('button', { name: /Transfer now/i })); + fireEvent.click(await screen.findByRole('button', { name: /ok/i })); await waitFor(() => { expect(mockSetCFDSuccessDialog).toHaveBeenCalledWith(false); @@ -498,7 +494,7 @@ describe('', () => { } ); fireEvent.change(await screen.findByTestId('dt_mt5_password'), { target: { value: user_input } }); - fireEvent.click(await screen.findByRole('button', { name: 'Create account' })); + fireEvent.click(await screen.findByRole('button', { name: 'Create Deriv MT5 password' })); await waitFor(() => { expect(mockSubmitMt5Password).toHaveBeenCalled(); diff --git a/packages/cfd/src/Containers/__tests__/dmt5-trade-modal.spec.tsx b/packages/cfd/src/Containers/__tests__/dmt5-trade-modal.spec.tsx index 01bb3c903c35..db81757422ea 100644 --- a/packages/cfd/src/Containers/__tests__/dmt5-trade-modal.spec.tsx +++ b/packages/cfd/src/Containers/__tests__/dmt5-trade-modal.spec.tsx @@ -120,7 +120,7 @@ describe('', () => { }; renderComponent({ props: new_mock_props }); - expect(screen.queryByText(/In review/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Pending verification/)).not.toBeInTheDocument(); expect(screen.queryByText(/No new positions/)).not.toBeInTheDocument(); expect(screen.queryByText(/Account closed/)).not.toBeInTheDocument(); }); @@ -134,7 +134,7 @@ describe('', () => { }; renderComponent({ props: new_mock_props }); - expect(screen.queryByText(/In review/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Pending verification/)).not.toBeInTheDocument(); expect(screen.queryByText(/No new positions/)).not.toBeInTheDocument(); expect(screen.queryByText(/Account closed/)).not.toBeInTheDocument(); }); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx index 3394176bf0ea..ae757c4bd6bb 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx @@ -25,6 +25,7 @@ describe('', () => { setAccountType: jest.fn(), setJurisdictionSelectedShortcode: jest.fn(), enableCFDPasswordModal: jest.fn(), + toggleCFDVerificationModal: jest.fn(), }, }, }); @@ -49,6 +50,20 @@ describe('', () => { expect(screen.queryByText(/New!/i)).not.toBeInTheDocument(); }); + it('should render the "Boom 300 and Crash 300 Index" for EU user', () => { + mocked_props.is_eu_user = true; + mocked_props.is_demo = false; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.queryByText(/New!/i)).not.toBeInTheDocument(); + expect(screen.getByText(/Boom 300 and Crash 300 Index/i)).toBeInTheDocument(); + }); + it('should renders the component and not render the "New!" banner for MT5 demo', () => { mocked_props.is_eu_user = false; mocked_props.is_demo = true; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx index 17bb86935567..383633b84489 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx @@ -9,114 +9,127 @@ describe('', () => { selected_region: 'Non-EU', }, }); + const mocked_props = { + trading_platforms: { + market_type: 'gaming', + shortcode: 'svg', + }, + is_demo: false, + }; + const assertContent = ( + leverageDescription: string, + spread: string, + spreadDescription: string, + counterpartyCompanyDescription: string, + jurisdictionDescription: string + ) => { + expect(screen.getByText(leverageDescription)).toBeInTheDocument(); + expect(screen.getByText(spread)).toBeInTheDocument(); + expect(screen.getByText(spreadDescription)).toBeInTheDocument(); + expect(screen.getByText(counterpartyCompanyDescription)).toBeInTheDocument(); + expect(screen.getByText(jurisdictionDescription)).toBeInTheDocument(); + }; const wrapper = ({ children }: { children: JSX.Element }) => {children}; - it('should render content for standard svg', () => { - const mocked_props = { - trading_platforms: { - market_type: 'gaming', - shortcode: 'svg', - instruments: ['Forex', 'Synthetic Indices', 'Stocks', 'Commodities', 'Cryptocurrencies'], - product_details: { max_leverage: '1:1000', min_spread: '0' }, - platform: 'mt5', - product: 'standard', - }, - is_demo: false, - }; + it('should render CFDCompareAccountsDescription component on default props', () => { + render(, { wrapper }); + }); + + it('should render content for gaming market type with market type svg', () => { render(, { wrapper }); + assertContent('Maximum leverage', '0.1 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); expect(screen.getByText('Up to 1:1000')).toBeInTheDocument(); - expect(screen.getByText('0 pips')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC')).toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); }); - it('should render content for swapfree', () => { - const mocked_props = { - trading_platforms: { - market_type: 'all', - shortcode: 'svg', - instruments: ['Forex', 'Synthetic Indices', 'Stocks', 'Commodities', 'Cryptocurrencies'], - product_details: { max_leverage: '1:1111000', min_spread: '0.44' }, - platform: 'mt5', - product: 'swap_free', - }, - is_demo: false, - }; + it('should render content for gaming market type with vanuatu shortcode', () => { + mocked_props.trading_platforms.shortcode = 'vanuatu'; render(, { wrapper }); - expect(screen.getByText('Up to 1:1111000')).toBeInTheDocument(); - expect(screen.getByText('0.44 pips')).toBeInTheDocument(); + assertContent('Maximum leverage', '0.1 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('Up to 1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (V) Ltd')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); }); - it('should render content for financial', () => { - const mocked_props = { - trading_platforms: { - market_type: 'financial', - shortcode: 'labuan', - instruments: ['Forex', 'Cryptocurrencies'], - product_details: { max_leverage: '1:1000', min_spread: '0.2' }, - platform: 'mt5', - product: 'standard', - }, - is_demo: false, - }; + it('should render content for all market type with svg shortcode', () => { + mocked_props.trading_platforms.market_type = 'all'; render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('Up to 1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC')).toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Financial Commission')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + it('should render content for financial market type with svg shortcode', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.2 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); expect(screen.getByText('Up to 1:1000')).toBeInTheDocument(); - expect(screen.getByText('0.2 pips')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC')).toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Financial Commission')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + + it('should render content for financial market type with vanuatu shortcode', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'vanuatu'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.2 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('Up to 1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (V) Ltd')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu Financial Services Commission')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + + it('should render content for financial market type with labuan shortcode', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'labuan'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.6 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('Up to 1:100')).toBeInTheDocument(); + expect(screen.getByText('Deriv (FX) Ltd')).toBeInTheDocument(); + expect(screen.getByText('Labuan')).toBeInTheDocument(); + expect(screen.getByText('Labuan Financial Services Authority')).toBeInTheDocument(); + expect(screen.getByText('(licence no. MB/18/0024)')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); }); it('should render content for financial market type with maltainvest shortcode ', () => { - const mock_store = mockStore({ - traders_hub: { - selected_region: 'EU', - }, - }); - - const mocked_props = { - trading_platforms: { - market_type: 'financial', - shortcode: 'maltainvest', - instruments: ['Forex', 'Cryptocurrencies'], - product_details: { max_leverage: '1:30', min_spread: '0.5' }, - platform: 'mt5', - product: 'financial', - }, - is_demo: false, - }; - - render( - - - - ); + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'maltainvest'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); expect(screen.getByText('Up to 1:30')).toBeInTheDocument(); - expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('Deriv Investments (Europe) Limited')).toBeInTheDocument(); + expect(screen.getByText('Malta')).toBeInTheDocument(); + expect(screen.getByText('Financial Commission')).toBeInTheDocument(); + expect( + screen.getByText('Regulated by the Malta Financial Services Authority (MFSA) (licence no. IS/70156)') + ).toBeInTheDocument(); }); - it('should render content for ctrader ', () => { - const mock_store = mockStore({ - traders_hub: { - selected_region: 'NON-EU', - }, - }); - - const mocked_props = { - trading_platforms: { - market_type: 'financial', - shortcode: 'maltainvest', - instruments: ['Forex', 'Cryptocurrencies'], - product_details: { max_leverage: '1:30', min_spread: '0.5' }, - platform: 'ctrader', - }, - is_demo: false, - }; - render( - - - - ); - expect(screen.getByText('Up to 1:30')).toBeInTheDocument(); - expect(screen.getByText('Leverage')).toBeInTheDocument(); + it('should render demo content for gaming market type with market type svg', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + + render(, { wrapper }); + expect(screen.getByText('Up to 1:1000')).toBeInTheDocument(); + expect(screen.getByText('Maximum leverage')).toBeInTheDocument(); + expect(screen.getByText('0.2 pips')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); }); }); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx index b808ad63682e..ec754603f315 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx @@ -17,11 +17,6 @@ describe('', () => { it('should renders Deriv X platform label', () => { mocked_props.trading_platforms.platform = 'dxtrade'; render(); - expect(screen.getByText(platformsHeaderLabel.derivx)).toBeInTheDocument(); - }); - it('should renders ctrader platform label', () => { - mocked_props.trading_platforms.platform = 'ctrader'; - render(); - expect(screen.getByText(platformsHeaderLabel.ctrader)).toBeInTheDocument(); + expect(screen.getByText(platformsHeaderLabel.other_cfds)).toBeInTheDocument(); }); }); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx index be78c5baf695..61e40e7fb639 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx @@ -5,152 +5,139 @@ import CFDCompareAccountsTitleIcon from '../cfd-compare-accounts-title-icon'; jest.mock('../../../Assets/svgs/trading-platform', () => jest.fn(() =>
Mocked Icon
)); const mocked_props = { - trading_platforms: {}, + trading_platforms: { + platform: 'mt5', + market_type: 'gaming', + shortcode: 'svg', + product: '', + }, is_eu_user: false, is_demo: false, }; describe('', () => { - test('should render correct title for Standard product type', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'gaming', - shortcode: 'svg', - product: 'standard', - }; - render(); - expect(screen.getByText('Standard')).toBeInTheDocument(); - }); - - test('should render correct title for financial_stp product type', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'financial', - shortcode: 'labuan', - product: 'stp', - }; - mocked_props.is_eu_user = false; + test('should render correct title for synthetic_svg market type and shortcode', () => { render(); - expect(screen.getByText('Financial - STP')).toBeInTheDocument(); + expect(screen.getByText('Standard - SVG')).toBeInTheDocument(); }); - test('should render correct title for financial product type', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'financial', - shortcode: 'vanuatu', - product: 'financial', - }; + test('should render correct title for synthetic_bvi market type and shortcode', () => { + mocked_props.trading_platforms.shortcode = 'bvi'; render(); - expect(screen.getByText('Financial')).toBeInTheDocument(); + expect(screen.getByText('Standard - BVI')).toBeInTheDocument(); }); - test('should render correct title for Swap-Free product type', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'all', - shortcode: 'svg', - product: 'swap_free', - }; + test('should render correct title for synthetic_vanuatu market type and shortcode', () => { + mocked_props.trading_platforms.shortcode = 'vanuatu'; render(); - expect(screen.getByText('Swap-Free')).toBeInTheDocument(); + expect(screen.getByText('Standard - Vanuatu')).toBeInTheDocument(); }); - test('should render correct title for Zero Spread product type', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'all', - shortcode: 'bvi', - product: 'zero_spread', - }; + test('should render correct title for financial_labuan market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'labuan'; + render(); + expect(screen.getByText('Financial - Labuan')).toBeInTheDocument(); + }); + + test('should render correct title for financial_vanuatu market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'vanuatu'; + render(); + expect(screen.getByText('Financial - Vanuatu')).toBeInTheDocument(); + }); + test('should render correct title for financial_bvi market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'bvi'; render(); - expect(screen.getByText('Zero Spread')).toBeInTheDocument(); + expect(screen.getByText('Financial - BVI')).toBeInTheDocument(); }); - test('should render correct title for Deriv X product type', () => { - mocked_props.trading_platforms = { - platform: 'dxtrade', - market_type: 'all', - shortcode: 'svg', - }; + test('should render correct title for Swap-Free market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.trading_platforms.product = 'swap_free'; + render(); + expect(screen.getByText('Swap-Free - SVG')).toBeInTheDocument(); + }); + test('should render correct title for Zero Spread market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'bvi'; + mocked_props.trading_platforms.product = 'zero_spread'; + render(); + expect(screen.getByText('Zero Spread - BVI')).toBeInTheDocument(); + }); + + test('should render correct title for Deriv X market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'dxtrade'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; render(); expect(screen.getByText('Deriv X')).toBeInTheDocument(); }); test('should render correct title for EU Clients', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'financial', - shortcode: 'maltainvest', - product: 'financial', - }; + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; mocked_props.is_eu_user = true; render(); expect(screen.getByText('CFDs')).toBeInTheDocument(); }); - test('should render correct title for standard product type in demo account', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'gaming', - shortcode: 'svg', - product: 'standard', - }; + test('should render correct title for gaming market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'gaming'; + mocked_props.trading_platforms.shortcode = 'svg'; mocked_props.is_demo = true; mocked_props.is_eu_user = false; render(); expect(screen.getByText('Standard Demo')).toBeInTheDocument(); }); - test('should render correct title for financial product in demo account', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'financial', - shortcode: 'svg', - product: 'financial', - }; - + test('should render correct title for financial market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; mocked_props.is_demo = true; mocked_props.is_eu_user = false; render(); expect(screen.getByText('Financial Demo')).toBeInTheDocument(); }); - test('should render correct title for Swap-Free with correct product type demo account', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'all', - shortcode: 'svg', - product: 'swap_free', - }; + test('should render correct title for Swap-Free with correct market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.trading_platforms.product = 'swap_free'; mocked_props.is_demo = true; mocked_props.is_eu_user = false; render(); expect(screen.getByText('Swap-Free Demo')).toBeInTheDocument(); }); - test('should render correct title for Zero Spread with correct product type demo account', () => { - mocked_props.trading_platforms = { - platform: 'mt5', - market_type: 'all', - shortcode: 'bvi', - product: 'zero_spread', - }; - + test('should render correct title for Zero Spread with correct market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'bvi'; + mocked_props.trading_platforms.product = 'zero_spread'; mocked_props.is_demo = true; mocked_props.is_eu_user = false; render(); expect(screen.getByText('Zero Spread Demo')).toBeInTheDocument(); }); - test('should render correct title for DerivX with correct product type demo account', () => { - mocked_props.trading_platforms = { - platform: 'dxtrade', - market_type: 'all', - shortcode: 'svg', - }; + test('should render correct title for DerivX with correct market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'dxtrade'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; mocked_props.is_demo = true; mocked_props.is_eu_user = false; render(); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx index 563844a791a5..3cee459e05bc 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx @@ -59,6 +59,7 @@ describe('', () => { setAccountType: jest.fn(), setJurisdictionSelectedShortcode: jest.fn(), enableCFDPasswordModal: jest.fn(), + toggleCFDVerificationModal: jest.fn(), }, }, }); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx index f496590f33dc..6bc3834871af 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx @@ -9,21 +9,18 @@ describe('', () => { const mock = mockStore({ traders_hub: { selected_region: 'Non-EU', - is_eu_user: false, }, }); const mocked_props = { trading_platforms: { - market_type: 'financial', - shortcode: 'maltainvest', - instruments: ['Forex', 'Cryptocurrencies'], - product_details: { max_leverage: '1:30', min_spread: '0.5' }, - platform: 'ctrader', + platform: 'mt5', + market_type: 'gaming', + shortcode: 'svg', }, }; - it('should renders icons for product type standard', () => { + it('should renders icons for market type gaming/synthetic', () => { const wrapper = ({ children }: { children: JSX.Element }) => ( {children} ); @@ -55,20 +52,4 @@ describe('', () => { expect(containerElement).toBeInTheDocument(); expect(containerElement).toHaveClass('compare-cfd-account-outline'); }); - - it('should render the "Boom 300 and Crash 300 Index" for EU user', () => { - const mockStoreEU = mockStore({ - traders_hub: { - selected_region: 'EU', - is_eu_user: true, - }, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - - render(, { wrapper }); - expect(screen.getByText(/Boom 300 and Crash 300 Index/i)).toBeInTheDocument(); - }); }); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx index 1011c78845b5..a4ccf243cf19 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx @@ -22,6 +22,7 @@ describe('', () => { expect(iconElement).toHaveClass('trading-instruments__icon'); expect(textElement).toBeInTheDocument(); expect(asteriskElement).toBeInTheDocument(); + expect(asteriskElement).toHaveClass('trading-instruments__span'); }); it('should not apply opacity if "highlighted" prop is true', () => { diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx index 1900c679cd1c..e5bb1e3d79ab 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx @@ -30,6 +30,13 @@ const CFDCompareAccountsCard = ({ trading_platforms, is_eu_user, is_demo }: TCom /> + {is_eu_user && ( +
+ + + +
+ )} ); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx index 5dab0c3206a8..0e008aa66602 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx @@ -8,26 +8,14 @@ import { getJuridisctionDescription, getMarketType } from '../../Helpers/compare import { REGION, CFD_PLATFORMS, MARKET_TYPE_SHORTCODE } from '../../Helpers/cfd-config'; const CFDCompareAccountsDescription = ({ trading_platforms, is_demo }: TCompareAccountsCard) => { + const market_type = getMarketType(trading_platforms); + const market_type_shortcode = + trading_platforms.platform === CFD_PLATFORMS.MT5 && market_type === 'all' + ? `${market_type}_${trading_platforms.product}_${trading_platforms.shortcode}` + : market_type.concat('_', trading_platforms.shortcode ?? ''); + const juridisction_data = getJuridisctionDescription(market_type_shortcode); const { traders_hub } = useStore(); const { selected_region } = traders_hub; - - const getMarketTypeShortcode = () => { - if ( - trading_platforms.platform === CFD_PLATFORMS.DXTRADE || - trading_platforms.platform === CFD_PLATFORMS.CTRADER - ) { - return market_type.concat('_', trading_platforms.shortcode ?? ''); - } else if (trading_platforms.platform === CFD_PLATFORMS.MT5 && market_type === 'all') { - return `${market_type}_${trading_platforms.product}_${trading_platforms.shortcode}`; - } - return market_type; - }; - - const market_type = getMarketType(trading_platforms); - const market_type_shortcode = getMarketTypeShortcode(); - - const juridisction_data = getJuridisctionDescription(market_type_shortcode, trading_platforms); - const zero_spread_spread_message = localize('Commissions apply'); return ( @@ -38,7 +26,7 @@ const CFDCompareAccountsDescription = ({ trading_platforms, is_demo }: TCompareA >
- {localize('Up to')} {juridisction_data.leverage ?? ''} + {localize('Up to')} {juridisction_data.leverage} {selected_region === REGION.NON_EU ? juridisction_data.leverage_description : localize('Leverage')} @@ -68,6 +56,39 @@ const CFDCompareAccountsDescription = ({ trading_platforms, is_demo }: TCompareA
)} + {!is_demo && ( + +
+ + {juridisction_data.counterparty_company} + + + {juridisction_data.counterparty_company_description} + +
+
+ + {juridisction_data.jurisdiction} + + + {juridisction_data.jurisdiction_description} + +
+
+ + {juridisction_data.regulator} + + {juridisction_data.regulator_license && ( + + {juridisction_data.regulator_license} + + )} + + {juridisction_data.regulator_description} + +
+
+ )} ); }; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx index 34a2cf2e8171..358dd45b4cf3 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx @@ -14,8 +14,6 @@ const CFDCompareAccountsPlatformLabel = ({ trading_platforms }: TCompareAccounts 'compare-cfd-account-platform-label--other-cfds': platform_label === platformsHeaderLabel.other_cfds || platform_label === platformsHeaderLabel.ctrader, - 'compare-cfd-account-platform-label--derivx': platform_label === platformsHeaderLabel.derivx, - 'compare-cfd-account-platform-label--ctrader': platform_label === platformsHeaderLabel.ctrader, })} > diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx index 9b7e97cc87d9..da89afd19d30 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx @@ -1,22 +1,17 @@ import React from 'react'; import { Text, Popover } from '@deriv/components'; -import { useDevice } from '@deriv-com/ui'; import { localize } from '@deriv/translations'; import TradigPlatformIconProps from '../../Assets/svgs/trading-platform'; import { TCompareAccountsCard } from 'Components/props.types'; import { getAccountCardTitle, getMarketType, getAccountIcon } from '../../Helpers/compare-accounts-config'; import { CFD_PLATFORMS, MARKET_TYPE, MARKET_TYPE_SHORTCODE } from '../../Helpers/cfd-config'; -import { PRODUCT } from '@deriv/shared'; const CFDCompareAccountsTitleIcon = ({ trading_platforms, is_eu_user, is_demo }: TCompareAccountsCard) => { - const { isDesktop } = useDevice(); const market_type = !is_eu_user ? getMarketType(trading_platforms) : 'CFDs'; - const market_type_shortcode = - trading_platforms.platform === CFD_PLATFORMS.MT5 && - (market_type === MARKET_TYPE.ALL || trading_platforms.product === PRODUCT.STP) + trading_platforms.platform === CFD_PLATFORMS.MT5 && market_type === MARKET_TYPE.ALL ? `${market_type}_${trading_platforms.product}_${trading_platforms.shortcode}` - : market_type ?? ''; + : market_type.concat('_', trading_platforms.shortcode ?? ''); const jurisdiction_card_icon = trading_platforms.platform === CFD_PLATFORMS.DXTRADE || trading_platforms.platform === CFD_PLATFORMS.CTRADER ? getAccountIcon(trading_platforms.platform) @@ -25,10 +20,8 @@ const CFDCompareAccountsTitleIcon = ({ trading_platforms, is_eu_user, is_demo }: trading_platforms.platform === CFD_PLATFORMS.DXTRADE || trading_platforms.platform === CFD_PLATFORMS.CTRADER ? getAccountCardTitle(trading_platforms.platform, is_demo) : getAccountCardTitle(market_type_shortcode, is_demo); - const labuan_jurisdiction_message = ( - - {localize('This account gives you direct market price access and tighter spreads.')} - + const labuan_jurisdiction_message = localize( + 'Choosing this jurisdiction will give you a Financial STP account. Your trades will go directly to the market and have tighter spreads.' ); return ( @@ -53,7 +46,7 @@ const CFDCompareAccountsTitleIcon = ({ trading_platforms, is_eu_user, is_demo }: )} -
+
); }; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss index 684bf67c6a1e..fbfdb6d319d7 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss @@ -53,15 +53,17 @@ width: 27rem; border: 1px solid var(--general-hover); border-radius: 2.4rem; + padding-bottom: 1.6rem; &:hover { box-shadow: 0 2px 8px 0 var(--shadow-menu); } @include mobile-or-tablet-screen { - width: 20rem; + width: 18rem; } &__eu-clients { position: relative; top: 0.5rem; + text-align: center; } &__banner { position: absolute; @@ -83,13 +85,17 @@ &-outline { display: flex; flex-direction: column; - padding: 1.6rem 2.4rem 2.4rem; + padding: 4rem 2.4rem 0; border-radius: 2.4rem; + @include mobile-or-tablet-screen { + padding: 7rem 1.5rem 1.5rem; + } } &-text-container { max-height: 25.5rem; @include mobile { + min-height: 32rem; height: fit-content; } @@ -131,9 +137,9 @@ position: relative; width: 62%; @include mobile-or-tablet-screen { - display: flex; + display: block; position: fixed; - width: 13.4rem; + width: 15.2rem; right: 0; } } @@ -161,21 +167,10 @@ &--other-cfds { background-color: var(--header-background-others); } - &--derivx { - background-color: var(--header-background-others); - } - &--ctrader { - background-color: #{$color-yellow-6}; - } } &-underline { - height: 1px; - width: 75%; - background-color: var(--general-hover); - position: absolute; - left: 50%; - transform: translateX(-50%); - margin-top: 0.4rem; + border-top: 0.5rem solid var(--less-prominent); + width: 21.3rem; } &__button { width: calc(100% - 4rem); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx index b6f97a23ece3..57ad68ada583 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx @@ -64,20 +64,19 @@ const CompareCFDs = observer(() => { ? all_cfd_available_accounts.length + 1 : all_cfd_available_accounts.length; - const getCompareAccountsHeader = () => - selected_region === REGION.EU ? ( - - ) : ( - - ); + const CompareAccountsHeader = ( + + ); const DesktopHeader = (
@@ -94,7 +93,7 @@ const CompareCFDs = observer(() => {

- {getCompareAccountsHeader()} + {CompareAccountsHeader}

@@ -143,7 +142,7 @@ const CompareCFDs = observer(() => { return ( history.push(routes.traders_hub)} diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx index 5d542839c2ab..438628b03420 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import { Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; import InstrumentsIconWithLabel from './instruments-icon-with-label'; import { TInstrumentsIcon, TCompareAccountsCard } from 'Components/props.types'; import { getHighlightedIconLabel } from '../../Helpers/compare-accounts-config'; @@ -8,38 +6,15 @@ import { useStore } from '@deriv/stores'; const CFDInstrumentsLabelHighlighted = ({ trading_platforms }: TCompareAccountsCard) => { const { traders_hub } = useStore(); - const { selected_region, is_eu_user } = traders_hub; + const selected_region = traders_hub.selected_region; const iconData: TInstrumentsIcon[] = [...getHighlightedIconLabel(trading_platforms, selected_region)]; return (
- {is_eu_user ? ( - - {iconData - .filter(item => item.highlighted) - .map(item => ( - - ))} -
- - - -
-
- ) : ( - iconData.map(item => ( - - )) - )} + {iconData.map(item => ( + + ))}
); }; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx index 2ff4404647bd..ae0624646d83 100644 --- a/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx +++ b/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx @@ -1,11 +1,9 @@ import React from 'react'; -import { useDevice } from '@deriv-com/ui'; import { Text } from '@deriv/components'; import { TInstrumentsIcon } from 'Components/props.types'; import TradingInstrumentsIcon from '../../Assets/svgs/trading-instruments'; const InstrumentsIconWithLabel = ({ icon, text, highlighted, className, is_asterisk }: TInstrumentsIcon) => { - const { isMobile } = useDevice(); return (
- + {text} - {is_asterisk && ( - - * - - )} + {is_asterisk && ( + + * + + )}
); }; diff --git a/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx b/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx new file mode 100644 index 000000000000..02073b333de5 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import { useDevice } from '@deriv-com/ui'; +import { PoiPoaDocsSubmitted } from '@deriv/account'; +import { AccountStatusResponse } from '@deriv/api-types'; +import { Button, Icon, Loading, MobileDialog, Modal, Text, UILoader } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { getAuthenticationStatusInfo, isMobile, WS, isPOARequiredForMT5 } from '@deriv/shared'; +import CFDFinancialStpRealAccountSignup from './cfd-financial-stp-real-account-signup'; +import { observer, useStore } from '@deriv/stores'; +import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; +import { JURISDICTION } from '../Helpers/cfd-config'; + +const SwitchToRealAccountMessage = ({ onClickOk }: { onClickOk: () => void }) => ( +
+ + + {localize('Switch to your real account to submit your documents')} + +
+); + +const CFDDbviOnboarding = observer(() => { + const { isDesktop } = useDevice(); + const { + client, + ui, + modules: { cfd }, + } = useStore(); + + const { account_status, fetchAccountSettings, is_virtual, updateAccountStatus, updateMT5Status } = client; + const { disableApp, enableApp } = ui; + const { setProduct } = cfd; + + const { + has_created_account_for_selected_jurisdiction, + has_submitted_cfd_personal_details, + is_cfd_verification_modal_visible, + jurisdiction_selected_shortcode, + enableCFDPasswordModal, + toggleCFDVerificationModal, + } = useCfdStore(); + + const [showSubmittedModal, setShowSubmittedModal] = React.useState(true); + const [is_loading, setIsLoading] = React.useState(false); + + const getAccountStatusFromAPI = () => { + WS.authorized.getAccountStatus().then((response: AccountStatusResponse) => { + const { get_account_status } = response; + if (get_account_status?.authentication) { + const { poi_acknowledged_for_maltainvest, poi_acknowledged_for_bvi_labuan_vanuatu, poa_acknowledged } = + getAuthenticationStatusInfo(get_account_status); + if (jurisdiction_selected_shortcode === JURISDICTION.MALTA_INVEST) { + setShowSubmittedModal(poi_acknowledged_for_maltainvest && poa_acknowledged); + } else { + /** + * Need to retrigger POA when user has not explicitly submitted Address proof docs + */ + const is_poa_required_for_mt5 = isPOARequiredForMT5( + account_status, + jurisdiction_selected_shortcode + ); + setShowSubmittedModal( + poi_acknowledged_for_bvi_labuan_vanuatu && + has_submitted_cfd_personal_details && + !is_poa_required_for_mt5 + ); + } + } + + setIsLoading(false); + }); + setIsLoading(false); + }; + + const clickOncloseButton = () => { + toggleCFDVerificationModal(); + setProduct(); + }; + + React.useEffect(() => { + if (is_cfd_verification_modal_visible) { + setIsLoading(true); + getAccountStatusFromAPI(); + fetchAccountSettings(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [is_cfd_verification_modal_visible]); + + const getModalContent = () => { + if (is_loading) { + return ; + } else if (is_virtual) { + return ; + } + return showSubmittedModal ? ( + + ) : ( + { + updateMT5Status(); + if (has_created_account_for_selected_jurisdiction) { + setShowSubmittedModal(true); + } else { + toggleCFDVerificationModal(); + enableCFDPasswordModal(); + } + }} + /> + ); + }; + + const getModalTitle = () => + has_created_account_for_selected_jurisdiction + ? localize('Submit your proof of identity and address') + : localize('Add a real MT5 account'); + + return ( + }> + {isDesktop ? ( + getAccountStatusFromAPI()} + exit_classname='cfd-modal--custom-exit' + > + {getModalContent()} + + ) : ( + + {getModalContent()} + + )} + + ); +}); + +export default CFDDbviOnboarding; diff --git a/packages/cfd/src/Containers/cfd-enter-password-modal-title.tsx b/packages/cfd/src/Containers/cfd-enter-password-modal-title.tsx deleted file mode 100644 index cbf559d369b2..000000000000 --- a/packages/cfd/src/Containers/cfd-enter-password-modal-title.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { Text } from '@deriv/components'; -import { getCFDPlatformLabel } from '@deriv/shared'; -import { observer, useStore } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; -import { CATEGORY, CFD_PLATFORMS } from '../Helpers/cfd-config'; - -type TCFDEnterPasswordModalTitleProps = { platform: typeof CFD_PLATFORMS[keyof typeof CFD_PLATFORMS] }; - -const CFDEnterPasswordModalTitle = observer(({ platform }: TCFDEnterPasswordModalTitleProps) => { - const { - modules: { cfd }, - } = useStore(); - const { account_title, account_type } = cfd; - - const getAccountCardTitle = () => { - switch (platform) { - case CFD_PLATFORMS.CTRADER: - case CFD_PLATFORMS.DXTRADE: - return account_type.category === CATEGORY.REAL ? 'Real' : ''; - default: - return account_title; - } - }; - - return ( - - {platform === CFD_PLATFORMS.MT5 ? ( - - ) : ( - - )} - - ); -}); - -export default CFDEnterPasswordModalTitle; diff --git a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx new file mode 100644 index 000000000000..777a17646914 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx @@ -0,0 +1,229 @@ +import React from 'react'; +import { useDevice } from '@deriv-com/ui'; +import { Div100vhContainer } from '@deriv/components'; +import { getAuthenticationStatusInfo, isPOARequiredForMT5 } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import type { TCoreStores } from '@deriv/stores/types'; +import CFDPOA from '../Components/cfd-poa'; +import CFDPOI from '../Components/cfd-poi'; +import CFDPersonalDetailsContainer from './cfd-personal-details-container'; +import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; +import { JURISDICTION } from '../Helpers/cfd-config'; + +type TCFDFinancialStpRealAccountSignupProps = { + onFinish: () => void; +}; + +type TNextStep = (index: number, value: { [key: string]: string | undefined }) => void; + +type TItem = { + refreshNotifications: TCoreStores['notifications']['refreshNotifications']; + removeNotificationMessage: TCoreStores['notifications']['removeNotificationMessage']; + removeNotificationByKey: TCoreStores['notifications']['removeNotificationByKey']; + addNotificationMessageByKey: TCoreStores['notifications']['addNotificationMessageByKey']; + authentication_status: TCoreStores['client']['authentication_status']; + account_settings: TCoreStores['client']['account_settings']; + email: TCoreStores['client']['email']; + is_fully_authenticated: TCoreStores['client']['is_fully_authenticated']; + landing_company: TCoreStores['client']['landing_company']; + residence_list: TCoreStores['client']['residence_list']; + states_list: TCoreStores['client']['states_list']; + fetchStatesList: TCoreStores['client']['fetchStatesList']; + account_status: TCoreStores['client']['account_status']; + jurisdiction_selected_shortcode: TCoreStores['modules']['cfd']['jurisdiction_selected_shortcode']; + has_submitted_cfd_personal_details: TCoreStores['modules']['cfd']['has_submitted_cfd_personal_details']; + onFinish: TCFDFinancialStpRealAccountSignupProps['onFinish']; +}; + +type TItemsState = { + body: typeof CFDPOI | typeof CFDPOA | typeof CFDPersonalDetailsContainer; + form_value: { [key: string]: string | undefined }; + forwarded_props: Array>; +}; + +const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialStpRealAccountSignupProps) => { + const { isDesktop } = useDevice(); + const { notifications, client } = useStore(); + + const { refreshNotifications, removeNotificationMessage, removeNotificationByKey, addNotificationMessageByKey } = + notifications; + + const { + authentication_status, + account_settings, + email, + is_fully_authenticated, + landing_company, + residence_list, + states_list, + fetchStatesList, + account_status, + } = client; + + const { jurisdiction_selected_shortcode, has_submitted_cfd_personal_details } = useCfdStore(); + + const passthroughProps = { + refreshNotifications, + removeNotificationMessage, + removeNotificationByKey, + addNotificationMessageByKey, + authentication_status, + account_settings, + email, + is_fully_authenticated, + landing_company, + residence_list, + states_list, + fetchStatesList, + account_status, + jurisdiction_selected_shortcode, + has_submitted_cfd_personal_details, + onFinish, + } as const; + + const [step, setStep] = React.useState(0); + const [form_error, setFormError] = React.useState(''); + const state_index = step; + let is_mounted = React.useRef(true).current; + + const { need_poi_for_maltainvest, need_poi_for_bvi_labuan_vanuatu } = getAuthenticationStatusInfo(account_status); + + const poi_config: TItemsState = { + body: CFDPOI, + form_value: { + poi_state: 'unknown', + }, + forwarded_props: [ + 'addNotificationMessageByKey', + 'authentication_status', + 'refreshNotifications', + 'removeNotificationMessage', + 'removeNotificationByKey', + 'jurisdiction_selected_shortcode', + ], + }; + + const poa_config: TItemsState = { + body: CFDPOA, + form_value: {}, + forwarded_props: [], + }; + + const personal_details_config: TItemsState = { + body: CFDPersonalDetailsContainer, + form_value: { + citizen: '', + place_of_birth: '', + tax_residence: '', + tax_identification_number: '', + account_opening_reason: '', + }, + forwarded_props: ['residence_list', 'landing_company'], + }; + + const should_show_poi = () => { + if (jurisdiction_selected_shortcode === JURISDICTION.MALTA_INVEST) { + return need_poi_for_maltainvest; + } + return need_poi_for_bvi_labuan_vanuatu; + }; + + const shouldShowPOA = () => { + return isPOARequiredForMT5(account_status, jurisdiction_selected_shortcode); + }; + + const should_show_personal_details = + !has_submitted_cfd_personal_details && jurisdiction_selected_shortcode !== JURISDICTION.MALTA_INVEST; + + const verification_configs = [ + ...(should_show_poi() ? [poi_config] : []), + ...(shouldShowPOA() ? [poa_config] : []), + ...(should_show_personal_details ? [personal_details_config] : []), + ]; + + const [items, setItems] = React.useState[]>(verification_configs); + + const clearError = () => { + setFormError(''); + }; + + React.useEffect(() => { + refreshNotifications(); + }, [items, refreshNotifications]); + + React.useEffect(() => { + fetchStatesList(); + }, [fetchStatesList]); + + const unmount = () => { + is_mounted = false; + onFinish(); + }; + + const saveFormData = (index: number, value: { [key: string]: string | undefined }) => { + if (!is_mounted) return; // avoiding state update on unmounted component + const cloned_items: TItemsState[] = [...items]; + cloned_items[index].form_value = value; + setItems(cloned_items); + }; + + const nextStep: TNextStep = (index, value) => { + clearError(); + if (step + 1 < items.length) { + saveFormData(index, value); + setStep(step + 1); + } else unmount(); + }; + + const prevStep = () => { + if (step - 1 >= 0) { + setStep(step - 1); + setFormError(''); + } else unmount(); + }; + + const getCurrent = (key?: keyof TItemsState) => { + return key ? items[state_index][key] : items[state_index]; + }; + + const BodyComponent = getCurrent('body') as typeof CFDPOI & typeof CFDPOA & typeof CFDPersonalDetailsContainer; + + const form_value = getCurrent('form_value'); + + const passthrough: Partial = ( + (getCurrent('forwarded_props') || []) as TItemsState['forwarded_props'] + ).reduce((forwarded_prop, item) => { + return Object.assign(forwarded_prop, { + [item]: passthroughProps[item], + }); + }, {}); + + return ( + +
+ +
+
+ ); +}); + +export default CFDFinancialStpRealAccountSignup; diff --git a/packages/cfd/src/Containers/cfd-password-change.tsx b/packages/cfd/src/Containers/cfd-password-change.tsx index 2d5e6d948d51..0dd66adc8e7c 100644 --- a/packages/cfd/src/Containers/cfd-password-change.tsx +++ b/packages/cfd/src/Containers/cfd-password-change.tsx @@ -26,7 +26,6 @@ type TCFDPasswordChangeProps = TCFDPasswordFormReusedProps & { onForgotPassword: () => void; setNewPasswordValue?: React.Dispatch>; should_set_trading_password: boolean; - need_tnc: boolean; }; type TOnSubmitPasswordChange = ( @@ -43,7 +42,6 @@ const CFDPasswordChange = observer( setNewPasswordValue, should_set_trading_password, platform, - need_tnc, }: TCFDPasswordChangeProps) => { const { isMobile } = useDevice(); const { ui, modules } = useStore(); @@ -201,7 +199,6 @@ const CFDPasswordChange = observer( platform={platform} checked={checked} onCheck={() => setChecked(prev => !prev)} - need_tnc={need_tnc} /> )} diff --git a/packages/cfd/src/Containers/cfd-password-modal-info.tsx b/packages/cfd/src/Containers/cfd-password-modal-info.tsx index 058f95ee8d90..7337a7719f91 100644 --- a/packages/cfd/src/Containers/cfd-password-modal-info.tsx +++ b/packages/cfd/src/Containers/cfd-password-modal-info.tsx @@ -2,54 +2,28 @@ import React from 'react'; import { Icon, Text } from '@deriv/components'; import { Localize } from '@deriv/translations'; import { DBVI_COMPANY_NAMES } from '@deriv/shared'; -import { useIsSelectedMT5AccountCreated } from '@deriv/hooks'; type CfdPasswordModalInfoProps = { jurisdiction_selected_shortcode: string; platform: string; product: string; - need_tnc: boolean; }; -const CfdPasswordModalInfo = ({ - jurisdiction_selected_shortcode, - platform, - product, - need_tnc, -}: CfdPasswordModalInfoProps) => { - const { available_account_to_create } = useIsSelectedMT5AccountCreated(); +const CfdPasswordModalInfo = ({ jurisdiction_selected_shortcode, platform, product }: CfdPasswordModalInfoProps) => { return (
- +
- {need_tnc ? ( - , - ]} - /> - ) : ( - - )} +
); diff --git a/packages/cfd/src/Containers/cfd-password-modal-title.tsx b/packages/cfd/src/Containers/cfd-password-modal-title.tsx new file mode 100644 index 000000000000..b4a523cf2a3b --- /dev/null +++ b/packages/cfd/src/Containers/cfd-password-modal-title.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { getCFDPlatformNames, getCFDPlatformLabel, getFormattedJurisdictionCode } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { CATEGORY, CFD_PLATFORMS } from '../Helpers/cfd-config'; +import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; + +type TCFDPasswordModalTitleProps = { platform: typeof CFD_PLATFORMS[keyof typeof CFD_PLATFORMS] }; + +const CFDPasswordModalTitle = observer(({ platform }: TCFDPasswordModalTitleProps) => { + const { traders_hub } = useStore(); + const { show_eu_related_content } = traders_hub; + const { account_title, account_type, jurisdiction_selected_shortcode } = useCfdStore(); + + const accountTitle = (category: typeof CATEGORY[keyof typeof CATEGORY]) => { + switch (platform) { + case CFD_PLATFORMS.CTRADER: + case CFD_PLATFORMS.DXTRADE: + return category === CATEGORY.REAL ? 'Real' : ''; + default: + return account_title; + } + }; + + const showJurisdiction = () => { + if (platform === CFD_PLATFORMS.DXTRADE) { + return ''; + } else if (!show_eu_related_content) { + return getFormattedJurisdictionCode(jurisdiction_selected_shortcode); + } + return 'CFDs'; + }; + + return ( + + {account_type.category === CATEGORY.REAL && ( + + )} + {account_type.category === CATEGORY.DEMO && ( + + )} + + ); +}); + +export default CFDPasswordModalTitle; diff --git a/packages/cfd/src/Containers/cfd-password-modal-tnc.tsx b/packages/cfd/src/Containers/cfd-password-modal-tnc.tsx index 544e79ebe26f..9bb595222285 100644 --- a/packages/cfd/src/Containers/cfd-password-modal-tnc.tsx +++ b/packages/cfd/src/Containers/cfd-password-modal-tnc.tsx @@ -1,41 +1,40 @@ import React from 'react'; import { getCFDPlatformLabel, CFD_PRODUCTS_TITLE, CFD_PLATFORMS } from '@deriv/shared'; -import { observer } from '@deriv/stores'; +import { observer, useStore } from '@deriv/stores'; import CfdPasswordModalInfo from './cfd-password-modal-info'; -import JurisdictionCheckBox from './jurisdiction-checkbox'; +import JurisdictionCheckBox from './jurisdiction-modal/jurisdiction-modal-checkbox'; import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; import classNames from 'classnames'; type CfdPasswordModalTncProps = { platform: typeof CFD_PLATFORMS[keyof typeof CFD_PLATFORMS]; checked: boolean; - need_tnc: boolean; onCheck: () => void; className?: string; }; -const CfdPasswordModalTnc = observer( - ({ platform, checked, onCheck, className, need_tnc }: CfdPasswordModalTncProps) => { - const { jurisdiction_selected_shortcode, account_title } = useCfdStore(); - return ( -
- - {need_tnc && ( - - )} -
- ); - } -); +const CfdPasswordModalTnc = observer(({ platform, checked, onCheck, className }: CfdPasswordModalTncProps) => { + const { client } = useStore(); + const { should_restrict_vanuatu_account_creation, should_restrict_bvi_account_creation } = client; + const { jurisdiction_selected_shortcode } = useCfdStore(); + + return ( +
+ + +
+ ); +}); export default CfdPasswordModalTnc; diff --git a/packages/cfd/src/Containers/cfd-password-modal.tsx b/packages/cfd/src/Containers/cfd-password-modal.tsx index 500167672766..3bd3a1f3f0d5 100644 --- a/packages/cfd/src/Containers/cfd-password-modal.tsx +++ b/packages/cfd/src/Containers/cfd-password-modal.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { useHistory } from 'react-router'; import { Formik, FormikErrors, FormikHelpers } from 'formik'; import { useDevice } from '@deriv-com/ui'; + import { SentEmailModal } from '@deriv/account'; -import '../sass/cfd.scss'; import { FormSubmitButton, Icon, @@ -15,9 +15,10 @@ import { Text, } from '@deriv/components'; import { + getAuthenticationStatusInfo, getCFDPlatformLabel, - getCFDPlatformNames, getErrorMessages, + getLegalEntityName, routes, validLength, validPassword, @@ -28,34 +29,25 @@ import { } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; -import CFDEnterPasswordModalTitle from './cfd-enter-password-modal-title'; + +import { TProducts } from '../Components/props.types'; import SuccessDialog from '../Components/success-dialog/success-dialog'; +import CFDPasswordModalTitle from './cfd-password-modal-title'; +import TradingPlatformIcon from '../Assets/svgs/trading-platform'; import MigrationSuccessModal from '../Components/migration-success-modal'; import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; -import { CFD_PLATFORMS, CATEGORY } from '../Helpers/cfd-config'; -import classNames from 'classnames'; +import { CFD_PLATFORMS, JURISDICTION, CATEGORY, PRODUCT } from '../Helpers/cfd-config'; import { getDxCompanies, getMtCompanies, TDxCompanies, TMtCompanies } from '../Stores/Modules/CFD/Helpers/cfd-config'; -const MT5CreatePassword = makeLazyLoader( - () => moduleLoader(() => import('./mt5-create-password/mt5-create-password')), - () =>
-)(); - -const CfdPasswordModalTnc = makeLazyLoader( - () => moduleLoader(() => import('./cfd-password-modal-tnc')), - () =>
-)(); +import '../sass/cfd.scss'; +import CfdPasswordModalTnc from './cfd-password-modal-tnc'; +import classNames from 'classnames'; const CFDPasswordChange = makeLazyLoader( () => moduleLoader(() => import('./cfd-password-change')), () =>
)(); -const CFDPasswordSuccessIcon = makeLazyLoader( - () => moduleLoader(() => import('./cfd-password-success-icon')), - () =>
-)(); - const CFDPasswordChangeContent = makeLazyLoader( () => moduleLoader(() => import('./cfd-password-change-content')), () =>
@@ -77,6 +69,13 @@ type TPasswordModalHeaderProps = { has_mt5_account?: boolean; }; +type TIconTypeProps = { + platform: string; + type?: string; + show_eu_related_content: boolean; + product?: TProducts; +}; + type TCFDPasswordFormReusedProps = { platform: typeof CFD_PLATFORMS[keyof typeof CFD_PLATFORMS]; error_message: string; @@ -86,34 +85,37 @@ type TCFDPasswordFormReusedProps = { type TCFDCreatePasswordProps = TCFDPasswordFormReusedProps & { password: string; onSubmit: TOnSubmitPassword; - need_tnc: boolean; + is_real_financial_stp: boolean; }; type TCFDCreatePasswordFormProps = TCFDPasswordFormReusedProps & { has_mt5_account: boolean; submitPassword: TOnSubmitPassword; - need_tnc: boolean; + is_real_financial_stp: boolean; }; type TMultiStepRefProps = { goNextStep: () => void; goPrevStep: () => void; }; +type TReviewMsgForMT5 = { + is_selected_mt5_verified: boolean; + jurisdiction_selected_shortcode: string; + manual_status: string; +}; type TCFDPasswordFormProps = TCFDPasswordFormReusedProps & { closeModal: () => void; error_type?: string; form_error?: string; has_mt5_account: boolean; + is_bvi: boolean; is_dxtrade_allowed: boolean; + is_real_financial_stp: boolean; onCancel: () => void; onForgotPassword: () => void; should_set_trading_password: boolean; submitPassword: TOnSubmitPassword; - account_type: { - type: string; - category: string; - }; }; type TCFDPasswordModalProps = { @@ -128,41 +130,21 @@ const PasswordModalHeader = ({ platform, }: TPasswordModalHeaderProps) => { const { isDesktop } = useDevice(); - const is_mt5 = platform === CFD_PLATFORMS.MT5; const element = !isDesktop ? 'p' : 'span'; - - const style = !isDesktop - ? { - padding: '2rem', - } - : {}; - - if (is_mt5 && !is_password_reset_error) { - const platform_name = getCFDPlatformNames(platform); - return ( - - {should_set_trading_password ? ( - - ) : ( - - )} - - ); - } + const alignment = 'center'; + const font_size = 's'; return ( - + + {should_set_trading_password && !is_password_reset_error && platform === CFD_PLATFORMS.MT5 && ( + + )} {!should_set_trading_password && !is_password_reset_error && ( ); }; +const ReviewMessageForMT5 = ({ + is_selected_mt5_verified, + jurisdiction_selected_shortcode, + manual_status, +}: TReviewMsgForMT5) => { + if (is_selected_mt5_verified) { + return ; + } else if ( + jurisdiction_selected_shortcode === JURISDICTION.BVI || + jurisdiction_selected_shortcode === JURISDICTION.VANUATU + ) { + if (manual_status === 'pending') { + return ; + } + return ; + } else if (jurisdiction_selected_shortcode === JURISDICTION.LABUAN) { + return ; + } else if (jurisdiction_selected_shortcode === JURISDICTION.MALTA_INVEST) { + return ( + + ); + } + return null; +}; + +const IconType = React.memo(({ platform, type, show_eu_related_content, product }: TIconTypeProps) => { + const traders_hub = window.location.pathname === routes.traders_hub; + if (platform === CFD_PLATFORMS.DXTRADE) { + return ; + } else if (traders_hub) { + if (platform === CFD_PLATFORMS.CTRADER) { + return ; + } + switch (type) { + case 'synthetic': + return ; + case 'all': + if (product === PRODUCT.ZEROSPREAD) { + return ; + } + return ; + case 'financial': + if (show_eu_related_content) { + return ; + } + return ; + default: + return ; + } + } else { + switch (type) { + case 'synthetic': + return ; + case 'all': + if (product === PRODUCT.ZEROSPREAD) { + return ; + } + return ; + case 'financial': + if (show_eu_related_content) { + return ; + } + return ; + default: + return ; + } + } +}); +IconType.displayName = 'IconType'; const getCancelButtonLabel = ({ should_set_trading_password, @@ -200,7 +251,19 @@ const handlePasswordInputChange = ( }); }; -const CreatePassword = ({ password, platform, validatePassword, onSubmit, error_message }: TCFDCreatePasswordProps) => { +const CreatePassword = ({ + password, + platform, + validatePassword, + onSubmit, + error_message, + is_real_financial_stp, +}: TCFDCreatePasswordProps) => { + const { product, account_type } = useCfdStore(); + const [checked, setChecked] = React.useState( + !(product === PRODUCT.ZEROSPREAD && account_type.category === CATEGORY.REAL) + ); + return ( - - - - - - - -
- - {() => ( - ) => { - handlePasswordInputChange(e, handleChange, validateForm, setFieldTouched); - }} - data_testId={`dt_${platform}_password`} - /> - )} - +
+ {platform === CFD_PLATFORMS.MT5 ? ( + <> + + + + + + ) : ( + <> + + + + + + + + + )} +
+ + {() => ( + ) => { + handlePasswordInputChange( + e, + handleChange, + validateForm, + setFieldTouched + ); + }} + data_testId={`dt_${platform}_password`} + /> + )} + +
+ {is_real_financial_stp && ( +
+ +
+ )} + {product === PRODUCT.ZEROSPREAD && account_type.category === CATEGORY.REAL && ( + setChecked(prev => !prev)} + /> + )}
0} + is_disabled={!values.password || !checked || Object.keys(errors).length > 0} is_loading={isSubmitting} label={localize('Create {{platform}} password', { platform: getCFDPlatformLabel(platform), @@ -296,7 +398,7 @@ const CFDCreatePasswordForm = ({ error_message, validatePassword, submitPassword, - need_tnc, + is_real_financial_stp, }: TCFDCreatePasswordFormProps) => { const multi_step_ref = React.useRef(); const [password, setPassword] = React.useState(''); @@ -312,26 +414,16 @@ const CFDCreatePasswordForm = ({ const steps = [ { - component: - platform === CFD_PLATFORMS.MT5 ? ( - - ) : ( - - ), + component: ( + + ), }, { component: ( @@ -357,18 +449,19 @@ const CFDPasswordForm = observer( error_type, form_error, has_mt5_account, + is_real_financial_stp, onCancel, onForgotPassword, platform, should_set_trading_password, submitPassword, validatePassword, - account_type, }: TCFDPasswordFormProps) => { const { isDesktop } = useDevice(); - const { jurisdiction_selected_shortcode } = useCfdStore(); - const [checked, setChecked] = React.useState(false); - const need_tnc = jurisdiction_selected_shortcode !== 'svg' && account_type.category === CATEGORY.REAL; + const { product, account_type } = useCfdStore(); + const [checked, setChecked] = React.useState( + !(product === PRODUCT.ZEROSPREAD && account_type.category === CATEGORY.REAL) + ); const button_label = React.useMemo(() => { if (error_type === 'PasswordReset') { @@ -427,11 +520,10 @@ const CFDPasswordForm = observer( validatePassword={validatePassword} submitPassword={submitPassword} has_mt5_account={has_mt5_account} - need_tnc={need_tnc} + is_real_financial_stp={is_real_financial_stp} /> ); } - // enter password modal return ( (
- + {!should_set_trading_password && }
+ + {is_real_financial_stp && ( +
+ +
+ )} {error_type === 'PasswordError' && ( )} - {account_type.category === CATEGORY.REAL && ( + {product === PRODUCT.ZEROSPREAD && account_type.category === CATEGORY.REAL && ( setChecked(prev => !prev)} - need_tnc={need_tnc} /> )}
{ + switch (jurisdiction_selected_shortcode) { + case JURISDICTION.SVG: + setIsSelectedMT5Verified(true); + break; + case JURISDICTION.BVI: + case JURISDICTION.VANUATU: + setIsSelectedMT5Verified(poi_verified_for_bvi_labuan_vanuatu); + break; + case JURISDICTION.LABUAN: + setIsSelectedMT5Verified(poi_verified_for_bvi_labuan_vanuatu && poa_verified); + break; + case JURISDICTION.MALTA_INVEST: + setIsSelectedMT5Verified(poi_verified_for_maltainvest && poa_verified); + break; + default: + } + }; + // Usecase: Added this timeout to render the Password Change modal after the password modal is closed. // It is to avoid the flickering of the modal. React.useEffect(() => { @@ -592,6 +720,11 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + React.useEffect(() => { + getVerificationStatus(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [jurisdiction_selected_shortcode, account_status]); + const validatePassword = (values: TCFDPasswordFormValues) => { const errors: FormikErrors = {}; const max_length = platform === CFD_PLATFORMS.MT5 && should_set_trading_password ? 16 : 25; @@ -639,7 +772,12 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr disableCFDPasswordModal(); closeDialogs(); if (account_type.category === CATEGORY.REAL) { - toggleAccountTransferModal(); + if (is_eu_user) { + toggleAccountTransferModal(); + } else { + sessionStorage.setItem('cfd_transfer_to_login_id', new_account_response.login || ''); + history.push(routes.cashier_acc_transfer); + } } }; @@ -698,6 +836,8 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr const should_show_sent_email_modal = is_sent_email_modal_enabled && is_password_modal_exited; + const is_real_financial_stp = [account_type.category, account_type.type].join('_') === 'real_financial_stp'; + const should_show_password_modal = React.useMemo(() => { if (should_show_password) { return should_set_trading_password ? true : isDesktop; @@ -714,10 +854,13 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr const success_modal_submit_label = React.useMemo(() => { if (account_type.category === CATEGORY.REAL) { + if (platform === CFD_PLATFORMS.MT5) { + return is_eu_user || is_selected_mt5_verified ? localize('Transfer now') : localize('OK'); + } return localize('Transfer now'); } return localize('Continue'); - }, [account_type]); + }, [platform, account_type, is_eu_user, is_selected_mt5_verified]); const success_modal_cancel_label = React.useMemo(() => { if (is_eu_user && account_type.category === 'real' && platform === CFD_PLATFORMS.MT5) { @@ -726,7 +869,7 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr return ''; }, [platform, account_type, is_eu_user]); - const getSuccssMessage = () => { + const getSubmitText = () => { const { category, type } = account_type; if (!category && !type) return ''; @@ -780,7 +923,11 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr type: accountTypes(), }} /> - + )} @@ -814,19 +961,20 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr const cfd_password_form = ( ); @@ -945,6 +1093,7 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr const invalid_mt5_password_modal = isMobileOrTabletPortrait ? is_mt5_password_format_invalid : is_mt5_password_format_invalid_desktop; + return ( {platform === CFD_PLATFORMS.MT5 && !isDesktop && password_modal_mobile} @@ -954,11 +1103,15 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr is_open={should_show_success} toggleModal={closeModal} onCancel={closeModal} - onSubmit={closeOpenSuccess} + onSubmit={ + !is_eu_user && platform === CFD_PLATFORMS.MT5 && !is_selected_mt5_verified + ? closeModal + : closeOpenSuccess + } classNameMessage='cfd-password-modal__message' - message={getSuccssMessage()} + message={getSubmitText()} icon={ - { - switch (type) { - case 'synthetic': - return 'Standard'; - case 'all': - return product === PRODUCT.ZEROSPREAD ? 'ZeroSpread' : 'SwapFree'; - case 'financial': - return show_eu_related_content ? 'CFDs' : 'Financial'; - default: - return 'Financial'; - } -}; - -const getIconForMt5 = (type: string | undefined, show_eu_related_content: boolean, product?: TProducts) => { - switch (type) { - case 'synthetic': - return 'IcMt5StandardPlatform'; - case 'all': - return product === PRODUCT.ZEROSPREAD ? 'IcMt5ZeroSpread' : 'IcMt5SwapFreePlatform'; - case 'financial': - return show_eu_related_content ? 'IcMt5CfdPlatform' : 'IcMt5FinancialPlatform'; - default: - return 'IcMt5FinancialStpPlatform'; - } -}; - -const CFDPasswordSuccessIcon = ({ platform, type, show_eu_related_content, product }: TCFDPasswordSuccessIconProps) => { - const isTradersHub = window.location.pathname === routes.traders_hub; - - if (platform === CFD_PLATFORMS.DXTRADE) { - return ; - } - - if (platform === CFD_PLATFORMS.CTRADER) { - return ; - } - - if (isTradersHub) { - const icon = getIconForTradersHub(type, show_eu_related_content, product); - return ; - } - - const icon = getIconForMt5(type, show_eu_related_content, product); - return ; -}; - -export default CFDPasswordSuccessIcon; diff --git a/packages/cfd/src/Containers/cfd-personal-details-container.tsx b/packages/cfd/src/Containers/cfd-personal-details-container.tsx new file mode 100644 index 000000000000..d9ff17cb277d --- /dev/null +++ b/packages/cfd/src/Containers/cfd-personal-details-container.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { Div100vhContainer, Text } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { TCFDPersonalDetailsContainerProps } from './props.types'; +import CFDPersonalDetailsForm from '../Components/cfd-personal-details-form'; +import { getPropertyValue, isDesktop, WS } from '@deriv/shared'; +import { GetSettings } from '@deriv/api-types'; +import { observer, useStore } from '@deriv/stores'; + +type TFormValues = { [key: string]: string }; +type TSetSubmitting = (isSubmitting: boolean) => void; + +const CFDPersonalDetailsContainer = observer(({ onSubmit }: TCFDPersonalDetailsContainerProps) => { + const { client } = useStore(); + + const { account_settings, getChangeableFields, residence_list, setAccountSettings } = client; + + const [form_error, setFormError] = React.useState(''); + const [is_loading, setIsLoading] = React.useState(false); + const [form_values, setFormValues] = React.useState({ + citizen: '', + place_of_birth: '', + tax_residence: '', + tax_identification_number: '', + account_opening_reason: '', + }); + + const initiatePersonalDetails = async (setSubmitting?: TSetSubmitting) => { + // force request to update settings cache since settings have been updated + let get_settings_response: GetSettings; + if (!account_settings) { + const response = await WS.authorized.storage.getSettings(); + + if (response.error) { + setFormError(response.error.message); + if (typeof setSubmitting === 'function') { + setSubmitting(false); + } + return; + } + get_settings_response = response.get_settings; + } else { + get_settings_response = account_settings; + } + + const { citizen, place_of_birth, tax_residence, tax_identification_number, account_opening_reason } = + get_settings_response; + + setFormValues({ + ...form_values, + citizen: transform(citizen) || '', + place_of_birth: transform(place_of_birth) || '', + tax_residence: transform(tax_residence) || '', + tax_identification_number: tax_identification_number || '', + account_opening_reason: account_opening_reason || '', + }); + }; + + React.useEffect(() => { + setIsLoading(true); + initiatePersonalDetails().then(() => { + setIsLoading(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const transform = (value: unknown) => { + const [result] = residence_list?.filter(item => item.value === value); + return getPropertyValue(result, ['text']) || value; + }; + + const saveFormData = (_index: number, value: TFormValues) => { + setFormValues({ + ...value, + citizen: transform(value.citizen), + place_of_birth: transform(value.place_of_birth), + tax_residence: transform(value.tax_residence), + }); + }; + + const updateValue = async (index: number, value: TFormValues, setSubmitting: TSetSubmitting, is_dirty = true) => { + if (is_dirty) { + // Set account settings + const data = await WS.setSettings(value); + if (data.error) { + setFormError(data.error.message); + setSubmitting(false); + return; + } + initiatePersonalDetails(setSubmitting); + } + saveFormData(index, value); + setAccountSettings({ ...account_settings, ...value }); + onSubmit(index, value); + }; + + return ( + +
+ + {localize('Complete your personal details')} + +
+
+ +
+
+ ); +}); + +export default CFDPersonalDetailsContainer; diff --git a/packages/cfd/src/Containers/dmt5-trade-modal.tsx b/packages/cfd/src/Containers/dmt5-trade-modal.tsx index 68182f252f74..5c11a957b48a 100644 --- a/packages/cfd/src/Containers/dmt5-trade-modal.tsx +++ b/packages/cfd/src/Containers/dmt5-trade-modal.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; import { useDevice } from '@deriv-com/ui'; import { Text, Icon, Money, StatusBadge } from '@deriv/components'; -import getMT5StatusBadgeConfig from '@deriv/account/src/Configs/get-mt5-status-badge-config'; +import getStatusBadgeConfig from '@deriv/account/src/Configs/get-status-badge-config'; import { getCFDAccountKey, MT5_ACCOUNT_STATUS } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; @@ -70,7 +70,16 @@ const DMT5TradeModal = observer( return 'Financial'; }; - const { text: badge_text, icon: badge_icon } = getMT5StatusBadgeConfig(mt5_trade_account?.status); + const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig( + mt5_trade_account?.status, + undefined, + undefined, + undefined, + { + poi_status: authentication?.identity?.status, + poa_status: authentication?.document?.status, + } + ); const has_migration_status = [ MT5_ACCOUNT_STATUS.MIGRATED_WITH_POSITION, MT5_ACCOUNT_STATUS.MIGRATED_WITHOUT_POSITION, diff --git a/packages/cfd/src/Containers/jurisdiction-checkbox.tsx b/packages/cfd/src/Containers/jurisdiction-checkbox.tsx deleted file mode 100644 index baece2c1c4fe..000000000000 --- a/packages/cfd/src/Containers/jurisdiction-checkbox.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { Checkbox, StaticUrl, Text } from '@deriv/components'; -import { DBVI_COMPANY_NAMES } from '@deriv/shared'; -import { observer } from '@deriv/stores'; -import { Localize } from '@deriv/translations'; -import { useIsSelectedMT5AccountCreated } from '@deriv/hooks'; - -type TJurisdictionCheckBoxProps = { - class_name: string; - is_checked: boolean; - jurisdiction_selected_shortcode: string; - onCheck: () => void; -}; - -const JurisdictionCheckBox = observer( - ({ class_name, is_checked, jurisdiction_selected_shortcode, onCheck }: TJurisdictionCheckBoxProps) => { - const { available_account_to_create } = useIsSelectedMT5AccountCreated(); - const getCheckboxLabel = () => ( - - , - ]} - /> - - ); - - return ( -
- -
- ); - } -); - -export default JurisdictionCheckBox; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-back.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-back.spec.tsx new file mode 100644 index 000000000000..973bafad2576 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-back.spec.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import JurisdictionCardBack from '../jurisdiction-card-back'; + +describe('', () => { + type TMockProps = { + card_classname: string; + disabled: boolean; + toggleCardFlip: jest.Mock; + is_card_selected: boolean; + verification_docs: ('document_number' | 'name_and_address' | 'selfie' | 'identity_document')[]; + }; + + const mock_props: TMockProps = { + card_classname: 'test_classname', + disabled: false, + is_card_selected: false, + toggleCardFlip: jest.fn(), + verification_docs: [], + }; + + const exampleVerificationMessage = () => { + expect(screen.getByText('Verification in review.')).toBeInTheDocument(); + expect(screen.getByText('Verification failed. Resubmit your details.')).toBeInTheDocument(); + expect(screen.getByText('Verification successful.')).toBeInTheDocument(); + }; + + it('should render JurisdictionCardBack without any required submission if verification_docs is empty', () => { + render(); + const container = screen.getByTestId('dt_jurisdiction_card_back'); + expect(container).toHaveClass( + 'test_classname__card-content-container', + 'test_classname__card-flipped-container' + ); + expect(screen.getByText('We need you to submit these in order to get this account:')).toBeInTheDocument(); + expect(screen.queryByText('A selfie of yourself.')).not.toBeInTheDocument(); + expect( + screen.queryByText("Document number (e.g. identity card, passport, driver's license)") + ).not.toBeInTheDocument(); + expect( + screen.queryByText( + 'A recent utility bill (e.g. electricity, water or gas) or recent bank statement or government-issued letter with your name and address.' + ) + ).not.toBeInTheDocument(); + expect( + screen.queryByText("A copy of your identity document (e.g. identity card, passport, driver's license)") + ).not.toBeInTheDocument(); + exampleVerificationMessage(); + }); + + it('should render JurisdictionCardBack display required document_number and name_and_address submission', () => { + mock_props.verification_docs = ['document_number', 'name_and_address']; + render(); + expect(screen.queryByText('A selfie of yourself.')).not.toBeInTheDocument(); + expect( + screen.queryByText("A copy of your identity document (e.g. identity card, passport, driver's license)") + ).not.toBeInTheDocument(); + expect(screen.getByText('We need you to submit these in order to get this account:')).toBeInTheDocument(); + expect( + screen.getByText("Document number (e.g. identity card, passport, driver's license)") + ).toBeInTheDocument(); + expect( + screen.getByText( + 'A recent utility bill (e.g. electricity, water or gas) or recent bank statement or government-issued letter with your name and address.' + ) + ).toBeInTheDocument(); + exampleVerificationMessage(); + }); + + it('should render JurisdictionCardBack display required selfie, identity_document and name_and_address submission', () => { + mock_props.verification_docs = ['selfie', 'identity_document', 'name_and_address']; + render(); + expect(screen.getByText('We need you to submit these in order to get this account:')).toBeInTheDocument(); + expect(screen.getByText('A selfie of yourself.')).toBeInTheDocument(); + expect( + screen.getByText("A copy of your identity document (e.g. identity card, passport, driver's license)") + ).toBeInTheDocument(); + expect( + screen.getByText( + 'A recent utility bill (e.g. electricity, water or gas) or recent bank statement or government-issued letter with your name and address.' + ) + ).toBeInTheDocument(); + exampleVerificationMessage(); + expect( + screen.queryByText("Document number (e.g. identity card, passport, driver's license)") + ).not.toBeInTheDocument(); + }); + + it('should render JurisdictionCardBack and include selected_card classname if is_card_selected is true', () => { + mock_props.is_card_selected = true; + render(); + const container = screen.getByTestId('dt_jurisdiction_card_back_container'); + expect(container).toHaveClass('test_classname--selected', 'selected-card'); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-front.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-front.spec.tsx new file mode 100644 index 000000000000..3d8a7b1b2a36 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-front.spec.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import JurisdictionCardFront from '../jurisdiction-card-front'; +import { render, screen } from '@testing-library/react'; +import { Jurisdiction } from '@deriv/shared'; + +describe('JurisdictionCardFront', () => { + const p2p_status: 'none' | 'active' | 'temp_ban' | 'perm_ban' = 'active'; + const prompt_client_to_authenticate: 0 | 1 = 1; + const mock_props = { + account_status: { + p2p_poa_required: 1, + currency_config: { usd: {} }, + p2p_status, + prompt_client_to_authenticate, + risk_classification: '', + status: [], + }, + disabled: false, + type_of_card: Jurisdiction.SVG, + card_classname: 'jurisdiction_test', + toggleCardFlip: jest.fn(), + card_values: { + financial_contents: [ + { + key: '', + title: '', + description: '', + title_indicators: { + type: 'displayText' as const, + display_text: '', + display_text_skin_color: '', + }, + }, + ], + synthetic_contents: [ + { + key: '', + title: '', + description: '', + title_indicators: { + type: 'displayText' as const, + display_text: '', + display_text_skin_color: '', + }, + }, + ], + header: 'Test Header', + over_header: 'Test Over Header', + is_over_header_available: false, + }, + card_data: [ + { + key: 'Test1', + title: 'Test Title 1', + description: 'Test Description 1', + title_indicators: { + type: 'displayText' as const, + display_text: 'Test 1', + display_text_skin_color: '', + }, + }, + { + key: 'Test2', + title: 'Test Title 2', + description: 'Test Description 2', + title_indicators: { + type: 'displayText' as const, + display_text: 'Test 2', + display_text_skin_color: '', + }, + }, + ], + is_card_selected: false, + verification_docs: [], + }; + + it('should render JurisdictionCardFront without over header', () => { + render(); + expect(screen.getByText('Test Header')).toBeInTheDocument(); + expect(screen.getByText('Test Title 1')).toBeInTheDocument(); + expect(screen.getByText('Test 1')).toBeInTheDocument(); + expect(screen.getByText('Test Description 1')).toBeInTheDocument(); + expect(screen.getByText('Test Title 2')).toBeInTheDocument(); + expect(screen.getByText('Test 2')).toBeInTheDocument(); + expect(screen.getByText('Test Description 2')).toBeInTheDocument(); + expect(screen.queryByText('Test Over Header')).not.toBeInTheDocument(); + }); + + it('should render JurisdictionCardFront with over header', () => { + mock_props.card_values.is_over_header_available = true; + render(); + expect(screen.getByText('Test Over Header')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-section.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-section.spec.tsx new file mode 100644 index 000000000000..f09d4d8f47f7 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card-section.spec.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import JurisdictionCardSection from '../jurisdiction-card-section'; +import { Jurisdiction } from '@deriv/shared'; + +describe('JurisdictionCardSection', () => { + type TMockProps = { + account_status: { + p2p_poa_required: 0 | 1; + authentication: { + document: { + status: 'none' | 'pending' | 'verified' | 'expired' | 'rejected' | undefined; + }; + identity: { + services: { + idv: { + status: 'none' | 'pending' | 'verified' | 'expired' | 'rejected' | undefined; + }; + onfido: { + status: 'none' | 'pending' | 'verified' | 'expired' | 'rejected' | undefined; + }; + manual: { + status: 'none' | 'pending' | 'verified' | 'expired' | 'rejected' | undefined; + }; + }; + }; + needs_verification: string[]; + }; + currency_config: { + [k: string]: { + is_deposit_suspended?: 0 | 1; + is_withdrawal_suspended?: 0 | 1; + }; + }; + p2p_status: 'none'; + prompt_client_to_authenticate: 0; + risk_classification: string; + status: string[]; + }; + card_section_item: { + key: string; + title: string; + title_indicators?: { + type: 'displayText'; + display_text: string; + display_text_skin_color: string; + }; + description?: string; + clickable_description?: [{ type: 'link' | 'text'; text: string }]; + }; + type_of_card: 'svg' | 'bvi' | 'vanuatu' | 'labuan' | 'maltainvest'; + toggleCardFlip: jest.Mock; + verification_docs: ['document_number' | 'selfie' | 'identity_document' | 'name_and_address'] | []; + }; + const mock_props: TMockProps = { + account_status: { + p2p_poa_required: 0, + authentication: { + document: { + status: 'none', + }, + identity: { + services: { + idv: { + status: 'none', + }, + onfido: { + status: 'none', + }, + manual: { + status: 'none', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + card_section_item: { + key: '', + title: 'Test Title', + title_indicators: { + type: 'displayText', + display_text: 'Test Title Indicators Text', + display_text_skin_color: '', + }, + description: 'Test Description', + }, + type_of_card: Jurisdiction.SVG, + toggleCardFlip: jest.fn(), + verification_docs: [], + }; + + it('should render JurisdictionCardSection component', () => { + render(); + expect(screen.getByText('Test Title')).toBeInTheDocument(); + expect(screen.getByText('Test Title Indicators Text')).toBeInTheDocument(); + expect(screen.getByText('Test Description')).toBeInTheDocument(); + }); + + it('should render JurisdictionCardSection component with clickable description', () => { + const mock_props_with_clickable_description = { + ...mock_props, + card_section_item: { + ...mock_props.card_section_item, + clickable_description: [{ type: 'link' as const, text: 'Test Link' }], + }, + }; + + render(); + expect(screen.getByText('Test Title')).toBeInTheDocument(); + expect(screen.getByText('Test Title Indicators Text')).toBeInTheDocument(); + expect(screen.getByText('Test Link')).toBeInTheDocument(); + expect(screen.queryByText('Test Description')).not.toBeInTheDocument(); + }); + + it('should render JurisdictionCardSection component without displaying title indicators if it is empty', () => { + mock_props.card_section_item.title_indicators = undefined; + render(); + expect(screen.getByText('Test Title')).toBeInTheDocument(); + expect(screen.queryByText('Test Title Indicators Text')).not.toBeInTheDocument(); + expect(screen.getByText('Test Description')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card.spec.tsx new file mode 100644 index 000000000000..bc6587bbe6a6 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-card.spec.tsx @@ -0,0 +1,241 @@ +import React from 'react'; +import { Jurisdiction } from '@deriv/shared'; +import { render, screen } from '@testing-library/react'; +import JurisdictionCard from '../jurisdiction-card'; +import { TTradingPlatformAvailableAccount } from 'Components/props.types'; + +describe('JurisdictionCard', () => { + type TMockProps = { + account_status: { + p2p_poa_required: 0 | 1; + authentication: { + document: { + status: 'none' | 'pending' | 'expired' | 'verified' | 'rejected'; + }; + identity: { + services: { + idv: { + status: 'none' | 'pending' | 'expired' | 'verified' | 'rejected'; + }; + onfido: { + status: 'none' | 'pending' | 'expired' | 'verified' | 'rejected'; + }; + manual: { + status: 'none' | 'pending' | 'expired' | 'verified' | 'rejected'; + }; + }; + }; + needs_verification: string[]; + }; + currency_config: { + [k: string]: { is_deposit_suspended?: 0 | 1; is_withdrawal_suspended?: 0 | 1 }; + }; + p2p_status: 'none' | 'active' | 'temp_ban' | 'perm_ban'; + prompt_client_to_authenticate: 0 | 1; + risk_classification: string; + status: string[]; + }; + swapfree_available_accounts: TTradingPlatformAvailableAccount[]; + account_type: 'financial' | 'synthetic'; + disabled: boolean; + is_non_idv_design: boolean; + toggleDynamicLeverage: React.MouseEventHandler; + jurisdiction_selected_shortcode: string; + setJurisdictionSelectedShortcode: jest.Mock; + type_of_card: 'svg' | 'bvi' | 'labuan' | 'maltainvest' | 'vanuatu'; + }; + + let mock_props: TMockProps; + beforeEach(() => { + mock_props = { + account_status: { + p2p_poa_required: 0, + authentication: { + document: { + status: 'none', + }, + identity: { + services: { + idv: { + status: 'none', + }, + onfido: { + status: 'none', + }, + manual: { + status: 'none', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + account_type: 'financial', + disabled: false, + is_non_idv_design: false, + toggleDynamicLeverage: jest.fn(), + jurisdiction_selected_shortcode: '', + setJurisdictionSelectedShortcode: jest.fn(), + swapfree_available_accounts: [], + type_of_card: Jurisdiction.SVG, + }; + }); + + it('should render JurisdictionCard with svg card', () => { + render(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('170+')).toBeInTheDocument(); + expect( + screen.getByText('Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs') + ).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.2 pips')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + }); + + it('should render JurisdictionCard with vanuatu card', () => { + mock_props.type_of_card = Jurisdiction.VANUATU; + render(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('170+')).toBeInTheDocument(); + expect( + screen.getByText('Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs') + ).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.2 pips')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect(screen.getByText('Learn more')).toBeInTheDocument(); + expect(screen.getByText('about required verifications.')).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu Financial Services Commission')).toBeInTheDocument(); + }); + + it('should render JurisdictionCard with maltainvest card', () => { + mock_props.type_of_card = Jurisdiction.MALTA_INVEST; + render(); + expect(screen.getByText('Malta')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('140+')).toBeInTheDocument(); + expect( + screen.getByText('Forex, stocks, stock indices, commodities, cryptocurrencies and synthetic indices.') + ).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:30')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.5 pips')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect(screen.getByText('Learn more')).toBeInTheDocument(); + expect(screen.getByText('about required verifications.')).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect( + screen.getByText('Malta Financial Services Authority (MFSA) (licence no. IS/70156)') + ).toBeInTheDocument(); + }); + + it('should render JurisdictionCard with bvi card', () => { + mock_props.type_of_card = Jurisdiction.BVI; + render(); + expect(screen.getByText('British Virgin Islands')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('170+')).toBeInTheDocument(); + expect( + screen.getByText('Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs') + ).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.2 pips')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect(screen.getByText('Learn more')).toBeInTheDocument(); + expect(screen.getByText('about required verifications.')).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect( + screen.getByText('British Virgin Islands Financial Services Commission (License no. SIBA/L/18/1114)') + ).toBeInTheDocument(); + }); + + it('should render JurisdictionCard with labuan card', () => { + mock_props.type_of_card = Jurisdiction.LABUAN; + render(); + expect(screen.getByText('Straight-through processing')).toBeInTheDocument(); + expect(screen.getByText('Labuan')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('80+')).toBeInTheDocument(); + expect(screen.getByText('Forex (standard/exotic) and cryptocurrencies')).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:100')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.6 pips')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect(screen.getByText('Learn more')).toBeInTheDocument(); + expect(screen.getByText('about required verifications.')).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Labuan Financial Services Authority (licence no. MB/18/0024)')).toBeInTheDocument(); + }); + + it('should render JurisdictionCard with synthetic account_type', () => { + mock_props.account_type = 'synthetic'; + render(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('210+')).toBeInTheDocument(); + expect( + screen.getByText( + 'Forex (standard), stock indices, commodities, cryptocurrencies, stocks, ETFs, synthetic indices, basket indices and derived FX' + ) + ).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + }); + + it('should render JurisdictionCard with disabled to be true', () => { + mock_props.disabled = true; + render(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + }); + + it('should render JurisdictionCard on the back', () => { + render(); + expect(screen.getByText('We need you to submit these in order to get this account:')).toBeInTheDocument(); + expect(screen.getByText('Verification in review.')).toBeInTheDocument(); + expect(screen.getByText('Verification failed. Resubmit your details.')).toBeInTheDocument(); + expect(screen.getByText('Verification successful.')).toBeInTheDocument(); + }); + + it('should click on JurisdictionCard and render setJurisdictionSelectedShortCode function', () => { + render(); + const jurisdiction_card = screen.getByTestId('dt_jurisdiction_card'); + jurisdiction_card.click(); + expect(mock_props.setJurisdictionSelectedShortcode).toHaveBeenCalledWith('svg'); + }); + + it('should click on Learn More and include cfd-card-flipped into classnames', () => { + mock_props.type_of_card = Jurisdiction.BVI; + render(); + const learn_more = screen.getByText('Learn more'); + learn_more.click(); + expect(screen.getByTestId('dt_jurisdiction_card')).toHaveClass('cfd-card-flipped'); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-clickable-description.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-clickable-description.spec.tsx new file mode 100644 index 000000000000..607ca2cae4dc --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-clickable-description.spec.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import JurisdictionClickableDescription from '../jurisdiction-clickable-description'; + +describe('JurisdictionClickableDescription', () => { + type TClickableDescription = { + text: string; + type: 'link' | 'text'; + onClick?: React.MouseEventHandler; + }; + type TMockProps = { + clickable_description: TClickableDescription[]; + toggleCardFlip: jest.Mock; + }; + + const mock_props: TMockProps = { + clickable_description: [ + { + text: 'Click here', + type: 'link', + }, + { + text: 'to learn more about the documents required for verification.', + type: 'text', + }, + { + text: 'Open another Modal', + type: 'link', + onClick: jest.fn(), + }, + ], + toggleCardFlip: jest.fn(), + }; + + it('should render JurisdictionClickableDescription', () => { + render(); + const container = screen.getAllByTestId('dt_jurisdiction_clickable_description'); + expect(container[0]).toHaveClass('cfd-card-clickable-description-link'); + expect(screen.getByText('Click here')).toBeInTheDocument(); + expect(screen.getByText('to learn more about the documents required for verification.')).toBeInTheDocument(); + }); + + it('should call toggleCardFlip when link is clicked', () => { + render(); + screen.getByText('Click here').click(); + expect(mock_props.toggleCardFlip).toHaveBeenCalled(); + }); + + it('should call onClick when link is clicked with an onClick function', () => { + render(); + screen.getByText('Open another Modal').click(); + expect(mock_props.clickable_description[2].onClick).toHaveBeenCalled(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-checkbox.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-checkbox.spec.tsx new file mode 100644 index 000000000000..3d23f3007fb9 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-checkbox.spec.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import JurisdictionModalCheckbox from '../jurisdiction-modal-checkbox'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { JURISDICTION } from '../../../Helpers/cfd-config'; + +describe('JurisdictionModalCheckbox', () => { + const mock_props = { + class_name: '', + is_checked: false, + jurisdiction_selected_shortcode: '', + onCheck: jest.fn(), + should_restrict_bvi_account_creation: false, + should_restrict_vanuatu_account_creation: false, + }; + + const renderComponent = (props = mock_props) => { + render( + + + + ); + }; + + it('should not render JurisdictionModalCheckbox when jurisdiction is not selected', () => { + renderComponent({ ...mock_props }); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); + }); + + it('should render labuan account and displays checkbox', () => { + renderComponent({ ...mock_props, jurisdiction_selected_shortcode: JURISDICTION.LABUAN }); + expect(screen.queryByRole('checkbox')).toBeInTheDocument(); + }); + + it('should render function onCheck when checkbox is clicked for labuan account', () => { + renderComponent({ ...mock_props, jurisdiction_selected_shortcode: JURISDICTION.LABUAN }); + const checkbox = screen.getByRole('checkbox'); + fireEvent.click(checkbox); + expect(mock_props.onCheck).toHaveBeenCalled(); + }); + + it('should render svg account without displaying checkbox', () => { + renderComponent({ ...mock_props, jurisdiction_selected_shortcode: JURISDICTION.SVG }); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); + }); + + it('should render bvi account without restriction and displays checkbox', () => { + renderComponent({ ...mock_props, jurisdiction_selected_shortcode: JURISDICTION.BVI }); + expect(screen.queryByRole('checkbox')).toBeInTheDocument(); + }); + + it('should render function onCheck when checkbox is clicked for bvi account without restriction', () => { + renderComponent({ ...mock_props, jurisdiction_selected_shortcode: JURISDICTION.BVI }); + const checkbox = screen.getByRole('checkbox'); + fireEvent.click(checkbox); + expect(mock_props.onCheck).toHaveBeenCalled(); + }); + + it('should render bvi account with restriction and does not display checkbox', () => { + renderComponent({ + ...mock_props, + jurisdiction_selected_shortcode: JURISDICTION.BVI, + should_restrict_bvi_account_creation: true, + }); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); + }); + + it('should render vanuatu account without restriction and displays checkbox', () => { + renderComponent({ + ...mock_props, + jurisdiction_selected_shortcode: JURISDICTION.VANUATU, + }); + expect(screen.queryByRole('checkbox')).toBeInTheDocument(); + }); + + it('should render function onCheck when checkbox is clicked for vanuatu account without restriction', () => { + renderComponent({ + ...mock_props, + jurisdiction_selected_shortcode: JURISDICTION.VANUATU, + }); + const checkbox = screen.getByRole('checkbox'); + fireEvent.click(checkbox); + expect(mock_props.onCheck).toHaveBeenCalled(); + }); + + it('should render vanuatu account with restriction and does not display checkbox', () => { + renderComponent({ + ...mock_props, + jurisdiction_selected_shortcode: JURISDICTION.VANUATU, + should_restrict_vanuatu_account_creation: true, + }); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx new file mode 100644 index 000000000000..d1810d0a067d --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-content.spec.tsx @@ -0,0 +1,394 @@ +import React from 'react'; +import RootStore from 'Stores/index'; +import { render, screen } from '@testing-library/react'; +import { Jurisdiction } from '@deriv/shared'; +import JurisdictionModalContent from '../jurisdiction-modal-content'; + +describe('JurisdictionModalContent', () => { + const mock_store = { + common: {}, + client: {}, + ui: {}, + }; + const mock_context = new RootStore(mock_store); + const mock_props = { + account_status: mock_context.client, + account_type: '', + is_non_idv_design: false, + is_virtual: false, + toggleDynamicLeverage: jest.fn(), + jurisdiction_selected_shortcode: '', + setJurisdictionSelectedShortcode: jest.fn(), + all_market_type_available_accounts: [ + { + market_type: 'all' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.SVG, + sub_account_type: '', + }, + ], + synthetic_available_accounts: [ + { + market_type: 'gaming' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.SVG, + sub_account_type: '', + }, + { + market_type: 'gaming' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.BVI, + sub_account_type: '', + }, + { + market_type: 'gaming' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.VANUATU, + sub_account_type: '', + }, + ], + financial_available_accounts: [ + { + market_type: 'financial' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.SVG, + sub_account_type: '', + }, + { + market_type: 'financial' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.BVI, + sub_account_type: '', + }, + { + market_type: 'financial' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.VANUATU, + sub_account_type: '', + }, + { + market_type: 'financial' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.LABUAN, + sub_account_type: '', + }, + ], + context: mock_context, + real_synthetic_accounts_existing_data: [], + real_financial_accounts_existing_data: [], + real_swapfree_accounts_existing_data: [], + swapfree_available_accounts: [ + { + market_type: 'gaming' as const, + name: '', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: Jurisdiction.SVG, + sub_account_type: '', + }, + ], + card_flip_status: { + svg: false, + bvi: false, + vanuatu: false, + labuan: false, + maltainvest: false, + }, + flipCard: jest.fn(), + }; + + it('should display cfd-jurisdiction-card--synthetic__wrapper in class name', () => { + render(); + const container = screen.getByTestId('dt-jurisdiction-modal-content'); + expect(container).toHaveClass('cfd-jurisdiction-card--synthetic__wrapper'); + }); + + it('should display 3 types of jurisdiction card for synthetics account', () => { + render(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('British Virgin Islands')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); + }); + + it('should display content of 3 types of jurisdiction correctly for synthetics account', () => { + render(); + expect(screen.getAllByText('Assets')).toHaveLength(3); + expect( + screen.getAllByText( + 'Forex (standard), stock indices, commodities, cryptocurrencies, stocks, ETFs, synthetic indices, basket indices and derived FX' + ) + ).toHaveLength(3); + expect(screen.getAllByText('210+')).toHaveLength(3); + expect(screen.getAllByText('Leverage')).toHaveLength(3); + expect(screen.getAllByText('1:1000')).toHaveLength(3); + expect(screen.getAllByText('Verifications')).toHaveLength(3); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getAllByText('Learn more')).toHaveLength(2); + expect(screen.getAllByText('about required verifications.')).toHaveLength(2); + expect(screen.getAllByText('Regulator/EDR')).toHaveLength(3); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + expect( + screen.getByText('British Virgin Islands Financial Services Commission (License no. SIBA/L/18/1114)') + ).toBeInTheDocument(); + expect(screen.getByText('Vanuatu Financial Services Commission')).toBeInTheDocument(); + }); + + it('should display cfd-jurisdiction-card--financial__wrapper in class name', () => { + render(); + const container = screen.getByTestId('dt-jurisdiction-modal-content'); + expect(container).toHaveClass('cfd-jurisdiction-card--financial__wrapper'); + }); + + it('should display 4 types of jurisdiction card for financial account', () => { + render(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('British Virgin Islands')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); + expect(screen.getByText('Labuan')).toBeInTheDocument(); + }); + + it('should display content of 4 types of jurisdiction correctly for synthetics account', () => { + render(); + expect(screen.getAllByText('Assets')).toHaveLength(4); + expect( + screen.getAllByText('Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs') + ).toHaveLength(3); + expect(screen.getByText('Forex (standard/exotic) and cryptocurrencies')).toBeInTheDocument(); + expect(screen.getAllByText('Leverage')).toHaveLength(4); + expect(screen.getAllByText('1:1000')).toHaveLength(3); + expect(screen.getByText('1:100')).toBeInTheDocument(); + expect(screen.getAllByText('Spreads from')).toHaveLength(4); + expect(screen.getByText('0.6 pips')).toBeInTheDocument(); + expect(screen.getAllByText('0.2 pips')).toHaveLength(3); + expect(screen.getAllByText('Verifications')).toHaveLength(4); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getAllByText('Learn more')).toHaveLength(3); + expect(screen.getAllByText('about required verifications.')).toHaveLength(3); + expect(screen.getAllByText('Regulator/EDR')).toHaveLength(4); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + expect( + screen.getByText('British Virgin Islands Financial Services Commission (License no. SIBA/L/18/1114)') + ).toBeInTheDocument(); + expect(screen.getByText('Vanuatu Financial Services Commission')).toBeInTheDocument(); + expect(screen.getByText('Labuan Financial Services Authority (licence no. MB/18/0024)')).toBeInTheDocument(); + }); + + it('should display only financial maltainvest for MF clients', () => { + mock_props.financial_available_accounts = [ + { ...mock_props.financial_available_accounts[0], shortcode: Jurisdiction.MALTA_INVEST }, + ]; + mock_props.synthetic_available_accounts = []; + mock_props.all_market_type_available_accounts = []; + render(); + const container = screen.getByTestId('dt-jurisdiction-modal-content'); + expect(container).toHaveClass('cfd-jurisdiction-card--financial__wrapper'); + expect(screen.queryByText('St. Vincent & Grenadines')).not.toBeInTheDocument(); + expect(screen.queryByText('British Virgin Islands')).not.toBeInTheDocument(); + expect(screen.queryByText('Vanuatu')).not.toBeInTheDocument(); + expect(screen.queryByText('Labuan')).not.toBeInTheDocument(); + expect(screen.getByText('Malta')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('140+')).toBeInTheDocument(); + expect( + screen.getByText('Forex, stocks, stock indices, commodities, cryptocurrencies and synthetic indices.') + ).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:30')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.5 pips')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect(screen.getByText('Learn more')).toBeInTheDocument(); + expect(screen.getByText('about required verifications.')).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect( + screen.getByText('Malta Financial Services Authority (MFSA) (licence no. IS/70156)') + ).toBeInTheDocument(); + }); + + it('should display only financial svg for highrisk clients', () => { + mock_props.financial_available_accounts = [ + { ...mock_props.financial_available_accounts[0], shortcode: Jurisdiction.SVG }, + ]; + mock_props.synthetic_available_accounts = [ + { ...mock_props.synthetic_available_accounts[0], shortcode: Jurisdiction.SVG }, + ]; + render(); + const container = screen.getByTestId('dt-jurisdiction-modal-content'); + expect(container).toHaveClass('cfd-jurisdiction-card--financial__wrapper'); + expect(screen.queryByText('British Virgin Islands')).not.toBeInTheDocument(); + expect(screen.queryByText('Vanuatu')).not.toBeInTheDocument(); + expect(screen.queryByText('Labuan')).not.toBeInTheDocument(); + expect(screen.queryByText('Malta')).not.toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + expect(screen.getByText('170+')).toBeInTheDocument(); + expect( + screen.getByText('Forex (standard/micro), stocks, stock indices, commodities, cryptocurrencies and ETFs') + ).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + expect(screen.getByText('0.2 pips')).toBeInTheDocument(); + }); + + it('should display only synthetic svg for highrisk clients', () => { + mock_props.financial_available_accounts = [ + { ...mock_props.financial_available_accounts[0], shortcode: Jurisdiction.SVG }, + ]; + mock_props.synthetic_available_accounts = [ + { ...mock_props.synthetic_available_accounts[0], shortcode: Jurisdiction.SVG }, + ]; + render(); + const container = screen.getByTestId('dt-jurisdiction-modal-content'); + expect(container).toHaveClass('cfd-jurisdiction-card--synthetic__wrapper'); + expect(screen.queryByText('British Virgin Islands')).not.toBeInTheDocument(); + expect(screen.queryByText('Vanuatu')).not.toBeInTheDocument(); + expect(screen.queryByText('Labuan')).not.toBeInTheDocument(); + expect(screen.queryByText('Malta')).not.toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + expect(screen.getByText('210+')).toBeInTheDocument(); + expect( + screen.getByText( + 'Forex (standard), stock indices, commodities, cryptocurrencies, stocks, ETFs, synthetic indices, basket indices and derived FX' + ) + ).toBeInTheDocument(); + }); + + it('should display cfd-jurisdiction-card--all__wrapper in class name', () => { + render(); + const container = screen.getByTestId('dt-jurisdiction-modal-content'); + expect(container).toHaveClass('cfd-jurisdiction-card--all__wrapper'); + }); + + it('should display only svg jurisdiction card for swap-free account', () => { + render(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.queryByText('British Virgin Islands')).not.toBeInTheDocument(); + expect(screen.queryByText('Labuan')).not.toBeInTheDocument(); + expect(screen.queryByText('Vanuatu')).not.toBeInTheDocument(); + }); + + it('should display content of swap-free jurisdiction correctly in card', () => { + render(); + expect(screen.getByText('Assets')).toBeInTheDocument(); + expect( + screen.getByText('Forex, stocks, stock indices, commodities, cryptocurrencies, ETFs and synthetic indices') + ).toBeInTheDocument(); + expect(screen.getByText('40+')).toBeInTheDocument(); + expect(screen.getByText('Leverage')).toBeInTheDocument(); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Verifications')).toBeInTheDocument(); + expect( + screen.getByText('You will need to submit proof of identity and address once you reach certain thresholds.') + ).toBeInTheDocument(); + expect(screen.getByText('Regulator/EDR')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC (company no. 273 LLC 2020)')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx new file mode 100644 index 000000000000..81fcf8b804ce --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-foot-note.spec.tsx @@ -0,0 +1,231 @@ +import React from 'react'; +import JurisdictionModalFootNote from '../jurisdiction-modal-foot-note'; +import { render, screen } from '@testing-library/react'; +import RootStore from 'Stores/index'; +import { Jurisdiction } from '@deriv/shared'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +describe('JurisdictionModalFootNote', () => { + const mock_store = mockStore({ + common: {}, + client: {}, + ui: {}, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const mock_context = new RootStore(mock_store); + const mock_props = { + account_status: { + p2p_poa_required: 1, + authentication: { + document: { + status: 'none' as const, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none' as const, + prompt_client_to_authenticate: 0 as const, + risk_classification: '', + status: [''], + }, + account_type: '', + context: mock_context, + card_classname: '', + jurisdiction_selected_shortcode: Jurisdiction.SVG, + should_restrict_bvi_account_creation: false, + should_restrict_vanuatu_account_creation: false, + }; + it('should render JurisdictionModalFootNote', () => { + render(, { wrapper }); + expect(screen.getByTestId('dt-jurisdiction-footnote')).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote with className', () => { + render(, { wrapper }); + const container = screen.getByTestId('dt-jurisdiction-footnote'); + expect(container).toHaveClass('mock_jurisdiction__footnote'); + }); + + it('should render JurisdictionModalFootNote and show svg message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText('Add your Deriv MT5 Standard account under Deriv (SVG) LLC (company no. 273 LLC 2020).') + ).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote without bvi_restriction and show bvi message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText( + 'Add your Deriv MT5 Standard account under Deriv (BVI) Ltd, regulated by the British Virgin Islands Financial Services Commission (License no. SIBA/L/18/1114).' + ) + ).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote with bvi_restriction and show bvi restriction message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText('To create this account first we need you to resubmit your proof of address.') + ).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote with bvi_restriction and poa is pending, then display resubmit poa message', () => { + const mock_account_status = { + p2p_poa_required: 0, + authentication: { + document: { + status: 'pending' as const, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none' as const, + prompt_client_to_authenticate: 0 as const, + risk_classification: '', + status: [''], + }; + render( + , + { wrapper } + ); + const poa_message = screen.getByText( + 'You can open this account once your submitted documents have been verified.' + ); + expect(poa_message).toBeInTheDocument(); + expect(poa_message).toHaveClass('mock_jurisdiction__footnote--pending'); + }); + + it('should render JurisdictionModalFootNote without vanuatu_restriction and show vanuatu message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText( + 'Add Your Deriv MT5 Standard account under Deriv (V) Ltd, regulated by the Vanuatu Financial Services Commission.' + ) + ).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote with vanuatu_restriction and show vanuatu restriction message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText('To create this account first we need you to resubmit your proof of address.') + ).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote with vanuatu_restriction and poa is pending, then display resubmit poa message', () => { + const mock_account_status = { + p2p_poa_required: 0, + authentication: { + document: { + status: 'pending' as const, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none' as const, + prompt_client_to_authenticate: 0 as const, + risk_classification: '', + status: [''], + }; + render( + , + { wrapper } + ); + const poa_message = screen.getByText( + 'You can open this account once your submitted documents have been verified.' + ); + expect(poa_message).toBeInTheDocument(); + expect(poa_message).toHaveClass('mock_jurisdiction__footnote--pending'); + }); + + it('should render JurisdictionModalFootNote show labuan message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText( + 'Add your Deriv MT5 Standard STP account under Deriv (FX) Ltd regulated by Labuan Financial Services Authority (Licence no. MB/18/0024).' + ) + ).toBeInTheDocument(); + }); + + it('should render JurisdictionModalFootNote show maltainvest message', () => { + render( + , + { wrapper } + ); + expect( + screen.getByText( + 'Add your Deriv MT5 CFDs account under Deriv Investments (Europe) Limited, regulated by the Malta Financial Services Authority (MFSA) (licence no. IS/70156).' + ) + ).toBeInTheDocument(); + }); + + it('should not render JurisdictionModalFootNote when jurisdiction_shortcode is empty', () => { + render(, { wrapper }); + expect(screen.queryByTestId('dt-jurisdiction-footnote')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx new file mode 100644 index 000000000000..2ee7105b2345 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal-title.spec.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { DynamicLeverageContext } from '../../dynamic-leverage/dynamic-leverage-context'; +import JurisdictionModalTitle from '../jurisdiction-modal-title'; + +type TMockProps = { + is_dynamic_leverage_visible: boolean; + toggleDynamicLeverage: React.MouseEventHandler; + account_type: string; + show_eu_related_content: boolean; + platform: any; +}; + +const mock_props = { + is_dynamic_leverage_visible: false, + toggleDynamicLeverage: jest.fn(), + account_type: 'Financial', + show_eu_related_content: false, + platform: 'mt5', +}; + +const JurisdictionModalTitleComponent = ({ + is_dynamic_leverage_visible, + toggleDynamicLeverage, + ...mockProps +}: TMockProps) => { + const dynamic_leverage_value = React.useMemo( + () => ({ is_dynamic_leverage_visible, toggleDynamicLeverage }), + [is_dynamic_leverage_visible, toggleDynamicLeverage] + ); + return ( + + + + ); +}; + +describe('JurisdictionModalTitle', () => { + it('should render JurisdictionModalTitle', () => { + const new_props = { ...mock_props, platform: 'mt5' }; + render(); + const title = screen.getByText(/jurisdiction/); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Choose a jurisdiction for your Deriv MT5 Financial account'); + }); + + it('should render JurisdictionModalTitle correctly if show_eu_related_content is true', () => { + const new_props = { ...mock_props, show_eu_related_content: true, platform: 'mt5' }; + render(); + const title = screen.getByText(/jurisdiction/); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Choose a jurisdiction for your Deriv MT5 CFDs account'); + }); + + it('should render JurisdictionModalTitle correctly if show_eu_related_content is true', () => { + const new_props = { ...mock_props, platform: 'ctrader' }; + render(); + const title = screen.getByText(/jurisdiction/); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Choose a jurisdiction for your cTrader account'); + }); + + it('should render JurisdictionModalTitle correctly if is_dynamic_leverage_visible is true', () => { + const new_props = { ...mock_props, is_dynamic_leverage_visible: true, platform: 'mt5' }; + render(); + const title = screen.getByText(/Deriv/); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Get more out of Deriv MT5 Financial'); + const back_button = screen.getByTestId('back_icon'); + expect(back_button).toBeInTheDocument(); + userEvent.click(back_button); + expect(new_props.toggleDynamicLeverage).toHaveBeenCalled(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx new file mode 100644 index 000000000000..fbbb119533a4 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-modal.spec.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { TStores } from '@deriv/stores/types'; +import { CFDStoreProvider } from 'Stores/Modules/CFD/Helpers/useCfdStores'; +import { TTradingPlatformAvailableAccount } from 'Components/props.types'; +import { TJurisdictionModalProps } from '../../props.types'; +import JurisdictionModal from '../jurisdiction-modal'; + +jest.mock('@deriv-com/ui', () => ({ + ...jest.requireActual('@deriv-com/ui'), + useDevice: jest.fn(() => ({ isMobile: false, isDesktop: true })), +})); + +jest.mock('@deriv/shared/src/utils/screen/responsive', () => ({ + ...jest.requireActual('@deriv/shared/src/utils/screen/responsive'), + isMobile: jest.fn(), + isDesktop: jest.fn(() => true), +})); + +jest.mock('../../dynamic-leverage/dynamic-leverage-modal-content', () => + jest.fn(() =>
) +); + +let modal_root_el: HTMLDivElement, store: TStores; + +const mock_store = { + common: {}, + client: { + trading_platform_available_accounts: [ + { + market_type: 'financial', + name: 'Deriv (SVG) LLC', + shortcode: 'svg', + sub_account_type: 'swap_free', + } as TTradingPlatformAvailableAccount, + ], + }, + ui: { + disableApp: jest.fn(), + enableApp: jest.fn(), + }, + traders_hub: { + show_eu_related_content: false, + }, + modules: { + cfd: { + account_type: { + type: 'Financial', + }, + is_jurisdiction_modal_visible: true, + toggleJurisdictionModal: jest.fn(), + setJurisdictionSelectedShortcode: jest.fn(), + }, + }, +}; + +beforeEach(() => { + store = mockStore(mock_store); +}); + +beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); +}); + +afterAll(() => { + document.body.removeChild(modal_root_el); +}); + +const JurisdictionModalComponent = (props: TJurisdictionModalProps) => { + return ( + + + + + + ); +}; + +describe('JurisdictionModal', () => { + const mock_props = { + openPasswordModal: jest.fn(), + }; + + it('should render JurisdictionModal', () => { + store = mockStore({ + ...mock_store, + common: { platform: 'mt5' }, + }); + render(); + + const title = screen.getByRole('heading'); + const close_button = screen.getAllByRole('button')[0]; + + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Choose a jurisdiction for your Deriv MT5 Financial account'); + expect(close_button).toBeInTheDocument(); + }); + + it('should render JurisdictionModal with show_eu_related_content', () => { + store = mockStore({ + ...mock_store, + traders_hub: { show_eu_related_content: true }, + common: { platform: 'mt5' }, + }); + + render(); + + const title = screen.getByRole('heading'); + + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Choose a jurisdiction for your Deriv MT5 CFDs account'); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-title-indicator.spec.tsx b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-title-indicator.spec.tsx new file mode 100644 index 000000000000..b1d6a4c676bf --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/__test__/jurisdiction-title-indicator.spec.tsx @@ -0,0 +1,426 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import JurisdictionTitleIndicator from '../jurisdiction-title-indicator'; +import { TJurisdictionTitleIndicatorProps } from 'Containers/props.types'; +import { Jurisdiction } from '@deriv/shared'; + +describe('JurisdictionTitleIndicator', () => { + const mock_props: TJurisdictionTitleIndicatorProps = { + title_indicators: { + type: 'displayText', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.BVI, + account_status: { + p2p_poa_required: 0, + authentication: { + document: { + status: 'none', + }, + identity: { + services: { + idv: { + status: 'none', + }, + onfido: { + status: 'none', + }, + manual: { + status: 'none', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: [], + }; + it('should render JurisdictionTitleIndicator with displayText', () => { + render(); + expect(screen.getByText('Test Display Text')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons', () => { + mock_props.title_indicators.type = 'displayIcons'; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Default icon variant', () => { + mock_props.title_indicators.type = 'displayIcons'; + mock_props.verification_docs = ['document_number']; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Default_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Pending icon variant', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + idv: { + status: 'pending', + }, + onfido: { + status: 'pending', + }, + manual: { + status: 'pending', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['document_number'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Pending_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Failed icon variant', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + idv: { + status: 'rejected', + }, + onfido: { + status: 'rejected', + }, + manual: { + status: 'rejected', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['document_number'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Failed_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Verified icon variant', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + idv: { + status: 'verified', + }, + onfido: { + status: 'verified', + }, + manual: { + status: 'verified', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['document_number'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Verified_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Pending icon variant with type_of_card to be Vanuatu', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.VANUATU, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + onfido: { + status: 'pending', + }, + manual: { + status: 'pending', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['selfie'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Pending_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Pending icon variant with type_of_card to be MaltaInvest', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.MALTA_INVEST, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + onfido: { + status: 'pending', + }, + manual: { + status: 'pending', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['identity_document'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Pending_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Failed icon variant with type_of_card to be Vanuatu', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.VANUATU, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + onfido: { + status: 'rejected', + }, + manual: { + status: 'rejected', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['selfie'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Failed_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Verified icon variant with type_of_card to be Vanuatu', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.VANUATU, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'none', + }, + identity: { + services: { + onfido: { + status: 'verified', + }, + manual: { + status: 'verified', + }, + }, + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['selfie'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Verified_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Pending icon variant when verification_document is name_and_address and type_of_card to be svg', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.SVG, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'pending', + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['name_and_address'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Pending_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Failed icon variant when verification_document is name_and_address and type_of_card to be svg', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.SVG, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'rejected', + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['name_and_address'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Failed_icon')).toBeInTheDocument(); + }); + + it('should render JurisdictionTitleIndicator with displayIcons and Verified icon variant when verification_document is name_and_address and type_of_card to be svg', () => { + const new_mock_props: TJurisdictionTitleIndicatorProps = { + ...mock_props, + title_indicators: { + type: 'displayIcons', + display_text: 'Test Display Text', + }, + type_of_card: Jurisdiction.SVG, + account_status: { + p2p_poa_required: 0, + authentication: { + ...mock_props.account_status.authentication, + document: { + status: 'verified', + }, + needs_verification: [], + }, + currency_config: {}, + p2p_status: 'none', + prompt_client_to_authenticate: 0, + risk_classification: '', + status: [''], + }, + verification_docs: ['name_and_address'], + }; + render(); + expect(screen.getByTestId('dt_jurisdiction_title_indicator_Verified_icon')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-back.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-back.tsx new file mode 100644 index 000000000000..ac7b7f2366cf --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-back.tsx @@ -0,0 +1,73 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import { jurisdictionVerificationContents } from '../../Constants/jurisdiction-contents/jurisdiction-verification-contents'; +import { TJurisdictionCardBackProps } from 'Containers/props.types'; + +const JurisdictionCardBack = ({ + card_classname, + disabled, + is_card_selected, + toggleCardFlip, + verification_docs, +}: TJurisdictionCardBackProps) => ( +
+
+
+ +
+ + {jurisdictionVerificationContents().short_description} + +
+ {verification_docs?.map(verification_item => ( +
+
+ +
+ + {jurisdictionVerificationContents().required_verification_docs[verification_item]?.text} + +
+ ))} +
+
+
+ {jurisdictionVerificationContents().status_references.map(status_item => ( +
+
+ +
+ + {status_item.text} + +
+ ))} +
+
+
+); + +export default JurisdictionCardBack; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-front.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-front.tsx new file mode 100644 index 000000000000..7a08da70dac3 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-front.tsx @@ -0,0 +1,82 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { TJurisdictionCardFrontProps } from 'Containers/props.types'; +import JurisdictionCardSection from './jurisdiction-card-section'; + +const JurisdictionCardFront = ({ + account_status, + card_classname, + card_data, + card_values, + disabled, + is_card_selected, + toggleCardFlip, + type_of_card, + verification_docs, +}: TJurisdictionCardFrontProps) => ( +
+
+ {card_values.is_over_header_available ? ( + + + + ) : ( +
+ )} + + + +
+ {card_data.map((item, index) => ( + + + {index < card_data.length - 1 &&
} + + ))} +
+ {disabled && ( +
+ + + +
+ )} +
+
+); + +export default JurisdictionCardFront; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-section.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-section.tsx new file mode 100644 index 000000000000..ee7e068062ce --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card-section.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { TJurisdictionCardSectionProps } from '../props.types'; +import JurisdictionClickableDescription from './jurisdiction-clickable-description'; +import JurisdictionTitleIndicator from './jurisdiction-title-indicator'; + +const JurisdictionCardSection = ({ + account_status, + card_section_item, + toggleCardFlip, + type_of_card, + verification_docs, +}: TJurisdictionCardSectionProps) => ( +
+
+ + {card_section_item.title} + + {card_section_item.title_indicators && ( + + )} +
+ {(card_section_item.clickable_description || card_section_item.description) && ( +
+ {card_section_item.clickable_description ? ( + + ) : ( + card_section_item.description && ( + + {card_section_item.description} + + ) + )} +
+ )} +
+); + +export default JurisdictionCardSection; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card.tsx new file mode 100644 index 000000000000..bfbf0b55651d --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-card.tsx @@ -0,0 +1,79 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Jurisdiction } from '@deriv/shared'; +import { getJurisdictionContents } from '../../Constants/jurisdiction-contents/jurisdiction-contents'; +import { TJurisdictionCardProps } from '../props.types'; +import JurisdictionCardBack from './jurisdiction-card-back'; +import JurisdictionCardFront from './jurisdiction-card-front'; +import { useDynamicLeverage } from '../dynamic-leverage/dynamic-leverage-context'; + +const JurisdictionCard = ({ + account_status, + account_type, + disabled, + is_non_idv_design = false, + jurisdiction_selected_shortcode, + setJurisdictionSelectedShortcode, + type_of_card, +}: TJurisdictionCardProps) => { + const card_classname = `cfd-jurisdiction-card--${account_type}`; + const is_synthetic = account_type === 'synthetic'; + const is_swapfree = account_type === 'all'; + const { toggleDynamicLeverage } = useDynamicLeverage(); + const card_values = getJurisdictionContents({ toggleDynamicLeverage })[type_of_card]; + const non_synthetic_card_data = is_swapfree + ? card_values?.swapfree_contents ?? [] + : card_values?.financial_contents; + const card_data = is_synthetic ? card_values?.synthetic_contents : non_synthetic_card_data; + const [is_card_flipped, setIsCardFlipped] = React.useState(false); + const is_card_selected = jurisdiction_selected_shortcode === type_of_card; + let verification_docs = is_synthetic + ? card_values?.synthetic_verification_docs + : card_values?.financial_verification_docs; + if ([Jurisdiction.BVI, Jurisdiction.VANUATU, Jurisdiction.LABUAN].includes(type_of_card) && is_non_idv_design) { + verification_docs = ['selfie', 'identity_document', 'name_and_address']; + } + + const cardSelection = (cardType: string) => { + setJurisdictionSelectedShortcode(jurisdiction_selected_shortcode === cardType ? '' : cardType); + }; + + const toggleCardFlip: React.MouseEventHandler = event => { + event.stopPropagation(); + setIsCardFlipped(!is_card_flipped); + }; + + return ( +
+
undefined : () => cardSelection(type_of_card)} + > + + +
+
+ ); +}; + +export default JurisdictionCard; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-clickable-description.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-clickable-description.tsx new file mode 100644 index 000000000000..1cea949dfa81 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-clickable-description.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { TJurisdictionClickableDescriptionProps } from 'Containers/props.types'; + +const JurisdictionClickableDescription = ({ + clickable_description, + toggleCardFlip, +}: TJurisdictionClickableDescriptionProps) => ( +
+ {clickable_description.map(description_part => { + const { type, text, onClick } = description_part; + return type === 'link' ? ( + + + {text} + +   + + ) : ( + + {text} + + ); + })} +
+); + +export default JurisdictionClickableDescription; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-checkbox.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-checkbox.tsx new file mode 100644 index 000000000000..6d8e6229d1fc --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-checkbox.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Checkbox, StaticUrl, Text } from '@deriv/components'; +import { DBVI_COMPANY_NAMES } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize } from '@deriv/translations'; +import { TJurisdictionCheckBoxProps } from '../props.types'; +import { JURISDICTION } from '../../Helpers/cfd-config'; + +const JurisdictionCheckBox = observer( + ({ + class_name, + is_checked, + jurisdiction_selected_shortcode, + onCheck, + should_restrict_bvi_account_creation, + should_restrict_vanuatu_account_creation, + }: TJurisdictionCheckBoxProps) => { + const { ui } = useStore(); + const { is_mobile } = ui; + const shouldShowCheckBox = () => { + if ( + !jurisdiction_selected_shortcode || + jurisdiction_selected_shortcode === JURISDICTION.SVG || + (jurisdiction_selected_shortcode === JURISDICTION.BVI && should_restrict_bvi_account_creation) || + (jurisdiction_selected_shortcode === JURISDICTION.VANUATU && should_restrict_vanuatu_account_creation) + ) { + return false; + } + return true; + }; + + const getCheckboxLabel = () => ( + + , + ]} + /> + + ); + + return ( + + {shouldShowCheckBox() && ( +
+ +
+ )} +
+ ); + } +); + +export default JurisdictionCheckBox; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx new file mode 100644 index 000000000000..ca42a72dd07f --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx @@ -0,0 +1,251 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Button, Modal } from '@deriv/components'; +import { getAuthenticationStatusInfo, isPOARequiredForMT5 } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { TJurisdictionModalContentWrapperProps } from '../props.types'; +import JurisdictionModalContent from './jurisdiction-modal-content'; +import JurisdictionCheckBox from './jurisdiction-modal-checkbox'; +import JurisdictionModalFootNote from './jurisdiction-modal-foot-note'; +import { useStore, observer } from '@deriv/stores'; +import { useCfdStore } from '../../Stores/Modules/CFD/Helpers/useCfdStores'; +import { MARKET_TYPE, JURISDICTION } from '../../Helpers/cfd-config'; + +const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisdictionModalContentWrapperProps) => { + const { client, traders_hub } = useStore(); + + const { show_eu_related_content, is_eu_user } = traders_hub; + + const { + trading_platform_available_accounts, + account_status, + fetchAccountSettings, + residence, + residence_list, + is_virtual, + updateMT5Status, + should_restrict_vanuatu_account_creation, + should_restrict_bvi_account_creation, + } = client; + + const { + is_jurisdiction_modal_visible, + has_submitted_cfd_personal_details, + jurisdiction_selected_shortcode, + toggleCFDVerificationModal, + toggleJurisdictionModal, + account_type, + real_financial_accounts_existing_data, + real_swapfree_accounts_existing_data, + real_synthetic_accounts_existing_data, + setJurisdictionSelectedShortcode, + } = useCfdStore(); + + const [checked, setChecked] = React.useState(false); + + const { + poi_or_poa_not_submitted, + poi_acknowledged_for_bvi_labuan_vanuatu, + poi_acknowledged_for_maltainvest, + poa_acknowledged, + need_poa_resubmission, + } = getAuthenticationStatusInfo(account_status); + + React.useEffect(() => { + if (is_jurisdiction_modal_visible) { + if (!is_virtual) { + updateMT5Status(); + fetchAccountSettings(); + } + setJurisdictionSelectedShortcode(''); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [is_jurisdiction_modal_visible]); + + React.useEffect(() => { + if (jurisdiction_selected_shortcode) { + setChecked(false); + } + }, [jurisdiction_selected_shortcode, is_jurisdiction_modal_visible]); + + const financial_available_accounts = trading_platform_available_accounts.filter( + available_account => + available_account.market_type === MARKET_TYPE.FINANCIAL && + (show_eu_related_content + ? available_account.shortcode === JURISDICTION.MALTA_INVEST + : available_account.shortcode !== JURISDICTION.MALTA_INVEST) + ); + + const synthetic_available_accounts = trading_platform_available_accounts.filter( + available_account => + available_account.market_type === MARKET_TYPE.GAMING && + (show_eu_related_content + ? available_account.shortcode === JURISDICTION.MALTA_INVEST + : available_account.shortcode !== JURISDICTION.MALTA_INVEST) + ); + + const all_market_type_available_accounts = trading_platform_available_accounts?.filter( + available_account => available_account.market_type === MARKET_TYPE.ALL + ); + + const is_svg_selected = jurisdiction_selected_shortcode === JURISDICTION.SVG; + const is_bvi_selected = jurisdiction_selected_shortcode === JURISDICTION.BVI; + const is_vanuatu_selected = jurisdiction_selected_shortcode === JURISDICTION.VANUATU; + const is_labuan_selected = jurisdiction_selected_shortcode === JURISDICTION.LABUAN; + const is_maltainvest_selected = jurisdiction_selected_shortcode === JURISDICTION.MALTA_INVEST; + + const is_idv_country = + residence_list.find(elem => elem?.value === residence)?.identity?.services?.idv?.is_country_supported === 1; + const has_idv_attempts = (account_status?.authentication?.identity?.services?.idv?.submissions_left ?? 0) > 0; + const is_non_idv_design = !is_idv_country || (is_idv_country && !has_idv_attempts); + + const swapfree_available_accounts = trading_platform_available_accounts.filter( + available_account => + available_account.market_type === MARKET_TYPE.ALL && + available_account.product === 'swap_free' && + (show_eu_related_content + ? available_account.shortcode === JURISDICTION.MALTA_INVEST + : available_account.shortcode !== JURISDICTION.MALTA_INVEST) + ); + + const isNextButtonDisabled = () => { + if (jurisdiction_selected_shortcode) { + let is_account_created; + if (account_type.type === MARKET_TYPE.SYNTHETIC) { + is_account_created = real_synthetic_accounts_existing_data?.some( + account => account.landing_company_short === jurisdiction_selected_shortcode + ); + } else if (account_type.type === MARKET_TYPE.ALL) { + is_account_created = real_swapfree_accounts_existing_data?.some( + account => account.landing_company_short === jurisdiction_selected_shortcode + ); + } else { + is_account_created = real_financial_accounts_existing_data?.some( + account => account.landing_company_short === jurisdiction_selected_shortcode + ); + } + if (!is_account_created) { + if ( + is_svg_selected || + (is_bvi_selected && should_restrict_bvi_account_creation && need_poa_resubmission) || + (is_vanuatu_selected && should_restrict_vanuatu_account_creation && need_poa_resubmission) + ) { + return false; + } + return !checked; + } + return true; + } + return true; + }; + + const onSelectRealAccount = () => { + const type_of_account = { + category: account_type.category, + type: account_type.type, + }; + + const is_poa_required_for_mt5 = isPOARequiredForMT5(account_status, jurisdiction_selected_shortcode); + + if (is_svg_selected) { + openPasswordModal(type_of_account); + } else if (is_vanuatu_selected) { + if ( + poi_acknowledged_for_bvi_labuan_vanuatu && + !poi_or_poa_not_submitted && + !should_restrict_vanuatu_account_creation && + poa_acknowledged && + has_submitted_cfd_personal_details && + !is_poa_required_for_mt5 + ) { + openPasswordModal(type_of_account); + } else { + toggleCFDVerificationModal(); + } + } else if (is_bvi_selected) { + if ( + poi_acknowledged_for_bvi_labuan_vanuatu && + !poi_or_poa_not_submitted && + !should_restrict_bvi_account_creation && + poa_acknowledged && + has_submitted_cfd_personal_details && + !is_poa_required_for_mt5 + ) { + openPasswordModal(type_of_account); + } else { + toggleCFDVerificationModal(); + } + } else if (is_labuan_selected) { + if ( + poi_acknowledged_for_bvi_labuan_vanuatu && + poa_acknowledged && + has_submitted_cfd_personal_details && + !is_poa_required_for_mt5 + ) { + openPasswordModal(type_of_account); + } else { + toggleCFDVerificationModal(); + } + } else if (is_maltainvest_selected) { + if (is_eu_user || (poi_acknowledged_for_maltainvest && poa_acknowledged)) { + openPasswordModal(type_of_account); + } else { + toggleCFDVerificationModal(); + } + } + }; + + return ( +
+ +
+
+ + setChecked(!checked)} + class_name={`cfd-jurisdiction-card--${account_type.type}__jurisdiction-checkbox`} + jurisdiction_selected_shortcode={jurisdiction_selected_shortcode} + should_restrict_bvi_account_creation={should_restrict_bvi_account_creation} + should_restrict_vanuatu_account_creation={should_restrict_vanuatu_account_creation} + /> +
+ + + +
+
+ ); +}); + +export default JurisdictionModalContentWrapper; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content.tsx new file mode 100644 index 000000000000..bb96b68d59c1 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { TJurisdictionModalContentProps } from '../props.types'; +import JurisdictionCard from './jurisdiction-card'; +import { MARKET_TYPE, JURISDICTION } from '../../Helpers/cfd-config'; + +const JurisdictionModalContent = ({ + account_status, + account_type, + is_non_idv_design = false, + is_virtual, + jurisdiction_selected_shortcode, + setJurisdictionSelectedShortcode, + synthetic_available_accounts, + financial_available_accounts, + real_synthetic_accounts_existing_data, + real_financial_accounts_existing_data, + real_swapfree_accounts_existing_data, + swapfree_available_accounts, +}: TJurisdictionModalContentProps) => { + const card_classname = `cfd-jurisdiction-card--${account_type}`; + + const cardsToBeShown = (type_of_card: string) => { + switch (account_type) { + case MARKET_TYPE.SYNTHETIC: + return synthetic_available_accounts?.some(account => account.shortcode === type_of_card); + case MARKET_TYPE.ALL: + return swapfree_available_accounts?.some(account => account.shortcode === type_of_card); + default: + return financial_available_accounts?.some(account => account.shortcode === type_of_card); + } + }; + + const disableCard = (type_of_card: string) => { + if (is_virtual && type_of_card !== JURISDICTION.SVG) { + return true; + } + switch (account_type) { + case MARKET_TYPE.SYNTHETIC: + return real_synthetic_accounts_existing_data?.some( + account => account.landing_company_short === type_of_card + ); + case MARKET_TYPE.ALL: + return real_swapfree_accounts_existing_data?.some( + account => account.landing_company_short === type_of_card + ); + default: + return real_financial_accounts_existing_data?.some( + account => account.landing_company_short === type_of_card + ); + } + }; + + const jurisdiction_cards_array = [ + JURISDICTION.SVG, + JURISDICTION.BVI, + JURISDICTION.VANUATU, + JURISDICTION.LABUAN, + JURISDICTION.MALTA_INVEST, + ]; + + return ( +
+ {jurisdiction_cards_array.map( + card => + cardsToBeShown(card) && ( + + ) + )} +
+ ); +}; + +export default JurisdictionModalContent; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-foot-note.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-foot-note.tsx new file mode 100644 index 000000000000..e3a81f3e5c15 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-foot-note.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import { getAuthenticationStatusInfo, isMobile, getMT5Title, CFD_PLATFORMS } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { TJurisdictionModalFootNoteProps } from '../props.types'; +import { JURISDICTION } from '../../Helpers/cfd-config'; + +const FooterNote = ({ + account_status, + account_type, + card_classname, + jurisdiction_selected_shortcode, + should_restrict_bvi_account_creation, + should_restrict_vanuatu_account_creation, +}: TJurisdictionModalFootNoteProps) => { + const { common } = useStore(); + + const { platform } = common; + + const account_type_name = getMT5Title(account_type); + + const { poa_pending } = getAuthenticationStatusInfo(account_status); + + if (platform === CFD_PLATFORMS.CTRADER && jurisdiction_selected_shortcode === 'svg') { + return ( + + ); + } + if (jurisdiction_selected_shortcode === JURISDICTION.SVG) { + return ( + + ); + } else if ( + (jurisdiction_selected_shortcode === JURISDICTION.BVI && should_restrict_bvi_account_creation) || + (jurisdiction_selected_shortcode === JURISDICTION.VANUATU && should_restrict_vanuatu_account_creation) + ) { + return poa_pending ? ( + ]} + /> + ) : ( + + ); + } else if (jurisdiction_selected_shortcode === JURISDICTION.BVI) { + return ( + + ); + } else if (jurisdiction_selected_shortcode === JURISDICTION.VANUATU) { + return ( + + ); + } else if (jurisdiction_selected_shortcode === JURISDICTION.LABUAN) { + return ( + + ); + } else if (jurisdiction_selected_shortcode === JURISDICTION.MALTA_INVEST) { + return ( + + ); + } + + return null; +}; + +const JurisdictionModalFootNote = (props: TJurisdictionModalFootNoteProps) => { + return ( + + {props.jurisdiction_selected_shortcode && ( +
+ + + +
+ )} +
+ ); +}; + +export default observer(JurisdictionModalFootNote); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx new file mode 100644 index 000000000000..9820832398ff --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-title.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { useDevice } from '@deriv-com/ui'; +import { Icon, Text } from '@deriv/components'; +import { getMT5Title, getCFDPlatformLabel } from '@deriv/shared'; +import { Localize, localize } from '@deriv/translations'; +import { useDynamicLeverage } from '../dynamic-leverage/dynamic-leverage-context'; +import { TJurisdictionModalTitleProps } from '../props.types'; +import { CFD_PLATFORMS } from '../../Helpers/cfd-config'; +import { platformsText } from '../../Helpers/constants'; + +export const JurisdictionModalTitle = ({ + show_eu_related_content, + account_type, + platform, +}: TJurisdictionModalTitleProps) => { + const { isDesktop } = useDevice(); + const { is_dynamic_leverage_visible, toggleDynamicLeverage } = useDynamicLeverage(); + if (is_dynamic_leverage_visible) { + return ( +
+ + + + + {localize('Get more out of Deriv MT5 Financial')} + +
+ ); + } else if (show_eu_related_content) { + return ; + } + + return ( + + ); +}; + +export default JurisdictionModalTitle; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx new file mode 100644 index 000000000000..55870cfb7005 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import classNames from 'classnames'; +import { useDevice } from '@deriv-com/ui'; +import { MobileDialog, Modal, UILoader } from '@deriv/components'; +import { TJurisdictionModalProps } from '../props.types'; +import { observer, useStore } from '@deriv/stores'; +import { useCfdStore } from '../../Stores/Modules/CFD/Helpers/useCfdStores'; +import { DynamicLeverageContext } from '../dynamic-leverage/dynamic-leverage-context'; +import DynamicLeverageModalContent from '../dynamic-leverage/dynamic-leverage-modal-content'; +import JurisdictionModalContentWrapper from './jurisdiction-modal-content-wrapper'; +import JurisdictionModalTitle from './jurisdiction-modal-title'; +import { MARKET_TYPE } from '../../Helpers/cfd-config'; + +const JurisdictionModal = observer(({ openPasswordModal }: TJurisdictionModalProps) => { + const { isDesktop } = useDevice(); + const { traders_hub, ui, common } = useStore(); + + const { show_eu_related_content } = traders_hub; + const { disableApp, enableApp } = ui; + const { platform } = common; + + const { account_type, is_jurisdiction_modal_visible, toggleJurisdictionModal } = useCfdStore(); + + const [is_dynamic_leverage_visible, setIsDynamicLeverageVisible] = React.useState(false); + + const toggleDynamicLeverage: React.MouseEventHandler = event => { + event.stopPropagation(); + setIsDynamicLeverageVisible(!is_dynamic_leverage_visible); + }; + + const onJurisdictionModalToggle = () => { + setIsDynamicLeverageVisible(false); + toggleJurisdictionModal(); + }; + + const modal_content = ( +
+ + {is_dynamic_leverage_visible && } +
+ ); + + return ( +
+ }> + + {isDesktop ? ( + + } + > + {modal_content} + + ) : ( + + } + > + {modal_content} + + )} + + +
+ ); +}); + +export default JurisdictionModal; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx new file mode 100644 index 000000000000..91c92415f9b5 --- /dev/null +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Icon, Text } from '@deriv/components'; +import { getAuthenticationStatusInfo } from '@deriv/shared'; +import { jurisdictionVerificationContents } from '../../Constants/jurisdiction-contents/jurisdiction-verification-contents'; +import { TJurisdictionTitleIndicatorProps } from 'Containers/props.types'; +import { TJurisdictionCardItemVerificationItem, TJurisdictionCardVerificationStatus } from 'Components/props.types'; +import { JURISDICTION } from '../../Helpers/cfd-config'; + +const JurisdictionTitleIndicator = ({ + account_status, + title_indicators, + type_of_card, + verification_docs, +}: TJurisdictionTitleIndicatorProps) => { + const { + poi_pending_for_bvi_labuan_vanuatu, + poi_resubmit_for_bvi_labuan_vanuatu, + poi_verified_for_bvi_labuan_vanuatu, + poi_pending_for_maltainvest, + poi_resubmit_for_maltainvest, + poi_verified_for_maltainvest, + poa_pending, + need_poa_resubmission, + poa_verified, + } = getAuthenticationStatusInfo(account_status); + + const getVerificationIconVariant = (verification_document: TJurisdictionCardItemVerificationItem): string => { + let icon_variant: TJurisdictionCardVerificationStatus = 'Default'; + if ( + type_of_card === JURISDICTION.BVI || + type_of_card === JURISDICTION.LABUAN || + type_of_card === JURISDICTION.VANUATU + ) { + if (['document_number', 'selfie', 'identity_document'].includes(verification_document)) { + if (poi_pending_for_bvi_labuan_vanuatu) { + icon_variant = 'Pending'; + } else if (poi_resubmit_for_bvi_labuan_vanuatu) { + icon_variant = 'Failed'; + } else if (poi_verified_for_bvi_labuan_vanuatu) { + icon_variant = 'Verified'; + } + } + } else if (type_of_card === JURISDICTION.MALTA_INVEST) { + if (['document_number', 'selfie', 'identity_document'].includes(verification_document)) { + if (poi_pending_for_maltainvest) { + icon_variant = 'Pending'; + } else if (poi_resubmit_for_maltainvest) { + icon_variant = 'Failed'; + } else if (poi_verified_for_maltainvest) { + icon_variant = 'Verified'; + } + } + } + if (verification_document === 'name_and_address') { + if (poa_pending) { + icon_variant = 'Pending'; + } else if (need_poa_resubmission) { + icon_variant = 'Failed'; + } else if (poa_verified) { + icon_variant = 'Verified'; + } + } + return icon_variant; + }; + + return title_indicators.type === 'displayText' ? ( + + {title_indicators.display_text} + + ) : ( +
+ {verification_docs?.map(verification_document => ( +
+ +
+ ))} +
+ ); +}; + +export default JurisdictionTitleIndicator; diff --git a/packages/cfd/src/Containers/mt5-create-password/__tests__/mt5-create-password.spec.tsx b/packages/cfd/src/Containers/mt5-create-password/__tests__/mt5-create-password.spec.tsx deleted file mode 100644 index 6c35845010fa..000000000000 --- a/packages/cfd/src/Containers/mt5-create-password/__tests__/mt5-create-password.spec.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react'; -import { Router } from 'react-router'; -import userEvent from '@testing-library/user-event'; -import { createBrowserHistory } from 'history'; -import { render, screen, waitFor } from '@testing-library/react'; -import MT5CreatePassword from '../mt5-create-password'; -import { mockStore } from '@deriv/stores'; -import CFDProviders from '../../../cfd-providers'; -import { CFD_PLATFORMS } from '@deriv/shared'; - -jest.mock('@deriv/components', () => ({ - ...jest.requireActual('@deriv/components'), - Icon: jest.fn(({ icon }) =>
{icon}
), - useDevice: () => ({ isMobile: false }), -})); - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - getErrorMessages: jest.fn().mockReturnValue({ - password_warnings: '', - password: jest.fn().mockReturnValue('Password should have lower and uppercase English letters with numbers.'), - }), - validPassword: jest.fn().mockReturnValue(true), - isDesktop: jest.fn().mockReturnValue(true), - DBVI_COMPANY_NAMES: { - svg: { name: 'Sample Company', licence_name: 'Sample Licence' }, - other: { name: 'Other Company', licence_name: 'Other Licence' }, - }, -})); - -describe('', () => { - const mockFn = jest.fn(); - const history = createBrowserHistory(); - let modalRoot; - - const mockRootStore = { - modules: { - cfd: { - error_message: '', - account_title: '', - account_type: { type: 'mt5', category: 'real' }, - getAccountStatus: mockFn, - new_account_response: {}, - jurisdiction_selected_shortcode: 'svg', - setProduct: jest.fn(), - }, - }, - }; - - const default_props = { - password: '', - platform: CFD_PLATFORMS.MT5, - error_message: '', - validatePassword: jest.fn(), - onSubmit: jest.fn(), - need_tnc: true, - }; - - beforeAll(() => { - modalRoot = document.createElement('div'); - modalRoot.setAttribute('id', 'modal_root'); - document.body.appendChild(modalRoot); - }); - - afterAll(() => { - document.body.removeChild(modalRoot); - }); - - it('should render MT5CreatePassword component', async () => { - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - - expect(await screen.findByTestId('dt_mt5_create_password')).toBeInTheDocument(); - }); - - it('should display IcMt5OnePassword icon in the component', async () => { - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - expect(await screen.findByText('IcMt5Password')).toBeInTheDocument(); - }); - - it('should display password field for user to enter the password and hold the entered value', async () => { - const user_input = 'zo8lAet#2q01Ih'; - - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - const password_input = await screen.findByTestId('dt_mt5_password'); - - userEvent.paste(password_input, user_input); - await waitFor(() => { - expect(password_input.value).toBe(user_input); - }); - }); - - it('should display password requirements message', async () => { - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - - expect(await screen.findByText(/This password works for all your Deriv MT5 accounts./i)).toBeInTheDocument(); - }); - - it('should show TNC checkbox when account type is real ', async () => { - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - const checkbox = await screen.findByRole('checkbox'); - expect(checkbox).toBeInTheDocument(); - }); - - it('should call onSubmit when form is submitted', async () => { - const user_input = 'zo8lAet#2q01Ih'; - const onSubmit = jest.fn(); - const validatePassword = jest.fn().mockReturnValue({}); - - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - - const passwordInput = await screen.findByTestId('dt_mt5_password'); - const submitButton = await screen.findByRole('button', { name: /Create account/i }); - - expect(passwordInput).toBeInTheDocument(); - expect(submitButton).toBeInTheDocument(); - - userEvent.paste(passwordInput, user_input); - const checkbox = await screen.findByRole('checkbox'); - expect(checkbox).toBeInTheDocument(); - userEvent.click(checkbox); - userEvent.click(submitButton); - - await waitFor(() => { - expect(onSubmit).toHaveBeenCalled(); - }); - }); -}); diff --git a/packages/cfd/src/Containers/mt5-create-password/mt5-create-password.scss b/packages/cfd/src/Containers/mt5-create-password/mt5-create-password.scss deleted file mode 100644 index de9127938261..000000000000 --- a/packages/cfd/src/Containers/mt5-create-password/mt5-create-password.scss +++ /dev/null @@ -1,128 +0,0 @@ -.dc-modal__container_mt5-password-modal, -.dc-mobile-dialog__mt5-password-modal { - display: flex; - flex-direction: column; - justify-content: flex-start; - - &__description { - text-transform: none; - margin-top: 1rem; - top: 2rem; - position: relative; - padding: 1.3rem; - } - - &__body { - width: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - margin-top: 3.2rem; - } - - .mt5-password-field { - margin-bottom: 1rem; - } - - .mt5-input-element { - width: 100%; - margin-bottom: 1rem; - } -} - -.mt5-password-modal { - &-form { - @include mobile-or-tablet-screen { - height: 100%; - } - padding: unset !important; - } - &__warning { - padding: 2.5rem; - max-width: 50rem; - align-self: center; - &-text { - border-radius: 0.8rem; - padding: 0.8rem; - background-color: var(--status-warning); - } - } - &__message { - max-width: 32rem; - margin: auto; - line-height: 1.43; - } - &__content { - @include mobile-or-tablet-screen { - overflow-y: auto; - overflow-x: hidden; - height: 100%; - width: 100%; - padding: 0; - justify-content: space-between; - } - } - &__create-password { - padding: 0 1.2rem; - - &-content { - display: flex; - flex-direction: column; - width: 100%; - padding: 2.4rem; - - @include mobile-or-tablet-screen { - height: 100%; - padding: 1.6rem; - } - } - &-icon { - display: flex; - flex-direction: column; - width: 100%; - align-items: center; - } - - &-title { - margin-top: 2.4rem; - margin-bottom: 0.8rem; - } - &-description { - max-width: 30rem; - margin-top: 1.6rem; - margin-bottom: 1.6rem; - } - } - - &__mt5-input-element { - width: 100%; - margin-bottom: 1rem; - } - &-tnc { - &--bottom { - @include mobile-or-tablet-screen { - margin-top: auto; - margin-bottom: 7.5rem; - } - } - } - &__checkbox { - display: flex; - align-self: baseline; - margin-inline-start: -0.8rem; - } - &-info, - &__checkbox { - .dc-icon { - margin-inline-start: 0; - } - } - &__submit-button { - align-items: center; - align-self: flex-end; - justify-content: flex-end; - width: 100%; - padding-bottom: 1.6rem; - } -} diff --git a/packages/cfd/src/Containers/mt5-create-password/mt5-create-password.tsx b/packages/cfd/src/Containers/mt5-create-password/mt5-create-password.tsx deleted file mode 100644 index c9cce6d6a840..000000000000 --- a/packages/cfd/src/Containers/mt5-create-password/mt5-create-password.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react'; -import { Formik, FormikErrors, FormikHelpers } from 'formik'; -import { FormSubmitButton, PasswordInput, PasswordMeter, Text, Icon } from '@deriv/components'; -import { getCFDPlatformLabel, getCFDPlatformNames, getErrorMessages } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; -import { useCfdStore } from '../../Stores/Modules/CFD/Helpers/useCfdStores'; -import { CATEGORY, CFD_PLATFORMS } from '../../Helpers/cfd-config'; -import './mt5-create-password.scss'; -import '../../sass/cfd.scss'; -import CfdPasswordModalTnc from '../cfd-password-modal-tnc'; -// This component is for first MT5 password modal - -export type TCFDPasswordFormValues = { password: string }; - -type TCFDPasswordFormReusedProps = { - platform: typeof CFD_PLATFORMS[keyof typeof CFD_PLATFORMS]; - error_message: string; - validatePassword?: (values: TCFDPasswordFormValues) => FormikErrors; -}; - -type TOnSubmitPassword = (values: TCFDPasswordFormValues, actions: FormikHelpers) => void; -type TCFDCreatePasswordProps = TCFDPasswordFormReusedProps & { - password: string; - onSubmit: TOnSubmitPassword; - need_tnc: boolean; -}; - -const handlePasswordInputChange = ( - e: React.ChangeEvent, - handleChange: (el: React.ChangeEvent) => void, - validateForm: (values?: TCFDPasswordFormValues) => Promise>, - setFieldTouched: (field: string, isTouched?: boolean, shouldValidate?: boolean) => void -) => { - handleChange(e); - validateForm().then(() => { - setFieldTouched('password', true); - }); -}; - -const MT5CreatePassword = ({ - password, - platform, - validatePassword, - onSubmit, - error_message, - need_tnc, -}: TCFDCreatePasswordProps) => { - const { account_type } = useCfdStore(); - const [checked, setChecked] = React.useState(!(need_tnc && account_type.category === CATEGORY.REAL)); - - return ( - - {({ - errors, - isSubmitting, - handleBlur, - handleChange, - handleSubmit, - setFieldTouched, - touched, - values, - validateForm, - }) => ( - - -
-
-
- -
- - - - -
- - {({ has_warning }: { has_warning: boolean }) => ( - ) => { - handlePasswordInputChange( - e, - handleChange, - validateForm, - setFieldTouched - ); - }} - data_testId={`dt_${platform}_password`} - hint={ - (!has_warning || values.password.length === 0) && - localize('This password works for all your Deriv MT5 accounts.') - } - /> - )} - -
- {account_type.category === CATEGORY.REAL && ( - setChecked(prev => !prev)} - need_tnc={need_tnc} - /> - )} -
-
- 0} - is_loading={isSubmitting} - label={localize('Create account')} - onClick={handleSubmit} - /> -
-
- -
- )} -
- ); -}; -export default MT5CreatePassword; diff --git a/packages/cfd/src/Containers/props.types.ts b/packages/cfd/src/Containers/props.types.ts index daa30141edb5..38ec73f0819c 100644 --- a/packages/cfd/src/Containers/props.types.ts +++ b/packages/cfd/src/Containers/props.types.ts @@ -16,7 +16,7 @@ import { TJurisdictionCardItemVerification, TJurisdictionCardSection, TJurisdictionCardSectionTitleIndicators, - TModifiedTradingPlatformAvailableAccount, + TTradingPlatformAvailableAccount, } from '../Components/props.types'; import RootStore from '../Stores/index'; @@ -54,6 +54,8 @@ export type TCFDDashboardContainer = { export type TMT5AccountOpeningRealFinancialStpModal = { enableApp: () => void; disableApp: () => void; + toggleCFDVerificationModal: () => void; + is_cfd_verification_modal_visible: boolean; }; export type TMissingRealAccount = { @@ -205,6 +207,14 @@ export type TVerificationStatusBannerProps = { residence_list: ResidenceList; }; +export type TJurisdictionCheckBoxProps = { + class_name: string; + is_checked: boolean; + jurisdiction_selected_shortcode: string; + onCheck: () => void; + should_restrict_bvi_account_creation: boolean; + should_restrict_vanuatu_account_creation: boolean; +}; export type TOpenAccountTransferMeta = { category: string; type?: string; @@ -224,10 +234,10 @@ export type TJurisdictionModalContentProps = { is_non_idv_design: boolean; jurisdiction_selected_shortcode: string; setJurisdictionSelectedShortcode: (card_type: string) => void; - synthetic_available_accounts: TModifiedTradingPlatformAvailableAccount[]; - financial_available_accounts: TModifiedTradingPlatformAvailableAccount[]; - all_market_type_available_accounts: TModifiedTradingPlatformAvailableAccount[]; - swapfree_available_accounts: TModifiedTradingPlatformAvailableAccount[]; + synthetic_available_accounts: TTradingPlatformAvailableAccount[]; + financial_available_accounts: TTradingPlatformAvailableAccount[]; + all_market_type_available_accounts: TTradingPlatformAvailableAccount[]; + swapfree_available_accounts: TTradingPlatformAvailableAccount[]; real_synthetic_accounts_existing_data: TExistingData; real_financial_accounts_existing_data: TExistingData; real_swapfree_accounts_existing_data: TExistingData; @@ -299,6 +309,23 @@ export type TDMT5CompareModalContentProps = { toggleCompareAccounts: () => void; }; +export type TCFDDbviOnboardingProps = { + account_status: GetAccountStatus; + context: RootStore; + disableApp: () => void; + enableApp: () => void; + fetchAccountSettings: () => void; + has_created_account_for_selected_jurisdiction: boolean; + has_submitted_cfd_personal_details: boolean; + is_cfd_verification_modal_visible: boolean; + is_virtual: boolean; + jurisdiction_selected_shortcode: string; + openPasswordModal: () => void; + toggleCFDVerificationModal: () => void; + updateAccountStatus: () => void; + updateMT5Status: () => void; +}; + type TDynamicLeverage = { from: number; to: number; diff --git a/packages/cfd/src/Helpers/cfd-config.ts b/packages/cfd/src/Helpers/cfd-config.ts index 89a89035a48c..4f93f99c4a4f 100644 --- a/packages/cfd/src/Helpers/cfd-config.ts +++ b/packages/cfd/src/Helpers/cfd-config.ts @@ -23,7 +23,6 @@ export const PRODUCT = { ZEROSPREAD: 'zero_spread', CTRADER: 'ctrader', DERIVX: 'derivx', - STP: 'stp', } as const; export const MOBILE_PLATFORMS = { @@ -52,10 +51,14 @@ export const JURISDICTION = { } as const; export const MARKET_TYPE_SHORTCODE = { - SYNTHETIC: 'synthetic', + SYNTHETIC_SVG: 'synthetic_svg', + SYNTHETIC_BVI: 'synthetic_bvi', + SYNTHETIC_VANUATU: 'synthetic_vanuatu', GAMING: 'gaming', - FINANCIAL: 'financial', - FINANCIAL_LABUAN: 'financial_stp_labuan', + FINANCIAL_SVG: 'financial_svg', + FINANCIAL_BVI: 'financial_bvi', + FINANCIAL_VANUATU: 'financial_vanuatu', + FINANCIAL_LABUAN: 'financial_labuan', FINANCIAL_MALTA_INVEST: 'financial_maltainvest', ALL_SWAP_FREE_SVG: 'all_swap_free_svg', ALL_ZERO_SPREAD_BVI: 'all_zero_spread_bvi', diff --git a/packages/cfd/src/Helpers/compare-accounts-config.ts b/packages/cfd/src/Helpers/compare-accounts-config.ts index 9935902e5b71..c6a19423df5a 100644 --- a/packages/cfd/src/Helpers/compare-accounts-config.ts +++ b/packages/cfd/src/Helpers/compare-accounts-config.ts @@ -28,59 +28,126 @@ const getHighlightedIconLabel = ( } return localize('Forex: standard/micro'); }; - const getIdForInstruments = (instruments: TModifiedTradingPlatformAvailableAccount['instruments']) => { - return instruments?.map(item => { - if (item.toLowerCase().includes('forex')) { - return 'forex'; - } else if (item === 'ETFs') { - return 'ETFs'; // Preserve the original form for ETFs - } else if (item === 'Derived FX') { - return 'derived_FX'; // Handle FX case - } - return item.toLowerCase().replace(/\s+/g, '_'); // Replace spaces with underscores - }); - }; - const instrumentsData: TInstrumentsIcon[] = [ - { id: 'forex', icon: 'Forex', text: getForexLabel() }, - { id: 'stocks', icon: 'Stocks', text: localize('Stocks') }, - { id: 'stock_indices', icon: 'StockIndices', text: localize('Stock indices') }, - { id: 'commodities', icon: 'Commodities', text: localize('Commodities') }, - { - id: 'cryptocurrencies', - icon: 'Cryptocurrencies', - text: localize('Cryptocurrencies'), - }, - { id: 'ETFs', icon: 'ETF', text: localize('ETFs') }, - { - id: 'synthetic_indices', - icon: 'Synthetics', - text: localize('Synthetic indices'), - is_asterisk: selected_region === REGION.EU, - }, - { id: 'basket_indices', icon: 'Baskets', text: localize('Basket indices') }, - { id: 'derived_FX', icon: 'DerivedFX', text: localize('Derived FX') }, - ]; + switch (trading_platforms.market_type) { + case MARKET_TYPE.GAMING: + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETFs'), highlighted: true }, + { icon: 'Synthetics', text: localize('Synthetic indices'), highlighted: true }, + { icon: 'Baskets', text: localize('Basket indices'), highlighted: true }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: true }, + ]; + case MARKET_TYPE.FINANCIAL: + switch (trading_platforms.shortcode) { + case JURISDICTION.MALTA_INVEST: + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { + icon: 'Synthetics', + text: localize('Synthetic indices'), + highlighted: true, + is_asterisk: true, + }, + ]; + case JURISDICTION.LABUAN: + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: false }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: false }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: false }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETFs'), highlighted: false }, + { icon: 'Synthetics', text: localize('Synthetic indices'), highlighted: false }, + { icon: 'Baskets', text: localize('Basket indices'), highlighted: false }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: false }, + ]; + default: + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETFs'), highlighted: true }, + { icon: 'Synthetics', text: localize('Synthetic indices'), highlighted: false }, + { icon: 'Baskets', text: localize('Basket indices'), highlighted: false }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: false }, + ]; + } + case MARKET_TYPE.ALL: + default: + if (trading_platforms.platform === CFD_PLATFORMS.MT5) { + if (trading_platforms.product === PRODUCT.SWAPFREE) { + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETFs'), highlighted: true }, + { icon: 'Synthetics', text: localize('Synthetic indices'), highlighted: true }, + { icon: 'Baskets', text: localize('Basket indices'), highlighted: false }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: false }, + ]; + } else if (trading_platforms.product === PRODUCT.ZEROSPREAD) { + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: false }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETFs'), highlighted: false }, + { icon: 'Synthetics', text: localize('Synthetic indices'), highlighted: true }, + { icon: 'Baskets', text: localize('Basket indices'), highlighted: true }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: true }, + ]; + } + } - return instrumentsData.map((item: TInstrumentsIcon) => ({ - ...item, // Copy all existing properties - highlighted: getIdForInstruments(trading_platforms?.instruments)?.includes(item?.id ?? '') ?? true, - })); + return [ + { icon: 'Forex', text: getForexLabel(), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETFs'), highlighted: true }, + { icon: 'Synthetics', text: localize('Synthetic indices'), highlighted: true }, + { icon: 'Baskets', text: localize('Basket indices'), highlighted: true }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: true }, + ]; + } }; // Get the Account Title according to the market type and jurisdiction const getAccountCardTitle = (shortcode: string, is_demo?: boolean) => { switch (shortcode) { - case MARKET_TYPE_SHORTCODE.SYNTHETIC: - return is_demo ? localize('Standard Demo') : localize('Standard'); - case MARKET_TYPE_SHORTCODE.FINANCIAL: - return is_demo ? localize('Financial Demo') : localize('Financial'); + case MARKET_TYPE_SHORTCODE.SYNTHETIC_SVG: + return is_demo ? localize('Standard Demo') : localize('Standard - SVG'); + case MARKET_TYPE_SHORTCODE.SYNTHETIC_BVI: + return localize('Standard - BVI'); + case MARKET_TYPE_SHORTCODE.SYNTHETIC_VANUATU: + return localize('Standard - Vanuatu'); + case MARKET_TYPE_SHORTCODE.FINANCIAL_SVG: + return is_demo ? localize('Financial Demo') : localize('Financial - SVG'); + case MARKET_TYPE_SHORTCODE.FINANCIAL_BVI: + return localize('Financial - BVI'); + case MARKET_TYPE_SHORTCODE.FINANCIAL_VANUATU: + return localize('Financial - Vanuatu'); case MARKET_TYPE_SHORTCODE.FINANCIAL_LABUAN: - return localize('Financial - STP'); + return localize('Financial - Labuan'); case MARKET_TYPE_SHORTCODE.ALL_SWAP_FREE_SVG: - return is_demo ? localize('Swap-Free Demo') : localize('Swap-Free'); + return is_demo ? localize('Swap-Free Demo') : localize('Swap-Free - SVG'); case MARKET_TYPE_SHORTCODE.ALL_ZERO_SPREAD_BVI: - return is_demo ? localize('Zero Spread Demo') : localize('Zero Spread'); + return is_demo ? localize('Zero Spread Demo') : localize('Zero Spread - BVI'); case CFD_PLATFORMS.DXTRADE: return is_demo ? localize('Deriv X Demo') : localize('Deriv X'); case CFD_PLATFORMS.CTRADER: @@ -94,7 +161,6 @@ const getAccountCardTitle = (shortcode: string, is_demo?: boolean) => { const getPlatformLabel = (shortcode?: string) => { switch (shortcode) { case CFD_PLATFORMS.DXTRADE: - return localize('Deriv X'); case CFD_PLATFORMS.CFDS: return localize('Other CFDs Platform'); case CFD_PLATFORMS.CTRADER: @@ -109,7 +175,6 @@ const getPlatformLabel = (shortcode?: string) => { const platformsHeaderLabel = { mt5: localize('MT5 Platform'), ctrader: localize('Deriv cTrader'), - derivx: localize('Deriv X'), other_cfds: localize('Other CFDs Platform'), }; @@ -145,28 +210,15 @@ const getMarketType = (trading_platforms: TModifiedTradingPlatformAvailableAccou // Get the color of Header based on the platform const getHeaderColor = (shortcode: string) => { switch (shortcode) { + case platformsHeaderLabel.other_cfds: case platformsHeaderLabel.ctrader: - return 'orange'; - case platformsHeaderLabel.derivx: return 'green'; case platformsHeaderLabel.mt5: - case platformsHeaderLabel.other_cfds: default: return 'blue'; } }; -const getDefaultJurisdictionDetails = (data: TModifiedTradingPlatformAvailableAccount) => { - const leverage = `${data?.product_details?.max_leverage}`; - const spread = `${data?.product_details?.min_spread} pips`; - return { - leverage, - leverage_description: localize('Maximum leverage'), - spread, - spread_description: localize('Spreads from'), - }; -}; - // Config for different Jurisdictions const cfd_config = () => ({ leverage: '1:1000', @@ -183,15 +235,84 @@ const cfd_config = () => ({ }); // Map the Jurisdictions with the config -const getJuridisctionDescription = (shortcode: string, trading_platforms: TModifiedTradingPlatformAvailableAccount) => { +const getJuridisctionDescription = (shortcode: string) => { + const createDescription = ( + counterparty_company: string, + jurisdiction: string, + regulator: string, + regulator_license: string | undefined, + regulator_description: string, + leverage: string = cfd_config().leverage, + spread: string = cfd_config().spread + ) => ({ + ...cfd_config(), + counterparty_company, + jurisdiction, + regulator, + regulator_license, + regulator_description, + leverage, + spread, + }); + switch (shortcode) { - case MARKET_TYPE_SHORTCODE.SYNTHETIC: - case MARKET_TYPE_SHORTCODE.FINANCIAL: - case MARKET_TYPE_SHORTCODE.FINANCIAL_LABUAN: + case MARKET_TYPE_SHORTCODE.SYNTHETIC_BVI: + case MARKET_TYPE_SHORTCODE.FINANCIAL_BVI: + return createDescription( + 'Deriv (BVI) Ltd', + 'British Virgin Islands', + localize('British Virgin Islands Financial Services Commission'), + localize('(License no. SIBA/L/18/1114)'), + localize('Regulator/External dispute resolution'), + cfd_config().leverage, + shortcode === MARKET_TYPE_SHORTCODE.SYNTHETIC_BVI ? '0.1 pips' : '0.2 pips' + ); case MARKET_TYPE_SHORTCODE.ALL_ZERO_SPREAD_BVI: - case MARKET_TYPE_SHORTCODE.ALL_SWAP_FREE_SVG: + return createDescription( + 'Deriv (BVI) Ltd', + 'British Virgin Islands', + localize('British Virgin Islands Financial Services Commission'), + localize('(License no. SIBA/L/18/1114)'), + localize('Regulator/External dispute resolution'), + '1:1000', + '0.0 pips' + ); + case MARKET_TYPE_SHORTCODE.SYNTHETIC_VANUATU: + case MARKET_TYPE_SHORTCODE.FINANCIAL_VANUATU: + return createDescription( + 'Deriv (V) Ltd', + 'Vanuatu', + localize('Vanuatu Financial Services Commission'), + '', + localize('Regulator/External dispute resolution'), + cfd_config().leverage, + shortcode === MARKET_TYPE_SHORTCODE.SYNTHETIC_VANUATU ? '0.1 pips' : '0.2 pips' + ); + case MARKET_TYPE_SHORTCODE.FINANCIAL_LABUAN: + return createDescription( + 'Deriv (FX) Ltd', + 'Labuan', + localize('Labuan Financial Services Authority'), + localize('(licence no. MB/18/0024)'), + localize('Regulator/External dispute resolution'), + '1:100', + '0.6 pips' + ); case MARKET_TYPE_SHORTCODE.FINANCIAL_MALTA_INVEST: - return getDefaultJurisdictionDetails(trading_platforms); + return createDescription( + 'Deriv Investments (Europe) Limited', + 'Malta', + localize('Financial Commission'), + localize('Regulated by the Malta Financial Services Authority (MFSA) (licence no. IS/70156)'), + '', + '1:30' + ); + case MARKET_TYPE_SHORTCODE.SYNTHETIC_SVG: + return { ...cfd_config(), spread: '0.1 pips' }; + case MARKET_TYPE_SHORTCODE.FINANCIAL_SVG: + return { ...cfd_config(), spread: '0.2 pips' }; + case MARKET_TYPE_SHORTCODE.ALL_SWAP_FREE_SVG: + return { ...cfd_config(), spread: '0.3 pips' }; case MARKET_TYPE_SHORTCODE.ALL_DXTRADE: default: return cfd_config(); @@ -201,31 +322,16 @@ const getJuridisctionDescription = (shortcode: string, trading_platforms: TModif // Sort the MT5 accounts in the order of derived, financial and swap-free const getSortedCFDAvailableAccounts = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { const swap_free_accounts = available_accounts - .filter( - item => - item.market_type === MARKET_TYPE.ALL && - item.product === PRODUCT.SWAPFREE && - item.is_default_jurisdiction === 'true' - ) + .filter(item => item.market_type === MARKET_TYPE.ALL && item.product === PRODUCT.SWAPFREE) .map(item => ({ ...item, platform: CFD_PLATFORMS.MT5 } as const)); const zero_spread_accounts = available_accounts - .filter( - item => - item.market_type === MARKET_TYPE.ALL && - item.product === PRODUCT.ZEROSPREAD && - item.is_default_jurisdiction === 'true' - ) + .filter(item => item.market_type === MARKET_TYPE.ALL && item.product === PRODUCT.ZEROSPREAD) .map(item => ({ ...item, platform: CFD_PLATFORMS.MT5 } as const)); const financial_accounts = available_accounts - .filter( - item => - item.market_type === MARKET_TYPE.FINANCIAL && - item.shortcode !== JURISDICTION.MALTA_INVEST && - item.is_default_jurisdiction === 'true' - ) + .filter(item => item.market_type === MARKET_TYPE.FINANCIAL && item.shortcode !== JURISDICTION.MALTA_INVEST) .map(item => ({ ...item, platform: CFD_PLATFORMS.MT5 } as const)); const gaming_accounts = available_accounts - .filter(item => item.market_type === MARKET_TYPE.GAMING && item.is_default_jurisdiction === 'true') + .filter(item => item.market_type === MARKET_TYPE.GAMING) .map(item => ({ ...item, platform: CFD_PLATFORMS.MT5 } as const)); return [...gaming_accounts, ...financial_accounts, ...swap_free_accounts, ...zero_spread_accounts]; }; @@ -233,12 +339,7 @@ const getSortedCFDAvailableAccounts = (available_accounts: TModifiedTradingPlatf // Get the maltainvest accounts for EU and DIEL clients const getEUAvailableAccounts = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { const financial_accounts = available_accounts - .filter( - item => - item.market_type === MARKET_TYPE.FINANCIAL && - item.shortcode === JURISDICTION.MALTA_INVEST && - item.is_default_jurisdiction === 'true' - ) + .filter(item => item.market_type === MARKET_TYPE.FINANCIAL && item.shortcode === JURISDICTION.MALTA_INVEST) .map(item => ({ ...item, platform: CFD_PLATFORMS.MT5 } as const)); return [...financial_accounts]; }; @@ -322,7 +423,6 @@ const getCtraderDemoData = (available_accounts: TModifiedTradingPlatformAvailabl export { getHighlightedIconLabel, getJuridisctionDescription, - getDefaultJurisdictionDetails, getAccountCardTitle, getMarketType, getAccountIcon, diff --git a/packages/cfd/src/Stores/Modules/CFD/cfd-store.js b/packages/cfd/src/Stores/Modules/CFD/cfd-store.js index 210cbef51d42..3814b7fe18ba 100644 --- a/packages/cfd/src/Stores/Modules/CFD/cfd-store.js +++ b/packages/cfd/src/Stores/Modules/CFD/cfd-store.js @@ -14,6 +14,7 @@ import BaseStore from '../../base-store'; import { getDxCompanies, getMtCompanies } from './Helpers/cfd-config'; export default class CFDStore extends BaseStore { + is_cfd_personal_details_modal_visible = false; is_ctrader_transfer_modal_visible = false; is_jurisdiction_modal_visible = false; jurisdiction_selected_shortcode = ''; @@ -47,6 +48,7 @@ export default class CFDStore extends BaseStore { error_type = undefined; + is_cfd_verification_modal_visible = false; dxtrade_tokens = { demo: '', real: '', @@ -68,6 +70,7 @@ export default class CFDStore extends BaseStore { makeObservable(this, { is_compare_accounts_visible: observable, + is_cfd_personal_details_modal_visible: observable, is_jurisdiction_modal_visible: observable, is_mt5_trade_modal_visible: observable, is_ctrader_transfer_modal_visible: observable, @@ -85,6 +88,7 @@ export default class CFDStore extends BaseStore { is_cfd_password_modal_enabled: observable, is_sent_email_modal_enabled: observable, current_account: observable, + is_cfd_verification_modal_visible: observable, error_type: observable, product: observable, dxtrade_tokens: observable, @@ -97,6 +101,8 @@ export default class CFDStore extends BaseStore { is_account_unavailable_modal_visible: observable, account_title: computed, current_list: computed, + has_created_account_for_selected_jurisdiction: computed, + has_submitted_cfd_personal_details: computed, onMount: action.bound, onUnmount: override, checkShouldOpenAccount: action.bound, @@ -142,6 +148,7 @@ export default class CFDStore extends BaseStore { topUpVirtual: action.bound, sendVerifyEmail: action.bound, setJurisdictionSelectedShortcode: action.bound, + toggleCFDVerificationModal: action.bound, setDxtradeToken: action.bound, setCTraderToken: action.bound, loadDxtradeTokens: action.bound, @@ -159,6 +166,12 @@ export default class CFDStore extends BaseStore { : ''; } + get has_submitted_cfd_personal_details() { + const { citizen, place_of_birth, tax_residence, tax_identification_number, account_opening_reason } = + this.root_store.client.account_settings; + return !!(citizen && place_of_birth && tax_residence && tax_identification_number && account_opening_reason); + } + get current_list() { const list = {}; const show_eu_related_content = this.root_store.traders_hub.show_eu_related_content; @@ -199,6 +212,24 @@ export default class CFDStore extends BaseStore { return getDxCompanies(); } + get has_created_account_for_selected_jurisdiction() { + switch (this.account_type.type) { + case 'synthetic': + return this.real_synthetic_accounts_existing_data?.some( + account => account.landing_company_short === this.jurisdiction_selected_shortcode + ); + // here + case 'all': + return this.real_swapfree_accounts_existing_data?.some( + account => account.landing_company_short === this.jurisdiction_selected_shortcode + ); + default: + return this.real_financial_accounts_existing_data?.some( + account => account.landing_company_short === this.jurisdiction_selected_shortcode + ); + } + } + onMount() { this.checkShouldOpenAccount(); this.onRealAccountSignupEnd(this.realAccountSignupEndListener); @@ -444,7 +475,11 @@ export default class CFDStore extends BaseStore { phone, state: address_state, zipCode: address_postcode, - product: this.product, + ...(this.account_type.type === 'all' + ? this.product === 'swap_free' + ? { product: 'swap_free' } + : { product: 'zero_spread' } + : {}), ...(values.server ? { server: values.server } : {}), ...(this.jurisdiction_selected_shortcode && this.account_type.category === 'real' ? { company: this.jurisdiction_selected_shortcode } @@ -845,6 +880,10 @@ export default class CFDStore extends BaseStore { this.jurisdiction_selected_shortcode = shortcode; } + toggleCFDVerificationModal() { + this.is_cfd_verification_modal_visible = !this.is_cfd_verification_modal_visible; + } + setMigratedMT5Accounts(accounts) { this.migrated_mt5_accounts = accounts; } diff --git a/packages/cfd/src/sass/cfd-dashboard.scss b/packages/cfd/src/sass/cfd-dashboard.scss index 670756008ba5..3ca7ee8aa12e 100644 --- a/packages/cfd/src/sass/cfd-dashboard.scss +++ b/packages/cfd/src/sass/cfd-dashboard.scss @@ -1756,6 +1756,31 @@ } } +.cfd-personal-details-modal { + grid-template-rows: 8rem minmax(10rem, 1fr); + overflow-y: scroll; + + &__heading-container { + padding-top: 2.4rem; + } + + &__form { + .dc-modal-footer { + width: 100%; + padding: 1.6rem 2.4rem; + margin-top: 7.7rem; + bottom: 0; + right: 0; + display: flex; + justify-content: flex-end; + } + + .dc-autocomplete { + margin-bottom: 3.6rem; + } + } +} + .dc-modal { &__container { min-width: initial; @@ -1783,6 +1808,178 @@ } } +.cfd-proof-of-identity { + height: 100%; + overflow: auto; + + &__fields { + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + + @include mobile-or-tablet-screen { + display: unset; + position: relative; + } + + .proof-of-identity { + &:is(span) { + width: unset; + height: unset; + } + + &__footer { + width: 45%; + display: inline-flex; + justify-content: flex-end; + height: unset; + position: unset; + bottom: unset; + left: unset; + padding: unset; + z-index: unset; + border-radius: unset; + border-top: unset; + background-color: unset; + align-items: unset; + flex-direction: unset; + + @include mobile-or-tablet-screen { + width: 95%; + margin-top: 8px; + } + + &-alert { + margin-right: unset; + } + } + + &__container { + display: flex; + flex-direction: column; + align-items: center; + width: 45%; + justify-content: unset; + + @include mobile-or-tablet-screen { + width: 100%; + padding: 0 1.2rem; + overflow-y: unset; + justify-content: unset; + } + + .icon { + width: 12.8rem; + height: 12.8rem; + margin: 1.6rem 0 2.4rem 0; + } + .dc-input__bottom-label { + margin: unset; + } + .btm-spacer { + margin-bottom: 1.6rem; + } + .top-spacer { + margin-top: 1.6rem; + } + + .proof-of-identity__footer { + // for cases when __footer is a child of proof-of-identity__container: + width: 100%; + margin-bottom: 8.6em; + + span.dc-text.dc-btn__text { + display: inline-flex; + align-items: center; + } + .back-btn { + margin-right: unset; + + &-icon { + margin-right: 0.8rem; + } + } + } + } + + &__header { + margin: 0 0 1.6rem; + } + &__country-text { + text-align: center; + margin-bottom: 1.6rem; + } + &__inner-container { + display: unset; + flex-direction: unset; + justify-content: unset; + align-items: unset; + width: 100%; + } + &__image { + width: 100%; + max-width: unset; + border-radius: unset; + object-fit: unset; + + &-container { + width: unset; + height: unset; + padding: unset; + border-radius: unset; + background-color: unset; + } + } + &__fieldset { + width: 100%; + + @include mobile-or-tablet-screen { + margin: 0 0 1.8rem; + } + + &-container { + display: unset; + flex-direction: unset; + justify-content: unset; + align-items: unset; + } + &-input { + width: unset; + } + } + &__sample-container { + margin-top: 2.4rem; + margin-left: unset; + width: unset; + } + + &__submit-button { + margin-left: 0.8rem; + @include mobile-or-tablet-screen { + margin-right: unset; + } + } + } + .dc-themed-scrollbars { + height: 100%; + } + } + & .details-form { + display: grid; + grid-template-rows: minmax(10rem, 1fr) 8.2rem; + height: 100%; + position: relative; + + @include mobile-or-tablet-screen { + max-height: calc(100% - 1rem); + } + } + @include mobile-or-tablet-screen { + overflow: hidden; + } +} + .cfd-change-password { &__description { margin-bottom: 2.4rem; diff --git a/packages/cfd/src/sass/cfd.scss b/packages/cfd/src/sass/cfd.scss index c01c27851bed..0965b987a81b 100644 --- a/packages/cfd/src/sass/cfd.scss +++ b/packages/cfd/src/sass/cfd.scss @@ -205,7 +205,7 @@ padding: 0.8rem; align-items: flex-start; gap: 0.8rem; - margin: 1.6rem auto; + margin-block-end: 0.8rem; &__icon { flex-basis: 1.6rem; max-width: 1.6rem; @@ -395,6 +395,54 @@ } } +/** @define poi-icon-row; weak */ +.poi-icon-row { + display: flex; + margin-bottom: 16px; + + &__icon-container { + align-items: center; + display: flex; + flex-direction: column; + color: var(--text-less-prominent); + + &:not(:first-child):not(:last-child) { + margin: 0 40px; + } + p { + font-size: var(--text-size-xxs); + } + p:first-of-type { + line-height: 1.5; + margin-top: 8px; + font-weight: bold; + } + @include mobile-or-tablet-screen { + &:not(:first-child):not(:last-child) { + margin: 0; + } + & .dc-icon { + width: 8.8rem; + height: auto; + + /* iPhone SE screen width fixes due to UI space restrictions */ + @media only screen and (max-width: 340px) { + width: 7rem; + } + } + p { + line-height: 20px; + } + } + } + @include mobile-or-tablet-screen { + display: grid; + grid-gap: 2.4rem; + grid-template-columns: 1fr 1fr 1fr; + margin-top: 4rem; + } +} + .dc-modal__container_cfd-financial-stp-modal { .dc-modal-header { border-bottom: 1px solid var(--general-section-1); @@ -457,7 +505,7 @@ } .dc-form-submit-button { - box-shadow: 0px 2px 0px 0px var(--border-divider) inset; + box-shadow: 0px 2px 0px 0px #f2f3f4 inset; width: 100%; padding-inline: 1.6rem; padding-block-start: 1.6rem; @@ -498,7 +546,7 @@ } .dc-modal-header { &--cfd-password-modal { - box-shadow: 0px -2px 0px 0px var(--border-divider) inset; + box-shadow: 0px -2px 0px 0px #f2f3f4 inset; } } } diff --git a/packages/cfd/src/types/cfd-store.types.ts b/packages/cfd/src/types/cfd-store.types.ts index 2d731f2dd096..03b47a15316e 100644 --- a/packages/cfd/src/types/cfd-store.types.ts +++ b/packages/cfd/src/types/cfd-store.types.ts @@ -1,11 +1,12 @@ import { FormikHelpers } from 'formik'; import { DetailsOfEachMT5Loginid, Mt5NewAccount, VerifyEmailResponse } from '@deriv/api-types'; -import { TDetailsOfEachMT5Loginid, TProducts, TModifiedTradingPlatformAvailableAccount } from 'Components/props.types'; +import { TDetailsOfEachMT5Loginid, TProducts, TTradingPlatformAvailableAccount } from 'Components/props.types'; import { TCFDPasswordFormValues } from 'Containers/cfd-password-modal'; import { TDxCompanies, TMtCompanies } from 'Stores/Modules/CFD/Helpers/cfd-config'; export type TCFDStore = { setMT5TradeAccount: (arg: T) => void; + toggleCFDVerificationModal: () => void; setJurisdictionSelectedShortcode: (shortcode: string) => void; setAccountType: (account_type: { category: string; type?: string }) => void; product: TProducts; @@ -20,7 +21,7 @@ export type TCFDStore = { loadCTraderTokens: (url: string, account_type: 'real' | 'demo') => void; mt5_trade_account: Required< TDetailsOfEachMT5Loginid & { - market_type?: TModifiedTradingPlatformAvailableAccount['market_type'] | 'synthetic'; + market_type?: TTradingPlatformAvailableAccount['market_type'] | 'synthetic'; } >; real_synthetic_accounts_existing_data: DetailsOfEachMT5Loginid & DetailsOfEachMT5Loginid[]; @@ -31,6 +32,9 @@ export type TCFDStore = { category: string; }; jurisdiction_selected_shortcode: string; + toggleJurisdictionModal: () => void; + has_submitted_cfd_personal_details: boolean; + is_jurisdiction_modal_visible: boolean; clearCFDError: () => void; current_list: Record; is_compare_accounts_visible: boolean; @@ -70,6 +74,8 @@ export type TCFDStore = { actions: FormikHelpers ) => void; new_account_response: Mt5NewAccount; + is_cfd_verification_modal_visible: boolean; + has_created_account_for_selected_jurisdiction: boolean; enableCFDPasswordModal: () => void; setSentEmailModalStatus: (status: boolean) => void; onMount: () => void; diff --git a/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss index a3ccd3dd8e1d..8e9018114a41 100644 --- a/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss +++ b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss @@ -5,6 +5,7 @@ --slide-height: 19rem; overflow: hidden; &__viewport { + overflow: hidden; width: 100%; height: 100%; @include mobile-or-tablet-screen { @@ -17,7 +18,7 @@ touch-action: pan-y; flex-direction: row; max-height: auto; - margin: 1.6rem; + margin-left: calc(var(--slide-spacing) * -1); transition: transform 0s ease-in-out; @include mobile-or-tablet-screen { justify-content: safe center; diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index 368d25cf23e0..f30954f7249b 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -747,7 +747,6 @@ import './mt5/ic-mt5-device-laptop.svg'; import './mt5/ic-mt5-device-phone.svg'; import './mt5/ic-mt5-device-tablet.svg'; import './mt5/ic-mt5-expired.svg'; -import './mt5/ic-mt5-failed.svg'; import './mt5/ic-mt5-financial-platform.svg'; import './mt5/ic-mt5-financial-stp-platform.svg'; import './mt5/ic-mt5-financial-stp.svg'; @@ -761,12 +760,10 @@ import './mt5/ic-mt5-one-password.svg'; import './mt5/ic-mt5-open-markets.svg'; import './mt5/ic-mt5-password-updated.svg'; import './mt5/ic-mt5-password.svg'; -import './mt5/ic-mt5-pending.svg'; import './mt5/ic-mt5-responsive.svg'; import './mt5/ic-mt5-standard-financial-bvi.svg'; import './mt5/ic-mt5-standard-financial-vanuatu.svg'; import './mt5/ic-mt5-standard.svg'; -import './mt5/ic-mt5-success.svg'; import './mt5/ic-mt5-support.svg'; import './mt5/ic-mt5-svg-derived.svg'; import './mt5/ic-mt5-svg-financial.svg'; @@ -782,7 +779,6 @@ import './mt5/ic-mt5-vanuatu-derived.svg'; import './mt5/ic-mt5-vanuatu-financial.svg'; import './mt5/ic-mt5-vanuatu-standard.svg'; import './mt5/ic-mt5-vanuatu.svg'; -import './mt5/ic-mt5-verification.svg'; import './mt5/ic-mt5-zero-spread.svg'; import './option/ic-option-accumulators.svg'; import './option/ic-option-call-put-reset.svg'; diff --git a/packages/components/src/components/icon/mt5/ic-mt5-failed.svg b/packages/components/src/components/icon/mt5/ic-mt5-failed.svg deleted file mode 100644 index 1f06dd2b4974..000000000000 --- a/packages/components/src/components/icon/mt5/ic-mt5-failed.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/components/src/components/icon/mt5/ic-mt5-pending.svg b/packages/components/src/components/icon/mt5/ic-mt5-pending.svg deleted file mode 100644 index 3dcf707fd135..000000000000 --- a/packages/components/src/components/icon/mt5/ic-mt5-pending.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/components/src/components/icon/mt5/ic-mt5-success.svg b/packages/components/src/components/icon/mt5/ic-mt5-success.svg deleted file mode 100644 index 8a92254b5b93..000000000000 --- a/packages/components/src/components/icon/mt5/ic-mt5-success.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/components/src/components/icon/mt5/ic-mt5-verification.svg b/packages/components/src/components/icon/mt5/ic-mt5-verification.svg deleted file mode 100644 index b9d08027243d..000000000000 --- a/packages/components/src/components/icon/mt5/ic-mt5-verification.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/components/src/components/status-badge/status-badge.scss b/packages/components/src/components/status-badge/status-badge.scss index 9e059eda612a..d1ccb87bb69b 100644 --- a/packages/components/src/components/status-badge/status-badge.scss +++ b/packages/components/src/components/status-badge/status-badge.scss @@ -4,56 +4,27 @@ } &__container { - display: flex; + display: inline-flex; padding: 2px 7px; + color: var(--status-info); border-radius: 4px; + border: 1.5px solid var(--status-info); align-items: center; white-space: nowrap; - font-size: var(--text-size-xxs); &--migrated_with_position, &--migrated_without_position, + &--pending, &--under_maintenance, &--unavailable { - display: inline-flex; - border: 1.5px solid var(--status-warning); + border-color: var(--status-warning); color: var(--status-warning); font-size: var(--text-size-xxxs); align-items: center; } - - &--pending { - height: 2.4rem; - justify-content: center; - color: var(--status-warning-dark); - background-color: var(--status-warning-transparent); - text-decoration: underline; - font-weight: bold; - } - - &--needs_verification { - background-color: var(--status-info-transparent); - color: var(--status-info); - text-decoration: underline; - font-weight: bold; - } - &--failed, - &--rejected, - &--suspected { - height: 2.4rem; + &--failed { border-color: var(--status-danger); - color: $color-status-danger; - font-weight: bold; - text-decoration: underline; - background-color: var(--status-danger-transparent); - } - - &--verified { - height: 2.4rem; - margin-top: 0.1rem; - color: $color-status-verified; - background-color: var(--status-success-transparent); - font-weight: bold; + color: var(--status-danger); } &--icon { @@ -65,3 +36,20 @@ } } } + +.link-need-verification { + color: var(--status-info); + font-weight: bold; + cursor: pointer; + font-size: var(--text-size-xxxs); + padding-left: 0.3rem; + text-decoration: underline; +} + +.link-verification-failed { + color: var(--status-danger); + font-weight: bold; + text-decoration: underline; + font-size: var(--text-size-xxxs); + cursor: pointer; +} diff --git a/packages/components/src/components/status-badge/status-badge.tsx b/packages/components/src/components/status-badge/status-badge.tsx index ce242080249e..4158a4f9cae4 100644 --- a/packages/components/src/components/status-badge/status-badge.tsx +++ b/packages/components/src/components/status-badge/status-badge.tsx @@ -7,14 +7,12 @@ type TStatusBadgeProps = { account_status: string | null; icon: string; text: ReactNode; - icon_size?: string; onClick?: () => void; }; const StatusBadge = ({ account_status, icon, - icon_size = '11', text, className, onClick, @@ -31,10 +29,10 @@ const StatusBadge = ({
- +
{text}
diff --git a/packages/core/package.json b/packages/core/package.json index e45704b4ac32..3605ef90c39e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -114,7 +114,7 @@ "@deriv/hooks": "^1.0.0", "@deriv/p2p": "^0.7.3", "@deriv/quill-design": "^1.3.2", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@deriv/reports": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", diff --git a/packages/core/src/App/Containers/CurrencySelectionModal/curreny-selection-modal.tsx b/packages/core/src/App/Containers/CurrencySelectionModal/curreny-selection-modal.tsx index d64b4079f71e..9996c8ed5cd5 100644 --- a/packages/core/src/App/Containers/CurrencySelectionModal/curreny-selection-modal.tsx +++ b/packages/core/src/App/Containers/CurrencySelectionModal/curreny-selection-modal.tsx @@ -23,12 +23,18 @@ const CurrencySelectionModal = observer(({ is_visible }: CurrencySelectionModalP account_status, loginid: current_loginid, } = client; - const { closeModal, selected_region } = traders_hub; + const { closeModal, selected_region, openFailedVerificationModal } = traders_hub; const { openRealAccountSignup, toggleSetCurrencyModal } = ui; + const { authentication } = account_status || {}; const mf_account_status = useMFAccountStatus(); - - const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig(mf_account_status); + const { text: badge_text, icon: badge_icon } = getStatusBadgeConfig( + mf_account_status, + openFailedVerificationModal, + 'multipliers', + undefined, + { poi_status: authentication?.identity?.status, poa_status: authentication?.document?.status } + ); const hasSetCurrency = useHasSetCurrency(); let timeout: ReturnType; diff --git a/packages/core/src/Constants/cfd-text.js b/packages/core/src/Constants/cfd-text.js index 18a134087a64..52dbcd017fe2 100644 --- a/packages/core/src/Constants/cfd-text.js +++ b/packages/core/src/Constants/cfd-text.js @@ -14,7 +14,7 @@ export const CFD_TEXT = { financial: () => localize('Financial'), financial_demo: () => localize('Financial Demo'), financial_bvi: () => localize('Financial BVI'), - stp: () => localize('Financial Labuan'), + financial_fx: () => localize('Financial Labuan'), financial_v: () => localize('Financial Vanuatu'), financial_svg: () => localize('Financial SVG'), all_swap_free_demo: () => localize('Swap-Free Demo'), diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index e19840ca1dcd..0b0cada855fa 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -146,6 +146,7 @@ export default class ClientStore extends BaseStore { mt5_trading_servers = []; dxtrade_trading_servers = []; + is_cfd_poi_completed = false; cfd_score = 0; @@ -235,6 +236,7 @@ export default class ClientStore extends BaseStore { financial_assessment: observable, mt5_trading_servers: observable, dxtrade_trading_servers: observable, + is_cfd_poi_completed: observable, prev_real_account_loginid: observable, prev_account_type: observable, is_already_attempted: observable, @@ -364,7 +366,6 @@ export default class ClientStore extends BaseStore { setAccountSettings: action.bound, setAccountStatus: action.bound, updateAccountStatus: action.bound, - updateMT5AccountDetails: action.bound, setInitialized: action.bound, setIsClientStoreInitialized: action.bound, cleanUp: action.bound, @@ -2019,15 +2020,6 @@ export default class ClientStore extends BaseStore { } } - async updateMT5AccountDetails() { - if (this.is_logged_in) { - await WS.authorized.mt5LoginList().then(this.responseMt5LoginList); - await WS.authorized - .tradingPlatformAvailableAccounts(CFD_PLATFORMS.MT5) - .then(this.responseTradingPlatformAvailableAccounts); - } - } - setInitialized(is_initialized) { this.initialized_broadcast = is_initialized; } @@ -2479,7 +2471,6 @@ export default class ClientStore extends BaseStore { /^(MT[DR]?)/i, '' ); - if (account.error) { const { account_type, server } = account.error.details; this.setMT5DisabledSignupTypes({ diff --git a/packages/core/src/Stores/traders-hub-store.js b/packages/core/src/Stores/traders-hub-store.js index bfccb29f370f..2ab2458450e5 100644 --- a/packages/core/src/Stores/traders-hub-store.js +++ b/packages/core/src/Stores/traders-hub-store.js @@ -4,10 +4,10 @@ import { CFD_PLATFORMS, ContentFlag, formatMoney, + isPOARequiredForMT5, getAppstorePlatforms, getCFDAvailableAccount, - WS, - PRODUCT, + getAuthenticationStatusInfo, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import BaseStore from './base-store'; @@ -24,15 +24,16 @@ export default class TradersHubStore extends BaseStore { selected_region; is_onboarding_visited = false; is_first_time_visit = true; - is_verification_docs_list_modal_visible = false; + is_failed_verification_modal_visible = false; is_regulators_compare_modal_visible = false; account_type_card = ''; selected_platform_type = 'options'; + mt5_existing_account = {}; + open_failed_verification_for = ''; modal_data = { active_modal: '', data: {}, }; - selected_jurisdiction_kyc_status = {}; is_account_transfer_modal_open = false; selected_account = {}; is_real_wallets_upgrade_on = false; @@ -63,17 +64,18 @@ export default class TradersHubStore extends BaseStore { combined_cfd_mt5_accounts: observable, is_account_transfer_modal_open: observable, is_regulators_compare_modal_visible: observable, - is_verification_docs_list_modal_visible: observable, + is_failed_verification_modal_visible: observable, modal_data: observable, is_onboarding_visited: observable, is_first_time_visit: observable, selected_account: observable, - selected_jurisdiction_kyc_status: observable, selected_account_type: observable, selected_platform_type: observable, active_modal_tab: observable, active_modal_wallet_id: observable, selected_region: observable, + mt5_existing_account: observable, + open_failed_verification_for: observable, is_real_wallets_upgrade_on: observable, is_wallet_migration_failed: observable, is_cfd_restricted_country: observable, @@ -85,9 +87,7 @@ export default class TradersHubStore extends BaseStore { getAvailableCFDAccounts: action.bound, getAvailableDxtradeAccounts: action.bound, getAvailableCTraderAccounts: action.bound, - setSelectedJurisdictionKYCStatus: action.bound, getExistingAccounts: action.bound, - getMT5AccountKYCStatus: action.bound, handleTabItemClick: action.bound, setWalletModalActiveTab: action.bound, setWalletModalActiveWalletID: action.bound, @@ -118,7 +118,9 @@ export default class TradersHubStore extends BaseStore { closeAccountTransferModal: action.bound, setIsOnboardingVisited: action.bound, setIsFirstTimeVisit: action.bound, - setVerificationModalOpen: action.bound, + toggleFailedVerificationModalVisibility: action.bound, + setMT5ExistingAccount: action.bound, + openFailedVerificationModal: action.bound, toggleRegulatorsCompareModal: action.bound, showTopUpModal: action.bound, toggleWalletsUpgrade: action.bound, @@ -127,7 +129,6 @@ export default class TradersHubStore extends BaseStore { setIsCFDRestrictedCountry: action.bound, setIsFinancialRestrictedCountry: action.bound, setIsSetupRealAccountOrGoToDemoModalVisible: action.bound, - getDefaultJurisdiction: action.bound, }); reaction( @@ -212,9 +213,6 @@ export default class TradersHubStore extends BaseStore { this.is_cfd_restricted_country = value; } - setSelectedJurisdictionKYCStatus(status) { - this.selected_jurisdiction_kyc_status = status; - } setIsFinancialRestrictedCountry(value) { this.is_financial_restricted_country = value; } @@ -424,15 +422,6 @@ export default class TradersHubStore extends BaseStore { icon: !this.is_eu_user || this.is_demo_low_risk ? 'Financial' : 'CFDs', availability: 'All', }, - { - name: 'Financial', - description: getAccountDesc(), - platform: CFD_PLATFORMS.MT5, - market_type: 'financial', - product: PRODUCT.STP, - icon: 'Financial', - availability: 'Non-EU', - }, { name: 'Swap-Free', description: getSwapFreeAccountDesc(), @@ -557,21 +546,7 @@ export default class TradersHubStore extends BaseStore { account => account.platform === CFD_PLATFORMS.CTRADER ); } - /** - * Get default Jurisdiction for MT5 product types - * Product types = Standard /Financial /Swap Free /Zero Spread/ - * - */ - getDefaultJurisdiction() { - const { trading_platform_available_accounts } = this.root_store.client; - const { product } = this.root_store.modules.cfd; - - const default_jurisdiction = trading_platform_available_accounts.filter( - available_account => - available_account.product === product && available_account.is_default_jurisdiction === 'true' - )[0]?.shortcode; - return default_jurisdiction; - } + getExistingAccounts(platform, market_type, product) { const { residence } = this.root_store.client; const current_list = this.root_store.modules?.cfd?.current_list || []; @@ -580,9 +555,7 @@ export default class TradersHubStore extends BaseStore { const existing_accounts = current_list_keys .filter(key => { const maltainvest_account = current_list[key].landing_company_short === 'maltainvest'; - if (product === PRODUCT.STP) { - return key.startsWith(`${platform}.${selected_account_type}.${product}`); - } else if ( + if ( platform === CFD_PLATFORMS.MT5 && market_type !== 'all' && !this.is_eu_user && @@ -653,17 +626,48 @@ export default class TradersHubStore extends BaseStore { } } + openRealPasswordModal = account_type => { + const { modules } = this.root_store; + const { enableCFDPasswordModal, setAccountType } = modules.cfd; + setAccountType(account_type); + enableCFDPasswordModal(); + }; + async openRealAccount(account_type, platform) { const { client, modules } = this.root_store; - const { has_active_real_account } = client; - const { createCFDAccount, enableCFDPasswordModal } = modules.cfd; - await this.getMT5AccountKYCStatus(); + const { has_active_real_account, account_status, should_restrict_bvi_account_creation } = client; + const { + createCFDAccount, + enableCFDPasswordModal, + toggleJurisdictionModal, + product, + toggleCFDVerificationModal, + setJurisdictionSelectedShortcode, + has_submitted_cfd_personal_details, + } = modules.cfd; + const { poi_or_poa_not_submitted, poi_acknowledged_for_bvi_labuan_vanuatu, poa_acknowledged } = + getAuthenticationStatusInfo(account_status); + const is_poa_required_for_mt5 = isPOARequiredForMT5(account_status, 'bvi'); if (has_active_real_account && platform === CFD_PLATFORMS.MT5) { - if (this.selected_jurisdiction_kyc_status && Object.keys(this.selected_jurisdiction_kyc_status)?.length) { - this.setVerificationModalOpen(true); - } else { - //all kyc requirements satisfied + if (product !== 'zero_spread' && product !== 'swap_free') { + toggleJurisdictionModal(); + } else if (product === 'swap_free') { + setJurisdictionSelectedShortcode('svg'); enableCFDPasswordModal(); + } else if (product === 'zero_spread') { + setJurisdictionSelectedShortcode('bvi'); + if ( + poi_acknowledged_for_bvi_labuan_vanuatu && + !poi_or_poa_not_submitted && + !should_restrict_bvi_account_creation && + poa_acknowledged && + has_submitted_cfd_personal_details && + !is_poa_required_for_mt5 + ) { + this.openRealPasswordModal(account_type); + } else { + toggleCFDVerificationModal(); + } } } else if (platform === CFD_PLATFORMS.DXTRADE) { enableCFDPasswordModal(); @@ -780,8 +784,6 @@ export default class TradersHubStore extends BaseStore { }, ]; }); - } else if (account.product === PRODUCT.STP) { - this.combined_cfd_mt5_accounts = [...this.combined_cfd_mt5_accounts]; } else { this.combined_cfd_mt5_accounts = [ ...this.combined_cfd_mt5_accounts, @@ -810,8 +812,36 @@ export default class TradersHubStore extends BaseStore { this.is_account_transfer_modal_open = !this.is_account_transfer_modal_open; } - setVerificationModalOpen(value) { - this.is_verification_docs_list_modal_visible = value; + toggleFailedVerificationModalVisibility() { + this.is_failed_verification_modal_visible = !this.is_failed_verification_modal_visible; + } + + setMT5ExistingAccount(existing_account) { + this.mt5_existing_account = existing_account; + } + + openFailedVerificationModal(selected_account_type) { + const { + common, + modules: { cfd }, + } = this.root_store; + const { setJurisdictionSelectedShortcode, setAccountType } = cfd; + const { setAppstorePlatform } = common; + + if (selected_account_type?.platform === CFD_PLATFORMS.MT5) { + setAppstorePlatform(selected_account_type.platform); + setAccountType({ + category: selected_account_type.category, + type: selected_account_type.type, + }); + this.setMT5ExistingAccount(selected_account_type); + setJurisdictionSelectedShortcode(selected_account_type.jurisdiction); + } else { + setJurisdictionSelectedShortcode(''); + } + this.open_failed_verification_for = + selected_account_type?.platform === CFD_PLATFORMS.MT5 ? selected_account_type?.jurisdiction : 'multipliers'; + this.toggleFailedVerificationModalVisibility(); } showTopUpModal(data) { @@ -848,28 +878,4 @@ export default class TradersHubStore extends BaseStore { setIsSetupRealAccountOrGoToDemoModalVisible(value) { this.is_setup_real_account_or_go_to_demo_modal_visible = value; } - - async getMT5AccountKYCStatus() { - const { jurisdiction_selected_shortcode, product } = this.root_store.modules.cfd; - const { trading_platform_available_accounts } = await WS.authorized.tradingPlatformAvailableAccounts( - CFD_PLATFORMS.MT5 - ); - const { mt5_login_list } = await WS.authorized.mt5LoginList(); - const current_account = mt5_login_list?.filter( - account => account.landing_company_short === jurisdiction_selected_shortcode && account.product === product - ); - - if (current_account.length) { - this.setSelectedJurisdictionKYCStatus(current_account[0]?.client_kyc_status ?? {}); - } else { - const selected_mt5_account = trading_platform_available_accounts?.filter( - account => account.shortcode === jurisdiction_selected_shortcode && account.product === product - ); - if (selected_mt5_account.length) { - this.setSelectedJurisdictionKYCStatus(selected_mt5_account[0]?.client_kyc_status ?? {}); - } else { - this.setSelectedJurisdictionKYCStatus({}); - } - } - } } diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index fafb35933336..76c0e77ffb85 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -149,6 +149,7 @@ export default class UIStore extends BaseStore { target_label: '', target_dmt5_label: '', }; + is_mt5_verification_failed_modal = false; manage_real_account_tab_index = 0; @@ -266,6 +267,7 @@ export default class UIStore extends BaseStore { is_app_disabled: observable, is_cashier_visible: observable, is_cfd_page: observable, + is_mt5_verification_failed_modal: observable, is_closing_create_real_account_modal: observable, is_dark_mode_on: observable, @@ -388,6 +390,7 @@ export default class UIStore extends BaseStore { toggleNeedRealAccountForCashierModal: action.bound, toggleShouldShowRealAccountsList: action.bound, shouldNavigateAfterChooseCrypto: action.bound, + setIsMT5VerificationFailedModal: action.bound, setShouldShowRiskWarningModal: action.bound, setRedirectFromEmail: action.bound, setIsWalletModalVisible: action.bound, @@ -497,6 +500,10 @@ export default class UIStore extends BaseStore { this.notification_messages_ui = notification_messages; } + setIsMT5VerificationFailedModal(value) { + this.is_mt5_verification_failed_modal = value; + } + setAppContentsScrollRef(value) { this.app_contents_scroll_ref = value; } diff --git a/packages/hooks/src/__tests__/useGetMFAccountStatus.spec.tsx b/packages/hooks/src/__tests__/useGetMFAccountStatus.spec.tsx index 9e1f2831c148..e95b8b4d9e27 100644 --- a/packages/hooks/src/__tests__/useGetMFAccountStatus.spec.tsx +++ b/packages/hooks/src/__tests__/useGetMFAccountStatus.spec.tsx @@ -35,11 +35,10 @@ describe('useGetMFAccountStatus', () => { wrapper, }); - expect(result.current.mf_account_status).toBe('needs_verification'); - expect(result.current.kyc_status).toStrictEqual({ poa_status: 'none', poi_status: 'verified', valid_tin: 1 }); + expect(result.current).toBe('needs_verification'); }); - it('should return failed if either POI or POA is failed', () => { + it('should return failed if either of none, pending or verified statuses are not present', () => { const mock = mockStore({ client: { account_status: { @@ -53,7 +52,6 @@ describe('useGetMFAccountStatus', () => { status: 'rejected', }, }, - status: 'rejected', }, document: { status: 'suspected', @@ -71,13 +69,10 @@ describe('useGetMFAccountStatus', () => { wrapper, }); - expect(result.current).toStrictEqual({ - kyc_status: { poa_status: 'suspected', poi_status: 'rejected', valid_tin: 1 }, - mf_account_status: 'failed', - }); + expect(result.current).toBe('failed'); }); - it('should return pending if POA status is verified and POI is pending', () => { + it('should return pending if poa status is pending or verified status is not present in onfido or manual status', () => { const mock = mockStore({ client: { account_status: { @@ -91,10 +86,9 @@ describe('useGetMFAccountStatus', () => { status: 'none', }, }, - status: 'pending', }, document: { - status: 'verified', + status: 'suspected', }, }, }, @@ -109,9 +103,6 @@ describe('useGetMFAccountStatus', () => { wrapper, }); - expect(result.current).toStrictEqual({ - kyc_status: { poa_status: 'verified', poi_status: 'pending', valid_tin: 1 }, - mf_account_status: 'pending', - }); + expect(result.current).toBe('failed'); }); }); diff --git a/packages/hooks/src/__tests__/useGetStatus.spec.tsx b/packages/hooks/src/__tests__/useGetStatus.spec.tsx deleted file mode 100644 index a2c08be86321..000000000000 --- a/packages/hooks/src/__tests__/useGetStatus.spec.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import * as React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { mockStore, StoreProvider } from '@deriv/stores'; -import useGetStatus from '../useGetStatus'; -import useGetMFAccountStatus from '../useGetMFAccountStatus'; -import useIsSelectedMT5AccountCreated from '../useIsSelectedMT5AccountCreated'; -import { CFD_PLATFORMS, MT5_ACCOUNT_STATUS } from '@deriv/shared'; - -jest.mock('../useIsSelectedMT5AccountCreated'); -jest.mock('../useGetMFAccountStatus', () => jest.fn()); - -const mockUseIsSelectedMT5AccountCreated = useIsSelectedMT5AccountCreated as jest.MockedFunction< - typeof useIsSelectedMT5AccountCreated ->; -const mockUseGetMFAccountStatus = useGetMFAccountStatus as jest.MockedFunction; - -describe('useGetStatus', () => { - beforeEach(() => { - // Reset all mocks before each test - jest.resetAllMocks(); - }); - - it('should return correct MT5 status when platform is MT5 and default jurisdiction is not created', () => { - const mock = mockStore({ - common: { platform: CFD_PLATFORMS.MT5 }, - traders_hub: { - selected_jurisdiction_kyc_status: {}, - }, - modules: { cfd: { jurisdiction_selected_shortcode: 'bvi' } }, - }); - mockUseGetMFAccountStatus.mockReturnValue({ - mf_account_status: null, - kyc_status: {}, - }); - - mockUseIsSelectedMT5AccountCreated.mockReturnValue({ - available_account_to_create: { - is_default_jurisdiction: 'true', - requirements: { - after_first_deposit: { - financial_assessment: ['financial_information', 'trading_experience'], - }, - compliance: { - mt5: ['fully_authenticated', 'expiration_check'], - tax_information: ['tax_residence', 'tax_identification_number'], - }, - signup: ['phone', 'citizen', 'account_opening_reason'], - }, - sub_account_type: 'standard', - shortcode: 'bvi', - market_type: 'financial', - product: 'financial', - name: 'sample company', - client_kyc_status: { - poa_status: 'pending', - poi_status: 'verified', - valid_tin: 1, - }, - }, - is_selected_MT5_account_created: false, - existing_account_status: MT5_ACCOUNT_STATUS.PENDING, - existing_account: null, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useGetStatus(), { wrapper }); - expect(result.current).toEqual({ - status_badge: MT5_ACCOUNT_STATUS.PENDING, - client_kyc_status: { - poa_status: 'pending', - poi_status: 'verified', - valid_tin: 1, - }, - }); - }); - - it('should return correct MT5 status when platform is MT5 and jurisdictions is created', () => { - const mock = mockStore({ - common: { platform: CFD_PLATFORMS.MT5 }, - traders_hub: { - selected_jurisdiction_kyc_status: {}, - }, - modules: { cfd: { jurisdiction_selected_shortcode: 'vanautu' } }, - }); - mockUseGetMFAccountStatus.mockReturnValue({ - mf_account_status: null, - kyc_status: {}, - }); - - mockUseIsSelectedMT5AccountCreated.mockReturnValue({ - existing_account: { - account_type: 'real', - balance: 0, - country: 'bh', - currency: 'USD', - display_balance: '0.00', - group: 'real\\p01_ts01\\financial\\svg_std-hr_usd', - landing_company_short: 'vanuatu', - leverage: 1000, - login: 'MTR9586832', - market_type: 'financial', - product: 'financial', - status: 'verification_pending', - client_kyc_status: {}, - }, - available_account_to_create: null, - is_selected_MT5_account_created: true, - existing_account_status: MT5_ACCOUNT_STATUS.PENDING, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useGetStatus(), { wrapper }); - expect(result.current).toEqual({ - status_badge: MT5_ACCOUNT_STATUS.PENDING, - client_kyc_status: {}, - }); - }); - - it('should return correct account status for deriv account', () => { - const mock = mockStore({ - common: { platform: '' }, - traders_hub: { - selected_jurisdiction_kyc_status: {}, - }, - modules: { cfd: { jurisdiction_selected_shortcode: '' } }, - }); - mockUseGetMFAccountStatus.mockReturnValue({ - mf_account_status: 'pending', - kyc_status: { - poi_status: 'pending', - poa_status: 'verified', - valid_tin: 1, - }, - }); - - mockUseIsSelectedMT5AccountCreated.mockReturnValue({ - available_account_to_create: null, - is_selected_MT5_account_created: false, - existing_account_status: null, - existing_account: null, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useGetStatus(), { wrapper }); - expect(result.current).toEqual({ - status_badge: MT5_ACCOUNT_STATUS.PENDING, - client_kyc_status: { - poi_status: 'pending', - poa_status: 'verified', - valid_tin: 1, - }, - }); - }); - - it('should return empty requirements when fully verified for deriv account', () => { - const mock = mockStore({ - common: { platform: '' }, - traders_hub: { - selected_jurisdiction_kyc_status: {}, - }, - modules: { cfd: { jurisdiction_selected_shortcode: '' } }, - }); - mockUseGetMFAccountStatus.mockReturnValue({ - mf_account_status: null, - kyc_status: {}, - }); - - mockUseIsSelectedMT5AccountCreated.mockReturnValue({ - available_account_to_create: null, - existing_account: null, - is_selected_MT5_account_created: false, - existing_account_status: MT5_ACCOUNT_STATUS.PENDING, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useGetStatus(), { wrapper }); - expect(result.current).toEqual({ - status_badge: null, - client_kyc_status: {}, - }); - }); -}); diff --git a/packages/hooks/src/__tests__/useIsSelectedMT5AccountCreated.spec.tsx b/packages/hooks/src/__tests__/useIsSelectedMT5AccountCreated.spec.tsx deleted file mode 100644 index f7912df8c47f..000000000000 --- a/packages/hooks/src/__tests__/useIsSelectedMT5AccountCreated.spec.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import * as React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { mockStore, StoreProvider } from '@deriv/stores'; -import useIsSelectedMT5AccountCreated from '../useIsSelectedMT5AccountCreated'; -import { CFD_PLATFORMS, MT5_ACCOUNT_STATUS } from '@deriv/shared'; - -describe('useIsSelectedMT5AccountCreated', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - it('should return account details and status when MT5 account is created', () => { - const mock = mockStore({ - common: { platform: CFD_PLATFORMS.MT5 }, - client: { - mt5_login_list: [ - { landing_company_short: 'svg', product: 'financial', status: 'proof_failed', login: '123' }, - ], - updateMT5AccountDetails: jest.fn(), - trading_platform_available_accounts: [{ shortcode: 'svg', product: 'financial' }], - }, - - modules: { cfd: { jurisdiction_selected_shortcode: 'svg', product: 'financial' } }, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useIsSelectedMT5AccountCreated(), { wrapper }); - - expect(result.current.is_selected_MT5_account_created).toBe(true); - expect(result.current.existing_account).toEqual({ - landing_company_short: 'svg', - product: 'financial', - status: 'proof_failed', - login: '123', - }); - expect(result.current.existing_account_status).toBe(MT5_ACCOUNT_STATUS.FAILED); - expect(result.current.available_account_to_create).toBeNull(); - }); - it('should return account details and status when MT5 account is not created', () => { - const mock = mockStore({ - common: { platform: CFD_PLATFORMS.MT5 }, - client: { - mt5_login_list: [ - { landing_company_short: 'svg', product: 'financial', status: 'proof_failed', login: '123' }, - ], - updateMT5AccountDetails: jest.fn(), - trading_platform_available_accounts: [ - { shortcode: 'svg', product: 'financial' }, - { shortcode: 'bvi', product: 'zero_spread' }, - ], - }, - - modules: { cfd: { jurisdiction_selected_shortcode: 'bvi', product: 'zero_spread' } }, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useIsSelectedMT5AccountCreated(), { wrapper }); - - expect(result.current.is_selected_MT5_account_created).toBe(false); - expect(result.current.existing_account).toBeNull(); - expect(result.current.existing_account_status).toBeNull(); - expect(result.current.available_account_to_create).toEqual({ - shortcode: 'bvi', - product: 'zero_spread', - }); - }); - - it('should return the correct status when account status "verification_pending"', () => { - const mock = mockStore({ - client: { - mt5_login_list: [{ landing_company_short: 'svg', product: 'standard', status: 'verification_pending' }], - trading_platform_available_accounts: [{ shortcode: 'svg', product: 'standard' }], - updateMT5AccountDetails: jest.fn(), - }, - modules: { - cfd: { - jurisdiction_selected_shortcode: 'svg', - product: 'standard', - }, - }, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useIsSelectedMT5AccountCreated(), { wrapper }); - - expect(result.current.existing_account_status).toBe(MT5_ACCOUNT_STATUS.PENDING); - }); - - it('should handle cases where mt5_login_list and trading_platform_available_accounts are empty', () => { - const mock = mockStore({ - client: { - mt5_login_list: [], - trading_platform_available_accounts: [], - updateMT5AccountDetails: jest.fn(), - }, - modules: { - cfd: { - jurisdiction_selected_shortcode: 'svg', - product: 'standard', - }, - }, - }); - - const wrapper = ({ children }: { children: JSX.Element }) => ( - {children} - ); - const { result } = renderHook(() => useIsSelectedMT5AccountCreated(), { wrapper }); - - expect(result.current.is_selected_MT5_account_created).toBe(false); - expect(result.current.existing_account).toBeNull(); - expect(result.current.existing_account_status).toBeNull(); - }); -}); diff --git a/packages/hooks/src/__tests__/useMFAccountStatus.spec.tsx b/packages/hooks/src/__tests__/useMFAccountStatus.spec.tsx index 092646d07ec6..cbf34f14f5f6 100644 --- a/packages/hooks/src/__tests__/useMFAccountStatus.spec.tsx +++ b/packages/hooks/src/__tests__/useMFAccountStatus.spec.tsx @@ -1,8 +1,6 @@ -import * as React from 'react'; import { mockStore, StoreProvider } from '@deriv/stores'; -import { ACCOUNT_BADGE_STATUS } from '@deriv/shared'; import { renderHook } from '@testing-library/react-hooks'; - +import * as React from 'react'; import useMFAccountStatus from '../useMFAccountStatus'; import useHasMaltaInvestAccount from '../useHasMaltaInvestAccount'; import useGetMFAccountStatus from '../useGetMFAccountStatus'; @@ -22,10 +20,7 @@ describe('useMFAccountStatus', () => { it('should return mf_status if conditions are met', () => { mock_store.client.is_eu = true; mockUseHasMaltaInvestAccount.mockReturnValue(true); - mockUseGetMFAccountStatus.mockReturnValue({ - kyc_status: { poi_status: 'verified', poa_status: 'verified', valid_tin: 1 }, - mf_account_status: ACCOUNT_BADGE_STATUS.NEEDS_VERIFICATION, - }); + mockUseGetMFAccountStatus.mockReturnValue('needs_verification'); const { result } = renderHook(() => useMFAccountStatus(), { wrapper, }); @@ -33,10 +28,7 @@ describe('useMFAccountStatus', () => { }); it('should return null if conditions are not met', () => { mockUseHasMaltaInvestAccount.mockReturnValue(false); - mockUseGetMFAccountStatus.mockReturnValue({ - kyc_status: { poi_status: 'verified', poa_status: 'verified', valid_tin: 1 }, - mf_account_status: ACCOUNT_BADGE_STATUS.NEEDS_VERIFICATION, - }); + mockUseGetMFAccountStatus.mockReturnValue('needs_verification'); const { result } = renderHook(() => useMFAccountStatus(), { wrapper, }); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 6671eccaaaf4..15943390aba6 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -94,12 +94,10 @@ export { default as useTotalAssetCurrency } from './useTotalAssetCurrency'; export { default as useGrowthbookIsOn } from './useGrowthbookIsOn'; export { default as useResidenceList } from './useResidenceList'; export { default as useTinValidations } from './useTinValidations'; -export { default as useGetStatus } from './useGetStatus'; export { default as usePhoneNumberVerificationSetTimer } from './usePhoneNumberVerificationSetTimer'; export { default as usePhoneNumberVerificationSessionTimer } from './usePhoneNumberVerificationSessionTimer'; export { default as useIsPhoneNumberVerified } from './useIsPhoneNumberVerified'; export { default as usePhoneVerificationAnalytics } from './usePhoneVerificationAnalytics'; export { default as useTradingPlatformStatus } from './useTradingPlatformStatus'; -export { default as useIsSelectedMT5AccountCreated } from './useIsSelectedMT5AccountCreated'; export { default as useOauth2 } from './useOauth2'; export type { TradingPlatformStatus } from './useTradingPlatformStatus'; diff --git a/packages/hooks/src/useGetMFAccountStatus.ts b/packages/hooks/src/useGetMFAccountStatus.ts index 1a783c6a8898..0fd07192784d 100644 --- a/packages/hooks/src/useGetMFAccountStatus.ts +++ b/packages/hooks/src/useGetMFAccountStatus.ts @@ -1,19 +1,10 @@ -import { useEffect } from 'react'; import { useStore } from '@deriv/stores'; -import { ACCOUNT_BADGE_STATUS } from '@deriv/shared'; const useGetMFAccountStatus = () => { const { client } = useStore(); - const { account_status, updateAccountStatus } = client || {}; - - useEffect(() => { - async function fetchData() { - await updateAccountStatus(); - } - fetchData(); - }, []); + const { account_status } = client || {}; const authentication = account_status?.authentication; - const poi_status = authentication?.identity?.status; + const onfido_status = authentication?.identity?.services?.onfido?.status; const manual_status = authentication?.identity?.services?.manual?.status; const poa_status = authentication?.document?.status; @@ -40,30 +31,20 @@ const useGetMFAccountStatus = () => { const poi_not_submitted_by_onfido_or_manual = [onfido_status, manual_status].every( status => status === STATUS.NONE ); - const need_poi_submission = !poi_pending_by_onfido_or_manual && !poi_verified_by_onfido_or_manual; - const need_poi_resubmission = !poi_not_submitted_by_onfido_or_manual && need_poi_submission; - const is_verified = poi_status === STATUS.VERIFIED && poa_status === STATUS.VERIFIED; + const need_poi_resubmission_by_onfido_or_manual = + !poi_pending_by_onfido_or_manual && !poi_not_submitted_by_onfido_or_manual && !poi_verified_by_onfido_or_manual; - const getMFAccountStatus = () => { - if (poa_status && onfido_status && manual_status) { - if (need_poi_resubmission || need_poa_resubmission) { - return ACCOUNT_BADGE_STATUS.FAILED; - } else if (poi_not_submitted_by_onfido_or_manual || poa_not_submitted) { - return ACCOUNT_BADGE_STATUS.NEEDS_VERIFICATION; - } else if (poi_pending_by_onfido_or_manual || poa_pending) { - return ACCOUNT_BADGE_STATUS.PENDING; - } - return null; + if (poa_status && onfido_status && manual_status) { + if (need_poi_resubmission_by_onfido_or_manual || need_poa_resubmission) { + return 'failed'; + } else if (poi_not_submitted_by_onfido_or_manual || poa_not_submitted) { + return 'needs_verification'; + } else if (poi_pending_by_onfido_or_manual || poa_pending) { + return 'pending'; } - return null; - }; - const getVerificationStatus = () => (!is_verified ? { poi_status, poa_status, valid_tin: 1 } : {}); - - return { - mf_account_status: getMFAccountStatus(), - kyc_status: getVerificationStatus(), - }; + } + return null; }; export default useGetMFAccountStatus; diff --git a/packages/hooks/src/useGetStatus.ts b/packages/hooks/src/useGetStatus.ts deleted file mode 100644 index 26c57bad89d6..000000000000 --- a/packages/hooks/src/useGetStatus.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useStore } from '@deriv/stores'; -import useGetMFAccountStatus from './useGetMFAccountStatus'; -import useIsSelectedMT5AccountCreated from './useIsSelectedMT5AccountCreated'; -import { CFD_PLATFORMS } from '@deriv/shared'; - -/** A custom hook to fetch the status badge value and client_kyc_status for MF account and MT5 account -MF account status is fetched from useGetMFAccountStatus -MT5 account status is fetched from useIsSelectedMT5AccountCreated -*/ -const useGetStatus = () => { - const { common } = useStore(); - const { mf_account_status, kyc_status } = useGetMFAccountStatus(); - const { is_selected_MT5_account_created, existing_account_status, existing_account, available_account_to_create } = - useIsSelectedMT5AccountCreated(); - const { platform } = common; - - if (platform === CFD_PLATFORMS.MT5) { - return { - status_badge: existing_account_status, - client_kyc_status: is_selected_MT5_account_created - ? existing_account?.client_kyc_status - : available_account_to_create?.client_kyc_status, - }; - } - return { - status_badge: mf_account_status, - client_kyc_status: kyc_status, - }; -}; - -export default useGetStatus; diff --git a/packages/hooks/src/useIsSelectedMT5AccountCreated.ts b/packages/hooks/src/useIsSelectedMT5AccountCreated.ts deleted file mode 100644 index 7a37a63af76b..000000000000 --- a/packages/hooks/src/useIsSelectedMT5AccountCreated.ts +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { useStore } from '@deriv/stores'; -import { MT5_ACCOUNT_STATUS } from '@deriv/shared'; - -const getStatusBadge = (status: string) => { - if (status === 'proof_failed') { - return MT5_ACCOUNT_STATUS.FAILED; - } else if (status === 'verification_pending') { - return MT5_ACCOUNT_STATUS.PENDING; - } else if (status === 'needs_verification') { - return MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION; - } -}; - -/** - * Custom React hook to check if the selected MT5 account has been created. - * - * @returns {Object} An object containing the following properties: - * - * - `is_selected_MT5_account_created` (boolean): Indicates whether the selected MT5 account has been created. - * - `existing_account` (Object | null): If the selected account is created, contains the MT5 account details; otherwise `null`. - * - `existing_account_status` (string | null): Status of the created account (e.g., 'failed', 'pending', 'needs_verification'), or `null` if no account is created. - * - `available_account_to_create` (Object | null): The account available to create for the selected product. Will be `null` if an account is already created. - */ - -const useIsSelectedMT5AccountCreated = () => { - const { - client, - modules: { cfd }, - } = useStore(); - const { mt5_login_list, trading_platform_available_accounts, updateMT5AccountDetails } = client; - - React.useEffect(() => { - async function fetchData() { - await updateMT5AccountDetails(); - } - fetchData(); - }, [updateMT5AccountDetails]); - - const { jurisdiction_selected_shortcode, product } = cfd; - const created_account = mt5_login_list.filter( - account => account.landing_company_short === jurisdiction_selected_shortcode && account.product === product - ); - const selected_account = trading_platform_available_accounts.filter( - account => account.shortcode === jurisdiction_selected_shortcode && account.product === product - ); - - const is_selected_MT5_account_created = created_account && Object.keys(created_account).length > 0; - - const existing_account = is_selected_MT5_account_created ? created_account[0] : null; - const existing_account_status = existing_account?.status ? getStatusBadge(existing_account?.status) : null; - - const available_account_to_create = !is_selected_MT5_account_created ? selected_account[0] : null; - return { - is_selected_MT5_account_created, - existing_account, - existing_account_status, - available_account_to_create, - }; -}; - -export default useIsSelectedMT5AccountCreated; diff --git a/packages/hooks/src/useMFAccountStatus.ts b/packages/hooks/src/useMFAccountStatus.ts index b6db4fa5f6fb..4b74c71b90bc 100644 --- a/packages/hooks/src/useMFAccountStatus.ts +++ b/packages/hooks/src/useMFAccountStatus.ts @@ -1,4 +1,4 @@ -import { ACCOUNT_BADGE_STATUS } from '@deriv/shared'; +import { MT5_ACCOUNT_STATUS } from '@deriv/shared'; import useHasMaltaInvestAccount from './useHasMaltaInvestAccount'; import useGetMFAccountStatus from './useGetMFAccountStatus'; import { useStore } from '@deriv/stores'; @@ -8,16 +8,16 @@ const useMFAccountStatus = () => { client: { is_eu }, } = useStore(); const has_malta_invest_account = useHasMaltaInvestAccount(); - const { mf_account_status } = useGetMFAccountStatus(); + const mf_status = useGetMFAccountStatus(); const should_show_status_for_multipliers_account = is_eu && has_malta_invest_account && - mf_account_status && - [ACCOUNT_BADGE_STATUS.PENDING, ACCOUNT_BADGE_STATUS.FAILED, ACCOUNT_BADGE_STATUS.NEEDS_VERIFICATION].includes( - mf_account_status + mf_status && + [MT5_ACCOUNT_STATUS.PENDING, MT5_ACCOUNT_STATUS.FAILED, MT5_ACCOUNT_STATUS.NEEDS_VERIFICATION].includes( + mf_status ); - return should_show_status_for_multipliers_account ? mf_account_status : null; + return should_show_status_for_multipliers_account ? mf_status : null; }; export default useMFAccountStatus; diff --git a/packages/shared/package.json b/packages/shared/package.json index f1769304a62e..691e1ad50730 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -47,7 +47,7 @@ }, "dependencies": { "@deriv-com/analytics": "1.14.0", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@deriv/api-types": "1.0.172", "@deriv/translations": "^1.0.0", "@deriv-com/utils": "^0.0.36", diff --git a/packages/shared/src/styles/constants.scss b/packages/shared/src/styles/constants.scss index 7bd470d3360d..e386dffb7770 100644 --- a/packages/shared/src/styles/constants.scss +++ b/packages/shared/src/styles/constants.scss @@ -61,7 +61,6 @@ $color-grey-13: #2e2e2e; $color-grey-14: #e2e5e7; $color-grey-15: #f3f4f5; $color-orange: #ff6444; -$color-orange-2: #ff9c13; $color-purple: #722fe4; $color-red: #ff444f; $color-red-1: #ec3f3f; @@ -84,15 +83,12 @@ $color-yellow-2: #ffa912; $color-yellow-3: rgba(255, 173, 58, 0.16); $color-yellow-4: #fff7e6; $color-yellow-5: #6a4800; -$color-yellow-6: #ffeabf; /* status colors */ $color-status-warning: rgba(255, 173, 58, 0.16); $color-status-information: rgba(55, 124, 252, 0.16); $color-status-announcement: rgba(75, 180, 179, 0.16); $color-status-error: rgba(236, 63, 63, 0.16); -$color-status-verified: rgba(0, 122, 34); -$color-status-danger: rgba(196, 0, 0); /* alpha colors */ $alpha-color-black-1: transparentize($color-black-7, 0.28); @@ -114,7 +110,6 @@ $alpha-color-red-2: transparentize($color-red, 0.84); $alpha-color-red-3: transparentize($color-red, 0.76); $alpha-color-green-1: transparentize($color-green-1, 0.08); $alpha-color-green-2: transparentize($color-green-3, 0.08); -$alpha-color-green-3: transparentize($color-green-3, 0.84); $alpha-color-yellow-1: transparentize($color-yellow, 0.84); /* gradient colors */ @@ -169,7 +164,6 @@ $COLOR_CORAL_RED: #ff444f; $COLOR_SKY_BLUE: #2196f3; $COLOR_WHITE: #ffffff; $COLOR_BLUE: #1c5ae3; - // Light theme $COLOR_LIGHT_BLACK_1: rgba(0, 0, 0, 0.8); $COLOR_LIGHT_BLACK_2: rgba(0, 0, 0, 0.4); diff --git a/packages/shared/src/styles/themes.scss b/packages/shared/src/styles/themes.scss index cba66c7f1385..10bb50d5abe9 100644 --- a/packages/shared/src/styles/themes.scss +++ b/packages/shared/src/styles/themes.scss @@ -103,7 +103,6 @@ --icon-black-plus: #{$color-black-7}; --text-status-info-blue: #{$color-blue}; --text-hint: #{$color-black-1}; - --text-orange: #{$color-orange-2}; // Purchase --purchase-main-1: #{$color-green-1}; --purchase-section-1: #{$color-green-2}; @@ -159,12 +158,8 @@ --status-default: #{$color-grey-3}; --status-adjustment: #{$color-grey-1}; --status-danger: #{$color-red-1}; - --status-danger-transparent: #{$alpha-color-red-2}; --status-success: #{$color-green-1}; - --status-success-transparent: #{$alpha-color-green-3}; --status-warning: #{$color-yellow}; - --status-warning-dark: #{$color-yellow-1}; - --status-info-transparent: #{$alpha-color-blue-1}; --status-warning-transparent: #{$alpha-color-yellow-1}; --status-transfer: #{$color-orange}; --status-info: #{$color-blue}; @@ -324,13 +319,8 @@ --status-success: #{$color-green-3}; --status-transfer: #{$color-orange}; --status-info: #{$color-blue}; - --status-warning-dark: #{$color-yellow-1}; - --status-danger-transparent: #{$alpha-color-red-1}; - --status-success-transparent: #{$alpha-color-green-3}; --status-colored-background: #{$color-white}; --status-alert-background: #{$color-yellow-3}; - --status-warning-dark: #{$color-yellow-1}; - --status-info-transparent: #{$alpha-color-blue-1}; // Transparentize --transparent-success: #{$alpha-color-green-2}; --transparent-info: #{$alpha-color-blue-1}; diff --git a/packages/shared/src/utils/cfd/cfd.ts b/packages/shared/src/utils/cfd/cfd.ts index c02c93227034..270fa6529980 100644 --- a/packages/shared/src/utils/cfd/cfd.ts +++ b/packages/shared/src/utils/cfd/cfd.ts @@ -1,7 +1,7 @@ import { DetailsOfEachMT5Loginid, GetAccountStatus, LandingCompany } from '@deriv/api-types'; import { localize } from '@deriv/translations'; import { CFD_PLATFORMS } from '../platform'; -import { Jurisdiction, JURISDICTION_MARKET_TYPES } from '../constants'; +import { AUTH_STATUS_CODES, Jurisdiction, JURISDICTION_MARKET_TYPES } from '../constants'; let CFD_text_translated: { [key: string]: () => void }; @@ -26,7 +26,6 @@ export const CFD_text: { [key: string]: string } = { all_zero_spread_demo: 'Zero Spread Demo', all_swap_free_svg: 'Swap-Free SVG', all_zero_spread_bvi: 'Zero Spread BVI', - stp: 'Financial Labuan', } as const; export const CFD_PRODUCTS_TITLE = { @@ -44,7 +43,7 @@ export const getMT5Title = (account_type: string) => { return CFD_text.financial; }; -type TProduct = 'financial' | 'synthetic' | 'swap_free' | 'zero_spread' | 'cTrader' | 'derivx' | 'stp'; +type TProduct = 'financial' | 'synthetic' | 'swap_free' | 'zero_spread' | 'cTrader' | 'derivx'; export type TPlatform = 'dxtrade' | 'mt5' | 'ctrader'; type TMarketType = 'financial' | 'synthetic' | 'gaming' | 'all' | undefined; type TShortcode = 'svg' | 'bvi' | 'labuan' | 'vanuatu' | 'malta' | 'maltainvest'; @@ -58,16 +57,6 @@ type TGetCFDAccountKey = TGetAccount & { product?: TProduct; }; -export const PRODUCT = { - SWAPFREE: 'swap_free', - ZEROSPREAD: 'zero_spread', - CTRADER: 'ctrader', - DERIVX: 'derivx', - STP: 'stp', - FINANCIAL: 'financial', - STANDARD: 'standard', -} as const; - // * mt5_login_list returns these: // market_type: "financial" | "gaming" // sub_account_type: "financial" | "financial_stp" | "swap_free" @@ -123,15 +112,18 @@ export const getCFDAccountKey = ({ } } if (market_type === 'financial') { - if (product === PRODUCT.STP && sub_account_type === 'financial_stp') { - return 'stp'; - } else if (platform === CFD_PLATFORMS.DXTRADE || sub_account_type === 'financial') { + if ( + platform === CFD_PLATFORMS.DXTRADE || + sub_account_type === 'financial' || + sub_account_type === 'financial_stp' + ) { switch (shortcode) { case 'svg': return 'financial_svg'; case 'bvi': return 'financial_bvi'; - + case 'labuan': + return 'financial_fx'; case 'vanuatu': return 'financial_v'; case 'maltainvest': @@ -545,3 +537,12 @@ export const getMT5AccountTitle = ({ account_type, jurisdiction }: TGetMT5Accoun account_type )} ${getFormattedJurisdictionCode(jurisdiction)}`; }; + +export const isPOARequiredForMT5 = (account_status: GetAccountStatus, jurisdiction_shortcode: string) => { + const { document } = account_status?.authentication || {}; + if (document?.status === AUTH_STATUS_CODES.PENDING) { + return false; + } + // @ts-expect-error as the prop verified_jurisdiction is not yet present in GetAccountStatu + return !document?.verified_jurisdiction[jurisdiction_shortcode]; +}; diff --git a/packages/shared/src/utils/constants/auth-status-codes.ts b/packages/shared/src/utils/constants/auth-status-codes.ts index 29f456ae08a3..d5f31a06d307 100644 --- a/packages/shared/src/utils/constants/auth-status-codes.ts +++ b/packages/shared/src/utils/constants/auth-status-codes.ts @@ -6,9 +6,3 @@ export const AUTH_STATUS_CODES = { EXPIRED: 'expired', SUSPECTED: 'suspected', } as const; - -export const ACCOUNT_BADGE_STATUS = { - FAILED: 'failed', - NEEDS_VERIFICATION: 'needs_verification', - PENDING: 'pending', -} as const; diff --git a/packages/shared/src/utils/constants/jurisdictions-config.ts b/packages/shared/src/utils/constants/jurisdictions-config.ts index e3ab66962d83..9fa96057c693 100644 --- a/packages/shared/src/utils/constants/jurisdictions-config.ts +++ b/packages/shared/src/utils/constants/jurisdictions-config.ts @@ -12,19 +12,13 @@ export const Platforms = Object.freeze({ }); export const DBVI_COMPANY_NAMES = { - bvi: { - name: 'Deriv (BVI) Ltd', - tnc_url: 'tnc/deriv-(bvi)-ltd.pdf', - }, + bvi: { name: 'Deriv (BVI) Ltd', tnc_url: 'tnc/deriv-(bvi)-ltd.pdf' }, + labuan: { name: 'Deriv (FX) Ltd', tnc_url: 'tnc/deriv-(fx)-ltd.pdf' }, maltainvest: { name: 'Deriv Investments (Europe) Limited', tnc_url: 'tnc/deriv-investments-(europe)-limited.pdf', }, - vanuatu: { - name: 'Deriv (V) Ltd', - tnc_url: 'tnc/general-terms.pdf', - }, - svg: { name: 'Deriv (SVG) LLC', tnc_url: '' }, + vanuatu: { name: 'Deriv (V) Ltd', tnc_url: 'tnc/general-terms.pdf' }, } as const; export const JURISDICTION_MARKET_TYPES = { diff --git a/packages/shared/src/utils/constants/mt5-login-list-status.ts b/packages/shared/src/utils/constants/mt5-login-list-status.ts index 3d3565068c46..b00717841fb4 100644 --- a/packages/shared/src/utils/constants/mt5-login-list-status.ts +++ b/packages/shared/src/utils/constants/mt5-login-list-status.ts @@ -7,9 +7,7 @@ export const MT5_ACCOUNT_STATUS = { MIGRATED_WITHOUT_POSITION: 'migrated_without_position', NEEDS_VERIFICATION: 'needs_verification', PENDING: 'pending', - POA_REQUIRED: 'poa_required', POA_PENDING: 'poa_pending', - POA_OUTDATED: 'poa_outdated', POA_VERIFIED: 'poa_verified', UNDER_MAINTENANCE: 'under_maintenance', UNAVAILABLE: 'unavailable', diff --git a/packages/shared/src/utils/types.ts b/packages/shared/src/utils/types.ts index c01a5700a05d..f80e17605f06 100644 --- a/packages/shared/src/utils/types.ts +++ b/packages/shared/src/utils/types.ts @@ -1,9 +1,5 @@ -import { ACCOUNT_BADGE_STATUS, AUTH_STATUS_CODES, EMPLOYMENT_VALUES, Jurisdiction } from './constants'; +import { EMPLOYMENT_VALUES, Jurisdiction } from './constants'; export type TBrokerCodes = typeof Jurisdiction[keyof typeof Jurisdiction]; export type TEmploymentStatus = typeof EMPLOYMENT_VALUES[keyof typeof EMPLOYMENT_VALUES]; - -export type TAuthStatusCodes = typeof AUTH_STATUS_CODES[keyof typeof AUTH_STATUS_CODES]; - -export type TAccountBadgeStatus = typeof ACCOUNT_BADGE_STATUS[keyof typeof ACCOUNT_BADGE_STATUS] | null; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 55324c0d3a40..1c0821014c29 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -200,7 +200,6 @@ const mock = (): TStores & { is_mock: boolean } => { fetchStatesList: jest.fn(), setVerificationCode: jest.fn(), updateAccountStatus: jest.fn(), - updateMT5AccountDetails: jest.fn(), is_authentication_needed: false, authentication_status: { document_status: '', @@ -405,6 +404,7 @@ const mock = (): TStores & { is_mock: boolean } => { toggleCashier: jest.fn(), setDarkMode: jest.fn(), setReportsTabIndex: jest.fn(), + setIsMT5VerificationFailedModal: jest.fn(), has_real_account_signup_ended: false, real_account_signup_target: '', real_account_signup: { @@ -535,7 +535,6 @@ const mock = (): TStores & { is_mock: boolean } => { setSelectedAccount: jest.fn(), is_low_risk_cr_eu_real: false, is_real_wallets_upgrade_on: false, - is_verification_docs_list_modal_visible: false, toggleWalletsUpgrade: jest.fn(), show_eu_related_content: false, platform_real_balance: { @@ -565,6 +564,7 @@ const mock = (): TStores & { is_mock: boolean } => { active_modal: '', data: {}, }, + openFailedVerificationModal: jest.fn(), setTogglePlatformType: jest.fn(), toggleAccountTransferModal: jest.fn(), selectAccountType: jest.fn(), @@ -590,11 +590,6 @@ const mock = (): TStores & { is_mock: boolean } => { setIsFinancialRestrictedCountry: jest.fn(), is_setup_real_account_or_go_to_demo_modal_visible: false, setIsSetupRealAccountOrGoToDemoModalVisible: jest.fn(), - setVerificationModalOpen: jest.fn(), - selected_jurisdiction_kyc_status: {}, - setSelectedJurisdictionKYCStatus: jest.fn(), - getDefaultJurisdiction: jest.fn(), - getMT5AccountKYCStatus: jest.fn(), }, notifications: { addNotificationMessage: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 234794fb70e1..8fae8f391dd2 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -105,22 +105,10 @@ type TPopulateSettingsExtensionsMenuItem = { value: (props: T) => JSX.Element; }; -type TProduct = 'swap_free' | 'zero_spread' | 'ctrader' | 'derivx' | 'financial' | 'standard' | 'stp'; +type TProduct = 'swap_free' | 'zero_spread' | 'ctrader' | 'derivx'; type TRegionAvailability = 'Non-EU' | 'EU' | 'All'; -// TODO: Remove this type once the API types are updated - -type TClientKyCStatus = { - poi_status?: typeof AUTH_STATUS_CODES[keyof typeof AUTH_STATUS_CODES]; - poa_status?: typeof AUTH_STATUS_CODES[keyof typeof AUTH_STATUS_CODES]; - valid_tin?: 0 | 1; -}; -export type TAdditionalDetailsOfEachMT5Loginid = DetailsOfEachMT5Loginid & { - product?: 'swap_free' | 'zero_spread' | 'ctrader' | 'derivx' | 'financial' | 'standard' | 'stp'; - client_kyc_status?: TClientKyCStatus; -}; - type TIconTypes = | 'Derived' | 'Financial' @@ -206,7 +194,7 @@ type TAccount = NonNullable[0] & { account_category?: 'wallet' | 'trading'; }; -type TCtraderAccountsList = TAdditionalDetailsOfEachMT5Loginid & { +type TCtraderAccountsList = DetailsOfEachMT5Loginid & { display_balance?: string; platform?: string; }; @@ -238,7 +226,7 @@ type TAccountsList = { is_disabled?: boolean | number; loginid?: string; trader_accounts_list?: DetailsOfEachMT5Loginid[]; - mt5_login_list?: TAdditionalDetailsOfEachMT5Loginid[]; + mt5_login_list?: DetailsOfEachMT5Loginid[]; title?: string; }[]; @@ -252,7 +240,7 @@ export type TActiveAccount = TAccount & { token: string; }; -export type TTradingPlatformAvailableAccount = { +type TTradingPlatformAvailableAccount = { market_type: 'financial' | 'gaming' | 'all'; name: string; requirements: { @@ -265,21 +253,11 @@ export type TTradingPlatformAvailableAccount = { }; signup: string[]; }; - client_kyc_status?: TClientKyCStatus; shortcode?: DetailsOfEachMT5Loginid['landing_company_short']; sub_account_type: string; max_count?: number; available_count?: number; - //TODO: remove once api-types for default jurisdiction project - product?: TProduct; - is_default_jurisdiction?: string; - licence_number?: string; - regulatory_authority?: string; - instruments?: string[]; - product_details?: { - max_leverage?: string; - min_spread?: string; - }; + product: TProduct; }; type TAvailableCFDAccounts = { @@ -420,14 +398,6 @@ type RealAccountSignupSettings = { previous_currency: string; success_message: string; }; -const AUTH_STATUS_CODES = { - NONE: 'none', - PENDING: 'pending', - REJECTED: 'rejected', - VERIFIED: 'verified', - EXPIRED: 'expired', - SUSPECTED: 'suspected', -} as const; export type TClientStore = { fetchStatesList: () => Promise; @@ -525,8 +495,8 @@ export type TClientStore = { responseMt5LoginList: ({ mt5_login_list, }: { - mt5_login_list: TAdditionalDetailsOfEachMT5Loginid[]; - }) => TAdditionalDetailsOfEachMT5Loginid[]; + mt5_login_list: DetailsOfEachMT5Loginid[]; + }) => DetailsOfEachMT5Loginid[]; responseTradingPlatformAccountsList: ({ trading_platform_accounts, }: { @@ -563,10 +533,9 @@ export type TClientStore = { email: string; setVerificationCode: (code: string, action: string) => void; updateAccountStatus: () => Promise; - updateMT5AccountDetails: () => Promise; is_authentication_needed: boolean; authentication_status: TAuthenticationStatus; - mt5_login_list: TAdditionalDetailsOfEachMT5Loginid[]; + mt5_login_list: DetailsOfEachMT5Loginid[]; logout: () => Promise; should_allow_authentication: boolean; should_allow_poinc_authentication: boolean; @@ -614,7 +583,7 @@ export type TClientStore = { /** @deprecated Use `useCurrencyConfig` or `useCurrentCurrencyConfig` from `@deriv/hooks` package instead. */ is_crypto: (currency?: string) => boolean; ctrader_accounts_list: TCtraderAccountsList[]; - dxtrade_accounts_list: (TAdditionalDetailsOfEachMT5Loginid & { account_id?: string })[]; + dxtrade_accounts_list: (DetailsOfEachMT5Loginid & { account_id?: string })[]; default_currency: string; resetVirtualBalance: () => Promise; has_enabled_two_fa: boolean; @@ -821,6 +790,7 @@ type TUiStore = { setIsVerificationModalVisible: (value: boolean) => void; setIsFromSuccessDepositModal: (value: boolean) => void; setIsVerificationSubmitted: (value: boolean) => void; + setIsMT5VerificationFailedModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; setPurchaseState: (index: number) => void; simple_duration_unit: string; @@ -1161,11 +1131,10 @@ type TTradersHubStore = { setTogglePlatformType: (platform_type: string) => void; is_demo: boolean; is_real: boolean; - is_verification_docs_list_modal_visible: boolean; selectRegion: (region: string) => void; closeAccountTransferModal: () => void; toggleRegulatorsCompareModal: () => void; - setVerificationModalOpen: (value: boolean) => void; + openFailedVerificationModal: (selected_account_type: Record | string) => void; modal_data: TModalData; financial_restricted_countries: boolean; selected_account_type: string; @@ -1206,10 +1175,6 @@ type TTradersHubStore = { setIsCFDRestrictedCountry: (value: boolean) => void; setIsFinancialRestrictedCountry: (value: boolean) => void; setIsSetupRealAccountOrGoToDemoModalVisible: (value: boolean) => void; - selected_jurisdiction_kyc_status: Record; - setSelectedJurisdictionKYCStatus: (value: Record) => void; - getDefaultJurisdiction: () => string; - getMT5AccountKYCStatus: () => void; }; type TContractReplay = { diff --git a/packages/trader/package.json b/packages/trader/package.json index 6070c1808689..0b640dba8889 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -102,7 +102,7 @@ "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", "@deriv/utils": "^1.0.0", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@lottiefiles/dotlottie-react": "0.7.2", "@types/react-loadable": "^5.5.6", "classnames": "^2.2.6", diff --git a/packages/wallets/package.json b/packages/wallets/package.json index ac67476aaef2..68fecfd2ea8a 100644 --- a/packages/wallets/package.json +++ b/packages/wallets/package.json @@ -19,7 +19,7 @@ "@deriv-com/ui": "1.36.4", "@deriv-com/utils": "^0.0.36", "@deriv/api-v2": "^1.0.0", - "@deriv/quill-icons": "1.23.14", + "@deriv/quill-icons": "1.23.3", "@deriv/utils": "^1.0.0", "@tanstack/react-table": "^8.10.3", "@zxcvbn-ts/core": "^3.0.4",