From 32c17e0be32c90ae9f70a6333adc30134a2a7ded Mon Sep 17 00:00:00 2001 From: shontzu-deriv Date: Thu, 18 Apr 2024 11:33:50 +0800 Subject: [PATCH 1/8] feat: mt5 mobile redirect option (ddeplinking) --- src/cfd/constants/constants.tsx | 20 +++++- src/cfd/modals/TradeModal/TradeModal.tsx | 71 ++++++++++--------- .../TradeScreen/MT5MobileRedirectOption.tsx | 61 ++++++++++++++++ .../TradeScreen/TradeLink/TradeLink.tsx | 14 ++-- src/cfd/screens/TradeScreen/TradeScreen.tsx | 49 +++++++------ .../IconComponent/IconComponent.tsx | 6 ++ src/utils/index.ts | 2 + src/utils/mobileOsDetect.ts | 32 +++++++++ src/utils/userBrowser.ts | 17 +++++ 9 files changed, 210 insertions(+), 62 deletions(-) create mode 100644 src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx create mode 100644 src/utils/mobileOsDetect.ts create mode 100644 src/utils/userBrowser.ts diff --git a/src/cfd/constants/constants.tsx b/src/cfd/constants/constants.tsx index 08529fa..0477cf4 100644 --- a/src/cfd/constants/constants.tsx +++ b/src/cfd/constants/constants.tsx @@ -10,7 +10,8 @@ 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'; type TAppContent = { description: string; @@ -250,3 +251,20 @@ 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 = ({ details }: { details: THooks.MT5AccountsList }) => { + if (mobileOsDetect() === 'iOS') { + return details?.white_label_links?.ios; + } else if (mobileOsDetect() === 'huawei') { + return 'https://appgallery.huawei.com/#/app/C102015329'; + } + return details?.white_label_links?.android; +}; 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..353ce3e --- /dev/null +++ b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx @@ -0,0 +1,61 @@ +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'; + +const MT5MobileRedirectOption = ({ details }: { details: THooks.MT5AccountsList }) => { + const getMobileUrl = () => { + window.location.replace(getDeeplinkUrl({ details })); + + const timeout = setTimeout(() => { + const url = 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. + +
+ ); +}; + +export default MT5MobileRedirectOption; 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..0b1982d 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,25 +140,30 @@ export const TradeScreen = ({ account }: TradeScreenProps) => {
-
- {platform === mt5Platform && ( - - - {isDesktop && ( - - - - - - )} - - )} +
+ {platform === mt5Platform && + (isDesktop ? ( + + + + + + + + ) : ( + + ))} + {platform === dxtradePlatform && ( - + )} {platform === ctraderPlatform && isDesktop && ( 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 e5d5ee2..78a4e61 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,4 @@ export * from './tailwind'; export * from './password'; +export * from './mobileOsDetect'; +export * from './userBrowser'; diff --git a/src/utils/mobileOsDetect.ts b/src/utils/mobileOsDetect.ts new file mode 100644 index 0000000..d5d4864 --- /dev/null +++ b/src/utils/mobileOsDetect.ts @@ -0,0 +1,32 @@ +declare global { + interface Window { + MSStream: unknown; + } +} + +export const mobileOsDetect = () => { + const userAgent = navigator.userAgent || ''; + // huawei devices regex from: https://gist.github.com/megaacheyounes/e1c7eec5c790e577db602381b8c50bfa + const huaweiDevicesRegex = + /\bK\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-|WKG-|MLD-|RTE-|NAM-|NEN-|BAL-|JAD-|JLN-|YAL/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)) { + // Huawei UA is the same as android so we have to detect by the model + if (huaweiDevicesRegex.test(userAgent) || /huawei/i.test(userAgent)) { + return 'huawei'; + } + return 'Android'; + } + + // iOS detection from: http://stackoverflow.com/a/9039885/177710 + if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { + return 'iOS'; + } + + return 'unknown'; +}; 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'; From 19ecbd376cdf75429b740ee58f731f584bf7a52c Mon Sep 17 00:00:00 2001 From: shontzu-deriv Date: Fri, 19 Apr 2024 13:20:15 +0800 Subject: [PATCH 2/8] refactor: export named instead of default component --- src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx index 353ce3e..b7803c6 100644 --- a/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx +++ b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx @@ -5,7 +5,7 @@ import { IconComponent } from '@/components'; import { THooks } from '@/types'; import { isSafariBrowser } from '@/utils'; -const MT5MobileRedirectOption = ({ details }: { details: THooks.MT5AccountsList }) => { +export const MT5MobileRedirectOption = ({ details }: { details: THooks.MT5AccountsList }) => { const getMobileUrl = () => { window.location.replace(getDeeplinkUrl({ details })); @@ -57,5 +57,3 @@ const MT5MobileRedirectOption = ({ details }: { details: THooks.MT5AccountsList
); }; - -export default MT5MobileRedirectOption; From ae217a63dbb068e69b8bad45c9009accb36dcfaf Mon Sep 17 00:00:00 2001 From: shontzu-deriv Date: Fri, 19 Apr 2024 13:32:50 +0800 Subject: [PATCH 3/8] chore: komen --- src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx | 2 +- src/cfd/screens/TradeScreen/TradeScreen.tsx | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx index b7803c6..ad7856c 100644 --- a/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx +++ b/src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx @@ -38,7 +38,7 @@ export const MT5MobileRedirectOption = ({ details }: { details: THooks.MT5Accoun