From a27d3e3b886a89e4c45e55b60b0f05d96f77d7bd Mon Sep 17 00:00:00 2001 From: George Usynin <103181646+heorhi-deriv@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:33:00 +0300 Subject: [PATCH] george / WALL-431 / Demo: Input & selector stitching (#8794) * feat: :sparkles: account transfer component (demo) * fix: transfer hint message * fix: :art: apply comments * refactor: :art: remove reducer, simplify transfer logic, improve readability (#34) * fix: update padding of app-icon component (#33) * style: improve styles for account list * style: apply comments * fix: apply comments, refactor account modal in desktop * fix: :art: apply comments * feat: add scrolling behaviour to account list in mobile view * fix: apply comments, fix test * refactor: :fire: remove mobile dialog, refactor scrolling, remove redundant logic * fix: apply comments, refactor tests * test: :rocket: add tests for transfer related components * style: fix style for merged icon * fix: mock loginid * fix: typo, demo icon, transition on closing, blinking icons * perf: disable automatic opening of wallet modal --------- Co-authored-by: Hamid --- .../__tests__/wallet-modal-body.spec.tsx | 31 ++- .../__tests__/wallet-modal.spec.tsx | 10 +- .../modals/wallet-modal/provider.tsx | 38 +-- .../modals/wallet-modal/wallet-modal-body.tsx | 36 ++- .../wallet-modal/wallet-modal-header.tsx | 1 + .../modals/wallet-modal/wallet-modal.scss | 255 ++++++++---------- .../modals/wallet-modal/wallet-modal.tsx | 67 ++--- .../__tests__/wallet-transfer.spec.tsx | 25 ++ .../src/components/wallet-transfer/index.ts | 3 + .../mock_accounts/mock_accounts.tsx | 52 ++++ .../wallet-transfer/wallet-transfer.scss | 42 +++ .../wallet-transfer/wallet-transfer.tsx | 108 ++++++++ .../components/amount-input/amount-input.scss | 8 +- .../components/amount-input/amount-input.tsx | 2 + .../app-linked-with-wallet-icon.scss | 23 +- .../mobile-dialog/mobile-dialog.tsx | 35 +-- .../components/src/components/modal/modal.tsx | 9 +- .../__tests__/transfer-account-list.spec.tsx | 86 ++++++ .../transfer-account-selector.spec.tsx | 53 ++++ .../__tests__/transfer-tile.spec.tsx | 60 +++++ .../transfer-account-selector/index.ts | 2 +- .../transfer-account-list.tsx | 92 +++++++ .../transfer-account-selector.scss | 148 ++++++++-- .../transfer-account-selector.tsx | 155 +++++------ .../transfer-tile.tsx | 58 ++++ .../__tests__/wallet-tile.spec.tsx | 86 ++++++ .../components/wallet-tile/wallet-tile.scss | 27 +- .../components/wallet-tile/wallet-tile.tsx | 60 ++--- packages/components/src/index.js | 1 + packages/core/src/Stores/ui-store.js | 2 +- 30 files changed, 1154 insertions(+), 421 deletions(-) create mode 100644 packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/index.ts create mode 100644 packages/appstore/src/components/wallet-transfer/mock_accounts/mock_accounts.tsx create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer.scss create mode 100644 packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx create mode 100644 packages/components/src/components/transfer-account-selector/__tests__/transfer-account-list.spec.tsx create mode 100644 packages/components/src/components/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx create mode 100644 packages/components/src/components/transfer-account-selector/__tests__/transfer-tile.spec.tsx create mode 100644 packages/components/src/components/transfer-account-selector/transfer-account-list.tsx create mode 100644 packages/components/src/components/transfer-account-selector/transfer-tile.tsx create mode 100644 packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx 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 b2318acb265f..b14978312e54 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 @@ -1,18 +1,23 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; import WalletModalBody from '../wallet-modal-body'; +jest.mock('Components/wallet-transfer', () => jest.fn(() =>
WalletTransfer
)); + describe('WalletModalBody', () => { let mocked_props: React.ComponentProps; beforeEach(() => { mocked_props = { active_tab_index: 0, + contentScrollHandler: jest.fn(), is_dark: false, is_demo: true, + is_mobile: false, setActiveTabIndex: jest.fn(), + setIsWalletNameVisible: jest.fn(), is_wallet_name_visible: true, wallet_type: 'demo', }; @@ -22,16 +27,25 @@ describe('WalletModalBody', () => { render({component}); }; - it('Should render proper tabs for demo wallet with proper content', () => { + it('Should render proper tabs for demo wallet', () => { renderWithRouter(); expect(screen.getByText('Transfer')).toBeInTheDocument(); - expect(screen.getByText('Transfer Demo')).toBeInTheDocument(); expect(screen.getByText('Transactions')).toBeInTheDocument(); expect(screen.getByText('Reset balance')).toBeInTheDocument(); }); - it('Shoud trigger setActiveTabIndex callback when the user clicked on the tab', () => { + it('Should render proper content under the Transfer tab', () => { + mocked_props.active_tab_index = 1; + renderWithRouter(); + + const el_transfer_tab = screen.getByText('Transfer'); + userEvent.click(el_transfer_tab); + + expect(screen.getByText('WalletTransfer')).toBeInTheDocument(); + }); + + it('Should trigger setActiveTabIndex callback when the user clicked on the tab', () => { renderWithRouter(); const el_transactions_tab = screen.getByText('Transactions'); @@ -39,4 +53,13 @@ describe('WalletModalBody', () => { expect(mocked_props.setActiveTabIndex).toHaveBeenCalledTimes(1); }); + + it('Should trigger contentScrollHandler callback when the user scrolls the content', () => { + renderWithRouter(); + + const el_themed_scrollbars = screen.getByTestId('dt_themed_scrollbars'); + fireEvent.scroll(el_themed_scrollbars); + + expect(mocked_props.contentScrollHandler).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx index c81730e74e02..0c0f96e033d5 100644 --- a/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/__tests__/wallet-modal.spec.tsx @@ -4,16 +4,10 @@ import { mockStore, StoreProvider } from '@deriv/stores'; import WalletModal from '../wallet-modal'; jest.mock('../wallet-modal-header', () => jest.fn(() =>
WalletModalHeader
)); -jest.mock('../wallet-modal-body', () => { - const { forwardRef } = jest.requireActual('react'); - return { - __esModule: true, - default: forwardRef(() =>
WalletModalBody
), - }; -}); +jest.mock('../wallet-modal-body', () => jest.fn(() =>
WalletModalBody
)); describe('WalletModal', () => { - let modal_root_el; + let modal_root_el: HTMLDivElement; beforeAll(() => { modal_root_el = document.createElement('div'); modal_root_el.setAttribute('id', 'modal_root'); diff --git a/packages/appstore/src/components/modals/wallet-modal/provider.tsx b/packages/appstore/src/components/modals/wallet-modal/provider.tsx index 7471406e6191..91abc4bdbd9b 100644 --- a/packages/appstore/src/components/modals/wallet-modal/provider.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/provider.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import React from 'react'; import { localize } from '@deriv/translations'; -import { isMobile } from '@deriv/shared'; +import WalletTransfer from 'Components/wallet-transfer'; export type TWalletType = 'real' | 'demo' | 'p2p' | 'payment_agent'; @@ -13,8 +13,8 @@ export const getCashierOptions = (type: TWalletType) => { icon: 'IcAdd', label: localize('Deposit'), //Remove Lorem ipsum text after QA testing (testing scroll behaviour) - content: ( -
+ content: () => ( +
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas euismod lectus odio, sed pulvinar ex eleifend eu. Quisque elementum pellentesque felis. Maecenas elementum vitae purus sed ullamcorper. In quis tempus diam, non posuere ipsum. Quisque viverra in mauris @@ -70,16 +70,16 @@ export const getCashierOptions = (type: TWalletType) => {
), }, - { icon: 'IcMinus', label: localize('Withdraw'), content:

Withdraw Real

}, + { icon: 'IcMinus', label: localize('Withdraw'), content: () =>

Withdraw Real

}, { icon: 'IcAccountTransfer', label: localize('Transfer'), - content:

Transfer Real

, + content: () =>

Transfer Real

, }, { icon: 'IcStatement', label: localize('Transactions'), - content:

Transactions Real

, + content: () =>

Transactions Real

, }, ]; case 'demo': @@ -87,17 +87,17 @@ export const getCashierOptions = (type: TWalletType) => { { icon: 'IcAccountTransfer', label: localize('Transfer'), - content:

Transfer Demo

, + content: (props: React.ComponentProps) => , }, { icon: 'IcStatement', label: localize('Transactions'), - content:

Transactions Demo

, + content: () =>

Transactions Demo

, }, { icon: 'IcAdd', label: localize('Reset balance'), - content:

Reset balance

, + content: () =>

Reset balance

, }, ]; case 'p2p': @@ -105,47 +105,47 @@ export const getCashierOptions = (type: TWalletType) => { { icon: 'IcAdd', label: localize('Buy/Sell'), - content:

Buy/Sell

, + content: () =>

Transfer Real

, }, { icon: 'IcStatement', label: localize('Orders'), - content:

Orders P2P

, + content: () =>

Transfer Real

, }, { icon: 'IcStatement', label: localize('My ads'), - content:

My ads P2P

, + content: () =>

Transfer Real

, }, { icon: 'IcStatement', label: localize('My profile'), - content:

My profile P2P

, + content: () =>

Transfer Real

, }, { icon: 'IcAccountTransfer', label: localize('Transfer'), - content:

Transfer P2P

, + content: () =>

Transfer Real

, }, { icon: 'IcStatement', label: localize('Transactions'), - content:

Transactions P2P

, + content: () =>

Transfer Real

, }, ]; case 'payment_agent': return [ - { icon: 'IcAdd', label: localize('Deposit'), content:

Deposit PA

}, - { icon: 'IcMinus', label: localize('Withdraw'), content:

Withdraw PA

}, + { icon: 'IcAdd', label: localize('Deposit'), content: () =>

Transfer Real

}, + { icon: 'IcMinus', label: localize('Withdraw'), content: () =>

Transfer Real

}, { icon: 'IcAccountTransfer', label: localize('Transfer'), - content:

Transfer PA

, + content: () =>

Transfer Real

, }, { icon: 'IcStatement', label: localize('Transactions'), - content:

Transactions PA

, + content: () =>

Transfer Real

, }, ]; default: 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 af8ec397e565..f60b127a73a2 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,29 +1,38 @@ import React from 'react'; import classNames from 'classnames'; -import { Tabs, ThemedScrollbars } from '@deriv/components'; +import { Tabs, ThemedScrollbars, Div100vhContainer } from '@deriv/components'; import { getCashierOptions, TWalletType } from './provider'; type TWalletModalBodyProps = { active_tab_index: number; + contentScrollHandler: React.UIEventHandler; is_dark: boolean; is_demo: boolean; is_mobile: boolean; setActiveTabIndex: (index: number) => void; + setIsWalletNameVisible: (value: boolean) => void; is_wallet_name_visible: boolean; wallet_type: TWalletType; }; const WalletModalBody = ({ active_tab_index, + contentScrollHandler, is_dark, is_demo, is_mobile, - is_wallet_name_visible, setActiveTabIndex, + setIsWalletNameVisible, + is_wallet_name_visible, wallet_type, }: TWalletModalBodyProps) => { - const content_heigth = 'calc(100vh - 24.4rem)'; - const max_content_width = '128rem'; + const getHeightOffset = React.useCallback(() => { + const desktop_header_height = '24.4rem'; + const mobile_header_height = '8.2rem'; + + return is_mobile ? mobile_header_height : desktop_header_height; + }, [is_mobile]); + return ( { setActiveTabIndex(index); }} @@ -44,12 +54,18 @@ const WalletModalBody = ({ return (
- {option.content} + +
+ {option.content({ + is_wallet_name_visible, + setIsWalletNameVisible, + })} +
+
); @@ -58,6 +74,4 @@ const WalletModalBody = ({ ); }; -WalletModalBody.displayName = 'WalletModalBody'; - export default WalletModalBody; diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx index 98c213e3df50..f56379154acd 100644 --- a/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal-header.tsx @@ -139,6 +139,7 @@ const WalletModalHeader = ({ {formatMoney(currency, balance, true)} {getCurrencyDisplayCode(currency)}
+ {/* TODO: replace Icon with WalletIcon component */}
diff --git a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss index 0838d61e6e0e..a1bb4307aa82 100644 --- a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.scss @@ -8,76 +8,138 @@ margin: 4.8rem 0 3.6rem; border-radius: unset; background-color: var(--general-main-1); + z-index: 9997; + box-shadow: none; + + @include mobile { + margin: 0; + } } -.wallet-mobile-dialog { - /* IE and Edge */ - -ms-overflow-style: none; - /* Firefox */ - scrollbar-width: none; - overflow-x: hidden; - overflow-y: scroll; +.header-background { + width: 100%; + display: flex; + justify-content: center; + position: relative; + overflow: hidden; + background-color: #fbdddd; - &::-webkit-scrollbar { - display: none; + @include mobile { + position: fixed; + z-index: 3; } +} - &::-webkit-scrollbar-thumb { - display: none; - } +// styles for mobile and desktop modal header +.modal-header { + max-width: 128rem; + width: 100%; + display: flex; + position: relative; + height: 16rem; + padding: 2.4rem 4rem 7.2rem; + + @include mobile { + height: 12.2rem; + padding: 1.6rem 1.6rem 5.6rem; + transition: height 0.2s ease; - .dc-mobile-dialog { - &__header { - display: none; + .title-visibility { + height: 2rem; } - &__content { - margin: 0; - padding: 0; + .title-visibility, + .icon-visibility { + visibility: visible; + transition: visibility 0s, height 0.2s ease; + } + } - .dc-mobile-dialog__wallet-mobile-dialog__wrapper { - .modal-header { - height: 12.2rem; - transition: height 0.2s ease; - - .title-visibility { - height: 2rem; - } - - .title-visibility, - .icon-visibility { - visibility: visible; - opacity: 1; - transition: visibility 0s, opacity 0.2s, height 0.2s ease; - } - } + //TODO: check do we need this after bg change to radial-gradient + &__title-wrapper { + position: relative; + } + + &__title { + display: flex; + align-items: center; + + &-wallet { + padding-right: 0.8rem; + color: var(--text-general); + + &-demo { + color: var(--demo-text-color-1); } + } - .scrolled-content { - .modal-header { - height: 8.2rem; + &-balance { + color: var(--text-prominent); - .title-visibility, - .icon-visibility { - visibility: hidden; - opacity: 0; - height: 0; - } - } + &-demo { + color: var(--demo-text-color-2); } } } + + &--hidden-title { + height: 8.2rem; + align-items: center; + justify-content: space-between; + + .title-visibility, + .icon-visibility { + visibility: hidden; + height: 0; + } + } + + &__currency-icon { + z-index: 3; + margin-left: auto; + margin-right: 1.6rem; + + @include mobile { + margin-right: 0.8rem; + } + } + + &__close-icon { + position: relative; + + .dc-icon { + cursor: pointer; + } + } } // styles for mobile and desktop modal body (tabs with content) .dc-tabs--modal-body__tabs { - max-width: 128rem; width: 100%; margin-top: -4.8rem; @include mobile { top: 8.2rem; margin-top: 0; + transition: top 0.2s ease; + + &.is_scrolled { + top: 4.2rem; + } + } + + &__themed-scrollbar { + width: 100%; + } + + &__content-wrapper { + max-width: 128rem; + margin: 0 auto; + padding: 2.4rem 4rem; + + @include mobile { + padding: 1.6rem; + } } .dc-tabs__list { @@ -85,14 +147,17 @@ @include mobile { padding: 0 1.6rem; + } + + &--header--modal-body__tabs { + max-width: 128rem; + width: 100%; + margin: 0 auto; - &--header--modal-body__tabs { - position: fixed; + @include mobile { width: 100%; - top: 8.2rem; - z-index: 2; + z-index: 3; overflow-x: scroll; - transition: top 0.2s ease; /* IE and Edge */ -ms-overflow-style: none; @@ -106,10 +171,10 @@ &::-webkit-scrollbar-thumb { display: none; } - } - &--header--modal-body__tabs.is_scrolled { - top: 4.4rem; + &.is_scrolled { + top: 4.4rem; + } } } } @@ -139,7 +204,7 @@ } .dc-tabs__content { - padding: 0 4rem; + width: 100%; font-size: var(--text-size-l); color: var(--text-prominent); display: flex; @@ -147,89 +212,7 @@ justify-content: center; @include mobile { - top: 4rem; - position: relative; - padding: 0 1.6rem; font-size: var(--text-size-xs); } } } - -.header-background { - width: 100%; - display: flex; - justify-content: center; - position: relative; - overflow: hidden; - background-color: #fbdddd; - - @include mobile { - position: fixed; - z-index: 1; - } -} - -// styles for mobile and desktop modal header -.modal-header { - max-width: 128rem; - width: 100%; - display: flex; - position: relative; - height: 16rem; - padding: 2.4rem 4rem 7.2rem; - - @include mobile { - height: inherit; - padding: 1.6rem 1.6rem 5.6rem; - } - - //TODO: check do we need this after bg change to radial-gradient - &__title-wrapper { - position: relative; - } - - &__title { - display: flex; - align-items: center; - - &-wallet { - padding-right: 0.8rem; - color: var(--text-general); - - &-demo { - color: var(--demo-text-color-1); - } - } - - &-balance { - color: var(--text-prominent); - - &-demo { - color: var(--demo-text-color-2); - } - } - } - - &--hidden-title { - align-items: center; - justify-content: space-between; - } - - &__currency-icon { - z-index: 100; - margin-left: auto; - margin-right: 1.6rem; - - @include mobile { - margin-right: 0.8rem; - } - } - - &__close-icon { - position: relative; - - .dc-icon { - cursor: pointer; - } - } -} 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 6e462445c4e0..2063d55c676b 100644 --- a/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx +++ b/packages/appstore/src/components/modals/wallet-modal/wallet-modal.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import { DesktopWrapper, MobileDialog, MobileWrapper, Modal } from '@deriv/components'; +import { Modal } from '@deriv/components'; import WalletModalHeader from './wallet-modal-header'; import WalletModalBody from './wallet-modal-body'; import { observer, useStore } from '@deriv/stores'; -import classNames from 'classnames'; const WalletModal = observer(() => { const store = useStore(); @@ -16,36 +15,30 @@ const WalletModal = observer(() => { // TODO: Temporary wallet type. Will be refactored later. Add correct type const wallet_type = is_demo ? 'demo' : 'real'; - const mobile_dialog_ref = React.useRef(null); const [active_tab_index, setActiveTabIndex] = React.useState(0); const [is_wallet_name_visible, setIsWalletNameVisible] = React.useState(true); + React.useEffect(() => { + return setIsWalletNameVisible(true); + }, [active_tab_index, is_wallet_modal_visible]); + const closeModal = () => { setIsWalletModalVisible(false); setActiveTabIndex(0); }; - React.useEffect(() => { - const el_mobile_dialog = mobile_dialog_ref.current; - - const handleScroll = (e: Event) => { - const target = e.target as HTMLDivElement; - const height_offset = 40; - setIsWalletNameVisible(!(target.scrollTop > height_offset)); - }; - - if (is_mobile) { - el_mobile_dialog?.addEventListener('scroll', handleScroll); - setIsWalletNameVisible(true); - } - - return () => { - el_mobile_dialog?.removeEventListener('scroll', handleScroll); - }; - }, [active_tab_index, is_wallet_modal_visible, is_mobile]); + const contentScrollHandler = React.useCallback( + (e: React.UIEvent) => { + if (is_mobile && is_wallet_modal_visible) { + const target = e.target as HTMLDivElement; + setIsWalletNameVisible(!(target.scrollTop > 0)); + } + }, + [is_mobile, is_wallet_modal_visible] + ); - const body = ( - <> + return ( + { /> - - ); - - return ( - <> - - - {body} - - - - - {body} - - - + ); }); 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 new file mode 100644 index 000000000000..545e7a894e2c --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/__tests__/wallet-transfer.spec.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import WalletTransfer from '../wallet-transfer'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; + +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), + AmountInput: () =>
AmountInput
, + TransferAccountSelector: () =>
TransferAccountSelector
, +})); + +describe('WalletTransfer', () => { + const mocked_store = mockStore({}); + + it('Should render two amount inputs and two transfer account selectors', () => { + render( + + + + ); + + expect(screen.getAllByText('AmountInput').length).toBe(2); + expect(screen.getAllByText('TransferAccountSelector').length).toBe(2); + }); +}); diff --git a/packages/appstore/src/components/wallet-transfer/index.ts b/packages/appstore/src/components/wallet-transfer/index.ts new file mode 100644 index 000000000000..488f1606ef01 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/index.ts @@ -0,0 +1,3 @@ +import WalletTransfer from './wallet-transfer'; + +export default WalletTransfer; 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 new file mode 100644 index 000000000000..adabaee9acae --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/mock_accounts/mock_accounts.tsx @@ -0,0 +1,52 @@ +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', + balance: '0.00', + wallet_icon: 'IcCurrencyUsd', + icon: 'IcDxtradeDerived', + jurisdiction: , + type: 'fiat', + } as const) +); + +const wallets = [ + { + loginid: '18', + label: 'Demo USD Wallet', + currency: 'USD', + balance: '10,000.00', + wallet_icon: 'IcCurrencyUsd', + jurisdiction: , + type: 'fiat', + } as const, +]; + +export const transfer_accounts = { accounts, wallets }; diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss b/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss new file mode 100644 index 000000000000..67ecf58f9296 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer.scss @@ -0,0 +1,42 @@ +.wallet-transfer { + display: flex; + flex-direction: column; + row-gap: 3.2rem; + max-width: 65rem; + width: 100%; + margin: 9.6rem auto 4.8rem; + + @include mobile { + margin: 4.8rem auto; + } + + &__divider { + border: 0.5px solid $color-grey-5; + margin: 0.8rem 0; + } + + &__tile { + display: flex; + border: 1px solid $color-grey-5; + border-radius: $BORDER_RADIUS; + + .amount-input-wrapper { + border-radius: $BORDER_RADIUS; + flex-basis: 50%; + justify-content: space-between; + + @include mobile { + flex-basis: 60%; + } + } + + .transfer-account-selector { + border-radius: $BORDER_RADIUS; + flex-basis: 50%; + + @include mobile { + flex-basis: 40%; + } + } + } +} diff --git a/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx b/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx new file mode 100644 index 000000000000..b3d431c8c420 --- /dev/null +++ b/packages/appstore/src/components/wallet-transfer/wallet-transfer.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { AmountInput, TransferAccountSelector } from '@deriv/components'; +import { getDecimalPlaces } from '@deriv/shared'; +import { useStore, observer } 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 './wallet-transfer.scss'; + +type TAccount = React.ComponentProps['value']; + +type TWalletTransferProps = { + is_wallet_name_visible: boolean; + setIsWalletNameVisible: (value: boolean) => void; +}; + +const Divider = () =>
; + +const WalletTransfer = observer(({ is_wallet_name_visible, setIsWalletNameVisible }: TWalletTransferProps) => { + const { ui } = useStore(); + const { is_mobile } = ui; + + const [from_account, setFromAccount] = React.useState(transfer_accounts.wallets[0]); + const [to_account, setToAccount] = React.useState(); + const [from_amount, setFromAmount] = React.useState(); + const [to_amount, setToAmount] = React.useState(); + + const portal_id = is_mobile ? 'mobile_list_modal_root' : 'modal_root'; + + 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') { + setToAccount(undefined); + return { accounts: transfer_accounts.accounts, wallets: [] }; + } + 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]); + + return ( +
+
+ + + +
+
+ + + +
+
+ ); +}); + +export default WalletTransfer; diff --git a/packages/components/src/components/amount-input/amount-input.scss b/packages/components/src/components/amount-input/amount-input.scss index 33dd24cf1335..65d3bdb66108 100644 --- a/packages/components/src/components/amount-input/amount-input.scss +++ b/packages/components/src/components/amount-input/amount-input.scss @@ -2,12 +2,16 @@ display: flex; flex-direction: column; padding: 0.8rem; - height: 6.6rem; + height: auto; } .amount-input-container { position: relative; - flex: 1; + height: 2.8rem; + + @include mobile { + height: 2.4rem; + } } .amount-input { diff --git a/packages/components/src/components/amount-input/amount-input.tsx b/packages/components/src/components/amount-input/amount-input.tsx index bd3431374c29..337a519e9037 100644 --- a/packages/components/src/components/amount-input/amount-input.tsx +++ b/packages/components/src/components/amount-input/amount-input.tsx @@ -113,6 +113,8 @@ const AmountInput = ({ type='text' inputMode='numeric' value={`${displayNumber(value)} ${currency}`} + // Temporary workaround to avoid jest warnings + onChange={() => undefined} /> string; title?: string; visible?: boolean; wrapper_classname?: string; - header_classname?: string; - has_full_height?: boolean; - footer?: React.ReactNode; - has_close_icon?: boolean; }; -const MobileDialog = React.forwardRef>((props, ref) => { +const MobileDialog = (props: React.PropsWithChildren) => { const { - title, - visible, children, - className, + footer, + has_close_icon = true, has_full_height, + header_classname, portal_element_id, renderTitle, + title, + visible, wrapper_classname, - footer, - has_close_icon = true, - header_classname, } = props; const footer_ref = React.useRef(null); @@ -95,12 +93,7 @@ const MobileDialog = React.forwardRef -
+
, portal_element ); -}); - -MobileDialog.displayName = 'Mobile Dialog'; +}; export default MobileDialog; diff --git a/packages/components/src/components/modal/modal.tsx b/packages/components/src/components/modal/modal.tsx index 526640ed61bd..dcaa5aa319b5 100644 --- a/packages/components/src/components/modal/modal.tsx +++ b/packages/components/src/components/modal/modal.tsx @@ -28,6 +28,7 @@ type TModalElement = { is_vertical_bottom?: boolean; is_vertical_centered?: boolean; is_vertical_top?: boolean; + min_height?: string; onMount?: () => void; onUnmount?: () => void; portalId?: string; @@ -56,6 +57,7 @@ const ModalElement = ({ is_vertical_bottom, is_vertical_centered, is_vertical_top, + min_height, onMount, onUnmount, portalId, @@ -144,6 +146,7 @@ const ModalElement = ({ style={{ height: height || 'auto', width: width || 'auto', + minHeight: min_height || 0, }} > {!is_risk_warning_visible && (header || title || rendered_title) && ( @@ -209,6 +212,7 @@ type TModal = TModalElement & { exit_classname?: string; onEntered?: () => void; onExited?: () => void; + transition_timeout?: React.ComponentProps['timeout']; }; const Modal = ({ @@ -229,6 +233,7 @@ const Modal = ({ is_vertical_bottom, is_vertical_centered, is_vertical_top, + min_height, onEntered, onExited, onMount, @@ -238,13 +243,14 @@ const Modal = ({ should_header_stick_body = true, small, title, + transition_timeout, toggleModal, width, }: React.PropsWithChildren) => ( 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 new file mode 100644 index 000000000000..b655830e02ee --- /dev/null +++ b/packages/components/src/components/transfer-account-selector/__tests__/transfer-account-selector.spec.tsx @@ -0,0 +1,53 @@ +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 new file mode 100644 index 000000000000..7e5cb2ae2281 --- /dev/null +++ b/packages/components/src/components/transfer-account-selector/__tests__/transfer-tile.spec.tsx @@ -0,0 +1,60 @@ +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/index.ts b/packages/components/src/components/transfer-account-selector/index.ts index 24075df81977..50a91c417029 100644 --- a/packages/components/src/components/transfer-account-selector/index.ts +++ b/packages/components/src/components/transfer-account-selector/index.ts @@ -1,3 +1,3 @@ import TransferAccountSelector from './transfer-account-selector'; -export { TransferAccountSelector }; +export default TransferAccountSelector; diff --git a/packages/components/src/components/transfer-account-selector/transfer-account-list.tsx b/packages/components/src/components/transfer-account-selector/transfer-account-list.tsx new file mode 100644 index 000000000000..b8721af349d3 --- /dev/null +++ b/packages/components/src/components/transfer-account-selector/transfer-account-list.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import classNames from 'classnames'; +import Text from '../text'; +import { Localize, localize } from '@deriv/translations'; +import { WalletTile } from '../wallet-tile'; +import type { TTransferAccount } from './transfer-account-selector'; + +type TTransferAccountList = { + is_mobile?: boolean; + selected_account?: TTransferAccount; + setIsListModalOpen: (value: boolean) => void; + setSelectedAccount: React.Dispatch>; + transfer_accounts: { [k: string]: TTransferAccount[] }; + transfer_hint?: string | JSX.Element; + wallet_name?: string; +}; + +const TitleLine = () =>
; + +const TransferAccountList = ({ + is_mobile, + selected_account, + setIsListModalOpen, + setSelectedAccount, + transfer_accounts, + transfer_hint, + wallet_name, +}: TTransferAccountList) => { + const is_single_list = React.useMemo( + () => Object.keys(transfer_accounts).filter(key => transfer_accounts[key].length > 0).length === 1, + [transfer_accounts] + ); + + return ( +
+ {Object.keys(transfer_accounts).map((key, idx) => { + if (transfer_accounts[key].length === 0) return null; + + return ( + +
+
+ + {key === 'accounts' ? ( + + ) : ( + {localize('Wallets')} + )} + + +
+
+ {transfer_accounts[key].map((account, index) => ( + { + setSelectedAccount(account); + setIsListModalOpen(false); + }} + /> + ))} +
+
+ {transfer_hint && ( + + {transfer_hint} + + )} +
+ ); + })} +
+ ); +}; +export default React.memo(TransferAccountList); diff --git a/packages/components/src/components/transfer-account-selector/transfer-account-selector.scss b/packages/components/src/components/transfer-account-selector/transfer-account-selector.scss index 646cbc28a987..fd96b6e38c89 100644 --- a/packages/components/src/components/transfer-account-selector/transfer-account-selector.scss +++ b/packages/components/src/components/transfer-account-selector/transfer-account-selector.scss @@ -1,63 +1,173 @@ .transfer-account-selector { - cursor: pointer; + display: flex; + align-items: center; width: 100%; background-color: $color-white; padding: 0.8rem; - - &__label { - display: block; - } + cursor: pointer; &__value { padding: 0; } + &__chevron-icon { + display: flex; + margin-left: 1rem; + + @include mobile { + margin-left: auto; + } + } + &__heading { display: flex; - align-items: center; - justify-content: space-between; margin-bottom: 0.4rem; } - &__chevron-icon { - margin-left: 1rem; + &__heading-with-chevron { + display: flex; } &__content { display: flex; - align-items: center; - justify-content: space-between; + flex-direction: column; + align-items: flex-start; + margin-right: auto; + width: 100%; + + @include mobile { + align-items: unset; + } } &__list { - margin-bottom: 0.8rem; - border-bottom: 2px solid $color-grey-2; + border-bottom: 4px solid $color-grey-2; - &--is-last { + &__container { + @include mobile { + padding: 0 1.6rem 1.6rem; + } + + .transfer-hint { + margin: 1.6rem 0; + padding: 0 0.8rem; + + @include mobile { + margin: 0.8rem 0; + padding: 0; + } + } + } + + &--is-last, + &--is-single, + &--is-mobile { border-bottom: none; - margin-bottom: 0; + } + + &--is-last:is(&--is-mobile) { + margin-bottom: 0.8rem; } &-items { + margin-bottom: 1.6rem; padding: 0 0.8rem; + + @include mobile { + margin-bottom: 0; + padding: 0; + } } &-header { - display: block; - padding: 1.6rem 2.8rem; + display: flex; + padding: 1.6rem 2.4rem 0.8rem; + + @include mobile { + padding: 0.8rem 0.8rem 0.4rem; + + &__title-line { + flex-grow: 1; + border-bottom: 1px solid $color-grey-2; + margin: 0 0 0.65rem 1rem; + } + } } &-tile { + padding: 1rem 2rem; + flex-direction: row; + align-items: center; cursor: pointer; - &--is-last { - margin-bottom: 1.6rem; + @include mobile { + border-radius: unset; + padding: 0.8rem; + } + + .wallet-tile__icon { + margin-right: 1.6rem; + + @include mobile { + margin-right: 0.8rem; + } } } } } // Overwrite modal style +.dc-modal__container_transfer-account-selector__modal-header { + max-width: 40rem; + max-height: 52.8rem !important; + + @include mobile { + max-width: unset; + max-height: unset !important; + } +} + .dc-modal-header--transfer-account-selector__modal-header { border-bottom: 2px solid $color-grey-2; } + +#mobile_list_modal_root { + position: absolute; + inset: 0; + z-index: 2; + display: none; + opacity: 0; + + &:not(:empty) { + display: flex; + opacity: 1; + } + + .dc-modal { + width: 100vw; + + &__container { + max-width: unset !important; + border-radius: unset; + box-shadow: unset; + } + + &-header { + border: none; + padding: 1.6rem 2.4rem 0rem; + + &__title { + font-size: var(--text-size-xxs); + padding: 0; + margin-bottom: 0; + } + + &__close { + padding: 0; + margin: 0; + height: auto; + width: auto; + } + } + } +} diff --git a/packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx b/packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx index 3ce3c1e5a022..d2b65de5a1c8 100644 --- a/packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx +++ b/packages/components/src/components/transfer-account-selector/transfer-account-selector.tsx @@ -1,130 +1,113 @@ import React from 'react'; -import classNames from 'classnames'; -import { isMobile } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; -import Text from '../text'; -import { WalletTile } from '../wallet-tile'; import Modal from '../modal'; -import Icon from '../icon'; +import Div100vhContainer from '../div100vh-container'; +import ThemedScrollbars from '../themed-scrollbars'; +import TransferAccountList from './transfer-account-list'; +import TransferTile from './transfer-tile'; +import { WalletTile } from '../wallet-tile'; import './transfer-account-selector.scss'; -type TTransferAccount = React.ComponentProps['account']; +export type TTransferAccount = React.ComponentProps['account']; type TTransferAccountSelectorProps = { + is_mobile?: boolean; + is_wallet_name_visible?: boolean; label?: string; + onSelectAccount?: (account: TTransferAccount) => void; placeholder?: string; portal_id?: string; + setIsWalletNameVisible?: (value: boolean) => void; transfer_accounts: { [k: string]: TTransferAccount[] }; + transfer_hint?: string | JSX.Element; value?: TTransferAccount; wallet_name?: string; }; -const ChevronIcon = ({ className }: { className?: string }) => { - return ( - - - - ); -}; - const TransferAccountSelector = ({ + is_mobile, + is_wallet_name_visible, label, + onSelectAccount, placeholder, portal_id, + setIsWalletNameVisible, transfer_accounts = {}, + transfer_hint, value, wallet_name, }: TTransferAccountSelectorProps) => { 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]); + const openAccountsList = () => { setIsListModalOpen(true); }; - return ( -
-
- - {label} - + const contentScrollHandler = React.useCallback( + (e: React.UIEvent) => { + if (is_mobile && is_list_modal_open) { + const target = e.target as HTMLDivElement; + setIsWalletNameVisible?.(!(target.scrollTop > 0)); + } + }, + [is_list_modal_open, is_mobile, setIsWalletNameVisible] + ); - {isMobile() && } -
+ const getHeightOffset = React.useCallback(() => { + const header_height = '16.2rem'; + const collapsed_header_height = '12.2rem'; + return is_wallet_name_visible ? header_height : collapsed_header_height; + }, [is_wallet_name_visible]); -
- {!selected_account && ( - - {placeholder} - - )} - {selected_account && ( - - )} + return ( +
+ - {!isMobile() && } -
+
setIsWalletNameVisible?.(true)} portalId={portal_id} + transition_timeout={is_mobile ? { enter: 250, exit: 0 } : 250} title={label} toggleModal={() => setIsListModalOpen(old => !old)} > -
- {Object.keys(transfer_accounts).map((key, idx) => { - return ( - -
- - - {key === 'accounts' ? ( - - ) : ( - {localize('Wallets')} - )} - - -
- {transfer_accounts[key].map((account, index) => ( - { - setSelectedAccount(account); - setIsListModalOpen(false); - }} - /> - ))} -
-
-
- ); - })} -
+ + + + +
); diff --git a/packages/components/src/components/transfer-account-selector/transfer-tile.tsx b/packages/components/src/components/transfer-account-selector/transfer-tile.tsx new file mode 100644 index 000000000000..8f8a4f89bdbf --- /dev/null +++ b/packages/components/src/components/transfer-account-selector/transfer-tile.tsx @@ -0,0 +1,58 @@ +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-tile/__tests__/wallet-tile.spec.tsx b/packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx new file mode 100644 index 000000000000..1dd579a9e04f --- /dev/null +++ b/packages/components/src/components/wallet-tile/__tests__/wallet-tile.spec.tsx @@ -0,0 +1,86 @@ +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/wallet-tile.scss b/packages/components/src/components/wallet-tile/wallet-tile.scss index 36befc9e83fd..26124f753852 100644 --- a/packages/components/src/components/wallet-tile/wallet-tile.scss +++ b/packages/components/src/components/wallet-tile/wallet-tile.scss @@ -3,25 +3,17 @@ align-items: center; justify-content: flex-start; background-color: $color-white; - padding: 1rem 2rem; border-radius: $BORDER_RADIUS; flex-grow: 1; - &--mobile { + @include mobile { flex-direction: column; align-items: flex-start; - .wallet-tile { - &__header { - display: flex; - justify-content: flex-start; - align-items: flex-end; - margin-bottom: 0.4rem; - } - - &__icon { - margin-right: 0.4rem; - } + &__icon-with-badge { + display: flex; + align-items: end; + margin-bottom: 0.4rem; } } @@ -36,14 +28,17 @@ } &__icon { - margin-right: 1.6rem; + margin-right: 0.8rem; min-width: 4rem; + + @include mobile { + margin-right: 0.4rem; + } } &__content { display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; flex-grow: 1; } } diff --git a/packages/components/src/components/wallet-tile/wallet-tile.tsx b/packages/components/src/components/wallet-tile/wallet-tile.tsx index 7c73d893d68c..c96c4efac87f 100644 --- a/packages/components/src/components/wallet-tile/wallet-tile.tsx +++ b/packages/components/src/components/wallet-tile/wallet-tile.tsx @@ -1,18 +1,17 @@ import React from 'react'; import classNames from 'classnames'; +import Text from '../text'; import { getCurrencyDisplayCode } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import './wallet-tile.scss'; import { AppLinkedWithWalletIcon } from '../app-linked-with-wallet-icon'; import { WalletIcon } from '../wallet-icon'; -import Text from '../text'; -import Badge from '../badge'; +import './wallet-tile.scss'; type TAccount = { balance?: string; currency: string; icon?: string; - jurisdiction?: string; + jurisdiction?: JSX.Element; label?: string; loginid: string; type: 'fiat' | 'crypto'; @@ -93,7 +92,7 @@ const WalletTile = ({ else size = is_mobile ? 'xxxs' : 'xxs'; return ( - + {localize('Balance')}: {account.balance} {getCurrencyDisplayCode(account.currency)} ); @@ -102,58 +101,31 @@ const WalletTile = ({ return null; }; - const JurisdictionBadge = () => { - if (account.jurisdiction) { - return ; - } - - return null; - }; - - if (is_mobile) { - return ( -
onClick?.()} - > -
-
- -
- -
- -
- ); - } - return (
onClick?.()} > -
- +
+
+ +
+ + {is_value && is_mobile && account.jurisdiction}
-
-
- - +
+ + {!is_value && account.jurisdiction}
); }; -export default WalletTile; +export default React.memo(WalletTile); diff --git a/packages/components/src/index.js b/packages/components/src/index.js index d8bb153bddfc..7178e6149e43 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -107,6 +107,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 { default as TickPicker } from './components/tick-picker'; export { default as TickProgress } from './components/tick-progress'; export { default as Timeline } from './components/timeline'; diff --git a/packages/core/src/Stores/ui-store.js b/packages/core/src/Stores/ui-store.js index 40731111e540..872edbf3a439 100644 --- a/packages/core/src/Stores/ui-store.js +++ b/packages/core/src/Stores/ui-store.js @@ -168,7 +168,7 @@ export default class UIStore extends BaseStore { should_show_assessment_complete_modal = false; app_contents_scroll_ref = null; is_deriv_account_needed_modal_visible = false; - is_wallet_modal_visible = true; + is_wallet_modal_visible = false; is_ready_to_deposit_modal_visible = false; is_need_real_account_for_cashier_modal_visible = false; is_switch_to_deriv_account_modal_visible = false;