From bd1e8737e05f611c49d26123f8cd99d06c3f84bb Mon Sep 17 00:00:00 2001 From: George Usynin <103181646+heorhi-deriv@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:44:22 +0300 Subject: [PATCH] george / wall-1177 / Demo transfer insufficient funds error (#9211) * feat: add demo transfer error handlers, add useTransferBetweenAccounts hook * fix: types errors * fix: commit * feat: add transfer hooks * test: add tests * fix: commit * feat: :art: refactor code, add useActiveCFDAccounts hook * chore: remove comment * refactor: :fire: cleanup * fix: useAvaliableWallets hook * fix: typescript errors * test: add tests, apply comments * test: add test, add test accounts * test: fix tests * fix: fix ts errors * fix: apply comments * perf: small improvement * perf: small improvement * fix: resolve conflicts * refactor: improve logic, fix test * chore: remove unused import * feat: add reset balance button handler * fix: currency register * style: fix styles in dark mode * test: fix test * perf: minor improvements * refactor: :zap: transfer account data transfarmation layer * fix: minor fix * test: fix tests * fix: input error color * refactor: split accounts: trading_accounts and wallets * test: fix test * refactor: improve naming * fix: minor fix * feat: add useExistingCFDAccounts hook * test: fix tests * perf: replace useRequest with usefetch in useExistingCFDAccounts hook * test: fix test * style: fix selected tile style * refactor: apply comments --- packages/api/types.ts | 210 +++++++++ .../__test__/wallet-add-card.spec.tsx | 27 +- .../add-more-wallets/wallet-add-card.tsx | 2 +- .../__tests__/wallet-modal-body.spec.tsx | 29 +- .../__tests__/wallet-modal-header.spec.tsx | 53 ++- .../modals/wallet-modal/provider.tsx | 2 +- .../modals/wallet-modal/wallet-modal-body.tsx | 15 +- .../wallet-modal/wallet-modal-header.tsx | 116 ++--- .../modals/wallet-modal/wallet-modal.tsx | 37 +- .../wallet-jurisdiction-badge.spec.tsx | 23 + .../wallet-jurisdiction-badge/index.ts | 3 + .../wallet-jurisdiction-badge.tsx | 18 + .../__tests__/wallet-transfer.spec.tsx | 32 +- .../mock_accounts/mock_accounts.tsx | 54 --- .../__tests__/transfer-account-list.spec.tsx | 98 ++++ .../transfer-account-selector.spec.tsx | 88 ++++ .../transfer-account-selector/index.ts | 0 .../transfer-account-list.tsx | 52 ++- .../transfer-account-selector.scss | 4 +- .../transfer-account-selector.tsx | 80 +++- .../__tests__/wallet-transfer-tile.spec.tsx | 88 ++++ .../wallet-transfer-tile/index.ts | 3 + .../wallet-transfer-tile.scss} | 12 +- .../wallet-transfer-tile.tsx | 120 +++++ .../wallet-transfer/wallet-transfer.scss | 5 +- .../wallet-transfer/wallet-transfer.tsx | 434 +++++++++++------- packages/appstore/src/types/common.types.ts | 41 +- .../components/amount-input/amount-input.scss | 17 + .../components/amount-input/amount-input.tsx | 10 +- .../components/src/components/icon/icons.js | 3 + .../ic-wallet-error-message-with-cross.svg | 1 + ...ic-wallet-info-message-with-three-dots.svg | 1 + .../icon/wallet/ic-wallet-success-message.svg | 1 + .../__tests__/alert-message.spec.tsx | 59 +++ .../__tests__/message-list.spec.tsx | 17 + .../components/message-list/alert-message.tsx | 66 +++ .../src/components/message-list/index.ts | 1 + .../components/message-list/message-list.scss | 53 +++ .../components/message-list/message-list.tsx | 54 +++ .../__tests__/transfer-account-list.spec.tsx | 86 ---- .../transfer-account-selector.spec.tsx | 53 --- .../__tests__/transfer-tile.spec.tsx | 60 --- .../transfer-tile.tsx | 58 --- .../components/wallet-icon/wallet-icon.tsx | 6 + .../__tests__/wallet-tile.spec.tsx | 86 ---- .../src/components/wallet-tile/index.ts | 3 - .../components/wallet-tile/wallet-tile.tsx | 132 ------ packages/components/src/index.js | 4 +- packages/components/stories/icon/icons.js | 39 +- .../__tests__/useExistingCFDAccounts.spec.tsx | 173 +++++++ .../useTransferBetweenAccounts.spec.tsx | 294 ++++++++++++ .../__tests__/useWalletTransactions.spec.tsx | 13 + .../src/__tests__/useWalletTransfer.spec.tsx | 252 ++++++++++ packages/hooks/src/index.ts | 7 +- packages/hooks/src/useExistingCFDAccounts.ts | 106 +++++ .../hooks/src/useTransferBetweenAccounts.ts | 144 ++++++ packages/hooks/src/useWalletTransfer.ts | 54 +++ packages/shared/src/styles/themes.scss | 2 + ...spec.tsx => getWalletCurrencyIcon.spec.ts} | 0 ...pec.tsx => groupTransactionsByDay.spec.ts} | 0 ....spec.tsx => unFormatLocaleString.spec.ts} | 0 packages/utils/src/index.ts | 2 +- 62 files changed, 2593 insertions(+), 910 deletions(-) create mode 100644 packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx create mode 100644 packages/appstore/src/components/wallet-jurisdiction-badge/index.ts create mode 100644 packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx delete mode 100644 packages/appstore/src/components/wallet-transfer/mock_accounts/mock_accounts.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx rename packages/{components/src/components => appstore/src/components/wallet-transfer}/transfer-account-selector/index.ts (100%) rename packages/{components/src/components => appstore/src/components/wallet-transfer}/transfer-account-selector/transfer-account-list.tsx (60%) rename packages/{components/src/components => appstore/src/components/wallet-transfer}/transfer-account-selector/transfer-account-selector.scss (97%) rename packages/{components/src/components => appstore/src/components/wallet-transfer}/transfer-account-selector/transfer-account-selector.tsx (58%) create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/__tests__/wallet-transfer-tile.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts rename packages/{components/src/components/wallet-tile/wallet-tile.scss => appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss} (72%) create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-error-message-with-cross.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-info-message-with-three-dots.svg create mode 100644 packages/components/src/components/icon/wallet/ic-wallet-success-message.svg create mode 100644 packages/components/src/components/message-list/__tests__/alert-message.spec.tsx create mode 100644 packages/components/src/components/message-list/__tests__/message-list.spec.tsx create mode 100644 packages/components/src/components/message-list/alert-message.tsx create mode 100644 packages/components/src/components/message-list/index.ts create mode 100644 packages/components/src/components/message-list/message-list.scss create mode 100644 packages/components/src/components/message-list/message-list.tsx delete mode 100644 packages/components/src/components/transfer-account-selector/__tests__/transfer-account-list.spec.tsx delete mode 100644 packages/components/src/components/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx delete mode 100644 packages/components/src/components/transfer-account-selector/__tests__/transfer-tile.spec.tsx delete mode 100644 packages/components/src/components/transfer-account-selector/transfer-tile.tsx delete mode 100644 packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx delete mode 100644 packages/components/src/components/wallet-tile/index.ts delete mode 100644 packages/components/src/components/wallet-tile/wallet-tile.tsx create mode 100644 packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx create mode 100644 packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx create mode 100644 packages/hooks/src/__tests__/useWalletTransfer.spec.tsx create mode 100644 packages/hooks/src/useExistingCFDAccounts.ts create mode 100644 packages/hooks/src/useTransferBetweenAccounts.ts create mode 100644 packages/hooks/src/useWalletTransfer.ts rename packages/utils/src/__tests__/{getWalletCurrencyIcon.spec.tsx => getWalletCurrencyIcon.spec.ts} (100%) rename packages/utils/src/__tests__/{groupTransactionsByDay.spec.tsx => groupTransactionsByDay.spec.ts} (100%) rename packages/utils/src/__tests__/{unFormatLocaleString.spec.tsx => unFormatLocaleString.spec.ts} (100%) diff --git a/packages/api/types.ts b/packages/api/types.ts index 59b8cb6c1839..c874f1f20a80 100644 --- a/packages/api/types.ts +++ b/packages/api/types.ts @@ -243,6 +243,216 @@ type TPrivateEndpoints = { }; type TPrivateSocketEndpoints = { + trading_platform_accounts: { + request: { + /** + * Must be `1` + */ + trading_platform_accounts: 1; + /** + * Trading platform name + */ + platform: 'dxtrade' | 'mt5' | 'derivez'; + /** + * [Optional] Used to pass data through the websocket, which may be retrieved via the `echo_req` output field. + */ + passthrough?: { + [k: string]: unknown; + }; + /** + * [Optional] Used to map request to response. + */ + req_id?: number; + }; + response: { + /** + * Array containing Trading account objects. + */ + trading_platform_accounts?: { + /** + * ID of Trading account. + */ + account_id?: string; + /** + * Account type. + */ + account_type?: 'demo' | 'real'; + /** + * Balance of the Trading account. + */ + balance?: null | number; + /** + * Residence of the MT5 account. + */ + country?: string; + /** + * Currency of the Trading account. + */ + currency?: string; + /** + * Account balance, formatted to appropriate decimal places. + */ + display_balance?: null | string; + /** + * Email address of the MT5 account. + */ + email?: string; + /** + * Account enabled status + */ + enabled?: number; + /** + * Error in MT5 account details. + */ + error?: { + /** + * Error code string. + */ + code?: string; + /** + * Extra information about the error. + */ + details?: { + /** + * MT5 account type. + */ + account_type?: string; + /** + * MT5 account login ID. + */ + login?: string; + /** + * Trade server name of the MT5 account. + */ + server?: string; + /** + * Trade server information. + */ + server_info?: { + /** + * The environment. E.g. Deriv-Server. + */ + environment?: 'Deriv-Demo' | 'Deriv-Server' | 'Deriv-Server-02'; + /** + * Geographical location of the server. + */ + geolocation?: { + /** + * Internal server grouping. + */ + group?: string; + /** + * Sever location. + */ + location?: string; + /** + * Sever region. + */ + region?: string; + /** + * Sever sequence. + */ + sequence?: number; + }; + /** + * Server id. + */ + id?: string; + }; + }; + /** + * Error message. + */ + message_to_client?: string; + }; + /** + * Group type of the MT5 account, e.g. `demo\svg_financial` + */ + group?: string; + /** + * Landing company shortcode of the Trading account. + */ + landing_company_short?: 'bvi' | 'labuan' | 'malta' | 'maltainvest' | 'svg' | 'vanuatu' | 'seychelles'; + /** + * Leverage of the MT5 account (1 to 1000). + */ + leverage?: number; + /** + * Login name used to log in into Trading platform + */ + login?: string; + /** + * Market type + */ + market_type?: 'financial' | 'synthetic' | 'all'; + /** + * Name of the owner of the MT5 account. + */ + name?: string; + /** + * Name of trading platform. + */ + platform?: 'dxtrade' | 'mt5'; + /** + * Trade server name of the MT5 account. + */ + server?: string; + /** + * Trade server information. + */ + server_info?: { + /** + * The environment. E.g. Deriv-Server. + */ + environment?: 'Deriv-Demo' | 'Deriv-Server' | 'Deriv-Server-02'; + /** + * Geographical location of the server. + */ + geolocation?: { + /** + * Internal server grouping. + */ + group?: string; + /** + * Sever location. + */ + location?: string; + /** + * Sever region. + */ + region?: string; + /** + * Sever sequence. + */ + sequence?: number; + }; + /** + * Server id. + */ + id?: string; + }; + /** + * Sub account type + */ + sub_account_type?: 'financial' | 'financial_stp'; + }[]; + /** + * Echo of the request made. + */ + echo_req: { + [k: string]: unknown; + }; + /** + * Action name of the request made. + */ + msg_type: 'trading_platform_accounts'; + /** + * Optional field sent in request to map to response, present only when request contains `req_id`. + */ + req_id?: number; + [k: string]: unknown; + }; + }; cashier_payments: { request: { /** diff --git a/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx b/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx index aec14351d04d..259198629d88 100644 --- a/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx +++ b/packages/appstore/src/components/add-more-wallets/__test__/wallet-add-card.spec.tsx @@ -62,7 +62,14 @@ describe('AddWalletCard', () => { render( - + ); @@ -85,7 +92,14 @@ describe('AddWalletCard', () => { render( - + ); @@ -101,7 +115,14 @@ describe('AddWalletCard', () => { render( - + ); diff --git a/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx b/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx index 32e2ee9ede90..278f290177a9 100644 --- a/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx +++ b/packages/appstore/src/components/add-more-wallets/wallet-add-card.tsx @@ -3,7 +3,7 @@ import { TWalletInfo } from 'Types'; import { Text, WalletCard } from '@deriv/components'; import { useCurrencyConfig } from '@deriv/hooks'; import { observer, useStore } from '@deriv/stores'; -import { getWalletCurrencyIcon } from 'Constants/utils'; +import { getWalletCurrencyIcon } from '@deriv/utils'; import wallet_description_mapper from 'Constants/wallet_description_mapper'; type TAddWalletCard = { diff --git a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx index 21516d59727c..490a3e1efb79 100644 --- a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-body.spec.tsx @@ -16,11 +16,27 @@ describe('WalletModalBody', () => { mocked_props = { contentScrollHandler: jest.fn(), is_dark: false, - is_demo: true, is_mobile: false, - setIsWalletNameVisible: jest.fn(), is_wallet_name_visible: true, - wallet_type: 'demo', + setIsWalletNameVisible: jest.fn(), + wallet: { + balance: 1000, + currency: 'USD', + currency_config: { + display_code: 'USD', + is_crypto: false, + } as typeof mocked_props['wallet']['currency_config'], + gradient_card_class: 'wallet-card__usd', + gradient_header_class: 'wallet-header__usd', + icon: '', + is_demo: true, + is_disabled: 0, + is_malta_wallet: false, + is_selected: true, + is_virtual: 1, + landing_company_name: 'svg', + wallet_currency_type: 'Demo', + }, }; }); @@ -46,7 +62,6 @@ describe('WalletModalBody', () => { }); it('Should render proper content under the Transfer tab', () => { - mocked_props.wallet_type = 'real'; const mocked_store = mockStore({ traders_hub: { active_modal_tab: 'Transfer', @@ -61,11 +76,11 @@ describe('WalletModalBody', () => { const el_transfer_tab = screen.getByText('Transfer'); userEvent.click(el_transfer_tab); - expect(screen.getByText('Transfer Real')).toBeInTheDocument(); + expect(screen.getByText('WalletTransfer')).toBeInTheDocument(); }); it('Should trigger setWalletModalActiveTab callback when the user clicked on the tab', () => { - mocked_props.wallet_type = 'real'; + mocked_props.wallet.is_demo = false; const mocked_store = mockStore({ traders_hub: { active_modal_tab: 'Deposit', @@ -84,7 +99,7 @@ describe('WalletModalBody', () => { }); it('Should trigger contentScrollHandler callback when the user scrolls the content', () => { - mocked_props.wallet_type = 'real'; + mocked_props.wallet.is_demo = false; const mocked_store = mockStore({ traders_hub: { active_modal_tab: 'Deposit', diff --git a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx index 5ec64539a6e4..c6911fe75bce 100644 --- a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal-header.spec.tsx @@ -3,29 +3,68 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import WalletModalHeader from '../wallet-modal-header'; +jest.mock('@deriv/hooks', () => ({ + useCurrencyConfig: () => ({ getConfig: () => ({ display_code: 'USD' }) }), +})); + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(() => ({ + data: { + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'US Dollar', + stake_default: 10, + type: 'fiat', + }, + }, + }, + }, + })), +})); + describe('WalletModalHeader', () => { let mocked_props: React.ComponentProps; beforeEach(() => { mocked_props = { - balance: 999, closeModal: jest.fn(), - currency: 'USD', is_dark: false, - is_demo: true, is_mobile: false, is_wallet_name_visible: true, - shortcode: 'SVG', + wallet: { + balance: 1000, + currency: 'USD', + currency_config: { + display_code: 'USD', + is_crypto: false, + } as typeof mocked_props['wallet']['currency_config'], + gradient_card_class: 'wallet-card__usd', + gradient_header_class: 'wallet-header__usd', + icon: 'IcWalletIcon', + is_demo: true, + is_disabled: 0, + is_malta_wallet: false, + is_selected: true, + is_virtual: 1, + landing_company_name: 'svg', + wallet_currency_type: 'USD', + }, }; }); it('Should render header with proper title, balance, badge and icons', () => { render(); - expect(screen.getByText('Demo USD Wallet')).toBeInTheDocument(); + expect(screen.getByText('USD Wallet')).toBeInTheDocument(); expect(screen.getByText('Demo')).toBeInTheDocument(); - expect(screen.getByText('999.00 USD')).toBeInTheDocument(); - expect(screen.getByTestId('dt_currency_icon')).toBeInTheDocument(); + expect(screen.getByText('1,000.00 USD')).toBeInTheDocument(); + expect(screen.getByTestId('dt_wallet_icon')).toBeInTheDocument(); expect(screen.getByTestId('dt_close_icon')).toBeInTheDocument(); }); diff --git a/packages/appstore/src/components/modals/wallet-modal/provider.tsx b/packages/appstore/src/components/modals/wallet-modal/provider.tsx index 38e763f14a53..a4db49cd9083 100644 --- a/packages/appstore/src/components/modals/wallet-modal/provider.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/provider.tsx @@ -22,7 +22,7 @@ export const getCashierOptions = (type: TWalletType) => { { icon: 'IcAccountTransfer', label: localize('Transfer'), - content: () =>

Transfer Real

, + content: (props: React.ComponentProps) => , }, { icon: 'IcStatement', diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx index e214197c4459..f6d60d22010f 100644 --- a/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-body.tsx @@ -1,17 +1,17 @@ import React from 'react'; import classNames from 'classnames'; import { Tabs, ThemedScrollbars, Div100vhContainer } from '@deriv/components'; -import { getCashierOptions, TWalletType } from './provider'; +import { getCashierOptions } from './provider'; import { observer, useStore } from '@deriv/stores'; +import type { TWalletAccount } from 'Types'; type TWalletModalBodyProps = { contentScrollHandler: React.UIEventHandler; is_dark: boolean; - is_demo: boolean; is_mobile: boolean; setIsWalletNameVisible: (value: boolean) => void; is_wallet_name_visible: boolean; - wallet_type: TWalletType; + wallet: TWalletAccount; }; const real_tabs = { @@ -32,14 +32,15 @@ const WalletModalBody = observer( ({ contentScrollHandler, is_dark, - is_demo, is_mobile, setIsWalletNameVisible, is_wallet_name_visible, - wallet_type, + wallet, }: TWalletModalBodyProps) => { const store = useStore(); + const { is_demo } = wallet; + const { traders_hub: { active_modal_tab, setWalletModalActiveTab }, } = store; @@ -58,7 +59,7 @@ const WalletModalBody = observer( active_icon_color={is_dark ? 'var(--badge-white)' : ''} active_index={tabs[active_modal_tab || 'Deposit']} className={classNames('modal-body__tabs', { - is_scrolled: !is_wallet_name_visible, + is_scrolled: is_mobile && !is_wallet_name_visible, })} has_active_line={false} has_bottom_line={false} @@ -72,7 +73,7 @@ const WalletModalBody = observer( setWalletModalActiveTab(tab_name); }} > - {getCashierOptions(wallet_type).map(option => { + {getCashierOptions(is_demo ? 'demo' : 'real').map(option => { return (
{ - const header_class_name = 'modal-header'; + const { balance, currency, icon, currency_config, is_demo, gradient_header_class, landing_company_name } = wallet; + const is_crypto = currency_config?.is_crypto; + const display_currency_code = currency_config?.display_code; - const wallet_title = React.useMemo(() => { - return `${is_demo ? localize('Demo') : ''} ${getCurrencyDisplayCode(currency)} ${localize('Wallet')}`; - }, [currency, is_demo]); - - const getBadgeLabel = React.useCallback(() => { - if (is_demo) return localize('Demo'); - switch (shortcode) { - case 'svg': - return shortcode.toUpperCase(); - case 'malta': - case 'maltainvest': - return 'malta'.toUpperCase(); - default: - return ''; - } - }, [is_demo, shortcode]); + const header_class_name = 'modal-header'; const getCloseIcon = React.useCallback(() => { if (is_demo && is_dark) return 'IcAppstoreCloseLight'; @@ -54,47 +34,31 @@ const WalletModalHeader = ({ return 'IcAppstoreCloseLight'; }, [is_dark, is_demo]); - const getStylesByClassName = React.useCallback( - (class_name: string) => { - return classNames(class_name, { - [`${class_name}-demo`]: is_demo, - }); - }, - [is_demo] - ); - - const getCurrencyIconSize = React.useCallback(() => { - // TODO: add p2p and payment_agent check - const is_square_icon = - ['btc', 'eth', 'ltc', 'usdt', 'eusdt', 'tusdt', 'ust', 'usdc', 'p2p', 'payment_agent'].includes( - currency.toLowerCase() - ) || is_demo; - - const sizes = { - mobile: { - width: is_square_icon ? 64 : 48, - height: is_square_icon ? 40 : 48, - }, - desktop: { - width: is_square_icon ? 128 : 64, - height: is_square_icon ? 80 : 64, - }, - }; - - const size = is_mobile ? sizes.mobile : sizes.desktop; + const getWalletIcon = React.useCallback(() => { + if (currency && ['USDT', 'eUSDT', 'tUSDT', 'UST'].includes(currency)) { + return is_dark ? 'IcWalletModalTetherDark' : 'IcWalletModalTetherLight'; + } + return icon; + }, [currency, icon, is_dark]); - return size; - }, [currency, is_demo, is_mobile]); + const getStylesByClassName = (class_name: string) => { + return classNames(class_name, { + [`${class_name}-demo`]: is_demo, + }); + }; - const getCurrencyIconProps = React.useCallback(() => { - const icon = getWalletCurrencyIcon(is_demo ? 'demo' : currency, is_dark, true); - const size = getCurrencyIconSize(); + const getWalletIconType = (): React.ComponentProps['type'] => { + if (is_demo) return 'demo'; + return is_crypto ? 'crypto' : 'fiat'; + }; - return { icon, ...size }; - }, [currency, getCurrencyIconSize, is_dark, is_demo]); + const getWalletIconSize = (): React.ComponentProps['size'] => { + if (is_mobile) return is_demo || is_crypto ? 'large' : 'xlarge'; + return 'xxlarge'; + }; return ( -
+
- {wallet_title} + {getAccountName({ + display_currency_code: wallet.currency_config?.display_code, + account_type: 'wallet', + })} - {is_demo ? ( - - ) : ( - - )} +
- {formatMoney(currency, balance, true)} {getCurrencyDisplayCode(currency)} + {formatMoney(currency || '', balance, true)} {display_currency_code}
- {/* TODO: replace Icon with WalletIcon component */}
- +
diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx index d2fb116b283d..50ac7346cc4d 100644 --- a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx @@ -1,33 +1,31 @@ import React, { useEffect } from 'react'; -import { Modal, Loading } from '@deriv/components'; +import { Loading, Modal } from '@deriv/components'; +import { useActiveWallet } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; import WalletModalHeader from './wallet-modal-header'; import WalletModalBody from './wallet-modal-body'; -import { observer, useStore } from '@deriv/stores'; -import { useActiveWallet } from '@deriv/hooks'; const WalletModal = observer(() => { const store = useStore(); const { - client: { balance, currency, landing_company_shortcode: shortcode, is_authorize, switchAccount }, + client: { is_authorize, switchAccount }, ui: { is_dark_mode_on, is_wallet_modal_visible, is_mobile, setIsWalletModalVisible }, traders_hub: { active_modal_tab, active_modal_wallet_id, setWalletModalActiveTab }, } = store; - const wallet = useActiveWallet(); + const active_wallet = useActiveWallet(); useEffect(() => { let timeout_id: NodeJS.Timeout; - if (is_wallet_modal_visible && wallet?.loginid !== active_modal_wallet_id) { + + if (is_wallet_modal_visible && active_wallet?.loginid !== active_modal_wallet_id) { /** Adding a delay as per requirement because the modal must appear first, then switch the account */ - timeout_id = setTimeout(() => switchAccount(active_modal_wallet_id), 700); + timeout_id = setTimeout(() => switchAccount(active_modal_wallet_id), 500); } return () => clearTimeout(timeout_id); - }, [active_modal_wallet_id, is_wallet_modal_visible, switchAccount, wallet?.loginid]); - - const is_demo = wallet?.is_demo || false; - const wallet_type = is_demo ? 'demo' : 'real'; + }, [active_modal_wallet_id, active_wallet?.loginid, is_wallet_modal_visible, switchAccount]); const [is_wallet_name_visible, setIsWalletNameVisible] = React.useState(true); @@ -42,39 +40,36 @@ const WalletModal = observer(() => { const contentScrollHandler = React.useCallback( (e: React.UIEvent) => { - if (is_mobile) { + if (is_mobile && is_wallet_modal_visible) { const target = e.target as HTMLDivElement; setIsWalletNameVisible(!(target.scrollTop > 0)); } }, - [is_mobile] + [is_mobile, is_wallet_modal_visible] ); + const is_loading = active_wallet?.loginid !== active_modal_wallet_id || !is_authorize || !active_wallet; + return ( - {wallet?.loginid !== active_modal_wallet_id || !is_authorize ? ( + {is_loading ? ( ) : ( )} diff --git a/packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx b/packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx new file mode 100644 index 000000000000..8c036a66244c --- /dev/null +++ b/packages/appstore/src/components/wallet-jurisdiction-badge/__tests__/wallet-jurisdiction-badge.spec.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import WalletJurisdictionBadge from '../wallet-jurisdiction-badge'; +import { render, screen } from '@testing-library/react'; + +describe('WalletJurisdictionBadge', () => { + it('Should render demo badge', () => { + render(); + + expect(screen.getByText('Demo')).toBeInTheDocument(); + }); + + it('Should render svg badge', () => { + render(); + + expect(screen.getByText('SVG')).toBeInTheDocument(); + }); + + it('Should render malta badge', () => { + render(); + + expect(screen.getByText('MALTA')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-jurisdiction-badge/index.ts b/packages/appstore/src/components/wallet-jurisdiction-badge/index.ts new file mode 100644 index 000000000000..eb311bb13e05 --- /dev/null +++ b/packages/appstore/src/components/wallet-jurisdiction-badge/index.ts @@ -0,0 +1,3 @@ +import WalletJurisdictionBadge from './wallet-jurisdiction-badge'; + +export { WalletJurisdictionBadge }; diff --git a/packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx b/packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx new file mode 100644 index 000000000000..ab2750651c29 --- /dev/null +++ b/packages/appstore/src/components/wallet-jurisdiction-badge/wallet-jurisdiction-badge.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Badge } from '@deriv/components'; +import { localize } from '@deriv/translations'; + +type TWalletJurisdictionBadge = { + is_demo: boolean; + shortcode?: string; +}; + +const WalletJurisdictionBadge = ({ is_demo, shortcode }: TWalletJurisdictionBadge) => { + return is_demo ? ( + + ) : ( + + ); +}; + +export default WalletJurisdictionBadge; diff --git a/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx b/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx index 545e7a894e2c..93dc91976eef 100644 --- a/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx +++ b/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx @@ -1,22 +1,44 @@ import React from 'react'; import WalletTransfer from '../wallet-transfer'; +import { APIProvider } from '@deriv/api'; import { mockStore, StoreProvider } from '@deriv/stores'; import { render, screen } from '@testing-library/react'; +jest.mock('../transfer-account-selector', () => jest.fn(() =>
TransferAccountSelector
)); + jest.mock('@deriv/components', () => ({ ...jest.requireActual('@deriv/components'), AmountInput: () =>
AmountInput
, - TransferAccountSelector: () =>
TransferAccountSelector
, +})); + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(() => ({ data: undefined })), })); describe('WalletTransfer', () => { - const mocked_store = mockStore({}); + const mock = mockStore({ + client: { + loginid: 'CRW1030', + accounts: { + CRW1030: { + token: 'token', + }, + }, + }, + }); it('Should render two amount inputs and two transfer account selectors', () => { render( - - - + + + + + ); expect(screen.getAllByText('AmountInput').length).toBe(2); diff --git a/packages/appstore/src/components/wallet-transfer/mock_accounts/mock_accounts.tsx b/packages/appstore/src/components/wallet-transfer/mock_accounts/mock_accounts.tsx deleted file mode 100644 index a479a95e7501..000000000000 --- a/packages/appstore/src/components/wallet-transfer/mock_accounts/mock_accounts.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { Badge } from '@deriv/components'; - -// TODO: This mock_accounts file should be removed after connecting to API call - -const trading_account_names = [ - 'Deriv Apps Demo', - 'MT5 Derived Demo', - 'MT5 Financial Demo', - 'MT5 Swap-free Demo', - 'Deriv EZ Demo', - 'Deriv X Demo', - 'Deriv cTrader Demo', - 'Deriv EZ Demo', - 'Deriv X Demo', - 'Deriv cTrader Demo', - 'Deriv EZ Demo', - 'Deriv X Demo', - 'Deriv cTrader Demo', - 'Deriv cTrader Demo', - 'Deriv EZ Demo', - 'Deriv X Demo', - 'Deriv cTrader Demo', -]; - -const accounts = trading_account_names.map( - (name, idx) => - ({ - loginid: idx.toString(), - label: name, - currency: 'USD', - gradient_class: 'wallet-card__demo-bg', - balance: '0.00', - wallet_icon: 'IcWalletDerivDemoLight', - icon: 'IcDxtradeDerived', - jurisdiction: , - type: 'demo', - } as const) -); - -const wallets = [ - { - loginid: '18', - label: 'Demo USD Wallet', - currency: 'USD', - gradient_class: 'wallet-card__demo-bg', - balance: '10,000.00', - wallet_icon: 'IcWalletDerivDemoLight', - jurisdiction: , - type: 'demo', - } as const, -]; - -export const transfer_accounts = { accounts, wallets }; diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx new file mode 100644 index 000000000000..550bc129d9e4 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-list.spec.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import TransferAccountList from '../transfer-account-list'; +import { render, screen } from '@testing-library/react'; + +jest.mock('../../wallet-transfer-tile/wallet-transfer-tile', () => jest.fn(() =>
WalletTransferTile
)); + +describe('TransferAccountList', () => { + let mocked_props: React.ComponentProps; + + beforeEach(() => { + mocked_props = { + is_mobile: false, + selected_account: { + account_type: 'wallet', + balance: 100, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: 'CRW1000', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'Wallet Icon', + }, + setIsListModalOpen: jest.fn(), + setSelectedAccount: jest.fn(), + transfer_accounts: { + trading_accounts: { + CR1000: { + account_type: 'trading', + balance: 10, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: '1', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'IcCurrencyUsd', + }, + MTR2000: { + account_type: 'mt5', + balance: 10, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: '2', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'IcCurrencyUsd', + }, + }, + + wallet_accounts: { + CRW1000: { + account_type: 'wallet', + balance: 10000, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + loginid: '3', + shortcode: 'svg', + type: 'fiat', + active_wallet_icon: 'IcCurrencyUsd', + }, + }, + }, + transfer_hint: 'Transfer hint', + wallet_name: 'USD Wallet', + }; + }); + + it('Should render proper titles of transfer accounts', () => { + render(); + + expect(screen.getByText('Trading accounts linked with USD Wallet')).toBeInTheDocument(); + expect(screen.getByText('Wallets')).toBeInTheDocument(); + }); + + it('Should render proper amount of transfer accounts', () => { + render(); + + expect(screen.getAllByText('WalletTransferTile').length).toBe(3); + }); + + it('Should render transfer hint for Wallets account list', () => { + mocked_props.transfer_accounts = { ...mocked_props.transfer_accounts, trading_accounts: {} }; + render(); + + expect(screen.getByText('Transfer hint')).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx new file mode 100644 index 000000000000..c6a8f5395ee6 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import TransferAccountSelector from '../transfer-account-selector'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; + +jest.mock('../transfer-account-list', () => jest.fn(() =>
TransferAccountList
)); +jest.mock('../../wallet-transfer-tile', () => jest.fn(() =>
WalletTransferTile
)); + +describe('TransferAccountSelector', () => { + let modal_root_el: HTMLDivElement, mocked_props: React.ComponentProps; + + beforeAll(() => { + modal_root_el = document.createElement('div'); + modal_root_el.setAttribute('id', 'modal_root'); + document.body.appendChild(modal_root_el); + }); + + beforeEach(() => { + mocked_props = { + is_mobile: false, + is_wallet_name_visible: false, + label: 'Transfer from', + onSelectAccount: jest.fn(), + placeholder: 'Placeholder', + portal_id: 'modal_root', + setIsWalletNameVisible: jest.fn(), + transfer_accounts: { + trading_accounts: {}, + wallet_accounts: {}, + }, + transfer_hint: 'Transfer hint', + value: undefined, + wallet_name: 'USD Wallet', + }; + }); + + it('Should render placeholder, if there is no selected account', () => { + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + expect(screen.getByText('Placeholder')).toBeInTheDocument(); + expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); + }); + + it('Should render WalletTransferTile if the account was selected', () => { + mocked_props.value = { + active_wallet_icon: 'Icon', + display_currency_code: 'USD', + account_type: 'wallet', + balance: 100, + currency: 'USD', + gradient_class: `wallet-card__usd-bg`, + is_demo: false, + loginid: '12345678', + shortcode: 'svg', + type: 'fiat', + icon: 'Wallet Icon', + }; + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + expect(screen.getByText('WalletTransferTile')).toBeInTheDocument(); + expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); + }); + + it('Should render account selector transfer tile with default values default', () => { + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + expect(screen.getByText('Placeholder')).toBeInTheDocument(); + expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); + }); + + it('Should render TransferAccountList when the user is clicking on Transfer selector', () => { + render(); + + const el_transfer_tile = screen.getByTestId('dt_transfer_account_selector'); + userEvent.click(el_transfer_tile); + + expect(screen.getByText('TransferAccountList')).toBeInTheDocument(); + }); + + it('Should render proper label', () => { + render(); + + expect(screen.getByText('Transfer from')).toBeInTheDocument(); + }); +}); diff --git a/packages/components/src/components/transfer-account-selector/index.ts b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/index.ts similarity index 100% rename from packages/components/src/components/transfer-account-selector/index.ts rename to packages/appstore/src/components/wallet-transfer/transfer-account-selector/index.ts diff --git a/packages/components/src/components/transfer-account-selector/transfer-account-list.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx similarity index 60% rename from packages/components/src/components/transfer-account-selector/transfer-account-list.tsx rename to packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx index b8721af349d3..96d0d08ad461 100644 --- a/packages/components/src/components/transfer-account-selector/transfer-account-list.tsx +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-list.tsx @@ -1,16 +1,17 @@ import React from 'react'; import classNames from 'classnames'; -import Text from '../text'; +import { Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; -import { WalletTile } from '../wallet-tile'; -import type { TTransferAccount } from './transfer-account-selector'; +import WalletTransferTile from '../wallet-transfer-tile'; +import type { TTransferAccount } from 'Types'; type TTransferAccountList = { is_mobile?: boolean; + onSelectAccount?: (account: TTransferAccount) => void; selected_account?: TTransferAccount; setIsListModalOpen: (value: boolean) => void; setSelectedAccount: React.Dispatch>; - transfer_accounts: { [k: string]: TTransferAccount[] }; + transfer_accounts: Record<'trading_accounts' | 'wallet_accounts', Record>; transfer_hint?: string | JSX.Element; wallet_name?: string; }; @@ -19,6 +20,7 @@ const TitleLine = () =>
0 + ).length === 1, [transfer_accounts] ); return (
{Object.keys(transfer_accounts).map((key, idx) => { - if (transfer_accounts[key].length === 0) return null; + if (Object.values(transfer_accounts[key as 'trading_accounts' | 'wallet_accounts']).length === 0) + return null; return ( @@ -48,7 +54,7 @@ const TransferAccountList = ({ >
- {key === 'accounts' ? ( + {key === 'trading_accounts' ? (
- {transfer_accounts[key].map((account, index) => ( - { - setSelectedAccount(account); - setIsListModalOpen(false); - }} - /> - ))} + {Object.values(transfer_accounts[key as 'trading_accounts' | 'wallet_accounts']).map( + (account, index) => ( + { + setSelectedAccount(account); + if (account) onSelectAccount?.(account); + setIsListModalOpen(false); + }} + /> + ) + )}
{transfer_hint && ( diff --git a/packages/components/src/components/transfer-account-selector/transfer-account-selector.scss b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss similarity index 97% rename from packages/components/src/components/transfer-account-selector/transfer-account-selector.scss rename to packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss index fd96b6e38c89..acbaf273e617 100644 --- a/packages/components/src/components/transfer-account-selector/transfer-account-selector.scss +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.scss @@ -2,7 +2,7 @@ display: flex; align-items: center; width: 100%; - background-color: $color-white; + background-color: var(--general-main-1); padding: 0.8rem; cursor: pointer; @@ -105,7 +105,7 @@ padding: 0.8rem; } - .wallet-tile__icon { + .wallet-transfer-tile__icon { margin-right: 1.6rem; @include mobile { diff --git a/packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx similarity index 58% rename from packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx rename to packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx index b887134b20e2..b6bed30949af 100644 --- a/packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx +++ b/packages/appstore/src/components/wallet-transfer/transfer-account-selector/transfer-account-selector.tsx @@ -1,14 +1,11 @@ import React from 'react'; -import Modal from '../modal'; -import Div100vhContainer from '../div100vh-container'; -import ThemedScrollbars from '../themed-scrollbars'; +import { Div100vhContainer, Icon, Modal, Text, ThemedScrollbars } from '@deriv/components'; import TransferAccountList from './transfer-account-list'; -import TransferTile from './transfer-tile'; -import { WalletTile } from '../wallet-tile'; +import WalletTransferTile from '../wallet-transfer-tile'; +import { WalletJurisdictionBadge } from 'Components/wallet-jurisdiction-badge'; +import type { TTransferAccount } from 'Types'; import './transfer-account-selector.scss'; -export type TTransferAccount = React.ComponentProps['account']; - type TTransferAccountSelectorProps = { contentScrollHandler?: React.UIEventHandler; is_mobile?: boolean; @@ -18,12 +15,70 @@ type TTransferAccountSelectorProps = { placeholder?: string; portal_id?: string; setIsWalletNameVisible?: (value: boolean) => void; - transfer_accounts: { [k: string]: TTransferAccount[] }; + transfer_accounts: Record<'trading_accounts' | 'wallet_accounts', Record>; transfer_hint?: string | JSX.Element; value?: TTransferAccount; wallet_name?: string; }; +type TAccountSelectorTransferTileProps = { + is_mobile?: boolean; + label?: string; + selected_account?: TTransferAccount; + placeholder?: string; +}; + +const ChevronIcon = () => { + return ( +
+ +
+ ); +}; + +const AccountSelectorTransferTile = ({ + is_mobile, + label, + placeholder, + selected_account, +}: TAccountSelectorTransferTileProps) => { + return ( + +
+
+
+ {label} +
+ + {is_mobile && } +
+ + {selected_account ? ( + + ) : ( + + {placeholder} + + )} +
+ + {!is_mobile && ( + + + + + )} +
+ ); +}; + const TransferAccountSelector = ({ contentScrollHandler, is_mobile, @@ -33,7 +88,7 @@ const TransferAccountSelector = ({ placeholder, portal_id, setIsWalletNameVisible, - transfer_accounts = {}, + transfer_accounts = { trading_accounts: {}, wallet_accounts: {} }, transfer_hint, value, wallet_name, @@ -41,10 +96,6 @@ const TransferAccountSelector = ({ const [is_list_modal_open, setIsListModalOpen] = React.useState(false); const [selected_account, setSelectedAccount] = React.useState(value); - React.useEffect(() => { - if (selected_account) onSelectAccount?.(selected_account); - }, [onSelectAccount, selected_account]); - React.useEffect(() => { setSelectedAccount(value); }, [value]); @@ -65,7 +116,7 @@ const TransferAccountSelector = ({ data-testid='dt_transfer_account_selector' onClick={is_list_modal_open ? undefined : openAccountsList} > - ({ + ...jest.requireActual('@deriv/components'), + AppLinkedWithWalletIcon: jest.fn(() =>
AppLinkedWithWalletIcon
), + WalletIcon: jest.fn(() =>
WalletIcon
), +})); + +describe('WalletTransferTile', () => { + let mocked_props: Required>; + + beforeEach(() => { + mocked_props = { + account: { + account_type: 'trading', + balance: 100, + currency: 'USD', + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'Icon', + is_demo: false, + shortcode: 'svg', + loginid: '12345678', + type: 'fiat', + active_wallet_icon: 'Wallet Icon', + }, + className: 'classname', + has_hover: false, + icon_size: 'small', + is_active: false, + is_list_item: false, + is_mobile: false, + onClick: jest.fn(), + }; + }); + + it('Should render merged icon (App with Wallet)', () => { + render(); + + expect(screen.getByText('AppLinkedWithWalletIcon')).toBeInTheDocument(); + }); + + it('Should render single wallet icon, if there is wallet account type', () => { + mocked_props.account = { ...mocked_props.account, account_type: 'wallet' }; + render(); + + expect(screen.getByText('WalletIcon')).toBeInTheDocument(); + }); + + it('Should render jurisdiction in mobile view', () => { + mocked_props.is_list_item = false; + mocked_props.is_mobile = true; + render(); + + expect(screen.getByText('SVG')).toBeInTheDocument(); + }); + + it('Should render jurisdiction in desktop view', () => { + mocked_props.is_list_item = true; + render(); + + expect(screen.getByText('SVG')).toBeInTheDocument(); + }); + + it('Should render proper account label', () => { + render(); + + expect(screen.getByText('Deriv Apps')).toBeInTheDocument(); + }); + + it('Should render proper account balance', () => { + render(); + + expect(screen.getByText('Balance: 100.00 USD')).toBeInTheDocument(); + }); + + it('Should trigger onClick callback when the user is clicking on Wallet tile', () => { + render(); + + const el_wallet_tile = screen.getByTestId('dt_wallet_transfer_tile'); + userEvent.click(el_wallet_tile); + + expect(mocked_props.onClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts new file mode 100644 index 000000000000..e5851511bba4 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/index.ts @@ -0,0 +1,3 @@ +import WalletTransferTile from './wallet-transfer-tile'; + +export default WalletTransferTile; diff --git a/packages/components/src/components/wallet-tile/wallet-tile.scss b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss similarity index 72% rename from packages/components/src/components/wallet-tile/wallet-tile.scss rename to packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss index 26124f753852..606c6b4f0890 100644 --- a/packages/components/src/components/wallet-tile/wallet-tile.scss +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.scss @@ -1,8 +1,8 @@ -.wallet-tile { +.wallet-transfer-tile { display: flex; align-items: center; justify-content: flex-start; - background-color: $color-white; + background-color: var(--general-main-1); border-radius: $BORDER_RADIUS; flex-grow: 1; @@ -19,12 +19,16 @@ &--hover { &:hover { - background-color: $color-grey-4; + background-color: var(--general-hover); } } &--active { - background-color: $color-grey-5; + background-color: var(--state-active); + } + + &--list-item-background { + background-color: var(--general-main-2); } &__icon { diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx new file mode 100644 index 000000000000..5d02b9b9680e --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer-tile/wallet-transfer-tile.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Text, AppLinkedWithWalletIcon, WalletIcon } from '@deriv/components'; +import { formatMoney } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { getAccountName } from 'Constants/utils'; +import { WalletJurisdictionBadge } from 'Components/wallet-jurisdiction-badge'; +import type { TTransferAccount } from 'Types'; +import './wallet-transfer-tile.scss'; + +type TIconSize = + | React.ComponentProps['size'] + | React.ComponentProps['size']; + +type TWalletTileProps = { + account?: TTransferAccount; + className?: string; + has_hover?: boolean; + icon_size?: TIconSize; + is_active?: boolean; + is_list_item?: boolean; + is_mobile?: boolean; + onClick?: () => void; +}; + +const IconComponent = ({ account, icon_size }: TWalletTileProps) => { + if (account?.account_type === 'wallet') { + return account?.icon ? ( + ['size']} + type={account?.type} + /> + ) : null; + } + + return account?.icon && account?.active_wallet_icon ? ( + ['size']} + type={account?.type} + wallet_icon={account?.active_wallet_icon} + /> + ) : null; +}; + +const Balance = ({ account, is_list_item, is_mobile }: TWalletTileProps) => { + if (account?.balance !== undefined) { + let size; + if (is_list_item) size = is_mobile ? 'xxxs' : 'xxs'; + else size = is_mobile ? 'xxxxs' : 'xxxs'; + + return ( + + {localize('Balance')}: {formatMoney(account?.currency || '', account.balance, true)}{' '} + {account.display_currency_code} + + ); + } + + return null; +}; + +const Label = ({ account, is_list_item, is_mobile }: TWalletTileProps) => { + let size; + if (is_list_item) size = is_mobile ? 'xxs' : 'xs'; + else size = is_mobile ? 'xxxxs' : 'xxxs'; + + return ( + + {getAccountName({ ...account })} + + ); +}; + +const WalletTransferTile = ({ + account, + className, + has_hover, + icon_size = 'small', + is_active, + is_list_item, + is_mobile, + onClick, +}: TWalletTileProps) => { + return ( +
onClick?.()} + > +
+
+ +
+ + {!is_list_item && is_mobile && ( + + )} +
+ +
+
+ + {is_list_item && ( + + )} +
+ ); +}; + +export default React.memo(WalletTransferTile); diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss b/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss index 26ae40f30594..120976f2dc40 100644 --- a/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss @@ -12,17 +12,16 @@ &__tiles-container { display: flex; flex-direction: column; - row-gap: 3.2rem; } &__divider { - border: 0.5px solid $color-grey-5; + border: 0.5px solid var(--border-normal); margin: 0.8rem 0; } &__tile { display: flex; - border: 1px solid $color-grey-5; + border: 1px solid var(--border-normal); border-radius: $BORDER_RADIUS; .amount-input-wrapper { diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx b/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx index 3d454dcaa531..08f8c964ad1e 100644 --- a/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx @@ -1,15 +1,16 @@ -import React from 'react'; -import { Field, FieldProps, Formik, Form } from 'formik'; -import { AmountInput, TransferAccountSelector, Button } from '@deriv/components'; -import { getDecimalPlaces } from '@deriv/shared'; -import { useStore, observer } from '@deriv/stores'; +import React, { useEffect } from 'react'; +import classNames from 'classnames'; +import { Field, FieldProps, Formik, Form, FormikHelpers } from 'formik'; +import { AmountInput, Button, Loading, MessageList } from '@deriv/components'; +import { useCurrencyConfig, useWalletTransfer } from '@deriv/hooks'; +import { validNumber } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import { localize, Localize } from '@deriv/translations'; -// TODO: 'transfer_accounts' should be replaced after connecting to API call -import { transfer_accounts } from './mock_accounts/mock_accounts'; +import TransferAccountSelector from './transfer-account-selector'; +import { getAccountName } from 'Constants/utils'; +import type { TMessageItem } from 'Types'; import './wallet-transfer.scss'; -type TAccount = React.ComponentProps['value']; - type TWalletTransferProps = { contentScrollHandler: React.UIEventHandler; is_wallet_name_visible: boolean; @@ -18,172 +19,269 @@ type TWalletTransferProps = { const Divider = () =>
; -const WalletTransfer = observer( - ({ contentScrollHandler, is_wallet_name_visible, setIsWalletNameVisible }: TWalletTransferProps) => { - const { ui } = useStore(); - const { is_mobile } = ui; +const initial_demo_balance = 10000.0; + +const ERROR_CODES = { + is_demo: { + between_min_max: 'BetweenMinMax', + insufficient_fund: 'InsufficientFund', + }, +}; + +const WalletTransfer = observer(({ is_wallet_name_visible, setIsWalletNameVisible }: TWalletTransferProps) => { + const { client, ui, traders_hub } = useStore(); + const { setWalletModalActiveTab } = traders_hub; + const { is_switching } = client; + const { is_mobile } = ui; + + const { getConfig } = useCurrencyConfig(); + + const { + active_wallet, + is_accounts_loading, + from_account, + to_account, + to_account_list, + transfer_accounts, + setFromAccount, + setToAccount, + } = useWalletTransfer(); + + useEffect(() => { + if (!from_account?.loginid) { + setFromAccount(active_wallet); + } + }, [active_wallet, from_account, setFromAccount]); + + const portal_id = is_mobile ? 'mobile_list_modal_root' : 'modal_root'; + + const is_amount_to_input_disabled = !to_account; + + const active_wallet_name = getAccountName({ ...active_wallet }); + + const transfer_to_hint = React.useMemo(() => { + return to_account?.loginid === active_wallet?.loginid ? ( + + ) : ( + '' + ); + }, [active_wallet?.loginid, active_wallet_name, from_account, to_account?.loginid]); + + const [message_list, setMessageList] = React.useState([]); + + const clearErrorMessages = React.useCallback( + () => setMessageList(list => list.filter(el => el.type !== 'error')), + [] + ); - const [from_account, setFromAccount] = React.useState(transfer_accounts.wallets[0]); - const [to_account, setToAccount] = React.useState(); + const validateAmount = (amount: number) => { + clearErrorMessages(); - const portal_id = is_mobile ? 'mobile_list_modal_root' : 'modal_root'; + if (!amount || is_amount_to_input_disabled) return; - const to_account_list = React.useMemo(() => { - // TODO: 'Demo USD Wallet' should be replaced to the current open wallet after connecting to API call - if (from_account?.label === 'Demo USD Wallet') { + if (active_wallet?.is_demo) { + const { is_ok, message } = validNumber(amount.toString(), { + type: 'float', + decimals: getConfig(from_account?.currency || '')?.fractional_digits, + min: 1, + max: from_account?.balance, + }); + + const should_reset_balance = + active_wallet?.balance !== undefined && + amount > active_wallet?.balance && + active_wallet?.balance < initial_demo_balance; + + if (from_account?.loginid === active_wallet.loginid && should_reset_balance) { + setMessageList(list => { + if (list.some(el => el.key === ERROR_CODES.is_demo.insufficient_fund)) return list; + return [ + ...list, + { + variant: 'with-action-button', + key: ERROR_CODES.is_demo.insufficient_fund, + button_label: localize('Reset balance'), + onClickHandler: () => setWalletModalActiveTab('Deposit'), + message: localize( + 'You have insufficient fund in the selected wallet, please reset your virtual balance' + ), + type: 'error', + }, + ]; + }); + } else if (!is_ok) { + //else if not wallet loginid and not is_ok message + setMessageList(list => { + if (list.some(el => el.key === ERROR_CODES.is_demo.between_min_max)) return list; + return [ + ...list, + { + variant: 'base', + key: ERROR_CODES.is_demo.between_min_max, + message: `${message} ${from_account?.display_currency_code}` || '', + type: 'error', + }, + ]; + }); + } + } + }; + + const onSelectFromAccount = React.useCallback( + ( + account: typeof from_account, + resetForm: FormikHelpers<{ + to_amount: number; + from_amount: number; + }>['resetForm'] + ) => { + if (account?.loginid === from_account?.loginid) return; + setFromAccount(account); + if (account?.loginid === active_wallet?.loginid) { setToAccount(undefined); - return { accounts: transfer_accounts.accounts, wallets: [] }; + } else { + setToAccount(active_wallet); } - setToAccount(transfer_accounts.wallets[0]); - return { wallets: transfer_accounts.wallets, accounts: [] }; - }, [from_account?.label]); - - const transfer_to_hint = React.useMemo(() => { - // TODO: 'Demo USD Wallet' should be replaced to the current open wallet after connecting to API call - return to_account?.label === 'Demo USD Wallet' ? ( - - ) : ( - '' - ); - }, [from_account?.label, to_account?.label]); - - const is_amount_to_input_disabled = !to_account; - - return ( -
- undefined} - validateOnBlur={false} - > - {({ setFieldValue, values }) => { - const onSelectToAccount = React.useCallback( - (account: TAccount) => { - setToAccount(account); - setFieldValue('to_amount', values.from_amount); - }, - [setFieldValue, values.from_amount] - ); - - const onSelectFromAccount = React.useCallback( - (account: TAccount) => { - setFromAccount(account); - if (account?.label === 'Demo USD Wallet') { - setFieldValue('to_amount', 0); - } - }, - [setFieldValue] - ); - - return ( -
-
-
- - {({ field }: FieldProps) => ( - { - setFieldValue('from_amount', value); - if (!is_amount_to_input_disabled) { - setFieldValue('to_amount', value); - } - }} - /> - )} - - - ['resetForm'] + ) => { + if (account?.loginid === to_account?.loginid) return; + setToAccount(account); + clearErrorMessages(); + resetForm(); + }, + [clearErrorMessages, setToAccount, to_account?.loginid] + ); + + if (is_accounts_loading || is_switching) { + return ; + } + + return ( +
+ undefined} + validateOnBlur={false} + > + {({ setValues, values, resetForm }) => ( + +
+
0, + })} + > + + {({ field }: FieldProps) => ( + el.type === 'error')} + initial_value={field.value} + label={localize('Amount you send')} + onChange={(value: number) => { + setValues({ + from_amount: value, + to_amount: is_amount_to_input_disabled ? 0 : value, + }); + }} /> -
-
- - {({ field }: FieldProps) => ( - { - setFieldValue('from_amount', value); - setFieldValue('to_amount', value); - }} - /> - )} - - - + + onSelectFromAccount(account, resetForm)} + placeholder={localize('Select a trading account or a Wallet')} + portal_id={portal_id} + setIsWalletNameVisible={setIsWalletNameVisible} + transfer_accounts={transfer_accounts} + wallet_name={active_wallet_name} + value={from_account} + /> +
+ +
+ + {({ field }: FieldProps) => ( + el.type === 'error')} + initial_value={field.value} + label={localize('Amount you receive')} + onChange={(value: number) => { + setValues({ from_amount: value, to_amount: value }); + }} /> -
-
-
- -
- - ); - }} -
-
- ); - } -); + )} + + + onSelectToAccount(account, resetForm)} + placeholder={!to_account ? localize('Select a trading account or a Wallet') : ''} + portal_id={portal_id} + setIsWalletNameVisible={setIsWalletNameVisible} + transfer_accounts={to_account_list} + transfer_hint={transfer_to_hint} + wallet_name={active_wallet_name} + value={to_account} + /> +
+
+
+ +
+ + )} +
+
+ ); +}); export default WalletTransfer; diff --git a/packages/appstore/src/types/common.types.ts b/packages/appstore/src/types/common.types.ts index c7bbea0c231a..a2fd80ffd0ef 100644 --- a/packages/appstore/src/types/common.types.ts +++ b/packages/appstore/src/types/common.types.ts @@ -188,24 +188,41 @@ export type TLinkedTo = { export type TWalletAccount = NonNullable['data']>[number]; export type TWalletInfo = NonNullable['data']>[number]; -export type TWalletButton = { - name: Parameters['traders_hub']['setWalletModalActiveTab']>[0]; - text: string; - icon: string; - action: () => void; -}; export type TTransferAccount = { - active_wallet_icon?: string; + active_wallet_icon: string | undefined; account_type?: 'wallet' | 'trading' | 'dxtrade' | 'mt5' | 'derivez' | 'binary'; - balance?: number; + balance: number; currency?: string; - display_currency_code?: string; + display_currency_code: string | undefined; gradient_class?: `wallet-card__${string}`; icon?: string | undefined; - is_demo?: boolean; + is_demo: boolean; loginid?: string; mt5_market_type?: 'all' | 'financial' | 'synthetic'; - shortcode?: string; - type?: 'fiat' | 'crypto' | 'demo'; + shortcode: string | undefined; + type: 'fiat' | 'crypto' | 'demo'; +}; + +export type TMessageItem = + | { + variant: 'base'; + key: string; + type: 'info' | 'error' | 'success'; + message: string | JSX.Element; + } + | { + variant: 'with-action-button'; + onClickHandler: VoidFunction; + button_label: string; + key: string; + type: 'info' | 'error' | 'success'; + message: string | JSX.Element; + }; + +export type TWalletButton = { + name: Parameters['traders_hub']['setWalletModalActiveTab']>[0]; + text: string; + icon: string; + action: () => void; }; diff --git a/packages/components/src/components/amount-input/amount-input.scss b/packages/components/src/components/amount-input/amount-input.scss index 65d3bdb66108..0a4b9f2f84a8 100644 --- a/packages/components/src/components/amount-input/amount-input.scss +++ b/packages/components/src/components/amount-input/amount-input.scss @@ -9,6 +9,23 @@ position: relative; height: 2.8rem; + .dc-input__field { + color: var(--text-general); + } + + &--error { + .dc-input__field { + color: var(--text-loss-danger); + -webkit-text-fill-color: var(--text-loss-danger); + } + } + + &--disabled { + .dc-input__field { + color: var(--text-disabled-1); + } + } + @include mobile { height: 2.4rem; } diff --git a/packages/components/src/components/amount-input/amount-input.tsx b/packages/components/src/components/amount-input/amount-input.tsx index 9dad8483517e..95f1b21814e3 100644 --- a/packages/components/src/components/amount-input/amount-input.tsx +++ b/packages/components/src/components/amount-input/amount-input.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; +import classNames from 'classnames'; import { isMobile } from '@deriv/shared'; import Input from '../input'; import Text from '../text'; @@ -7,6 +8,7 @@ type TAmountInput = { currency: string; decimal_places?: number; disabled?: boolean; + has_error?: boolean; initial_value?: number; label?: string; locale?: Intl.LocalesArgument; @@ -18,6 +20,7 @@ const AmountInput = ({ currency, decimal_places = 2, disabled = false, + has_error, initial_value = 0, label, locale, @@ -110,7 +113,12 @@ const AmountInput = ({ return (
{label} -
+
\ No newline at end of file diff --git a/packages/components/src/components/icon/wallet/ic-wallet-info-message-with-three-dots.svg b/packages/components/src/components/icon/wallet/ic-wallet-info-message-with-three-dots.svg new file mode 100644 index 000000000000..48023c82e0e7 --- /dev/null +++ b/packages/components/src/components/icon/wallet/ic-wallet-info-message-with-three-dots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/wallet/ic-wallet-success-message.svg b/packages/components/src/components/icon/wallet/ic-wallet-success-message.svg new file mode 100644 index 000000000000..03491c4831f3 --- /dev/null +++ b/packages/components/src/components/icon/wallet/ic-wallet-success-message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/message-list/__tests__/alert-message.spec.tsx b/packages/components/src/components/message-list/__tests__/alert-message.spec.tsx new file mode 100644 index 000000000000..e8ef334dcd8d --- /dev/null +++ b/packages/components/src/components/message-list/__tests__/alert-message.spec.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import AlertMessage from '../alert-message'; + +describe('AlertMessage', () => { + it('Should render proper icon type', () => { + const { rerender } = render(); + + expect(screen.getByTestId('dt_IcWalletErrorMessageWithCross')).toBeInTheDocument(); + + rerender(); + + expect(screen.getByTestId('dt_IcWalletInfoMessageWithThreeDots')).toBeInTheDocument(); + + rerender(); + + expect(screen.getByTestId('dt_IcWalletSuccessMessage')).toBeInTheDocument(); + }); + + it('Should render proper message', () => { + render(); + + expect(screen.getByText('Error message')).toBeInTheDocument(); + }); + + it('Should render proper button', () => { + render( + + ); + + expect(screen.getByRole('button', { name: 'Error button' })).toBeInTheDocument(); + }); + + it('Should trigger onClick handler when the user is clicking on the button', () => { + const onClickHandler = jest.fn(); + + render( + + ); + + const el_btn = screen.getByRole('button', { name: 'Error button' }); + userEvent.click(el_btn); + + expect(onClickHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/components/src/components/message-list/__tests__/message-list.spec.tsx b/packages/components/src/components/message-list/__tests__/message-list.spec.tsx new file mode 100644 index 000000000000..80a591e753fa --- /dev/null +++ b/packages/components/src/components/message-list/__tests__/message-list.spec.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import MessageList from '../message-list'; + +describe('MessageList', () => { + it('Should render the list of messages', () => { + const list: React.ComponentProps['list'] = [ + { variant: 'base', key: '1', message: 'Success message', type: 'success' }, + { variant: 'base', key: '2', message: 'Error message', type: 'error' }, + { variant: 'base', key: '3', message: 'Info message', type: 'info' }, + ]; + + render(); + + expect(screen.getAllByTestId('dt_alert_message').length).toBe(3); + }); +}); diff --git a/packages/components/src/components/message-list/alert-message.tsx b/packages/components/src/components/message-list/alert-message.tsx new file mode 100644 index 000000000000..151df49a1607 --- /dev/null +++ b/packages/components/src/components/message-list/alert-message.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { isMobile } from '@deriv/shared'; +import Button from '../button'; +import Icon from '../icon'; +import Text from '../text'; + +const type_mapper = { + info: { + icon: 'IcWalletInfoMessageWithThreeDots', + color: 'status-info-blue', + }, + error: { + icon: 'IcWalletErrorMessageWithCross', + color: 'loss-danger', + }, + success: { + icon: 'IcWalletSuccessMessage', + color: 'profit-success', + }, +}; + +type TAlertMessageProps = + | { + variant: 'with-action-button'; + button_label: string; + message: string | JSX.Element; + onClickHandler: VoidFunction; + type: 'info' | 'error' | 'success'; + } + | { + variant: 'base'; + message: string | JSX.Element; + type: 'info' | 'error' | 'success'; + }; + +const AlertMessage = (props: TAlertMessageProps) => { + const { type, message, variant } = props; + + const icon = type_mapper[type].icon; + const color = type_mapper[type].color; + + return ( +
+
+
+
+ +
+
+
+ + {message} + +
+ {variant === 'with-action-button' && props.button_label && ( +
+ +
+ )} +
+ ); +}; + +export default AlertMessage; diff --git a/packages/components/src/components/message-list/index.ts b/packages/components/src/components/message-list/index.ts new file mode 100644 index 000000000000..bd4c491a159a --- /dev/null +++ b/packages/components/src/components/message-list/index.ts @@ -0,0 +1 @@ +export { default as MessageList } from './message-list'; diff --git a/packages/components/src/components/message-list/message-list.scss b/packages/components/src/components/message-list/message-list.scss new file mode 100644 index 000000000000..f5954f4ba4cd --- /dev/null +++ b/packages/components/src/components/message-list/message-list.scss @@ -0,0 +1,53 @@ +.message-list { + width: 100%; + max-width: 63.4rem; + margin: 0 auto; + min-height: 3.2rem; + + .alert-message { + display: flex; + flex-direction: row; + column-gap: 0.8rem; + justify-content: flex-start; + min-height: 3.2rem; + + @include mobile { + margin: 0; + } + + &__icon-container { + position: relative; + + .icon-container { + &__line { + border: 1px solid var(--border-normal); + position: absolute; + height: 100%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &__icon { + display: flex; + position: relative; + margin-top: 0.8rem; + } + } + } + + &__message-container, + &__button-container { + display: flex; + align-items: center; + } + + &__message-container { + padding: 0.7rem 0; + } + + &__button-container { + margin-left: auto; + } + } +} diff --git a/packages/components/src/components/message-list/message-list.tsx b/packages/components/src/components/message-list/message-list.tsx new file mode 100644 index 000000000000..705fdba43438 --- /dev/null +++ b/packages/components/src/components/message-list/message-list.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import AlertMessage from './alert-message'; +import './message-list.scss'; + +type TMessageListProps = { list: (React.ComponentProps & { key: string })[] }; + +const animations = { + initial: { + height: 0, + opacity: 0, + }, + animate: { + height: 'auto', + opacity: 1, + transition: { + height: { + duration: 0.2, + }, + opacity: { + duration: 0.15, + delay: 0.05, + }, + }, + }, + exit: { + height: 0, + opacity: 0, + transition: { + height: { + duration: 0.2, + }, + opacity: { duration: 0.1 }, + }, + }, +}; + +const MessageList = ({ list }: TMessageListProps) => { + return ( +
+ + {list.map(item => { + return ( + + + + ); + })} + +
+ ); +}; + +export default MessageList; diff --git a/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-list.spec.tsx b/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-list.spec.tsx deleted file mode 100644 index 82cdef92052f..000000000000 --- a/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-list.spec.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import Badge from '../../badge'; -import TransferAccountList from '../transfer-account-list'; -import { render, screen } from '@testing-library/react'; - -jest.mock('../../wallet-tile/wallet-tile', () => jest.fn(() =>
Wallet Tile
)); - -describe('TransferAccountList', () => { - let mocked_props: React.ComponentProps; - - beforeEach(() => { - mocked_props = { - is_mobile: false, - selected_account: { - balance: '100', - currency: 'USD', - icon: 'Icon', - jurisdiction: , - label: 'Account Label', - loginid: '12345678', - type: 'fiat', - wallet_icon: 'Wallet Icon', - wallet_name: 'USD Wallet', - }, - setIsListModalOpen: jest.fn(), - setSelectedAccount: jest.fn(), - transfer_accounts: { - accounts: [ - { - loginid: '1', - label: 'Deriv Apps', - currency: 'USD', - balance: '10.00', - wallet_icon: 'IcCurrencyUsd', - icon: 'IcDerivApps', - jurisdiction: , - type: 'fiat', - }, - { - loginid: '2', - label: 'MT5 Derived', - currency: 'USD', - balance: '10.00', - wallet_icon: 'IcCurrencyUsd', - icon: 'IcMT5Derived', - jurisdiction: , - type: 'fiat', - }, - ], - wallets: [ - { - loginid: '3', - label: 'USD Wallet', - currency: 'USD', - balance: '10,000.00', - wallet_icon: 'IcCurrencyUsd', - jurisdiction: , - type: 'fiat', - }, - ], - }, - transfer_hint: 'Transfer hint', - wallet_name: 'USD Wallet', - }; - }); - - it('Should render proper titles of transfer accounts', () => { - render(); - - expect(screen.getByText('Trading accounts linked with USD Wallet')).toBeInTheDocument(); - expect(screen.getByText('Wallets')).toBeInTheDocument(); - }); - - it('Should render proper amount of transfer accounts', () => { - render(); - - expect(screen.getAllByText('Wallet Tile').length).toBe(3); - }); - - it('Should render transfer hint for Wallets account list', () => { - mocked_props.transfer_accounts = { ...mocked_props.transfer_accounts, accounts: [] }; - render(); - - expect(screen.getByText('Transfer hint')).toBeInTheDocument(); - }); -}); diff --git a/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx b/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx deleted file mode 100644 index b655830e02ee..000000000000 --- a/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import TransferAccountSelector from '../transfer-account-selector'; -import userEvent from '@testing-library/user-event'; -import { render, screen } from '@testing-library/react'; - -jest.mock('../../transfer-account-selector/transfer-tile', () => jest.fn(() =>
Transfer Tile
)); -jest.mock('../../transfer-account-selector/transfer-account-list', () => - jest.fn(() =>
Transfer Account List
) -); - -describe('TransferAccountSelector', () => { - let modal_root_el: HTMLDivElement, mocked_props: React.ComponentProps; - - beforeAll(() => { - modal_root_el = document.createElement('div'); - modal_root_el.setAttribute('id', 'modal_root'); - document.body.appendChild(modal_root_el); - }); - - beforeEach(() => { - mocked_props = { - is_mobile: false, - is_wallet_name_visible: false, - label: 'Transfer from', - onSelectAccount: jest.fn(), - placeholder: 'Placeholder', - portal_id: 'modal_root', - setIsWalletNameVisible: jest.fn(), - transfer_accounts: { - accounts: [], - wallets: [], - }, - transfer_hint: 'Transfer hint', - value: undefined, - wallet_name: 'USD Wallet', - }; - }); - - it('Should render transfer tile by default', () => { - render(); - - expect(screen.getByText('Transfer Tile')).toBeInTheDocument(); - }); - - it('Should render TransferAccountList when the user is clicking on Transfer selector', () => { - render(); - - const el_transfer_tile = screen.getByTestId('dt_transfer_account_selector'); - userEvent.click(el_transfer_tile); - - expect(screen.getByText('Transfer Account List')).toBeInTheDocument(); - }); -}); diff --git a/packages/components/src/components/transfer-account-selector/__tests__/transfer-tile.spec.tsx b/packages/components/src/components/transfer-account-selector/__tests__/transfer-tile.spec.tsx deleted file mode 100644 index 7e5cb2ae2281..000000000000 --- a/packages/components/src/components/transfer-account-selector/__tests__/transfer-tile.spec.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import Badge from '../../badge'; -import TransferTile from '../transfer-tile'; -import { render, screen } from '@testing-library/react'; - -jest.mock('../../wallet-tile/wallet-tile', () => jest.fn(() =>
Wallet Tile
)); - -describe('TransferTile', () => { - let mocked_props: React.ComponentProps; - - beforeEach(() => { - mocked_props = { - is_mobile: false, - label: 'Transfer from', - selected_account: { - balance: '100', - currency: 'USD', - icon: 'IconSrc', - jurisdiction: , - label: 'Account Label', - loginid: '12345678', - type: 'fiat', - wallet_icon: 'Wallet Icon', - wallet_name: 'USD Wallet', - }, - placeholder: 'Placeholder', - }; - }); - - it('Should render proper label', () => { - render(); - - expect(screen.getByText('Transfer from')).toBeInTheDocument(); - }); - - it('Should render wallet tile if selected account exists', () => { - render(); - - expect(screen.getByText('Wallet Tile')).toBeInTheDocument(); - }); - - it("Should render placeholder if selected account doesn't exist", () => { - mocked_props.selected_account = undefined; - render(); - - expect(screen.getByText('Placeholder')).toBeInTheDocument(); - }); - - it('Should render proper jurisdiction if selected account exists', () => { - render(); - - expect(screen.getByText('SVG')).toBeInTheDocument(); - }); - - it('Should render chevron icon', () => { - render(); - - expect(screen.getByTestId('dt_chevron_icon')).toBeInTheDocument(); - }); -}); diff --git a/packages/components/src/components/transfer-account-selector/transfer-tile.tsx b/packages/components/src/components/transfer-account-selector/transfer-tile.tsx deleted file mode 100644 index 8f8a4f89bdbf..000000000000 --- a/packages/components/src/components/transfer-account-selector/transfer-tile.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import Icon from '../icon'; -import Text from '../text'; -import { WalletTile } from '../wallet-tile'; -import type { TTransferAccount } from './transfer-account-selector'; - -type TTransferTile = { - is_mobile?: boolean; - label?: string; - selected_account?: TTransferAccount; - placeholder?: string; -}; - -const ChevronIcon = () => { - return ( -
- -
- ); -}; - -const TransferTile = ({ is_mobile, label, placeholder, selected_account }: TTransferTile) => { - return ( - -
-
-
- {label} -
- - {is_mobile && } -
- - {selected_account ? ( - - ) : ( - - {placeholder} - - )} -
- - {!is_mobile && ( - - {selected_account?.jurisdiction} - - - )} -
- ); -}; - -export default React.memo(TransferTile); diff --git a/packages/components/src/components/wallet-icon/wallet-icon.tsx b/packages/components/src/components/wallet-icon/wallet-icon.tsx index 0cd618c6af04..37616338e99c 100644 --- a/packages/components/src/components/wallet-icon/wallet-icon.tsx +++ b/packages/components/src/components/wallet-icon/wallet-icon.tsx @@ -21,6 +21,7 @@ const sizes = { medium: 24, large: 32, xlarge: 48, + xxlarge: 64, }, // The crypto and demo sizes are the same box: { @@ -44,6 +45,10 @@ const sizes = { width: 96, height: 60, }, + xxlarge: { + width: 120, + height: 80, + }, }, } as const; @@ -59,6 +64,7 @@ const WalletIcon = ({ gradient_class, icon, size = 'medium', type, has_bg, hide_ (!!gradient_class && type !== 'app') || has_bg, 'wallet-card--hide-watermark': hide_watermark, })} + data-testid='dt_wallet_icon' > {(type === 'fiat' || type === 'app') && } {(type === 'demo' || type === 'crypto') && ( diff --git a/packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx b/packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx deleted file mode 100644 index 1dd579a9e04f..000000000000 --- a/packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import Badge from '../../badge'; -import WalletTile from '../wallet-tile'; -import userEvent from '@testing-library/user-event'; -import { render, screen } from '@testing-library/react'; - -jest.mock('../../app-linked-with-wallet-icon/app-linked-with-wallet-icon', () => - jest.fn(() =>
AppLinkedWithWalletIcon
) -); -jest.mock('../../wallet-icon/wallet-icon', () => jest.fn(() =>
WalletIcon
)); - -describe('WalletTile', () => { - let mocked_props: React.ComponentProps; - - beforeEach(() => { - mocked_props = { - account: { - balance: '100', - currency: 'USD', - icon: 'Icon', - jurisdiction: , - label: 'Account Label', - loginid: '12345678', - type: 'fiat', - wallet_icon: 'Wallet Icon', - wallet_name: 'USD Wallet', - }, - className: 'classname', - has_hover: false, - icon_size: 'small', - is_active: false, - is_mobile: false, - is_value: false, - onClick: jest.fn(), - }; - }); - - it('Should render merged icon (App with Wallet)', () => { - render(); - - expect(screen.getByText('AppLinkedWithWalletIcon')).toBeInTheDocument(); - }); - - it('Should render single wallet icon, if there is no app icon', () => { - mocked_props.account = { ...mocked_props.account, icon: '' }; - render(); - - expect(screen.getByText('WalletIcon')).toBeInTheDocument(); - }); - - it('Should render jurisdiction in mobile view', () => { - mocked_props.is_value = true; - mocked_props.is_mobile = true; - render(); - - expect(screen.getByText('SVG')).toBeInTheDocument(); - }); - - it('Should render jurisdiction in desktop view', () => { - mocked_props.is_value = false; - render(); - - expect(screen.getByText('SVG')).toBeInTheDocument(); - }); - - it('Should render proper account label', () => { - render(); - - expect(screen.getByText('Account Label')).toBeInTheDocument(); - }); - - it('Should render proper account balance', () => { - render(); - - expect(screen.getByText('Balance: 100 USD')).toBeInTheDocument(); - }); - - it('Should trigger onClick callback when the user is clicking on Wallet tile', () => { - render(); - - const el_wallet_tile = screen.getByTestId('dt_wallet_tile'); - userEvent.click(el_wallet_tile); - - expect(mocked_props.onClick).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/components/src/components/wallet-tile/index.ts b/packages/components/src/components/wallet-tile/index.ts deleted file mode 100644 index 5838b63b8187..000000000000 --- a/packages/components/src/components/wallet-tile/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import WalletTile from './wallet-tile'; - -export { WalletTile }; diff --git a/packages/components/src/components/wallet-tile/wallet-tile.tsx b/packages/components/src/components/wallet-tile/wallet-tile.tsx deleted file mode 100644 index 813d80869989..000000000000 --- a/packages/components/src/components/wallet-tile/wallet-tile.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import Text from '../text'; -import { getCurrencyDisplayCode } from '@deriv/shared'; -import { localize } from '@deriv/translations'; -import { AppLinkedWithWalletIcon } from '../app-linked-with-wallet-icon'; -import { WalletIcon } from '../wallet-icon'; -import './wallet-tile.scss'; - -type TAccount = { - balance?: string; - currency: string; - gradient_class: string; - icon?: string; - jurisdiction?: JSX.Element; - label?: string; - loginid: string; - type: 'fiat' | 'crypto'; - wallet_icon?: string; - wallet_name?: string; -}; - -type TIconSize = - | React.ComponentProps['size'] - | React.ComponentProps['size']; - -type TWalletTileProps = { - account: TAccount; - className?: string; - has_hover?: boolean; - icon_size?: TIconSize; - is_active?: boolean; - is_mobile?: boolean; - is_value?: boolean; - onClick?: () => void; -}; - -const WalletTile = ({ - account, - className, - has_hover, - icon_size = 'small', - is_active, - is_mobile, - is_value, - onClick, -}: TWalletTileProps) => { - const IconComponent = () => { - if (account.icon && account.wallet_icon) { - return ( - ['size']} - type={account.type} - wallet_icon={account.wallet_icon} - /> - ); - } else if (account.wallet_icon) { - return ( - ['size']} - type={account.type} - /> - ); - } - - return null; - }; - - const Label = () => { - if (account.label) { - let size; - if (is_value) size = is_mobile ? 'xxxxs' : 'xxxs'; - else size = is_mobile ? 'xxs' : 'xs'; - - return ( - - {account.label} - - ); - } - - return null; - }; - - const Balance = () => { - if (account.balance) { - let size; - if (is_value) size = is_mobile ? 'xxxxs' : 'xxxs'; - else size = is_mobile ? 'xxxs' : 'xxs'; - - return ( - - {localize('Balance')}: {account.balance} {getCurrencyDisplayCode(account.currency)} - - ); - } - - return null; - }; - - return ( -
onClick?.()} - > -
-
- -
- - {is_value && is_mobile && account.jurisdiction} -
- -
-
- - {!is_value && account.jurisdiction} -
- ); -}; - -export default React.memo(WalletTile); diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 0ae2fba6b5bd..cae57cc29e8e 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -108,7 +108,7 @@ export { default as Text } from './components/text'; export { default as Toast } from './components/toast'; export { default as ThemedScrollbars } from './components/themed-scrollbars'; export { default as ToggleSwitch } from './components/toggle-switch'; -export { default as TransferAccountSelector } from './components/transfer-account-selector'; +export * from './components/message-list'; export { default as TickPicker } from './components/tick-picker'; export { default as TickProgress } from './components/tick-progress'; export { default as Timeline } from './components/timeline'; @@ -119,7 +119,5 @@ export { default as VerticalTab } from './components/vertical-tab'; export { default as Wizard } from './components/wizard'; export * from './components/wallet-card'; export * from './components/wallet-icon'; -export * from './components/wallet-tile'; export * from './components/app-linked-with-wallet-icon'; -export * from './components/transfer-account-selector'; export * from './hooks'; diff --git a/packages/components/stories/icon/icons.js b/packages/components/stories/icon/icons.js index 44fc0b2c497e..8875e4b71248 100644 --- a/packages/components/stories/icon/icons.js +++ b/packages/components/stories/icon/icons.js @@ -57,7 +57,7 @@ export const icons = 'IcAppstoreWalletUsdcLight', 'IcAppstoreWalletUsdtLight', 'IcAppstoreWalletsLink', - 'IcAppstoreWarning' + 'IcAppstoreWarning', ], 'brand': [ 'IcBrandDerivEzWordmark', @@ -67,7 +67,7 @@ export const icons = 'IcBrandDmt5FinancialStp', 'IcBrandDmt5Financial', 'IcBrandDmt5Synthetics', - 'IcBrandDxtradeWordmark' + 'IcBrandDxtradeWordmark', ], 'cashier': [ 'IcCashierAdd', @@ -243,7 +243,7 @@ export const icons = 'IcCashierWyreLight', 'IcCashierZenithbankDark', 'IcCashierZenithbankLight', - 'IcCashier' + 'IcCashier', ], 'common': [ 'IcAccountCross', @@ -618,7 +618,7 @@ export const icons = 'IcWithdrawRequestVerification', 'IcZingpay', 'IcZoomIn', - 'IcZoomOut' + 'IcZoomOut', ], 'contract': [ 'IcContractBarrier', @@ -636,7 +636,7 @@ export const icons = 'IcContractStartTimeCircle', 'IcContractStartTime', 'IcContractStrike', - 'IcContractTarget' + 'IcContractTarget', ], 'currency': [ 'IcCurrencyAud', @@ -660,10 +660,10 @@ export const icons = 'IcCurrencyUsdc', 'IcCurrencyUsdk', 'IcCurrencyUst', - 'IcCurrencyVirtual' + 'IcCurrencyVirtual', ], 'derivez': [ - 'IcDerivez' + 'IcDerivez', ], 'dxtrade': [ 'IcDxtradeDerivX', @@ -680,7 +680,7 @@ export const icons = 'IcDxtradeFinancialPlatform', 'IcDxtradeFinancial', 'IcDxtradeOnePassword', - 'IcDxtradeSyntheticPlatform' + 'IcDxtradeSyntheticPlatform', ], 'flag': [ 'IcFlagDe', @@ -696,7 +696,7 @@ export const icons = 'IcFlagUk', 'IcFlagVi', 'IcFlagZhCn', - 'IcFlagZhTw' + 'IcFlagZhTw', ], 'mt5': [ 'IcMt5Acuity', @@ -725,7 +725,7 @@ export const icons = 'IcMt5SyntheticDashboard', 'IcMt5SyntheticIndices', 'IcMt5SyntheticPlatform', - 'IcMt5TradeTypes' + 'IcMt5TradeTypes', ], 'option': [ 'IcOptionAccumulators', @@ -743,7 +743,7 @@ export const icons = 'IcOptionOverUnder', 'IcOptionRaiseFall', 'IcOptionTouchNotouch', - 'IcOptionUpDownAsian' + 'IcOptionUpDownAsian', ], 'rebranding': [ 'IcRebrandingBinaryBot', @@ -768,7 +768,7 @@ export const icons = 'IcRebrandingMt5Logo', 'IcRebrandingMt5SwapFree', 'IcRebrandingSmarttraderDashboard', - 'IcRebrandingSmarttrader' + 'IcRebrandingSmarttrader', ], 'stock': [ 'IcStockAdidasSalomon', @@ -818,7 +818,7 @@ export const icons = 'IcStockVisa', 'IcStockWallMart', 'IcStockWaltDisney', - 'IcStockZoom' + 'IcStockZoom', ], 'tradetype': [ 'IcTradetypeAccu', @@ -856,7 +856,7 @@ export const icons = 'IcTradetypeTicklow', 'IcTradetypeUpordown', 'IcTradetypeVanillaLongCall', - 'IcTradetypeVanillaLongPut' + 'IcTradetypeVanillaLongPut', ], 'underlying': [ 'IcUnderlying1HZ100V', @@ -1015,7 +1015,7 @@ export const icons = 'IcUnderlyingWLDEUR', 'IcUnderlyingWLDGBP', 'IcUnderlyingWLDUSD', - 'IcUnderlyingWLDXAU' + 'IcUnderlyingWLDXAU', ], 'wallet': [ 'IcWalletBitcoinDark', @@ -1029,8 +1029,10 @@ export const icons = 'IcWalletDerivDemoLight', 'IcWalletDerivP2pDark', 'IcWalletDerivP2pLight', + 'IcWalletErrorMessageWithCross', 'IcWalletEthereumDark', 'IcWalletEthereumLight', + 'IcWalletInfoMessageWithThreeDots', 'IcWalletLiteCoinDark', 'IcWalletLiteCoinLight', 'IcWalletModalTetherDark', @@ -1040,9 +1042,10 @@ export const icons = 'IcWalletOptionsLight', 'IcWalletPaymentAgentDark', 'IcWalletPaymentAgentLight', + 'IcWalletSuccessMessage', 'IcWalletTetherDark', 'IcWalletTetherLight', 'IcWalletUsdCoinDark', - 'IcWalletUsdCoinLight' - ] -} \ No newline at end of file + 'IcWalletUsdCoinLight', + ], +} diff --git a/packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx b/packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx new file mode 100644 index 000000000000..d03eb6ec6d7d --- /dev/null +++ b/packages/hooks/src/__tests__/useExistingCFDAccounts.spec.tsx @@ -0,0 +1,173 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; +import useExistingCFDAccounts from '../useExistingCFDAccounts'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(name => { + if (name === 'authorize') { + return { + data: { + account_list: [ + { + account_category: 'wallet', + currency: 'USD', + is_virtual: 1, + linked_to: [ + { + loginid: 'CRW909900', + platform: 'mt5', + }, + ], + }, + ], + }, + }; + } + + if (name === 'mt5_login_list') { + return { + data: { + mt5_login_list: [ + { + display_login: 'CRW909900', + email: '', + leverage: '10012123123', + login: 'CRW909900', + server: 'Deriv-Server', + server_description: 'Deriv-Server', + type: 'demo', + }, + ], + }, + }; + } + + if (name === 'trading_platform_accounts') { + return { + data: { + trading_platform_accounts: [ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + market_type: 'all', + platform: 'dxtrade', + }, + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + }, + ], + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useExistingCFDAccounts', () => { + it('should return the existing cfd accounts', () => { + const mock = mockStore({ + client: { accounts: { CRW909900: { token: '12345' } }, loginid: 'CRW909900' }, + traders_hub: { + combined_cfd_mt5_accounts: [ + { + platform: 'mt5', + description: 'Deriv-Server', + icon: 'Derived', + sub_title: 'sub_name', + name: 'Derived', + }, + ], + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + const { result } = renderHook(() => useExistingCFDAccounts(), { wrapper }); + + expect(result.current.data.dxtrade_accounts).toEqual( + expect.arrayContaining([ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + loginid: 'DXR1646584', + market_type: 'all', + platform: 'dxtrade', + transfer_icon: 'IcRebrandingDerivX', + }, + ]) + ); + + expect(result.current.data.derivez_accounts).toEqual( + expect.arrayContaining([ + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + loginid: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + transfer_icon: 'IcRebrandingDerivEz', + }, + ]) + ); + }); +}); diff --git a/packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx b/packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx new file mode 100644 index 000000000000..90eb81b411a9 --- /dev/null +++ b/packages/hooks/src/__tests__/useTransferBetweenAccounts.spec.tsx @@ -0,0 +1,294 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import useTransferBetweenAccounts from '../useTransferBetweenAccounts'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + account_type: 'doughflow', + created_at: 1688642811, + currency: 'USD', + is_disabled: 0, + is_selected: true, + is_virtual: 0, + landing_company_name: 'svg', + linked_to: [ + { + loginid: 'MTR100967300', + platform: 'mt5', + }, + { + loginid: 'MTR80057067', + platform: 'mt5', + }, + { + loginid: 'DXR1646584', + platform: 'dxtrade', + }, + { + loginid: 'EZR80001086', + platform: 'derivez', + }, + ], + loginid: 'CRW1030', + }, + ], + }, + }, + }; + } + if (name === 'mt5_login_list') { + return { + data: { + mt5_login_list: [ + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts02\\synthetic\\svg_std_usd\\04', + landing_company_short: 'svg', + leverage: 500, + login: 'MTR100967300', + market_type: 'synthetic', + name: 'Name', + server: 'p02_ts02', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_synthetic', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts02', + }, + status: null, + sub_account_category: '', + sub_account_type: 'financial', + }, + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts01\\all\\svg_std-sf_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'MTR80057067', + market_type: 'all', + name: 'Name', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + status: null, + sub_account_category: 'swap_free', + sub_account_type: 'standard', + }, + ], + }, + }; + } + if (name === 'trading_platform_accounts') { + return { + data: { + trading_platform_accounts: [ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + market_type: 'all', + platform: 'dxtrade', + }, + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + }, + ], + }, + }; + } + if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + name: 'US Dollar', + type: 'fiat', + }, + }, + }, + }, + }; + } + if (name === 'transfer_between_accounts') { + return { + data: { + accounts: [ + { + account_type: 'wallet', + balance: '100.00', + currency: 'USD', + demo_account: 0, + loginid: 'CRW1030', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR100967300', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR80057067', + }, + { + account_type: 'derivez', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'EZR80001086', + }, + { + account_type: 'dxtrade', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'DXR1646584', + }, + ], + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useTransferBetweenAccounts', () => { + let mock_store: ReturnType, wrapper: ({ children }: { children: JSX.Element }) => JSX.Element; + + beforeEach(() => { + mock_store = mockStore({ + client: { + loginid: 'CRW1030', + accounts: { + CRW1030: { + token: 'token', + }, + }, + }, + }); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + }); + + it('should be correct amount of transfer accounts', () => { + const { + result: { + current: { trading_accounts, wallet_accounts }, + }, + } = renderHook(() => useTransferBetweenAccounts(), { wrapper }); + + expect(Object.keys(trading_accounts).length).toBe(4); + expect(Object.keys(wallet_accounts).length).toBe(1); + }); + + it('all transfer accounts should have extended properties', () => { + const { + result: { + current: { trading_accounts, wallet_accounts }, + }, + } = renderHook(() => useTransferBetweenAccounts(), { wrapper }); + + Object.values({ ...trading_accounts, ...wallet_accounts }).forEach(account => { + expect(account).toHaveProperty('active_wallet_icon'); + expect(account).toHaveProperty('display_currency_code'); + expect(account).toHaveProperty('gradient_class'); + expect(account).toHaveProperty('icon'); + expect(account).toHaveProperty('is_demo'); + expect(account).toHaveProperty('shortcode'); + expect(account).toHaveProperty('type'); + }); + }); + + it('should return proper active account with extended properties', () => { + const { + result: { + current: { active_wallet }, + }, + } = renderHook(() => useTransferBetweenAccounts(), { wrapper }); + + expect(active_wallet).toEqual({ + account_type: 'wallet', + active_wallet_icon: 'IcWalletCurrencyUsd', + balance: 100, + currency: 'USD', + demo_account: 0, + display_currency_code: 'USD', + gradient_class: 'wallet-card__usd-bg', + icon: 'IcWalletCurrencyUsd', + is_demo: false, + loginid: 'CRW1030', + shortcode: 'svg', + type: 'fiat', + }); + }); +}); diff --git a/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx b/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx index d20c813f4685..c87c966fd916 100644 --- a/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx +++ b/packages/hooks/src/__tests__/useWalletTransactions.spec.tsx @@ -91,6 +91,19 @@ describe('useWalletsList', () => { }, ], }, + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + is_deposit_suspended: 0, + is_suspended: 0, + is_withdrawal_suspended: 0, + name: 'US Dollar', + stake_default: 10, + type: 'fiat', + }, + }, + }, }, } as unknown as ReturnType); diff --git a/packages/hooks/src/__tests__/useWalletTransfer.spec.tsx b/packages/hooks/src/__tests__/useWalletTransfer.spec.tsx new file mode 100644 index 000000000000..4ecf2da6538e --- /dev/null +++ b/packages/hooks/src/__tests__/useWalletTransfer.spec.tsx @@ -0,0 +1,252 @@ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import useWalletTransfer from '../useWalletTransfer'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { APIProvider } from '@deriv/api'; + +jest.mock('@deriv/api', () => ({ + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn((name: string) => { + if (name === 'authorize') { + return { + data: { + authorize: { + account_list: [ + { + account_category: 'wallet', + account_type: 'doughflow', + created_at: 1688642811, + currency: 'USD', + is_disabled: 0, + is_selected: true, + is_virtual: 0, + landing_company_name: 'svg', + linked_to: [ + { + loginid: 'MTR100967300', + platform: 'mt5', + }, + { + loginid: 'MTR80057067', + platform: 'mt5', + }, + { + loginid: 'DXR1646584', + platform: 'dxtrade', + }, + { + loginid: 'EZR80001086', + platform: 'derivez', + }, + ], + loginid: 'CRW1030', + }, + ], + }, + }, + }; + } + if (name === 'mt5_login_list') { + return { + data: { + mt5_login_list: [ + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts02\\synthetic\\svg_std_usd\\04', + landing_company_short: 'svg', + leverage: 500, + login: 'MTR100967300', + market_type: 'synthetic', + name: 'Name', + server: 'p02_ts02', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_synthetic', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts02', + }, + status: null, + sub_account_category: '', + sub_account_type: 'financial', + }, + { + account_type: 'real', + balance: 0, + country: 'ng', + currency: 'USD', + display_balance: '0.00', + email: 'email@gmail.com', + group: 'real\\p02_ts01\\all\\svg_std-sf_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'MTR80057067', + market_type: 'all', + name: 'Name', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + status: null, + sub_account_category: 'swap_free', + sub_account_type: 'standard', + }, + ], + }, + }; + } + if (name === 'trading_platform_accounts') { + return { + data: { + trading_platform_accounts: [ + { + account_id: 'DXR1646584', + account_type: 'real', + balance: 0, + currency: 'USD', + display_balance: '0.00', + enabled: 1, + landing_company_short: 'svg', + login: '8807230', + market_type: 'all', + platform: 'dxtrade', + }, + { + account_type: 'real', + balance: 0, + country: 'za', + currency: 'USD', + display_balance: '0.00', + email: 'mei+za1@binary.com', + group: 'real\\p02_ts01\\all\\svg_ez_usd', + landing_company_short: 'svg', + leverage: 1000, + login: 'EZR80001086', + market_type: 'all', + name: 'Baily Pan', + server: 'p02_ts01', + server_info: { + environment: 'Deriv-Server-02', + geolocation: { + group: 'africa_derivez', + location: 'South Africa', + region: 'Africa', + sequence: 2, + }, + id: 'p02_ts01', + }, + }, + ], + }, + }; + } + if (name === 'website_status') { + return { + data: { + website_status: { + currencies_config: { + USD: { + fractional_digits: 2, + name: 'US Dollar', + type: 'fiat', + }, + }, + }, + }, + }; + } + if (name === 'transfer_between_accounts') { + return { + data: { + accounts: [ + { + account_type: 'wallet', + balance: '100.00', + currency: 'USD', + demo_account: 0, + loginid: 'CRW1030', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR100967300', + }, + { + account_type: 'mt5', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'MTR80057067', + }, + { + account_type: 'derivez', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'EZR80001086', + }, + { + account_type: 'dxtrade', + balance: '0.00', + currency: 'USD', + demo_account: 0, + loginid: 'DXR1646584', + }, + ], + }, + }; + } + + return { data: undefined }; + }), +})); + +describe('useWalletTransfer', () => { + let mock_store: ReturnType, wrapper: ({ children }: { children: JSX.Element }) => JSX.Element; + + beforeEach(() => { + mock_store = mockStore({ + client: { + loginid: 'CRW1030', + accounts: { + CRW1030: { + token: 'token', + }, + }, + }, + }); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + {children} + + ); + }); + + it('from_account should be undefined by default', () => { + const { + result: { + current: { from_account }, + }, + } = renderHook(() => useWalletTransfer(), { wrapper }); + + expect(from_account).toBeUndefined(); + }); +}); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 1e6144cbf18b..274d245c637b 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -1,5 +1,7 @@ export { default as useAccountTransferVisible } from './useAccountTransferVisible'; export { default as useActiveWallet } from './useActiveWallet'; +export { default as useAuthorize } from './useAuthorize'; +export { default as useAvailableWallets } from './useAvailableWallets'; export { default as useCFDAccounts } from './useCFDAccounts'; export { default as useCFDAllAccounts } from './useCFDAllAccounts'; export { default as useCFDDemoAccounts } from './useCFDDemoAccounts'; @@ -14,6 +16,7 @@ export { default as useDepositCryptoAddress } from './useDepositCryptoAddress'; export { default as useDepositFiatAddress } from './useDepositFiatAddress'; export { default as useDepositLocked } from './useDepositLocked'; export { default as useExchangeRate } from './useExchangeRate'; +export { default as useExistingCFDAccounts } from './useExistingCFDAccounts'; export { default as useFeatureFlags } from './useFeatureFlags'; export { default as useFiatAccountList } from './useFiatAccountList'; export { default as useHasActiveRealAccount } from './useHasActiveRealAccount'; @@ -44,10 +47,10 @@ export { default as usePlatformDemoAccount } from './usePlatformDemoAccount'; export { default as usePlatformRealAccounts } from './usePlatformRealAccounts'; export { default as useRealSTPAccount } from './useRealSTPAccount'; export { default as useTotalAccountBalance } from './useTotalAccountBalance'; +export { default as useTransferBetweenAccounts } from './useTransferBetweenAccounts'; export { default as useVerifyEmail } from './useVerifyEmail'; export { default as useWalletsList } from './useWalletsList'; -export { default as useAvailableWallets } from './useAvailableWallets'; -export { default as useAuthorize } from './useAuthorize'; export { default as useWalletTransactions } from './useWalletTransactions'; +export { default as useWalletTransfer } from './useWalletTransfer'; export { default as useWalletMigration } from './useWalletMigration'; export { useIsAccountStatusPresent } from './useIsAccountStatusPresent'; diff --git a/packages/hooks/src/useExistingCFDAccounts.ts b/packages/hooks/src/useExistingCFDAccounts.ts new file mode 100644 index 000000000000..2a1961a494cf --- /dev/null +++ b/packages/hooks/src/useExistingCFDAccounts.ts @@ -0,0 +1,106 @@ +import { useMemo } from 'react'; +import { useFetch } from '@deriv/api'; +import useActiveWallet from './useActiveWallet'; +import { useStore } from '@deriv/stores'; + +type TAccount = { + cfd_type?: 'mt5' | 'derivez' | 'dxtrade'; + market_type?: 'financial' | 'synthetic' | 'all'; +}; + +const getAccountIcon = ({ cfd_type, market_type }: TAccount) => { + switch (cfd_type) { + case 'mt5': { + switch (market_type) { + case 'financial': + return 'IcRebrandingMt5FinancialDashboard'; + case 'synthetic': + return 'IcRebrandingMt5DerivedDashboard'; + case 'all': + return 'IcRebrandingMt5SwapFree'; + default: + return 'IcRebrandingDmt5Dashboard'; + } + } + case 'derivez': + return 'IcRebrandingDerivEz'; + case 'dxtrade': + return 'IcRebrandingDerivX'; + default: + return ''; + } +}; + +/** + * @description This hook is used to get the created CFD accounts of the user. + */ +const useExistingCFDAccounts = () => { + const { traders_hub } = useStore(); + const { combined_cfd_mt5_accounts } = traders_hub; + const wallet = useActiveWallet(); + const { data: mt5, ...mt5_rest } = useFetch('mt5_login_list'); + const { data: derivez, ...derivez_rest } = useFetch('trading_platform_accounts', { + payload: { platform: 'derivez' }, + }); + const { data: dxtrade, ...dxtrade_rest } = useFetch('trading_platform_accounts', { + payload: { platform: 'dxtrade' }, + }); + + /** + * + * @description This is the modified MT5 accounts that will be used in the CFD account creation. + */ + const modified_mt5_accounts = useMemo(() => { + const getAccountInfo = (login?: string) => { + return { + platform: wallet?.linked_to?.find(linked => linked.loginid === login)?.platform, + icon: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.icon, + description: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.description, + name: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.name, + sub_title: combined_cfd_mt5_accounts?.find(cfd => cfd.login === login)?.sub_title, + action_type: 'multi-action', + }; + }; + + return mt5?.mt5_login_list?.map(account => ({ + ...account, + ...getAccountInfo(account.login), + loginid: account.login, + transfer_icon: getAccountIcon({ cfd_type: 'mt5', ...account }), + })); + }, [mt5?.mt5_login_list, wallet?.linked_to, combined_cfd_mt5_accounts]); + + const modified_derivez_accounts = useMemo( + () => + derivez?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.login, + transfer_icon: getAccountIcon({ cfd_type: 'derivez' }), + })), + [derivez?.trading_platform_accounts] + ); + const modified_dxtrade_accounts = useMemo( + () => + dxtrade?.trading_platform_accounts?.map(account => ({ + ...account, + loginid: account.account_id, + transfer_icon: getAccountIcon({ cfd_type: 'dxtrade' }), + })), + [dxtrade?.trading_platform_accounts] + ); + const data = useMemo( + () => ({ + mt5_accounts: modified_mt5_accounts || [], + dxtrade_accounts: modified_dxtrade_accounts || [], + derivez_accounts: modified_derivez_accounts || [], + }), + [modified_mt5_accounts, modified_dxtrade_accounts, modified_derivez_accounts] + ); + + return { + data, + isSuccess: [mt5_rest.isSuccess, dxtrade_rest.isSuccess, derivez_rest.isSuccess].every(Boolean), + }; +}; + +export default useExistingCFDAccounts; diff --git a/packages/hooks/src/useTransferBetweenAccounts.ts b/packages/hooks/src/useTransferBetweenAccounts.ts new file mode 100644 index 000000000000..35f232ab805c --- /dev/null +++ b/packages/hooks/src/useTransferBetweenAccounts.ts @@ -0,0 +1,144 @@ +import { useMemo } from 'react'; +import { useStore } from '@deriv/stores'; +import { useFetch } from '@deriv/api'; +import useActiveWallet from './useActiveWallet'; +import useCurrencyConfig from './useCurrencyConfig'; +import useExistingCFDAccounts from './useExistingCFDAccounts'; +import useWalletsList from './useWalletsList'; + +const useTransferBetweenAccounts = () => { + const { ui } = useStore(); + const { is_dark_mode_on } = ui; + + const active_wallet = useActiveWallet(); + + const { data: wallets } = useWalletsList(); + + const { getConfig } = useCurrencyConfig(); + + const trading_apps_icon = is_dark_mode_on ? 'IcWalletOptionsDark' : 'IcWalletOptionsLight'; + + const { + data: { derivez_accounts, dxtrade_accounts, mt5_accounts }, + isSuccess: is_cfd_accounts_loaded, + } = useExistingCFDAccounts(); + + const { data, ...rest } = useFetch('transfer_between_accounts', { + payload: { accounts: 'all' }, + options: { enabled: is_cfd_accounts_loaded }, + }); + + const modified_transfer_accounts = useMemo(() => { + const all_linked_cfd_accounts = [...derivez_accounts, ...dxtrade_accounts, ...mt5_accounts]; + + const getAccountType = (is_demo?: number, currency?: string): 'fiat' | 'crypto' | 'demo' => { + if (is_demo) return 'demo'; + return getConfig(currency || '')?.is_crypto ? 'crypto' : 'fiat'; + }; + + const accounts = data?.accounts?.map(account => { + return { + ...account, + active_wallet_icon: active_wallet?.icon, + balance: parseFloat( + Number(account.balance).toFixed(getConfig(account.currency || '')?.fractional_digits) + ), + display_currency_code: getConfig(account.currency || '')?.display_code, + is_demo: Boolean(account?.demo_account), + shortcode: active_wallet?.landing_company_name, + type: getAccountType(account.demo_account, account.currency), + }; + }); + + return { + trading_accounts: + accounts?.reduce( + (trading_accounts, account) => { + if (account.account_type === 'wallet') return trading_accounts; + if (!account.loginid) return trading_accounts; + + const cfd_icon = all_linked_cfd_accounts.find( + cfd_account => account.loginid && cfd_account.loginid?.includes(account.loginid) + )?.transfer_icon; + + trading_accounts[account.loginid] = { + ...account, + gradient_class: active_wallet?.gradient_card_class, + icon: account.account_type === 'trading' ? trading_apps_icon : cfd_icon, + ...(account.account_type === 'mt5' && { + mt5_market_type: mt5_accounts?.find( + mt5_account => account.loginid && mt5_account.loginid?.includes(account.loginid) + )?.market_type, + }), + }; + + return trading_accounts; + }, + {} as Record< + string, + NonNullable< + typeof accounts[number] & { + gradient_class?: `wallet-card__${string}`; + icon?: string; + mt5_market_type?: 'all' | 'financial' | 'synthetic'; + } + > + > + ) || {}, + wallet_accounts: + accounts?.reduce( + (wallet_accounts, wallet) => { + if (wallet.account_type !== 'wallet') return wallet_accounts; + if (!wallet.loginid) return wallet_accounts; + + const available_wallet = wallets?.find(acc => acc.loginid === wallet.loginid); + + wallet_accounts[wallet.loginid] = { + ...wallet, + icon: available_wallet?.icon, + gradient_class: available_wallet?.gradient_card_class, + }; + + return wallet_accounts; + }, + {} as Record< + string, + NonNullable< + typeof accounts[number] & { + gradient_class?: `wallet-card__${string}`; + icon?: string; + } + > + > + ) || {}, + }; + }, [ + active_wallet?.gradient_card_class, + active_wallet?.icon, + active_wallet?.landing_company_name, + data?.accounts, + derivez_accounts, + dxtrade_accounts, + getConfig, + mt5_accounts, + trading_apps_icon, + wallets, + ]); + + const modified_active_wallet = useMemo(() => { + return active_wallet?.loginid + ? { + ...modified_transfer_accounts.wallet_accounts[active_wallet?.loginid], + } + : undefined; + }, [active_wallet?.loginid, modified_transfer_accounts.wallet_accounts]); + + return { + ...rest, + active_wallet: modified_active_wallet, + trading_accounts: modified_transfer_accounts.trading_accounts, + wallet_accounts: modified_transfer_accounts.wallet_accounts, + }; +}; + +export default useTransferBetweenAccounts; diff --git a/packages/hooks/src/useWalletTransfer.ts b/packages/hooks/src/useWalletTransfer.ts new file mode 100644 index 000000000000..4f769ee57b43 --- /dev/null +++ b/packages/hooks/src/useWalletTransfer.ts @@ -0,0 +1,54 @@ +import { useMemo, useState, useEffect } from 'react'; +import useTransferBetweenAccounts from './useTransferBetweenAccounts'; + +const useWalletTransfer = () => { + const { + active_wallet, + trading_accounts, + wallet_accounts, + isLoading: is_accounts_loading, + } = useTransferBetweenAccounts(); + + const [from_account, setFromAccount] = useState(); + const [to_account, setToAccount] = useState(); + + const to_account_list = useMemo(() => { + if (!from_account?.loginid) return { trading_accounts: {}, wallet_accounts: {} }; + if (!active_wallet?.loginid) return { trading_accounts: {}, wallet_accounts: {} }; + + if (from_account?.loginid === active_wallet?.loginid) { + return { + trading_accounts, + wallet_accounts: Object.fromEntries( + Object.entries(wallet_accounts).filter( + ([key]) => active_wallet?.loginid && !key.includes(active_wallet?.loginid) + ) + ), + }; + } + return { trading_accounts: {}, wallet_accounts: { [active_wallet?.loginid]: active_wallet } }; + }, [active_wallet, from_account?.loginid, trading_accounts, wallet_accounts]); + + //this useEffect populates from/to accounts with updated values, if they were updated in the background + useEffect(() => { + setFromAccount(acc => { + return acc?.loginid ? { ...trading_accounts, ...wallet_accounts }[acc?.loginid] : undefined; + }); + setToAccount(acc => { + return acc?.loginid ? { ...trading_accounts, ...wallet_accounts }[acc?.loginid] : undefined; + }); + }, [setFromAccount, setToAccount, trading_accounts, wallet_accounts]); + + return { + active_wallet, + is_accounts_loading, + from_account, + to_account, + to_account_list, + transfer_accounts: { trading_accounts, wallet_accounts }, + setFromAccount, + setToAccount, + }; +}; + +export default useWalletTransfer; diff --git a/packages/shared/src/styles/themes.scss b/packages/shared/src/styles/themes.scss index 8a29f21f5e32..73c8777627aa 100644 --- a/packages/shared/src/styles/themes.scss +++ b/packages/shared/src/styles/themes.scss @@ -85,6 +85,7 @@ --text-less-prominent: #{$color-grey-1}; --text-prominent: #{$color-black-1}; --text-disabled: #{$color-grey-1}; + --text-disabled-1: #{$color-grey-6}; --text-loss-danger: #{$color-red-1}; --text-profit-success: #{$color-green-1}; --text-warning: #{$color-yellow}; @@ -235,6 +236,7 @@ --text-less-prominent: #{$color-grey-7}; --text-primary: #{$color-grey-1}; --text-disabled: #{$color-black-6}; + --text-disabled-1: #{$color-black-6}; --text-profit-success: #{$color-green-3}; --text-loss-danger: #{$color-red-2}; --text-red: #{$color-red}; diff --git a/packages/utils/src/__tests__/getWalletCurrencyIcon.spec.tsx b/packages/utils/src/__tests__/getWalletCurrencyIcon.spec.ts similarity index 100% rename from packages/utils/src/__tests__/getWalletCurrencyIcon.spec.tsx rename to packages/utils/src/__tests__/getWalletCurrencyIcon.spec.ts diff --git a/packages/utils/src/__tests__/groupTransactionsByDay.spec.tsx b/packages/utils/src/__tests__/groupTransactionsByDay.spec.ts similarity index 100% rename from packages/utils/src/__tests__/groupTransactionsByDay.spec.tsx rename to packages/utils/src/__tests__/groupTransactionsByDay.spec.ts diff --git a/packages/utils/src/__tests__/unFormatLocaleString.spec.tsx b/packages/utils/src/__tests__/unFormatLocaleString.spec.ts similarity index 100% rename from packages/utils/src/__tests__/unFormatLocaleString.spec.tsx rename to packages/utils/src/__tests__/unFormatLocaleString.spec.ts diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 56d7c5788ec8..3f0b1026c243 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,4 +1,4 @@ -export { default as getWalletCurrencyIcon } from './getWalletCurrencyIcon'; export { default as groupTransactionsByDay } from './groupTransactionsByDay'; +export { default as getWalletCurrencyIcon } from './getWalletCurrencyIcon'; export { default as unFormatLocaleString } from './unFormatLocaleString'; export { getLocalStorage } from './getLocalStorage';