From 60d370ecc1f30df7858f1d7cec54ed40249831e8 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari Date: Mon, 26 Jun 2023 13:44:40 +0400 Subject: [PATCH] Revert "Revert "Yaswanth/88554/remove connect from account (#8551)" (#9132)" This reverts commit eed5d60ad7221eeadf09f45f479a6af37ec98cd2. --- packages/account/package.json | 1 + packages/account/src/App.tsx | 16 +- .../Routes/__tests__/binary-link.spec.tsx | 6 - .../Routes/__tests__/binary-routes.spec.tsx | 6 - .../__tests__/account-limits.spec.tsx | 395 +++++++---- .../account-limits/account-limits.tsx | 632 +++++++++--------- .../api-token.spec.js} | 115 +++- .../src/Components/api-token/api-token.tsx | 18 +- .../__tests__/icon-with-message.spec.tsx | 46 +- .../icon-with-message/icon-with-message.tsx | 26 +- .../__tests__/self-exclusion.spec.tsx | 188 ++++-- .../self-exclusion/self-exclusion.tsx | 49 +- packages/account/src/Containers/account.jsx | 80 +-- .../src/Containers/reset-trading-password.jsx | 44 +- packages/account/src/Containers/routes.jsx | 33 +- .../financial-assessment.jsx | 65 +- .../TradingAssessment/trading-assessment.jsx | 13 +- .../LanguageSettings/language-settings.tsx | 21 +- .../__tests__/personal-details.spec.js | 43 +- .../PersonalDetails/personal-details.jsx | 113 +--- .../Security/AccountClosed/account-closed.jsx | 12 +- .../Security/AccountLimits/account-limits.jsx | 13 +- .../Sections/Security/ApiToken/api-token.jsx | 4 +- .../__tests__/closing-account-reason.spec.js | 26 +- .../ClosingAccount/closing-account-reason.jsx | 16 +- .../ClosingAccount/closing-account-steps.jsx | 10 +- .../Security/LoginHistory/login-history.jsx | 17 +- .../Security/Passwords/deriv-email.jsx | 15 +- .../Sections/Security/Passwords/passwords.jsx | 76 +-- .../Security/SelfExclusion/self-exclusion.jsx | 19 +- .../two-factor-authentication.jsx | 39 +- .../ProofOfAddress/proof-of-address-form.jsx | 43 +- .../ProofOfAddress/proof-of-address.jsx | 37 +- .../ProofOfIdentity/proof-of-identity.jsx | 53 +- .../__test__/proof-of-ownership.spec.js | 83 ++- .../ProofOfOwnership/proof-of-ownership.jsx | 24 +- packages/account/src/Stores/connect.js | 31 - packages/account/src/Stores/index.ts | 26 - packages/account/src/Stores/init-store.js | 12 - packages/stores/src/mockStore.ts | 98 ++- packages/stores/types.ts | 55 +- 41 files changed, 1335 insertions(+), 1284 deletions(-) rename packages/account/src/Components/api-token/{__tests__/api-token.spec.tsx => _tests_/api-token.spec.js} (84%) delete mode 100644 packages/account/src/Stores/connect.js delete mode 100644 packages/account/src/Stores/index.ts delete mode 100644 packages/account/src/Stores/init-store.js diff --git a/packages/account/package.json b/packages/account/package.json index cfd2445d9f8d..6feecfbc8024 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -31,6 +31,7 @@ "@deriv/api-types": "^1.0.94", "@deriv/components": "^1.0.0", "@deriv/shared": "^1.0.0", + "@deriv/stores":"^1.0.0", "@deriv/translations": "^1.0.0", "bowser": "^2.9.0", "classnames": "^2.2.6", diff --git a/packages/account/src/App.tsx b/packages/account/src/App.tsx index 0a666cb80bf1..f658afe8facd 100644 --- a/packages/account/src/App.tsx +++ b/packages/account/src/App.tsx @@ -1,27 +1,27 @@ import React from 'react'; import Routes from './Containers/routes'; import ResetTradingPassword from './Containers/reset-trading-password'; -import { MobxContentProvider } from './Stores/connect'; -import initStore from './Stores/init-store'; -import TCoreStore from './Stores/index'; +import { setWebsocket } from '@deriv/shared'; +import { StoreProvider } from '@deriv/stores'; +import { TCoreStores } from '@deriv/stores/types'; -// TODO: add correct types for stores and WS after implementing them +// TODO: add correct types for WS after implementing them type TAppProps = { passthrough: { - root_store: TCoreStore; + root_store: TCoreStores; WS: Record; }; }; const App = ({ passthrough }: TAppProps) => { const { root_store, WS } = passthrough; - initStore(root_store, WS); + setWebsocket(WS); return ( - + - + ); }; diff --git a/packages/account/src/Components/Routes/__tests__/binary-link.spec.tsx b/packages/account/src/Components/Routes/__tests__/binary-link.spec.tsx index 450cbb9b6aa3..7c47af226cd7 100644 --- a/packages/account/src/Components/Routes/__tests__/binary-link.spec.tsx +++ b/packages/account/src/Components/Routes/__tests__/binary-link.spec.tsx @@ -6,12 +6,6 @@ import { PlatformContext } from '@deriv/shared'; import { findRouteByPath } from '../helpers'; import BinaryLink from '../binary-link'; -jest.mock('Stores/connect', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: () => Component => Component, -})); - jest.mock('../helpers', () => ({ findRouteByPath: jest.fn(() => '/test/path'), normalizePath: jest.fn(() => '/test/path'), diff --git a/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx b/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx index c57920aaecb8..3938f7960aea 100644 --- a/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx +++ b/packages/account/src/Components/Routes/__tests__/binary-routes.spec.tsx @@ -5,12 +5,6 @@ import { render, screen } from '@testing-library/react'; import { PlatformContext } from '@deriv/shared'; import BinaryRoutes from '../binary-routes'; -jest.mock('Stores/connect', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: () => Component => Component, -})); - jest.mock('../route-with-sub-routes', () => jest.fn(() =>
RouteWithSubRoutes
)); jest.mock('Constants/routes-config', () => () => [{}]); diff --git a/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx b/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx index bc0f8b7f825c..07c98028a783 100644 --- a/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx +++ b/packages/account/src/Components/account-limits/__tests__/account-limits.spec.tsx @@ -3,12 +3,7 @@ import { screen, render } from '@testing-library/react'; import { formatMoney, isDesktop, isMobile, PlatformContext } from '@deriv/shared'; import AccountLimits from '../account-limits'; import { BrowserRouter } from 'react-router-dom'; - -jest.mock('Stores/connect.js', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: () => (Component: React.ReactElement) => Component, -})); +import { StoreProvider, mockStore } from '@deriv/stores'; jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); @@ -18,6 +13,11 @@ jest.mock('@deriv/components', () => { Loading: jest.fn(() => 'mockedLoading'), }; }); +jest.mock('@deriv/shared/src/services/ws-methods', () => ({ + __esModule: true, // this property makes it work, + default: 'mockedDefaultExport', + useWS: () => undefined, +})); jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), @@ -31,110 +31,133 @@ jest.mock('Components/load-error-message', () => jest.fn(() => 'mockedLoadErrorM jest.mock('../account-limits-footer', () => jest.fn(() => 'mockedAccountLimitsFooter')); describe('', () => { - const props: React.ComponentProps = { - currency: 'AUD', - is_fully_authenticated: true, - is_switching: false, - is_virtual: false, + let store = mockStore({}); + const props = { overlay_ref: document.createElement('div'), - getLimits: jest.fn(() => Promise.resolve({ data: {} })), - account_limits: { - account_balance: 300000, - daily_transfers: { - dxtrade: { - allowed: 12, - available: 12, - }, - internal: { - allowed: 10, - available: 10, - }, - mt5: { - allowed: 10, - available: 10, - }, - }, - lifetime_limit: 13907.43, - market_specific: { - commodities: [ - { - name: 'Commodities', - payout_limit: 5000, - profile_name: 'moderate_risk', - turnover_limit: 50000, - }, - ], - cryptocurrency: [ - { - name: 'Cryptocurrencies', - payout_limit: 100.0, - profile_name: 'extreme_risk', - turnover_limit: 1000.0, - }, - ], - forex: [ - { - name: 'Smart FX', - payout_limit: 5000, - profile_name: 'moderate_risk', - turnover_limit: 50000, - }, - { - name: 'Major Pairs', - payout_limit: 20000, - profile_name: 'medium_risk', - turnover_limit: 100000, - }, - { - name: 'Minor Pairs', - payout_limit: 5000, - profile_name: 'moderate_risk', - turnover_limit: 50000, + }; + const mock = { + client: { + currency: 'AUD', + is_fully_authenticated: true, + is_switching: false, + is_virtual: false, + getLimits: jest.fn(() => Promise.resolve({ data: {} })), + account_limits: { + account_balance: 300000, + daily_transfers: { + dxtrade: { + allowed: 12, + available: 12, }, - ], - indices: [ - { - name: 'Stock Indices', - payout_limit: 20000, - profile_name: 'medium_risk', - turnover_limit: 100000, + internal: { + allowed: 10, + available: 10, }, - ], - synthetic_index: [ - { - name: 'Synthetic Indices', - payout_limit: 50000, - profile_name: 'low_risk', - turnover_limit: 500000, + mt5: { + allowed: 10, + available: 10, }, - ], + }, + lifetime_limit: 13907.43, + market_specific: { + commodities: [ + { + name: 'Commodities', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + ], + cryptocurrency: [ + { + name: 'Cryptocurrencies', + payout_limit: 100.0, + profile_name: 'extreme_risk', + turnover_limit: 1000.0, + }, + ], + forex: [ + { + name: 'Smart FX', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + { + name: 'Major Pairs', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + { + name: 'Minor Pairs', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + ], + indices: [ + { + name: 'Stock Indices', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + ], + synthetic_index: [ + { + name: 'Synthetic Indices', + payout_limit: 50000, + profile_name: 'low_risk', + turnover_limit: 500000, + }, + ], + }, + num_of_days: 30, + num_of_days_limit: 13907.43, + open_positions: 100, + payout: 50000, + remainder: 13907.43, + withdrawal_for_x_days_monetary: 0, + withdrawal_since_inception_monetary: 0, }, - num_of_days: 30, - num_of_days_limit: 13907.43, - open_positions: 100, - payout: 50000, - remainder: 13907.43, - withdrawal_for_x_days_monetary: 0, - withdrawal_since_inception_monetary: 0, }, }; - + store = mockStore(mock); it('should render the Loading component if is_switching is true', () => { - render(); + store = mockStore({ + client: { + is_switching: true, + }, + }); + render( + + + + ); expect(screen.getByText('mockedLoading')).toBeInTheDocument(); }); it('should render DemoMessage component if is_virtual is true', () => { - render(); + store = mockStore({ + client: { + is_switching: false, + is_virtual: true, + }, + }); + render( + + + + ); expect(screen.queryByTestId('dt_account_demo_message_wrapper')).toHaveClass('account__demo-message-wrapper'); expect(screen.getByText('mockedDemoMessage')).toBeInTheDocument(); }); it('should render LoadErrorMessage component if there is api_initial_load_error', () => { - render( - ', () => { num_of_days_limit: '', remainder: '', withdrawal_since_inception_monetary: '', - }} - /> + }, + is_switching: false, + is_virtual: false, + }, + }); + render( + + + ); expect(screen.getByText('mockedLoadErrorMessage')).toBeInTheDocument(); }); it('should render AccountLimits component', () => { - render(); + store = mockStore(mock); + render( + + + + ); expect(screen.queryByTestId('account_limits_data')).toBeInTheDocument(); }); it('should call setIsPopupOverlayShown fn ', () => { + store = mockStore(mock); const setIsPopupOverlayShown = jest.fn(); - render(); + render( + + + + ); expect(setIsPopupOverlayShown).toHaveBeenCalledTimes(1); }); it('should render Loading component if is_loading is true', () => { - render(); + render( + + + + ); expect(screen.queryByTestId('account_limits_data')).toBeInTheDocument(); }); it('should render AccountLimitsArticle component if should_show_article is true and is_from_derivgo is false in mobile mode', () => { (isMobile as jest.Mock).mockReturnValue(true); (isDesktop as jest.Mock).mockReturnValue(false); - render(); + render( + + + + ); expect(screen.getByRole('heading', { name: /account limits/i })).toBeInTheDocument(); expect( screen.queryByText(/to learn more about trading limits and how they apply, please go to the/i) @@ -180,9 +228,18 @@ describe('', () => { }); it('should render AccountLimitsArticle component if should_show_article is true and is_from_derivgo is true in mobile mode', () => { + store = mockStore({ + common: { + is_from_derivgo: true, + }, + }); (isMobile as jest.Mock).mockReturnValue(true); (isDesktop as jest.Mock).mockReturnValue(false); - render(); + render( + + + + ); expect(screen.getByRole('heading', { name: /account limits/i })).toBeInTheDocument(); expect( screen.queryByText(/to learn more about trading limits and how they apply, please go to the/i) @@ -190,14 +247,23 @@ describe('', () => { }); it('should not render AccountLimitsArticle component if should_show_article is false', () => { + store = mockStore(mock); (isMobile as jest.Mock).mockReturnValue(true); (isDesktop as jest.Mock).mockReturnValue(false); - render(); + render( + + + + ); expect(screen.queryByText('/account limits/i')).not.toBeInTheDocument(); }); it('should render Trading limits table and its trading limits contents properly', () => { - render(); + render( + + + + ); expect(screen.queryByTestId('account_limits_data')).toBeInTheDocument(); expect( @@ -218,13 +284,17 @@ describe('', () => { }); it('should render Maximum number of open positions- table cell and its contents properly', () => { - render(); + render( + + + + ); expect( screen.getByRole('cell', { name: /\*maximum number of open positions/i, }) ).toBeInTheDocument(); - const { open_positions } = props.account_limits; + const { open_positions } = store.client.account_limits; expect( screen.getByRole('cell', { name: open_positions?.toString(), @@ -233,13 +303,21 @@ describe('', () => { }); it('should call formatMoney', () => { - render(); - const { account_balance } = props.account_limits; - expect(formatMoney).toHaveBeenCalledWith(props.currency, account_balance, true); + render( + + + + ); + const { account_balance } = store.client.account_limits; + expect(formatMoney).toHaveBeenCalledWith(store.client.currency, account_balance, true); }); it('should render Trading limits table and its maximum daily turnover contents properly', () => { - render(); + render( + + + + ); expect(screen.queryByTestId('trading_daily_turnover_table')).toBeInTheDocument(); expect( screen.getByRole('columnheader', { @@ -259,12 +337,20 @@ describe('', () => { }); it('should not render withdrawal_limits_table is_app_settings is true', () => { - render(); + render( + + + + ); expect(screen.queryByTestId('withdrawal_limits_table')).not.toBeInTheDocument(); }); it('should render withdrawal_limits_table is_app_settings is false', () => { - render(); + render( + + + + ); expect(screen.queryByTestId('withdrawal_limits_table')).toBeInTheDocument(); expect( screen.getByRole('columnheader', { @@ -274,12 +360,20 @@ describe('', () => { }); it('withdrawal_limits_table should have a Limits header if is_fully_authenticated is true', () => { - render(); + render( + + + + ); expect(screen.getByTestId('withdrawal_limits_table')).toHaveTextContent('Limit'); }); it('show show withdrawal limit lifted message if is_fully_authenticated is true', () => { - render(); + render( + + + + ); expect( screen.getByRole('cell', { @@ -289,10 +383,17 @@ describe('', () => { }); it('withdrawal_limits_table should show `Total withdrawal limit` if is_fully_authenticated is false and is_appstore is true', () => { + store = mockStore({ + client: { + is_fully_authenticated: false, + }, + }); render( - + + + ); @@ -300,19 +401,33 @@ describe('', () => { }); it('withdrawal_limits_table should show `Total withdrawal allowed` when is_fully_authenticated is false and is_appstore is true', () => { + store = mockStore({ + client: { + is_fully_authenticated: false, + }, + }); render( - + + + ); expect(screen.getByText(/total withdrawal allowed/i)).toBeInTheDocument(); }); it('withdrawal_limits_table should show the verfiy button when is_fully_authenticated is false and is_appstore is true', () => { + store = mockStore({ + client: { + is_fully_authenticated: false, + }, + }); render( - + + + ); @@ -322,34 +437,48 @@ describe('', () => { name: /verify/i, }) ).toHaveAttribute('href', '/account/proof-of-identity'); - const { num_of_days_limit } = props.account_limits; - expect(formatMoney).toHaveBeenCalledWith(props.currency, num_of_days_limit, true); + const { num_of_days_limit } = store.client.account_limits; + expect(formatMoney).toHaveBeenCalledWith(store.client.currency, num_of_days_limit, true); }); it('withdrawal_limits_table should show total withdrawn and withdrawn remaining details', () => { + store = mockStore({ + client: { + is_fully_authenticated: false, + }, + }); render( - + + + ); - const { withdrawal_since_inception_monetary, remainder } = props.account_limits; + const { withdrawal_since_inception_monetary, remainder } = store.client.account_limits; expect(screen.getByText(/total withdrawn/i)).toBeInTheDocument(); - expect(formatMoney).toHaveBeenCalledWith(props.currency, withdrawal_since_inception_monetary, true); + expect(formatMoney).toHaveBeenCalledWith(store.client.currency, withdrawal_since_inception_monetary, true); expect(screen.getByText(/maximum withdrawal remaining/i)).toBeInTheDocument(); - expect(formatMoney).toHaveBeenCalledWith(props.currency, remainder, true); + expect(formatMoney).toHaveBeenCalledWith(store.client.currency, remainder, true); }); it('should show limit_notice message when is_appstore is true and is_fully_authenticated is false in mobile mode', () => { + store = mockStore({ + client: { + is_fully_authenticated: false, + }, + }); (isMobile as jest.Mock).mockReturnValue(true); (isDesktop as jest.Mock).mockReturnValue(false); render( - + + + ); @@ -357,12 +486,19 @@ describe('', () => { }); it('should not show limit_notice message when is_appstore is false and is_fully_authenticated is false', () => { + store = mockStore({ + client: { + is_fully_authenticated: false, + }, + }); (isMobile as jest.Mock).mockReturnValue(false); (isDesktop as jest.Mock).mockReturnValue(true); render( - + + + ); @@ -372,9 +508,14 @@ describe('', () => { }); it('should show AccountLimitsArticle when should_show_article and isDesktop is true', () => { + store = mockStore(mock); (isMobile as jest.Mock).mockReturnValue(false); (isDesktop as jest.Mock).mockReturnValue(true); - render(); + render( + + + + ); expect(screen.getByRole('heading', { name: /account limits/i })).toBeInTheDocument(); expect(screen.getByText(/these are default limits that we apply to your accounts\./i)).toBeInTheDocument(); expect( @@ -390,7 +531,11 @@ describe('', () => { it('should show AccountLimitsFooter if footer_ref is passed', () => { const footer = React.createRef(); - render(); + render( + + + + ); expect(screen.getByText(/mockedaccountlimitsfooter/i)).toBeInTheDocument(); }); }); diff --git a/packages/account/src/Components/account-limits/account-limits.tsx b/packages/account/src/Components/account-limits/account-limits.tsx index 78ad00173a5a..0bb41406c1ab 100644 --- a/packages/account/src/Components/account-limits/account-limits.tsx +++ b/packages/account/src/Components/account-limits/account-limits.tsx @@ -12,38 +12,13 @@ import AccountLimitsFooter from './account-limits-footer'; import AccountLimitsOverlay from './account-limits-overlay'; import AccountLimitsTableCell from './account-limits-table-cell'; import AccountLimitsTableHeader from './account-limits-table-header'; -import AccountLimitsTurnoverLimitRow, { TAccountLimitsCollection } from './account-limits-turnover-limit-row'; +import AccountLimitsTurnoverLimitRow from './account-limits-turnover-limit-row'; +import { observer, useStore } from '@deriv/stores'; import { FormikValues } from 'formik'; type TAccountLimits = { - account_limits: { - api_initial_load_error?: string; - open_positions?: React.ReactNode; - account_balance: string | number; - daily_transfers?: object; - payout: string | number; - lifetime_limit?: number; - market_specific: { - commodities: TAccountLimitsCollection[]; - cryptocurrency: TAccountLimitsCollection[]; - forex: TAccountLimitsCollection[]; - indices: TAccountLimitsCollection[]; - synthetic_index: TAccountLimitsCollection[]; - }; - num_of_days?: number; - num_of_days_limit: string | number; - remainder: string | number; - withdrawal_for_x_days_monetary?: number; - withdrawal_since_inception_monetary: string | number; - }; - currency: string; footer_ref?: React.RefObject; is_app_settings?: boolean; - getLimits: () => Promise<{ data: object }>; - is_fully_authenticated: boolean; - is_from_derivgo?: boolean; - is_switching: boolean; - is_virtual: boolean; overlay_ref: HTMLDivElement; setIsOverlayShown?: (is_overlay_shown?: boolean) => void; setIsPopupOverlayShown?: (is_popup_overlay_shown: boolean) => void; @@ -51,327 +26,332 @@ type TAccountLimits = { should_show_article?: boolean; }; -const AccountLimits = ({ - account_limits, - currency, - footer_ref, - getLimits, - is_app_settings, - is_fully_authenticated, - is_switching, - is_virtual, - overlay_ref, - is_from_derivgo, - setIsOverlayShown: setIsPopupOverlayShown, - should_bypass_scrollbars, - should_show_article, -}: TAccountLimits) => { - const isMounted = useIsMounted(); - const [is_loading, setLoading] = React.useState(false); - const [is_overlay_shown, setIsOverlayShown] = React.useState(false); - const { is_appstore } = React.useContext(PlatformContext); +const AccountLimits = observer( + ({ + footer_ref, + is_app_settings, + overlay_ref, + setIsOverlayShown: setIsPopupOverlayShown, + should_bypass_scrollbars, + should_show_article = true, + }: TAccountLimits) => { + const { client, common } = useStore(); + const { account_limits, currency, getLimits, is_fully_authenticated, is_virtual, is_switching } = client; + const { is_from_derivgo } = common; + const isMounted = useIsMounted(); + const [is_loading, setLoading] = React.useState(false); + const [is_overlay_shown, setIsOverlayShown] = React.useState(false); + const { is_appstore } = React.useContext(PlatformContext); - React.useEffect(() => { - if (is_virtual) { - setLoading(false); - } else { - getLimits().then(() => { - if (isMounted()) setLoading(false); - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + React.useEffect(() => { + if (is_virtual) { + setLoading(false); + } else { + getLimits().then(() => { + if (isMounted()) setLoading(false); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - React.useEffect(() => { - if (!is_virtual && account_limits && is_loading) { - setLoading(false); - } - }, [account_limits, is_virtual, is_loading]); + React.useEffect(() => { + if (!is_virtual && account_limits && is_loading) { + setLoading(false); + } + }, [account_limits, is_virtual, is_loading]); - React.useEffect(() => { - if (typeof setIsPopupOverlayShown === 'function') { - setIsPopupOverlayShown(is_overlay_shown); - } - }, [is_overlay_shown, setIsPopupOverlayShown]); + React.useEffect(() => { + if (typeof setIsPopupOverlayShown === 'function') { + setIsPopupOverlayShown(is_overlay_shown); + } + }, [is_overlay_shown, setIsPopupOverlayShown]); - const toggleOverlay = () => setIsOverlayShown(!is_overlay_shown); + const toggleOverlay = () => setIsOverlayShown(!is_overlay_shown); - if (is_switching) { - return ; - } + if (is_switching) { + return ; + } - if (is_virtual) { - return ( -
- -
- ); - } + if (is_virtual) { + return ( +
+ +
+ ); + } - const { - api_initial_load_error, - open_positions, - account_balance, - payout, - market_specific, - num_of_days_limit, - remainder, - withdrawal_since_inception_monetary, - }: TAccountLimits['account_limits'] = account_limits; + const { + api_initial_load_error, + open_positions, + account_balance, + payout, + market_specific, + num_of_days_limit, + remainder, + withdrawal_since_inception_monetary, + } = account_limits; - if (api_initial_load_error) { - return ; - } + if (api_initial_load_error) { + return ; + } - if (is_switching || is_loading) { - return ; - } + if (is_switching || is_loading) { + return ; + } - const { commodities, forex, indices, synthetic_index } = { ...market_specific }; - const forex_ordered = forex - ?.slice() - .sort((a: FormikValues, b: FormikValues) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)); - const derived_ordered = synthetic_index - ?.slice() - .sort((a: FormikValues, b: FormikValues) => (a.level > b.level ? 1 : -1)); + const { commodities, forex, indices, synthetic_index } = { ...market_specific }; + const forex_ordered = forex + ?.slice() + .sort((a: FormikValues, b: FormikValues) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)); + const derived_ordered = synthetic_index + ?.slice() + .sort((a: FormikValues, b: FormikValues) => (a.level > b.level ? 1 : -1)); - const context_value: TAccountLimitsContext = { - currency, - footer_ref, - overlay_ref, - toggleOverlay, - }; + const context_value: TAccountLimitsContext = { + currency, + footer_ref, + overlay_ref, + toggleOverlay, + }; - return ( - -
-
- {should_show_article && isMobile() && } -
- - - - - - - - - - - - - - - ( - - )} - > - - - {open_positions} - - - ( - - )} - > - - - - {/* null or 0 are expected form BE when max balance limit is not set */} - {account_balance ? ( - formatMoney(currency, account_balance, true) - ) : ( - - )} - - - - ( - - )} - > - - - - {formatMoney(currency, payout, true)} - - - - - - - - - -
- - - - ( - - )} - > - - - - - - - - - - - - - -
- {/* We only show "Withdrawal Limits" on account-wide settings pages. */} - {!is_app_settings && ( - - - - - - - - {is_fully_authenticated && ( - - - + return ( + +
+
+ {should_show_article && isMobile() && ( + + )} +
+ +
+ + + + + + + + + + + + + ( + )} - - - - {is_fully_authenticated ? ( + > + + + + {open_positions} + + + + ( + + )} + > + + + + {/* null or 0 are expected form BE when max balance limit is not set */} + {account_balance ? ( + formatMoney(currency, account_balance, true) + ) : ( + + )} + + + + ( + + )} + > + + + + {formatMoney(currency, payout, true)} + + + + + + + + + +
+ + + + ( + + )} + > + + + + + + + + + + + + + +
+ {/* We only show "Withdrawal Limits" on account-wide settings pages. */} + {!is_app_settings && ( + + + - - - - {localize( - 'Your account is fully authenticated and your withdrawal limits have been lifted.' - )} - - - - + + + + {is_fully_authenticated && ( + + + + )} - ) : ( - + + + {is_fully_authenticated ? ( - {is_appstore ? ( - - ) : ( - - )} - {is_appstore && !is_fully_authenticated && ( - - - {localize( - 'To increase limit please verify your identity' - )} - - + + + {localize( + 'Your account is fully authenticated and your withdrawal limits have been lifted.' + )} + + + + + + ) : ( + + + + {is_appstore ? ( + + ) : ( + + )} + {is_appstore && !is_fully_authenticated && ( + - {localize('Verify')} + {localize( + 'To increase limit please verify your identity' + )} - - - )} - - - {formatMoney(currency, num_of_days_limit, true)} - - - - - - - - {formatMoney( - currency, - withdrawal_since_inception_monetary, - true - )} - - - - - - - - {formatMoney(currency, remainder, true)} - - - - )} - -
- {(!is_appstore || isMobile()) && ( -
- - {is_fully_authenticated ? ( - - ) : ( - + + + {localize('Verify')} + + + + )} + + + {formatMoney(currency, num_of_days_limit, true)} + + + + + + + + {formatMoney( + currency, + withdrawal_since_inception_monetary, + true + )} + + + + + + + + {formatMoney(currency, remainder, true)} + + + )} - -
- )} -
- )} -
+ + + {(!is_appstore || isMobile()) && ( +
+ + {is_fully_authenticated ? ( + + ) : ( + + )} + +
+ )} + + )} + +
+ {should_show_article && isDesktop() && } + {footer_ref && } + {is_overlay_shown && overlay_ref && }
- {should_show_article && isDesktop() && } - {footer_ref && } - {is_overlay_shown && overlay_ref && } - -
-
- ); -}; + + + ); + } +); export default AccountLimits; diff --git a/packages/account/src/Components/api-token/__tests__/api-token.spec.tsx b/packages/account/src/Components/api-token/_tests_/api-token.spec.js similarity index 84% rename from packages/account/src/Components/api-token/__tests__/api-token.spec.tsx rename to packages/account/src/Components/api-token/_tests_/api-token.spec.js index 0c6ff192bcd4..1243918e1153 100644 --- a/packages/account/src/Components/api-token/__tests__/api-token.spec.tsx +++ b/packages/account/src/Components/api-token/_tests_/api-token.spec.js @@ -1,7 +1,8 @@ import React from 'react'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { getPropertyValue, isDesktop, isMobile, useIsMounted } from '@deriv/shared'; -import ApiToken, { TApiToken } from '../api-token'; +import { getPropertyValue, isDesktop, isMobile, useIsMounted, WS } from '@deriv/shared'; +import ApiToken from '../api-token'; +import { StoreProvider, mockStore } from '@deriv/stores'; jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), @@ -10,6 +11,29 @@ jest.mock('@deriv/shared', () => ({ isMobile: jest.fn(() => false), useIsMounted: jest.fn().mockImplementation(() => () => true), })); +jest.mock('@deriv/shared/src/services/ws-methods', () => ({ + __esModule: true, // this property makes it work, + default: 'mockedDefaultExport', + WS: { + apiToken: jest.fn(() => + Promise.resolve({ + api_token: { + tokens: [], + }, + }) + ), + authorized: { + apiToken: jest.fn(() => + Promise.resolve({ + api_token: { + tokens: [], + }, + }) + ), + }, + }, + useWS: () => undefined, +})); jest.mock('@deriv/components', () => ({ ...jest.requireActual('@deriv/components'), @@ -48,13 +72,18 @@ describe('', () => { const your_access_description = "To access your mobile apps and other third-party apps, you'll first need to generate an API token."; - const mock_props: TApiToken = { + let store = mockStore(); + store = mockStore({ + client: { + is_switching: false, + }, + }); + const mock_props = { footer_ref: undefined, is_app_settings: false, - is_switching: false, overlay_ref: undefined, setIsOverlayShown: jest.fn(), - ws: { + WS: { apiToken: jest.fn(() => Promise.resolve({ api_token: { @@ -75,9 +104,13 @@ describe('', () => { }; it('should render ApiToken component without app_settings and footer', async () => { - render(); + render( + + + + ); - expect(mock_props.ws.authorized.apiToken).toHaveBeenCalled(); + expect(WS.authorized.apiToken).toHaveBeenCalled(); expect(await screen.findByText(admin_scope_description)).toBeInTheDocument(); expect(await screen.findByText(admin_scope_note)).toBeInTheDocument(); @@ -95,9 +128,13 @@ describe('', () => { it('should not render ApiToken component if is not mounted', () => { useIsMounted.mockImplementationOnce(() => () => false); - render(); + render( + + + + ); - expect(mock_props.ws.authorized.apiToken).toHaveBeenCalled(); + expect(WS.authorized.apiToken).toHaveBeenCalled(); expect(screen.getByText('Loading')).toBeInTheDocument(); expect(screen.queryByText(admin_scope_description)).not.toBeInTheDocument(); @@ -117,7 +154,11 @@ describe('', () => { isMobile.mockReturnValueOnce(true); isDesktop.mockReturnValueOnce(false); - render(); + render( + + + + ); expect(await screen.findByText(admin_scope_description)).toBeInTheDocument(); expect(await screen.findByText(admin_scope_note)).toBeInTheDocument(); @@ -134,7 +175,11 @@ describe('', () => { it('should render ApiToken component with app_settings', async () => { mock_props.is_app_settings = true; - render(); + render( + + + + ); await waitFor(() => { expect(screen.queryByText(our_access_description)).not.toBeInTheDocument(); @@ -150,7 +195,11 @@ describe('', () => { mock_props.footer_ref = footer_portal_root_el; mock_props.overlay_ref = overlay_portal_root_el; - render(); + render( + + + + ); expect(await screen.findByText(learn_more_title)).toBeInTheDocument(); expect(screen.queryByText(our_access_description)).not.toBeInTheDocument(); @@ -166,14 +215,18 @@ describe('', () => { }); it('should choose checkbox, enter a valid value and create token', async () => { - render(); + render( + + + + ); expect(screen.queryByText('New token name')).not.toBeInTheDocument(); - const checkboxes: HTMLInputElement[] = await screen.findAllByRole('checkbox'); + const checkboxes = await screen.findAllByRole('checkbox'); const create_btn = await screen.findByRole('button'); - const read_checkbox = checkboxes.find(card => card.name === 'read') as HTMLInputElement; // Typecasting it since find can return undefined as well - const token_name_input: HTMLInputElement = await screen.findByLabelText('Token name'); + const read_checkbox = checkboxes.find(card => card.name === 'read'); // Typecasting it since find can return undefined as well + const token_name_input = await screen.findByLabelText('Token name'); expect(checkboxes.length).toBe(5); expect(create_btn).toBeDisabled(); @@ -202,7 +255,7 @@ describe('', () => { const updated_token_name_input = await screen.findByLabelText('Token name'); expect(updated_token_name_input.value).toBe(''); - const createToken = mock_props.ws.apiToken; + const createToken = WS.apiToken; expect(createToken).toHaveBeenCalledTimes(1); }); @@ -226,7 +279,11 @@ describe('', () => { }, ]); - render(); + render( + + + + ); expect(await screen.findByText('First test token')).toBeInTheDocument(); expect(await screen.findByText('Last used')).toBeInTheDocument(); @@ -255,7 +312,7 @@ describe('', () => { expect(yes_btn_1).toBeInTheDocument(); fireEvent.click(yes_btn_1); - const deleteToken = mock_props.ws.authorized.apiToken; + const deleteToken = WS.authorized.apiToken; expect(deleteToken).toHaveBeenCalled(); await waitFor(() => { expect(yes_btn_1).not.toBeInTheDocument(); @@ -287,7 +344,11 @@ describe('', () => { }, ]); - render(); + render( + + + + ); expect(await screen.findByText('First test token')).toBeInTheDocument(); expect(screen.queryByText('FirstTokenID')).not.toBeInTheDocument(); @@ -349,7 +410,11 @@ describe('', () => { }, ]); - render(); + render( + + + + ); expect((await screen.findAllByText('Name')).length).toBe(3); expect((await screen.findAllByText('Last Used')).length).toBe(3); @@ -364,7 +429,7 @@ describe('', () => { }); it('should show token error if exists', async () => { - mock_props.ws.authorized.apiToken = jest.fn(() => + WS.authorized.apiToken = jest.fn(() => Promise.resolve({ api_token: { tokens: [] }, error: { message: 'New test error' }, @@ -373,7 +438,11 @@ describe('', () => { getPropertyValue.mockReturnValue('New test error'); - render(); + render( + + + + ); expect(await screen.findByText('New test error')).toBeInTheDocument(); }); diff --git a/packages/account/src/Components/api-token/api-token.tsx b/packages/account/src/Components/api-token/api-token.tsx index 0c6dbb2e9170..558e16a5725c 100644 --- a/packages/account/src/Components/api-token/api-token.tsx +++ b/packages/account/src/Components/api-token/api-token.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { Formik, Form, Field, FormikValues, FormikErrors, FieldProps } from 'formik'; import { Timeline, Input, Button, ThemedScrollbars, Loading } from '@deriv/components'; import InlineNoteWithIcon from '../inline-note-with-icon'; -import { isDesktop, isMobile, getPropertyValue, useIsMounted } from '@deriv/shared'; +import { isDesktop, isMobile, getPropertyValue, useIsMounted, WS } from '@deriv/shared'; import { localize } from '@deriv/translations'; import LoadErrorMessage from 'Components/load-error-message'; import ApiTokenArticle from './api-token-article'; @@ -13,6 +13,7 @@ import ApiTokenOverlay from './api-token-overlay'; import ApiTokenTable from './api-token-table'; import ApiTokenContext from './api-token-context'; import { TToken } from 'Types'; +import { observer, useStore } from '@deriv/stores'; const MIN_TOKEN = 2; const MAX_TOKEN = 32; @@ -32,7 +33,6 @@ type AptTokenState = { export type TApiToken = { footer_ref: Element | DocumentFragment | undefined; is_app_settings: boolean; - is_switching: boolean; overlay_ref: | undefined | ((...args: unknown[]) => unknown) @@ -41,10 +41,11 @@ export type TApiToken = { }>; setIsOverlayShown: (is_overlay_shown: boolean | undefined) => void; // eslint-disable-next-line @typescript-eslint/no-explicit-any - ws: any; }; -const ApiToken = ({ footer_ref, is_app_settings, is_switching, overlay_ref, setIsOverlayShown, ws }: TApiToken) => { +const ApiToken = ({ footer_ref, is_app_settings, overlay_ref, setIsOverlayShown }: TApiToken) => { + const { client } = useStore(); + const { is_switching } = client; const isMounted = useIsMounted(); const prev_is_switching = React.useRef(is_switching); const [state, setState] = React.useReducer( @@ -122,9 +123,8 @@ const ApiToken = ({ footer_ref, is_app_settings, is_switching, overlay_ref, setI const selectedTokenScope = (values: FormikValues) => Object.keys(values).filter(item => item !== 'token_name' && values[item]); - const handleSubmit = async (values: FormikValues, { setSubmitting, setFieldError, resetForm }: any) => { - const token_response = await ws.apiToken({ + const token_response = await WS.apiToken({ api_token: 1, new_token: values.token_name, new_token_scopes: selectedTokenScope(values), @@ -163,14 +163,14 @@ const ApiToken = ({ footer_ref, is_app_settings, is_switching, overlay_ref, setI const getApiTokens = async () => { setState({ is_loading: true }); - const token_response = await ws.authorized.apiToken({ api_token: 1 }); + const token_response = await WS.authorized.apiToken({ api_token: 1 }); populateTokenResponse(token_response); }; const deleteToken = async (token: string) => { setState({ is_delete_loading: true }); - const token_response = await ws.authorized.apiToken({ api_token: 1, delete_token: token }); + const token_response = await WS.authorized.apiToken({ api_token: 1, delete_token: token }); populateTokenResponse(token_response); @@ -356,4 +356,4 @@ const ApiToken = ({ footer_ref, is_app_settings, is_switching, overlay_ref, setI ); }; -export default ApiToken; +export default observer(ApiToken); diff --git a/packages/account/src/Components/icon-with-message/__tests__/icon-with-message.spec.tsx b/packages/account/src/Components/icon-with-message/__tests__/icon-with-message.spec.tsx index a7654d2bcf37..6c032996c671 100644 --- a/packages/account/src/Components/icon-with-message/__tests__/icon-with-message.spec.tsx +++ b/packages/account/src/Components/icon-with-message/__tests__/icon-with-message.spec.tsx @@ -1,12 +1,7 @@ import React from 'react'; import { fireEvent, screen, render } from '@testing-library/react'; import IconWithMessage from '../icon-with-message'; - -jest.mock('Stores/connect', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: () => Component => Component, -})); +import { mockStore, StoreProvider } from '@deriv/stores'; jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); @@ -20,27 +15,46 @@ describe('', () => { icon: 'string', message: 'title', }; + const store = mockStore(); it('should render the IconWithMessage component', () => { - render(); + render( + + + + ); expect(screen.getByText('mockedIcon')).toBeInTheDocument(); expect(screen.getByText('title')).toBeInTheDocument(); }); it('should not render the button component if has_button is false', () => { - render(); + render( + + + + ); const btn = screen.queryByTestId('icon-with-message-button'); expect(btn).not.toBeInTheDocument(); }); it('should show "Switch to real account" button label if user have real account', () => { - render(); + store.client.has_any_real_account = true; + render( + + + + ); const btn = screen.getByTestId('icon-with-message-button'); expect(btn).toBeInTheDocument(); expect(screen.getByText('Switch to real account')).toBeInTheDocument(); }); it('should show "Add a real account" button label if user doesnt have real account', () => { - render(); + store.client.has_any_real_account = false; + render( + + + + ); expect(screen.getByText('Add a real account')).toBeInTheDocument(); }); @@ -49,16 +63,16 @@ describe('', () => { icon: 'string', message: 'title', has_button: true, - has_real_account: true, }; + store.client.has_any_real_account = true; const toggleShouldShowRealAccountsList = jest.fn(); const toggleAccountsDialog = jest.fn(); + store.ui.toggleShouldShowRealAccountsList = toggleShouldShowRealAccountsList; + store.ui.toggleAccountsDialog = toggleAccountsDialog; render( - + + + ); const btn = screen.getByTestId('icon-with-message-button'); expect(btn).toBeInTheDocument(); diff --git a/packages/account/src/Components/icon-with-message/icon-with-message.tsx b/packages/account/src/Components/icon-with-message/icon-with-message.tsx index bdd939eaf56f..ce3ca078034d 100644 --- a/packages/account/src/Components/icon-with-message/icon-with-message.tsx +++ b/packages/account/src/Components/icon-with-message/icon-with-message.tsx @@ -3,26 +3,18 @@ import classNames from 'classnames'; import { Icon, Text, Button } from '@deriv/components'; import { isMobile, PlatformContext } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer, useStore } from '@deriv/stores'; type TIconWithMessage = { icon: string; has_button?: boolean; - has_real_account?: boolean; message: string; - toggleAccountsDialog: (status?: boolean) => void; - toggleShouldShowRealAccountsList: (value: boolean) => void; }; -const IconWithMessage = ({ - has_button, - has_real_account, - icon, - message, - toggleAccountsDialog, - toggleShouldShowRealAccountsList, -}: TIconWithMessage) => { +const IconWithMessage = observer(({ has_button, icon, message }: TIconWithMessage) => { + const { client, ui } = useStore(); + const { has_any_real_account: has_real_account } = client; + const { toggleAccountsDialog, toggleShouldShowRealAccountsList } = ui; const { is_appstore } = React.useContext(PlatformContext); return ( @@ -53,10 +45,6 @@ const IconWithMessage = ({ )} ); -}; +}); -export default connect(({ client, ui }: RootStore) => ({ - has_real_account: client.has_any_real_account, - toggleAccountsDialog: ui.toggleAccountsDialog, - toggleShouldShowRealAccountsList: ui.toggleShouldShowRealAccountsList, -}))(IconWithMessage); +export default IconWithMessage; diff --git a/packages/account/src/Components/self-exclusion/__tests__/self-exclusion.spec.tsx b/packages/account/src/Components/self-exclusion/__tests__/self-exclusion.spec.tsx index 2453cecb7313..909dbbdfdb9a 100644 --- a/packages/account/src/Components/self-exclusion/__tests__/self-exclusion.spec.tsx +++ b/packages/account/src/Components/self-exclusion/__tests__/self-exclusion.spec.tsx @@ -3,46 +3,71 @@ import { act } from 'react-dom/test-utils'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import SelfExclusion from '../self-exclusion'; import { FormikValues } from 'formik'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { WS } from '@deriv/shared'; const portal_root = document.createElement('div'); document.body.appendChild(portal_root); -jest.mock('Stores/connect.js', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: () => (Component: React.Component) => Component, -})); - jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), useIsMounted: jest.fn().mockImplementation(() => () => true), })); +jest.mock('@deriv/shared/src/services/ws-methods', () => ({ + __esModule: true, // this property makes it work, + default: 'mockedDefaultExport', + WS: { + authorized: { + getLimits: jest.fn(() => + Promise.resolve({ + get_limits: {}, + }) + ), + getSelfExclusion: jest.fn(() => + Promise.resolve({ + error: { message: '' }, + }) + ), + setSelfExclusion: jest.fn(() => + Promise.resolve({ + error: {}, + }) + ), + }, + }, + useWS: () => undefined, +})); jest.mock('../self-exclusion-modal', () => { const MockSelfExclusionModal = () =>
SelfExclusionModal
; return MockSelfExclusionModal; }); - +const mock = { + client: { + currency: '', + standpoint: { + svg: false, + }, + is_uk: false, + is_virtual: false, + is_switching: false, + landing_company_shortcode: '', + logout: jest.fn(), + }, + ui: { + is_tablet: false, + }, +}; +let store = mockStore(mock); describe('', () => { let mock_props = { - currency: '', footer_ref: undefined, is_app_settings: false, is_appstore: false, - is_cr: false, - is_eu: false, - is_mf: false, - is_mlt: false, - is_mx: false, - is_switching: false, - is_tablet: false, - is_uk: false, - is_virtual: false, is_wrapper_bypassed: false, - logout: jest.fn(), overlay_ref: document.createElement('div'), setIsOverlayShown: jest.fn(), - ws: { + WS: { authorized: { getLimits: () => Promise.resolve({ @@ -54,61 +79,52 @@ describe('', () => { }), setSelfExclusion: () => Promise.resolve({ - error: {}, + error: false, }), }, }, }; beforeEach(() => { + mock.client.currency = 'Test currency'; + store = mockStore(mock); mock_props = { - currency: 'Test currency', footer_ref: undefined, is_app_settings: false, is_appstore: false, - is_cr: false, - is_eu: false, - is_mf: false, - is_mlt: false, - is_mx: false, - is_switching: false, - is_tablet: false, - is_uk: false, - is_virtual: false, is_wrapper_bypassed: false, - logout: jest.fn(), overlay_ref: document.createElement('div'), setIsOverlayShown: jest.fn(), - ws: { - authorized: { - getLimits: () => - Promise.resolve({ - get_limits: {}, - }), - getSelfExclusion: () => - Promise.resolve({ - error: { message: '' }, - }), - setSelfExclusion: () => - Promise.resolve({ - error: {}, - }), - }, - }, }; }); + afterEach(() => { + jest.clearAllMocks(); + }); it('should render SelfExclusion component for virtual account', () => { - mock_props.is_virtual = true; + store = mockStore({ + client: { + is_virtual: true, + }, + }); - render(); + render( + + + + ); expect(screen.getByText('This feature is not available for demo accounts.')).toBeInTheDocument(); }); it('should render SelfExclusion component with SelfExclusionModal', async () => { + store = mockStore(mock); await act(async () => { - render(); + render( + + + + ); }); expect(screen.getByText('SelfExclusionModal')).toBeInTheDocument(); @@ -120,22 +136,40 @@ describe('', () => { }); it('should render SelfExclusion component with error', async () => { - mock_props.ws.authorized.getSelfExclusion = () => + WS.authorized.getSelfExclusion = jest.fn(() => Promise.resolve({ error: { message: 'Test getSelfExclusion response error' }, - }); + }) + ); await act(async () => { - render(); + render( + + + + ); }); expect(screen.queryByText('Test getSelfExclusion response error')).toBeInTheDocument(); }); it('Should trigger session_duration_limit input and show error if the value is greater than 60480 or does not show if less than 60480', async () => { - mock_props.is_eu = true; + store = mockStore({ + client: { + is_eu: true, + }, + }); + WS.authorized.getSelfExclusion = jest.fn(() => + Promise.resolve({ + error: { message: '' }, + }) + ); - render(); + render( + + + + ); const inputs = await screen.findAllByRole('textbox'); const session_duration_limit_input = inputs.find( @@ -163,8 +197,17 @@ describe('', () => { it('Should trigger exclude_until input and show error depends on input value', async () => { (Date.now as jest.Mock) = jest.fn(() => new Date('2022-02-03')); - - render(); + WS.authorized.getSelfExclusion = jest.fn(() => + Promise.resolve({ + error: { message: '' }, + }) + ); + store = mockStore(mock); + render( + + + + ); const inputs = await screen.findAllByRole('textbox'); const exclude_until_input = inputs.find((input: FormikValues) => input.name === 'exclude_until'); @@ -187,13 +230,24 @@ describe('', () => { }); it('should trigger inputs with data, add new data, and show error wih invalid input data', async () => { - mock_props.ws.authorized.setSelfExclusion = () => + store = mockStore(mock); + WS.authorized.getSelfExclusion = jest.fn(() => + Promise.resolve({ + error: { message: '' }, + }) + ); + WS.authorized.setSelfExclusion = jest.fn(() => Promise.resolve({ error: { message: 'Test setSelfExclusion response error' }, - }); + }) + ); await act(async () => { - render(); + render( + + + + ); }); expect(screen.getByText('Your stake and loss limits')).toBeInTheDocument(); @@ -262,15 +316,19 @@ describe('', () => { }); it('should trigger inputs with correct data set timeout limit and logout', async () => { - mock_props.ws.authorized.setSelfExclusion = () => + WS.authorized.setSelfExclusion = jest.fn(() => Promise.resolve({ error: false, - }); - - const logout = mock_props.logout; + }) + ); + const logout = store.client.logout; await act(async () => { - render(); + render( + + + + ); }); expect(screen.getByText('Your stake and loss limits')).toBeInTheDocument(); diff --git a/packages/account/src/Components/self-exclusion/self-exclusion.tsx b/packages/account/src/Components/self-exclusion/self-exclusion.tsx index 0fbfc605ef18..2df2503ac0dd 100644 --- a/packages/account/src/Components/self-exclusion/self-exclusion.tsx +++ b/packages/account/src/Components/self-exclusion/self-exclusion.tsx @@ -8,6 +8,7 @@ import { getCurrencyDisplayCode, validNumber, useIsMounted, + WS, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import DemoMessage from 'Components/demo-message'; @@ -18,26 +19,15 @@ import SelfExclusionModal from './self-exclusion-modal'; import SelfExclusionWrapper from './self-exclusion-wrapper'; import SelfExclusionForm from './self-exclusion-form'; import { FormikHelpers, FormikValues } from 'formik'; +import { observer, useStore } from '@deriv/stores'; type TSelfExclusion = { - currency: string; footer_ref?: React.RefObject; - is_app_settings: boolean; - is_cr: boolean; is_appstore: boolean; - is_eu: boolean; - is_mf: boolean; - is_mlt: boolean; - is_mx: boolean; - is_switching: boolean; - is_tablet: boolean; - is_uk: boolean; - is_virtual: boolean; + is_app_settings: boolean; is_wrapper_bypassed: boolean; - logout: () => void; overlay_ref: HTMLDivElement; setIsOverlayShown?: React.Dispatch>; - ws: FormikValues; }; type TExclusionData = { @@ -83,25 +73,20 @@ type TResponse = { }; const SelfExclusion = ({ - currency, footer_ref, is_app_settings, - is_cr, is_appstore, - is_eu, - is_mf, - is_mlt, - is_mx, - is_switching, - is_tablet, - is_uk, - is_virtual, - is_wrapper_bypassed, - logout, overlay_ref, setIsOverlayShown, - ws, }: TSelfExclusion) => { + const { client, ui } = useStore(); + const { currency, is_virtual, is_switching, standpoint, is_eu, is_uk, logout, landing_company_shortcode } = client; + const { is_tablet } = ui; + const is_wrapper_bypassed = false; + const is_mlt = landing_company_shortcode === 'malta'; + const is_mf = landing_company_shortcode === 'maltainvest'; + const is_mx = landing_company_shortcode === 'iom'; + const is_cr = standpoint.svg; const exclusion_fields_settings = Object.freeze({ max_number: 9999999999999, max_open_positions: 999999999, @@ -196,7 +181,6 @@ const SelfExclusion = ({ }, [state.show_article, setIsOverlayShown]); const resetState = () => setState(initial_state); - const validateFields = (values: FormikValues) => { const errors: Record = {}; @@ -309,7 +293,6 @@ const SelfExclusion = ({ }); return errors; }; - const handleSubmit = async (values: FormikValues, { setSubmitting }: FormikHelpers) => { const need_logout_exclusions = ['exclude_until', 'timeout_until']; const string_exclusions = ['exclude_until']; @@ -325,7 +308,7 @@ const SelfExclusion = ({ request[attr] = string_exclusions.includes(attr) ? values[attr] : +values[attr]; }); - ws.authorized.setSelfExclusion(request).then((response: TResponse) => resolve(response)); + WS.authorized.setSelfExclusion(request).then((response: TResponse) => resolve(response)); }); if (has_need_logout) { @@ -404,7 +387,7 @@ const SelfExclusion = ({ const getSelfExclusion = () => { setState({ is_loading: true }); - ws.authorized.getSelfExclusion({ get_self_exclusion: 1 }).then((self_exclusion_response: FormikValues) => { + WS.authorized.getSelfExclusion({ get_self_exclusion: 1 }).then((self_exclusion_response: FormikValues) => { populateExclusionResponse(self_exclusion_response); }); }; @@ -412,7 +395,7 @@ const SelfExclusion = ({ const getLimits = () => { setState({ is_loading: true }); - ws.authorized.getLimits({ get_limits: 1 }).then((limits: FormikValues) => { + WS.authorized.getLimits({ get_limits: 1 }).then((limits: FormikValues) => { exclusion_limits.current = limits; }); }; @@ -484,7 +467,7 @@ const SelfExclusion = ({ return ( - {/* Only show the modal in non-"" views, others will + {/* Only show the modal in non-"" views, others will use the overlay provided by */} {!is_app_settings && } @@ -494,4 +477,4 @@ const SelfExclusion = ({ ); }; -export default SelfExclusion; +export default observer(SelfExclusion); diff --git a/packages/account/src/Containers/account.jsx b/packages/account/src/Containers/account.jsx index f1fb09a3fcaf..5c035d7a13ae 100644 --- a/packages/account/src/Containers/account.jsx +++ b/packages/account/src/Containers/account.jsx @@ -1,14 +1,13 @@ import 'Styles/account.scss'; - -import { FadeWrapper, Icon, Loading, PageOverlay, Text, VerticalTab } from '@deriv/components'; import { PlatformContext, getSelectedRoute, isMobile, matchRoute, routes as shared_routes } from '@deriv/shared'; import PropTypes from 'prop-types'; import React from 'react'; -import { connect } from 'Stores/connect'; +import { withRouter } from 'react-router-dom'; +import { VerticalTab, FadeWrapper, PageOverlay, Loading, Text, Icon } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; import { flatten } from '../Helpers/flatten'; import { localize } from '@deriv/translations'; import { useHistory } from 'react-router'; -import { withRouter } from 'react-router-dom'; const AccountLogout = ({ logout, history }) => { return ( @@ -104,23 +103,20 @@ const PageOverlayWrapper = ({ ); }; -const Account = ({ - active_account_landing_company, - history, - is_from_derivgo, - is_logged_in, - is_logging_in, - is_pending_proof_of_ownership, - is_virtual, - is_visible, - location, - logout, - platform, - routeBackInApp, - routes, - should_allow_authentication, - toggleAccount, -}) => { +const Account = observer(({ history, location, routes }) => { + const { client, common, ui } = useStore(); + const { + is_virtual, + is_logged_in, + is_logging_in, + is_risky_client, + is_pending_proof_of_ownership, + landing_company_shortcode, + should_allow_authentication, + logout, + } = client; + const { is_from_derivgo, routeBackInApp, platform } = common; + const { toggleAccountSettings, is_account_settings_visible } = ui; const { is_appstore } = React.useContext(PlatformContext); const subroutes = flatten(routes.map(i => i.subroutes)); let list_groups = [...routes]; @@ -133,17 +129,17 @@ const Account = ({ const onClickClose = React.useCallback(() => routeBackInApp(history), [routeBackInApp, history]); React.useEffect(() => { - toggleAccount(true); - }, [toggleAccount]); + toggleAccountSettings(true); + }, [toggleAccountSettings]); routes.forEach(menu_item => { menu_item.subroutes.forEach(route => { if (route.path === shared_routes.financial_assessment) { - route.is_disabled = is_virtual; + route.is_disabled = is_virtual || (landing_company_shortcode === 'maltainvest' && !is_risky_client); } if (route.path === shared_routes.trading_assessment) { - route.is_disabled = is_virtual || active_account_landing_company !== 'maltainvest'; + route.is_disabled = is_virtual || landing_company_shortcode !== 'maltainvest'; } if (route.path === shared_routes.proof_of_identity || route.path === shared_routes.proof_of_address) { @@ -169,7 +165,11 @@ const Account = ({ const selected_route = getSelectedRoute({ routes: subroutes, pathname: location.pathname }); return ( - +
); -}; +}); Account.propTypes = { active_account_landing_company: PropTypes.string, history: PropTypes.object, - is_from_derivgo: PropTypes.bool, - is_logged_in: PropTypes.bool, - is_logging_in: PropTypes.bool, - is_pending_proof_of_ownership: PropTypes.bool, - is_virtual: PropTypes.bool, - is_visible: PropTypes.bool, location: PropTypes.object, - logout: PropTypes.func, - platform: PropTypes.string, - routeBackInApp: PropTypes.func, routes: PropTypes.arrayOf(PropTypes.object), - should_allow_authentication: PropTypes.bool, - toggleAccount: PropTypes.func, }; -export default connect(({ client, common, ui }) => ({ - active_account_landing_company: client.landing_company_shortcode, - is_from_derivgo: common.is_from_derivgo, - is_logged_in: client.is_logged_in, - is_logging_in: client.is_logging_in, - is_pending_proof_of_ownership: client.is_pending_proof_of_ownership, - is_virtual: client.is_virtual, - is_visible: ui.is_account_settings_visible, - logout: client.logout, - platform: common.platform, - routeBackInApp: common.routeBackInApp, - should_allow_authentication: client.should_allow_authentication, - toggleAccount: ui.toggleAccountSettings, -}))(withRouter(Account)); +export default withRouter(Account); diff --git a/packages/account/src/Containers/reset-trading-password.jsx b/packages/account/src/Containers/reset-trading-password.jsx index 94709bbcd8f2..76669b31cb1f 100644 --- a/packages/account/src/Containers/reset-trading-password.jsx +++ b/packages/account/src/Containers/reset-trading-password.jsx @@ -1,54 +1,32 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { useLocation } from 'react-router-dom'; import { CFD_PLATFORMS } from '@deriv/shared'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import ResetTradingPasswordModal from '../Components/reset-trading-password-modal'; -const ResetTradingPassword = ({ - enableApp, - disableApp, - toggleResetTradingPasswordModal, - mt5_verification_code, - dxtrade_verification_code, - is_visible, - is_loading, -}) => { +const ResetTradingPassword = observer(() => { + const { ui, client } = useStore(); + const { enableApp, disableApp, is_visible, is_loading, setResetTradingPasswordModalOpen } = ui; const location = useLocation(); const query_params = new URLSearchParams(location.search); const cfd_platform = /^trading_platform_(.*)_password_reset$/.exec(query_params.get('action') || '')?.[1]; const [platform] = React.useState(cfd_platform); - const verification_code = platform === CFD_PLATFORMS.MT5 ? mt5_verification_code : dxtrade_verification_code; + const verification_code = + platform === CFD_PLATFORMS.MT5 + ? client.verification_code.trading_platform_mt5_password_reset + : client.verification_code.trading_platform_dxtrade_password_reset; return ( ); -}; +}); -ResetTradingPassword.propTypes = { - disableApp: PropTypes.func, - enableApp: PropTypes.func, - is_loading: PropTypes.bool, - is_visible: PropTypes.bool, - toggleResetTradingPasswordModal: PropTypes.func, - mt5_verification_code: PropTypes.string, - dxtrade_verification_code: PropTypes.string, -}; - -export default connect(({ ui, client }) => ({ - disableApp: ui.disableApp, - enableApp: ui.enableApp, - is_loading: ui.is_loading, - is_visible: ui.is_reset_trading_password_modal_visible, - toggleResetTradingPasswordModal: ui.setResetTradingPasswordModalOpen, - mt5_verification_code: client.verification_code.trading_platform_mt5_password_reset, - dxtrade_verification_code: client.verification_code.trading_platform_dxtrade_password_reset, -}))(ResetTradingPassword); +export default ResetTradingPassword; diff --git a/packages/account/src/Containers/routes.jsx b/packages/account/src/Containers/routes.jsx index ac85dcb0e31d..49b81f854432 100644 --- a/packages/account/src/Containers/routes.jsx +++ b/packages/account/src/Containers/routes.jsx @@ -1,41 +1,26 @@ -import { PropTypes as MobxPropTypes } from 'mobx-react'; import PropTypes from 'prop-types'; import React from 'react'; import { withRouter } from 'react-router'; import { BinaryRoutes } from 'Components/Routes'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import ErrorComponent from 'Components/error-component'; -const Routes = props => { +const Routes = observer(props => { + const { client, common } = useStore(); + const { is_logged_in, is_logging_in } = client; + const { error } = common; if (props.has_error) { - return ; + return ; } - return ( - - ); -}; + return ; +}); Routes.propTypes = { - error: MobxPropTypes.objectOrObservableObject, - has_error: PropTypes.bool, - is_logged_in: PropTypes.bool, - is_logging_in: PropTypes.bool, is_virtual: PropTypes.bool, passthrough: PropTypes.object, }; // need to wrap withRouter around connect // to prevent updates on from being blocked -export default withRouter( - connect(({ client, common }) => ({ - is_logged_in: client.is_logged_in, - is_logging_in: client.is_logging_in, - error: common.error, - has_error: common.has_error, - }))(Routes) -); +export default withRouter(Routes); diff --git a/packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx b/packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx index 88684b5f67ba..1ca1e3ca9c35 100644 --- a/packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx +++ b/packages/account/src/Sections/Assessment/FinancialAssessment/financial-assessment.jsx @@ -1,6 +1,5 @@ import classNames from 'classnames'; import React from 'react'; -import { PropTypes } from 'prop-types'; import { Formik } from 'formik'; import { useHistory, withRouter } from 'react-router'; import { @@ -17,7 +16,7 @@ import { } from '@deriv/components'; import { routes, isMobile, isDesktop, platforms, PlatformContext, WS } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import LeaveConfirm from 'Components/leave-confirm'; import IconMessageContent from 'Components/icon-message-content'; import DemoMessage from 'Components/demo-message'; @@ -175,20 +174,22 @@ const SubmittedPage = ({ platform, routeBackInApp }) => { ); }; -const FinancialAssessment = ({ - is_authentication_needed, - is_financial_account, - is_mf, - is_svg, - is_trading_experience_incomplete, - is_financial_information_incomplete, - is_virtual, - platform, - refreshNotifications, - routeBackInApp, - setFinancialAndTradingAssessment, - updateAccountStatus, -}) => { +const FinancialAssessment = observer(() => { + const { client, common, notifications } = useStore(); + const { + landing_company_shortcode, + is_virtual, + is_financial_account, + is_trading_experience_incomplete, + is_svg, + setFinancialAndTradingAssessment, + updateAccountStatus, + is_authentication_needed, + is_financial_information_incomplete, + } = client; + const { platform, routeBackInApp } = common; + const { refreshNotifications } = notifications; + const is_mf = landing_company_shortcode === 'maltainvest'; const history = useHistory(); const { is_appstore } = React.useContext(PlatformContext); const [is_loading, setIsLoading] = React.useState(true); @@ -1005,34 +1006,6 @@ const FinancialAssessment = ({ ); -}; - -FinancialAssessment.propTypes = { - is_authentication_needed: PropTypes.bool, - is_financial_account: PropTypes.bool, - is_mf: PropTypes.bool, - is_svg: PropTypes.bool, - is_trading_experience_incomplete: PropTypes.bool, - is_financial_information_incomplete: PropTypes.bool, - is_virtual: PropTypes.bool, - platform: PropTypes.string, - refreshNotifications: PropTypes.func, - routeBackInApp: PropTypes.func, - setFinancialAndTradingAssessment: PropTypes.func, - updateAccountStatus: PropTypes.func, -}; +}); -export default connect(({ client, common, notifications }) => ({ - is_authentication_needed: client.is_authentication_needed, - is_financial_account: client.is_financial_account, - is_mf: client.landing_company_shortcode === 'maltainvest', - is_svg: client.is_svg, - is_financial_information_incomplete: client.is_financial_information_incomplete, - is_trading_experience_incomplete: client.is_trading_experience_incomplete, - is_virtual: client.is_virtual, - platform: common.platform, - refreshNotifications: notifications.refreshNotifications, - routeBackInApp: common.routeBackInApp, - setFinancialAndTradingAssessment: client.setFinancialAndTradingAssessment, - updateAccountStatus: client.updateAccountStatus, -}))(withRouter(FinancialAssessment)); +export default withRouter(FinancialAssessment); diff --git a/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx b/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx index f3f91274c509..242ac860951b 100644 --- a/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx +++ b/packages/account/src/Sections/Assessment/TradingAssessment/trading-assessment.jsx @@ -16,7 +16,7 @@ import { } from '@deriv/components'; import FormFooter from 'Components/form-footer'; import { isMobile, routes, WS } from '@deriv/shared'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import { useHistory, withRouter } from 'react-router'; import { Formik, Form } from 'formik'; @@ -35,7 +35,9 @@ const populateData = form_data => { }; }; -const TradingAssessment = ({ is_virtual, setFinancialAndTradingAssessment }) => { +const TradingAssessment = observer(() => { + const { client } = useStore(); + const { is_virtual, setFinancialAndTradingAssessment } = client; const history = useHistory(); const [is_loading, setIsLoading] = React.useState(true); const [is_btn_loading, setIsBtnLoading] = React.useState(false); @@ -282,9 +284,6 @@ const TradingAssessment = ({ is_virtual, setFinancialAndTradingAssessment }) => }} ); -}; +}); -export default connect(({ client }) => ({ - is_virtual: client.is_virtual, - setFinancialAndTradingAssessment: client.setFinancialAndTradingAssessment, -}))(withRouter(TradingAssessment)); +export default withRouter(TradingAssessment); diff --git a/packages/account/src/Sections/Profile/LanguageSettings/language-settings.tsx b/packages/account/src/Sections/Profile/LanguageSettings/language-settings.tsx index 8a97f4f2f697..cb0b7eea8b27 100644 --- a/packages/account/src/Sections/Profile/LanguageSettings/language-settings.tsx +++ b/packages/account/src/Sections/Profile/LanguageSettings/language-settings.tsx @@ -2,20 +2,15 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Button, DesktopWrapper } from '@deriv/components'; import { localize, getAllowedLanguages } from '@deriv/translations'; -import { connect } from 'Stores/connect'; import FormSubHeader from 'Components/form-sub-header'; -import TCoreStore from '../../../Stores'; import { Formik, FormikHandlers, FormikHelpers, FormikValues } from 'formik'; import FormFooter from 'Components/form-footer'; import LanguageRadioButton from 'Components/language-settings'; +import { observer, useStore } from '@deriv/stores'; -type TLanguageSettings = { - current_language: string; - changeSelectedLanguage: (lang: string) => void; - isCurrentLanguage: (lang: string) => boolean; -}; - -const LanguageSettings = ({ changeSelectedLanguage, current_language, isCurrentLanguage }: TLanguageSettings) => { +const LanguageSettings = observer(() => { + const { common } = useStore(); + const { changeSelectedLanguage, current_language, isCurrentLanguage } = common; const { i18n } = useTranslation(); const allowed_language_keys: string[] = Object.keys(getAllowedLanguages()); const initial_values = { language_code: current_language }; @@ -69,10 +64,6 @@ const LanguageSettings = ({ changeSelectedLanguage, current_language, isCurrentL }} ); -}; +}); -export default connect(({ common }: TCoreStore) => ({ - changeSelectedLanguage: common.changeSelectedLanguage, - current_language: common.current_language, - isCurrentLanguage: common.isCurrentLanguage, -}))(LanguageSettings); +export default LanguageSettings; diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 5908e032cb17..1f54dcae50be 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -4,6 +4,7 @@ import { cleanup, render, waitForElementToBeRemoved, waitFor } from '@testing-li import { createBrowserHistory } from 'history'; import { Router } from 'react-router'; import { PersonalDetailsForm } from '../personal-details.jsx'; +import { StoreProvider, mockStore } from '@deriv/stores'; afterAll(cleanup); @@ -15,9 +16,11 @@ jest.mock('@deriv/shared/src/services/ws-methods', () => ({ return Promise.resolve([...payload]); }, }, + useWS: () => undefined, })); describe('', () => { + let store = mockStore(); const history = createBrowserHistory(); it('should_render_successfully', async () => { @@ -32,20 +35,36 @@ describe('', () => { value: 'value', }, ]; + store = mockStore({ + client: { + account_settings: { + email_consent: 1, + }, + is_virtual: false, + states_list: residence_list, + residence_list: residence_list, + has_residence: true, + getChangeableFields: () => [], + fetchResidenceList: fetchResidenceList, + fetchStatesList: fetchStatesList, + }, + }); + + // store.client.fetchResidenceList = fetchResidenceList; + // store.client.fetchStatesList = fetchStatesList; + // store.client.residence_list = residence_list; + // store.client.account_settings = { + // email_consent: 1, + // }; + // store.client.is_virtual = false; + // store.client.states_list = residence_list; + // store.client.getChangeableFields = () => []; + // store.client.has_residence = true; const screen = render( - []} - is_virtual={false} - states_list={residence_list} - /> + + + ); await waitForElementToBeRemoved(() => screen.container.querySelector('.account__initial-loader')); diff --git a/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx b/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx index b7b352190d0a..3978f839086f 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx @@ -39,14 +39,14 @@ import { removeEmptyPropertiesFromObject, } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; +import { observer, useStore } from '@deriv/stores'; +import LeaveConfirm from 'Components/leave-confirm'; +import FormFooter from 'Components/form-footer'; import FormBody from 'Components/form-body'; import FormBodySection from 'Components/form-body-section'; -import FormFooter from 'Components/form-footer'; import FormSubHeader from 'Components/form-sub-header'; -import LeaveConfirm from 'Components/leave-confirm'; import LoadErrorMessage from 'Components/load-error-message'; import POAAddressMismatchHintBox from 'Components/poa-address-mismatch-hint-box'; -import { connect } from 'Stores/connect'; import { getEmploymentStatusList } from 'Sections/Assessment/FinancialAssessment/financial-information-list'; import { validateName, validate } from 'Helpers/utils'; @@ -96,31 +96,7 @@ const TaxResidenceSelect = ({ field, errors, setFieldValue, values, is_changeabl ); -export const PersonalDetailsForm = ({ - authentication_status, - is_eu, - is_mf, - is_uk, - is_svg, - is_virtual, - residence_list, - states_list, - current_landing_company, - refreshNotifications, - showPOAAddressMismatchSuccessNotification, - showPOAAddressMismatchFailureNotification, - Notifications, - fetchResidenceList, - fetchStatesList, - has_residence, - account_settings, - getChangeableFields, - history, - is_social_signup, - updateAccountStatus, - has_poa_address_mismatch, - is_language_changing, -}) => { +export const PersonalDetailsForm = observer(({ history }) => { const [is_loading, setIsLoading] = React.useState(true); const [is_state_loading, setIsStateLoading] = useStateCallback(false); @@ -128,7 +104,38 @@ export const PersonalDetailsForm = ({ const [is_btn_loading, setIsBtnLoading] = React.useState(false); const [is_submit_success, setIsSubmitSuccess] = useStateCallback(false); + const { client, notifications, ui, common } = useStore(); + + const { + authentication_status, + is_eu, + landing_company_shortcode, + is_uk, + is_svg, + is_virtual, + residence_list, + states_list, + current_landing_company, + fetchResidenceList, + fetchStatesList, + has_residence, + account_settings, + getChangeableFields, + updateAccountStatus, + is_social_signup, + account_status, + } = client; + + const { + refreshNotifications, + showPOAAddressMismatchSuccessNotification, + showPOAAddressMismatchFailureNotification, + } = notifications; + const { Notifications } = ui; + const { is_language_changing } = common; + const is_mf = landing_company_shortcode === 'maltainvest'; + const has_poa_address_mismatch = account_status.status?.includes('poa_address_mismatch'); const [rest_state, setRestState] = React.useState({ show_form: true, errors: false, @@ -141,7 +148,6 @@ export const PersonalDetailsForm = ({ }); const { is_appstore } = React.useContext(PlatformContext); - const isMounted = useIsMounted(); React.useEffect(() => { @@ -1272,55 +1278,10 @@ export const PersonalDetailsForm = ({ )} ); -}; +}); PersonalDetailsForm.propTypes = { - authentication_status: PropTypes.object, - is_eu: PropTypes.bool, - is_mf: PropTypes.bool, - is_uk: PropTypes.bool, - is_svg: PropTypes.bool, - is_virtual: PropTypes.bool, - residence_list: PropTypes.arrayOf(PropTypes.object), - states_list: PropTypes.array, - refreshNotifications: PropTypes.func, - showPOAAddressMismatchSuccessNotification: PropTypes.func, - showPOAAddressMismatchFailureNotification: PropTypes.func, - Notifications: PropTypes.node, - fetchResidenceList: PropTypes.func, - fetchStatesList: PropTypes.func, - has_residence: PropTypes.bool, - account_settings: PropTypes.object, - getChangeableFields: PropTypes.func, - current_landing_company: PropTypes.object, history: PropTypes.object, - is_social_signup: PropTypes.bool, - updateAccountStatus: PropTypes.func, - has_poa_address_mismatch: PropTypes.bool, - is_language_changing: PropTypes.bool, }; -export default connect(({ client, notifications, ui, common }) => ({ - account_settings: client.account_settings, - authentication_status: client.authentication_status, - has_residence: client.has_residence, - getChangeableFields: client.getChangeableFields, - current_landing_company: client.current_landing_company, - is_eu: client.is_eu, - is_mf: client.landing_company_shortcode === 'maltainvest', - is_svg: client.is_svg, - is_uk: client.is_uk, - is_virtual: client.is_virtual, - residence_list: client.residence_list, - states_list: client.states_list, - fetchResidenceList: client.fetchResidenceList, - fetchStatesList: client.fetchStatesList, - is_social_signup: client.is_social_signup, - refreshNotifications: notifications.refreshNotifications, - showPOAAddressMismatchSuccessNotification: notifications.showPOAAddressMismatchSuccessNotification, - showPOAAddressMismatchFailureNotification: notifications.showPOAAddressMismatchFailureNotification, - Notifications: ui.notification_messages_ui, - updateAccountStatus: client.updateAccountStatus, - has_poa_address_mismatch: client.account_status.status?.includes('poa_address_mismatch'), - is_language_changing: common.is_language_changing, -}))(withRouter(PersonalDetailsForm)); +export default withRouter(PersonalDetailsForm); diff --git a/packages/account/src/Sections/Security/AccountClosed/account-closed.jsx b/packages/account/src/Sections/Security/AccountClosed/account-closed.jsx index d3e553109ee5..e3379dfa2279 100644 --- a/packages/account/src/Sections/Security/AccountClosed/account-closed.jsx +++ b/packages/account/src/Sections/Security/AccountClosed/account-closed.jsx @@ -2,9 +2,11 @@ import React from 'react'; import { Modal, Text } from '@deriv/components'; import { Localize } from '@deriv/translations'; import { getStaticUrl, PlatformContext } from '@deriv/shared'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; -const AccountClosed = ({ logout }) => { +const AccountClosed = observer(() => { + const { client } = useStore(); + const { logout } = client; const [is_modal_open, setModalState] = React.useState(true); const [timer, setTimer] = React.useState(10); const { is_appstore } = React.useContext(PlatformContext); @@ -38,8 +40,6 @@ const AccountClosed = ({ logout }) => { ); -}; +}); -export default connect(({ client }) => ({ - logout: client.logout, -}))(AccountClosed); +export default AccountClosed; diff --git a/packages/account/src/Sections/Security/AccountLimits/account-limits.jsx b/packages/account/src/Sections/Security/AccountLimits/account-limits.jsx index 8428842a49f2..5a0771faf09b 100644 --- a/packages/account/src/Sections/Security/AccountLimits/account-limits.jsx +++ b/packages/account/src/Sections/Security/AccountLimits/account-limits.jsx @@ -1,15 +1,4 @@ import AccountLimits from 'Components/account-limits/account-limits'; import 'Components/account-limits/account-limits.scss'; -import { connect } from 'Stores/connect'; -export default connect(({ client, common, ui }) => ({ - account_limits: client.account_limits, - currency: client.currency, - getLimits: client.getLimits, - is_fully_authenticated: client.is_fully_authenticated, - is_from_derivgo: common.is_from_derivgo, - is_virtual: client.is_virtual, - is_switching: client.is_switching, - should_show_article: true, - toggleAccountsDialog: ui.toggleAccountsDialog, -}))(AccountLimits); +export default AccountLimits; diff --git a/packages/account/src/Sections/Security/ApiToken/api-token.jsx b/packages/account/src/Sections/Security/ApiToken/api-token.jsx index 05363454eef6..cb02ee645088 100644 --- a/packages/account/src/Sections/Security/ApiToken/api-token.jsx +++ b/packages/account/src/Sections/Security/ApiToken/api-token.jsx @@ -1,6 +1,4 @@ -import { WS } from '@deriv/shared'; -import { connect } from 'Stores/connect'; import ApiToken from 'Components/api-token/api-token'; import 'Components/api-token/api-token.scss'; -export default connect(({ client }) => ({ is_switching: client.is_switching, ws: WS }))(ApiToken); +export default ApiToken; diff --git a/packages/account/src/Sections/Security/ClosingAccount/__tests__/closing-account-reason.spec.js b/packages/account/src/Sections/Security/ClosingAccount/__tests__/closing-account-reason.spec.js index 49ff2da25f9f..ca7527b3c459 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/__tests__/closing-account-reason.spec.js +++ b/packages/account/src/Sections/Security/ClosingAccount/__tests__/closing-account-reason.spec.js @@ -1,14 +1,10 @@ import React from 'react'; import { act, render, screen, waitFor, fireEvent, userEvent } from '@testing-library/react'; import ClosingAccountReason from '../closing-account-reason'; - -jest.mock('Stores/connect', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: () => Component => Component, -})); +import { mockStore, StoreProvider } from '@deriv/stores'; describe('', () => { + let store = mockStore(); beforeAll(() => { const modal_root_el = document.createElement('div'); modal_root_el.setAttribute('id', 'modal_root'); @@ -21,14 +17,22 @@ describe('', () => { }); test('Should render properly', async () => { - render(); + render( + + + + ); await waitFor(() => { screen.getAllByText(/Please tell us why you’re leaving/i); }); }); test('Should be disabled when no reason has been selected', async () => { - render(); + render( + + + + ); // clicking the checkbox twice to select and unselect fireEvent.click(screen.getByRole('checkbox', { name: /I have other financial priorities./i })); @@ -43,7 +47,11 @@ describe('', () => { }); test('should reduce remaining chars', async () => { - render(); + render( + + + + ); expect(screen.getByText(/Remaining characters: 110/i)).toBeInTheDocument(); diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.jsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.jsx index d78f78ff2f73..7a2eeeba0d55 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.jsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-reason.jsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { routes, PlatformContext, WS } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { FormSubmitButton, Modal, Icon, Loading, Text, Button } from '@deriv/components'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import AccountHasPendingConditions from './account-has-balance.jsx'; import ClosingAccountReasonFrom from './closing-account-reason-form.jsx'; @@ -73,7 +73,9 @@ const GeneralErrorContent = ({ message, onClick }) => ( const character_limit_no = 110; const max_allowed_reasons = 3; -const ClosingAccountReason = ({ onBackClick, mt5_login_list, client_accounts, dxtrade_accounts_list }) => { +const ClosingAccountReason = observer(({ onBackClick }) => { + const { client } = useStore(); + const { dxtrade_accounts_list, mt5_login_list, account_list } = client; const { is_appstore } = React.useContext(PlatformContext); const [is_account_closed, setIsAccountClosed] = React.useState(false); const [is_loading, setIsLoading] = React.useState(false); @@ -228,7 +230,7 @@ const ClosingAccountReason = ({ onBackClick, mt5_login_list, client_accounts, dx @@ -239,10 +241,6 @@ const ClosingAccountReason = ({ onBackClick, mt5_login_list, client_accounts, dx
); -}; +}); -export default connect(({ client }) => ({ - client_accounts: client.account_list, - mt5_login_list: client.mt5_login_list, - dxtrade_accounts_list: client.dxtrade_accounts_list, -}))(ClosingAccountReason); +export default ClosingAccountReason; diff --git a/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.jsx b/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.jsx index fb138cfd3f5b..51a7215a66bc 100644 --- a/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.jsx +++ b/packages/account/src/Sections/Security/ClosingAccount/closing-account-steps.jsx @@ -1,12 +1,14 @@ import React from 'react'; import classNames from 'classnames'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import { localize, Localize } from '@deriv/translations'; import { Link } from 'react-router-dom'; import { Button, Text, StaticUrl } from '@deriv/components'; import { PlatformContext } from '@deriv/shared'; -const ClosingAccountSteps = ({ redirectToReasons, is_from_derivgo }) => { +const ClosingAccountSteps = observer(({ redirectToReasons }) => { + const { common } = useStore(); + const { is_from_derivgo } = common; const { is_appstore } = React.useContext(PlatformContext); return ( @@ -84,5 +86,5 @@ const ClosingAccountSteps = ({ redirectToReasons, is_from_derivgo }) => { )} ); -}; -export default connect(({ common }) => ({ is_from_derivgo: common.is_from_derivgo }))(ClosingAccountSteps); +}); +export default ClosingAccountSteps; diff --git a/packages/account/src/Sections/Security/LoginHistory/login-history.jsx b/packages/account/src/Sections/Security/LoginHistory/login-history.jsx index 449c4e253e09..099b5c5d9139 100644 --- a/packages/account/src/Sections/Security/LoginHistory/login-history.jsx +++ b/packages/account/src/Sections/Security/LoginHistory/login-history.jsx @@ -1,11 +1,10 @@ -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import { Loading, Table, Text, ThemedScrollbars } from '@deriv/components'; import Bowser from 'bowser'; import { convertDateFormat, isMobile, isDesktop, PlatformContext, WS } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; import LoadErrorMessage from 'Components/load-error-message'; const API_FETCH_LIMIT = 50; @@ -168,7 +167,9 @@ const ListCell = ({ title, text, className, right }) => ( ); -const LoginHistory = ({ is_switching }) => { +const LoginHistory = observer(() => { + const { client } = useStore(); + const { is_switching } = client; const [is_loading, setLoading] = React.useState(true); const [error, setError] = React.useState(''); const [data, setData] = React.useState([]); @@ -197,12 +198,6 @@ const LoginHistory = ({ is_switching }) => { {data.length ? : null} ); -}; - -LoginHistory.propTypes = { - is_switching: PropTypes.bool, -}; +}); -export default connect(({ client }) => ({ - is_switching: client.is_switching, -}))(LoginHistory); +export default LoginHistory; diff --git a/packages/account/src/Sections/Security/Passwords/deriv-email.jsx b/packages/account/src/Sections/Security/Passwords/deriv-email.jsx index c2c85c1492a9..a962f2526c33 100644 --- a/packages/account/src/Sections/Security/Passwords/deriv-email.jsx +++ b/packages/account/src/Sections/Security/Passwords/deriv-email.jsx @@ -8,9 +8,11 @@ import { Button, Text, Input } from '@deriv/components'; import FormSubHeader from 'Components/form-sub-header'; import SentEmailModal from 'Components/sent-email-modal'; import UnlinkAccountModal from 'Components/unlink-account-modal'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; -const DerivEmail = ({ email, social_identity_provider, is_social_signup, is_from_derivgo }) => { +const DerivEmail = observer(({ email, social_identity_provider, is_social_signup }) => { + const { common } = useStore(); + const { is_from_derivgo } = common; const [is_unlink_account_modal_open, setIsUnlinkAccountModalOpen] = React.useState(false); const [is_send_email_modal_open, setIsSendEmailModalOpen] = React.useState(false); @@ -88,18 +90,13 @@ const DerivEmail = ({ email, social_identity_provider, is_social_signup, is_from ); -}; +}); DerivEmail.propTypes = { email: PropTypes.string, is_dark_mode_on: PropTypes.bool, is_social_signup: PropTypes.bool, - is_from_derivgo: PropTypes.bool, social_identity_provider: PropTypes.string, }; -export default withRouter( - connect(({ common }) => ({ - is_from_derivgo: common.is_from_derivgo, - }))(DerivEmail) -); +export default withRouter(DerivEmail); diff --git a/packages/account/src/Sections/Security/Passwords/passwords.jsx b/packages/account/src/Sections/Security/Passwords/passwords.jsx index 86f6dbcb1f58..719d784a160c 100644 --- a/packages/account/src/Sections/Security/Passwords/passwords.jsx +++ b/packages/account/src/Sections/Security/Passwords/passwords.jsx @@ -1,32 +1,36 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Loading } from '@deriv/components'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import DerivPassword from './deriv-password.jsx'; import DerivEmail from './deriv-email.jsx'; import PasswordsPlatform from './passwords-platform.jsx'; -const Passwords = ({ - email, - is_dark_mode_on, - mt5_login_list, - is_social_signup, - dxtrade_accounts_list, - social_identity_provider, - is_eu_user, - financial_restricted_countries, - is_loading_dxtrade, - is_loading_mt5, - is_mt5_password_not_set, - is_dxtrade_password_not_set, - is_from_derivgo, -}) => { +const Passwords = observer(() => { const [is_loading, setIsLoading] = React.useState(true); + const { client, ui, common, traders_hub } = useStore(); + const { + is_populating_mt5_account_list, + is_populating_dxtrade_account_list, + is_social_signup, + email, + social_identity_provider, + mt5_login_list, + is_mt5_password_not_set, + dxtrade_accounts_list, + is_dxtrade_password_not_set, + } = client; + const { is_from_derivgo } = common; + const { is_eu_user, financial_restricted_countries } = traders_hub; + const { is_dark_mode_on } = ui; React.useEffect(() => { - if (is_loading_mt5 === false && is_loading_dxtrade === false && is_social_signup !== undefined) { + if ( + is_populating_mt5_account_list === false && + is_populating_dxtrade_account_list === false && + is_social_signup !== undefined + ) { setIsLoading(false); } - }, [is_loading_mt5, is_loading_dxtrade, is_social_signup]); + }, [is_populating_mt5_account_list, is_populating_dxtrade_account_list, is_social_signup]); if (is_loading) { return ; @@ -61,36 +65,6 @@ const Passwords = ({ )} ); -}; +}); -Passwords.propTypes = { - email: PropTypes.string, - is_dark_mode_on: PropTypes.bool, - dxtrade_accounts_list: PropTypes.array, - is_social_signup: PropTypes.bool, - mt5_login_list: PropTypes.array, - social_identity_provider: PropTypes.string, - is_loading_mt5: PropTypes.bool, - is_loading_dxtrade: PropTypes.bool, - is_eu_user: PropTypes.bool, - financial_restricted_countries: PropTypes.bool, - is_mt5_password_not_set: PropTypes.bool, - is_dxtrade_password_not_set: PropTypes.bool, - is_from_derivgo: PropTypes.bool, -}; - -export default connect(({ client, ui, common, traders_hub }) => ({ - email: client.email, - is_dark_mode_on: ui.is_dark_mode_on, - is_social_signup: client.is_social_signup, - mt5_login_list: client.mt5_login_list, - dxtrade_accounts_list: client.dxtrade_accounts_list, - social_identity_provider: client.social_identity_provider, - is_eu_user: traders_hub.is_eu_user, - financial_restricted_countries: traders_hub.financial_restricted_countries, - is_loading_mt5: client.is_populating_mt5_account_list, - is_loading_dxtrade: client.is_populating_dxtrade_account_list, - is_mt5_password_not_set: client.is_mt5_password_not_set, - is_dxtrade_password_not_set: client.is_dxtrade_password_not_set, - is_from_derivgo: common.is_from_derivgo, -}))(Passwords); +export default Passwords; diff --git a/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.jsx b/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.jsx index 76609ebb4e7b..e0f4928efa53 100644 --- a/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.jsx +++ b/packages/account/src/Sections/Security/SelfExclusion/self-exclusion.jsx @@ -1,6 +1,5 @@ import React from 'react'; -import { PlatformContext, WS } from '@deriv/shared'; -import { connect } from 'Stores/connect'; +import { PlatformContext } from '@deriv/shared'; import SelfExclusionComponent from 'Components/self-exclusion/self-exclusion'; import 'Components/self-exclusion/self-exclusion.scss'; @@ -9,18 +8,4 @@ const SelfExclusion = props => { return ; }; -export default connect(({ client, ui }) => ({ - is_tablet: ui.is_tablet, - currency: client.currency, - is_virtual: client.is_virtual, - is_switching: client.is_switching, - is_cr: client.standpoint.svg, - is_eu: client.is_eu, - is_mlt: client.landing_company_shortcode === 'malta', - is_mf: client.landing_company_shortcode === 'maltainvest', - is_mx: client.landing_company_shortcode === 'iom', - is_uk: client.is_uk, - is_wrapper_bypassed: false, - logout: client.logout, - ws: WS, -}))(SelfExclusion); +export default SelfExclusion; diff --git a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.jsx b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.jsx index c18230f8aec6..4b805f37eec9 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.jsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.jsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import classNames from 'classnames'; import React from 'react'; import QRCode from 'qrcode.react'; @@ -14,20 +13,16 @@ import { } from '@deriv/components'; import { getPropertyValue, isMobile, PlatformContext, WS } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; import LoadErrorMessage from 'Components/load-error-message'; import DigitForm from './digit-form.jsx'; import TwoFactorAuthenticationArticle from './two-factor-authentication-article.jsx'; +import { observer, useStore } from '@deriv/stores'; -const TwoFactorAuthentication = ({ - email_address, - is_switching, - setTwoFAStatus, - getTwoFAStatus, - has_enabled_two_fa, - Notifications, - setTwoFAChangedStatus, -}) => { +const TwoFactorAuthentication = observer(() => { + const { client, ui } = useStore(); + const { email_address, getTwoFAStatus, has_enabled_two_fa, is_switching, setTwoFAStatus, setTwoFAChangedStatus } = + client; + const { notification_messages_ui: Notifications } = ui; const [is_loading, setLoading] = React.useState(true); const [is_qr_loading, setQrLoading] = React.useState(false); const [error_message, setErrorMessage] = React.useState(''); @@ -205,24 +200,6 @@ const TwoFactorAuthentication = ({ ); -}; +}); -TwoFactorAuthentication.propTypes = { - email_address: PropTypes.string, - is_switching: PropTypes.bool, - setTwoFAStatus: PropTypes.func, - getTwoFAStatus: PropTypes.func, - has_enabled_two_fa: PropTypes.bool, - Notifications: PropTypes.node, - setTwoFAChangedStatus: PropTypes.func, -}; - -export default connect(({ client, ui }) => ({ - email_address: client.email_address, - is_switching: client.is_switching, - setTwoFAStatus: client.setTwoFAStatus, - getTwoFAStatus: client.getTwoFAStatus, - has_enabled_two_fa: client.has_enabled_two_fa, - Notifications: ui.notification_messages_ui, - setTwoFAChangedStatus: client.setTwoFAChangedStatus, -}))(TwoFactorAuthentication); +export default TwoFactorAuthentication; diff --git a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx index aee2265a2ca7..b47e1d6190e6 100644 --- a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx +++ b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address-form.jsx @@ -24,7 +24,6 @@ import { getLocation, WS, } from '@deriv/shared'; -import { connect } from 'Stores/connect'; import FormFooter from 'Components/form-footer'; import FormBody from 'Components/form-body'; import FormBodySection from 'Components/form-body-section'; @@ -32,6 +31,7 @@ import FormSubHeader from 'Components/form-sub-header'; import LoadErrorMessage from 'Components/load-error-message'; import LeaveConfirm from 'Components/leave-confirm'; import FileUploaderContainer from 'Components/file-uploader-container'; +import { observer, useStore } from '@deriv/stores'; const validate = (errors, values) => (fn, arr, err_msg) => { arr.forEach(field => { @@ -53,18 +53,14 @@ const UploaderSideNote = () => ( ); -const ProofOfAddressForm = ({ - account_settings, - addNotificationByKey, - is_eu, - is_resubmit, - fetchResidenceList, - fetchStatesList, - onSubmit, - removeNotificationByKey, - removeNotificationMessage, - states_list, -}) => { +const ProofOfAddressForm = observer(({ is_resubmit, onSubmit }) => { + const { client, notifications } = useStore(); + const { account_settings, fetchResidenceList, fetchStatesList, is_eu, states_list } = client; + const { + addNotificationMessageByKey: addNotificationByKey, + removeNotificationMessage, + removeNotificationByKey, + } = notifications; const [document_file, setDocumentFile] = React.useState({ files: [], error_message: null }); const [is_loading, setIsLoading] = React.useState(true); const [form_values, setFormValues] = useStateCallback({}); @@ -458,28 +454,11 @@ const ProofOfAddressForm = ({ )} ); -}; +}); ProofOfAddressForm.propTypes = { - account_settings: PropTypes.object, - addNotificationByKey: PropTypes.func, - is_eu: PropTypes.bool, is_resubmit: PropTypes.bool, - fetchResidenceList: PropTypes.func, - fetchStatesList: PropTypes.func, onSubmit: PropTypes.func, - removeNotificationByKey: PropTypes.func, - removeNotificationMessage: PropTypes.func, - states_list: PropTypes.array, }; -export default connect(({ client, notifications }) => ({ - account_settings: client.account_settings, - is_eu: client.is_eu, - addNotificationByKey: notifications.addNotificationMessageByKey, - removeNotificationMessage: notifications.removeNotificationMessage, - removeNotificationByKey: notifications.removeNotificationByKey, - states_list: client.states_list, - fetchResidenceList: client.fetchResidenceList, - fetchStatesList: client.fetchStatesList, -}))(ProofOfAddressForm); +export default ProofOfAddressForm; diff --git a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address.jsx b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address.jsx index 7d558e882487..1b9e16ba3e4e 100644 --- a/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address.jsx +++ b/packages/account/src/Sections/Verification/ProofOfAddress/proof-of-address.jsx @@ -1,45 +1,26 @@ import DemoMessage from 'Components/demo-message'; import { PlatformContext } from '@deriv/shared'; import ProofOfAddressContainer from './proof-of-address-container.jsx'; -import { PropTypes } from 'prop-types'; import React from 'react'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; -const ProofOfAddress = ({ - is_virtual, - is_mx_mlt, - is_switching, - has_restricted_mt5_account, - refreshNotifications, - app_routing_history, -}) => { +const ProofOfAddress = observer(() => { + const { client, notifications, common } = useStore(); + const { app_routing_history } = common; + const { is_virtual, landing_company_shortcode, has_restricted_mt5_account, is_switching } = client; + const { refreshNotifications } = notifications; const { is_appstore } = React.useContext(PlatformContext); if (is_virtual) return ; return ( ); -}; +}); -ProofOfAddress.propTypes = { - is_mx_mlt: PropTypes.bool, - is_switching: PropTypes.bool, - is_virtual: PropTypes.bool, - refreshNotifications: PropTypes.func, - has_restricted_mt5_account: PropTypes.bool, -}; - -export default connect(({ client, notifications, common }) => ({ - is_mx_mlt: client.landing_company_shortcode === 'iom' || client.landing_company_shortcode === 'malta', - is_switching: client.is_switching, - is_virtual: client.is_virtual, - refreshNotifications: notifications.refreshNotifications, - has_restricted_mt5_account: client.has_restricted_mt5_account, - app_routing_history: common.app_routing_history, -}))(ProofOfAddress); +export default ProofOfAddress; diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity.jsx index ed975b1a027b..ebcbd6f37dcf 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity.jsx @@ -2,26 +2,25 @@ import { AutoHeightWrapper } from '@deriv/components'; import ProofOfIdentityContainer from './proof-of-identity-container.jsx'; import React from 'react'; import { changeMetaTagWithOG } from '@deriv/shared'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; import { withRouter } from 'react-router-dom'; -const ProofOfIdentity = ({ - account_settings, - account_status, - app_routing_history, - fetchResidenceList, - getChangeableFields, - is_from_external, - is_switching, - is_virtual, - is_high_risk, - is_withdrawal_lock, - onStateChange, - refreshNotifications, - routeBackInApp, - should_allow_authentication, - updateAccountStatus, -}) => { +const ProofOfIdentity = observer(({ is_from_external, onStateChange }) => { + const { client, common, notifications } = useStore(); + const { + account_status, + account_settings, + fetchResidenceList, + getChangeableFields, + is_switching, + is_high_risk, + is_withdrawal_lock, + should_allow_authentication, + is_virtual, + updateAccountStatus, + } = client; + const { refreshNotifications } = notifications; + const { app_routing_history, routeBackInApp } = common; // next useEffect implements seo requirements React.useEffect(() => { const description_content = 'Submit your proof of identity documents to verify your account and start trading'; @@ -65,20 +64,6 @@ const ProofOfIdentity = ({ )} ); -}; +}); -export default connect(({ client, common, notifications }) => ({ - account_settings: client.account_settings, - account_status: client.account_status, - app_routing_history: common.app_routing_history, - fetchResidenceList: client.fetchResidenceList, - getChangeableFields: client.getChangeableFields, - is_switching: client.is_switching, - is_virtual: client.is_virtual, - is_high_risk: client.is_high_risk, - is_withdrawal_lock: client.is_withdrawal_lock, - refreshNotifications: notifications.refreshNotifications, - routeBackInApp: common.routeBackInApp, - should_allow_authentication: client.should_allow_authentication, - updateAccountStatus: client.updateAccountStatus, -}))(withRouter(ProofOfIdentity)); +export default withRouter(ProofOfIdentity); diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js index f79ea6d2e251..6bf1b94608e2 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js @@ -2,80 +2,93 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { ProofOfOwnership } from '../proof-of-ownership.jsx'; import test_data from './test-data'; +import { StoreProvider, mockStore } from '@deriv/stores'; describe('proof-of-ownership.jsx', () => { let ownership_temp; beforeAll(() => { ownership_temp = test_data; }); + const ProofOfOwnershipScreen = () => { + return ( + + + + ); + }; + let store = mockStore(); it('should render no poo required status page', () => { - render( - - ); + }, + }, + }); + render(); + const element = screen.getByText("Your proof of ownership isn't required.", { exact: true }); expect(element).toBeInTheDocument(); }); it('should render poo verified status page', () => { - render( - - ); + }, + }, + }); + render(); + const element = screen.getByText('Proof of ownership verification passed.', { exact: true }); expect(element).toBeInTheDocument(); }); it('should render poo submitted status page', () => { - render( - - ); + }, + }, + }); + render(); + const element = screen.getByText('We’ve received your proof of ownership.', { exact: true }); expect(element).toBeInTheDocument(); }); it('should render poo rejected status page', () => { - render( - - ); + }, + }, + }); + render(); + const element = screen.getByTestId('dt_try-again-button', { exact: true }); expect(element).toBeInTheDocument(); }); it('should render ProofOfOwnershipForm', () => { - render( - - ); + }, + }, + }); + render(); expect(screen.getByTestId('dt_poo_form', { exact: true })).toBeInTheDocument(); }); }); diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx b/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx index e8c45c71728e..b0ce9a0cde8d 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx @@ -1,19 +1,17 @@ import React, { useEffect, useState } from 'react'; import { withRouter } from 'react-router-dom'; -import { connect } from 'Stores/connect'; import ProofOfOwnershipForm from './proof-of-ownership-form.jsx'; import { POONotRequired, POOVerified, POORejetced, POOSubmitted } from 'Components/poo/statuses'; import { Loading } from '@deriv/components'; import { POO_STATUSES } from './constants/constants'; import getPaymentMethodsConfig from './payment-method-config.js'; +import { observer, useStore } from '@deriv/stores'; -export const ProofOfOwnership = ({ - account_status, - client_email, - is_dark_mode, - refreshNotifications, - updateAccountStatus, -}) => { +export const ProofOfOwnership = observer(() => { + const { client, notifications, ui } = useStore(); + const { account_status, email: client_email, updateAccountStatus } = client; + const { refreshNotifications } = notifications; + const { is_dark_mode_on: is_dark_mode } = ui; const cards = account_status?.authentication?.ownership?.requests; const [status, setStatus] = useState(POO_STATUSES.none); const grouped_payment_method_data = React.useMemo(() => { @@ -68,12 +66,6 @@ export const ProofOfOwnership = ({ return ; // Proof of ownership rejected } return ; -}; +}); -export default connect(({ client, notifications, ui }) => ({ - account_status: client.account_status, - client_email: client.email, - is_dark_mode: ui.is_dark_mode_on, - refreshNotifications: notifications.refreshNotifications, - updateAccountStatus: client.updateAccountStatus, -}))(withRouter(ProofOfOwnership)); +export default withRouter(ProofOfOwnership); diff --git a/packages/account/src/Stores/connect.js b/packages/account/src/Stores/connect.js deleted file mode 100644 index 4ef42c8d18b6..000000000000 --- a/packages/account/src/Stores/connect.js +++ /dev/null @@ -1,31 +0,0 @@ -import { useObserver } from 'mobx-react'; -import React from 'react'; - -const isClassComponent = Component => - !!(typeof Component === 'function' && Component.prototype && Component.prototype.isReactComponent); - -export const MobxContent = React.createContext(null); - -function injectStorePropsToComponent(propsToSelectFn, BaseComponent) { - const Component = own_props => { - const store = React.useContext(MobxContent); - - let ObservedComponent = BaseComponent; - - if (isClassComponent(BaseComponent)) { - const FunctionalWrapperComponent = props => ; - ObservedComponent = FunctionalWrapperComponent; - } - - return useObserver(() => ObservedComponent({ ...own_props, ...propsToSelectFn(store, own_props) })); - }; - - Component.displayName = BaseComponent.name; - return Component; -} - -export const MobxContentProvider = ({ store, children }) => { - return {children}; -}; - -export const connect = propsToSelectFn => Component => injectStorePropsToComponent(propsToSelectFn, Component); diff --git a/packages/account/src/Stores/index.ts b/packages/account/src/Stores/index.ts deleted file mode 100644 index 9797a63b3b03..000000000000 --- a/packages/account/src/Stores/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export type TCoreStore = { - client: Record; - common: Record; - ui: Record; - gtm: Record; - rudderstack: Record; - pushwoosh: Record; -}; - -export default class RootStore { - public client: Record; - public common: Record; - public ui: Record; - public gtm: Record; - public rudderstack: Record; - public pushwoosh: Record; - - constructor(core_store: TCoreStore) { - this.client = core_store.client; - this.common = core_store.common; - this.ui = core_store.ui; - this.gtm = core_store.gtm; - this.rudderstack = core_store.rudderstack; - this.pushwoosh = core_store.pushwoosh; - } -} diff --git a/packages/account/src/Stores/init-store.js b/packages/account/src/Stores/init-store.js deleted file mode 100644 index 78df16195a36..000000000000 --- a/packages/account/src/Stores/init-store.js +++ /dev/null @@ -1,12 +0,0 @@ -import { configure } from 'mobx'; -import { setWebsocket } from '@deriv/shared'; -import RootStore from 'Stores'; - -configure({ enforceActions: 'observed' }); - -const initStore = (core_store, websocket) => { - setWebsocket(websocket); - return new RootStore(core_store); -}; - -export default initStore; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index e837c5439a6a..ce2e1965a666 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -5,11 +5,28 @@ const mock = (): TStores & { is_mock: boolean } => { return { is_mock: true, client: { + fetchResidenceList: jest.fn(), + fetchStatesList: jest.fn(), + getChangeableFields: jest.fn(), + residence_list: [ + { + text: 'Text', + value: 'value', + }, + ], + states_list: [ + { + text: 'Text', + value: 'value', + }, + ], account_settings: {}, accounts: {}, + is_social_signup: false, active_account_landing_company: '', trading_platform_available_accounts: [], account_limits: { + account_balance: 300000, daily_transfers: { dxtrade: { allowed: 0, @@ -24,6 +41,68 @@ const mock = (): TStores & { is_mock: boolean } => { available: 0, }, }, + lifetime_limit: 13907.43, + market_specific: { + commodities: [ + { + name: 'Commodities', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + ], + cryptocurrency: [ + { + name: 'Cryptocurrencies', + payout_limit: 100.0, + profile_name: 'extreme_risk', + turnover_limit: 1000.0, + }, + ], + forex: [ + { + name: 'Smart FX', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + { + name: 'Major Pairs', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + { + name: 'Minor Pairs', + payout_limit: 5000, + profile_name: 'moderate_risk', + turnover_limit: 50000, + }, + ], + indices: [ + { + name: 'Stock Indices', + payout_limit: 20000, + profile_name: 'medium_risk', + turnover_limit: 100000, + }, + ], + synthetic_index: [ + { + name: 'Synthetic Indices', + payout_limit: 50000, + profile_name: 'low_risk', + turnover_limit: 500000, + }, + ], + }, + num_of_days: 30, + num_of_days_limit: 13907.43, + open_positions: 100, + payout: 50000, + remainder: 13907.43, + withdrawal_for_x_days_monetary: 0, + withdrawal_since_inception_monetary: 0, }, account_status: { authentication: { @@ -49,6 +128,7 @@ const mock = (): TStores & { is_mock: boolean } => { document: { status: 'verified', }, + identity: { services: { idv: { @@ -113,7 +193,8 @@ const mock = (): TStores & { is_mock: boolean } => { current_fiat_currency: '', cfd_score: 0, setCFDScore: jest.fn(), - getLimits: jest.fn(), + getLimits: jest.fn(() => Promise.resolve({ get_limits: {} })), + has_any_real_account: false, has_active_real_account: false, has_logged_out: false, has_maltainvest_account: false, @@ -123,6 +204,10 @@ const mock = (): TStores & { is_mock: boolean } => { is_deposit_lock: false, is_dxtrade_allowed: false, is_eu: false, + is_eu_country: false, + is_uk: false, + has_residence: false, + is_fully_authenticated: false, is_financial_account: false, is_financial_information_incomplete: false, is_low_risk: false, @@ -149,7 +234,11 @@ const mock = (): TStores & { is_mock: boolean } => { responseTradingPlatformAccountsList: jest.fn(), standpoint: { iom: '', + svg: '', malta: '', + maltainvest: '', + gaming_company: '', + financial_company: '', }, switchAccount: jest.fn(), verification_code: { @@ -195,7 +284,6 @@ const mock = (): TStores & { is_mock: boolean } => { setTwoFAStatus: jest.fn(), has_changed_two_fa: false, setTwoFAChangedStatus: jest.fn(), - has_any_real_account: false, real_account_creation_unlock_date: 0, setPrevAccountType: jest.fn(), }, @@ -212,6 +300,8 @@ const mock = (): TStores & { is_mock: boolean } => { redirectOnClick: jest.fn(), setError: jest.fn(), }, + current_language: 'EN', + isCurrentLanguage: jest.fn(), is_from_derivgo: false, has_error: false, platform: '', @@ -219,7 +309,6 @@ const mock = (): TStores & { is_mock: boolean } => { routeTo: jest.fn(), changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), - current_language: 'EN', is_network_online: false, server_time: undefined, is_language_changing: false, @@ -259,7 +348,9 @@ const mock = (): TStores & { is_mock: boolean } => { toggleReports: jest.fn(), setSubSectionIndex: jest.fn(), sub_section_index: 0, + toggleShouldShowRealAccountsList: jest.fn(), toggleReadyToDepositModal: jest.fn(), + is_tablet: false, is_ready_to_deposit_modal_visible: false, is_real_acc_signup_on: false, is_need_real_account_for_cashier_modal_visible: false, @@ -310,6 +401,7 @@ const mock = (): TStores & { is_mock: boolean } => { setP2POrderProps: jest.fn(), showAccountSwitchToRealNotification: jest.fn(), setP2PRedirectTo: jest.fn(), + addNotificationMessageByKey: jest.fn(), }, portfolio: { active_positions: [], diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 0b33c95fd546..b95da3267e84 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -6,6 +6,8 @@ import type { GetLimits, GetSettings, LogOutResponse, + ResidenceList, + StatesList, ProposalOpenContract, } from '@deriv/api-types'; import type { Moment } from 'moment'; @@ -18,6 +20,33 @@ type TPopulateSettingsExtensionsMenuItem = { value: (props: T) => JSX.Element; }; +type TAccountLimitsCollection = { + level?: string; + name: string; + payout_limit: number; + profile_name: string; + turnover_limit: number; +}; +type TAccount_limits = { + api_initial_load_error?: string; + open_positions?: React.ReactNode; + account_balance: string | number; + daily_transfers?: object; + payout: string | number; + lifetime_limit?: number; + market_specific: { + commodities: TAccountLimitsCollection[]; + cryptocurrency: TAccountLimitsCollection[]; + forex: TAccountLimitsCollection[]; + indices: TAccountLimitsCollection[]; + synthetic_index: TAccountLimitsCollection[]; + }; + num_of_days?: number; + num_of_days_limit: string | number; + remainder: string | number; + withdrawal_for_x_days_monetary?: number; + withdrawal_since_inception_monetary: string | number; +}; type TAccount = NonNullable[0] & { balance?: number; }; @@ -128,6 +157,9 @@ type TNotification = | ((excluded_until: number) => TNotificationMessage); type TClientStore = { + fetchResidenceList: () => Promise; + fetchStatesList: () => Promise; + getChangeableFields: () => string[]; accounts: { [k: string]: TActiveAccount }; active_accounts: TActiveAccount[]; active_account_landing_company: string; @@ -145,8 +177,11 @@ type TClientStore = { cfd_score: number; setCFDScore: (score: number) => void; currency: string; + residence_list: ResidenceList; + states_list: StatesList; current_currency_type?: string; current_fiat_currency?: string; + has_any_real_account: boolean; getLimits: () => Promise<{ get_limits?: GetLimits }>; has_active_real_account: boolean; has_logged_out: boolean; @@ -156,6 +191,10 @@ type TClientStore = { is_deposit_lock: boolean; is_dxtrade_allowed: boolean; is_eu: boolean; + is_eu_country: boolean; + is_uk: boolean; + is_social_signup: boolean; + has_residence: boolean; is_authorize: boolean; is_financial_account: boolean; is_financial_information_incomplete: boolean; @@ -191,7 +230,11 @@ type TClientStore = { }) => DetailsOfEachMT5Loginid[]; standpoint: { iom: string; + svg: string; malta: string; + maltainvest: string; + gaming_company: string; + financial_company: string; }; setAccountStatus: (status?: GetAccountStatus) => void; setBalanceOtherAccounts: (balance: number) => void; @@ -231,7 +274,7 @@ type TClientStore = { setTwoFAStatus: (status: boolean) => void; has_changed_two_fa: boolean; setTwoFAChangedStatus: (status: boolean) => void; - has_any_real_account: boolean; + is_fully_authenticated: boolean; real_account_creation_unlock_date: number; setPrevAccountType: (account_type: string) => void; }; @@ -250,6 +293,7 @@ type TCommonStoreError = { }; type TCommonStore = { + isCurrentLanguage(language_code: string): boolean; error: TCommonStoreError; has_error: boolean; is_from_derivgo: boolean; @@ -277,6 +321,8 @@ type TUiStore = { is_reports_visible: boolean; is_language_settings_modal_on: boolean; is_mobile: boolean; + sub_section_index: number; + toggleShouldShowRealAccountsList: (value: boolean) => void; openRealAccountSignup: ( value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' ) => void; @@ -286,7 +332,6 @@ type TUiStore = { setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; - sub_section_index: number; setSubSectionIndex: (index: number) => void; shouldNavigateAfterChooseCrypto: (value: string) => void; toggleAccountsDialog: () => void; @@ -294,6 +339,7 @@ type TUiStore = { toggleLanguageSettingsModal: () => void; toggleReadyToDepositModal: () => void; toggleSetCurrencyModal: () => void; + is_tablet: boolean; removeToast: (key: string) => void; is_ready_to_deposit_modal_visible: boolean; reports_route_tab_index: number; @@ -332,12 +378,13 @@ type TMenuStore = { }; type TNotificationStore = { + addNotificationMessageByKey: (key: string) => void; addNotificationMessage: (message: TNotification) => void; client_notifications: object; filterNotificationMessages: () => void; refreshNotifications: () => void; - removeNotificationByKey: (obj: { key: string }) => void; - removeNotificationMessage: (obj: { key: string; should_show_again?: boolean }) => void; + removeNotificationByKey: (key: string) => void; + removeNotificationMessage: (key: string, should_show_again?: boolean) => void; setP2POrderProps: () => void; showAccountSwitchToRealNotification: (loginid: string, currency: string) => void; setP2PRedirectTo: () => void;