diff --git a/package-lock.json b/package-lock.json index a423dc0..b01df6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dotenv": "^16.4.5", + "embla-carousel": "^8.0.2", + "embla-carousel-react": "^8.0.2", "formik": "^2.4.5", "js-cookie": "^3.0.5", "qrcode.react": "^3.1.0", @@ -6570,6 +6572,31 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz", "integrity": "sha512-t/MXMzFKQC3UfMDpw7V5wdB/UAB8dWx4hEsy+fpPYJWW3gqh3u5T1uXp6vR+H6dGCPBxkRo+YBcapBLvbGQHRw==" }, + "node_modules/embla-carousel": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.2.tgz", + "integrity": "sha512-bogsDO8xosuh/l3PxIvA5AMl3+BnRVAse9sDW/60amzj4MbGS5re4WH5eVEXiuH8G1/3G7QUAX2QNr3Yx8z5rA==" + }, + "node_modules/embla-carousel-react": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.2.tgz", + "integrity": "sha512-RHe1GKLulOW8EDN+cJgbFbVVfRXcaLT2/89dyVw3ONGgVpZjD19wB87I1LUZ1aCzOSrTccx0PFSQanK4OOfGPA==", + "dependencies": { + "embla-carousel": "8.0.2", + "embla-carousel-reactive-utils": "8.0.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.2.tgz", + "integrity": "sha512-nLZqDkQdO0hvOP49/dUwjkkepMnUXgIzhyRuDjwGqswpB4Ibnc5M+w7rSQQAM+uMj0cPaXnYOTlv8XD7I/zVNw==", + "peerDependencies": { + "embla-carousel": "8.0.2" + } + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", diff --git a/package.json b/package.json index 7d33cba..5a60a60 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dotenv": "^16.4.5", + "embla-carousel": "^8.0.2", + "embla-carousel-react": "^8.0.2", "formik": "^2.4.5", "js-cookie": "^3.0.5", "qrcode.react": "^3.1.0", diff --git a/src/assets/cfd/tradingInstruments/ic-instrument-derived-fx.svg b/src/assets/cfd/tradingInstruments/ic-instrument-derived-fx.svg index 5c2014c..75ab103 100644 --- a/src/assets/cfd/tradingInstruments/ic-instrument-derived-fx.svg +++ b/src/assets/cfd/tradingInstruments/ic-instrument-derived-fx.svg @@ -1,7 +1 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/assets/cfd/tradingInstruments/index.tsx b/src/assets/cfd/tradingInstruments/index.tsx index d5bd7f7..109ba86 100644 --- a/src/assets/cfd/tradingInstruments/index.tsx +++ b/src/assets/cfd/tradingInstruments/index.tsx @@ -1,12 +1,12 @@ -import Baskets from './ic-instrument-baskets.svg'; -import Commodities from './ic-instrument-commodities.svg'; -import Cryptocurrencies from './ic-instrument-cryptocurrencies.svg'; -import DerivedFX from './ic-instrument-derived-fx.svg'; -import ETF from './ic-instrument-etf.svg'; -import Forex from './ic-instrument-forex.svg'; -import StockIndices from './ic-instrument-stock-indices.svg'; -import Stocks from './ic-instrument-stocks.svg'; -import Synthetics from './ic-instrument-synthetics.svg'; +import Baskets from './ic-instrument-baskets.svg?react'; +import Commodities from './ic-instrument-commodities.svg?react'; +import Cryptocurrencies from './ic-instrument-cryptocurrencies.svg?react'; +import DerivedFX from './ic-instrument-derived-fx.svg?react'; +import ETF from './ic-instrument-etf.svg?react'; +import Forex from './ic-instrument-forex.svg?react'; +import StockIndices from './ic-instrument-stock-indices.svg?react'; +import Stocks from './ic-instrument-stocks.svg?react'; +import Synthetics from './ic-instrument-synthetics.svg?react'; const InstrumentsIcons = { Baskets, diff --git a/src/cfd/components/CompareAccountsCarousel/CompareAccountsCarousel.tsx b/src/cfd/components/CompareAccountsCarousel/CompareAccountsCarousel.tsx new file mode 100644 index 0000000..2a85412 --- /dev/null +++ b/src/cfd/components/CompareAccountsCarousel/CompareAccountsCarousel.tsx @@ -0,0 +1,45 @@ +import { PropsWithChildren, useCallback, useEffect, useState } from 'react'; +import { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel'; +import useEmblaCarousel from 'embla-carousel-react'; + +import CFDCompareAccountsCarouselButton from './CompareAccountsCarouselButton'; + +const CompareAccountsCarousel = ({ children }: PropsWithChildren) => { + const options: EmblaOptionsType = { + align: 'start', + containScroll: 'trimSnaps', + }; + const [emblaRef, emblaApi] = useEmblaCarousel(options); + const [prevBtnEnabled, setPrevBtnEnabled] = useState(false); + const [nextBtnEnabled, setNextBtnEnabled] = useState(false); + + const scrollPrev = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]); + const scrollNext = useCallback(() => emblaApi?.scrollNext(), [emblaApi]); + + const onSelect = useCallback((emblaApi: EmblaCarouselType) => { + setPrevBtnEnabled(emblaApi.canScrollPrev()); + setNextBtnEnabled(emblaApi.canScrollNext()); + }, []); + + useEffect(() => { + if (!emblaApi) return; + + onSelect(emblaApi); + emblaApi.on('reInit', onSelect); + emblaApi.on('select', onSelect); + }, [emblaApi, onSelect]); + + return ( +
+
+
+ {children} +
+
+ + +
+ ); +}; + +export default CompareAccountsCarousel; diff --git a/src/cfd/components/CompareAccountsCarousel/CompareAccountsCarouselButton.tsx b/src/cfd/components/CompareAccountsCarousel/CompareAccountsCarouselButton.tsx new file mode 100644 index 0000000..d27e74b --- /dev/null +++ b/src/cfd/components/CompareAccountsCarousel/CompareAccountsCarouselButton.tsx @@ -0,0 +1,20 @@ +import { LabelPairedChevronLeftMdRegularIcon, LabelPairedChevronRightMdRegularIcon } from '@deriv/quill-icons'; + +type TPrevNextButtonProps = { + enabled: boolean; + isNext?: boolean; + onClick: () => void; +}; + +const CFDCompareAccountsCarouselButton = ({ enabled, isNext = false, onClick }: TPrevNextButtonProps) => ( + +); +export default CFDCompareAccountsCarouselButton; diff --git a/src/cfd/components/CompareAccountsCarousel/index.ts b/src/cfd/components/CompareAccountsCarousel/index.ts new file mode 100644 index 0000000..1b0190c --- /dev/null +++ b/src/cfd/components/CompareAccountsCarousel/index.ts @@ -0,0 +1 @@ +export { default as CompareAccountsCarousel } from './CompareAccountsCarousel'; diff --git a/src/cfd/components/MT5PlatformsList/MT5PlatformsList.tsx b/src/cfd/components/MT5PlatformsList/MT5PlatformsList.tsx index 1b24fa6..6d81278 100644 --- a/src/cfd/components/MT5PlatformsList/MT5PlatformsList.tsx +++ b/src/cfd/components/MT5PlatformsList/MT5PlatformsList.tsx @@ -37,7 +37,7 @@ export const MT5PlatformsList = () => { return ( ); diff --git a/src/cfd/screens/CFDCompareAccounts/CTraderCompareAccountsCard.tsx b/src/cfd/screens/CFDCompareAccounts/CTraderCompareAccountsCard.tsx new file mode 100644 index 0000000..bb1b55a --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CTraderCompareAccountsCard.tsx @@ -0,0 +1,38 @@ +import { useMemo } from 'react'; + +import { useActiveDerivTradingAccount, useCFDAccountsList, useCFDCompareAccounts, useRegulationFlags } from '@/hooks'; + +import CFDCompareAccountsCard from './CompareAccountsCard'; +import { isCTraderAccountAdded } from './CompareAccountsConfig'; + +const CTraderCompareAccountsCard = () => { + const { data: activeDerivTrading } = useActiveDerivTradingAccount(); + const { regulationFlags } = useRegulationFlags(); + const { isEU } = regulationFlags; + + const { data: compareAccounts, hasCTraderAccountAvailable } = useCFDCompareAccounts(); + + const { is_virtual: isDemo = false } = activeDerivTrading ?? {}; + + const { data: cfdAccounts } = useCFDAccountsList(); + + const { ctraderAccount } = compareAccounts; + + const isCtraderAdded = useMemo( + () => !!cfdAccounts && isCTraderAccountAdded(cfdAccounts.ctrader, !!isDemo), + [cfdAccounts, isDemo] + ); + + if (isEU || !hasCTraderAccountAvailable || !ctraderAccount) return null; + + return ( + + ); +}; + +export default CTraderCompareAccountsCard; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccounts.classnames.ts b/src/cfd/screens/CFDCompareAccounts/CompareAccounts.classnames.ts new file mode 100644 index 0000000..9d9e33b --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccounts.classnames.ts @@ -0,0 +1,17 @@ +import { cva, VariantProps } from 'class-variance-authority'; + +export const CompareAccountsPlatformLabelClass = cva('bg-system-light-platform-background p-[9px] rounded-t-xl', { + variants: { background: { CTrader: 'bg-[#ffeabf]', MT5: 'bg-[#e6f5ff]', DerivX: 'bg-[#e8fdf8]' } }, +}); + +export const CompareAccountsPlatformLabelTextColorClass = cva('text-center', { + variants: { label: { CTrader: 'text-[#ff9c13]', MT5: 'text-[#2C9aff]', DerivX: 'text-[#17eabd]' } }, +}); + +export type TCompareAccountsPlatformLabelClassProps = NonNullable< + VariantProps +>; + +export type TCompareAccountsPlatformLabelTextClassProps = NonNullable< + VariantProps +>; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsButton.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsButton.tsx new file mode 100644 index 0000000..66e9023 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsButton.tsx @@ -0,0 +1,130 @@ +import { useEffect, useMemo } from 'react'; + +import { Category, CFDPlatforms, MarketType } from '@cfd/constants'; +import { Button } from '@deriv-com/ui'; + +import { + useActiveDerivTradingAccount, + useAuthentication, + useCreateOtherCFDAccount, + useMT5AccountsList, + useQueryParams, + useSettings, +} from '@/hooks'; +import { THooks, TPlatforms } from '@/types'; + +import { + getAccountVerificationStatus, + shouldRestrictBviAccountCreation, + shouldRestrictVanuatuAccountCreation, +} from './CompareAccountsConfig'; + +type TCompareAccountButton = { + isAccountAdded: boolean; + platform: TPlatforms.All; + shortCode: THooks.AvailableMT5Accounts['shortcode']; +}; + +/* + * This is a button component for Compare Accounts that is used to add a CFD account. +@params {boolean} isAccountAdded - Whether the account is added or not. +@params {string} platform - The platform of the account. +@params {string} shortCode - The short code of the account. +@params {string} marketType - The market type of the account. //Removed for now as it is needed by Verification flow + */ +const CompareAccountsButton = ({ isAccountAdded, platform, shortCode }: TCompareAccountButton) => { + const { openModal } = useQueryParams(); + + const { data: accountSettings } = useSettings(); + const { data: authenticationInfo } = useAuthentication(); + const { + error: createAccountError, + isSuccess: isAccountCreated, + mutate: createAccount, + } = useCreateOtherCFDAccount(); + const { data: mt5Accounts } = useMT5AccountsList(); + const { data: activeTradingAccount } = useActiveDerivTradingAccount(); + + const { is_virtual: isDemo = false } = activeTradingAccount ?? {}; + + const { + account_opening_reason: accountOpeningReason, + citizen, + place_of_birth: placeOfBirth, + tax_identification_number: taxIdentificationNumber, + tax_residence: taxResidence, + } = accountSettings; + + const hasSubmittedPersonalDetails = !!( + citizen && + placeOfBirth && + taxResidence && + taxIdentificationNumber && + accountOpeningReason + ); + + const restrictBviAccountCreation = useMemo( + () => shouldRestrictBviAccountCreation(mt5Accounts ?? []), + [mt5Accounts] + ); + + const restrictVanuatuAccountCreation = useMemo( + () => shouldRestrictVanuatuAccountCreation(mt5Accounts ?? []), + [mt5Accounts] + ); + + const isAccountStatusVerified = getAccountVerificationStatus( + shortCode, + restrictBviAccountCreation, + restrictVanuatuAccountCreation, + hasSubmittedPersonalDetails, + authenticationInfo, + !!isDemo + ); + + useEffect(() => { + if (isAccountCreated) { + openModal('CTraderSuccessModal'); + } + if (createAccountError) { + // Error Component to be implemented + openModal('DummyComponentModal'); + } + }, [createAccountError, isAccountCreated, isDemo, openModal]); + + const onClickAdd = () => { + if (platform === CFDPlatforms.MT5) { + // Going to remove Placeholder once Verification flow is implemented + if (isAccountStatusVerified) return; + + if (isAccountStatusVerified) { + openModal('MT5PasswordModal'); + } else { + openModal('DummyComponentModal'); + } + } else if (platform === CFDPlatforms.DXTRADE) { + openModal('DxtradePasswordModal'); + } else { + createAccount({ + account_type: isDemo ? Category.DEMO : Category.REAL, + market_type: MarketType.ALL, + platform: CFDPlatforms.CTRADER, + }); + } + }; + return ( +
+ +
+ ); +}; + +export default CompareAccountsButton; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsCard.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsCard.tsx new file mode 100644 index 0000000..343487e --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsCard.tsx @@ -0,0 +1,58 @@ +import { Text } from '@deriv-com/ui'; + +import { useRegulationFlags } from '@/hooks'; +import { THooks, TPlatforms } from '@/types'; + +import { CFDPlatforms } from '../../constants'; + +import CompareAccountsButton from './CompareAccountsButton'; +import CompareAccountsDescription from './CompareAccountsDescription'; +import CompareAccountsPlatformLabel from './CompareAccountsPlatformLabel'; +import CompareAccountsTitleIcon from './CompareAccountsTitleIcon'; +import InstrumentsLabelHighlighted from './InstrumentsLabelHighlighted'; + +type TCompareAccountsCard = { + isAccountAdded: boolean; + marketType: THooks.AvailableMT5Accounts['market_type']; + platform: TPlatforms.All; + shortCode: THooks.AvailableMT5Accounts['shortcode']; +}; + +const CompareAccountsCard = ({ isAccountAdded, marketType, platform, shortCode }: TCompareAccountsCard) => { + const { regulationFlags } = useRegulationFlags(); + const { isEU } = regulationFlags; + + return ( +
+
+ + {platform === CFDPlatforms.CTRADER && ( +
+ + New! + +
+ )} + + + + {isEU && ( +
+ + *Boom 300 and Crash 300 Index + +
+ )} + +
+
+ ); +}; + +export default CompareAccountsCard; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsConfig.ts b/src/cfd/screens/CFDCompareAccounts/CompareAccountsConfig.ts new file mode 100644 index 0000000..6c25c0a --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsConfig.ts @@ -0,0 +1,311 @@ +import InstrumentsIcons from '@/assets/cfd/tradingInstruments'; +import { THooks, TPlatforms } from '@/types'; + +import { CFDPlatforms, MarketType } from '../../constants'; + +import { Jurisdiction, MarketTypeShortcode } from './constants'; + +type THighlightedIconLabel = { + highlighted: boolean; + icon: keyof typeof InstrumentsIcons; + isAsterisk?: boolean; + text: string; +}; + +type TMarketTypes = THooks.AvailableMT5Accounts['market_type']; +type TShortCode = THooks.AvailableMT5Accounts['shortcode']; + +const getHighlightedIconLabel = ( + platform: TPlatforms.All, + isEuRegion: boolean, + marketType: TMarketTypes, + shortCode: TShortCode +): THighlightedIconLabel[] => { + const marketTypeShortCode = marketType?.concat('_', shortCode ?? ''); + + const forexLabel = (() => { + if (isEuRegion) { + return 'Forex'; + } else if (marketTypeShortCode === MarketTypeShortcode.FINANCIAL_LABUAN) { + return 'Forex: standard/exotic'; + } else if ( + (platform === CFDPlatforms.MT5 && marketTypeShortCode === MarketTypeShortcode.ALL_SVG) || + platform === CFDPlatforms.CTRADER + ) { + return 'Forex: major/minor'; + } + return 'Forex: standard/micro'; + })(); + + switch (marketType) { + case MarketType.SYNTHETIC: + return [ + { highlighted: false, icon: 'Forex', text: forexLabel }, + { highlighted: false, icon: 'Stocks', text: 'Stocks' }, + { highlighted: false, icon: 'StockIndices', text: 'Stock indices' }, + { highlighted: false, icon: 'Commodities', text: 'Commodities' }, + { highlighted: false, icon: 'Cryptocurrencies', text: 'Cryptocurrencies' }, + { highlighted: false, icon: 'ETF', text: 'ETFs' }, + { highlighted: true, icon: 'Synthetics', text: 'Synthetic indices' }, + { highlighted: true, icon: 'Baskets', text: 'Basket indices' }, + { highlighted: true, icon: 'DerivedFX', text: 'Derived FX' }, + ]; + case MarketType.FINANCIAL: + switch (shortCode) { + case Jurisdiction.MALTAINVEST: + return [ + { highlighted: true, icon: 'Forex', text: forexLabel }, + { highlighted: true, icon: 'Stocks', text: 'Stocks' }, + { highlighted: true, icon: 'StockIndices', text: 'Stock indices' }, + { highlighted: true, icon: 'Commodities', text: 'Commodities' }, + { highlighted: true, icon: 'Cryptocurrencies', text: 'Cryptocurrencies' }, + { highlighted: true, icon: 'Synthetics', isAsterisk: true, text: 'Synthetic indices' }, + ]; + case Jurisdiction.LABUAN: + return [ + { highlighted: true, icon: 'Forex', text: forexLabel }, + { highlighted: false, icon: 'Stocks', text: 'Stocks' }, + { highlighted: false, icon: 'StockIndices', text: 'Stock indices' }, + { highlighted: false, icon: 'Commodities', text: 'Commodities' }, + { highlighted: true, icon: 'Cryptocurrencies', text: 'Cryptocurrencies' }, + { highlighted: false, icon: 'ETF', text: 'ETFs' }, + { highlighted: false, icon: 'Synthetics', text: 'Synthetic indices' }, + { highlighted: false, icon: 'Baskets', text: 'Basket indices' }, + { highlighted: false, icon: 'DerivedFX', text: 'Derived FX' }, + ]; + default: + return [ + { highlighted: true, icon: 'Forex', text: forexLabel }, + { highlighted: true, icon: 'Stocks', text: 'Stocks' }, + { highlighted: true, icon: 'StockIndices', text: 'Stock indices' }, + { highlighted: true, icon: 'Commodities', text: 'Commodities' }, + { highlighted: true, icon: 'Cryptocurrencies', text: 'Cryptocurrencies' }, + { highlighted: true, icon: 'ETF', text: 'ETFs' }, + { highlighted: false, icon: 'Synthetics', text: 'Synthetic indices' }, + { highlighted: false, icon: 'Baskets', text: 'Basket indices' }, + { highlighted: false, icon: 'DerivedFX', text: 'Derived FX' }, + ]; + } + case MarketType.ALL: + default: + if (platform === CFDPlatforms.MT5) { + return [ + { highlighted: true, icon: 'Forex', text: forexLabel }, + { highlighted: true, icon: 'Stocks', text: 'Stocks' }, + { highlighted: true, icon: 'StockIndices', text: 'Stock indices' }, + { highlighted: true, icon: 'Commodities', text: 'Commodities' }, + { highlighted: true, icon: 'Cryptocurrencies', text: 'Cryptocurrencies' }, + { highlighted: true, icon: 'ETF', text: 'ETFs' }, + { highlighted: true, icon: 'Synthetics', text: 'Synthetics indices' }, + { highlighted: false, icon: 'Baskets', text: 'Basket indices' }, + { highlighted: false, icon: 'DerivedFX', text: 'Derived FX' }, + ]; + } + return [ + { highlighted: true, icon: 'Forex', text: forexLabel }, + { highlighted: true, icon: 'Stocks', text: 'Stocks' }, + { highlighted: true, icon: 'StockIndices', text: 'Stock indices' }, + { highlighted: true, icon: 'Commodities', text: 'Commodities' }, + { highlighted: true, icon: 'Cryptocurrencies', text: 'Cryptocurrencies' }, + { highlighted: true, icon: 'ETF', text: 'ETFs' }, + { highlighted: true, icon: 'Synthetics', text: 'Synthetic indices' }, + { highlighted: true, icon: 'Baskets', text: 'Basket indices' }, + { highlighted: true, icon: 'DerivedFX', text: 'Derived FX' }, + ]; + } +}; + +const getPlatformType = (platform: TPlatforms.All) => { + switch (platform) { + case CFDPlatforms.MT5: + return 'MT5'; + case CFDPlatforms.CTRADER: + return 'CTrader'; + case CFDPlatforms.DXTRADE: + default: + return 'DerivX'; + } +}; + +const cfdConfig = { + counterpartyCompany: 'Deriv (SVG) LLC', + counterpartyCompanyDescription: 'Counterparty company', + jurisdiction: 'St. Vincent & Grenadines', + jurisdictionDescription: 'Jurisdiction', + leverage: '1:1000', + leverageDescription: 'Maximum leverage', + regulator: 'Financial Commission', + regulatorDescription: 'Regulator/External dispute resolution', + regulatorLicense: '', + spread: '0.5 pips', + spreadDescription: 'Spreads from', +}; + +const getJurisdictionDescription = (shortcode?: string) => { + switch (shortcode) { + case MarketTypeShortcode.SYNTHETIC_BVI: + case MarketTypeShortcode.FINANCIAL_BVI: + return { + ...cfdConfig, + counterpartyCompany: 'Deriv (BVI) Ltd', + jurisdiction: 'British Virgin Islands', + regulator: 'British Virgin Islands Financial Services Commission', + regulatorDescription: 'Regulator/External dispute resolution', + regulatorLicense: '(License no. SIBA/L/18/1114)', + }; + case MarketTypeShortcode.SYNTHETIC_VANUATU: + case MarketTypeShortcode.FINANCIAL_VANUATU: + return { + ...cfdConfig, + counterpartyCompany: 'Deriv (V) Ltd', + jurisdiction: 'Vanuatu', + regulator: 'Vanuatu Financial Services Commission', + regulatorDescription: 'Regulator/External dispute resolution', + regulatorLicense: '', + }; + case MarketTypeShortcode.FINANCIAL_LABUAN: + return { + ...cfdConfig, + counterpartyCompany: 'Deriv (FX) Ltd', + jurisdiction: 'Labuan', + leverage: '1:100', + regulator: 'Labuan Financial Services Authority', + regulatorDescription: 'Regulator/External dispute resolution', + regulatorLicense: '(License no. MB/18/0024)', + }; + case MarketTypeShortcode.FINANCIAL_MALTAINVEST: + return { + ...cfdConfig, + counterpartyCompany: 'Deriv Investments (Europe) Limited', + jurisdiction: 'Malta', + leverage: '1:30', + regulator: 'Financial Commission', + regulatorDescription: '', + regulatorLicense: 'Regulated by the Malta Financial Services Authority (MFSA) (licence no. IS/70156)', + }; + case MarketTypeShortcode.ALL_DXTRADE: + case MarketTypeShortcode.ALL_SVG: + case MarketTypeShortcode.SYNTHETIC_SVG: + case MarketTypeShortcode.FINANCIAL_SVG: + default: + return cfdConfig; + } +}; + +const acknowledgedStatus = ['pending', 'verified']; + +const getPoiAcknowledgedForMaltainvest = (authenticationInfo?: THooks.Authentication) => { + const services = authenticationInfo?.identity?.services ?? {}; + const { manual: { status: manualStatus } = {}, onfido: { status: onfidoStatus } = {} } = services; + + return [onfidoStatus, manualStatus].some(status => acknowledgedStatus.includes(status ?? '')); +}; + +const getPoiAcknowledgedForBviLabuanVanuatu = (authenticationInfo?: THooks.Authentication) => { + const services = authenticationInfo?.identity?.services ?? {}; + const riskClassification = authenticationInfo?.risk_classification ?? ''; + const { + idv: { status: idvStatus } = {}, + manual: { status: manualStatus } = {}, + onfido: { status: onfidoStatus } = {}, + } = services; + + if (riskClassification === 'high') { + return Boolean(onfidoStatus && acknowledgedStatus.includes(onfidoStatus)); + } + return [idvStatus, onfidoStatus, manualStatus].some(status => acknowledgedStatus.includes(status ?? '')); +}; + +const shouldRestrictBviAccountCreation = (mt5Accounts: THooks.MT5AccountsList[]) => + !!mt5Accounts.filter(item => item?.landing_company_short === 'bvi' && item?.status === 'poa_failed').length; + +const shouldRestrictVanuatuAccountCreation = (mt5Accounts: THooks.MT5AccountsList[]) => + !!mt5Accounts.filter(item => item?.landing_company_short === 'vanuatu' && item?.status === 'poa_failed').length; + +const getAccountVerificationStatus = ( + shortCode: THooks.AvailableMT5Accounts['shortcode'], + shouldRestrictBviAccountCreation: boolean, + shouldRestrictVanuatuAccountCreation: boolean, + hasSubmittedPersonalDetails: boolean, + authenticationInfo?: THooks.Authentication, + isDemo?: boolean +) => { + const { + has_poa_been_attempted: hasPoaBeenAttempted, + has_poi_been_attempted: hasPoiBeenAttempted, + poa_status: poaStatus, + } = authenticationInfo || {}; + const poiOrPoaNotSubmitted = !hasPoaBeenAttempted || !hasPoiBeenAttempted; + const poaAcknowledged = acknowledgedStatus.includes(poaStatus ?? ''); + + const poiAcknowledgedForMaltainvest = getPoiAcknowledgedForMaltainvest(authenticationInfo); + const poiAcknowledgedForBviLabuanVanuatu = getPoiAcknowledgedForBviLabuanVanuatu(authenticationInfo); + + if (shortCode === Jurisdiction.SVG) { + return true; + } + if (shortCode === Jurisdiction.BVI) { + return ( + poiAcknowledgedForBviLabuanVanuatu && + !poiOrPoaNotSubmitted && + !shouldRestrictBviAccountCreation && + hasSubmittedPersonalDetails && + poaAcknowledged + ); + } + if (shortCode === Jurisdiction.VANUATU) { + return ( + poiAcknowledgedForBviLabuanVanuatu && + !poiOrPoaNotSubmitted && + !shouldRestrictVanuatuAccountCreation && + hasSubmittedPersonalDetails && + poaAcknowledged + ); + } + if (shortCode === Jurisdiction.LABUAN) { + return poiAcknowledgedForBviLabuanVanuatu && poaAcknowledged && hasSubmittedPersonalDetails; + } + if (shortCode === Jurisdiction.MALTAINVEST) { + return (poiAcknowledgedForMaltainvest && poaAcknowledged) || isDemo; + } +}; + +const isMt5AccountAdded = ( + list: THooks.MT5AccountsList[], + marketType: TMarketTypes, + companyShortCode: string, + isDemo?: boolean +) => + list.some(item => { + const currentAccountType = isDemo ? 'demo' : 'real'; + return ( + item.account_type === currentAccountType && + item.market_type === marketType && + item.landing_company_short === companyShortCode && + item.platform === CFDPlatforms.MT5 + ); + }); + +const isDxtradeAccountAdded = (list: THooks.DxtradeAccountsList[], isDemo?: boolean) => + list.some(item => { + const currentAccountType = isDemo ? 'demo' : 'real'; + return item.account_type === currentAccountType && item.platform === CFDPlatforms.DXTRADE; + }); + +const isCTraderAccountAdded = (list: THooks.CtraderAccountsList[], isDemo?: boolean) => + list.some(item => { + const currentAccountType = isDemo ? 'demo' : 'real'; + return item.account_type === currentAccountType && item.platform === CFDPlatforms.CTRADER; + }); + +export { + getAccountVerificationStatus, + getHighlightedIconLabel, + getJurisdictionDescription, + getPlatformType, + isCTraderAccountAdded, + isDxtradeAccountAdded, + isMt5AccountAdded, + shouldRestrictBviAccountCreation, + shouldRestrictVanuatuAccountCreation, +}; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsDescription.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsDescription.tsx new file mode 100644 index 0000000..cc838b4 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsDescription.tsx @@ -0,0 +1,86 @@ +import { Fragment } from 'react'; + +import { Text } from '@deriv-com/ui'; + +import { useActiveDerivTradingAccount, useRegulationFlags } from '@/hooks'; +import { THooks } from '@/types'; + +import { getJurisdictionDescription } from './CompareAccountsConfig'; + +type TCompareAccountsDescription = { + marketType: THooks.AvailableMT5Accounts['market_type']; + shortCode: THooks.AvailableMT5Accounts['shortcode']; +}; + +const CompareAccountsDescription = ({ marketType, shortCode }: TCompareAccountsDescription) => { + const { data: activeTrading } = useActiveDerivTradingAccount(); + const { regulationFlags } = useRegulationFlags(); + const { isEU: isEuRegion } = regulationFlags; + const isDemo = activeTrading?.is_virtual; + const marketTypeShortCode = marketType?.concat('_', shortCode ?? ''); + const { + leverage, + counterpartyCompany, + counterpartyCompanyDescription, + jurisdiction, + jurisdictionDescription, + leverageDescription, + regulator, + regulatorDescription, + regulatorLicense, + spread, + spreadDescription, + } = getJurisdictionDescription(marketTypeShortCode ?? ''); + + return ( +
+ + {'Up to'} {leverage} + + + {!isEuRegion ? leverageDescription : 'Leverage'} + + {!isEuRegion && ( + + + {spread} + + + {spreadDescription} + + + )} + {!isDemo && ( +
+ + {counterpartyCompany} + + + {counterpartyCompanyDescription} + + + {jurisdiction} + + + {jurisdictionDescription} + + + {regulator} + +
+ {regulatorLicense && ( + + {regulatorLicense} + + )} + + {regulatorDescription} + +
+
+ )} +
+ ); +}; + +export default CompareAccountsDescription; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsHeader.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsHeader.tsx new file mode 100644 index 0000000..f96df77 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsHeader.tsx @@ -0,0 +1,38 @@ +import { useNavigate } from 'react-router-dom'; + +import { Text } from '@deriv-com/ui'; + +import { IconComponent } from '@/components'; +import { useActiveDerivTradingAccount, useRegulationFlags } from '@/hooks'; + +const CompareAccountsHeader = () => { + const { data: activeDerivTrading } = useActiveDerivTradingAccount(); + const { regulationFlags } = useRegulationFlags(); + const { isEU } = regulationFlags; + const navigate = useNavigate(); + + const isDemo = activeDerivTrading?.is_virtual; + + const accountType = isDemo ? 'Demo' : 'real'; + const demoSuffix = isDemo ? 'demo ' : ''; + const headerTitle = isEU ? `Deriv MT5 CFDs ${accountType} account` : `Compare CFDs ${demoSuffix}accounts`; + + return ( +
+
+ + {headerTitle} + +
+ { + navigate('/traders-hub'); + }} + /> +
+ ); +}; + +export default CompareAccountsHeader; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsPlatformLabel.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsPlatformLabel.tsx new file mode 100644 index 0000000..132f3ba --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsPlatformLabel.tsx @@ -0,0 +1,46 @@ +import { twJoin } from 'tailwind-merge'; + +import { Text } from '@deriv-com/ui'; + +import { TPlatforms } from '@/types'; + +import { + CompareAccountsPlatformLabelClass, + CompareAccountsPlatformLabelTextColorClass, + TCompareAccountsPlatformLabelClassProps, + TCompareAccountsPlatformLabelTextClassProps, +} from './CompareAccounts.classnames'; +import { getPlatformType } from './CompareAccountsConfig'; +import { platformLabel } from './constants'; + +type TCompareAccountsPlatformLabel = { + platform: TPlatforms.All; +}; + +const CompareAccountsPlatformLabel = ({ platform }: TCompareAccountsPlatformLabel) => { + const platformType = getPlatformType(platform); + + return ( +
+ + {platformLabel[platformType]} + +
+ ); +}; + +export default CompareAccountsPlatformLabel; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsScreen.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsScreen.tsx new file mode 100644 index 0000000..910fc33 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsScreen.tsx @@ -0,0 +1,37 @@ +import { useCFDCompareAccounts } from '@/hooks'; + +import { CompareAccountsCarousel } from '../../components/CompareAccountsCarousel'; + +import CFDCompareAccountsCard from './CompareAccountsCard'; +import CompareAccountsHeader from './CompareAccountsHeader'; +import CTraderCompareAccountsCard from './CTraderCompareAccountsCard'; +import DerivXCompareAccountsCard from './DerivXCompareAccountsCard'; + +const CompareAccountsScreen = () => { + const { data: compareAccounts } = useCFDCompareAccounts(); + + const { mt5Accounts } = compareAccounts; + + return ( +
+ +
+ + {mt5Accounts?.map(item => ( + + ))} + + + +
+
+ ); +}; + +export default CompareAccountsScreen; diff --git a/src/cfd/screens/CFDCompareAccounts/CompareAccountsTitleIcon.tsx b/src/cfd/screens/CFDCompareAccounts/CompareAccountsTitleIcon.tsx new file mode 100644 index 0000000..d535359 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/CompareAccountsTitleIcon.tsx @@ -0,0 +1,105 @@ +import { Fragment, useRef } from 'react'; + +import { CFDPlatforms } from '@cfd/constants'; +import { Divider, Text, Tooltip, useDevice } from '@deriv-com/ui'; + +import InfoIcon from '@/assets/svgs/ic-info-outline.svg?react'; +import { IconComponent } from '@/components'; +import { useActiveDerivTradingAccount, useRegulationFlags } from '@/hooks'; +import { THooks, TPlatforms } from '@/types'; + +import { AccountIcons, MarketTypeShortcode } from './constants'; + +type TMarketType = THooks.AvailableMT5Accounts['market_type']; + +type TCompareAccountsTitleIcon = { + marketType: TMarketType; + platform: TPlatforms.All; + shortCode: THooks.AvailableMT5Accounts['shortcode']; +}; + +type TMarketWithShortCode = `${TMarketType}_${string}`; + +const getAccountIcon = (platform: TPlatforms.All, marketType: TMarketType, isEU: boolean) => { + if (isEU) { + return AccountIcons.default; + } + if (platform === CFDPlatforms.DXTRADE || platform === CFDPlatforms.CTRADER) { + return AccountIcons[platform]; + } + return (marketType && AccountIcons[marketType]) || AccountIcons.default; +}; + +const getAccountCardTitle = (shortCode: TMarketWithShortCode | TPlatforms.OtherAccounts, isDemo?: boolean) => { + switch (shortCode) { + case MarketTypeShortcode.SYNTHETIC_SVG: + return isDemo ? 'Derived Demo' : 'Derived - SVG'; + case MarketTypeShortcode.SYNTHETIC_BVI: + return 'Derived - BVI'; + case MarketTypeShortcode.SYNTHETIC_VANUATU: + return 'Derived - Vanuatu'; + case MarketTypeShortcode.FINANCIAL_SVG: + return isDemo ? 'Financial Demo' : 'Financial - SVG'; + case MarketTypeShortcode.FINANCIAL_BVI: + return 'Financial - BVI'; + case MarketTypeShortcode.FINANCIAL_VANUATU: + return 'Financial - Vanuatu'; + case MarketTypeShortcode.FINANCIAL_LABUAN: + return 'Financial - Labuan'; + case MarketTypeShortcode.ALL_SVG: + return isDemo ? 'Swap-Free Demo' : 'Swap-Free - SVG'; + case CFDPlatforms.DXTRADE: + return isDemo ? 'Deriv X Demo' : 'Deriv X'; + case CFDPlatforms.CTRADER: + return isDemo ? 'Deriv cTrader Demo' : 'Deriv cTrader'; + default: + return isDemo ? 'CFDs Demo' : 'CFDs'; + } +}; + +const CompareAccountsTitleIcon = ({ marketType, platform, shortCode }: TCompareAccountsTitleIcon) => { + const { data: activeDerivTrading } = useActiveDerivTradingAccount(); + const isDemo = !!activeDerivTrading?.is_virtual; + const marketTypeShortCode: TMarketWithShortCode = `${marketType}_${shortCode}`; + const { regulationFlags } = useRegulationFlags(); + const { isEU } = regulationFlags; + const jurisdictionCardIcon = getAccountIcon(platform, marketType, isEU); + + const hoverRef = useRef(null); + const { isDesktop } = useDevice(); + + const jurisdictionCardTitle = + platform === CFDPlatforms.DXTRADE || platform === CFDPlatforms.CTRADER + ? getAccountCardTitle(platform, !!isDemo) + : getAccountCardTitle(marketTypeShortCode, !!isDemo); + const labuanJurisdictionMessage = + 'Choosing this jurisdiction will give you a Financial STP account. Your trades will go directly to the market and have tighter spreads.'; + + return ( + +
+ +
+ + {jurisdictionCardTitle} + + {marketTypeShortCode === MarketTypeShortcode.FINANCIAL_LABUAN && ( + +
+ +
+
+ )} +
+
+ +
+ ); +}; + +export default CompareAccountsTitleIcon; diff --git a/src/cfd/screens/CFDCompareAccounts/DerivXCompareAccountsCard.tsx b/src/cfd/screens/CFDCompareAccounts/DerivXCompareAccountsCard.tsx new file mode 100644 index 0000000..83aba4e --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/DerivXCompareAccountsCard.tsx @@ -0,0 +1,38 @@ +import { useMemo } from 'react'; + +import { useActiveDerivTradingAccount, useCFDAccountsList, useCFDCompareAccounts, useRegulationFlags } from '@/hooks'; + +import CFDCompareAccountsCard from './CompareAccountsCard'; +import { isDxtradeAccountAdded } from './CompareAccountsConfig'; + +const DerivXCompareAccountsCard = () => { + const { data: activeDerivTrading } = useActiveDerivTradingAccount(); + + const { regulationFlags } = useRegulationFlags(); + const { isEU } = regulationFlags; + const { is_virtual: isDemo = false } = activeDerivTrading ?? {}; + + const { data: cfdAccounts } = useCFDAccountsList(); + + const { data: compareAccounts, hasDxtradeAccountAvailable } = useCFDCompareAccounts(); + + const { dxtradeAccount } = compareAccounts; + + const isDxtradeAdded = useMemo( + () => !!cfdAccounts && isDxtradeAccountAdded(cfdAccounts.dxtrade, !!isDemo), + [cfdAccounts, isDemo] + ); + + if (isEU || !hasDxtradeAccountAvailable || !dxtradeAccount) return null; + + return ( + + ); +}; + +export default DerivXCompareAccountsCard; diff --git a/src/cfd/screens/CFDCompareAccounts/InstrumentsIconWithLabel.tsx b/src/cfd/screens/CFDCompareAccounts/InstrumentsIconWithLabel.tsx new file mode 100644 index 0000000..7064598 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/InstrumentsIconWithLabel.tsx @@ -0,0 +1,32 @@ +import { FC } from 'react'; + +import { Text } from '@deriv-com/ui'; + +import InstrumentsIcons from '@/assets/cfd/tradingInstruments'; + +type TInstrumentsIcon = { + highlighted: boolean; + icon: keyof typeof InstrumentsIcons; + isAsterisk?: boolean; + text: string; +}; + +const InstrumentsIconWithLabel: FC = ({ highlighted, icon, isAsterisk, text }) => { + const InstrumentIcon = InstrumentsIcons[icon]; + return ( +
+ +
+ + {text} + +
+ {isAsterisk && *} +
+ ); +}; + +export default InstrumentsIconWithLabel; diff --git a/src/cfd/screens/CFDCompareAccounts/InstrumentsLabelHighlighted.tsx b/src/cfd/screens/CFDCompareAccounts/InstrumentsLabelHighlighted.tsx new file mode 100644 index 0000000..7af1126 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/InstrumentsLabelHighlighted.tsx @@ -0,0 +1,32 @@ +import { useActiveDerivTradingAccount, useRegulationFlags } from '@/hooks'; +import { THooks, TPlatforms } from '@/types'; + +import { getHighlightedIconLabel } from './CompareAccountsConfig'; +import InstrumentsIconWithLabel from './InstrumentsIconWithLabel'; + +type TInstrumentsLabelHighlighted = { + marketType: THooks.AvailableMT5Accounts['market_type']; + platform: TPlatforms.All; + shortCode: THooks.AvailableMT5Accounts['shortcode']; +}; + +const InstrumentsLabelHighlighted = ({ marketType, platform, shortCode }: TInstrumentsLabelHighlighted) => { + const { data: activeDerivTrading } = useActiveDerivTradingAccount(); + const { regulationFlags } = useRegulationFlags(); + const { isEU: isEuRegion } = regulationFlags; + const isDemo = activeDerivTrading?.is_virtual; + const iconData = [...getHighlightedIconLabel(platform, isEuRegion, marketType, shortCode)]; + + return ( +
+ {iconData.map(item => ( + + ))} +
+ ); +}; + +export default InstrumentsLabelHighlighted; diff --git a/src/cfd/screens/CFDCompareAccounts/constants.ts b/src/cfd/screens/CFDCompareAccounts/constants.ts new file mode 100644 index 0000000..bb40088 --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/constants.ts @@ -0,0 +1,38 @@ +import { CFDPlatforms, MarketType } from '../../constants'; + +export const AccountIcons = { + [MarketType.SYNTHETIC]: 'Derived', + [MarketType.FINANCIAL]: 'Financial', + [MarketType.ALL]: 'SwapFree', + [CFDPlatforms.DXTRADE]: 'DerivX', + [CFDPlatforms.CTRADER]: 'CTrader', + default: 'CFDs', +} as const; + +export const MarketTypeShortcode = { + ALL_DXTRADE: 'all_', + ALL_SVG: 'all_svg', + FINANCIAL_BVI: 'financial_bvi', + FINANCIAL_LABUAN: 'financial_labuan', + FINANCIAL_MALTAINVEST: 'financial_maltainvest', + FINANCIAL_SVG: 'financial_svg', + FINANCIAL_VANUATU: 'financial_vanuatu', + GAMING: 'gaming', + SYNTHETIC_BVI: 'synthetic_bvi', + SYNTHETIC_SVG: 'synthetic_svg', + SYNTHETIC_VANUATU: 'synthetic_vanuatu', +} as const; + +export const Jurisdiction = { + BVI: 'bvi', + LABUAN: 'labuan', + MALTAINVEST: 'maltainvest', + SVG: 'svg', + VANUATU: 'vanuatu', +} as const; + +export const platformLabel = { + CTrader: 'Deriv cTrader', + MT5: 'MT5 Platform', + DerivX: 'Deriv X', +} as const; diff --git a/src/cfd/screens/CFDCompareAccounts/index.ts b/src/cfd/screens/CFDCompareAccounts/index.ts new file mode 100644 index 0000000..be415ad --- /dev/null +++ b/src/cfd/screens/CFDCompareAccounts/index.ts @@ -0,0 +1 @@ +export { default as CompareAccountsScreen } from './CompareAccountsScreen'; diff --git a/src/cfd/screens/Jurisdiction/JurisdictionCard/JurisdictionCard.tsx b/src/cfd/screens/Jurisdiction/JurisdictionCard/JurisdictionCard.tsx index 2940a2f..9307a92 100644 --- a/src/cfd/screens/Jurisdiction/JurisdictionCard/JurisdictionCard.tsx +++ b/src/cfd/screens/Jurisdiction/JurisdictionCard/JurisdictionCard.tsx @@ -121,8 +121,7 @@ export const JurisdictionCard = ({ row?.titleIndicators?.displayText && (
diff --git a/src/components/CFDSection/CFDHeading/CFDHeading.tsx b/src/components/CFDSection/CFDHeading/CFDHeading.tsx index a298cda..6d0378b 100644 --- a/src/components/CFDSection/CFDHeading/CFDHeading.tsx +++ b/src/components/CFDSection/CFDHeading/CFDHeading.tsx @@ -18,13 +18,7 @@ const CompareAccountsButton = () => { if (!isAuthorized) return null; return ( - ); diff --git a/src/components/CFDSection/CFDHeading/__tests__/CFDHeading.spec.tsx b/src/components/CFDSection/CFDHeading/__tests__/CFDHeading.spec.tsx index a157232..74dec1d 100644 --- a/src/components/CFDSection/CFDHeading/__tests__/CFDHeading.spec.tsx +++ b/src/components/CFDSection/CFDHeading/__tests__/CFDHeading.spec.tsx @@ -123,6 +123,6 @@ describe('CFDHeading', () => { const button = screen.getByText(/Compare Accounts/i); fireEvent.click(button); - expect(navigate).toHaveBeenCalledWith('/traders-hub/compare-accounts'); + expect(navigate).toHaveBeenCalledWith('/compare-accounts'); }); }); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index d7e4a9d..d26683b 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -3,6 +3,8 @@ export { useActiveDerivTradingAccount } from './useActiveDerivTradingAccount'; export { useAvailableMT5Accounts } from './useAvailableMT5Accounts'; export { useBalance } from './useBalance'; export { useCreateOtherCFDAccount } from './useCreateOtherCFDAccount'; +export { useCFDAccountsList } from './useCFDAccountsList'; +export { useCFDCompareAccounts } from './useCFDCompareAccounts'; export { useCtraderAccountsList } from './useCtraderAccountsList'; export { useCurrencyConfig } from './useCurrencyConfig'; export { useDerivTradingAccountsList } from './useDerivTradingAccountsList'; diff --git a/src/hooks/useCFDAccountsList.tsx b/src/hooks/useCFDAccountsList.tsx new file mode 100644 index 0000000..4331b31 --- /dev/null +++ b/src/hooks/useCFDAccountsList.tsx @@ -0,0 +1,50 @@ +import { useMemo } from 'react'; + +import { useCtraderAccountsList } from './useCtraderAccountsList'; +import { useDxtradeAccountsList } from './useDxtradeAccountsList'; +import { useMT5AccountsList } from './useMT5AccountsList'; + +/** A custom hook that gets the list all created CFD accounts of the user. */ +export const useCFDAccountsList = () => { + const { + data: mt5_accounts, + isError: isMT5AccountsListError, + isLoading: isMT5AccountsListLoading, + isSuccess: isMT5AccountsListSuccess, + } = useMT5AccountsList(); + const { + data: dxtrade_accounts, + isError: isDxtradeAccountsListError, + isLoading: isDxtradeAccountsListLoading, + isSuccess: isDxtradeAccountsListSuccess, + } = useDxtradeAccountsList(); + const { + data: ctrader_accounts, + isError: isCtraderAccountsListError, + isLoading: CtraderAccountsListLoading, + isSuccess: isCtraderAccountsListSuccess, + } = useCtraderAccountsList(); + + const data = useMemo(() => { + if (!mt5_accounts || !dxtrade_accounts || !ctrader_accounts) return; + + return { + mt5: mt5_accounts, + dxtrade: dxtrade_accounts, + ctrader: ctrader_accounts, + }; + }, [mt5_accounts, dxtrade_accounts, ctrader_accounts]); + + const isError = isMT5AccountsListError || isDxtradeAccountsListError || isCtraderAccountsListError; + + const isLoading = isMT5AccountsListLoading || isDxtradeAccountsListLoading || CtraderAccountsListLoading; + + const isSuccess = isMT5AccountsListSuccess && isDxtradeAccountsListSuccess && isCtraderAccountsListSuccess; + + return { + data, + isError, + isLoading, + isSuccess, + }; +}; diff --git a/src/hooks/useCFDCompareAccounts.tsx b/src/hooks/useCFDCompareAccounts.tsx new file mode 100644 index 0000000..7b08f8c --- /dev/null +++ b/src/hooks/useCFDCompareAccounts.tsx @@ -0,0 +1,162 @@ +import { useMemo } from 'react'; + +import { + useActiveDerivTradingAccount, + useAvailableMT5Accounts, + useLandingCompany, + useMT5AccountsList, + useRegulationFlags, +} from '.'; + +// Remove the hardcoded values and use the values from the API once it's ready +export const MARKET_TYPE = { + ALL: 'all', + FINANCIAL: 'financial', + SYNTHETIC: 'synthetic', +} as const; + +// Remove the hardcoded values and use the values from the API once it's ready +export const CFD_PLATFORMS = { + CFDS: 'CFDs', + CTRADER: 'ctrader', + DXTRADE: 'dxtrade', + MT5: 'mt5', +} as const; + +// Remove the hardcoded values and use the values from the API once it's ready +export const JURISDICTION = { + BVI: 'bvi', + LABUAN: 'labuan', + MALTAINVEST: 'maltainvest', + SVG: 'svg', + VANUATU: 'vanuatu', +} as const; + +// Remove the hardcoded values and use the values from the API once it's ready +const dxtradeAccount = { + leverage: 0, + market_type: MARKET_TYPE.ALL, + name: 'Deriv X', + platform: CFD_PLATFORMS.DXTRADE, + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: JURISDICTION.SVG, +}; + +// Remove the hardcoded values and use the values from the API once it's ready +const ctraderAccount = { + leverage: 0, + market_type: MARKET_TYPE.ALL, + name: 'cTrader', + platform: CFD_PLATFORMS.CTRADER, + requirements: { + after_first_deposit: { + financial_assessment: [''], + }, + compliance: { + mt5: [''], + tax_information: [''], + }, + signup: [''], + }, + shortcode: JURISDICTION.SVG, +}; + +/** A custom hook that gets compare accounts values. */ +export const useCFDCompareAccounts = () => { + const { regulationFlags } = useRegulationFlags(); + const { isEU } = regulationFlags; + const { data: activeDerivTradingAccount } = useActiveDerivTradingAccount(); + const { is_virtual: isDemoTrading } = activeDerivTradingAccount ?? {}; + + const isDemo = isDemoTrading; + + const { data: allAvailableMt5Accounts } = useAvailableMT5Accounts(); + const { data: addedAccounts, ...rest } = useMT5AccountsList(); + + const modifiedMt5Data = useMemo(() => { + if (!allAvailableMt5Accounts || !addedAccounts) return; + + return allAvailableMt5Accounts?.map(availableAccount => { + const createdAccount = addedAccounts?.find(account => { + return ( + availableAccount.market_type === account.market_type && + availableAccount.shortcode === account.landing_company_short + ); + }); + if (createdAccount) + return { + ...availableAccount, + + /** Determine if the account is added or not */ + is_added: true, + } as const; + + return { + ...availableAccount, + + /** Determine if the account is added or not */ + is_added: false, + } as const; + }); + }, [addedAccounts, allAvailableMt5Accounts]); + + // Sort the data by market_type to make sure the order is 'synthetic', 'financial', 'all' + const sortedMt5Accounts = useMemo(() => { + const marketTypeOrder = ['synthetic', 'financial', 'all']; + + if (!modifiedMt5Data) return; + + if (isEU) { + return modifiedMt5Data.filter( + account => account.shortcode === 'maltainvest' && account.market_type === 'financial' + ); + } + + const sortedData = marketTypeOrder.reduce( + (acc, marketType) => { + const accounts = modifiedMt5Data.filter( + account => account.market_type === marketType && account.shortcode !== 'maltainvest' + ); + if (!accounts.length) return acc; + return [...acc, ...accounts]; + }, + [] as typeof modifiedMt5Data + ); + return sortedData; + }, [isEU, modifiedMt5Data]); + + const { data: landingCompany } = useLandingCompany(); + + const hasDxtradeAccountAvailable = landingCompany?.dxtrade_all_company; + const hasCTraderAccountAvailable = landingCompany?.ctrader?.all?.standard === JURISDICTION.SVG; + + const demoAvailableAccounts = useMemo(() => { + if (!sortedMt5Accounts) return; + if (isEU) return sortedMt5Accounts.filter(account => account.shortcode === JURISDICTION.MALTAINVEST); + return sortedMt5Accounts.filter(account => account.shortcode === JURISDICTION.SVG); + }, [isEU, sortedMt5Accounts]); + + const modifiedData = useMemo(() => { + return { + ctraderAccount: hasCTraderAccountAvailable ? ctraderAccount : undefined, + dxtradeAccount: hasDxtradeAccountAvailable ? dxtradeAccount : undefined, + mt5Accounts: isDemo ? demoAvailableAccounts : sortedMt5Accounts, + }; + }, [demoAvailableAccounts, hasCTraderAccountAvailable, hasDxtradeAccountAvailable, isDemo, sortedMt5Accounts]); + + return { + data: modifiedData, + hasCTraderAccountAvailable, + hasDxtradeAccountAvailable, + ...rest, + }; +}; diff --git a/src/pages/compareAccounts/compareAccounts.tsx b/src/pages/compareAccounts/compareAccounts.tsx index afb597f..6dbf69f 100644 --- a/src/pages/compareAccounts/compareAccounts.tsx +++ b/src/pages/compareAccounts/compareAccounts.tsx @@ -1,10 +1,12 @@ import { Link } from 'react-router-dom'; +import CompareAccountsScreen from '../../cfd/screens/CFDCompareAccounts/CompareAccountsScreen'; + export const CompareAccounts = () => { return ( <> -

Compare Accounts

Go to Homepage + ); };