From 86816fabe8585a0c2bdca9a70bd57bfddf1afd73 Mon Sep 17 00:00:00 2001 From: hirad-deriv Date: Fri, 1 Sep 2023 14:54:04 +0800 Subject: [PATCH] Hirad-Hamza-ShonTzu/Feature revamp compare account re-deployment (#9611) * feat: initializing the compare account implementation * feat: icon reusable component v1.0 * feat: icon reusable component v1.1 * feat: icon reusable component v1.1.1 * feat: initialize compare cfd account page (todo:css) * feat: blank compare cfd accounts page & navi done * feat: mobile view * fix: compare-accounts naming convention * fix: updated path import * fix: reverted icons.js changes * fix: compare-cfds -> compare-accounts * feat: reusable component v1.2 * feat: reusable component v1.2.1 * feat: icon reusable component added v1.3 * feat: reusable component for Icons * feat: reusable component for Icons refactor * feat: reusable component for Icons refactor v1.2 * feat: description div added * feat: description with title +icon is added * feat: platform label + icon component padding added * chore: labuan leverage updated * chore: css issue fixed for Cards * feat: carousel added with sorting o f MT5 accounts * feat: carousel arrow background and container added * chore: added dxtrade in the card for dynamic rendering with type fixes * chore: changes in platform label header with respect to props data * chore: icons updated because of white line issue in icons * chore: change the components css name and other naming convention tweaks * refactor: suggestions implemented * refactor: convert carousel button into one * feat: initial commit for starting collaboration * feat: added the button placeholder * feat: added new banner to cfd cards * refactor: make the banner smaller * fix: added condition to show banner for derivez and ctrader only * refactor: changed the types and applied suggestions * refactor: removed the invalide shortcode for dxtrade * refactor: round up patches based on reviews * fix: round up patch 2 based on review * Update packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss Co-authored-by: Muhammad Hamza <120543468+hamza-deriv@users.noreply.github.com> * refactor: button width * refactor: button classname * chore: fix css of underline * chore: added redirection to homepage in case of modals * fix: circle ci failed due to wrong type of client function * chore: added the disabled property for the Added accounts * chore: added condition for the dxtrade * chore: rearrange block scoped variables * chore: fix css of labuan tooltip with removal of commented code * chore: responsive view fixes * feat: demo accounts added for low risk * feat: demo accounts label added and swap-free account creation fixed * chore: added derivx account creation flow * refactor: replace ternary operators with if-else * chore: remove unused jurisdiction * feat: demo account compare implemetation tweaks as per design * feat: rectified the Demo title as per design * chore: font weigth of instruments as per design * feat: Eu flow for DIEL * feat: changes in the icons label and correction in EU flow * chore: addded translations demo title and rename baskets * chore: rename icon type * chore: final changes prop drilling instead of observer EU flow finalized * fix: hidding deriv ez * fix: EU flow platform label change * feat: test case for description added * feat: platform label test case added * feat: title icon test case added * feat: title icon test case added * chore: fix test case description + instruments icon test added * refactor: blank spaces removed * chore: cfd-instruments-label test added * chore: test file added cfd-instrument-label * feat: button partial test case added * chore: added more test case for Button component * feat: added test case for compare-account-card * chore: merge conflicts * chore: test for cfd-compare-accounts added * feat: derivX demo * chore: added testcase for dxtrade in button * fix: dxtrade for australian clients * fix: swapfree account creation added * fix: comapre account fixes first round * fix: remove the css because scroll not working * fix: renamed variable + icons * fix: cursor not allowed on instrument icons * fix: fixed the circle ci issue * fix: fixed one of our test issues * refactor: review comments resolved * fix: changing the text of mf accounts to pass the tests * fix: made changes according to suggestions * fix: made changes according to suggestions * fix: fixed the test case issues * chore: added s to spread(s) * chore: fixed capitalization * chore: line split * fix: failing test case * fix: updated markets offerings for MT5 Financial Labuan * fix: Other CFDs --> Other CFDs Platform * fix: removed standard/micro from DerivX Forex label * refactor: optimised code * fix: icon size inconsistencyn mobile VP * refactor: css refactoring to reflect figma as much as possible * refactor: minor css fixes * fix: fixed * style: added bottom padding for mobile * fix: fixed the build issue * chore: re running the tests * fix: subtasks * chore: typo * style: position the tooltip to be center within the card * style: resize width for word-wrap * chore: d and r should be lowercase dispute and resolution * fix: fixing sonar cloud issues * fix: making changes to pass the tests --------- Co-authored-by: hamza-deriv Co-authored-by: shontzu-deriv Co-authored-by: shontzu <108507236+shontzu-deriv@users.noreply.github.com> Co-authored-by: Muhammad Hamza <120543468+hamza-deriv@users.noreply.github.com> --- .../components/cfds-listing/cfds-listing.scss | 3 - .../src/components/cfds-listing/index.tsx | 17 +- .../compare-account/compare-account.scss | 3 + .../compare-account/compare-account.tsx | 28 ++ .../src/components/compare-account/index.ts | 4 + .../appstore/src/constants/routes-config.ts | 6 + packages/appstore/src/stores/config-store.ts | 1 + .../ic-instrument-baskets.svg | 12 + .../ic-instrument-commodities.svg | 1 + .../ic-instrument-cryptocurrencies.svg | 1 + .../ic-instrument-derived-fx.svg | 7 + .../trading-instruments/ic-instrument-etf.svg | 1 + .../ic-instrument-forex.svg | 1 + .../ic-instrument-stock-indices.svg | 1 + .../ic-instrument-stocks.svg | 1 + .../ic-instrument-synthetics.svg | 7 + .../Assets/svgs/trading-instruments/index.tsx | 39 ++ .../trading-platform/ic-appstore-deriv-x.svg | 1 + .../trading-platform/ic-appstore-derived.svg | 11 +- .../ic-appstore-financial.svg | 2 +- .../Assets/svgs/trading-platform/index.tsx | 2 + packages/cfd/src/Components/props.types.ts | 50 ++ packages/cfd/src/Constants/routes-config.js | 10 +- .../cfd-compare-accounts-button.spec.tsx | 273 +++++++++++ .../cfd-compare-accounts-card.spec.tsx | 110 +++++ .../cfd-compare-accounts-description.spec.tsx | 135 ++++++ ...d-compare-accounts-platform-label.spec.tsx | 22 + .../cfd-compare-accounts-title-icon.spec.tsx | 133 ++++++ .../__tests__/cfd-compare-accounts.spec.tsx | 108 +++++ ...cfd-instruments-label-highlighted.spec.tsx | 55 +++ .../instruments-icon-with-label.spec.tsx | 51 +++ .../cfd-compare-accounts-button.tsx | 136 ++++++ .../cfd-compare-accounts-card.tsx | 43 ++ .../cfd-compare-accounts-description.tsx | 76 ++++ .../cfd-compare-accounts-platform-label.tsx | 24 + .../cfd-compare-accounts-title-icon.tsx | 51 +++ .../cfd-compare-accounts.scss | 162 +++++++ .../cfd-compare-accounts.tsx | 163 +++++++ .../cfd-instruments-label-highlighted.tsx | 22 + .../Containers/cfd-compare-accounts/index.tsx | 4 + .../instruments-icon-with-label.tsx | 28 ++ .../src/Helpers/compare-accounts-config.ts | 428 ++++++++++++++++++ packages/components/package.json | 1 + .../cfd-compare-accounts-carousel-button.tsx | 30 ++ .../cfd-compare-accounts-carousel.scss | 69 +++ .../cfd-compare-accounts-carousel.tsx | 45 ++ .../cfd-compare-accounts-carousel/index.ts | 4 + packages/components/src/index.js | 1 + .../core/src/App/Constants/routes-config.js | 6 + .../App/Containers/Layout/header/header.jsx | 5 +- packages/shared/src/styles/constants.scss | 3 + packages/shared/src/styles/themes.scss | 7 + packages/shared/src/utils/routes/routes.ts | 1 + packages/stores/src/mockStore.ts | 5 +- packages/stores/types.ts | 12 +- 55 files changed, 2399 insertions(+), 23 deletions(-) create mode 100644 packages/appstore/src/components/compare-account/compare-account.scss create mode 100644 packages/appstore/src/components/compare-account/compare-account.tsx create mode 100644 packages/appstore/src/components/compare-account/index.ts create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-baskets.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-commodities.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-cryptocurrencies.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-derived-fx.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-etf.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-forex.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stock-indices.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stocks.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-synthetics.svg create mode 100644 packages/cfd/src/Assets/svgs/trading-instruments/index.tsx create mode 100644 packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-deriv-x.svg create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-button.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/index.tsx create mode 100644 packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx create mode 100644 packages/cfd/src/Helpers/compare-accounts-config.ts create mode 100644 packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel-button.tsx create mode 100644 packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss create mode 100644 packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.tsx create mode 100644 packages/components/src/components/cfd-compare-accounts-carousel/index.ts diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index a48de9048fe3..51a2195d4a8e 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -2057,6 +2057,3 @@ } } } -.cfd-accounts__compare-table-title { - cursor: pointer; -} diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index bbe800358463..74c44540298f 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -7,6 +7,7 @@ import ListingContainer from 'Components/containers/listing-container'; import AddOptionsAccount from 'Components/add-options-account'; import TradingAppCard from 'Components/containers/trading-app-card'; import PlatformLoader from 'Components/pre-loader/platform-loader'; +import CompareAccount from 'Components/compare-account'; import GetMoreAccounts from 'Components/get-more-accounts'; import { Actions } from 'Components/containers/trading-app-card-actions'; import { getHasDivider } from 'Constants/utils'; @@ -53,7 +54,7 @@ const CFDsListing = observer(() => { financial_restricted_countries, } = traders_hub; - const { toggleCompareAccountsModal, setAccountType } = cfd; + const { setAccountType } = cfd; const { is_landing_company_loaded, real_account_creation_unlock_date, account_status } = client; const { setAppstorePlatform } = common; const { openDerivRealAccountNeededModal, setShouldShowCooldownModal } = ui; @@ -154,11 +155,7 @@ const CFDsListing = observer(() => { {localize('CFDs')} -
- - - -
+ ) } @@ -173,13 +170,7 @@ const CFDsListing = observer(() => { } > - {isMobile() && ( -
- - - -
- )} + {isMobile() && } diff --git a/packages/appstore/src/components/compare-account/compare-account.scss b/packages/appstore/src/components/compare-account/compare-account.scss new file mode 100644 index 000000000000..a4320f9f826d --- /dev/null +++ b/packages/appstore/src/components/compare-account/compare-account.scss @@ -0,0 +1,3 @@ +.cfd-accounts__compare-table-title { + cursor: pointer; +} diff --git a/packages/appstore/src/components/compare-account/compare-account.tsx b/packages/appstore/src/components/compare-account/compare-account.tsx new file mode 100644 index 000000000000..876e3207d31f --- /dev/null +++ b/packages/appstore/src/components/compare-account/compare-account.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { Localize } from '@deriv/translations'; +import { useHistory } from 'react-router-dom'; +import { routes } from '@deriv/shared'; + +type TCompareAccount = { + accounts_sub_text: string; + is_desktop?: boolean; +}; + +const CompareAccount = ({ accounts_sub_text, is_desktop }: TCompareAccount) => { + const history = useHistory(); + return ( +
{ + history.push(routes.compare_cfds); + }} + > + + + +
+ ); +}; + +export default CompareAccount; diff --git a/packages/appstore/src/components/compare-account/index.ts b/packages/appstore/src/components/compare-account/index.ts new file mode 100644 index 000000000000..98ab98258316 --- /dev/null +++ b/packages/appstore/src/components/compare-account/index.ts @@ -0,0 +1,4 @@ +import CompareAccount from './compare-account'; +import './compare-account.scss'; + +export default CompareAccount; diff --git a/packages/appstore/src/constants/routes-config.ts b/packages/appstore/src/constants/routes-config.ts index 0cc6824d683c..11352cd34190 100644 --- a/packages/appstore/src/constants/routes-config.ts +++ b/packages/appstore/src/constants/routes-config.ts @@ -3,6 +3,7 @@ import TradersHub from 'Modules/traders-hub'; import ConfigStore from 'Stores/config-store'; import { TRoute } from 'Types'; import Onboarding from 'Modules/onboarding'; +import CFDCompareAccounts from '@deriv/cfd/src/Containers/cfd-compare-accounts'; type TRoutesConfig = { consumer_routes: ConfigStore['routes']; @@ -21,6 +22,11 @@ const initRoutesConfig = ({ consumer_routes }: TRoutesConfig): TRoute[] => [ component: Onboarding, getTitle: () => localize('Onboarding'), }, + { + path: consumer_routes.compare_cfds, + component: CFDCompareAccounts, + getTitle: () => localize('CFDCompareAccounts'), + }, ]; let routes_config: Array; diff --git a/packages/appstore/src/stores/config-store.ts b/packages/appstore/src/stores/config-store.ts index abe3d633621e..7b92e450171a 100644 --- a/packages/appstore/src/stores/config-store.ts +++ b/packages/appstore/src/stores/config-store.ts @@ -6,6 +6,7 @@ export default class ConfigStore extends BaseStore { public routes = { traders_hub: '/appstore/traders-hub', onboarding: '/appstore/onboarding', + compare_cfds: '/appstore/compare-accounts', my_apps: '/my-apps', explore: '/explore', diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-baskets.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-baskets.svg new file mode 100644 index 000000000000..44c28bd3c7db --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-baskets.svg @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-commodities.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-commodities.svg new file mode 100644 index 000000000000..3b0ec9032df4 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-commodities.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-cryptocurrencies.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-cryptocurrencies.svg new file mode 100644 index 000000000000..2910a1a088e0 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-cryptocurrencies.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-derived-fx.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-derived-fx.svg new file mode 100644 index 000000000000..5c2014c21c95 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-derived-fx.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-etf.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-etf.svg new file mode 100644 index 000000000000..ecaba49dd980 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-etf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-forex.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-forex.svg new file mode 100644 index 000000000000..cb059c723893 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-forex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stock-indices.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stock-indices.svg new file mode 100644 index 000000000000..bae58577ad7c --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stock-indices.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stocks.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stocks.svg new file mode 100644 index 000000000000..bae58577ad7c --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-stocks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-synthetics.svg b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-synthetics.svg new file mode 100644 index 000000000000..57c1c7500777 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/ic-instrument-synthetics.svg @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-instruments/index.tsx b/packages/cfd/src/Assets/svgs/trading-instruments/index.tsx new file mode 100644 index 000000000000..cf1dab7fb771 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-instruments/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import DerivedFX from './ic-instrument-derived-fx.svg'; +import Synthetics from './ic-instrument-synthetics.svg'; +import Baskets from './ic-instrument-baskets.svg'; +import Stocks from './ic-instrument-stocks.svg'; +import StockIndices from './ic-instrument-stock-indices.svg'; +import Commodities from './ic-instrument-commodities.svg'; +import Forex from './ic-instrument-forex.svg'; +import Cryptocurrencies from './ic-instrument-cryptocurrencies.svg'; +import ETF from './ic-instrument-etf.svg'; + +export type IconProps = { + icon: T; + className?: string; + size?: number; + onClick?: () => void; +}; + +export const InstrumentsIcons = { + DerivedFX, + Synthetics, + Baskets, + Stocks, + StockIndices, + Commodities, + Forex, + Cryptocurrencies, + ETF, +}; + +const TradingInstrumentsIcon = ({ icon, className, size, onClick }: IconProps) => { + const InstrumentIcon = InstrumentsIcons[icon] as React.ElementType; + + return InstrumentIcon ? ( + + ) : null; +}; + +export default TradingInstrumentsIcon; diff --git a/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-deriv-x.svg b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-deriv-x.svg new file mode 100644 index 000000000000..f3946f897a35 --- /dev/null +++ b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-deriv-x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-derived.svg b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-derived.svg index 52b8a909ccd6..b0abc3df8f0b 100644 --- a/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-derived.svg +++ b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-derived.svg @@ -1 +1,10 @@ - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-financial.svg b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-financial.svg index 2ecb392ebc1e..7fd867a129f0 100644 --- a/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-financial.svg +++ b/packages/cfd/src/Assets/svgs/trading-platform/ic-appstore-financial.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/cfd/src/Assets/svgs/trading-platform/index.tsx b/packages/cfd/src/Assets/svgs/trading-platform/index.tsx index 3f6e42f96452..3cce434188af 100644 --- a/packages/cfd/src/Assets/svgs/trading-platform/index.tsx +++ b/packages/cfd/src/Assets/svgs/trading-platform/index.tsx @@ -4,6 +4,7 @@ import Financial from './ic-appstore-financial.svg'; import CFDs from './ic-appstore-cfds.svg'; import DerivEz from './ic-appstore-derivez.svg'; import SwapFree from './ic-appstore-swap-free.svg'; +import DerivX from './ic-appstore-deriv-x.svg'; export interface IconProps { icon: T; @@ -18,6 +19,7 @@ export const PlatformIcons = { CFDs, DerivEz, SwapFree, + DerivX, }; const TradingPlatformIcon = ({ icon, className, size, onClick }: IconProps) => { diff --git a/packages/cfd/src/Components/props.types.ts b/packages/cfd/src/Components/props.types.ts index 0d86ca21c272..b404870245ac 100644 --- a/packages/cfd/src/Components/props.types.ts +++ b/packages/cfd/src/Components/props.types.ts @@ -47,6 +47,11 @@ export type TCFDDashboardContainer = { }; }; +type TOpenAccountTransferMeta = { + category: string; + type?: string; +}; + export type TCFDAccountCardActionProps = { button_label?: string | JSX.Element; handleClickSwitchAccount: () => void; @@ -82,6 +87,11 @@ export type TTradingPlatformAvailableAccount = { landing_company_short?: 'bvi' | 'labuan' | 'svg' | 'vanuatu'; }; +export type TModifiedTradingPlatformAvailableAccount = Omit & { + platform?: 'mt5' | 'dxtrade'; + market_type: TTradingPlatformAvailableAccount['market_type'] | 'synthetic'; +}; + export type TCardFlipStatus = { svg: boolean; bvi: boolean; @@ -228,3 +238,43 @@ export type TTradingPlatformAccounts = { */ platform?: 'dxtrade' | string; }; + +export type TInstrumentsIcon = { + icon: + | 'DerivedFX' + | 'Synthetics' + | 'Baskets' + | 'Stocks' + | 'StockIndices' + | 'Commodities' + | 'Forex' + | 'Cryptocurrencies' + | 'ETF'; + text: string; + highlighted: boolean; + className?: string; + is_asterisk?: boolean; +}; + +export type TCompareAccountsCard = { + trading_platforms: TModifiedTradingPlatformAvailableAccount; + is_eu_user?: boolean; + is_demo?: boolean; +}; + +export type TJurisdictionData = { + jurisdiction?: 'bvi' | 'labuan' | 'svg' | 'vanuatu' | 'maltainvest' | 'malta'; +}; + +export type TDetailsOfEachMT5Loginid = DetailsOfEachMT5Loginid & { + display_login?: string; + landing_company_short?: string; + short_code_and_region?: string; + mt5_acc_auth_status?: string | null; + selected_mt5_jurisdiction?: TOpenAccountTransferMeta & + TJurisdictionData & { + platform?: string; + }; + + openFailedVerificationModal?: (from_account: string) => void; +}; diff --git a/packages/cfd/src/Constants/routes-config.js b/packages/cfd/src/Constants/routes-config.js index 4ca69924eaf6..e843c19e9b70 100644 --- a/packages/cfd/src/Constants/routes-config.js +++ b/packages/cfd/src/Constants/routes-config.js @@ -2,7 +2,7 @@ import React from 'react'; import CFD from '../Containers'; import { routes } from '@deriv/shared'; import { localize } from '@deriv/translations'; - +import CFDCompareAccounts from 'Containers/cfd-compare-accounts'; // Error Routes const Page404 = React.lazy(() => import(/* webpackChunkName: "404" */ '../Modules/Page404')); @@ -23,6 +23,14 @@ const initRoutesConfig = () => { getTitle: () => localize('MT5'), is_authenticated: false, }, + // This is placed here to avoid conflict with other routes + // TODO: [refactoring] - Remove this route once we do refactoring + { + path: routes.compare_cfds, + component: props => , + getTitle: () => localize('Compare CFD accounts'), + is_authenticated: false, + }, ]; }; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-button.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-button.spec.tsx new file mode 100644 index 000000000000..a7395b64d0d6 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-button.spec.tsx @@ -0,0 +1,273 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import CFDCompareAccountsButton from '../cfd-compare-accounts-button'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: jest.fn(), + }), +})); + +jest.mock('@deriv/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel', () => + jest.fn(() =>
CFD Carousel
) +); + +const cfd_store_mock = { + current_list: {}, + setAccountType: jest.fn(), + setJurisdictionSelectedShortcode: jest.fn(), + enableCFDPasswordModal: jest.fn(), + toggleCFDVerificationModal: jest.fn(), +}; + +describe('', () => { + const mocked_props = { + trading_platforms: { + platform: 'mt5', + shortcode: 'svg', + market_type: 'gaming', + }, + is_demo: false, + }; + + it('should render the component with correct mocked_props', () => { + const mock = mockStore({ + client: { + account_status: { cashier_validation: ['system_maintenance'] }, + current_currency_type: 'crypto', + is_logged_in: true, + }, + modules: { cfd: cfd_store_mock }, + traders_hub: { + getAccount: jest.fn(), + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { + wrapper, + }); + + const buttonElement = screen.getByRole('button'); + + expect(buttonElement).toBeInTheDocument(); + expect(buttonElement).toHaveClass('compare-cfd-account__button'); + expect(buttonElement).toHaveTextContent('Add'); + expect(buttonElement).toBeEnabled(); + }); + + it('should open the account creation modal', async () => { + jest.mock('@deriv/shared', () => ({ + getAuthenticationStatusInfo: jest.fn(() => ({ + poi_or_poa_not_submitted: false, + poi_acknowledged_for_vanuatu_maltainvest: true, + poi_acknowledged_for_bvi_labuan: true, + poa_acknowledged: true, + poa_pending: false, + })), + })); + + const mock = mockStore({ + client: { + account_status: { cashier_validation: ['system_maintenance'] }, + current_currency_type: 'crypto', + is_logged_in: true, + should_restrict_bvi_account_creation: false, + should_restrict_vanuatu_account_creation: false, + }, + modules: { + cfd: { + ...cfd_store_mock, + current_list: {}, + }, + }, + traders_hub: { + getAccount: jest.fn(), + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render( + + + , + { + wrapper, + } + ); + + const buttonElement = screen.getByRole('button', { name: /Add/i }); + + userEvent.click(buttonElement); + await waitFor(() => { + expect(cfd_store_mock.setAccountType).toHaveBeenCalledTimes(1); + expect(mock.common.setAppstorePlatform).toHaveBeenCalledWith('mt5'); + expect(mock.modules.cfd.setJurisdictionSelectedShortcode).toHaveBeenCalled(); + expect(mock.modules.cfd.toggleCFDVerificationModal).toHaveBeenCalledTimes(0); + }); + }); + + it('should open account verification modal for unauthorized account', async () => { + jest.mock('@deriv/shared', () => ({ + getAuthenticationStatusInfo: jest.fn(() => ({ + poi_or_poa_not_submitted: true, + poi_acknowledged_for_vanuatu_maltainvest: false, + poi_acknowledged_for_bvi_labuan: false, + poa_acknowledged: false, + poa_pending: true, + })), + })); + + const mock = mockStore({ + client: { + account_status: { cashier_validation: ['system_maintenance'] }, + current_currency_type: 'crypto', + is_logged_in: true, + should_restrict_bvi_account_creation: false, + should_restrict_vanuatu_account_creation: false, + }, + modules: { + cfd: { + ...cfd_store_mock, + current_list: {}, + }, + }, + traders_hub: { + getAccount: jest.fn(), + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render( + + + , + { + wrapper, + } + ); + + const buttonElement = screen.getByRole('button', { name: /Add/i }); + + userEvent.click(buttonElement); + await waitFor(() => { + expect(mock.common.setAppstorePlatform).toHaveBeenCalledWith('mt5'); + expect(mock.modules.cfd.setJurisdictionSelectedShortcode).toHaveBeenCalled(); + expect(mock.modules.cfd.toggleCFDVerificationModal).toHaveBeenCalledTimes(1); + }); + }); + + it('should open account creation modal for dxtrade account', async () => { + jest.mock('@deriv/shared', () => ({ + getAuthenticationStatusInfo: jest.fn(() => ({ + poi_or_poa_not_submitted: true, + poi_acknowledged_for_vanuatu_maltainvest: false, + poi_acknowledged_for_bvi_labuan: false, + poa_acknowledged: false, + poa_pending: true, + })), + })); + + const mock = mockStore({ + client: { + account_status: { cashier_validation: ['system_maintenance'] }, + current_currency_type: 'crypto', + is_logged_in: true, + should_restrict_bvi_account_creation: false, + should_restrict_vanuatu_account_creation: false, + }, + modules: { + cfd: { + ...cfd_store_mock, + current_list: {}, + }, + }, + traders_hub: { + getAccount: jest.fn(), + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render( + + + , + { + wrapper, + } + ); + + const buttonElement = screen.getByRole('button', { name: /Add/i }); + + userEvent.click(buttonElement); + await waitFor(() => { + expect(mock.common.setAppstorePlatform).toHaveBeenCalledWith('dxtrade'); + expect(mock.traders_hub.getAccount).toHaveBeenCalled(); + }); + }); + + it('should disable the button and display "Added" text when account is already added', () => { + const mock = mockStore({ + client: { + account_status: { cashier_validation: ['system_maintenance'] }, + current_currency_type: 'crypto', + is_logged_in: true, + }, + modules: { + cfd: { + current_list: { + 'mt5.real.synthetic_svg@p01_ts03': { + landing_company_short: 'svg', + account_type: 'real', + market_type: 'synthetic', + }, + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render( + + + , + { + wrapper, + } + ); + + const buttonElement = screen.getByRole('button'); + + expect(buttonElement).toBeDisabled(); + expect(buttonElement).toHaveTextContent('Added'); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx new file mode 100644 index 000000000000..95602bc888bd --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-card.spec.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { CFD_PLATFORMS } from '@deriv/shared'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import CFDCompareAccountsCard from '../cfd-compare-accounts-card'; + +jest.mock('../../../Assets/svgs/trading-platform', () => jest.fn(() =>
Mocked Icon
)); +jest.mock('../instruments-icon-with-label', () => jest.fn(() =>
Mocked Icon With Label
)); + +describe('', () => { + const mock = mockStore({ + client: { + trading_platform_available_accounts: {}, + }, + traders_hub: { + available_cfd_accounts: [], + is_demo: false, + is_eu_user: false, + }, + modules: { + cfd: { + current_list: {}, + setAccountType: jest.fn(), + setJurisdictionSelectedShortcode: jest.fn(), + enableCFDPasswordModal: jest.fn(), + toggleCFDVerificationModal: jest.fn(), + }, + }, + }); + + const mocked_props = { + trading_platforms: { + market_type: 'gaming', + shortcode: 'svg', + platform: 'mt5', + }, + is_demo: false, + is_eu_user: false, + }; + + it('should render the component and not render the "New!" banner for MT5', () => { + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + const buttonElement = screen.getByRole('button', { name: /Add/i }); + expect(buttonElement).toBeInTheDocument(); + expect(buttonElement).toHaveClass('compare-cfd-account__button'); + expect(buttonElement).toBeEnabled(); + + expect(screen.queryByText(/New!/i)).not.toBeInTheDocument(); + }); + + it('should render the "Boom 300 and Crash 300 Index" for EU user', () => { + mocked_props.is_eu_user = true; + mocked_props.is_demo = false; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.queryByText(/New!/i)).not.toBeInTheDocument(); + expect(screen.getByText(/Boom 300 and Crash 300 Index/i)).toBeInTheDocument(); + }); + + it('should renders the component and not render the "New!" banner for MT5 demo', () => { + mocked_props.is_eu_user = false; + mocked_props.is_demo = true; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + const buttonElement = screen.getByRole('button', { name: /Add/i }); + expect(buttonElement).toBeInTheDocument(); + expect(buttonElement).toHaveClass('compare-cfd-account__button'); + expect(buttonElement).toBeEnabled(); + + expect(screen.queryByText(/New!/i)).not.toBeInTheDocument(); + }); + + it('should not render the "New!" banner for Deriv X', () => { + mocked_props.trading_platforms.platform = CFD_PLATFORMS.DXTRADE; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.queryByText(/New!/i)).not.toBeInTheDocument(); + }); + + it('should render the "New!" banner for DerivEz', () => { + mocked_props.trading_platforms.platform = CFD_PLATFORMS.DERIVEZ; + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.queryByText(/New!/i)).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx new file mode 100644 index 000000000000..420e7222d4a1 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-description.spec.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CFDCompareAccountsDescription from '../cfd-compare-accounts-description'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +describe('', () => { + const mock = mockStore({ + traders_hub: { + selected_region: 'Non-EU', + }, + }); + const mocked_props = { + trading_platforms: { + market_type: 'gaming', + shortcode: 'svg', + }, + is_demo: false, + }; + + const assertContent = ( + leverageDescription: string, + spread: string, + spreadDescription: string, + counterpartyCompanyDescription: string, + jurisdictionDescription: string + ) => { + expect(screen.getByText(leverageDescription)).toBeInTheDocument(); + expect(screen.getByText(spread)).toBeInTheDocument(); + expect(screen.getByText(spreadDescription)).toBeInTheDocument(); + expect(screen.getByText(counterpartyCompanyDescription)).toBeInTheDocument(); + expect(screen.getByText(jurisdictionDescription)).toBeInTheDocument(); + }; + const wrapper = ({ children }: { children: JSX.Element }) => {children}; + + it('should render CFDCompareAccountsDescription component on default props', () => { + render(, { wrapper }); + }); + + it('should render content for gaming market type with market type svg', () => { + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC')).toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + }); + + it('should render content for gaming market type with vanuatu shortcode', () => { + mocked_props.trading_platforms.shortcode = 'vanuatu'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (V) Ltd')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); + }); + + it('should render content for all market type with svg shortcode', () => { + mocked_props.trading_platforms.market_type = 'all'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC')).toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Financial Commission')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + + it('should render content for financial market type with svg shortcode', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (SVG) LLC')).toBeInTheDocument(); + expect(screen.getByText('St. Vincent & Grenadines')).toBeInTheDocument(); + expect(screen.getByText('Financial Commission')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + + it('should render content for financial market type with vanuatu shortcode', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'vanuatu'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Deriv (V) Ltd')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu')).toBeInTheDocument(); + expect(screen.getByText('Vanuatu Financial Services Commission')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + + it('should render content for financial market type with labuan shortcode', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'labuan'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('1:100')).toBeInTheDocument(); + expect(screen.getByText('Deriv (FX) Ltd')).toBeInTheDocument(); + expect(screen.getByText('Labuan')).toBeInTheDocument(); + expect(screen.getByText('Labuan Financial Services Authority')).toBeInTheDocument(); + expect(screen.getByText('(licence no. MB/18/0024)')).toBeInTheDocument(); + expect(screen.getByText('Regulator/External dispute resolution')).toBeInTheDocument(); + }); + + it('should render content for financial market type with maltainvest shortcode ', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'maltainvest'; + + render(, { wrapper }); + assertContent('Maximum leverage', '0.5 pips', 'Spreads from', 'Counterparty company', 'Jurisdiction'); + expect(screen.getByText('Up to 1:30')).toBeInTheDocument(); + expect(screen.getByText('Deriv Investments (Europe) Limited')).toBeInTheDocument(); + expect(screen.getByText('Malta')).toBeInTheDocument(); + expect(screen.getByText('Financial Commission')).toBeInTheDocument(); + expect( + screen.getByText('Regulated by the Malta Financial Services Authority (MFSA) (licence no. IS/70156)') + ).toBeInTheDocument(); + }); + + it('should render demo content for gaming market type with market type svg', () => { + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + + render(, { wrapper }); + expect(screen.getByText('1:1000')).toBeInTheDocument(); + expect(screen.getByText('Maximum leverage')).toBeInTheDocument(); + expect(screen.getByText('0.5 pips')).toBeInTheDocument(); + expect(screen.getByText('Spreads from')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx new file mode 100644 index 000000000000..7a859dfd72de --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-platform-label.spec.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CFDCompareAccountsPlatformLabel from '../cfd-compare-accounts-platform-label'; +import { platfromsHeaderLabel } from '../../../Helpers/compare-accounts-config'; + +describe('', () => { + const mocked_props = { + trading_platforms: { + platform: 'mt5', + }, + }; + it('should renders MT5 platform label', () => { + render(); + expect(screen.getByText(platfromsHeaderLabel.mt5)).toBeInTheDocument(); + }); + + it('should renders Deriv X platform label', () => { + mocked_props.trading_platforms.platform = 'dxtrade'; + render(); + expect(screen.getByText(platfromsHeaderLabel.other_cfds)).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx new file mode 100644 index 000000000000..069246a9836e --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts-title-icon.spec.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CFDCompareAccountsTitleIcon from '../cfd-compare-accounts-title-icon'; + +jest.mock('../../../Assets/svgs/trading-platform', () => jest.fn(() =>
Mocked Icon
)); + +const mocked_props = { + trading_platforms: { + platform: 'mt5', + market_type: 'gaming', + shortcode: 'svg', + }, + is_eu_user: false, + is_demo: false, +}; + +describe('', () => { + test('should render correct title for synthetic_svg market type and shortcode', () => { + render(); + expect(screen.getByText('Derived - SVG')).toBeInTheDocument(); + }); + + test('should render correct title for synthetic_bvi market type and shortcode', () => { + mocked_props.trading_platforms.shortcode = 'bvi'; + render(); + expect(screen.getByText('Derived - BVI')).toBeInTheDocument(); + }); + + test('should render correct title for synthetic_vanuatu market type and shortcode', () => { + mocked_props.trading_platforms.shortcode = 'vanuatu'; + render(); + expect(screen.getByText('Derived - Vanuatu')).toBeInTheDocument(); + }); + + test('should render correct title for financial_labuan market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'labuan'; + render(); + expect(screen.getByText('Financial - Labuan')).toBeInTheDocument(); + }); + + test('should render correct title for financial_vanuatu market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'vanuatu'; + render(); + expect(screen.getByText('Financial - Vanuatu')).toBeInTheDocument(); + }); + + test('should render correct title for financial_bvi market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'bvi'; + render(); + expect(screen.getByText('Financial - BVI')).toBeInTheDocument(); + }); + + test('should render correct title for Swap-Free market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; + render(); + expect(screen.getByText('Swap-Free - SVG')).toBeInTheDocument(); + }); + + test('should render correct title for Deriv X market type and shortcode', () => { + mocked_props.trading_platforms.platform = 'dxtrade'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; + render(); + expect(screen.getByText('Deriv X')).toBeInTheDocument(); + }); + + test('should render correct title for EU Clients', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_eu_user = true; + render(); + expect(screen.getByText('CFDs')).toBeInTheDocument(); + }); + + test('should render correct title for gaming market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'gaming'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + mocked_props.is_eu_user = false; + render(); + expect(screen.getByText('Derived Demo')).toBeInTheDocument(); + }); + + test('should render correct title for financial market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + mocked_props.is_eu_user = false; + render(); + expect(screen.getByText('Financial Demo')).toBeInTheDocument(); + }); + + test('should render correct title for Swap-Free with correct market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + mocked_props.is_eu_user = false; + render(); + expect(screen.getByText('Swap-Free Demo')).toBeInTheDocument(); + }); + + test('should render correct title for Swap-Free with correct market type and shortcode demo account', () => { + mocked_props.trading_platforms.platform = 'dxtrade'; + mocked_props.trading_platforms.market_type = 'all'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + mocked_props.is_eu_user = false; + render(); + expect(screen.getByText('Deriv X Demo')).toBeInTheDocument(); + }); + + test('should render correct title for EU clients demo accounts', () => { + mocked_props.trading_platforms.platform = 'mt5'; + mocked_props.trading_platforms.market_type = 'financial'; + mocked_props.trading_platforms.shortcode = 'svg'; + mocked_props.is_demo = true; + mocked_props.is_eu_user = true; + render(); + expect(screen.getByText('CFDs Demo')).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx new file mode 100644 index 000000000000..d5990fa6c0a6 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-compare-accounts.spec.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { MemoryRouter, useHistory } from 'react-router-dom'; +import { routes } from '@deriv/shared'; +import { render, screen } from '@testing-library/react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import CompareCFDs from '../cfd-compare-accounts'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: jest.fn(), +})); + +jest.mock('../../../Assets/svgs/trading-platform', () => jest.fn(() =>
Mocked Icon
)); +jest.mock('../instruments-icon-with-label', () => jest.fn(() =>
Mocked Icon With Label
)); +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), + CFDCompareAccountsCarousel: jest.fn(() =>
Next Button
), +})); + +describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const mock = mockStore({ + client: { + trading_platform_available_accounts: {}, + }, + traders_hub: { + available_cfd_accounts: [ + { + availability: 'Non-EU', + platform: 'dxtrade', + name: 'Deriv X', + market_type: 'all', + icon: '"DerivX"', + description: 'Example Description', + }, + ], + is_demo: false, + is_eu_user: false, + }, + modules: { + cfd: { + current_list: {}, + setAccountType: jest.fn(), + setJurisdictionSelectedShortcode: jest.fn(), + enableCFDPasswordModal: jest.fn(), + toggleCFDVerificationModal: jest.fn(), + }, + }, + }); + + it('should render the component and redirect to compare account page', () => { + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.getByText(/Trader's hub/i)).toBeInTheDocument(); + expect(screen.getByText(/Compare CFDs accounts/i)).toBeInTheDocument(); + }); + + it('should render the carousel componet with the correct content', () => { + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.getByText(/Next Button/i)).toBeInTheDocument(); + }); + + it('should render the component and redirect to compare demo account page', () => { + mock.traders_hub.is_demo = true; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render(, { wrapper }); + + expect(screen.getByText(/Trader's hub/i)).toBeInTheDocument(); + expect(screen.getByText(/Compare CFDs demo accounts/i)).toBeInTheDocument(); + }); + + it("navigates to the trader's hub when the navigation element is clicked", () => { + const historyMock = { + push: jest.fn(), + }; + (useHistory as jest.Mock).mockReturnValue(historyMock); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render( + + + , + { wrapper } + ); + + const navigationElement = screen.getByText(/Trader's hub/i); + navigationElement.click(); + + expect(historyMock.push).toHaveBeenCalledWith(routes.traders_hub); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx new file mode 100644 index 000000000000..6bc3834871af --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/cfd-instruments-label-highlighted.spec.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CFDInstrumentsLabelHighlighted from '../cfd-instruments-label-highlighted'; +import { StoreProvider, mockStore } from '@deriv/stores'; + +jest.mock('../instruments-icon-with-label', () => jest.fn(() =>
Mocked Icon
)); + +describe('', () => { + const mock = mockStore({ + traders_hub: { + selected_region: 'Non-EU', + }, + }); + + const mocked_props = { + trading_platforms: { + platform: 'mt5', + market_type: 'gaming', + shortcode: 'svg', + }, + }; + + it('should renders icons for market type gaming/synthetic', () => { + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const containerElement = screen.getByTestId('dt_compare_cfd_account_outline__container'); + expect(containerElement).toBeInTheDocument(); + expect(containerElement).toHaveClass('compare-cfd-account-outline'); + }); + + it('should renders icons for market type all financial', () => { + mocked_props.trading_platforms.market_type = 'financial'; + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + const containerElement = screen.getByTestId('dt_compare_cfd_account_outline__container'); + expect(containerElement).toBeInTheDocument(); + expect(containerElement).toHaveClass('compare-cfd-account-outline'); + }); + + it('should renders icons for market type all', () => { + mocked_props.trading_platforms.market_type = 'financial'; + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + const containerElement = screen.getByTestId('dt_compare_cfd_account_outline__container'); + expect(containerElement).toBeInTheDocument(); + expect(containerElement).toHaveClass('compare-cfd-account-outline'); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx new file mode 100644 index 000000000000..a4ccf243cf19 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/__tests__/instruments-icon-with-label.spec.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import InstrumentsIconWithLabel from '../instruments-icon-with-label'; + +describe('', () => { + const mocked_props = { + icon: 'example-icon', + text: 'Synthethics', + highlighted: true, + className: 'trading-instruments__icon', + is_asterisk: true, + }; + + it('should renders the component with correct props', () => { + render(); + + const iconElement = screen.getByTestId('dt_instruments_icon_container'); + const textElement = screen.getByText('Synthethics'); + const asteriskElement = screen.getByText('*'); + + expect(iconElement).toBeInTheDocument(); + expect(iconElement).toHaveClass('trading-instruments__icon'); + expect(textElement).toBeInTheDocument(); + expect(asteriskElement).toBeInTheDocument(); + expect(asteriskElement).toHaveClass('trading-instruments__span'); + }); + + it('should not apply opacity if "highlighted" prop is true', () => { + render(); + const containerElement = screen.getByTestId('dt_instruments_icon_container'); + expect(containerElement).toHaveStyle({ opacity: '0.2' }); + }); + + it('should not apply opacity if "highlighted" prop is true', () => { + render(); + const containerElement = screen.getByTestId('dt_instruments_icon_container'); + expect(containerElement).not.toHaveStyle({ opacity: '0.2' }); + }); + + it('should show the asterisk span when "is_asterisk" prop is true', () => { + render(); + const asteriskElement = screen.queryByText('*'); + expect(asteriskElement).toBeInTheDocument(); + }); + + it('should hide the asterisk span when "is_asterisk" prop is false', () => { + render(); + const asteriskElement = screen.queryByText('*'); + expect(asteriskElement).not.toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx new file mode 100644 index 000000000000..aa9272a9a75e --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-button.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { routes, getAuthenticationStatusInfo, WS, CFD_PLATFORMS } from '@deriv/shared'; +import { Button } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { observer, useStore } from '@deriv/stores'; +import { GetSettings, GetAccountSettingsResponse } from '@deriv/api-types'; +import { TCompareAccountsCard } from 'Components/props.types'; +import { + getMarketType, + getAccountVerficationStatus, + isMt5AccountAdded, + isDxtradeAccountAdded, +} from '../../Helpers/compare-accounts-config'; + +const CFDCompareAccountsButton = observer(({ trading_platforms, is_demo }: TCompareAccountsCard) => { + const history = useHistory(); + + const market_type = getMarketType(trading_platforms); + const market_type_shortcode = market_type.concat('_', trading_platforms.shortcode); + const { + modules: { cfd }, + common, + client, + traders_hub, + } = useStore(); + + const { + setAccountType, + setJurisdictionSelectedShortcode, + enableCFDPasswordModal, + toggleCFDVerificationModal, + current_list, + } = cfd; + const { getAccount } = traders_hub; + const { setAppstorePlatform } = common; + + const { + account_status, + account_settings, + should_restrict_bvi_account_creation, + should_restrict_vanuatu_account_creation, + is_logged_in, + is_virtual, + setAccountSettings, + updateMT5Status, + } = client; + + const { + poi_or_poa_not_submitted, + poi_acknowledged_for_vanuatu_maltainvest, + poi_acknowledged_for_bvi_labuan, + poa_acknowledged, + poa_pending, + } = getAuthenticationStatusInfo(account_status); + + const type_of_account = { + category: is_demo ? 'demo' : 'real', + type: market_type, + }; + + const [has_submitted_personal_details, setHasSubmittedPersonalDetails] = React.useState(false); + let is_account_added = false; + + if (trading_platforms.platform === CFD_PLATFORMS.MT5) { + is_account_added = isMt5AccountAdded(current_list, market_type_shortcode, is_demo); + } else if (trading_platforms.platform === CFD_PLATFORMS.DXTRADE) { + is_account_added = isDxtradeAccountAdded(current_list, is_demo); + } + + React.useEffect(() => { + if (is_logged_in && !is_virtual) { + updateMT5Status(); + } + if (!has_submitted_personal_details) { + let get_settings_response: GetSettings = {}; + if (!account_settings) { + WS.authorized.storage.getSettings().then((response: GetAccountSettingsResponse) => { + get_settings_response = response.get_settings as GetSettings; + setAccountSettings(response.get_settings as GetSettings); + }); + } else { + get_settings_response = account_settings; + } + const { citizen, place_of_birth, tax_residence, tax_identification_number, account_opening_reason } = + get_settings_response; + if (citizen && place_of_birth && tax_residence && tax_identification_number && account_opening_reason) { + setHasSubmittedPersonalDetails(true); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const is_account_status_verified = getAccountVerficationStatus( + market_type_shortcode, + poi_or_poa_not_submitted, + poi_acknowledged_for_vanuatu_maltainvest, + poi_acknowledged_for_bvi_labuan, + poa_acknowledged, + poa_pending, + should_restrict_bvi_account_creation, + should_restrict_vanuatu_account_creation, + has_submitted_personal_details, + is_demo + ); + + const onClickAdd = () => { + setAppstorePlatform(trading_platforms.platform as string); + if (trading_platforms.platform === CFD_PLATFORMS.MT5) { + setJurisdictionSelectedShortcode(trading_platforms.shortcode); + if (is_account_status_verified) { + setAccountType(type_of_account); + enableCFDPasswordModal(); + } else { + toggleCFDVerificationModal(); + } + } else { + setAccountType(type_of_account); + getAccount(); + } + history.push(routes.traders_hub); + }; + return ( + + ); +}); + +export default CFDCompareAccountsButton; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx new file mode 100644 index 000000000000..7e3ba00c73c2 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-card.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { CFD_PLATFORMS } from '@deriv/shared'; +import { localize, Localize } from '@deriv/translations'; +import { TCompareAccountsCard } from 'Components/props.types'; +import CFDInstrumentsLabelHighlighted from './cfd-instruments-label-highlighted'; +import CFDCompareAccountsDescription from './cfd-compare-accounts-description'; +import CFDCompareAccountsTitleIcon from './cfd-compare-accounts-title-icon'; +import CFDCompareAccountsPlatformLabel from './cfd-compare-accounts-platform-label'; +import CFDCompareAccountsButton from './cfd-compare-accounts-button'; + +const CFDCompareAccountsCard = ({ trading_platforms, is_eu_user, is_demo }: TCompareAccountsCard) => { + return ( +
+
+ + {(trading_platforms.platform === CFD_PLATFORMS.DERIVEZ || + trading_platforms.platform === CFD_PLATFORMS.CTRADER) && ( + + + + )} + + + + {is_eu_user && ( +
+ + {localize('*Boom 300 and Crash 300 Index')} + +
+ )} + +
+
+ ); +}; + +export default CFDCompareAccountsCard; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx new file mode 100644 index 000000000000..7ac6bf93c953 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-description.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Text } from '@deriv/components'; +import { TCompareAccountsCard } from 'Components/props.types'; +import { getJuridisctionDescription, getMarketType } from '../../Helpers/compare-accounts-config'; +import { useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; + +const CFDCompareAccountsDescription = ({ trading_platforms, is_demo }: TCompareAccountsCard) => { + const market_type = getMarketType(trading_platforms); + const market_type_shortcode = market_type.concat('_', trading_platforms.shortcode); + const juridisction_data = getJuridisctionDescription(market_type_shortcode); + const { traders_hub } = useStore(); + const { selected_region } = traders_hub; + return ( +
+
+ + {juridisction_data.leverage} + + + {selected_region === 'Non-EU' ? juridisction_data.leverage_description : localize('Leverage')} + +
+ {selected_region === 'Non-EU' && ( +
+ + {juridisction_data.spread} + + + {juridisction_data.spread_description} + +
+ )} + {!is_demo && ( + +
+ + {juridisction_data.counterparty_company} + + + {juridisction_data.counterparty_company_description} + +
+
+ + {juridisction_data.jurisdiction} + + + {juridisction_data.jurisdiction_description} + +
+
+ + {juridisction_data.regulator} + + {juridisction_data.regulator_license && ( + + {juridisction_data.regulator_license} + + )} + + {juridisction_data.regulator_description} + +
+
+ )} +
+ ); +}; + +export default CFDCompareAccountsDescription; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx new file mode 100644 index 000000000000..1ee66bb2eccc --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-platform-label.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Text } from '@deriv/components'; +import { TCompareAccountsCard } from 'Components/props.types'; +import { getPlatformLabel, getHeaderColor, platfromsHeaderLabel } from '../../Helpers/compare-accounts-config'; + +const CFDCompareAccountsPlatformLabel = ({ trading_platforms }: TCompareAccountsCard) => { + const platform_label = getPlatformLabel(trading_platforms.platform); + const header_color = getHeaderColor(platform_label); + + return ( +
+ + {platform_label} + +
+ ); +}; + +export default CFDCompareAccountsPlatformLabel; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx new file mode 100644 index 000000000000..962ff68dfe74 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts-title-icon.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Text, Popover } from '@deriv/components'; +import { isMobile } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import TradigPlatformIconProps from '../../Assets/svgs/trading-platform'; +import { TCompareAccountsCard } from 'Components/props.types'; +import { getAccountCardTitle, getMarketType, getAccountIcon } from '../../Helpers/compare-accounts-config'; + +const CFDCompareAccountsTitleIcon = ({ trading_platforms, is_eu_user, is_demo }: TCompareAccountsCard) => { + const market_type = !is_eu_user ? getMarketType(trading_platforms) : 'CFDs'; + const market_type_shortcode = market_type.concat('_', trading_platforms.shortcode); + const jurisdiction_card_icon = + trading_platforms.platform === 'dxtrade' + ? getAccountIcon(trading_platforms.platform) + : getAccountIcon(market_type); + const jurisdiction_card_title = + trading_platforms.platform === 'dxtrade' + ? getAccountCardTitle(trading_platforms.platform, is_demo) + : getAccountCardTitle(market_type_shortcode, is_demo); + const labuan_jurisdiction_message = localize( + 'Choosing this jurisdiction will give you a Financial STP account. Your trades will go directly to the market and have tighter spreads.' + ); + + return ( + +
+ +
+ + {jurisdiction_card_title} + + {market_type_shortcode === 'financial_labuan' && ( + + )} +
+
+
+
+ ); +}; + +export default CFDCompareAccountsTitleIcon; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss new file mode 100644 index 000000000000..22b03fc7049b --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.scss @@ -0,0 +1,162 @@ +.compare-cfd-header { + display: flex; + align-items: center; + justify-content: space-between; + &-navigation { + display: flex; + align-items: center; + gap: 1rem; + cursor: pointer; + } + &-title { + flex: 1; + @include mobile { + text-align: center; + padding: 1rem; + } + } +} + +.compare-cfd-account { + max-width: 123.2rem; + margin: auto; + &-container { + margin: 1.5rem; + @include mobile { + margin: 0; + overflow-x: auto; + overflow-y: scroll; + padding: 0 0 20.1rem; + max-height: 80rem; + width: 100%; + } + &__card-count { + display: flex; + justify-content: center; + margin-left: 13rem; + &--mobile { + margin-left: 10.5rem; + } + } + } + &-main-container { + padding-left: 1rem; + padding-right: 1rem; + } + &-card-container { + position: relative; + overflow: hidden; + width: 27rem; + border: 1px solid var(--general-hover); + border-radius: 2.4rem; + &:hover { + box-shadow: 0 2px 8px 0 var(--shadow-menu); + } + @include mobile { + width: 18rem; + } + &__eu-clients { + position: relative; + top: 0.5rem; + text-align: center; + } + &__banner { + position: absolute; + z-index: 1; + display: flex; + justify-content: center; + align-items: center; + padding: 1.5rem; + width: 15rem; + height: 2rem; + background: var(--status-transfer); + color: var(--text-colored-background); + transform: translateX(17rem) translateY(-2rem) rotate(45deg); + } + } + &-outline { + display: flex; + flex-direction: column; + padding: 4rem 2.4rem 0; + border-radius: 2.4rem; + @include mobile { + padding: 7rem 1.5rem 0; + } + } + &-text-container { + max-height: 25.5rem; + &--demo { + max-height: 13.5rem; + } + &__separator { + margin: 0.9rem; + } + } + &-icon-title { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding-top: 2rem; + align-items: center; + &__separator { + display: flex; + align-items: center; + gap: 0.8em; + } + } + &-instrument-icon { + display: flex; + align-items: center; + margin: 0.2rem; + cursor: not-allowed; + } + &-labuan-tooltip { + position: relative; + left: 0.5rem; + top: 0.2rem; + &--msg { + position: relative; + width: 62%; + @include mobile { + display: block; + position: fixed; + width: 15.2rem; + right: 0; + } + } + } + &-platform-label { + background-color: var(--header-background-mt5); + padding: 0.9rem; + border-top-left-radius: 1.4rem; + border-top-right-radius: 1.4rem; + &--other-cfds { + background-color: var(--header-background-others); + } + } + &-underline { + border-top: 0.5rem solid var(--less-prominent); + width: 21.3rem; + } + &__button { + width: calc(100% - 4rem); + height: 4rem; + margin: 2rem; + } +} +.card-list { + display: flex; + gap: 2rem; +} + +.trading-instruments { + &__span { + position: relative; + top: 0.2rem; + font-size: 1.6rem; + color: var(--text-red); + } + &__text { + margin-left: 0.5rem; + } +} diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx new file mode 100644 index 000000000000..1fc53ca34d29 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-compare-accounts.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import classNames from 'classnames'; +import { Text, Icon, PageOverlay, DesktopWrapper, MobileWrapper, CFDCompareAccountsCarousel } from '@deriv/components'; +import { routes } from '@deriv/shared'; +import { Localize, localize } from '@deriv/translations'; +import { observer, useStore } from '@deriv/stores'; +import CFDCompareAccountsCard from './cfd-compare-accounts-card'; +import { + getSortedCFDAvailableAccounts, + getEUAvailableAccounts, + getMT5DemoData, + getDxtradeDemoData, + dxtrade_data, +} from '../../Helpers/compare-accounts-config'; + +const CompareCFDs = observer(() => { + const history = useHistory(); + const store = useStore(); + const { client, traders_hub } = store; + const { trading_platform_available_accounts } = client; + const { is_demo, is_eu_user, available_dxtrade_accounts, selected_region } = traders_hub; + + const sorted_available_accounts = !is_eu_user + ? getSortedCFDAvailableAccounts(trading_platform_available_accounts) + : getEUAvailableAccounts(trading_platform_available_accounts); + + // Check if dxtrade data is available + const has_dxtrade_account_available = available_dxtrade_accounts.length > 0; + + const sorted_cfd_available_eu_accounts = + is_eu_user && sorted_available_accounts.length ? [...sorted_available_accounts] : []; + + // Getting real accounts data + const all_real_sorted_cfd_available_accounts = !is_eu_user + ? [...sorted_available_accounts] + : [...sorted_cfd_available_eu_accounts]; + + // Getting demo accounts data + const demo_cfd_available_accounts = [ + ...getMT5DemoData(all_real_sorted_cfd_available_accounts), + ...getDxtradeDemoData(all_real_sorted_cfd_available_accounts), + ]; + + const all_cfd_available_accounts = + is_demo && demo_cfd_available_accounts.length > 0 + ? demo_cfd_available_accounts + : all_real_sorted_cfd_available_accounts; + + // Calculate the card count for alignment of card in center + const card_count = has_dxtrade_account_available + ? all_cfd_available_accounts.length + 1 + : all_cfd_available_accounts.length; + + const DesktopHeader = ( +
+
{ + history.push(routes.traders_hub); + }} + > + + + + +
+

+ + + +

+
+ ); + + return ( + + +
+ +
+
+ + {all_cfd_available_accounts.map(item => ( + + ))} + {/* Renders Deriv X data */} + {all_cfd_available_accounts.length > 0 && has_dxtrade_account_available && ( + + )} + +
+
+
+
+ + + } + header_classname='compare-cfd-header-title' + is_from_app={!routes.traders_hub} + onClickClose={() => history.push(routes.traders_hub)} + > +
+ + {all_cfd_available_accounts.map(item => ( + + ))} + {/* Renders Deriv X data */} + {all_cfd_available_accounts.length > 0 && has_dxtrade_account_available && ( + + )} + +
+
+
+
+ ); +}); + +export default CompareCFDs; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx new file mode 100644 index 000000000000..77647bb7a951 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/cfd-instruments-label-highlighted.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import InstrumentsIconWithLabel from './instruments-icon-with-label'; +import { TInstrumentsIcon, TCompareAccountsCard } from 'Components/props.types'; +import { getHighlightedIconLabel } from '../../Helpers/compare-accounts-config'; +import { useStore } from '@deriv/stores'; + +const CFDInstrumentsLabelHighlighted = ({ trading_platforms, is_demo }: TCompareAccountsCard) => { + const { traders_hub } = useStore(); + const selected_region = traders_hub.selected_region; + + const iconData: TInstrumentsIcon[] = [...getHighlightedIconLabel(trading_platforms, selected_region, is_demo)]; + + return ( +
+ {iconData.map(item => ( + + ))} +
+ ); +}; + +export default CFDInstrumentsLabelHighlighted; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/index.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/index.tsx new file mode 100644 index 000000000000..ef3d6141abff --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/index.tsx @@ -0,0 +1,4 @@ +import CFDCompareAccounts from './cfd-compare-accounts'; +import './cfd-compare-accounts.scss'; + +export default CFDCompareAccounts; diff --git a/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx b/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx new file mode 100644 index 000000000000..ae0624646d83 --- /dev/null +++ b/packages/cfd/src/Containers/cfd-compare-accounts/instruments-icon-with-label.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { TInstrumentsIcon } from 'Components/props.types'; +import TradingInstrumentsIcon from '../../Assets/svgs/trading-instruments'; + +const InstrumentsIconWithLabel = ({ icon, text, highlighted, className, is_asterisk }: TInstrumentsIcon) => { + return ( +
+ + + {text} + + {is_asterisk && ( + + * + + )} +
+ ); +}; + +export default InstrumentsIconWithLabel; diff --git a/packages/cfd/src/Helpers/compare-accounts-config.ts b/packages/cfd/src/Helpers/compare-accounts-config.ts new file mode 100644 index 000000000000..a34dc1a14a64 --- /dev/null +++ b/packages/cfd/src/Helpers/compare-accounts-config.ts @@ -0,0 +1,428 @@ +import { CFD_PLATFORMS } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { + TInstrumentsIcon, + TModifiedTradingPlatformAvailableAccount, + TDetailsOfEachMT5Loginid, +} from '../Components/props.types'; + +// Map the accounts according to the market type +const getHighlightedIconLabel = ( + trading_platforms: TModifiedTradingPlatformAvailableAccount, + selected_region?: string, + is_demo?: boolean +): TInstrumentsIcon[] => { + const market_type = getMarketType(trading_platforms); + const market_type_shortcode = market_type.concat('_', trading_platforms.shortcode); + // Forex for these: MT5 Financial Vanuatu, MT5 Financial Labuan + const forex_label = + ['financial_labuan', 'financial_vanuatu'].includes(market_type_shortcode) || + is_demo || + trading_platforms.platform === CFD_PLATFORMS.DXTRADE || + selected_region === 'EU' + ? localize('Forex') + : localize('Forex: standard/micro'); + + switch (trading_platforms.market_type) { + case 'gaming': + return [ + { icon: 'Synthetics', text: localize('Synthetics'), highlighted: true }, + { icon: 'Baskets', text: localize('Baskets'), highlighted: true }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: false }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: false }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: false }, + { icon: 'Forex', text: forex_label, highlighted: false }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: false }, + { icon: 'ETF', text: localize('ETF'), highlighted: false }, + ]; + case 'financial': + switch (trading_platforms.shortcode) { + case 'maltainvest': + return [ + { icon: 'Synthetics', text: localize('Synthetics'), highlighted: true, is_asterisk: true }, + { icon: 'Forex', text: forex_label, highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + ]; + case 'labuan': + return [ + { icon: 'Synthetics', text: localize('Synthetics'), highlighted: false }, + { icon: 'Baskets', text: localize('Baskets'), highlighted: false }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: false }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: false }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: false }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: false }, + { icon: 'Forex', text: forex_label, highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETF'), highlighted: true }, + ]; + default: + return [ + { icon: 'Synthetics', text: localize('Synthetics'), highlighted: false }, + { icon: 'Baskets', text: localize('Baskets'), highlighted: false }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: false }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Forex', text: forex_label, highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETF'), highlighted: true }, + ]; + } + case 'all': + default: + if (trading_platforms.platform === 'mt5') { + return [ + { icon: 'Synthetics', text: localize('Synthetics'), highlighted: true }, + { icon: 'Baskets', text: localize('Baskets'), highlighted: false }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: false }, + { icon: 'Forex', text: forex_label, highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETF'), highlighted: true }, + ]; + } + return [ + { icon: 'Synthetics', text: localize('Synthetics'), highlighted: true }, + { icon: 'Baskets', text: localize('Baskets'), highlighted: true }, + { icon: 'DerivedFX', text: localize('Derived FX'), highlighted: true }, + { icon: 'Stocks', text: localize('Stocks'), highlighted: true }, + { icon: 'StockIndices', text: localize('Stock indices'), highlighted: true }, + { icon: 'Commodities', text: localize('Commodities'), highlighted: true }, + { icon: 'Forex', text: forex_label, highlighted: true }, + { icon: 'Cryptocurrencies', text: localize('Cryptocurrencies'), highlighted: true }, + { icon: 'ETF', text: localize('ETF'), highlighted: true }, + ]; + } +}; + +// Get the Account Title according to the market type and jurisdiction +const getAccountCardTitle = (shortcode: string, is_demo?: boolean) => { + switch (shortcode) { + case 'synthetic_svg': + return is_demo ? localize('Derived Demo') : localize('Derived - SVG'); + case 'synthetic_bvi': + return localize('Derived - BVI'); + case 'synthetic_vanuatu': + return localize('Derived - Vanuatu'); + case 'financial_svg': + return is_demo ? localize('Financial Demo') : localize('Financial - SVG'); + case 'financial_bvi': + return localize('Financial - BVI'); + case 'financial_vanuatu': + return localize('Financial - Vanuatu'); + case 'financial_labuan': + return localize('Financial - Labuan'); + case 'all_svg': + return is_demo ? localize('Swap-Free Demo') : localize('Swap-Free - SVG'); + case 'dxtrade': + return is_demo ? localize('Deriv X Demo') : localize('Deriv X'); + default: + return is_demo ? localize('CFDs Demo') : localize('CFDs'); + } +}; + +// Get the Platform label +const getPlatformLabel = (shortcode?: string) => { + switch (shortcode) { + case 'dxtrade': + case 'CFDs': + return localize('Other CFDs Platform'); + case 'mt5': + default: + return localize('MT5 Platform'); + } +}; + +// Object to map the platform label +const platfromsHeaderLabel = { + mt5: localize('MT5 Platform'), + other_cfds: localize('Other CFDs Platform'), +}; + +// Get the Account Icons based on the market type +const getAccountIcon = (shortcode: string) => { + switch (shortcode) { + case 'synthetic': + return 'Derived'; + case 'financial': + return 'Financial'; + case 'all': + return 'SwapFree'; + case 'dxtrade': + return 'DerivX'; + default: + return 'CFDs'; + } +}; + +// Convert the market type from gaming to synthethics +const getMarketType = (trading_platforms: TModifiedTradingPlatformAvailableAccount) => { + return trading_platforms.market_type === 'gaming' ? 'synthetic' : trading_platforms.market_type; +}; + +// Get the color of Header based on the platform +const getHeaderColor = (shortcode: string) => { + switch (shortcode) { + case platfromsHeaderLabel.other_cfds: + return 'green'; + case platfromsHeaderLabel.mt5: + default: + return 'blue'; + } +}; + +// Config for different Jurisdictions +const cfd_config = () => ({ + leverage: '1:1000', + leverage_description: localize('Maximum leverage'), + spread: '0.5 pips', + spread_description: localize('Spreads from'), + counterparty_company: 'Deriv (SVG) LLC', + counterparty_company_description: localize('Counterparty company'), + jurisdiction: 'St. Vincent & Grenadines', + jurisdiction_description: localize('Jurisdiction'), + regulator: localize('Financial Commission'), + regulator_description: localize('Regulator/External dispute resolution'), + regulator_license: '', +}); + +// Map the Jurisdictions with the config +const getJuridisctionDescription = (shortcode: string) => { + const createDescription = ( + counterparty_company: string, + jurisdiction: string, + regulator: string, + regulator_license: string | undefined, + regulator_description: string, + leverage: string = cfd_config().leverage + ) => ({ + ...cfd_config(), + counterparty_company, + jurisdiction, + regulator, + regulator_license, + regulator_description, + leverage, + }); + + switch (shortcode) { + case 'synthetic_bvi': + return createDescription( + 'Deriv (BVI) Ltd', + 'British Virgin Islands', + localize('British Virgin Islands Financial Services Commission'), + localize('(License no. SIBA/L/18/1114)'), + localize('Regulator/External dispute resolution') + ); + case 'synthetic_vanuatu': + return createDescription( + 'Deriv (V) Ltd', + 'Vanuatu', + localize('Vanuatu Financial Services Commission'), + '', + localize('Regulator/External dispute resolution') + ); + case 'financial_bvi': + return createDescription( + 'Deriv (BVI) Ltd', + 'British Virgin Islands', + localize('British Virgin Islands Financial Services Commission'), + localize('(License no. SIBA/L/18/1114)'), + localize('Regulator/External dispute resolution') + ); + case 'financial_vanuatu': + return createDescription( + 'Deriv (V) Ltd', + 'Vanuatu', + localize('Vanuatu Financial Services Commission'), + '', + localize('Regulator/External dispute resolution') + ); + case 'financial_labuan': + return createDescription( + 'Deriv (FX) Ltd', + 'Labuan', + localize('Labuan Financial Services Authority'), + localize('(licence no. MB/18/0024)'), + localize('Regulator/External dispute resolution'), + '1:100' + ); + case 'financial_maltainvest': + return createDescription( + 'Deriv Investments (Europe) Limited', + 'Malta', + localize('Financial Commission'), + localize('Regulated by the Malta Financial Services Authority (MFSA) (licence no. IS/70156)'), + '', + 'Up to 1:30' + ); + // Dxtrade + case 'all_': + case 'all_svg': + case 'synthetic_svg': + case 'financial_svg': + default: + return cfd_config(); + } +}; + +// Sort the MT5 accounts in the order of derived, financial and swap-free +const getSortedCFDAvailableAccounts = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { + const swap_free_accounts = available_accounts + .filter(item => item.market_type === 'all') + .map(item => ({ ...item, platform: 'mt5' } as const)); + const financial_accounts = available_accounts + .filter(item => item.market_type === 'financial' && item.shortcode !== 'maltainvest') + .map(item => ({ ...item, platform: 'mt5' } as const)); + const gaming_accounts = available_accounts + .filter(item => item.market_type === 'gaming') + .map(item => ({ ...item, platform: 'mt5' } as const)); + return [...gaming_accounts, ...financial_accounts, ...swap_free_accounts]; +}; + +// Get the maltainvest accounts for EU and DIEL clients +const getEUAvailableAccounts = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { + const financial_accounts = available_accounts + .filter(item => item.market_type === 'financial' && item.shortcode === 'maltainvest') + .map(item => ({ ...item, platform: 'mt5' } as const)); + return [...financial_accounts]; +}; + +// Make the Deriv X data same as trading_platform_available_accounts +const dxtrade_data: TModifiedTradingPlatformAvailableAccount = { + market_type: 'all', + name: 'Deriv X', + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: 'svg', + sub_account_type: '', + platform: 'dxtrade', +}; + +// Check whether the POA POI status are completed for different jurisdictions +const getAccountVerficationStatus = ( + market_type_shortcode: string, + poi_or_poa_not_submitted: boolean, + poi_acknowledged_for_vanuatu_maltainvest: boolean, + poi_acknowledged_for_bvi_labuan: boolean, + poa_acknowledged: boolean, + poa_pending: boolean, + should_restrict_bvi_account_creation: boolean, + should_restrict_vanuatu_account_creation: boolean, + has_submitted_personal_details: boolean, + is_demo?: boolean +) => { + switch (market_type_shortcode) { + case 'synthetic_svg': + case 'financial_svg': + case 'all_svg': + return true; + case 'synthetic_bvi': + case 'financial_bvi': + if ( + poi_acknowledged_for_bvi_labuan && + !poi_or_poa_not_submitted && + !should_restrict_bvi_account_creation && + has_submitted_personal_details && + poa_acknowledged + ) { + return true; + } + return false; + case 'synthetic_vanuatu': + case 'financial_vanuatu': + if ( + poi_acknowledged_for_vanuatu_maltainvest && + !poi_or_poa_not_submitted && + !should_restrict_vanuatu_account_creation && + has_submitted_personal_details && + poa_acknowledged + ) { + return true; + } + return false; + + case 'financial_labuan': + if (poi_acknowledged_for_bvi_labuan && poa_acknowledged && has_submitted_personal_details) { + return true; + } + return false; + + case 'financial_maltainvest': + if ((poi_acknowledged_for_vanuatu_maltainvest && poa_acknowledged) || is_demo) { + return true; + } + return false; + default: + return false; + } +}; + +// Check what MT5 accounts are added based on jurisdisction +const isMt5AccountAdded = (current_list: Record, item: string, is_demo?: boolean) => + Object.entries(current_list).some(([key, value]) => { + const [market, type] = item.split('_'); + const current_account_type = is_demo ? 'demo' : 'real'; + return ( + value.market_type === market && + value.landing_company_short === type && + value.account_type === current_account_type && + key.includes(CFD_PLATFORMS.MT5) + ); + }); + +const isDxtradeAccountAdded = (current_list: Record, is_demo?: boolean) => + Object.entries(current_list).some(([key, value]) => { + const current_account_type = is_demo ? 'demo' : 'real'; + return value.account_type === current_account_type && key.includes(CFD_PLATFORMS.DXTRADE); + }); + +// Get the MT5 demo accounts of the user +const getMT5DemoData = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { + const swap_free_demo_accounts = available_accounts.filter( + item => item.market_type === 'all' && item.shortcode === 'svg' && item.platform === CFD_PLATFORMS.MT5 + ); + const financial_demo_accounts = available_accounts.filter( + item => item.market_type === 'financial' && item.shortcode === 'svg' + ); + const gaming_demo_accounts = available_accounts.filter( + item => item.market_type === 'gaming' && item.shortcode === 'svg' + ); + return [...gaming_demo_accounts, ...financial_demo_accounts, ...swap_free_demo_accounts]; +}; +const getDxtradeDemoData = (available_accounts: TModifiedTradingPlatformAvailableAccount[]) => { + return available_accounts.filter(item => item.platform === CFD_PLATFORMS.DXTRADE); +}; + +export { + getHighlightedIconLabel, + getJuridisctionDescription, + getAccountCardTitle, + getMarketType, + getAccountIcon, + getPlatformLabel, + getSortedCFDAvailableAccounts, + getEUAvailableAccounts, + dxtrade_data, + getHeaderColor, + platfromsHeaderLabel, + getAccountVerficationStatus, + isMt5AccountAdded, + isDxtradeAccountAdded, + getMT5DemoData, + getDxtradeDemoData, +}; diff --git a/packages/components/package.json b/packages/components/package.json index a588bc258f8e..a0bd581f4b13 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -73,6 +73,7 @@ "@deriv/shared": "^1.0.0", "@deriv/translations": "^1.0.0", "classnames": "^2.2.6", + "embla-carousel-react": "^8.0.0-rc12", "gh-pages": "^2.1.1", "glob": "^7.1.5", "lodash.throttle": "^4.1.1", diff --git a/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel-button.tsx b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel-button.tsx new file mode 100644 index 000000000000..fb4f356a5bd4 --- /dev/null +++ b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel-button.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import classNames from 'classnames'; +import Icon from '../icon'; + +type TPrevNextButtonProps = { + enabled: boolean; + onClick: () => void; + isNext: boolean; +}; + +const CFDCompareAccountsCarouselButton = (props: TPrevNextButtonProps) => { + const { enabled, onClick, isNext } = props; + + return ( + + ); +}; +export default CFDCompareAccountsCarouselButton; diff --git a/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss new file mode 100644 index 000000000000..e8af0d17e45a --- /dev/null +++ b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.scss @@ -0,0 +1,69 @@ +.cfd-compare-accounts-carousel { + position: relative; + --slide-spacing: 1rem; + --slide-size: 50%; + --slide-height: 19rem; + overflow: hidden; + &__viewport { + overflow: hidden; + width: 100%; + height: 100%; + @include mobile { + padding-bottom: 6rem; + } + } + &__container { + backface-visibility: hidden; + display: flex; + touch-action: pan-y; + flex-direction: row; + max-height: auto; + margin-left: calc(var(--slide-spacing) * -1); + transition: transform 0s ease-in-out; + } + &__slide { + flex: 0 0 var(--slide-size); + min-width: 0; + padding-left: var(--slide-spacing); + position: relative; + &__img { + display: block; + height: var(--slide-height); + width: 100%; + object-fit: cover; + } + } + &__button { + background-color: var(--general-main-1); + z-index: 1; + color: var(--background-site); + position: absolute; + display: flex; + align-items: center; + justify-content: center; + top: 50%; + cursor: pointer; + width: 4rem; + height: 4rem; + border: 1px solid var(--general-background-main); + border-radius: 50%; + box-shadow: 0px 0px 24px rgba(0, 0, 0, 0.08), 0px 24px 24px rgba(0, 0, 0, 0.08); + &--prev { + left: 1.6rem; + } + &--next { + right: 1.6rem; + } + &:disabled { + opacity: 0.3; + display: none; + } + &__svg { + width: 50%; + height: 35%; + } + @include mobile { + display: none; + } + } +} diff --git a/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.tsx b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.tsx new file mode 100644 index 000000000000..dfad813ee75c --- /dev/null +++ b/packages/components/src/components/cfd-compare-accounts-carousel/cfd-compare-accounts-carousel.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import useEmblaCarousel, { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel-react'; +import CFDCompareAccountsCarouselButton from './cfd-compare-accounts-carousel-button'; + +type TCFDCompareAccountsCarousel = { + children: React.ReactNode; +}; + +const CFDCompareAccountsCarousel = (props: TCFDCompareAccountsCarousel) => { + const options: EmblaOptionsType = { + align: 0, + containScroll: 'trimSnaps', + }; + const [emblaRef, emblaApi] = useEmblaCarousel(options); + const [prev_btn_enabled, setPrevBtnEnabled] = React.useState(false); + const [next_btn_enabled, setNextBtnEnabled] = React.useState(false); + + const scrollPrev = React.useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]); + const scrollNext = React.useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]); + + const onSelect = React.useCallback((embla_api: EmblaCarouselType) => { + setPrevBtnEnabled(embla_api.canScrollPrev()); + setNextBtnEnabled(embla_api.canScrollNext()); + }, []); + + React.useEffect(() => { + if (!emblaApi) return; + + onSelect(emblaApi); + emblaApi.on('reInit', onSelect); + emblaApi.on('select', onSelect); + }, [emblaApi, onSelect]); + + return ( +
+
+
{props.children}
+
+ + +
+ ); +}; + +export default CFDCompareAccountsCarousel; diff --git a/packages/components/src/components/cfd-compare-accounts-carousel/index.ts b/packages/components/src/components/cfd-compare-accounts-carousel/index.ts new file mode 100644 index 000000000000..214b18e90ee0 --- /dev/null +++ b/packages/components/src/components/cfd-compare-accounts-carousel/index.ts @@ -0,0 +1,4 @@ +import CFDCompareAccountsCarousel from './cfd-compare-accounts-carousel'; +import './cfd-compare-accounts-carousel.scss'; + +export default CFDCompareAccountsCarousel; diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 5c95f4df7fdd..639324205a3c 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -20,6 +20,7 @@ export { default as ButtonToggle } from './components/button-toggle'; export { default as Calendar } from './components/calendar'; export { default as Card } from './components/card'; export { default as Carousel } from './components/carousel'; +export { default as CFDCompareAccountsCarousel } from './components/cfd-compare-accounts-carousel'; export { default as Clipboard } from './components/clipboard'; export { default as Checkbox } from './components/checkbox'; export { default as Checklist } from './components/checklist'; diff --git a/packages/core/src/App/Constants/routes-config.js b/packages/core/src/App/Constants/routes-config.js index 8f2f7ed444e2..8e6688ffad13 100644 --- a/packages/core/src/App/Constants/routes-config.js +++ b/packages/core/src/App/Constants/routes-config.js @@ -5,6 +5,7 @@ import { Loading } from '@deriv/components'; import { localize } from '@deriv/translations'; import Redirect from 'App/Containers/Redirect'; import Endpoint from 'Modules/Endpoint'; +import CFDCompareAccounts from '@deriv/cfd/src/Containers/cfd-compare-accounts'; // Error Routes const Page404 = React.lazy(() => import(/* webpackChunkName: "404" */ 'Modules/Page404')); @@ -104,6 +105,11 @@ const getModules = () => { component: props => , getTitle: () => localize('Deriv X'), }, + { + path: routes.compare_cfds, + component: CFDCompareAccounts, + getTitle: () => localize('Compare CFD accounts'), + }, { path: routes.mt5, component: props => , diff --git a/packages/core/src/App/Containers/Layout/header/header.jsx b/packages/core/src/App/Containers/Layout/header/header.jsx index b3c5c2bde537..3760a93b2944 100644 --- a/packages/core/src/App/Containers/Layout/header/header.jsx +++ b/packages/core/src/App/Containers/Layout/header/header.jsx @@ -11,7 +11,10 @@ const Header = ({ is_logged_in }) => { const { is_appstore } = React.useContext(PlatformContext); const { pathname } = useLocation(); const trading_hub_routes = - pathname === routes.traders_hub || pathname.startsWith(routes.account) || pathname.startsWith(routes.cashier); + pathname === routes.traders_hub || + pathname.startsWith(routes.account) || + pathname.startsWith(routes.cashier) || + pathname.startsWith(routes.compare_cfds); if (is_appstore) { return ; diff --git a/packages/shared/src/styles/constants.scss b/packages/shared/src/styles/constants.scss index 1a6b7f52a830..d9c22b41345b 100644 --- a/packages/shared/src/styles/constants.scss +++ b/packages/shared/src/styles/constants.scss @@ -24,6 +24,7 @@ $color-blue-4: #0677af; $color-blue-5: #dfeaff; $color-blue-6: #92b8ff; $color-blue-7: #182130; +$color-blue-8: #e6f5ff; $color-brown: #664407; $color-green: #85acb0; $color-green-1: #4bb4b3; @@ -31,6 +32,8 @@ $color-green-2: #3d9494; $color-green-3: #00a79e; $color-green-4: #008079; $color-green-5: #4bb4b329; +$color-green-6: #17eabd; +$color-green-7: #e8fdf8; $color-grey: #c2c2c2; $color-grey-1: #999999; $color-grey-2: #f2f3f4; diff --git a/packages/shared/src/styles/themes.scss b/packages/shared/src/styles/themes.scss index 4f078608c0da..0c1cc6188795 100644 --- a/packages/shared/src/styles/themes.scss +++ b/packages/shared/src/styles/themes.scss @@ -89,6 +89,7 @@ --text-profit-success: #{$color-green-1}; --text-warning: #{$color-yellow}; --text-red: #{$color-red}; + --text-green: #{$color-green-6}; --text-blue: #{$color-blue-3}; --text-info-blue: #{$color-blue}; --text-info-blue-background: #{$color-blue-5}; @@ -198,6 +199,9 @@ --badge-blue: #{$color-blue-4}; --badge-violet: #{$color-blue-2}; --badge-green: #{$color-green-3}; + // Header + --header-background-mt5: #{$color-blue-8}; + --header-background-others: #{$color-green-7}; } .theme--dark { // General @@ -314,5 +318,8 @@ --badge-blue: #{$color-blue-4}; --badge-violet: #{$color-blue-2}; --badge-green: #{$color-green-3}; + // Header + --header-background-mt5: #{$color-blue-8}; + --header-background-others: #{$color-green-7}; } } diff --git a/packages/shared/src/utils/routes/routes.ts b/packages/shared/src/utils/routes/routes.ts index 8f756f2514c6..94744d59611d 100644 --- a/packages/shared/src/utils/routes/routes.ts +++ b/packages/shared/src/utils/routes/routes.ts @@ -71,4 +71,5 @@ export const routes = { appstore: '/appstore', traders_hub: '/appstore/traders-hub', onboarding: '/appstore/onboarding', + compare_cfds: '/appstore/cfd-compare-acccounts', }; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index da779c6c7307..74848c909359 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -351,8 +351,10 @@ const mock = (): TStores & { is_mock: boolean } => { setResetTradingPasswordModalOpen: jest.fn(), }, traders_hub: { + getAccount: jest.fn(), closeModal: jest.fn(), combined_cfd_mt5_accounts: [], + available_cfd_accounts: [], content_flag: '', CFDs_restricted_countries: false, openModal: jest.fn(), @@ -400,16 +402,15 @@ const mock = (): TStores & { is_mock: boolean } => { setTogglePlatformType: jest.fn(), toggleAccountTransferModal: jest.fn(), selectAccountType: jest.fn(), + available_dxtrade_accounts: [], toggleIsTourOpen: jest.fn(), is_demo_low_risk: false, is_mt5_notification_modal_visible: false, setMT5NotificationModal: jest.fn(), - available_dxtrade_accounts: [], available_derivez_accounts: [], has_any_real_account: false, startTrade: jest.fn(), getExistingAccounts: jest.fn(), - getAccount: jest.fn(), toggleAccountTypeModalVisibility: jest.fn(), can_get_more_cfd_mt5_accounts: false, showTopUpModal: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 20687872a115..9a1d9597a6b1 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -166,6 +166,15 @@ type TTradingPlatformAvailableAccount = { sub_account_type: string; }; +type TAvailableCFDAccounts = { + availability: 'Non-EU' | 'EU' | 'All'; + description: string; + icon: 'Derived' | 'Financial' | 'DerivX' | 'SwapFree'; + market_type: 'synthetic' | 'financial' | 'all' | 'gaming'; + name: string; + platform: 'mt5' | 'dxtrade'; +}; + type TAuthenticationStatus = { document_status: string; identity_status: string }; type TMenuItem = { @@ -603,11 +612,12 @@ type TTradersHubStore = { platform_demo_balance: TBalance; cfd_real_balance: TBalance; selectAccountType: (account_type: string) => void; + available_cfd_accounts: TAvailableCFDAccounts[]; + available_dxtrade_accounts: TAvailableCFDAccounts[]; toggleIsTourOpen: (is_tour_open: boolean) => void; is_demo_low_risk: boolean; is_mt5_notification_modal_visible: boolean; setMT5NotificationModal: (value: boolean) => void; - available_dxtrade_accounts: DetailsOfEachMT5Loginid[]; available_derivez_accounts: DetailsOfEachMT5Loginid[]; has_any_real_account: boolean; startTrade: () => void;