Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTORING] shontzu/TRAH-3015/ui-for-seamless-MT5-mobile-login-integration #111

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/cfd/constants/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -250,3 +251,20 @@ export const AppToIconMapper: Record<string, ComponentType<SVGAttributes<SVGElem
huawei: InstallationHuaweiIcon,
ios: InstallationAppleIcon,
};

export const getWebtraderUrl = ({ details }: { details: THooks.MT5AccountsList }) => {
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') {
shontzu-deriv marked this conversation as resolved.
Show resolved Hide resolved
return details?.white_label_links?.ios;
} else if (mobileOsDetect() === 'huawei') {
return 'https://appgallery.huawei.com/#/app/C102015329';
shontzu-deriv marked this conversation as resolved.
Show resolved Hide resolved
}
return details?.white_label_links?.android;
};
71 changes: 37 additions & 34 deletions src/cfd/modals/TradeModal/TradeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,41 +34,44 @@ export const TradeModal = () => {
<Modal.Body className='w-auto h-auto'>
<TradeScreen account={account} />
</Modal.Body>
<Modal.Footer>
<div className='pt-0 min-h-[190px] flex justify-center items-center flex-col h-fit w-full gap-16'>
<Text align='center' size='xs' weight='bold'>
Download {PlatformDetails[platform]?.title} on your phone to trade with the{' '}
{PlatformDetails[platform].title} account
</Text>
<div className='flex gap-16'>
<div className='flex flex-col justify-center gap-8'>
{appOrder.map(app => {
const AppsLinkMapper = LinksMapper[platform][app as keyof TAppLinks];
if (AppsLinkMapper) {
const AppIcon = AppToIconMapper[app];
const appLink = AppsLinkMapper;
return (
<AppIcon
className='w-[137px] h-[40px] cursor-pointer'
key={app}
onClick={() => window.open(appLink)}
/>
);
}
return null;
})}
</div>
{isDesktop && (
<div className='border-1 border-solid border-system-light-hover-background rounded-xs flex flex-col justify-center items-center w-[150px] gap-[5px] p-8'>
<QRCode size={80} value={PlatformDetails[platform].link} />
<Text align='center' size='xs'>
Scan the QR code to download {PlatformDetails[platform].title}
</Text>
{platform !== PlatformDetails.mt5.platform ||
(isDesktop && (
<Modal.Footer>
<div className='pt-0 min-h-[190px] flex justify-center items-center flex-col h-fit w-full gap-16'>
<Text align='center' size='xs' weight='bold'>
Download {PlatformDetails[platform]?.title} on your phone to trade with the{' '}
{PlatformDetails[platform].title} account
</Text>
<div className='flex gap-16'>
<div className='flex flex-col justify-center gap-8'>
{appOrder.map(app => {
const AppsLinkMapper = LinksMapper[platform][app as keyof TAppLinks];
if (AppsLinkMapper) {
const AppIcon = AppToIconMapper[app];
const appLink = AppsLinkMapper;
return (
<AppIcon
className='w-[137px] h-[40px] cursor-pointer'
key={app}
onClick={() => window.open(appLink)}
/>
);
}
return null;
})}
</div>
{isDesktop && (
<div className='border-1 border-solid border-system-light-hover-background rounded-xs flex flex-col justify-center items-center w-[150px] gap-[5px] p-8'>
<QRCode size={80} value={PlatformDetails[platform].link} />
<Text align='center' size='xs'>
Scan the QR code to download {PlatformDetails[platform].title}
</Text>
</div>
)}
</div>
)}
</div>
</div>
</Modal.Footer>
</div>
</Modal.Footer>
))}
</Modal>
);
};
59 changes: 59 additions & 0 deletions src/cfd/screens/TradeScreen/MT5MobileRedirectOption.tsx
Original file line number Diff line number Diff line change
@@ -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(() => {
const url = getMobileAppInstallerUrl({ details });
if (url) window.location.replace(url);
}, 1500);

if (!isSafariBrowser() || (isSafariBrowser() && /Version\/17/.test(navigator.userAgent))) {
window.onblur = () => {
clearTimeout(timeout);
};
}
};

return (
<div className='flex flex-col gap-4 align-center'>
<a
className='flex justify-between w-full gap-2 p-8 bg-gray-200 rounded-md text-decoration-none'
href={getWebtraderUrl({ details })}
rel='noopener noreferrer'
target='_blank'
>
<div className='flex justify-between w-full gap-5 align-center'>
<IconComponent height={16} icon='Laptop' width={16} />
<Text align='left' className='flex-1' size='xs' weight='bold'>
MetaTrader5 web terminal
</Text>
<IconComponent height={16} icon='ChevronRight' width={16} />
</div>
</a>
<button
className='flex justify-between w-full gap-2 p-8 bg-blue-600 rounded-md align-center text-decoration-none'
onClick={getMobileUrl}
>
<div className='flex justify-between w-full gap-5 align-center'>
<IconComponent height={16} icon='Mobile' width={16} />
<Text align='left' className='flex-1 text-white' size='xs' weight='bold'>
Trade with MT5 mobile app
</Text>
<IconComponent height={16} icon='ChevronRight' width={16} />
</div>
</button>

<Text as='p' size='2xs'>
Note: Don&apos;t have the MT5 app? Tap the <span>Trade with MT5 mobile app</span> button to download.
Once you have installed the app, return to this screen and hit the same button to log in.
</Text>
</div>
);
};
14 changes: 8 additions & 6 deletions src/cfd/screens/TradeScreen/TradeLink/TradeLink.tsx
Original file line number Diff line number Diff line change
@@ -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'];
};
Expand Down
43 changes: 23 additions & 20 deletions src/cfd/screens/TradeScreen/TradeScreen.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -129,7 +130,8 @@ export const TradeScreen = ({ account }: TradeScreenProps) => {
)}
</div>
<div className='flex items-center gap-8'>
<ImportantIcon
<IconComponent
icon='ImportantIcon'
height={platform === mt5Platform ? 16 : 20}
width={platform === mt5Platform ? 16 : 20}
/>
Expand All @@ -138,23 +140,24 @@ export const TradeScreen = ({ account }: TradeScreenProps) => {
</Text>
</div>
</div>
<div className='w-full'>
{platform === mt5Platform && (
<Fragment>
<TradeLink
app={DesktopLinks.MT5_WEB}
platform={mt5Platform}
webtraderUrl={(details as THooks.MT5AccountsList)?.webtrader_url}
/>
{isDesktop && (
<Fragment>
<TradeLink app={DesktopLinks.MT5_WINDOWS} platform={mt5Platform} />
<TradeLink app={DesktopLinks.MT5_MACOS} platform={mt5Platform} />
<TradeLink app={DesktopLinks.MT5_LINUX} platform={mt5Platform} />
</Fragment>
)}
</Fragment>
)}
<div className='w-full p-24'>
{platform === mt5Platform &&
(isDesktop ? (
<Fragment>
<TradeLink
app={DesktopLinks.MT5_WEB}
platform={mt5Platform}
webtraderUrl={getWebtraderUrl({ details } as { details: THooks.MT5AccountsList })}
/>

<TradeLink app={DesktopLinks.MT5_WINDOWS} platform={mt5Platform} />
<TradeLink app={DesktopLinks.MT5_MACOS} platform={mt5Platform} />
<TradeLink app={DesktopLinks.MT5_LINUX} platform={mt5Platform} />
</Fragment>
) : (
<MT5MobileRedirectOption details={details as THooks.MT5AccountsList} />
))}

{platform === dxtradePlatform && (
<TradeLink app={DesktopLinks.DXTRADE_WEB} platform={dxtradePlatform} />
)}
Expand Down
6 changes: 6 additions & 0 deletions src/components/IconComponent/IconComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {
DerivProductDerivGoBrandLightLogoHorizontalIcon,
DerivProductDerivTraderBrandLightLogoHorizontalIcon,
DerivProductDerivXBrandLightLogoIcon,
LabelPairedChevronRightXlFillIcon,
LabelPairedLaptopLgFillIcon,
LabelPairedMobileNotchLgBoldIcon,
PartnersProductBinaryBotBrandLightLogoHorizontalIcon,
PartnersProductDerivCtraderBrandLightLogoHorizontalIcon,
PartnersProductSmarttraderBrandLightLogoIcon,
Expand Down Expand Up @@ -63,6 +66,9 @@ export const Icons: Record<string, ElementType> = {
UST: CurrencyUsdtIcon,
virtual: CurrencyDemoIcon,
ImportantIcon,
ArrowRight: LabelPairedChevronRightXlFillIcon,
Laptop: LabelPairedLaptopLgFillIcon,
Mobile: LabelPairedMobileNotchLgBoldIcon,
};

export const IconComponent = ({ className, height = 48, icon, onClick, width = 48 }: IconProps<keyof typeof Icons>) => {
Expand Down
2 changes: 2 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './tailwind';
export * from './password';
export * from './mobileOsDetect';
export * from './userBrowser';
export * from './performance-metrics-methods';
32 changes: 32 additions & 0 deletions src/utils/mobileOsDetect.ts
Original file line number Diff line number Diff line change
@@ -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';
};
17 changes: 17 additions & 0 deletions src/utils/userBrowser.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading