diff --git a/packages/account/src/Components/account-limits/account-limits.jsx b/packages/account/src/Components/account-limits/account-limits.jsx index c98aa33ea542..1871c9aa832c 100644 --- a/packages/account/src/Components/account-limits/account-limits.jsx +++ b/packages/account/src/Components/account-limits/account-limits.jsx @@ -97,7 +97,14 @@ const AccountLimits = ({ const { commodities, forex, indices, synthetic_index } = { ...market_specific }; const forex_ordered = forex?.slice().sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)); - const derived_ordered = synthetic_index?.slice().sort((b, a) => (a.level < b.level ? 1 : -1)); + // sort submarkets by names alphabetically and put 'market' at the beginning + const derived_ordered = synthetic_index + ?.slice() + .sort((a, b) => + a.level === 'submarket' && b.level === 'submarket' + ? a.name.localeCompare(b.name) + : a.level.localeCompare(b.level) + ); const context_value = { currency, diff --git a/packages/account/src/Components/api-token/api-token.tsx b/packages/account/src/Components/api-token/api-token.tsx index d1e7d45f3de5..0c6dbb2e9170 100644 --- a/packages/account/src/Components/api-token/api-token.tsx +++ b/packages/account/src/Components/api-token/api-token.tsx @@ -224,7 +224,7 @@ const ApiToken = ({ footer_ref, is_app_settings, is_switching, overlay_ref, setI setFieldTouched, }) => (
- + 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 a942d8fdde06..e178b40218a9 100644 --- a/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.jsx +++ b/packages/account/src/Sections/Security/TwoFactorAuthentication/two-factor-authentication.jsx @@ -112,7 +112,7 @@ const TwoFactorAuthentication = ({ {localize('How to set up 2FA for your Deriv account')}
- + { + Object.entries(values).forEach(([key, value]) => (values[key] = value.trim())); + setFormState({ ...form_state, ...{ should_allow_submit: false } }); const errors = {}; const validateValues = validate(errors, values); diff --git a/packages/appstore/package.json b/packages/appstore/package.json index 90eb468d93b4..9eb5b8d600d7 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -29,6 +29,7 @@ "@deriv/components": "^1.0.0", "@deriv/cfd": "^1.0.0", "@deriv/shared": "^1.0.0", + "@deriv/stores": "^1.0.0", "@deriv/trader": "^3.8.0", "@deriv/translations": "^1.0.0", "@deriv/ui": "^0.0.15", diff --git a/packages/appstore/src/components/add-options-account/add-options-account.tsx b/packages/appstore/src/components/add-options-account/add-options-account.tsx index fe0551b1392d..b56847b5827e 100644 --- a/packages/appstore/src/components/add-options-account/add-options-account.tsx +++ b/packages/appstore/src/components/add-options-account/add-options-account.tsx @@ -12,7 +12,7 @@ const AddOptions = ({ ui }: Pick) => { const { is_eu } = client; const is_eu_country_text = is_eu ? 'You need to create a Multipliers account to create a CFD account.' - : 'You need to create an Options and Multipliers account to create a CFD account.'; + : 'You need to create an Options and Multipliers account to add a CFD account.'; const is_eu_country_btn = is_eu ? localize('Get a Multipliers account') diff --git a/packages/appstore/src/components/app.tsx b/packages/appstore/src/components/app.tsx index 9e473be6cfa5..4376a080c4e6 100644 --- a/packages/appstore/src/components/app.tsx +++ b/packages/appstore/src/components/app.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames'; import * as React from 'react'; import { observer } from 'mobx-react-lite'; import { setWebsocket, routes } from '@deriv/shared'; +import { StoreProvider } from '@deriv/stores'; import Routes from 'Components/routes/routes'; import { useStores, initContext } from 'Stores'; import { TRootStore } from 'Types'; @@ -21,17 +22,19 @@ const App: React.FC = ({ passthrough }: TAppProps) => { const { ui }: TRootStore = useStores(); return ( -
-
- -
-
+ +
+
+ +
+
+
); }; diff --git a/packages/appstore/src/modules/trading-hub/index.tsx b/packages/appstore/src/modules/trading-hub/index.tsx index d7fe1d49a9f9..93100b4b1cac 100644 --- a/packages/appstore/src/modules/trading-hub/index.tsx +++ b/packages/appstore/src/modules/trading-hub/index.tsx @@ -22,11 +22,11 @@ import { CFDDbviOnBoarding, CFDPersonalDetailsModal, CFDResetPasswordModal, - CFDServerErrorDialog, CFDTopUpDemoModal, MT5TradeModal, CFDPasswordManagerModal, } from '@deriv/cfd'; +import CFDServerErrorDialog from '@deriv/cfd/src/Containers/cfd-server-error-dialog'; import CFDAccounts from 'Components/CFDs'; import OptionsAccounts from 'Components/options'; import TotalAssets from 'Components/total-assets'; @@ -283,7 +283,7 @@ const TradingHub: React.FC = () => { - + React.useContext(stores_context); diff --git a/packages/appstore/webpack.config.js b/packages/appstore/webpack.config.js index d48be4877300..83623bc3a4a6 100644 --- a/packages/appstore/webpack.config.js +++ b/packages/appstore/webpack.config.js @@ -75,9 +75,6 @@ module.exports = function (env) { test: /\.(js|jsx|ts|tsx)$/, exclude: /node_modules/, use: [ - { - loader: '@deriv/shared/src/loaders/react-import-loader.js', - }, { loader: '@deriv/shared/src/loaders/deriv-trader-loader.js', }, diff --git a/packages/cashier/src/constants/constants.js b/packages/cashier/src/constants/constants.js index 9faf281351c6..1e809a460091 100644 --- a/packages/cashier/src/constants/constants.js +++ b/packages/cashier/src/constants/constants.js @@ -177,6 +177,8 @@ const payment_methods = { 'Local bank transfer', 'Local bank Transfers', 'Local deposits', + 'Local deposit', + 'local deposits', 'BANKTRANSFERS', 'Bank Deposits', 'Bank deposit and transfer', @@ -187,6 +189,7 @@ const payment_methods = { 'OnlineTransfer', 'ZWbanktransfers', 'localbanktransfer', + 'Bank transfer Bank wire', ], 'Bank wire': [ 'bank', @@ -212,11 +215,13 @@ const payment_methods = { 'E-wallets and bank wires', 'local bank wire', 'LocalBankWire', + 'LocalBankwire', 'Wire Bank Transfer', 'WIRE TRANSFER', 'Bank Wire transfer', 'Fiat', 'OnlineTransfer', + 'Bank transfer Bank wire', ], BankABC: ['BankABC', 'BANCABC BANK'], 'Bank BTN': ['Bank BTN', 'BTN'], @@ -270,7 +275,7 @@ const payment_methods = { 'cash send Absa', 'Cash send Absa bank', ], - 'Chipper Cash': ['Chipper Cash', 'Chipper', 'Chippercash'], + 'Chipper Cash': ['Chipper Cash', 'Chipper', 'Chippercash', 'Chipperchash'], 'CIH Bank': ['CIH Bank', 'Cih Bank'], CIMB: ['CIMB', 'CIMBNIAGA', 'NIAGA'], 'City Hopper': ['City hopper', 'City Hopper'], @@ -290,6 +295,7 @@ const payment_methods = { 'Cripto', 'crypto', 'Crypto', + 'Cyptocurrency', 'cryptocurrencies', 'Cryptocurrencies', 'Crypto Currencies', @@ -503,8 +509,9 @@ const payment_methods = { 'KudaMFB', 'KUDA MICROFINANCE BANK 2014563937', 'Kuda Microfinance', + 'KUDA', ], - 'Luno Wallet': ['Luno Wallet', 'Luno', 'Luno crypto wallet', 'Luno e-wallet', 'Luno ewallet'], + 'Luno Wallet': ['Luno Wallet', 'Luno', 'Luno crypto wallet', 'Luno e-wallet', 'Luno ewallet', 'luno'], 'Mandiri Bank': ['Mandiri Bank', 'Mandiri', 'MANDIRI', 'MandiriSyariah', 'BankMandiri'], 'Meezan Bank': ['Meezan Bank', 'MeezanBank'], 'Millenium Bim Visa': ['Millenium Bim Visa', 'Millenium Bim'], @@ -742,17 +749,18 @@ const payment_methods = { 'Ussd transfer', 'USSD transfer', 'USSD Transfer', + 'ussd transfer', ], Vodacom: ['Vodacom', 'VodacomMpesa'], 'Vodafone Cash': ['Vodafon Cash Methods', 'Vodafone cash', 'Vodafone Cash', 'VODAFONE CASH', 'VODAFONECASH'], - Webmoney: ['Perfect Money and Webmoney', 'Webmoney', 'WebMoney', 'Web Money'], + Webmoney: ['Perfect Money and Webmoney', 'Webmoney', 'WebMoney', 'Web Money', 'Web money'], 'WeChat Pay': ['WeChat Pay', 'WeChatPay'], 'Wema Bank': ['Wema Bank', 'Wema'], WesternUnion: ['Western union', 'Western Union', 'WesternUnion', 'westernunion'], Wise: ['Wise', 'transferwise'], 'World Remit': ['World remit', 'World Remit', 'CoinbaseworldRemit'], 'Zanaco bank': ['ZANACO', 'Zanaco bank'], - 'Zenith bank': ['Zenith bank', 'Zenithbank', 'Zenith Bank', 'ZenithBank', 'ZENITH BANK', 'Zenith'], + 'Zenith bank': ['Zenith bank', 'Zenithbank', 'Zenith Bank', 'ZenithBank', 'ZENITH BANK', 'Zenith', 'zenithbank'], Zipit: ['Zipit', 'ZIPIT', 'ZIPIT bank transfers'], }; diff --git a/packages/cashier/src/containers/routes/__tests__/route-with-sub-routes.spec.tsx b/packages/cashier/src/containers/routes/__tests__/route-with-sub-routes.spec.tsx index 9a6f81c30e2b..e9f7065abac0 100644 --- a/packages/cashier/src/containers/routes/__tests__/route-with-sub-routes.spec.tsx +++ b/packages/cashier/src/containers/routes/__tests__/route-with-sub-routes.spec.tsx @@ -1,48 +1,48 @@ -// TODO refactor old tests in this component import React from 'react'; -import { RouteWithSubRoutesRender } from '../route-with-sub-routes'; -import { MemoryRouter, Redirect } from 'react-router-dom'; -import { render } from '@testing-library/react'; +import { Redirect } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import RouteWithSubRoutes from '../route-with-sub-routes'; + +type TMockFunction = { + path: string; + exact?: boolean; +}; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + Route: jest.fn(({ path, exact }: TMockFunction) => ( +
+ {`path param: ${path}`} + {`exact param: ${exact}`} +
+ )), +})); + +afterEach(() => jest.clearAllMocks()); -const mockFunction = jest.fn(); const route = { - getTitle: mockFunction, + getTitle: jest.fn(), component: Redirect, is_logging_in: true, is_logged_in: true, exact: true, - path: '/', - to: '/root', + path: '/test-path', }; -const MockComponent = () => ( - - - -); +const MockRouteWithSubRoutes = () => ; -describe('', () => { - it('should render one component', () => { - render(); +describe('RouteWithSubRoutes component', () => { + it('should render the "RouteWithSubRoutes" component', () => { + render(); + const span_element = screen.getByText(/path param: \/test-path/i); + expect(span_element).toBeInTheDocument(); }); -}); -// import React from 'react'; -// import { RouteWithSubRoutesRender } from '../route-with-sub-routes'; -// import { Redirect } from 'react-router-dom'; - -// configure({ adapter: new Adapter() }); - -// describe('', () => { -// it('should render one component', () => { -// const wrapper = shallow(); -// expect(wrapper).toHaveLength(1); -// }); -// it('should have props as passed as route', () => { -// const route = { path: '/', component: Redirect, title: '', exact: true, to: '/root' }; -// const wrapper = shallow(); -// expect(wrapper.prop('exact')).toBe(true); -// expect(wrapper.prop('path')).toBe('/'); -// expect(wrapper.prop('render')).toBeInstanceOf(Function); -// }); -// }); + it('should render properties', () => { + render(); + const path_param = screen.getByText(/\/test-path/i); + const exact_param = screen.getByText(/exact param: true/i); + expect(path_param).toBeInTheDocument(); + expect(exact_param).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/containers/routes/route-with-sub-routes.tsx b/packages/cashier/src/containers/routes/route-with-sub-routes.tsx index 36a985b0615e..7563ea28ac7f 100644 --- a/packages/cashier/src/containers/routes/route-with-sub-routes.tsx +++ b/packages/cashier/src/containers/routes/route-with-sub-routes.tsx @@ -56,6 +56,4 @@ const RouteWithSubRoutes = (route: TRouteWithSubRoutesProps) => { return ; }; -export { RouteWithSubRoutes as RouteWithSubRoutesRender }; // For tests - export default RouteWithSubRoutes; diff --git a/packages/cashier/src/pages/payment-agent/payment-agent-card/payment-agent-card-description.jsx b/packages/cashier/src/pages/payment-agent/payment-agent-card/payment-agent-card-description.jsx index 15d970b8894d..b75f70119fde 100644 --- a/packages/cashier/src/pages/payment-agent/payment-agent-card/payment-agent-card-description.jsx +++ b/packages/cashier/src/pages/payment-agent/payment-agent-card/payment-agent-card-description.jsx @@ -21,7 +21,10 @@ const PaymentAgentCardDescription = ({ is_dark_mode_on, payment_agent }) => { line_height='s' size='xs' > - {capitalizeFirstLetter(payment_agent.further_information)} + {capitalizeFirstLetter(payment_agent.further_information).replace( + /( ?Skril?l,? ?)|( ?Net?tel?ler,? ?)/gi, + '' + )} )} {payment_agent_urls && ( diff --git a/packages/cashier/src/pages/payment-agent/payment-agent-list/withdrawal-tab.tsx b/packages/cashier/src/pages/payment-agent/payment-agent-list/withdrawal-tab.tsx index 868313b20a5c..cc5feba24bce 100644 --- a/packages/cashier/src/pages/payment-agent/payment-agent-list/withdrawal-tab.tsx +++ b/packages/cashier/src/pages/payment-agent/payment-agent-list/withdrawal-tab.tsx @@ -15,7 +15,11 @@ const WithdrawalTab = observer(() => { if (payment_agent.active_tab_index && !verification_code) { verify.send(); } - }, [payment_agent.active_tab_index, verification_code, verify]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [payment_agent.active_tab_index, verification_code]); + // TODO: `verify` should not be a dependency of the `useEffect` hook as it will cause a loop, + // We shouldn't call `verify.send()` inside the `useEffect` and we should improve the UX to + // match the behavior of the `Withdrawal` page and first inform the user. if (verify.error && 'code' in verify.error) return ; if (!verify.is_loading && verify.has_been_sent) diff --git a/packages/cfd/@deriv-stores.d.ts b/packages/cfd/@deriv-stores.d.ts new file mode 100644 index 000000000000..1a3e2b075288 --- /dev/null +++ b/packages/cfd/@deriv-stores.d.ts @@ -0,0 +1,10 @@ +import type { TRootStore } from '@deriv/stores/types'; +import type CFDStore from './src/Stores/Modules/CFD/cfd-store'; + +declare module '@deriv/stores' { + export function useStore(): TRootStore & { + modules: { + cfd: CFDStore; + }; + }; +} diff --git a/packages/cfd/build/loaders-config.js b/packages/cfd/build/loaders-config.js index 244d0aa2d1b8..4024a6b51072 100644 --- a/packages/cfd/build/loaders-config.js +++ b/packages/cfd/build/loaders-config.js @@ -2,9 +2,6 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const path = require('path'); const js_loaders = [ - { - loader: '@deriv/shared/src/loaders/react-import-loader.js', - }, { loader: '@deriv/shared/src/loaders/deriv-account-loader.js', }, diff --git a/packages/cfd/package.json b/packages/cfd/package.json index 3566a79cf27c..8f62dbe676da 100644 --- a/packages/cfd/package.json +++ b/packages/cfd/package.json @@ -83,6 +83,7 @@ "@deriv/components": "^1.0.0", "@deriv/deriv-api": "^1.0.8", "@deriv/shared": "^1.0.0", + "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", "@types/classnames": "^2.2.11", "@types/react-loadable": "^5.5.6", diff --git a/packages/cfd/src/Components/Routes/__tests__/route-with-sub-routes.spec.js b/packages/cfd/src/Components/Routes/__tests__/route-with-sub-routes.spec.js deleted file mode 100644 index 608b86a24c20..000000000000 --- a/packages/cfd/src/Components/Routes/__tests__/route-with-sub-routes.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -// TODO refactor old tests in this component -import React from 'react'; -import { RouteWithSubRoutesRender } from '../route-with-sub-routes'; -import { Redirect, MemoryRouter } from 'react-router-dom'; -import { PlatformContext } from '@deriv/shared'; -import { render } from '@testing-library/react'; - -const mockFunction = jest.fn(); -const route = { - getTitle: mockFunction, - component: Redirect, - is_logging_in: true, - is_logged_in: true, - exact: true, - path: '/', - to: '/root', -}; - -const MockComponent = () => ( - - - -); - -describe('', () => { - it('should render one component', () => { - render(); - }); -}); - -// configure({ adapter: new Adapter() }); - -// describe('', () => { -// it('should render one component', () => { -// const comp = ( -// -// -// -// ); -// const wrapper = shallow(comp); -// expect(wrapper).toHaveLength(1); -// }); -// it('should have props as passed as route', () => { -// const route = { path: '/', component: Redirect, title: '', exact: true, to: '/root' }; -// const comp = ( -// -// -// -// ); -// const wrapper = shallow(comp); -// expect(wrapper.prop('exact')).toBe(true); -// expect(wrapper.prop('path')).toBe('/'); -// }); -// }); diff --git a/packages/cfd/src/Components/Routes/__tests__/route-with-sub-routes.spec.tsx b/packages/cfd/src/Components/Routes/__tests__/route-with-sub-routes.spec.tsx new file mode 100644 index 000000000000..e9f7065abac0 --- /dev/null +++ b/packages/cfd/src/Components/Routes/__tests__/route-with-sub-routes.spec.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import RouteWithSubRoutes from '../route-with-sub-routes'; + +type TMockFunction = { + path: string; + exact?: boolean; +}; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + Route: jest.fn(({ path, exact }: TMockFunction) => ( +
+ {`path param: ${path}`} + {`exact param: ${exact}`} +
+ )), +})); + +afterEach(() => jest.clearAllMocks()); + +const route = { + getTitle: jest.fn(), + component: Redirect, + is_logging_in: true, + is_logged_in: true, + exact: true, + path: '/test-path', +}; + +const MockRouteWithSubRoutes = () => ; + +describe('RouteWithSubRoutes component', () => { + it('should render the "RouteWithSubRoutes" component', () => { + render(); + const span_element = screen.getByText(/path param: \/test-path/i); + expect(span_element).toBeInTheDocument(); + }); + + it('should render properties', () => { + render(); + const path_param = screen.getByText(/\/test-path/i); + const exact_param = screen.getByText(/exact param: true/i); + expect(path_param).toBeInTheDocument(); + expect(exact_param).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Components/Routes/route-with-sub-routes.jsx b/packages/cfd/src/Components/Routes/route-with-sub-routes.jsx index 5b4d7ad1ec08..e4924ce54da9 100644 --- a/packages/cfd/src/Components/Routes/route-with-sub-routes.jsx +++ b/packages/cfd/src/Components/Routes/route-with-sub-routes.jsx @@ -45,6 +45,4 @@ const RouteWithSubRoutes = route => { return ; }; -export { RouteWithSubRoutes as RouteWithSubRoutesRender }; // For tests - export default RouteWithSubRoutes; diff --git a/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx b/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx index 8d021ae46337..aed9aa885c16 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx +++ b/packages/cfd/src/Containers/__tests__/cfd-dashboard.spec.tsx @@ -59,6 +59,7 @@ jest.mock('../../Components/success-dialog.jsx', () => () => 'SuccessDialog'); jest.mock('../cfd-password-modal.tsx', () => props => props.is_cfd_password_modal_enabled ? 'CFDPasswordModal' : ''); jest.mock('../cfd-top-up-demo-modal', () => props => props.is_top_up_virtual_open ? 'CFDTopUpDemoModal' : ''); jest.mock('../cfd-personal-details-modal', () => () => 'CFDPersonalDetailsModal'); +jest.mock('../cfd-server-error-dialog.tsx', () => () => 'CFDServerErrorDialog'); jest.mock('../mt5-trade-modal', () => props => props.is_open ? 'MT5TradeModal' : ''); jest.mock( '../jurisdiction-modal/jurisdiction-modal', diff --git a/packages/cfd/src/Containers/__tests__/cfd-server-error-dialog.spec.js b/packages/cfd/src/Containers/__tests__/cfd-server-error-dialog.spec.js index 80083528ad35..41034fcabac9 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-server-error-dialog.spec.js +++ b/packages/cfd/src/Containers/__tests__/cfd-server-error-dialog.spec.js @@ -1,5 +1,6 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; +import { StoreProvider } from '@deriv/stores'; import CFDServerErrorDialog from '../cfd-server-error-dialog'; jest.mock('Stores/connect.js', () => ({ @@ -19,40 +20,128 @@ describe(' ', () => { document.body.removeChild(modal_root_el); }); - const mock_props = { - clearCFDError: jest.fn(), - error_type: 'error_type', - has_cfd_error: true, - is_cfd_success_dialog_enabled: false, - error_message: 'Sorry, an error occured while processing your request.', - }; - it('should render the component properly', () => { - const { container } = render(); + const mockRootStore = { + ui: { + disableApp: jest.fn(), + enableApp: jest.fn(), + }, + modules: { + cfd: { + clearCFDError: jest.fn(), + error_type: 'error_type', + has_cfd_error: true, + is_cfd_success_dialog_enabled: false, + error_message: 'Sorry, an error occured while processing your request.', + }, + }, + }; + + const { container } = render(, { + wrapper: ({ children }) => {children}, + }); + expect(container.firstChild).toHaveClass('dc-dialog__wrapper dc-dialog__wrapper--enter'); }); it('should render the proper text and error message', () => { - render(); + const mockRootStore = { + ui: { + disableApp: jest.fn(), + enableApp: jest.fn(), + }, + modules: { + cfd: { + clearCFDError: jest.fn(), + error_type: 'error_type', + has_cfd_error: true, + is_cfd_success_dialog_enabled: false, + error_message: 'Sorry, an error occured while processing your request.', + }, + }, + }; + + render(, { + wrapper: ({ children }) => {children}, + }); + expect(screen.getByText(/Sorry, an error occured while processing your request/i)).toBeInTheDocument(); expect(screen.getByRole('button')).toHaveTextContent('OK'); expect(screen.getByRole('heading')).toHaveTextContent(/Something’s not right/i); }); it('should not render the component if has_cfd_error is false', () => { - render(); + const mockRootStore = { + ui: { + disableApp: jest.fn(), + enableApp: jest.fn(), + }, + modules: { + cfd: { + clearCFDError: jest.fn(), + error_type: 'error_type', + has_cfd_error: false, + is_cfd_success_dialog_enabled: false, + error_message: 'Sorry, an error occured while processing your request.', + }, + }, + }; + + render(, { + wrapper: ({ children }) => {children}, + }); + expect(screen.queryByText(/Sorry, an error occured while processing your request/i)).not.toBeInTheDocument(); }); it('should not render the component if is_cfd_success_dialog_enabled', () => { - render(); + const mockRootStore = { + ui: { + disableApp: jest.fn(), + enableApp: jest.fn(), + }, + modules: { + cfd: { + clearCFDError: jest.fn(), + error_type: 'error_type', + has_cfd_error: false, + is_cfd_success_dialog_enabled: true, + error_message: 'Sorry, an error occured while processing your request.', + }, + }, + }; + + render(, { + wrapper: ({ children }) => {children}, + }); + expect(screen.queryByText(/Sorry, an error occured while processing your request/i)).not.toBeInTheDocument(); }); it('should clear the component if OK is clicked', () => { - render(); + const mockRootStore = { + ui: { + disableApp: jest.fn(), + enableApp: jest.fn(), + }, + modules: { + cfd: { + clearCFDError: jest.fn(), + error_type: 'error_type', + has_cfd_error: true, + is_cfd_success_dialog_enabled: false, + error_message: 'Sorry, an error occured while processing your request.', + }, + }, + }; + + render(, { + wrapper: ({ children }) => {children}, + }); + const ok_btn = screen.getByRole('button', { name: /OK/i }); fireEvent.click(ok_btn); - expect(mock_props.clearCFDError).toHaveBeenCalled(); + + expect(mockRootStore.modules.cfd.clearCFDError).toHaveBeenCalled(); }); }); diff --git a/packages/cfd/src/Containers/cfd-server-error-dialog.tsx b/packages/cfd/src/Containers/cfd-server-error-dialog.tsx index bd11bcb0d093..ce012e1828ba 100644 --- a/packages/cfd/src/Containers/cfd-server-error-dialog.tsx +++ b/packages/cfd/src/Containers/cfd-server-error-dialog.tsx @@ -1,35 +1,20 @@ import React from 'react'; import { Dialog } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import { connect } from '../Stores/connect'; -import RootStore from '../Stores/index'; +import { observer, useStore } from '@deriv/stores'; -type TCFDServerErrorDialogProps = { - clearCFDError: () => void; - context: RootStore; - disableApp: () => void; - enableApp: () => void; - error_message: string; - error_type?: string; - has_cfd_error: boolean; - is_cfd_success_dialog_enabled: boolean; -}; +const CFDServerErrorDialog = observer(() => { + const { ui, modules } = useStore(); + const { enableApp, disableApp } = ui; + const { cfd } = modules; + const { clearCFDError, error_message, error_type, has_cfd_error, is_cfd_success_dialog_enabled } = cfd; -const CFDServerErrorDialog = ({ - clearCFDError, - context, - disableApp, - enableApp, - error_message, - error_type, - has_cfd_error, - is_cfd_success_dialog_enabled, -}: TCFDServerErrorDialogProps) => { const should_show_error = has_cfd_error && !is_cfd_success_dialog_enabled && error_type && !['PasswordReset', 'PasswordError'].includes(error_type); + return ( {error_message || } ); -}; +}); -export default connect(({ ui, modules }: RootStore) => ({ - clearCFDError: modules.cfd.clearCFDError, - disableApp: ui.disableApp, - enableApp: ui.enableApp, - error_message: modules.cfd.error_message, - error_type: modules.cfd.error_type, - has_cfd_error: modules.cfd.has_cfd_error, - is_cfd_success_dialog_enabled: modules.cfd.is_cfd_success_dialog_enabled, -}))(CFDServerErrorDialog); +export default CFDServerErrorDialog; diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx index 1a2076e1fc4f..944c53b6a6db 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal.tsx @@ -255,7 +255,6 @@ const JurisdictionModal = ({ toggleModal={toggleJurisdictionModal} type='button' context={context} - height='664px' width={account_type.type === 'synthetic' ? '1040px' : '1200px'} > diff --git a/packages/cfd/src/app.tsx b/packages/cfd/src/app.tsx index 7c6ffb8a1a0c..721f91275641 100644 --- a/packages/cfd/src/app.tsx +++ b/packages/cfd/src/app.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import PropTypes from 'prop-types'; import React from 'react'; +import { StoreProvider } from '@deriv/stores'; import Routes from './Containers/routes.jsx'; import { MobxContentProvider } from './Stores/connect'; import initStore from './init-store'; // eslint-disable-line import/extensions @@ -17,9 +18,11 @@ const App = ({ passthrough }: TAppProps) => { return ( - - - + + + + + ); }; diff --git a/packages/cfd/tsconfig.json b/packages/cfd/tsconfig.json index 28010cc7e051..dce40b01bd32 100644 --- a/packages/cfd/tsconfig.json +++ b/packages/cfd/tsconfig.json @@ -15,6 +15,6 @@ "outDir": "./dist", "baseUrl": "./" }, - "include": ["src", "globals.d.ts", "../../utils.d.ts"], + "include": ["src", "globals.d.ts", "../../utils.d.ts", "@deriv-stores.d.ts"], "exclude": ["**/__tests__/**/*"] } diff --git a/packages/components/src/components/timeline/timeline.jsx b/packages/components/src/components/timeline/timeline.jsx deleted file mode 100644 index b43858e6802c..000000000000 --- a/packages/components/src/components/timeline/timeline.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import Text from '../text'; - -const Oval = ({ children }) => { - return ( -
- - {children} - -
- ); -}; - -const Timeline = ({ children, disabled_items, ...props }) => { - return ( -
- {children.map((child, idx) => ( -
- {idx + 1} -
- - {child.props.item_title} - -
{child}
-
-
- ))} -
- ); -}; - -const Item = ({ children, ...props }) =>
{children}
; - -Timeline.Item = Item; - -export default Timeline; diff --git a/packages/components/src/components/timeline/timeline.tsx b/packages/components/src/components/timeline/timeline.tsx index f924b0304357..835bfd5ae1df 100644 --- a/packages/components/src/components/timeline/timeline.tsx +++ b/packages/components/src/components/timeline/timeline.tsx @@ -6,7 +6,7 @@ type TOval = { children: number; }; type TItem = React.HTMLAttributes & { item_title: React.ReactNode }; -type TTimeline = React.HTMLAttributes & { disabled_items?: number[] }; +type TTimeline = React.HTMLAttributes & { disabled_items?: number[]; line_height?: string }; const Oval = ({ children }: TOval) => { return ( @@ -18,7 +18,7 @@ const Oval = ({ children }: TOval) => { ); }; -const Timeline = ({ children, disabled_items, ...props }: React.PropsWithChildren) => { +const Timeline = ({ children, disabled_items, line_height = 'xxl', ...props }: React.PropsWithChildren) => { return (
{Array.isArray(children) && @@ -32,7 +32,13 @@ const Timeline = ({ children, disabled_items, ...props }: React.PropsWithChildre > {idx + 1}
- + {child.props.item_title}
{child}
diff --git a/packages/core/src/App/Components/Layout/Footer/__tests__/toggle-settings.spec.tsx b/packages/core/src/App/Components/Layout/Footer/__tests__/toggle-settings.spec.tsx index 1c157b71519d..4d1056f1705a 100644 --- a/packages/core/src/App/Components/Layout/Footer/__tests__/toggle-settings.spec.tsx +++ b/packages/core/src/App/Components/Layout/Footer/__tests__/toggle-settings.spec.tsx @@ -1,40 +1,34 @@ -// TODO refactor old tests in this component import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; -import { Modal, Icon } from '@deriv/components'; import { ToggleSettings } from '../toggle-settings.jsx'; -const mockFunction = jest.fn(); +jest.mock('Stores/connect', () => ({ + __esModule: true, + default: 'mockedDefaultExport', + connect: + () => + (Component: T) => + Component, +})); describe('ToggleSettings Component', () => { it('should has "ic-settings--active" class when "is_settings_visible" is true', () => { render(); - const aElement = screen.getByTestId('dt_settings_toggle'); - expect(aElement).toHaveClass('ic-settings--active'); + const link = screen.getByTestId('dt_toggle_settings'); + expect(link).toHaveClass('ic-settings--active'); }); - // it('should open the modal when the user clicked on the link', () => { - // render(); - // const aElement = screen.getByTestId('dt_settings_toggle'); - // fireEvent.click(aElement); - // const modal = screen.getByTestId(''); - // // expect(modal). - // }); - - // it('should contain "IcGear" icon', () => { - // render(); - // const icon = screen.getByText('IcGear'); - // // expect(icon).toBeInTheDocument(); - // }); - - // it('should render one component with active class if is_settings_visible is true', () => { - // const wrapper = shallow(); - // expect(wrapper).toHaveLength(1); - // expect(wrapper.find('.ic-settings--active').exists()).toBe(true); - // }); + it('should contain "IcGear" icon', () => { + render(); + const icon = screen.getByTestId('dt_icon'); + expect(icon).toBeInTheDocument(); + }); - // it("property 'is_open' should depend on 'is_settings_visible'", () => { - // const wrapper = shallow(); - // expect(wrapper.find(Modal).prop('is_open')).toBe(true); - // }); + it('should call "toggleSettings" function when the user clicked on the link', () => { + const toggleSettings = jest.fn(); + render(); + const link = screen.getByTestId('dt_toggle_settings'); + fireEvent.click(link); + expect(toggleSettings).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/core/src/App/Components/Layout/Footer/toggle-settings.jsx b/packages/core/src/App/Components/Layout/Footer/toggle-settings.jsx index e37b19bc4d18..dc65c753b799 100644 --- a/packages/core/src/App/Components/Layout/Footer/toggle-settings.jsx +++ b/packages/core/src/App/Components/Layout/Footer/toggle-settings.jsx @@ -55,12 +55,12 @@ const ToggleSettings = ({ enableApp, is_settings_visible, disableApp, toggleSett - + ({ + ...jest.requireActual('@deriv/shared'), + isMobile: jest.fn(() => true), +})); -describe('TestedPlatformSwitcher', () => { - // const props = { - // app_routing_history: [{ pathname: 'test' }], - // platform_config: [], - // toggleDrawer: () => {}, - // }; +const withRouter = (Component: React.ComponentType) => { + const history = createBrowserHistory(); + const WrapperComponent = (props: T & U) => ( + + + + ); - // const wrapper = shallow(); + return WrapperComponent; +}; - it('should render one component', () => { - // expect(wrapper).toHaveLength(1); - // console.log(wrapper.debug()); +const PlatformSwitcherComponent = withRouter(PlatformSwitcher); + +describe('PlatformSwitcher component', () => { + it('should render component if "app_routing_history" is an empty array', () => { + render(); + const div_element = screen.getByTestId('dt_platform_switcher_preloader'); + expect(div_element).toBeInTheDocument(); + }); + + it('should have "platform-switcher__preloader--is-mobile" class if "app_routing_history" is an empty array and "isMobile" is "true"', () => { + render(); + const div_element = screen.getByTestId('dt_platform_switcher_preloader'); + expect(div_element).toHaveClass('platform-switcher__preloader--is-mobile'); + }); + + it('should render "platform-switcher" if "app_routing_history" is not an empty array', () => { + render(); + const div_element = screen.getByTestId('dt_platform_switcher'); + expect(div_element).toBeInTheDocument(); + }); + + it('should not have "platform-switcher--active" class if "app_routing_history" is not an empty array and "is_open" is "false"', () => { + render(); + const div_element = screen.getByTestId('dt_platform_switcher'); + expect(div_element).not.toHaveClass('platform-switcher--active'); }); - // it('should have .platform-switcher__preloader and have not .platform-switcher if app_routing_history is an empty array', () => { - // const wrapper = shallow(); - // expect(wrapper.find('.platform-switcher__preloader').exists()).toBe(true); - // expect(wrapper.find('.platform-switcher').exists()).toBe(false); - // }); - - // it('should not have .platform-switcher__preloader and have .platform-switcher if app_routing_history is not an empty array', () => { - // expect(wrapper.find('.platform-switcher__preloader').exists()).toBe(false); - // expect(wrapper.find('.platform-switcher').exists()).toBe(true); - // }); - - // it('should contain if app_routing_history is not an empty array', () => { - // expect( - // wrapper.contains( - //

} - // >
- // ) - // ); - // }); + it('should have "platform-switcher--is-mobile" class if "app_routing_history" is not an empty array and "isMobile" is "true"', () => { + render(); + const div_element = screen.getByTestId('dt_platform_switcher'); + expect(div_element).toHaveClass('platform-switcher--is-mobile'); + }); }); diff --git a/packages/core/src/App/Components/Layout/Header/platform-switcher.jsx b/packages/core/src/App/Components/Layout/Header/platform-switcher.jsx index 66b0a207ad00..c7cbf2ac27f4 100644 --- a/packages/core/src/App/Components/Layout/Header/platform-switcher.jsx +++ b/packages/core/src/App/Components/Layout/Header/platform-switcher.jsx @@ -30,6 +30,7 @@ const PlatformSwitcher = ({ toggleDrawer, app_routing_history, platform_config } return app_routing_history.length === 0 ? (
- + Deriv App | Deriv diff --git a/packages/p2p/src/components/buy-sell/buy-sell-modal.jsx b/packages/p2p/src/components/buy-sell/buy-sell-modal.jsx index 7731b7d98dbf..6ad831f5e84c 100644 --- a/packages/p2p/src/components/buy-sell/buy-sell-modal.jsx +++ b/packages/p2p/src/components/buy-sell/buy-sell-modal.jsx @@ -1,3 +1,4 @@ +import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { @@ -21,9 +22,9 @@ import { useStores } from 'Stores'; import BuySellForm from './buy-sell-form.jsx'; import BuySellFormReceiveAmount from './buy-sell-form-receive-amount.jsx'; import NicknameForm from '../nickname-form'; -import 'Components/buy-sell/buy-sell-modal.scss'; import AddPaymentMethodForm from '../my-profile/payment-methods/add-payment-method/add-payment-method-form.jsx'; import { api_error_codes } from 'Constants/api-error-codes'; +import 'Components/buy-sell/buy-sell-modal.scss'; const LowBalanceMessage = () => (
@@ -70,7 +71,7 @@ BuySellModalFooter.propTypes = { const generateModalTitle = (formik_ref, my_profile_store, table_type, selected_ad) => { if (my_profile_store.should_show_add_payment_method_form) { - if (!isMobile()) { + if (isDesktop()) { return ( {}); const [error_message, setErrorMessage] = useSafeState(null); const [is_submit_disabled, setIsSubmitDisabled] = useSafeState(true); - const [is_account_balance_low, setIsAccountBalanceLow] = React.useState(false); const [show_market_rate_change_error_modal, setShowMarketRateChangeErrorModal] = React.useState(false); const [has_rate_changed_recently, setHasRateChangedRecently] = React.useState(false); @@ -123,15 +123,41 @@ const BuySellModal = ({ table_type, selected_ad, should_show_popup, setShouldSho React.useEffect(() => { const disposeHasRateChangedReaction = reaction( - () => buy_sell_store?.advert?.rate, + () => buy_sell_store.advert, + (new_advert, previous_advert) => { + // check to see if the rate is initialized in the store for the first time (when unitialized it is undefined) AND + const rate_has_changed = previous_advert?.rate && previous_advert.rate !== new_advert.rate; + // check to see if user is not switching between different adverts, it should not trigger rate change modal + const is_the_same_advert = previous_advert?.id === new_advert.id; + if (rate_has_changed && is_the_same_advert) { + setHasRateChangedRecently(true); + setTimeout(() => { + setHasRateChangedRecently(false); + }, MAX_ALLOWED_RATE_CHANGED_WARNING_DELAY); + } + } + ); + + const disposeFormErrorCodeReaction = reaction( + () => buy_sell_store.form_error_code, () => { - setHasRateChangedRecently(true); - setTimeout(() => { - setHasRateChangedRecently(false); - }, MAX_ALLOWED_RATE_CHANGED_WARNING_DELAY); + if (buy_sell_store.form_error_code === api_error_codes.ORDER_CREATE_FAIL_RATE_CHANGED) { + if (isDesktop()) { + buy_sell_store.hidePopup(); + setTimeout(() => setShowMarketRateChangeErrorModal(true), 280); + } else { + setShowMarketRateChangeErrorModal(true); + } + buy_sell_store.setFormErrorCode(''); + setErrorMessage(null); + } } ); - return disposeHasRateChangedReaction; + + return () => { + disposeHasRateChangedReaction(); + disposeFormErrorCodeReaction(); + }; }, []); const onSubmitWhenRateChanged = () => { @@ -145,22 +171,24 @@ const BuySellModal = ({ table_type, selected_ad, should_show_popup, setShouldSho const BuySellFormError = () => { if (!!error_message && buy_sell_store.form_error_code !== api_error_codes.ORDER_CREATE_FAIL_RATE_CHANGED) { -
- - {buy_sell_store.form_error_code === api_error_codes.ORDER_CREATE_FAIL_CLIENT_BALANCE ? ( - - ) : ( - error_message - )} - - } - is_danger - /> -
; + return ( +
+ + {buy_sell_store.form_error_code === api_error_codes.ORDER_CREATE_FAIL_CLIENT_BALANCE ? ( + + ) : ( + error_message + )} + + } + is_danger + /> +
+ ); } return null; }; @@ -189,30 +217,10 @@ const BuySellModal = ({ table_type, selected_ad, should_show_popup, setShouldSho const setSubmitForm = submitFormFn => (submitForm.current = submitFormFn); const has_rate_changed = - (!!error_message && buy_sell_store.form_error_code !== api_error_codes.ORDER_CREATE_FAIL_RATE_CHANGED) || + (!!error_message && buy_sell_store.form_error_code === api_error_codes.ORDER_CREATE_FAIL_RATE_CHANGED) || has_rate_changed_recently; const onSubmit = has_rate_changed ? onSubmitWhenRateChanged : submitForm.current; - React.useEffect(() => { - const disposeFormErrorCodeReaction = reaction( - () => buy_sell_store.form_error_code, - () => { - if (buy_sell_store.form_error_code === api_error_codes.ORDER_CREATE_FAIL_RATE_CHANGED) { - if (!isMobile()) { - buy_sell_store.hidePopup(); - setTimeout(() => setShowMarketRateChangeErrorModal(true), 280); - } else { - setShowMarketRateChangeErrorModal(true); - } - buy_sell_store.setFormErrorCode(''); - setErrorMessage(null); - } - } - ); - - return disposeFormErrorCodeReaction; - }, []); - React.useEffect(() => { const balance_check = parseFloat(general_store.balance) === 0 || @@ -254,17 +262,15 @@ const BuySellModal = ({ table_type, selected_ad, should_show_popup, setShouldSho {my_profile_store.should_show_add_payment_method_form ? ( ) : ( - - )} - {!my_profile_store.should_show_add_payment_method_form && ( + form { + height: fit-content !important; + } + } } &-danger { @@ -63,6 +69,10 @@ } } + &-form { + height: fit-content !important; + } + &-footer { @include mobile { border-top: 2px solid var(--general-section-1); @@ -208,6 +218,3 @@ border-top: 2px solid var(--general-section-5); } } -.dc-mobile-full-page-modal form { - height: fit-content; -} diff --git a/packages/p2p/src/components/my-ads/create-ad-add-payment-method-modal.jsx b/packages/p2p/src/components/my-ads/create-ad-add-payment-method-modal.jsx index b707029f672d..d3f256d98119 100644 --- a/packages/p2p/src/components/my-ads/create-ad-add-payment-method-modal.jsx +++ b/packages/p2p/src/components/my-ads/create-ad-add-payment-method-modal.jsx @@ -52,7 +52,9 @@ const CreateAdAddPaymentMethodModal = () => { if (isMobile()) { return ( { return ( { { if ( (formik_ref.current && formik_ref.current.dirty) || diff --git a/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss b/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss index fe0b6c3846d4..8291a24d4f7a 100644 --- a/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss +++ b/packages/p2p/src/components/my-profile/my-profile-header/my-profile-header.scss @@ -2,5 +2,5 @@ align-items: center; display: flex; height: 3.5rem; - width: 65rem; + width: 70rem; } diff --git a/packages/p2p/src/components/my-profile/payment-methods/add-payment-method/add-payment-method-form.jsx b/packages/p2p/src/components/my-profile/payment-methods/add-payment-method/add-payment-method-form.jsx index 9776dc327455..7581e2cab122 100644 --- a/packages/p2p/src/components/my-profile/payment-methods/add-payment-method/add-payment-method-form.jsx +++ b/packages/p2p/src/components/my-profile/payment-methods/add-payment-method/add-payment-method-form.jsx @@ -4,11 +4,12 @@ import PropTypes from 'prop-types'; import { observer } from 'mobx-react-lite'; import { Field, Form, Formik } from 'formik'; import { Button, Icon, Input, Loading, Modal, Text } from '@deriv/components'; +import { isDesktop, isMobile } from '@deriv/shared'; import { Localize, localize } from 'Components/i18next'; import { useStores } from 'Stores'; const AddPaymentMethodForm = ({ formik_ref, should_show_separated_footer = false }) => { - const { my_ads_store, my_profile_store } = useStores(); + const { general_store, my_ads_store, my_profile_store } = useStores(); React.useEffect(() => { my_profile_store.getPaymentMethodsList(); @@ -94,7 +95,11 @@ const AddPaymentMethodForm = ({ formik_ref, should_show_separated_footer = false
-
+