diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index 9ee277dc1c72..54bbf0ff9211 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -1,23 +1,51 @@ import React from 'react'; import { Text, StaticUrl } from '@deriv/components'; -import { Localize } from '@deriv/translations'; +import { localize, Localize } from '@deriv/translations'; import ListingContainer from 'Components/containers/listing-container'; import './cfds-listing.scss'; import { useStores } from 'Stores/index'; import { observer } from 'mobx-react-lite'; import AddOptionsAccount from 'Components/add-options-account'; -import { isMobile } from '@deriv/shared'; +import { isMobile, formatMoney } from '@deriv/shared'; +import TradingAppCard from 'Components/containers/trading-app-card'; +import { AvailableAccount, TDetailsOfEachMT5Loginid } from 'Types'; const CFDsListing = () => { const { tradinghub } = useStores(); - const is_real = tradinghub.selected_account_type === 'real'; - const has_no_real_account = !tradinghub.has_any_real_account; + const { + available_dxtrade_accounts, + available_mt5_accounts, + selected_region, + has_any_real_account, + startTrade, + is_eu_user, + is_demo, + getExistingAccounts, + getAccount, + } = tradinghub; + const has_no_real_account = !has_any_real_account; + + const accounts_sub_text = is_eu_user ? localize('Account Information') : localize('Compare accounts'); + + const getShortCode = (account: TDetailsOfEachMT5Loginid) => { + return account.landing_company_short && + account.landing_company_short !== 'svg' && + account.landing_company_short !== 'bvi' + ? account.landing_company_short?.charAt(0).toUpperCase() + account.landing_company_short?.slice(1) + : account.landing_company_short?.toUpperCase(); + }; return ( - + , + ]} + /> ) } @@ -32,13 +60,98 @@ const CFDsListing = () => { } > - {is_real && has_no_real_account && ( + {!is_demo && has_no_real_account && (
)} -
CFD Listing
+
+ + {localize('Deriv MT5')} + +
+ {available_mt5_accounts?.map((account: AvailableAccount) => { + const existing_accounts = getExistingAccounts(account.platform, account.market_type); + const has_existing_accounts = existing_accounts.length > 0; + return has_existing_accounts ? ( + existing_accounts.map((existing_account: TDetailsOfEachMT5Loginid) => ( + { + startTrade(account.platform, existing_account); + }} + /> + )) + ) : ( + { + getAccount(account.market_type, account.platform); + }} + /> + ); + })} +
+
+
+ {available_dxtrade_accounts?.length > 0 && ( +
+ + {localize('Other CFDs')} + +
+ )} + {available_dxtrade_accounts?.map((account: AvailableAccount) => { + const existing_accounts = getExistingAccounts(account.platform, account.market_type); + const has_existing_accounts = existing_accounts.length > 0; + return has_existing_accounts ? ( + existing_accounts.map((existing_account: TDetailsOfEachMT5Loginid) => ( + + )) + ) : ( + { + getAccount(account.market_type, account.platform); + }} + key={`trading_app_card_${account.name}`} + type='get' + availability={selected_region} + /> + ); + })}
); }; diff --git a/packages/appstore/src/components/containers/listing-container.scss b/packages/appstore/src/components/containers/listing-container.scss index afa6a4e4d731..6252b4a6b117 100644 --- a/packages/appstore/src/components/containers/listing-container.scss +++ b/packages/appstore/src/components/containers/listing-container.scss @@ -46,18 +46,17 @@ } } -.full-row { - width: 100%; +.cfd-full-row { display: grid; - grid-template-columns: repeat(3, 1fr); + width: 100%; grid-column: 1 / span 3; - gap: 1.6rem 4.8rem; } -.cfd-full-row { - display: grid; +.divider { width: 100%; - grid-column: 1 / span 3; + height: 1px; + background-color: var(--general-hover); + border: none; } .options { diff --git a/packages/appstore/src/components/containers/trading-app-card-actions.tsx b/packages/appstore/src/components/containers/trading-app-card-actions.tsx index 92dc986746eb..9bf1305d6e42 100644 --- a/packages/appstore/src/components/containers/trading-app-card-actions.tsx +++ b/packages/appstore/src/components/containers/trading-app-card-actions.tsx @@ -1,23 +1,30 @@ import { Button } from '@deriv/components'; import { localize } from '@deriv/translations'; +import TradeButton from 'Components/trade-button/trade-button'; +import TransferTradeButtonGroup from 'Components/transfer-trade-button-group'; import React from 'react'; -import { Link } from 'react-router-dom'; export type Actions = { - type: 'get' | 'none' | 'trade'; + type: 'get' | 'none' | 'trade' | 'dxtrade' | 'transfer_trade' | 'dxtrade_transfer_trade'; link_to?: string; + is_disabled?: boolean; + onAction?: () => void; }; -const TradingAppCardActions = ({ type, link_to }: Actions) => { +const TradingAppCardActions = ({ type, link_to, is_disabled, onAction }: Actions) => { switch (type) { case 'get': - return ; - case 'trade': return ( - - - + ); + case 'trade': + return ; + case 'dxtrade': + return ; + case 'transfer_trade': + return ; case 'none': default: return null; diff --git a/packages/appstore/src/components/containers/trading-app-card.scss b/packages/appstore/src/components/containers/trading-app-card.scss index 43a620cf2116..e0ddd987b01c 100644 --- a/packages/appstore/src/components/containers/trading-app-card.scss +++ b/packages/appstore/src/components/containers/trading-app-card.scss @@ -1,7 +1,6 @@ .trading-app-card { display: inline-flex; align-items: center; - width: 100%; height: 8.8rem; &__details { @@ -10,12 +9,7 @@ flex-direction: column; height: 100%; margin-left: 1.6rem; - - .title { - } - - .description { - } + padding: 0 2rem; } &__actions { diff --git a/packages/appstore/src/components/containers/trading-app-card.tsx b/packages/appstore/src/components/containers/trading-app-card.tsx index 8da59d5dbfd8..c86207cc3763 100644 --- a/packages/appstore/src/components/containers/trading-app-card.tsx +++ b/packages/appstore/src/components/containers/trading-app-card.tsx @@ -4,13 +4,30 @@ import TradigPlatformIconProps from 'Assets/svgs/trading-platform'; import { platform_config, BrandConfig } from 'Constants/platform-config'; import './trading-app-card.scss'; import TradingAppCardActions, { Actions } from './trading-app-card-actions'; +import { AvailableAccount, TDetailsOfEachMT5Loginid } from 'Types'; +import { isMobile } from '@deriv/shared'; -const TradingAppCard = ({ name, icon, type }: Actions & BrandConfig) => { - const { app_desc, link_to } = platform_config.find(config => config.name === name) || platform_config[0]; +const TradingAppCard = ({ + name, + icon, + type, + description, + is_disabled, + onAction, + sub_title, +}: Actions & BrandConfig & AvailableAccount & TDetailsOfEachMT5Loginid) => { + const { app_desc, link_to } = platform_config.find(config => config.name === name) || { + app_desc: description, + link_to: '', + }; + const icon_size = isMobile() ? 48 : 64; return (
- +
+ + {sub_title} + {name} @@ -19,7 +36,7 @@ const TradingAppCard = ({ name, icon, type }: Actions & BrandConfig) => {
- +
); diff --git a/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss b/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss index 4372422a6346..6535e5764374 100644 --- a/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss +++ b/packages/appstore/src/components/main-title-bar/account-type-dropdown.scss @@ -1,23 +1,41 @@ .account-type-dropdown { - padding: 2px 8px 2px 2px; - border-radius: 4px; + &--parent { + padding: 0; + border: none; + } font-size: 1.4rem; font-weight: 700; line-height: 2rem; text-transform: capitalize; - border: 1px solid; + border: 2px solid; - &:focus { + &:focus, + &:hover { outline: none; } - &--real { - border-color: var(--status-info); + &--real, + &--real:focus, + &--real:hover { + border: 2px solid var(--status-info); color: var(--status-info); } - &--demo { - border-color: var(--status-success); + &--demo, + &--demo:focus, + &--demo:hover { + border: 2px solid var(--status-success); color: var(--status-success); } } +.dc-dropdown { + &-container { + min-width: 8rem; + width: 8rem; + } + &__display { + &-text { + color: currentColor; + } + } +} diff --git a/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx b/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx index 21e5b31f16df..509083e5bdec 100644 --- a/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx +++ b/packages/appstore/src/components/main-title-bar/account-type-dropdown.tsx @@ -1,28 +1,26 @@ -import { account_types, AccountType } from 'Constants/platform-config'; +import { account_types } from 'Constants/platform-config'; import { observer } from 'mobx-react-lite'; import { useStores } from 'Stores'; import classNames from 'classnames'; import React from 'react'; import './account-type-dropdown.scss'; +import { Dropdown } from '@deriv/components'; const AccountTypeDropdown = () => { const { tradinghub } = useStores(); return ( - +
+ ) => tradinghub.selectAccountType(e.target.value)} + /> +
); }; diff --git a/packages/appstore/src/components/trade-button/index.ts b/packages/appstore/src/components/trade-button/index.ts new file mode 100644 index 000000000000..e03e6f5b0a57 --- /dev/null +++ b/packages/appstore/src/components/trade-button/index.ts @@ -0,0 +1,4 @@ +import TradeButton from './trade-button'; +import './trade-button.scss'; + +export default TradeButton; diff --git a/packages/appstore/src/components/trade-button/trade-button.scss b/packages/appstore/src/components/trade-button/trade-button.scss new file mode 100644 index 000000000000..688c9d127661 --- /dev/null +++ b/packages/appstore/src/components/trade-button/trade-button.scss @@ -0,0 +1,6 @@ +.trade-button { + width: 100%; + margin: 0.5rem 0; + padding: 0; + display: flex; +} diff --git a/packages/appstore/src/components/trade-button/trade-button.tsx b/packages/appstore/src/components/trade-button/trade-button.tsx new file mode 100644 index 000000000000..8c27b2cb6820 --- /dev/null +++ b/packages/appstore/src/components/trade-button/trade-button.tsx @@ -0,0 +1,44 @@ +import { Button } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { Actions } from 'Components/containers/trading-app-card-actions'; +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useStores } from 'Stores'; + +const TradeButton = ({ link_to, onAction }: Pick) => { + const { tradinghub, modules } = useStores(); + if (link_to) { + return ( + + + + ); + } else if (onAction) { + return ( + + ); + } + const { is_demo } = tradinghub; + const { dxtrade_tokens } = modules.cfd; + const REAL_DXTRADE_URL = 'https://dx.deriv.com'; + const DEMO_DXTRADE_URL = 'https://dx-demo.deriv.com'; + return ( + + + + ); +}; + +export default TradeButton; diff --git a/packages/appstore/src/components/transfer-trade-button-group/index.ts b/packages/appstore/src/components/transfer-trade-button-group/index.ts new file mode 100644 index 000000000000..a53f4cd41a6c --- /dev/null +++ b/packages/appstore/src/components/transfer-trade-button-group/index.ts @@ -0,0 +1,4 @@ +import TransferTradeButtonGroup from './transfer-trade-button-group'; +import './transfer-trade-button-group.scss'; + +export default TransferTradeButtonGroup; diff --git a/packages/appstore/src/components/transfer-trade-button-group/transfer-trade-button-group.scss b/packages/appstore/src/components/transfer-trade-button-group/transfer-trade-button-group.scss new file mode 100644 index 000000000000..fc520ec5ec6e --- /dev/null +++ b/packages/appstore/src/components/transfer-trade-button-group/transfer-trade-button-group.scss @@ -0,0 +1,5 @@ +.transfer-trade-button-group { + display: flex; + flex-direction: column; + justify-content: space-between; +} diff --git a/packages/appstore/src/components/transfer-trade-button-group/transfer-trade-button-group.tsx b/packages/appstore/src/components/transfer-trade-button-group/transfer-trade-button-group.tsx new file mode 100644 index 000000000000..004747573a84 --- /dev/null +++ b/packages/appstore/src/components/transfer-trade-button-group/transfer-trade-button-group.tsx @@ -0,0 +1,15 @@ +import { Button } from '@deriv/components'; +import { localize } from '@deriv/translations'; +import { Actions } from 'Components/containers/trading-app-card-actions'; +import TradeButton from 'Components/trade-button'; +import React from 'react'; + +const TransferTradeButtonGroup = ({ link_to, onAction }: Pick) => { + return ( +
+ + +
+ ); +}; +export default TransferTradeButtonGroup; diff --git a/packages/appstore/src/constants/platform-config.ts b/packages/appstore/src/constants/platform-config.ts index c1d1b5d0fddb..88a5fc810747 100644 --- a/packages/appstore/src/constants/platform-config.ts +++ b/packages/appstore/src/constants/platform-config.ts @@ -2,9 +2,12 @@ import { getPlatformSettingsAppstore, routes, getStaticUrl } from '@deriv/shared import { localize } from '@deriv/translations'; import { PlatformIcons } from 'Assets/svgs/trading-platform'; -export type AccountType = 'real' | 'demo'; +export type AccountType = { text: 'Real' | 'Demo'; value: 'real' | 'demo' }; export type RegionAvailability = 'Non-EU' | 'EU' | 'All'; -export const account_types: AccountType[] = ['real', 'demo']; +export const account_types: AccountType[] = [ + { text: 'Demo', value: 'demo' }, + { text: 'Real', value: 'real' }, +]; export const region_availability: RegionAvailability[] = ['Non-EU', 'EU']; export type BrandConfig = { diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts index 79e424609ae5..5a252b0270ad 100644 --- a/packages/appstore/src/types/common.types.ts +++ b/packages/appstore/src/types/common.types.ts @@ -1,4 +1,6 @@ import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; +import { PlatformIcons } from 'Assets/svgs/trading-platform'; +import { RegionAvailability } from 'Constants/platform-config'; export type ArrayElement = ArrayType extends readonly (infer ElementType)[] ? ElementType @@ -78,3 +80,15 @@ export type TStaticAccountProps = { platform: TPlatform; type: TMarketType; }; + +export interface AvailableAccount { + name: string; + sub_title?: string; + description?: string; + is_visible?: boolean; + is_disabled?: boolean; + platform?: string; + market_type?: 'all' | 'financial' | 'synthetic'; + icon: keyof typeof PlatformIcons; + availability: RegionAvailability; +} diff --git a/packages/core/src/Stores/traders-hub-store.js b/packages/core/src/Stores/traders-hub-store.js index 6009fbaeaeb1..4e8ad076eaca 100644 --- a/packages/core/src/Stores/traders-hub-store.js +++ b/packages/core/src/Stores/traders-hub-store.js @@ -1,9 +1,18 @@ import { action, makeObservable, observable, reaction, computed } from 'mobx'; -import { getAppstorePlatforms } from '@deriv/shared'; +import { + getAppstorePlatforms, + CFD_PLATFORMS, + isLandingCompanyEnabled, + available_traders_hub_cfd_accounts, +} from '@deriv/shared'; import BaseStore from './base-store'; +import { localize } from '@deriv/translations'; export default class TradersHubStore extends BaseStore { available_platforms = []; + available_cfd_accounts = []; + available_mt5_accounts = []; + available_dxtrade_accounts = []; selected_region; selected_account_type = 'demo'; is_regulators_compare_modal_visible = false; @@ -16,6 +25,9 @@ export default class TradersHubStore extends BaseStore { makeObservable(this, { available_platforms: observable, + available_cfd_accounts: observable, + available_dxtrade_accounts: observable, + available_mt5_accounts: observable, is_regulators_compare_modal_visible: observable, selected_account_type: observable, selected_platform_type: observable, @@ -29,12 +41,21 @@ export default class TradersHubStore extends BaseStore { handleTabItemClick: action.bound, toggleRegulatorsCompareModal: action.bound, has_any_real_account: computed, + is_demo: computed, + getAvailableDxtradeAccounts: action.bound, + getAvailableCFDAccounts: action.bound, + getExistingAccounts: action.bound, + getAccount: action.bound, + startTrade: action.bound, + openDemoCFDAccount: action.bound, + openRealAccount: action.bound, }); reaction( () => [this.selected_account_type, this.selected_region], () => { this.getAvailablePlatforms(); + this.getAvailableCFDAccounts(); } ); @@ -75,7 +96,121 @@ export default class TradersHubStore extends BaseStore { setTogglePlatformType(platform_type) { this.selected_platform_type = platform_type; } + getAvailableCFDAccounts() { + const account_desc = this.is_eu_user + ? localize( + 'Trade CFDs on forex, stocks, stock indices, synthetic indices, cryptocurrencies, and commodities with leverage.' + ) + : localize('Trade CFDs on Deriv MT5 with forex, stocks & indices, commodities, and cryptocurrencies.'); + const all_available_accounts = [ + ...available_traders_hub_cfd_accounts, + { + name: this.is_eu_user ? localize('CFDs') : localize('Financial'), + description: account_desc, + platform: CFD_PLATFORMS.MT5, + market_type: 'financial', + icon: this.is_eu_user ? 'CFDs' : 'Financial', + availability: 'All', + }, + ]; + this.available_cfd_accounts = all_available_accounts.map(account => { + return { + ...account, + description: account.description, + is_visible: ['synthetic', 'all'].some(market_type => account.market_type === market_type) + ? this.isDerivedVisible(account.platform) + : this.isFinancialVisible(account.platform_name), + // is_disabled: this.has_cfd_account_error(account.platform), + }; + }); + this.getAvailableDxtradeAccounts(); + this.getAvailableMt5Accounts(); + } + getAvailableMt5Accounts() { + if (this.is_eu_user) { + this.available_mt5_accounts = this.available_cfd_accounts.filter(account => + ['EU', 'All'].some(region => region === account.availability) + ); + return; + } + this.available_mt5_accounts = this.available_cfd_accounts.filter( + account => account.platform === CFD_PLATFORMS.MT5 + ); + } + getAvailableDxtradeAccounts() { + if (this.is_eu_user) { + this.available_dxtrade_accounts = this.available_cfd_accounts.filter( + account => + ['EU', 'All'].some(region => region === account.availability) && + account.platform === CFD_PLATFORMS.DXTRADE + ); + return; + } + this.available_dxtrade_accounts = this.available_cfd_accounts.filter( + account => account.platform === CFD_PLATFORMS.DXTRADE + ); + } + hasAccount(cfd_platform, landing_company_short) { + const current_list_keys = Object.keys(this.root_store.modules.cfd.current_list); + return current_list_keys.some(key => + landing_company_short + ? key.startsWith(`${cfd_platform}.${this.selected_account_type}`) + : key.startsWith(`${cfd_platform}.${this.selected_account_type}.${landing_company_short}`) + ); + } + isDerivedVisible(platform) { + const { is_logged_in, is_eu_country, is_eu, landing_companies } = this.root_store.client; + // Hiding card for logged out EU users + + if ((!is_logged_in && is_eu_country) || (is_eu && this.hasAccount(platform, 'synthetic'))) return false; + + return isLandingCompanyEnabled({ landing_companies, platform, type: 'gaming' }) || !is_logged_in; + } + isFinancialVisible(platform) { + const { client } = this.root_store; + return ( + !client.is_logged_in || + isLandingCompanyEnabled({ + landing_companies: client.landing_companies, + platform, + type: 'financial', + }) + ); + } + getExistingAccounts(platform, market_type) { + const { current_list } = this.root_store.modules.cfd; + const current_list_keys = Object.keys(current_list); + const existing_accounts = current_list_keys + .filter(key => { + if (platform === CFD_PLATFORMS.MT5) { + return key.startsWith(`${platform}.${this.selected_account_type}.${market_type}`); + } + if (platform === CFD_PLATFORMS.DXTRADE && market_type === 'all') { + return key.startsWith(`${platform}.${this.selected_account_type}.${platform}@${market_type}`); + } + return key.startsWith(`${platform}.${this.selected_account_type}.${market_type}@${market_type}`); + }) + .reduce((_acc, cur) => { + _acc.push(current_list[cur]); + return _acc; + }, []); + return existing_accounts; + } + startTrade(platform, account) { + const { common, modules } = this.root_store; + const { toggleMT5TradeModal, setMT5TradeAccount } = modules.cfd; + const { setAppstorePlatform } = common; + setAppstorePlatform(platform); + toggleMT5TradeModal(); + setMT5TradeAccount(account); + } + get is_demo() { + return this.selected_account_type === 'demo'; + } + get is_eu_user() { + return this.root_store.client.isEuropeCountry() || this.selected_region === 'EU'; + } setActiveIndex(active_index) { this.active_index = active_index; } @@ -88,4 +223,59 @@ export default class TradersHubStore extends BaseStore { this.selected_region = 'EU'; } } + openDemoCFDAccount(account_type, platform) { + const { client, common, modules } = this.root_store; + + const { setAppstorePlatform } = common; + const { + standpoint, + createCFDAccount, + enableCFDPasswordModal, + openAccountNeededModal, + has_maltainvest_account, + } = modules.cfd; + const { is_eu } = client; + setAppstorePlatform(platform); + if (is_eu && !has_maltainvest_account && standpoint?.iom) { + openAccountNeededModal('maltainvest', localize('Deriv Multipliers'), localize('demo CFDs')); + return; + } + createCFDAccount({ + category: 'demo', + type: account_type, + platform, + }); + enableCFDPasswordModal(); + } + openRealAccount(account_type, platform) { + const { client, modules, common } = this.root_store; + const { has_active_real_account } = client; + const { setAccountType, createCFDAccount, enableCFDPasswordModal, toggleJurisdictionModal } = modules.cfd; + const { setAppstorePlatform } = common; + setAppstorePlatform(platform); + if (has_active_real_account && platform === CFD_PLATFORMS.MT5) { + toggleJurisdictionModal(); + setAccountType({ + category: 'real', + type: account_type, + }); + } else { + setAccountType({ + category: 'real', + type: account_type, + }); + createCFDAccount({ + category: 'real', + type: account_type, + }); + enableCFDPasswordModal(); + } + } + getAccount(account_type, platform) { + if (this.is_demo) { + this.openDemoCFDAccount(account_type, platform); + } else { + this.openRealAccount(account_type, platform); + } + } } diff --git a/packages/shared/src/utils/cfd/available-cfd-accounts.ts b/packages/shared/src/utils/cfd/available-cfd-accounts.ts new file mode 100644 index 000000000000..d13ef1cd4ce0 --- /dev/null +++ b/packages/shared/src/utils/cfd/available-cfd-accounts.ts @@ -0,0 +1,37 @@ +import { localize } from '@deriv/translations'; +import { CFD_PLATFORMS } from '../platform'; + +export interface AvailableAccount { + name: string; + description?: string; + is_visible?: boolean; + is_disabled?: boolean; + platform?: string; + market_type?: 'all' | 'financial' | 'synthetic'; + icon: string; + availability: string; + link_to?: string; +} + +export const available_traders_hub_cfd_accounts: AvailableAccount[] = [ + { + name: 'Derived', + description: localize( + 'Trade CFDs on Deriv MT5 with Derived indices that simulate real-world market movements.' + ), + platform: CFD_PLATFORMS.MT5, + market_type: 'synthetic', + icon: 'Derived', + availability: 'Non-EU', + }, + { + name: 'Deriv X', + description: localize( + 'Trade CFDs on Deriv X with Derived indices, forex, stocks & indices, commodities and cryptocurrencies.' + ), + platform: CFD_PLATFORMS.DXTRADE, + market_type: 'all', + icon: 'DerivX', + availability: 'Non-EU', + }, +]; diff --git a/packages/shared/src/utils/cfd/index.ts b/packages/shared/src/utils/cfd/index.ts index a20db86c044a..ac3bde7ba629 100644 --- a/packages/shared/src/utils/cfd/index.ts +++ b/packages/shared/src/utils/cfd/index.ts @@ -1 +1,2 @@ export * from './cfd'; +export * from './available-cfd-accounts';