diff --git a/src/cfd/constants/constants.tsx b/src/cfd/constants/constants.tsx index cfd4973..70b6663 100644 --- a/src/cfd/constants/constants.tsx +++ b/src/cfd/constants/constants.tsx @@ -10,7 +10,10 @@ import MacOSIcon from '@/assets/svgs/ic-macos-logo.svg?react'; import MT5Icon from '@/assets/svgs/ic-mt5.svg?react'; import WindowsIcon from '@/assets/svgs/ic-windows-logo.svg?react'; import { IconComponent } from '@/components'; -import { TJurisdiction, TMarketTypes, TPlatforms } from '@/types'; +import { THooks, TJurisdiction, TMarketTypes, TPlatforms } from '@/types'; +import { mobileOsDetect } from '@/utils'; + +import { ctrader_links, dxtrade_links, white_label_links } from './urlConfig'; type TAppContent = { description: string; @@ -163,29 +166,29 @@ export const companyNamesAndUrls: TcompanyNamesAndUrls = { export const LinksMapper: Record = { ctrader: { - android: 'https://play.google.com/store/apps/details?id=com.deriv.ct', - ios: 'https://apps.apple.com/us/app/deriv-ctrader/id6466996509', + android: ctrader_links.android, + ios: ctrader_links.ios, }, 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', + android: dxtrade_links.android, + huawei: dxtrade_links.huawei, + ios: dxtrade_links.ios, }, 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', + android: white_label_links.android, + huawei: white_label_links.huawei, + ios: white_label_links.ios, }, }; export const PlatformUrls: TPlatformUrls = { ctrader: { - live: 'https://ct.deriv.com', - staging: 'https://ct-uat.deriv.com', + live: ctrader_links.live, + staging: ctrader_links.staging, }, dxtrade: { - demo: 'https://dx-demo.deriv.com', - live: 'https://dx.deriv.com', + demo: dxtrade_links.demo, + live: dxtrade_links.live, }, }; @@ -209,25 +212,25 @@ export const AppToContentMapper: TAppToContentMapper = { }, ctrader_windows: { icon: , - link: 'https://getctrader.com/deriv/ctrader-deriv-setup.exe', + link: ctrader_links.windows, text: 'Download', title: 'cTrader Windows App', }, ctrader_mac: { icon: , - link: 'https://getctradermac.com/deriv/ctrader-deriv-setup.dmg', + link: ctrader_links.mac, text: 'Download', title: 'cTrader MacOS App', }, mt5_linux: { icon: , - link: 'https://www.metatrader5.com/en/terminal/help/start_advanced/install_linux', + link: white_label_links.linux, text: 'Learn more', title: 'MetaTrader 5 Linux app', }, mt5_macos: { icon: , - link: 'https://download.mql5.com/cdn/web/metaquotes.software.corp/mt5/MetaTrader5.dmg', + link: white_label_links.macos, text: 'Download', title: 'MetaTrader 5 MacOS app', }, @@ -239,7 +242,7 @@ export const AppToContentMapper: TAppToContentMapper = { }, mt5_windows: { icon: , - link: 'https://download.mql5.com/cdn/web/deriv.com.limited/mt5/deriv5setup.exe', + link: white_label_links.windows, text: 'Download', title: 'MetaTrader 5 Windows app', }, @@ -250,3 +253,21 @@ export const AppToIconMapper: Record { + return `${details?.white_label_links?.webtrader_url}?login=${details?.display_login}&server=${details?.server_info?.environment}`; +}; + +export const getDeeplinkUrl = ({ details }: { details: THooks.MT5AccountsList }) => { + return `metatrader5://account?login=${details?.display_login}&server=${details?.server_info?.environment}`; +}; + +export const getMobileAppInstallerUrl = async ({ details }: { details: THooks.MT5AccountsList }) => { + const os = await mobileOsDetect(); + if (os === 'iOS') { + return details?.white_label_links?.ios; + } else if (os === 'huawei') { + return white_label_links.huawei; + } + return details?.white_label_links?.android; +}; diff --git a/src/cfd/constants/urlConfig.ts b/src/cfd/constants/urlConfig.ts new file mode 100644 index 0000000..25e057d --- /dev/null +++ b/src/cfd/constants/urlConfig.ts @@ -0,0 +1,40 @@ +/** + * This file contains the URLs for different platforms and environments. + * urlConfig will be sent as a proposal to the backend. + */ + +/** + * URLs for the cTrader platform. + */ +export const ctrader_links = { + android: 'https://play.google.com/store/apps/details?id=com.deriv.ct', + ios: 'https://apps.apple.com/us/app/deriv-ctrader/id6466996509', + live: 'https://ct.deriv.com', + staging: 'https://ct-uat.deriv.com', + windows: 'https://getctrader.com/deriv/ctrader-deriv-setup.exe', + mac: 'https://getctradermac.com/deriv/ctrader-deriv-setup.dmg', +}; + +/** + * URLs for the dxTrade platform. + */ +export const dxtrade_links = { + 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', + demo: 'https://dx-demo.deriv.com', + live: 'https://dx.deriv.com', +}; + +/** + * URLs for the mt5 platform. + */ +export const white_label_links = { + 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', + linux: 'https://www.metatrader5.com/en/terminal/help/start_advanced/install_linux', + macos: 'https://download.mql5.com/cdn/web/metaquotes.software.corp/mt5/MetaTrader5.dmg', + windows: 'https://download.mql5.com/cdn/web/deriv.com.limited/mt5/deriv5setup.exe', + webtrader_url: 'https://mt5-real01-web-svg.deriv.com/terminal', +}; diff --git a/src/cfd/modals/TradeModal/TradeModal.tsx b/src/cfd/modals/TradeModal/TradeModal.tsx index 2e8be13..5d2eb7d 100644 --- a/src/cfd/modals/TradeModal/TradeModal.tsx +++ b/src/cfd/modals/TradeModal/TradeModal.tsx @@ -34,41 +34,44 @@ export const TradeModal = () => { - -
- - 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} - + {platform !== PlatformDetails.mt5.platform || + (isDesktop && ( + +
+ + 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} + +
+ )}
- )} -
-
- +
+ + ))} ); }; diff --git a/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx new file mode 100644 index 0000000..2fdc19b --- /dev/null +++ b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx @@ -0,0 +1,59 @@ +import { getDeeplinkUrl, getMobileAppInstallerUrl, getWebtraderUrl } from '@cfd/constants'; +import { Text } from '@deriv-com/ui'; + +import { IconComponent } from '@/components'; +import { THooks } from '@/types'; +import { isSafariBrowser } from '@/utils'; + +export const MT5MobileRedirectOption = ({ details }: { details: THooks.MT5AccountsList }) => { + const getMobileUrl = () => { + window.location.replace(getDeeplinkUrl({ details })); + + const timeout = setTimeout(async () => { + const url = await getMobileAppInstallerUrl({ details }); + if (url) window.location.replace(url); + }, 1500); + + if (!isSafariBrowser() || (isSafariBrowser() && /Version\/17/.test(navigator.userAgent))) { + window.onblur = () => { + clearTimeout(timeout); + }; + } + }; + + return ( +
+ +
+ + + MetaTrader5 web terminal + + +
+
+ + + + Note: Don't have the MT5 app? Tap the Trade with MT5 mobile app button to download. + Once you have installed the app, return to this screen and hit the same button to log in. + +
+ ); +}; diff --git a/src/cfd/screens/TradeScreen/TradeLink/TradeLink.tsx b/src/cfd/screens/TradeScreen/TradeLink/TradeLink.tsx index a4dc0e9..fe6ac10 100644 --- a/src/cfd/screens/TradeScreen/TradeLink/TradeLink.tsx +++ b/src/cfd/screens/TradeScreen/TradeLink/TradeLink.tsx @@ -1,20 +1,22 @@ import { Fragment } from 'react'; +import { Button, Text } from '@deriv-com/ui'; + +import { getPlatformFromUrl } from '@/helpers'; +import { useActiveDerivTradingAccount, useCtraderServiceToken } from '@/hooks'; +import { THooks, TPlatforms } from '@/types'; + import { AppToContentMapper, DesktopLinks, PlatformDetails, PlatformToLabelIconMapper, PlatformUrls, -} from '@cfd/constants'; -import { Button, Text } from '@deriv-com/ui'; - -import { getPlatformFromUrl } from '@/helpers'; -import { useActiveDerivTradingAccount, useCtraderServiceToken } from '@/hooks'; -import { THooks, TPlatforms } from '@/types'; +} from '../../../constants'; type TTradeLinkProps = { app: keyof typeof AppToContentMapper; + isDemo?: boolean; platform?: TPlatforms.All; webtraderUrl?: THooks.MT5AccountsList['webtrader_url']; }; diff --git a/src/cfd/screens/TradeScreen/TradeScreen.tsx b/src/cfd/screens/TradeScreen/TradeScreen.tsx index 935cf93..e5851a4 100644 --- a/src/cfd/screens/TradeScreen/TradeScreen.tsx +++ b/src/cfd/screens/TradeScreen/TradeScreen.tsx @@ -1,9 +1,9 @@ import { Fragment, useMemo } from 'react'; -import { DesktopLinks, MarketType, MarketTypeDetails, PlatformDetails } from '@cfd/constants'; +import { DesktopLinks, getWebtraderUrl, MarketType, MarketTypeDetails, PlatformDetails } from '@cfd/constants'; import { Text, useDevice } from '@deriv-com/ui'; -import ImportantIcon from '@/assets/svgs/ic-important.svg?react'; +import { IconComponent } from '@/components'; import { useActiveDerivTradingAccount, useCtraderAccountsList, @@ -13,6 +13,7 @@ import { import { useCFDContext } from '@/providers'; import { THooks, TPlatforms } from '@/types'; +import { MT5MobileRedirectOption } from './MT5MobileRedirectOption'; import { TradeDetailsItem } from './TradeDetailsItem'; import { TradeLink } from './TradeLink'; @@ -129,7 +130,8 @@ export const TradeScreen = ({ account }: TradeScreenProps) => { )}
- @@ -138,23 +140,24 @@ export const TradeScreen = ({ account }: TradeScreenProps) => {
-
- {platform === mt5Platform && ( - - - {isDesktop && ( - - - - - - )} - - )} +
+ {platform === mt5Platform && + (isDesktop ? ( + + + + + + + + ) : ( + + ))} + {platform === dxtradePlatform && ( )} diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index 219a544..6102c6d 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -21,6 +21,9 @@ import { DerivProductDerivGoBrandLightLogoHorizontalIcon, DerivProductDerivTraderBrandLightLogoHorizontalIcon, DerivProductDerivXBrandLightLogoIcon, + LabelPairedChevronRightXlFillIcon, + LabelPairedLaptopLgFillIcon, + LabelPairedMobileNotchLgBoldIcon, PartnersProductBinaryBotBrandLightLogoHorizontalIcon, PartnersProductDerivCtraderBrandLightLogoHorizontalIcon, PartnersProductSmarttraderBrandLightLogoIcon, @@ -63,6 +66,9 @@ export const Icons: Record = { UST: CurrencyUsdtIcon, virtual: CurrencyDemoIcon, ImportantIcon, + ArrowRight: LabelPairedChevronRightXlFillIcon, + Laptop: LabelPairedLaptopLgFillIcon, + Mobile: LabelPairedMobileNotchLgBoldIcon, }; export const IconComponent = ({ className, height = 48, icon, onClick, width = 48 }: IconProps) => { diff --git a/src/utils/index.ts b/src/utils/index.ts index 0e580ef..73af294 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,5 @@ export * from './tailwind'; export * from './password'; +export * from './mobileOsDetect'; +export * from './userBrowser'; export * from './performance-metrics-methods'; diff --git a/src/utils/mobileOsDetect.ts b/src/utils/mobileOsDetect.ts new file mode 100644 index 0000000..4a510e5 --- /dev/null +++ b/src/utils/mobileOsDetect.ts @@ -0,0 +1,54 @@ +declare global { + interface Window { + opera?: string; + MSStream?: { + readonly type: string; + msClose: () => void; + msDetachStream: () => void; + }; + } + interface Navigator { + userAgentData?: NavigatorUAData; + } +} + +interface NavigatorUAData { + brands: Array<{ brand: string; version: string }>; + mobile: boolean; + getHighEntropyValues(hints: string[]): Promise; +} + +type HighEntropyValues = { + platform?: string; + platformVersion?: string; + model?: string; + uaFullVersion?: string; +}; + +export const mobileOsDetect = async () => { + const userAgent = navigator.userAgent || navigator.vendor || window.opera || ''; + const huaweiDevicesRegex = + /\b(ALP-|AMN-|ANA-|ANE-|ANG-|AQM-|ARS-|ART-|ATU-|BAC-|BLA-|BRQ-|CAG-|CAM-|CAN-|CAZ-|CDL-|CDY-|CLT-|CRO-|CUN-|DIG-|DRA-|DUA-|DUB-|DVC-|ELE-|ELS-|EML-|EVA-|EVR-|FIG-|FLA-|FRL-|GLK-|HMA-|HW-|HWI-|INE-|JAT-|JEF-|JER-|JKM-|JNY-|JSC-|LDN-|LIO-|LON-|LUA-|LYA-|LYO-|MAR-|MED-|MHA-|MLA-|MRD-|MYA-|NCE-|NEO-|NOH-|NOP-|OCE-|PAR-|PIC-|POT-|PPA-|PRA-|RNE-|SEA-|SLA-|SNE-|SPN-|STK-|TAH-|TAS-|TET-|TRT-|VCE-|VIE-|VKY-|VNS-|VOG-|VTR-|WAS-|WKG-|WLZ-|JAD-|MLD-|RTE-|NAM-|NEN-|BAL-|JLN-|YAL|MGA-|FGD-|XYAO-|BON-|ALN-|ALT-|BRA-|DBY2-|STG-|MAO-|LEM-|GOA-|FOA-|MNA-|LNA-)\b/i; + + // Windows Phone must come first because its UA also contains "Android" + if (/windows phone/i.test(userAgent)) { + return 'Windows Phone'; + } + + if (/android/i.test(userAgent)) { + // Check if navigator.userAgentData is available for modern browsers + if (navigator?.userAgentData) { + const ua = await navigator.userAgentData.getHighEntropyValues(['model']); + if (huaweiDevicesRegex.test(ua?.model || '')) { + return 'huawei'; + } + } else if (huaweiDevicesRegex.test(userAgent) || /huawei/i.test(userAgent)) { + return 'huawei'; + } + return 'Android'; + } + + if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { + return 'iOS'; + } +}; diff --git a/src/utils/userBrowser.ts b/src/utils/userBrowser.ts new file mode 100644 index 0000000..dcba743 --- /dev/null +++ b/src/utils/userBrowser.ts @@ -0,0 +1,17 @@ +const getUserBrowser = () => { + // We can't rely only on navigator.userAgent.index, the verification order is also important + if ((navigator.userAgent.indexOf('Opera') || navigator.userAgent.indexOf('OPR')) !== -1) { + return 'Opera'; + } else if (navigator.userAgent.indexOf('Edg') !== -1) { + return 'Edge'; + } else if (navigator.userAgent.indexOf('Chrome') !== -1) { + return 'Chrome'; + } else if (navigator.userAgent.indexOf('Safari') !== -1) { + return 'Safari'; + } else if (navigator.userAgent.indexOf('Firefox') !== -1) { + return 'Firefox'; + } + return 'unknown'; +}; + +export const isSafariBrowser = () => getUserBrowser() === 'Safari';