diff --git a/packages/library/src/providers/CFDProvider/CFDProvider.tsx b/packages/library/src/providers/CFDProvider/CFDProvider.tsx index e7b900d3e040..3aec1960390b 100644 --- a/packages/library/src/providers/CFDProvider/CFDProvider.tsx +++ b/packages/library/src/providers/CFDProvider/CFDProvider.tsx @@ -3,6 +3,7 @@ import { TMarketTypes, TPlatforms, THooks } from '../../types'; type TCFDState = { // Add your CFD states here + accountId?: string; marketType?: TMarketTypes.All; platform?: TPlatforms.All; selectedJurisdiction?: THooks.AvailableMT5Accounts['shortcode']; diff --git a/packages/tradershub/src/App.tsx b/packages/tradershub/src/App.tsx index 30970ae2ddc7..33b38f1b1f4e 100644 --- a/packages/tradershub/src/App.tsx +++ b/packages/tradershub/src/App.tsx @@ -9,13 +9,13 @@ import './index.scss'; const App = () => ( - - + + - - + + ); diff --git a/packages/tradershub/src/components/Clipboard/Clipboard.tsx b/packages/tradershub/src/components/Clipboard/Clipboard.tsx index 7b00439068d3..01ad99307e14 100644 --- a/packages/tradershub/src/components/Clipboard/Clipboard.tsx +++ b/packages/tradershub/src/components/Clipboard/Clipboard.tsx @@ -50,7 +50,7 @@ const Clipboard = ({ textCopy, tooltip }: TClipboardProps) => { isVisible={isHovered && !isMobile} message={isCopied ? 'Copied!' : 'Copy'} > - diff --git a/packages/tradershub/src/components/Modal/Modal.tsx b/packages/tradershub/src/components/Modal/Modal.tsx index 67bbcdcbae15..afbe806c29c1 100644 --- a/packages/tradershub/src/components/Modal/Modal.tsx +++ b/packages/tradershub/src/components/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, ReactNode } from 'react'; +import React, { PropsWithChildren, ReactElement } from 'react'; import { qtMerge } from '@deriv/quill-design'; import ModalContent from './ModalContent'; import ModalFooter from './ModalFooter'; @@ -33,10 +33,9 @@ type TModal = { * @property {ReactNode} children - Children nodes * @property {string} [className] - Optional CSS class name */ -export type TModalComponents = { - children?: ReactNode; +export type TModalComponents = PropsWithChildren<{ className?: string; -}; +}>; /** * Modal component diff --git a/packages/tradershub/src/features/cfd/constants.tsx b/packages/tradershub/src/features/cfd/constants.tsx index d9ca0254a1de..ca30049f765d 100644 --- a/packages/tradershub/src/features/cfd/constants.tsx +++ b/packages/tradershub/src/features/cfd/constants.tsx @@ -6,6 +6,9 @@ import FinancialMT5Icon from '../../public/images/cfd/mt5-financial.svg'; import SwapFreeMT5Icon from '../../public/images/cfd/mt5-swap-free.svg'; import CTraderLabelIcon from '../../public/images/ctrader-label.svg'; import DerivXLabelIcon from '../../public/images/derivx-label.svg'; +import InstallationAppleIcon from '../../public/images/ic-installation-apple.svg'; +import InstallationGoogleIcon from '../../public/images/ic-installation-google.svg'; +import InstallationHuaweiIcon from '../../public/images/ic-installation-huawei.svg'; import LinuxIcon from '../../public/images/ic-linux-logo.svg'; import MacOSIcon from '../../public/images/ic-macos-logo.svg'; import MT5Icon from '../../public/images/ic-mt5.svg'; @@ -158,3 +161,32 @@ export const PlatformUrls: TPlatformUrls = { live: 'https://dx.deriv.com', }, }; + +export type TAppLinks = { + android: string; + huawei?: string; + ios: string; +}; + +export const LinksMapper: Record = { + ctrader: { + android: 'https://play.google.com/store/apps/details?id=com.deriv.ct', + ios: 'https://apps.apple.com/cy/app/ctrader/id767428811', + }, + dxtrade: { + android: 'https://play.google.com/store/apps/details?id=com.deriv.dx', + huawei: 'https://appgallery.huawei.com/app/C104633219', + ios: 'https://apps.apple.com/us/app/deriv-x/id1563337503', + }, + mt5: { + android: 'https://download.mql5.com/cdn/mobile/mt5/android?server=Deriv-Demo,Deriv-Server,Deriv-Server-02', + huawei: 'https://appgallery.huawei.com/#/app/C102015329', + ios: 'https://download.mql5.com/cdn/mobile/mt5/ios?server=Deriv-Demo,Deriv-Server,Deriv-Server-02', + }, +}; + +export const AppToIconMapper: Record>> = { + android: InstallationGoogleIcon, + huawei: InstallationHuaweiIcon, + ios: InstallationAppleIcon, +}; diff --git a/packages/tradershub/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx b/packages/tradershub/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx index 49b7d2a678c4..c744e2f171f3 100644 --- a/packages/tradershub/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx +++ b/packages/tradershub/src/features/cfd/flows/CTrader/AddedCTraderAccountsList/AddedCTraderAccountsList.tsx @@ -1,14 +1,18 @@ import React, { Fragment } from 'react'; import { useActiveTradingAccount, useCtraderAccountsList } from '@deriv/api'; +import { Provider } from '@deriv/library'; import { Button, Text } from '@deriv/quill-design'; import { TradingAccountCard } from '../../../../../components'; import { getStaticUrl } from '../../../../../helpers/urls'; import CTrader from '../../../../../public/images/cfd/ctrader.svg'; -import { PlatformDetails } from '../../../constants'; +import { CFDPlatforms, PlatformDetails } from '../../../constants'; +import { TradeModal } from '../../../modals/TradeModal'; const AddedCTraderAccountsList = () => { const { data: cTraderAccounts } = useCtraderAccountsList(); const { data: activeTrading } = useActiveTradingAccount(); + const { show } = Provider.useModal(); + const account = cTraderAccounts?.find(account => account.is_virtual === activeTrading?.is_virtual); const leading = () => (
{ > Transfer - +
); diff --git a/packages/tradershub/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx b/packages/tradershub/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx index 032dfd9ff33e..fb772af8c370 100644 --- a/packages/tradershub/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx +++ b/packages/tradershub/src/features/cfd/flows/MT5/AddedMT5AccountsList/AddedMT5AccountsList.tsx @@ -1,14 +1,17 @@ import React, { useMemo } from 'react'; import { useHistory } from 'react-router-dom'; -import { useAuthorize, useJurisdictionStatus } from '@deriv/api'; +import { useActiveTradingAccount, useJurisdictionStatus } from '@deriv/api'; +import { Provider } from '@deriv/library'; import { Button, Text } from '@deriv/quill-design'; import { TradingAccountCard } from '../../../../../components/TradingAccountCard'; import { THooks } from '../../../../../types'; -import { MarketTypeDetails } from '../../../constants'; +import { CFDPlatforms, MarketTypeDetails } from '../../../constants'; +import { TradeModal } from '../../../modals/TradeModal'; import { MT5AccountIcon } from '../MT5AccountIcon'; const AddedMT5AccountsList = ({ account }: { account: THooks.MT5AccountsList }) => { - const { data: activeWallet } = useAuthorize(); + const { data: activeAccount } = useActiveTradingAccount(); + const { show } = Provider.useModal(); const history = useHistory(); const { getVerificationStatus } = useJurisdictionStatus(); const jurisdictionStatus = useMemo( @@ -27,7 +30,7 @@ const AddedMT5AccountsList = ({ account }: { account: THooks.MT5AccountsList }) colorStyle='black' disabled={jurisdictionStatus.is_failed || jurisdictionStatus.is_pending} onClick={() => { - history.push('/wallets/cashier/transfer'); + history.push('/cashier/transfer'); }} variant='secondary' > @@ -36,7 +39,15 @@ const AddedMT5AccountsList = ({ account }: { account: THooks.MT5AccountsList }) @@ -46,7 +57,7 @@ const AddedMT5AccountsList = ({ account }: { account: THooks.MT5AccountsList })
{title} - {!activeWallet?.is_virtual && ( + {!activeAccount?.is_virtual && (
{account.landing_company_short?.toUpperCase()} diff --git a/packages/tradershub/src/features/cfd/flows/OtherCFDs/Dxtrade/AddedDxtradeAccountsList/AddedDxtradeAccountsList.tsx b/packages/tradershub/src/features/cfd/flows/OtherCFDs/Dxtrade/AddedDxtradeAccountsList/AddedDxtradeAccountsList.tsx index b09d4631a254..c399c222e72d 100644 --- a/packages/tradershub/src/features/cfd/flows/OtherCFDs/Dxtrade/AddedDxtradeAccountsList/AddedDxtradeAccountsList.tsx +++ b/packages/tradershub/src/features/cfd/flows/OtherCFDs/Dxtrade/AddedDxtradeAccountsList/AddedDxtradeAccountsList.tsx @@ -1,14 +1,18 @@ import React, { Fragment } from 'react'; import { useActiveTradingAccount, useDxtradeAccountsList } from '@deriv/api'; +import { Provider } from '@deriv/library'; import { Button, Text } from '@deriv/quill-design'; import { TradingAccountCard } from '../../../../../../components'; import { getStaticUrl } from '../../../../../../helpers/urls'; import DerivX from '../../../../../../public/images/cfd/derivx.svg'; -import { PlatformDetails } from '../../../../constants'; +import { CFDPlatforms, PlatformDetails } from '../../../../constants'; +import { TradeModal } from '../../../../modals/TradeModal'; const AddedDxtradeAccountsList = () => { const { data: dxTradeAccounts } = useDxtradeAccountsList(); const { data: activeTrading } = useActiveTradingAccount(); + const { show } = Provider.useModal(); + const account = dxTradeAccounts?.find(account => account.is_virtual === activeTrading?.is_virtual); const leading = () => (
{ > Transfer - +
); diff --git a/packages/tradershub/src/features/cfd/modals/TradeModal/TradeModal.tsx b/packages/tradershub/src/features/cfd/modals/TradeModal/TradeModal.tsx new file mode 100644 index 000000000000..359fa82fc20d --- /dev/null +++ b/packages/tradershub/src/features/cfd/modals/TradeModal/TradeModal.tsx @@ -0,0 +1,74 @@ +import React, { useEffect } from 'react'; +import QRCode from 'qrcode.react'; +import { Provider } from '@deriv/library'; +import { Text, useBreakpoint } from '@deriv/quill-design'; +import { Modal } from '../../../../components/Modal'; +import { THooks, TMarketTypes, TPlatforms } from '../../../../types'; +import { AppToIconMapper, CFDPlatforms, LinksMapper, PlatformDetails, TAppLinks } from '../../constants'; +import { MT5TradeScreen } from '../../screens/MT5TradeScreen'; + +type TTradeModalProps = { + account?: THooks.CtraderAccountsList | THooks.DxtradeAccountsList | THooks.MT5AccountsList; + marketType?: TMarketTypes.All; + platform: TPlatforms.All; +}; + +const TradeModal = ({ account, marketType, platform }: TTradeModalProps) => { + const { isDesktop } = useBreakpoint(); + const { setCfdState } = Provider.useCFDContext(); + + useEffect(() => { + setCfdState('marketType', marketType); + setCfdState('platform', platform); + if (platform === CFDPlatforms.MT5) setCfdState('accountId', (account as THooks.MT5AccountsList)?.loginid); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const appOrder = ['ios', 'android', 'huawei']; + + return ( + + + + + + +
+ + Download {PlatformDetails[platform].title} on your phone to trade with the{' '} + {PlatformDetails[platform].title} account + +
+
+ {appOrder.map(app => { + const AppsLinkMapper = LinksMapper[platform][app as keyof TAppLinks]; + if (AppsLinkMapper) { + const AppIcon = AppToIconMapper[app]; + const appLink = AppsLinkMapper; + return ( + window.open(appLink)} + /> + ); + } + return null; + })} +
+ {isDesktop && ( +
+ + + Scan the QR code to download {PlatformDetails[platform].title} + +
+ )} +
+
+
+
+ ); +}; + +export default TradeModal; diff --git a/packages/tradershub/src/features/cfd/modals/TradeModal/index.ts b/packages/tradershub/src/features/cfd/modals/TradeModal/index.ts new file mode 100644 index 000000000000..e5a5d6344e09 --- /dev/null +++ b/packages/tradershub/src/features/cfd/modals/TradeModal/index.ts @@ -0,0 +1 @@ +export { default as TradeModal } from './TradeModal'; diff --git a/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeDetailsItem/MT5TradeDetailsItem.tsx b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeDetailsItem/MT5TradeDetailsItem.tsx index 14b91ee92aa7..46441d2b776a 100644 --- a/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeDetailsItem/MT5TradeDetailsItem.tsx +++ b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeDetailsItem/MT5TradeDetailsItem.tsx @@ -1,14 +1,14 @@ import React, { FC, useRef } from 'react'; import { useHover } from 'usehooks-ts'; -import { qtMerge, Text, useBreakpoint } from '@deriv/quill-design'; +import { Button, qtMerge, Text, useBreakpoint } from '@deriv/quill-design'; import { Clipboard, Tooltip } from '../../../../../components'; import EditIcon from '../../../../../public/images/ic-edit.svg'; type TMT5TradeDetailsItemProps = { className?: string; - label: string; + label?: string; value: string; - variant: 'clipboard' | 'password'; + variant?: 'clipboard' | 'info' | 'password'; }; const MT5TradeDetailsItem: FC = ({ className, label, value, variant = 'clipboard' }) => { @@ -25,15 +25,17 @@ const MT5TradeDetailsItem: FC = ({ className, label, {label} -
- +
+ {value} {variant === 'clipboard' && } {variant === 'password' && (
- +
)} diff --git a/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeLink/MT5TradeLink.tsx b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeLink/MT5TradeLink.tsx index df8a1c3d373a..1cde526e9c37 100644 --- a/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeLink/MT5TradeLink.tsx +++ b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeLink/MT5TradeLink.tsx @@ -7,6 +7,7 @@ import { AppToContentMapper, PlatformDetails, PlatformToLabelIconMapper, Platfor type TMT5TradeLinkProps = { app?: keyof typeof AppToContentMapper; + isDemo?: boolean; platform?: TPlatforms.All; webtraderUrl?: THooks.MT5AccountsList['webtrader_url']; }; @@ -40,7 +41,7 @@ const MT5TradeLink: FC = ({ app = 'linux', platform, webtrad }; return ( -
+
{(platform === mt5Platform || app === ctraderPlatform) && ( @@ -54,9 +55,11 @@ const MT5TradeLink: FC = ({ app = 'linux', platform, webtrad
{(platform === mt5Platform || app === ctraderPlatform) && ( @@ -64,14 +67,16 @@ const MT5TradeLink: FC = ({ app = 'linux', platform, webtrad {platform !== mt5Platform && app !== ctraderPlatform && ( )}
diff --git a/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx new file mode 100644 index 000000000000..ea5abb031a3a --- /dev/null +++ b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/MT5TradeScreen.tsx @@ -0,0 +1,155 @@ +import React, { Fragment, useMemo } from 'react'; +import { useActiveTradingAccount, useCtraderAccountsList, useDxtradeAccountsList } from '@deriv/api'; +import { Provider } from '@deriv/library'; +import { Text, useBreakpoint } from '@deriv/quill-design'; +import ImportantIcon from '../../../../public/images/ic-important.svg'; +import { THooks, TPlatforms } from '../../../../types'; +import { AppToContentMapper, MarketTypeDetails, PlatformDetails } from '../../constants'; +import { MT5TradeDetailsItem } from './MT5TradeDetailsItem'; +import { MT5TradeLink } from './MT5TradeLink'; + +type MT5TradeScreenProps = { + account?: THooks.CtraderAccountsList | THooks.DxtradeAccountsList | THooks.MT5AccountsList; +}; + +const serviceMaintenanceMessages: Record = { + ctrader: + 'Server maintenance occurs every first Saturday of the month from 7 to 10 GMT time. You may experience service disruption during this time.', + dxtrade: + 'Server maintenance starts at 06:00 GMT every Sunday and may last up to 2 hours. You may experience service disruption during this time.', + mt5: 'Server maintenance starts at 01:00 GMT every Sunday, and this process may take up to 2 hours to complete. Service may be disrupted during this time.', +}; + +const MT5TradeScreen = ({ account }: MT5TradeScreenProps) => { + const { isMobile } = useBreakpoint(); + const { getCFDState } = Provider.useCFDContext(); + const { data: dxtradeAccountsList } = useDxtradeAccountsList(); + const { data: ctraderAccountsList } = useCtraderAccountsList(); + const { data: activeAccount } = useActiveTradingAccount(); + + const mt5Platform = PlatformDetails.mt5.platform; + const dxtradePlatform = PlatformDetails.dxtrade.platform; + const ctraderPlatform = PlatformDetails.ctrader.platform; + + const marketType = getCFDState('marketType'); + const platform = getCFDState('platform') ?? mt5Platform; + + const platformToAccountsListMapper = useMemo( + () => ({ + ctrader: ctraderAccountsList?.find(account => account.is_virtual === activeAccount?.is_virtual), + dxtrade: dxtradeAccountsList?.find(account => account.is_virtual === activeAccount?.is_virtual), + mt5: account, + }), + [ctraderAccountsList, dxtradeAccountsList, account, activeAccount?.is_virtual] + ); + + const details = platformToAccountsListMapper[platform as TPlatforms.All]; + + const loginId = useMemo(() => { + if (platform === mt5Platform) { + return (details as THooks.MT5AccountsList)?.display_login; + } else if (platform === dxtradePlatform) { + return (details as THooks.DxtradeAccountsList)?.account_id; + } + return (details as THooks.CtraderAccountsList)?.login; + }, [details, dxtradePlatform, mt5Platform, platform]); + + return ( +
+
+
+
+
+ {platform === mt5Platform + ? MarketTypeDetails[(marketType ?? 'all') as keyof typeof MarketTypeDetails].icon + : PlatformDetails[platform as keyof typeof PlatformDetails].icon} +
+
+
+ + {platform === mt5Platform + ? MarketTypeDetails[(marketType ?? 'all') as keyof typeof MarketTypeDetails] + .title + : PlatformDetails[platform as keyof typeof PlatformDetails].title}{' '} + {!activeAccount?.is_virtual && details?.landing_company_short?.toUpperCase()} + +
+ + {loginId} + +
+
+
+ {details?.display_balance} +
+
+
+ {platform === mt5Platform && ( + + + + + + + )} + {platform === dxtradePlatform && ( + + + + + )} + {platform === ctraderPlatform && ( + + )} +
+
+
+ +
+ {serviceMaintenanceMessages[(platform || mt5Platform) as TPlatforms.All]} +
+
+
+ {platform === mt5Platform && ( + + + {!isMobile && ( + + + + + + )} + + )} + {platform === dxtradePlatform && ( + + )} + {platform === ctraderPlatform && ( + + + + + )} +
+
+ ); +}; + +export default MT5TradeScreen; diff --git a/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/index.ts b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/index.ts new file mode 100644 index 000000000000..a52bacfa2a77 --- /dev/null +++ b/packages/tradershub/src/features/cfd/screens/MT5TradeScreen/index.ts @@ -0,0 +1 @@ +export { default as MT5TradeScreen } from './MT5TradeScreen'; diff --git a/packages/tradershub/src/public/images/ic-important.svg b/packages/tradershub/src/public/images/ic-important.svg new file mode 100644 index 000000000000..e9e8dc2e226d --- /dev/null +++ b/packages/tradershub/src/public/images/ic-important.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/tradershub/src/public/images/ic-installation-apple.svg b/packages/tradershub/src/public/images/ic-installation-apple.svg new file mode 100644 index 000000000000..56373da55ee3 --- /dev/null +++ b/packages/tradershub/src/public/images/ic-installation-apple.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/tradershub/src/public/images/ic-installation-google.svg b/packages/tradershub/src/public/images/ic-installation-google.svg new file mode 100644 index 000000000000..5830df93b50b --- /dev/null +++ b/packages/tradershub/src/public/images/ic-installation-google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/tradershub/src/public/images/ic-installation-huawei.svg b/packages/tradershub/src/public/images/ic-installation-huawei.svg new file mode 100644 index 000000000000..f58d1d2b6303 --- /dev/null +++ b/packages/tradershub/src/public/images/ic-installation-huawei.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file