diff --git a/packages/api/jest.config.js b/packages/api/jest.config.js index f53d37480f70..207910d7e732 100644 --- a/packages/api/jest.config.js +++ b/packages/api/jest.config.js @@ -1 +1,5 @@ -module.exports = require('../../jest.config.base'); +const baseConfigForPackages = require('../../jest.config.base'); + +module.exports = { + ...baseConfigForPackages, +}; diff --git a/packages/cashier/src/components/account-prompt-dialog/__tests__/account-prompt-dialog.spec.tsx b/packages/cashier/src/components/account-prompt-dialog/__tests__/account-prompt-dialog.spec.tsx deleted file mode 100644 index ff1ecdc3bb71..000000000000 --- a/packages/cashier/src/components/account-prompt-dialog/__tests__/account-prompt-dialog.spec.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { mockStore } from '@deriv/stores'; -import AccountPromptDialog from '../account-prompt-dialog'; -import CashierProviders from '../../../cashier-providers'; - -jest.mock('@deriv/components', () => ({ - ...jest.requireActual('@deriv/components'), - Dialog: () =>
Dialog
, -})); - -describe('', () => { - const mock_root_store = mockStore({ - client: { - accounts: { - CR90000001: { is_virtual: 0, currency: 'USD' }, - CR90000002: { is_virtual: 0, currency: 'BTC' }, - }, - }, - modules: { - cashier: { - account_prompt_dialog: { - continueRoute: jest.fn(), - is_confirmed: false, - last_location: '', - onCancel: jest.fn(), - onConfirm: jest.fn(), - should_show: true, - }, - }, - }, - }); - it('should render dialog', () => { - render(, { - wrapper: ({ children }) => {children}, - }); - - expect(screen.getByText('Dialog')).toBeInTheDocument(); - }); -}); diff --git a/packages/cashier/src/components/account-prompt-dialog/account-prompt-dialog.tsx b/packages/cashier/src/components/account-prompt-dialog/account-prompt-dialog.tsx deleted file mode 100644 index 3730ad1a65f6..000000000000 --- a/packages/cashier/src/components/account-prompt-dialog/account-prompt-dialog.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { Dialog } from '@deriv/components'; -import { isCryptocurrency } from '@deriv/shared'; -import { localize, Localize } from '@deriv/translations'; -import { useStore, observer } from '@deriv/stores'; -import { useCashierStore } from '../../stores/useCashierStores'; - -const AccountPromptDialog = observer(() => { - const { client } = useStore(); - const { accounts } = client; - const { account_prompt_dialog } = useCashierStore(); - const { continueRoute, is_confirmed, last_location, onCancel, onConfirm, should_show } = account_prompt_dialog; - - React.useEffect(continueRoute, [is_confirmed, last_location, continueRoute]); - - const non_crypto_account_loginid = React.useMemo( - () => - Object.entries(accounts).reduce((initial_value, [loginid, settings]) => { - return !settings.is_virtual && !isCryptocurrency(settings.currency || '') ? loginid : initial_value; - }, ''), - [accounts] - ); - - const non_crypto_currency = non_crypto_account_loginid && accounts[non_crypto_account_loginid].currency; - - return ( - - - - ); -}); - -export default AccountPromptDialog; diff --git a/packages/cashier/src/components/account-prompt-dialog/index.ts b/packages/cashier/src/components/account-prompt-dialog/index.ts deleted file mode 100644 index 96805fec7b2c..000000000000 --- a/packages/cashier/src/components/account-prompt-dialog/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import AccountPromptDialog from './account-prompt-dialog'; - -export default AccountPromptDialog; diff --git a/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding-details.spec.tsx b/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding-details.spec.tsx deleted file mode 100644 index f8aa21047d28..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding-details.spec.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; -import CashierOnboardingDetails from '../cashier-onboarding-details'; -import type { TCashierOnboardingProvider } from '../cashier-onboarding-providers'; - -describe('', () => { - let props: TCashierOnboardingProvider; - beforeEach(() => { - props = { - detail_click: jest.fn(), - detail_description: 'Deposit via the following payment methods:', - detail_header: 'Deposit via bank wire, credit card, and e-wallet', - is_dark_mode_on: false, - is_mobile: false, - detail_contents: [ - { - icons: [ - { - dark: 'IcWalletCreditDebitDark', - light: 'IcWalletCreditDebitLight', - }, - ], - }, - ], - }; - }); - - it('should show the proper messages', () => { - render(); - - expect(screen.getByText('Deposit via bank wire, credit card, and e-wallet')).toBeInTheDocument(); - expect(screen.getByText('Deposit via the following payment methods:')).toBeInTheDocument(); - }); - - it('should show contain the correct className, when detail_contents has icons', () => { - render(); - - expect(screen.getByTestId('dt_cashier_onboarding_detail_array')).toBeInTheDocument(); - }); - - it('should trigger onClick callback, when the user clicks on the block with details', () => { - render(); - - const details_block = screen.getByTestId('dt_cashier_onboarding_detail_div'); - fireEvent.click(details_block); - - expect(props.detail_click).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding-side-note.spec.tsx b/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding-side-note.spec.tsx deleted file mode 100644 index a86fb9b09ca1..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding-side-note.spec.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { fireEvent, render, screen } from '@testing-library/react'; -import CashierOnboardingSideNote from '../cashier-onboarding-side-note'; -import { mockStore } from '@deriv/stores'; -import CashierProviders from '../../../cashier-providers'; - -describe('', () => { - let mockRootStore: ReturnType; - beforeEach(() => { - mockRootStore = mockStore({ - client: { - currency: 'USD', - }, - ui: { - openRealAccountSignup: jest.fn(), - }, - modules: { - cashier: { - general_store: { - setDepositTarget: jest.fn(), - }, - }, - }, - }); - }); - - const props = { - is_crypto: false, - }; - - const renderCashierOnboardingSideNote = () => - render(, { - wrapper: ({ children }) => {children}, - }); - - it('should show the proper messages, with fiat currency and can_change_fiat_currency={false} property', () => { - if (mockRootStore.client) mockRootStore.client.loginid = 'CR12345678'; - renderCashierOnboardingSideNote(); - - expect(screen.getByText('This is your USD account CR12345678')).toBeInTheDocument(); - expect(screen.getByTestId('dt_side_note_text')).toHaveTextContent( - 'If you want to change your account currency, please contact us via live chat.' - ); - }); - - it('should trigger onClick callback when the client clicks the "live chat" link', () => { - window.LC_API = { - on_chat_ended: jest.fn(), - open_chat_window: jest.fn(), - }; - renderCashierOnboardingSideNote(); - - const live_chat_link = screen.getByText('live chat'); - fireEvent.click(live_chat_link); - expect(window.LC_API.open_chat_window).toHaveBeenCalledTimes(1); - }); - - it('should show the proper messages when is_crypto is true', () => { - if (mockRootStore.client) mockRootStore.client.currency = 'BTC'; - if (mockRootStore.client) mockRootStore.client.loginid = 'CR12345678'; - props.is_crypto = true; - - renderCashierOnboardingSideNote(); - - expect(screen.getByText('This is your BTC account CR12345678')).toBeInTheDocument(); - expect( - screen.getByText("Don't want to trade in BTC? You can open another cryptocurrency account.") - ).toBeInTheDocument(); - expect(screen.getByText('Manage your accounts')).toBeInTheDocument(); - }); - - it('should trigger onClick callbacks when the client clicks on "Manage your accounts" link', () => { - if (mockRootStore.client) mockRootStore.client.currency = 'BTC'; - props.is_crypto = true; - - renderCashierOnboardingSideNote(); - - fireEvent.click(screen.getByTestId('dt_cashier_onboarding_side_note_link')); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding.spec.tsx b/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding.spec.tsx deleted file mode 100644 index 6bdf53a8a90c..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/__tests__/cashier-onboarding.spec.tsx +++ /dev/null @@ -1,400 +0,0 @@ -import React from 'react'; -import { createBrowserHistory } from 'history'; -import { fireEvent, render, screen } from '@testing-library/react'; -import CashierOnboarding from '../cashier-onboarding'; -import { Router } from 'react-router'; -import { routes } from '@deriv/shared'; -import { mockStore } from '@deriv/stores'; -import CashierProviders from '../../../cashier-providers'; - -jest.mock('@deriv/hooks', () => { - return { - ...jest.requireActual('@deriv/hooks'), - usePaymentAgentList: jest.fn(() => ({ data: ['PA1', 'PA2'], isLoading: false })), - useIsP2PEnabled: jest.fn(() => ({ data: true, isLoading: false, isSuccess: true })), - useHasUSDCurrency: jest.fn(() => true), - }; -}); - -describe('', () => { - let mockRootStore: ReturnType; - beforeEach(() => { - mockRootStore = mockStore({ - client: { - accounts: { CR90000001: { is_virtual: 0, currency: 'USD' } }, - account_status: { - cashier_validation: [], - }, - loginid: undefined, - current_currency_type: 'crypto', - is_deposit_lock: false, - is_withdrawal_lock: true, - is_identity_verification_needed: false, - is_switching: false, - is_landing_company_loaded: true, - currency: 'USD', - available_crypto_currencies: ['BTC', 'ETH'], - account_list: [], - is_crypto: jest.fn(), - }, - common: { - is_from_derivgo: false, - }, - ui: { - openRealAccountSignup: jest.fn(), - shouldNavigateAfterChooseCrypto: jest.fn(), - toggleSetCurrencyModal: jest.fn(), - is_mobile: false, - }, - modules: { - cashier: { - general_store: { - onMountCashierOnboarding: jest.fn(), - setDepositTarget: jest.fn(), - setIsCashierOnboarding: jest.fn(), - setIsDeposit: jest.fn(), - setShouldShowAllAvailableCurrencies: jest.fn(), - has_set_currency: true, - }, - account_prompt_dialog: { - shouldNavigateAfterPrompt: jest.fn(), - }, - payment_agent: { - is_payment_agent_visible_in_onboarding: true, - }, - }, - }, - }); - }); - - const props = () => ({ - setSideNotes: jest.fn(), - }); - - const history = createBrowserHistory(); - - const renderCashierOnboarding = () => - render(, { - wrapper: ({ children }) => {children}, - }); - - const renderCashierOnboardingWithRouter = () => - render( - - - , - { - wrapper: ({ children }) => {children}, - } - ); - - it('should show the proper messages when is rendered with fiat account', () => { - renderCashierOnboarding(); - - expect(screen.getByText('Choose a way to fund your account')).toBeInTheDocument(); - expect( - screen.getByText('Please note that some payment methods might not be available in your country.') - ).toBeInTheDocument(); - expect(screen.getByText('Deposit via bank wire, credit card, and e-wallet')).toBeInTheDocument(); - expect(screen.getByText('Deposit via the following payment methods:')).toBeInTheDocument(); - expect(screen.getByText('Deposit cryptocurrencies')).toBeInTheDocument(); - expect(screen.getByText('We accept the following cryptocurrencies:')).toBeInTheDocument(); - expect(screen.getByText('Buy cryptocurrencies via fiat onramp')).toBeInTheDocument(); - expect(screen.getByText('Choose any of these exchanges to buy cryptocurrencies:')).toBeInTheDocument(); - expect(screen.getByText('Deposit via payment agents')).toBeInTheDocument(); - expect( - screen.getByText( - 'Deposit in your local currency via an authorised, independent payment agent in your country.' - ) - ).toBeInTheDocument(); - expect(screen.getByText('Deposit with Deriv P2P')).toBeInTheDocument(); - expect( - screen.getByText( - 'Deposit with your local currency via peer-to-peer exchange with fellow traders in your country.' - ) - ).toBeInTheDocument(); - }); - - it('should show the proper message when is rendered with crypto account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000002: { is_virtual: 0, currency: 'BTC' } }; - if (mockRootStore.client) mockRootStore.client.currency = 'BTC'; - renderCashierOnboarding(); - - expect(screen.getByText('Buy cryptocurrencies')).toBeInTheDocument(); - expect(screen.queryByText('Buy cryptocurrencies via fiat onramp')).not.toBeInTheDocument(); - }); - - it('should trigger proper callbacks when the client chooses "Deposit via bank wire, credit card, and e-wallet" section from his fiat account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - if (mockRootStore.client) mockRootStore.client.currency = 'USD'; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_bank_card_ewallet_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('Deposit via the following payment methods:') - ); - if (deposit_bank_card_ewallet_detail_div) fireEvent.click(deposit_bank_card_ewallet_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.modules?.cashier?.general_store?.setIsDeposit).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Deposit via bank wire, credit card, and e-wallet" section from his crypto account, not having the fiat account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000002: { is_virtual: 0, currency: 'BTC' } }; - if (mockRootStore.client) mockRootStore.client.currency = 'BTC'; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_bank_card_ewallet_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('Deposit via the following payment methods:') - ); - if (deposit_bank_card_ewallet_detail_div) fireEvent.click(deposit_bank_card_ewallet_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Deposit via bank wire, credit card, and e-wallet" section from his crypto account, having the fiat account', () => { - if (mockRootStore.client) { - mockRootStore.client.accounts = { - CR90000001: { is_virtual: 0, currency: 'USD' }, - CR90000002: { is_virtual: 0, currency: 'BTC' }, - }; - mockRootStore.client.currency = 'BTC'; - } - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_bank_card_ewallet_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('Deposit via the following payment methods:') - ); - if (deposit_bank_card_ewallet_detail_div) fireEvent.click(deposit_bank_card_ewallet_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.modules?.cashier?.account_prompt_dialog?.shouldNavigateAfterPrompt).toHaveBeenCalledTimes( - 1 - ); - }); - - it('should trigger proper callbacks when the client chooses "Deposit cryptocurrencies" section from his fiat account, not having the crypto account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_crypto_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('We accept the following cryptocurrencies:') - ); - if (deposit_crypto_detail_div) fireEvent.click(deposit_crypto_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Deposit cryptocurrencies" section from his crypto account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000002: { is_virtual: 0, currency: 'BTC' } }; - if (mockRootStore.client) mockRootStore.client.currency = 'BTC'; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_crypto_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('We accept the following cryptocurrencies:') - ); - if (deposit_crypto_detail_div) fireEvent.click(deposit_crypto_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.shouldNavigateAfterChooseCrypto).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Buy cryptocurrencies via fiat onramp" section from his fiat account, not having the crypto account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const buy_crypto_onramp_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('Choose any of these exchanges to buy cryptocurrencies:') - ); - if (buy_crypto_onramp_detail_div) fireEvent.click(buy_crypto_onramp_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Buy cryptocurrencies" section from his crypto account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000002: { is_virtual: 0, currency: 'BTC' } }; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const buy_crypto_onramp_detail_div = Array.from(node_list).find(node => - node.textContent?.includes('Choose any of these exchanges to buy cryptocurrencies:') - ); - if (buy_crypto_onramp_detail_div) fireEvent.click(buy_crypto_onramp_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.shouldNavigateAfterChooseCrypto).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Deposit via payment agents" section', () => { - if (mockRootStore.client) { - mockRootStore.client.accounts = { - CR90000001: { is_virtual: 0, currency: 'USD' }, - CR90000002: { is_virtual: 0, currency: 'BTC' }, - }; - } - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_via_pa_detail_div = Array.from(node_list).find(node => - node.textContent?.includes( - 'Deposit in your local currency via an authorised, independent payment agent in your country.' - ) - ); - if (deposit_via_pa_detail_div) fireEvent.click(deposit_via_pa_detail_div); - - expect( - mockRootStore.modules?.cashier?.general_store?.setShouldShowAllAvailableCurrencies - ).toHaveBeenCalledTimes(1); - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - }); - - it('should trigger proper callbacks when the client chooses "Deposit with Deriv P2P" section from his fiat account', () => { - if (mockRootStore.client) { - mockRootStore.client.accounts = { - CR90000001: { is_virtual: 0, currency: 'USD' }, - CR90000002: { is_virtual: 0, currency: 'BTC' }, - }; - } - - renderCashierOnboardingWithRouter(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_with_dp2p_detail_div = Array.from(node_list).find(node => - node.textContent?.includes( - 'Deposit with your local currency via peer-to-peer exchange with fellow traders in your country.' - ) - ); - if (deposit_with_dp2p_detail_div) fireEvent.click(deposit_with_dp2p_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(history.location.pathname).toBe(routes.cashier_p2p); - }); - - it('should trigger proper callbacks when the client chooses "Deposit with Deriv P2P" section from his crypto account, already having the fiat account', () => { - if (mockRootStore.client) { - mockRootStore.client.accounts = { - CR90000001: { is_virtual: 0, currency: 'USD' }, - CR90000002: { is_virtual: 0, currency: 'BTC' }, - }; - mockRootStore.client.currency = 'BTC'; - } - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_with_dp2p_detail_div = Array.from(node_list).find(node => - node.textContent?.includes( - 'Deposit with your local currency via peer-to-peer exchange with fellow traders in your country.' - ) - ); - if (deposit_with_dp2p_detail_div) fireEvent.click(deposit_with_dp2p_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.modules?.cashier?.account_prompt_dialog?.shouldNavigateAfterPrompt).toHaveBeenCalledTimes( - 1 - ); - }); - - it('should trigger proper callbacks when the client chooses "Deposit with Deriv P2P" section from his crypto account, not having the fiat account', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000002: { is_virtual: 0, currency: 'BTC' } }; - if (mockRootStore.client) mockRootStore.client.currency = 'BTC'; - - renderCashierOnboarding(); - - const node_list = screen.getAllByTestId('dt_cashier_onboarding_detail_div'); - const deposit_with_dp2p_detail_div = Array.from(node_list).find(node => - node.textContent?.includes( - 'Deposit with your local currency via peer-to-peer exchange with fellow traders in your country.' - ) - ); - if (deposit_with_dp2p_detail_div) fireEvent.click(deposit_with_dp2p_detail_div); - - expect(mockRootStore.modules?.cashier?.general_store?.setDepositTarget).toHaveBeenCalledTimes(1); - expect(mockRootStore.ui?.openRealAccountSignup).toHaveBeenCalledTimes(1); - }); - - it('should show the "Learn more about payment methods" message in Mobile mode', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - if (mockRootStore.ui) mockRootStore.ui.is_mobile = true; - - renderCashierOnboarding(); - - expect(screen.getByText('Learn more about payment methods')).toBeInTheDocument(); - }); - - it('should trigger onClick callback when the user clicks "Learn more about payment methods" message in Mobile mode', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - if (mockRootStore.ui) mockRootStore.ui.is_mobile = true; - window.open = jest.fn(); - - renderCashierOnboarding(); - - const link = screen.getByTestId('dt_cashier_onboarding_header_learn_more'); - fireEvent.click(link); - - expect(window.open).toHaveBeenCalledTimes(1); - }); - - it('should not show "Choose a way to fund your account" message if is_switching is true', () => { - if (mockRootStore.client) mockRootStore.client.is_switching = true; - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - - renderCashierOnboarding(); - - expect(screen.queryByText('Choose a way to fund your account')).not.toBeInTheDocument(); - }); - - it('should not show "Choose a way to fund your account" message if accounts_list is an empty array', () => { - if (mockRootStore.client?.accounts) mockRootStore.client.accounts = {}; - - renderCashierOnboarding(); - - expect(screen.queryByText('Choose a way to fund your account')).not.toBeInTheDocument(); - }); - - it('should not show "Choose a way to fund your account" message if is_landing_company_loaded is false', () => { - if (mockRootStore.client) mockRootStore.client.accounts = { CR90000001: { is_virtual: 0, currency: 'USD' } }; - if (mockRootStore.client) mockRootStore.client.is_landing_company_loaded = false; - - renderCashierOnboarding(); - - expect(screen.queryByText('Choose a way to fund your account')).not.toBeInTheDocument(); - }); - - it('should redirect to "routes.trade" when the component will unmount', () => { - if (mockRootStore.client) { - mockRootStore.client.accounts = { - CR90000001: { is_virtual: 0, currency: 'USD' }, - CR90000002: { is_virtual: 0, currency: 'BTC' }, - }; - } - if (mockRootStore.modules?.cashier?.general_store) { - mockRootStore.modules.cashier.general_store.has_set_currency = false; - } - - const { unmount } = renderCashierOnboardingWithRouter(); - unmount(); - - expect(history.location.pathname).toBe(routes.trade); - expect(mockRootStore.ui?.toggleSetCurrencyModal).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-details.tsx b/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-details.tsx deleted file mode 100644 index 9a120641f620..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-details.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import { Icon, NewsTicker, Text } from '@deriv/components'; -import type { TCashierOnboardingProvider } from './cashier-onboarding-providers'; -import './cashier-onboarding.scss'; - -const CashierOnboardingDetails = ({ - detail_click, - detail_contents, - detail_description, - detail_header, - is_dark_mode_on, - is_mobile, -}: TCashierOnboardingProvider) => { - return ( -
- - {detail_header} - -
-
- - {detail_description} - - -
- {detail_contents?.map((content, id) => ( -
-
- -
- {content.icons?.map((icon, index) => { - return ( -
- -
- ); - })} -
-
-
-
- ))} -
-
- ); -}; - -export default CashierOnboardingDetails; diff --git a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-providers.ts b/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-providers.ts deleted file mode 100644 index 5885c1ba9b16..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-providers.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { localize } from '@deriv/translations'; - -export type TCashierOnboardingProvider = { - detail_click: VoidFunction; - detail_contents?: { - icons: { light: string; dark: string }[]; - }[]; - detail_description: string; - detail_header: string; - is_dark_mode_on?: boolean; - is_mobile?: boolean; -}; - -const cash_contents = [ - { - icons: [ - { light: 'IcWalletCreditDebitLight', dark: 'IcWalletCreditDebitDark' }, - { light: 'IcCashierInstantBankTransferLight', dark: 'IcCashierInstantBankTransferDark' }, - { light: 'IcCashierEwalletLight', dark: 'IcCashierEwalletDark' }, - { light: 'IcCashierLocalPaymentMethodsLight', dark: 'IcCashierLocalPaymentMethodsDark' }, - ], - }, -]; -const crypto_contents = [ - { - icons: [ - { light: 'IcCashierBitcoinLight', dark: 'IcCashierBitcoinDark' }, - { light: 'IcCashierEthereumLight', dark: 'IcCashierEthereumDark' }, - { light: 'IcCashierLiteCoinLight', dark: 'IcCashierLiteCoinDark' }, - { light: 'IcCashierUsdCoinLight', dark: 'IcCashierUsdCoinDark' }, - { light: 'IcCashierTetherLight', dark: 'IcCashierTetherDark' }, - ], - }, -]; -const onramp_contents = [ - { - icons: [{ light: 'IcCashierBanxaLight', dark: 'IcCashierBanxaDark' }], - }, -]; - -const createCashProvider = (onClick: VoidFunction) => { - return { - detail_click: onClick, - detail_description: localize('Deposit via the following payment methods:'), - detail_header: localize('Deposit via bank wire, credit card, and e-wallet'), - detail_contents: cash_contents, - }; -}; - -const createCryptoProvider = (onClick: VoidFunction) => { - return { - detail_click: onClick, - detail_description: localize('We accept the following cryptocurrencies:'), - detail_header: localize('Deposit cryptocurrencies'), - detail_contents: crypto_contents, - }; -}; - -const createOnrampProvider = (onClick: VoidFunction, is_crypto: boolean) => { - return { - detail_click: onClick, - detail_description: localize('Choose any of these exchanges to buy cryptocurrencies:'), - detail_header: is_crypto ? localize('Buy cryptocurrencies') : localize('Buy cryptocurrencies via fiat onramp'), - detail_contents: onramp_contents, - }; -}; - -const createPaymentAgentProvider = (onClick: VoidFunction) => { - return { - detail_click: onClick, - detail_description: localize( - 'Deposit in your local currency via an authorised, independent payment agent in your country.' - ), - detail_header: localize('Deposit via payment agents'), - }; -}; - -const createDp2pProvider = (onClick: VoidFunction) => { - return { - detail_click: onClick, - detail_description: localize( - 'Deposit with your local currency via peer-to-peer exchange with fellow traders in your country.' - ), - detail_header: localize('Deposit with Deriv P2P'), - }; -}; - -export default { - createCashProvider, - createCryptoProvider, - createDp2pProvider, - createOnrampProvider, - createPaymentAgentProvider, -}; diff --git a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-side-note.tsx b/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-side-note.tsx deleted file mode 100644 index 6a167d72ae5a..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding-side-note.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { Localize } from '@deriv/translations'; -import { Icon, Text } from '@deriv/components'; -import { getCurrencyDisplayCode, getPlatformSettings, routes, isMobile } from '@deriv/shared'; -import { useStore, observer } from '@deriv/stores'; -import { useCashierStore } from '../../stores/useCashierStores'; -import './cashier-onboarding.scss'; - -type TCashierOnboardingSideNoteProps = { - is_crypto?: boolean; -}; - -const CashierOnboardingSideNote = observer(({ is_crypto }: TCashierOnboardingSideNoteProps) => { - const { client, ui } = useStore(); - const { general_store } = useCashierStore(); - const { currency, is_eu, loginid, is_low_risk } = client; - const { openRealAccountSignup } = ui; - const { setDepositTarget } = general_store; - - const currency_code = getCurrencyDisplayCode(currency); - - const getSideNoteDescription = () => { - if (is_crypto) { - return ( - - ); - } - - return ( - window.LC_API.open_chat_window()} - />, - ]} - values={{ platform_name_dxtrade: getPlatformSettings('dxtrade').name }} - /> - ); - }; - - const getHeaderTitle = () => { - if (is_low_risk && !is_crypto) { - const regulation_text = is_eu ? 'EU' : 'non-EU'; - return ( - - ); - } - return ( - - ); - }; - - return ( -
- - {getHeaderTitle()} - - - {getSideNoteDescription()} - - {is_crypto && ( -
{ - setDepositTarget(routes.cashier_deposit); - openRealAccountSignup('add_crypto'); - }} - > - - - - -
- )} -
- ); -}); - -export default CashierOnboardingSideNote; diff --git a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding.scss b/packages/cashier/src/components/cashier-onboarding/cashier-onboarding.scss deleted file mode 100644 index fb50d42cdb4e..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding.scss +++ /dev/null @@ -1,137 +0,0 @@ -.cashier-onboarding { - height: calc(100vh - 20rem); - - @include mobile { - height: unset; - margin-top: 2.4rem; - } - - &__loader { - position: relative; - left: 0; - } - - &-header { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 2rem; - - @include mobile { - margin-right: 4rem; - margin-left: 4rem; - } - - &-learn-more { - text-align: center; - } - } - - &-content { - &::-webkit-scrollbar { - display: none; - } - - &__description { - margin-bottom: 5rem; - } - } - - &-detail { - margin: 2.4rem; - width: 62.4rem; - - // TODO: Replace the media query with Mobile/Tablet mixin after syncing the responsive.js and devices.scss - @media (max-width: 926px) { - width: unset; - } - - &__icons { - position: relative; - margin-top: auto; - overflow: hidden; - width: 100%; - } - - &__div { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - padding: 1.6rem; - position: static; - border: 2px solid var(--border-normal-1); - border-radius: $BORDER_RADIUS * 2; - margin: 0.8rem 0rem; - cursor: pointer; - } - - &__content { - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - } - - &__text { - position: static; - width: 100%; - } - - &__array { - display: flex; - flex-direction: row; - align-items: center; - position: static; - margin-top: 1.6rem; - gap: 1.2rem; - width: 100%; - white-space: nowrap; - - @include mobile { - flex-direction: column; - align-items: flex-start; - width: 100%; - } - } - - &__icons-array { - display: flex; - flex-direction: row; - align-items: center; - margin-right: 1.2rem; - } - - &__icon { - margin-right: 1.2rem; - - @include mobile { - display: inline-block; - } - } - } - - &-side-note { - &__link { - cursor: pointer; - display: flex; - align-items: center; - - &:hover { - text-decoration: underline var(--brand-red-coral); - } - - .dc-text { - margin-right: 0.4rem; - } - } - - &__text { - margin-bottom: 0.8rem; - - &-nowrap { - white-space: nowrap; - } - } - } -} diff --git a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding.tsx b/packages/cashier/src/components/cashier-onboarding/cashier-onboarding.tsx deleted file mode 100644 index 35bb9845d97e..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/cashier-onboarding.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import React from 'react'; -import { useHistory } from 'react-router-dom'; -import { usePaymentAgentList, useHasUSDCurrency, useIsP2PEnabled } from '@deriv/hooks'; -import { getStaticUrl, isCryptocurrency, routes } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; -import { Loading, ThemedScrollbars, Text } from '@deriv/components'; -import { useStore, observer } from '@deriv/stores'; -import Providers from './cashier-onboarding-providers'; -import CashierOnboardingDetails from './cashier-onboarding-details'; -import CashierOnboardingSideNote from './cashier-onboarding-side-note'; -import SideNote from 'Components/side-note'; -import type { TCashierOnboardingProvider } from './cashier-onboarding-providers'; -import { useCashierStore } from '../../stores/useCashierStores'; - -type TCashierOnboardingProps = { - setSideNotes?: (component: React.ReactElement[]) => void; -}; - -const CashierOnboarding = observer(({ setSideNotes }: TCashierOnboardingProps) => { - const { client, ui, common } = useStore(); - const { general_store, account_prompt_dialog } = useCashierStore(); - const { - accounts, - available_crypto_currencies, - can_change_fiat_currency, - currency, - is_landing_company_loaded, - is_switching, - } = client; - const { is_from_derivgo } = common; - const { - has_set_currency, - onMountCashierOnboarding, - is_cashier_onboarding, - setIsCashierOnboarding, - setIsDeposit, - setDepositTarget, - setShouldShowAllAvailableCurrencies, - } = general_store; - const { - app_contents_scroll_ref, - is_dark_mode_on, - is_mobile, - openRealAccountSignup, - shouldNavigateAfterChooseCrypto, - toggleSetCurrencyModal, - } = ui; - const { shouldNavigateAfterPrompt } = account_prompt_dialog; - const { data: is_p2p_enabled, isLoading: is_p2p_enabled_loading } = useIsP2PEnabled(); - - const has_usd_currency = useHasUSDCurrency(); - - const history = useHistory(); - const is_crypto = !!currency && isCryptocurrency(currency); - const has_crypto_account = React.useMemo( - () => Object.values(accounts).some(acc_settings => isCryptocurrency(acc_settings.currency || '')), - [accounts] - ); - const has_fiat_account = React.useMemo( - () => - Object.values(accounts).some( - acc_settings => !acc_settings.is_virtual && !isCryptocurrency(acc_settings.currency || '') - ), - [accounts] - ); - - const is_currency_banner_visible = - (!is_crypto && !can_change_fiat_currency) || (is_crypto && available_crypto_currencies.length > 0); - - const { data: all_payment_agent_list, isLoading: is_loading } = usePaymentAgentList(); - - const is_payment_agent_visible_in_onboarding = Boolean(all_payment_agent_list?.length); - - React.useEffect(() => { - onMountCashierOnboarding(); - return () => { - setIsCashierOnboarding(false); - if (!has_set_currency && window.location.pathname.includes(routes.cashier)) { - history.push(routes.trade); - toggleSetCurrencyModal(); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - React.useEffect(() => { - return () => { - if (app_contents_scroll_ref.current) app_contents_scroll_ref.current.scrollTop = 0; - }; - }, [app_contents_scroll_ref]); - - React.useEffect(() => { - if ( - typeof setSideNotes === 'function' && - !is_switching && - Object.keys(accounts).length > 0 && - is_landing_company_loaded && - is_currency_banner_visible - ) { - setSideNotes([]); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [is_switching, accounts, is_landing_company_loaded, is_cashier_onboarding]); - - const openRealAccount = (target: string) => { - openRealAccountSignup('choose'); - shouldNavigateAfterChooseCrypto(target); - }; - - const openTarget = (target: string) => { - setDepositTarget(target); - if (is_crypto || has_crypto_account) { - openRealAccount(target); - } else { - openRealAccountSignup('add_crypto'); - } - }; - - const fiatAccountConditions = (next_location: string, current_location: string) => { - if (has_fiat_account) { - shouldNavigateAfterPrompt(next_location, current_location); - } else { - openRealAccountSignup('add_fiat'); - } - }; - - const onClickDepositCash = () => { - setDepositTarget(routes.cashier_deposit); - - if (is_crypto) { - fiatAccountConditions(routes.cashier_deposit, 'deposit'); - } else { - setIsDeposit(true); - } - }; - - const onClickDepositCrypto = () => { - openTarget(routes.cashier_deposit); - }; - - const onClickOnramp = () => { - openTarget(routes.cashier_onramp); - }; - - const onClickPaymentAgent = () => { - setShouldShowAllAvailableCurrencies(true); - setDepositTarget(routes.cashier_pa); - openRealAccount(routes.cashier_pa); - }; - - const onClickDp2p = () => { - setDepositTarget(routes.cashier_p2p); - - if (is_crypto) { - fiatAccountConditions(routes.cashier_p2p, 'DP2P'); - } else { - history.push(routes.cashier_p2p); - } - }; - - const getDepositOptions = () => { - const options: TCashierOnboardingProvider[] = []; - options.push(Providers.createCashProvider(onClickDepositCash)); - options.push(Providers.createCryptoProvider(onClickDepositCrypto)); - options.push(Providers.createOnrampProvider(onClickOnramp, is_crypto)); - if (is_payment_agent_visible_in_onboarding) { - options.push(Providers.createPaymentAgentProvider(onClickPaymentAgent)); - } - - if (is_p2p_enabled && has_usd_currency) { - options.push(Providers.createDp2pProvider(onClickDp2p)); - } - return options; - }; - - if ( - is_switching || - Object.keys(accounts).length === 0 || - !is_landing_company_loaded || - is_loading || - is_p2p_enabled_loading - ) - return ; - - return ( -
- {is_currency_banner_visible && ( - - - - )} -
-
- - - - - - -
- {is_mobile && !is_from_derivgo && ( -
window.open(getStaticUrl('/payment-methods'))} - > - - - -
- )} - -
- {getDepositOptions()?.map((deposit, idx) => ( - - ))} -
-
-
-
- ); -}); - -export default CashierOnboarding; diff --git a/packages/cashier/src/components/cashier-onboarding/index.tsx b/packages/cashier/src/components/cashier-onboarding/index.tsx deleted file mode 100644 index a0d9f9e2ce2f..000000000000 --- a/packages/cashier/src/components/cashier-onboarding/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import CashierOnboarding from './cashier-onboarding'; -import CashierOnboardingSideNote from './cashier-onboarding-side-note'; - -export { CashierOnboarding, CashierOnboardingSideNote }; diff --git a/packages/cashier/src/components/page-container/__test__/page-container.test.tsx b/packages/cashier/src/components/page-container/__test__/page-container.test.tsx new file mode 100644 index 000000000000..ed4b8dca8ffa --- /dev/null +++ b/packages/cashier/src/components/page-container/__test__/page-container.test.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import CashierProviders from '../../../cashier-providers'; +import PageContainer from '../page-container'; + +describe('PageContainer', () => { + test('should show loading if is_authorize is false', () => { + const mock = mockStore({ client: { is_authorize: false } }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render( + +
children
+
, + { wrapper } + ); + + expect(screen.getByTestId('dt_initial_loader')).toBeInTheDocument(); + }); + + test('should show children if is_authorize is true', () => { + const mock = mockStore({ + client: { is_authorize: true }, + modules: { cashier: { general_store: { setIsDeposit: jest.fn() } } }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + render( + +
children
+
, + { wrapper } + ); + + expect(screen.getByText('children')).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/components/page-container/index.ts b/packages/cashier/src/components/page-container/index.ts new file mode 100644 index 000000000000..211a01ed4a01 --- /dev/null +++ b/packages/cashier/src/components/page-container/index.ts @@ -0,0 +1,3 @@ +import PageContainer from './page-container'; + +export { PageContainer }; diff --git a/packages/cashier/src/components/page-container/page-container.scss b/packages/cashier/src/components/page-container/page-container.scss new file mode 100644 index 000000000000..d450a607ae40 --- /dev/null +++ b/packages/cashier/src/components/page-container/page-container.scss @@ -0,0 +1,24 @@ +.page-container { + display: flex; + flex-direction: column; + width: 100%; + gap: 1.6rem; + background-color: var(--general-main-1); + height: calc(100vh - 17rem); + + @include mobile { + height: calc(100vh - 8rem); + } + + &__content { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 2.4rem 2.4rem; + gap: 2.4rem; + + @include mobile { + padding: 1.6rem; + } + } +} diff --git a/packages/cashier/src/components/page-container/page-container.tsx b/packages/cashier/src/components/page-container/page-container.tsx new file mode 100644 index 000000000000..30acb767827c --- /dev/null +++ b/packages/cashier/src/components/page-container/page-container.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Loading, ThemedScrollbars } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import CashierBreadcrumb from '../cashier-breadcrumb'; +import './page-container.scss'; + +type TProps = { + hide_breadcrumb?: boolean; +}; + +const PageContainer: React.FC> = observer(({ hide_breadcrumb = false, children }) => { + const { client } = useStore(); + const { is_authorize } = client; + const is_loading = !is_authorize; + + return ( +
+ {is_loading && } + {!is_loading && ( + + {!hide_breadcrumb && } + {children} + + )} +
+ ); +}); + +export default PageContainer; diff --git a/packages/cashier/src/components/side-note-card/__test__/side-note-card.test.tsx b/packages/cashier/src/components/side-note-card/__test__/side-note-card.test.tsx new file mode 100644 index 000000000000..4038bb0fd346 --- /dev/null +++ b/packages/cashier/src/components/side-note-card/__test__/side-note-card.test.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import SideNoteCard from '../side-note-card'; + +describe('SideNoteCard', () => { + test('should render the given title and description', async () => { + const props: React.ComponentProps = { + title: 'foo', + description: 'bar', + }; + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + expect(screen.getByText(props.title)).toBeInTheDocument(); + expect(screen.getByText(props.description)).toBeInTheDocument(); + }); + + test('should render the given children', async () => { + const props: React.ComponentProps = { + title: 'foo', + description: 'bar', + }; + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render( + +
children
+
, + { wrapper } + ); + + expect(screen.getByText(props.title)).toBeInTheDocument(); + expect(screen.getByText(props.description)).toBeInTheDocument(); + expect(screen.getByText('children')).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/components/side-note-card/index.ts b/packages/cashier/src/components/side-note-card/index.ts new file mode 100644 index 000000000000..84ea9c830519 --- /dev/null +++ b/packages/cashier/src/components/side-note-card/index.ts @@ -0,0 +1,3 @@ +import SideNoteCard from './side-note-card'; + +export { SideNoteCard }; diff --git a/packages/cashier/src/components/side-note-card/side-note-card.scss b/packages/cashier/src/components/side-note-card/side-note-card.scss new file mode 100644 index 000000000000..30b50a23d6e1 --- /dev/null +++ b/packages/cashier/src/components/side-note-card/side-note-card.scss @@ -0,0 +1,16 @@ +.side-note-card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 1.6rem; + background-color: var(--general-section-1); + border-radius: $BORDER_RADIUS * 2; + padding: 1.6rem 2.4rem; + + &__content { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + } +} diff --git a/packages/cashier/src/components/side-note-card/side-note-card.tsx b/packages/cashier/src/components/side-note-card/side-note-card.tsx new file mode 100644 index 000000000000..a4d0b40a7742 --- /dev/null +++ b/packages/cashier/src/components/side-note-card/side-note-card.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import './side-note-card.scss'; + +type TProps = { + title: string; + description: JSX.Element | string; + hide_paddings?: boolean; // Todo: remove this prop when page layout is refactored +}; + +const SideNoteCard: React.FC> = observer( + ({ title, description, hide_paddings = false, children }) => { + const { ui } = useStore(); + const { is_mobile } = ui; + + return ( +
+
+ + {title} + + {description} +
+ {children} +
+ ); + } +); + +export default SideNoteCard; diff --git a/packages/cashier/src/containers/cashier/__tests__/cashier.spec.tsx b/packages/cashier/src/containers/cashier/__tests__/cashier.spec.tsx index 2ca61ef54d9a..5bc2e97c102a 100644 --- a/packages/cashier/src/containers/cashier/__tests__/cashier.spec.tsx +++ b/packages/cashier/src/containers/cashier/__tests__/cashier.spec.tsx @@ -43,7 +43,6 @@ jest.mock('@deriv/shared', () => { }; }); -jest.mock('Components/account-prompt-dialog', () => jest.fn(() => 'mockedAccountPromptDialog')); jest.mock('Components/error-dialog', () => jest.fn(() => 'mockedErrorDialog')); jest.mock('Pages/deposit', () => jest.fn(() => 'mockedDeposit')); jest.mock('Pages/withdrawal', () => jest.fn(() => 'mockedWithdrawal')); @@ -98,9 +97,6 @@ describe('', () => { payment_agent: { is_payment_agent_visible: false, }, - account_prompt_dialog: { - resetLastLocation: jest.fn(), - }, }, }, }); @@ -150,9 +146,6 @@ describe('', () => { payment_agent: { is_payment_agent_visible: true, }, - account_prompt_dialog: { - resetLastLocation: jest.fn(), - }, }, }, }); @@ -210,9 +203,6 @@ describe('', () => { payment_agent: { is_payment_agent_visible: true, }, - account_prompt_dialog: { - resetLastLocation: jest.fn(), - }, }, }, }); @@ -262,9 +252,6 @@ describe('', () => { // payment_agent: { // is_payment_agent_visible: true, // }, - // account_prompt_dialog: { - // resetLastLocation: jest.fn(), - // }, // }, // }, // }); @@ -317,9 +304,6 @@ describe('', () => { payment_agent: { is_payment_agent_visible: true, }, - account_prompt_dialog: { - resetLastLocation: jest.fn(), - }, }, }, }); @@ -371,9 +355,6 @@ describe('', () => { payment_agent: { is_payment_agent_visible: true, }, - account_prompt_dialog: { - resetLastLocation: jest.fn(), - }, }, }, }); @@ -424,9 +405,6 @@ describe('', () => { payment_agent: { is_payment_agent_visible: true, }, - account_prompt_dialog: { - resetLastLocation: jest.fn(), - }, }, }, }); diff --git a/packages/cashier/src/containers/cashier/cashier.scss b/packages/cashier/src/containers/cashier/cashier.scss index 512e37bea77b..7c16530a54d8 100644 --- a/packages/cashier/src/containers/cashier/cashier.scss +++ b/packages/cashier/src/containers/cashier/cashier.scss @@ -286,9 +286,6 @@ min-width: 25.6rem; padding: 0.6rem 0.8rem; } - .acc-prompt-dialog { - padding: 0 1rem; - } } &-locked { width: 100%; diff --git a/packages/cashier/src/containers/cashier/cashier.tsx b/packages/cashier/src/containers/cashier/cashier.tsx index c5eb722550d7..baad47f16897 100644 --- a/packages/cashier/src/containers/cashier/cashier.tsx +++ b/packages/cashier/src/containers/cashier/cashier.tsx @@ -19,13 +19,12 @@ import { useP2PNotificationCount, } from '@deriv/hooks'; import { getSelectedRoute, getStaticUrl, isMobile, routes, WS } from '@deriv/shared'; -import AccountPromptDialog from '../../components/account-prompt-dialog'; import ErrorDialog from '../../components/error-dialog'; import { TRoute } from '../../types'; import { localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; import { useCashierStore } from '../../stores/useCashierStores'; -import { TStores } from '@deriv/stores/types'; +import type { TCoreStores } from '@deriv/stores/types'; import './cashier.scss'; type TCashierProps = RouteComponentProps & { @@ -34,8 +33,8 @@ type TCashierProps = RouteComponentProps & { onMount: (should_remount?: boolean) => void; setAccountSwitchListener: () => void; setTabIndex: (index: number) => void; - routeBackInApp: TStores['common']['routeBackInApp']; - toggleCashier: TStores['ui']['toggleCashier']; + routeBackInApp: TCoreStores['common']['routeBackInApp']; + toggleCashier: TCoreStores['ui']['toggleCashier']; resetLastLocation: () => void; }; @@ -51,7 +50,7 @@ type TCashierOptions = { const Cashier = observer(({ history, location, routes: routes_config }: TCashierProps) => { const { common, ui, client } = useStore(); - const { withdraw, general_store, transaction_history, payment_agent, account_prompt_dialog } = useCashierStore(); + const { withdraw, general_store, transaction_history, payment_agent } = useCashierStore(); const { error } = withdraw; const { is_cashier_onboarding, @@ -68,7 +67,6 @@ const Cashier = observer(({ history, location, routes: routes_config }: TCashier isSuccess: is_payment_agent_transfer_visible_is_success, } = usePaymentAgentTransferVisible(); const { is_payment_agent_visible } = payment_agent; - const { resetLastLocation } = account_prompt_dialog; const { is_from_derivgo } = common; const { is_cashier_visible: is_visible, toggleCashier } = ui; const { is_account_setting_loaded, is_logged_in, is_logging_in } = client; @@ -86,9 +84,7 @@ const Cashier = observer(({ history, location, routes: routes_config }: TCashier // we still need to populate the tabs shown on cashier return () => { toggleCashier(); - resetLastLocation(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [toggleCashier]); React.useEffect(() => { @@ -170,7 +166,6 @@ const Cashier = observer(({ history, location, routes: routes_config }: TCashier return ( -
diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/__test__/cashier-onboarding-card.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/__test__/cashier-onboarding-card.test.tsx new file mode 100644 index 000000000000..4aadbf5bd591 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/__test__/cashier-onboarding-card.test.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import CashierOnboardingCard from '../cashier-onboarding-card'; + +describe('CashierOnboardingCard', () => { + test('should call the onClick callback when clicked', async () => { + const mock = mockStore({}); + const props: React.ComponentProps = { + title: 'foo', + description: 'bar', + onClick: jest.fn(), + }; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_card'); + + userEvent.click(container); + + expect(props.onClick).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/cashier-onboarding-card.scss b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/cashier-onboarding-card.scss new file mode 100644 index 000000000000..90715c318ea4 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/cashier-onboarding-card.scss @@ -0,0 +1,29 @@ +.cashier-onboarding-card { + align-self: stretch; + + &__content { + display: flex; + align-items: center; + gap: 1.2rem; + } + + &__description { + flex: 1; + } + + &__container { + display: flex; + flex-direction: column; + padding: 1.6rem; + border: 2px solid var(--border-normal-1); + border-radius: $BORDER_RADIUS * 2; + margin-top: 0.8rem; + cursor: pointer; + gap: 1.6rem; + transition: all 0.25s ease; + + &:hover { + transform: scale(0.99); + } + } +} diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/cashier-onboarding-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/cashier-onboarding-card.tsx new file mode 100644 index 000000000000..08f8b0d2dbd4 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/cashier-onboarding-card.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import './cashier-onboarding-card.scss'; + +type TProps = { + title: string; + description: string; + onClick?: VoidFunction; +}; + +const CashierOnboardingCard: React.FC> = observer( + ({ title, description, onClick, children }) => { + const { ui } = useStore(); + const { is_dark_mode_on, is_mobile } = ui; + + return ( +
+ + {title} + +
+
+ + {description} + + +
+ {children} +
+
+ ); + } +); + +export default CashierOnboardingCard; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/index.ts new file mode 100644 index 000000000000..5271e2b80851 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-card/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingCard from './cashier-onboarding-card'; + +export { CashierOnboardingCard }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/__test__/cashier-onboarding-crypto-card.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/__test__/cashier-onboarding-crypto-card.test.tsx new file mode 100644 index 000000000000..68d0bb75a8fb --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/__test__/cashier-onboarding-crypto-card.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { fireEvent, render, screen } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import CashierOnboardingCryptoCard from '../cashier-onboarding-crypto-card'; + +describe('CashierOnboardingCryptoCard', () => { + test('should call the onClick callback when clicked', async () => { + const mock = mockStore({ + client: { + is_crypto: () => false, + }, + modules: { + cashier: { + general_store: { + setDepositTarget: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_card'); + + fireEvent.click(container); + + expect(mock.modules.cashier.general_store.setDepositTarget).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/cashier-onboarding-crypto-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/cashier-onboarding-crypto-card.tsx new file mode 100644 index 000000000000..5b66ba9372f8 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/cashier-onboarding-crypto-card.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useHasCryptoCurrency } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useCashierStore } from '../../../../stores/useCashierStores'; +import { CashierOnboardingCard } from '../cashier-onboarding-card'; +import { CashierOnboardingIconMarquee } from '../cashier-onboarding-icon-marquee'; + +const icons: React.ComponentProps['icons'] = [ + { light: 'IcCashierBitcoinLight', dark: 'IcCashierBitcoinDark' }, + { light: 'IcCashierEthereumLight', dark: 'IcCashierEthereumDark' }, + { light: 'IcCashierLiteCoinLight', dark: 'IcCashierLiteCoinDark' }, + { light: 'IcCashierUsdCoinLight', dark: 'IcCashierUsdCoinDark' }, + { light: 'IcCashierTetherLight', dark: 'IcCashierTetherDark' }, +]; + +const CashierOnboardingCryptoCard: React.FC = observer(() => { + const { client, ui } = useStore(); + const { general_store } = useCashierStore(); + const { is_crypto } = client; + const { openRealAccountSignup, shouldNavigateAfterChooseCrypto } = ui; + const { setDepositTarget } = general_store; + const has_crypto_account = useHasCryptoCurrency(); + + const onClick = () => { + setDepositTarget(routes.cashier_deposit); + if (is_crypto() || has_crypto_account) { + openRealAccountSignup('choose'); + shouldNavigateAfterChooseCrypto(routes.cashier_deposit); + } else { + openRealAccountSignup('add_crypto'); + } + }; + + return ( + + + + ); +}); + +export default CashierOnboardingCryptoCard; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/index.ts new file mode 100644 index 000000000000..44573bcaa49a --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-crypto-card/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingCryptoCard from './cashier-onboarding-crypto-card'; + +export { CashierOnboardingCryptoCard }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/__test__/cashier-onboarding-fiat-card.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/__test__/cashier-onboarding-fiat-card.test.tsx new file mode 100644 index 000000000000..0a112c019cbe --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/__test__/cashier-onboarding-fiat-card.test.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { fireEvent, render, screen } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import CashierOnboardingFiatCard from '../cashier-onboarding-fiat-card'; + +describe('CashierOnboardingFiatCard', () => { + test('should call the onClick callback when clicked', async () => { + const mock = mockStore({ + client: { + is_crypto: () => false, + }, + modules: { + cashier: { + general_store: { + setDepositTarget: jest.fn(), + setIsDeposit: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_card'); + + fireEvent.click(container); + + expect(mock.modules.cashier.general_store.setDepositTarget).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx new file mode 100644 index 000000000000..00405fae512e --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/cashier-onboarding-fiat-card.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { useHasFiatCurrency } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useCashierStore } from '../../../../stores/useCashierStores'; +import { CashierOnboardingCard } from '../cashier-onboarding-card'; +import { CashierOnboardingIconMarquee } from '../cashier-onboarding-icon-marquee'; +import { SwitchToFiatAccountDialog } from '../switch-to-fiat-account-dialog'; + +const icons: React.ComponentProps['icons'] = [ + { light: 'IcWalletCreditDebitLight', dark: 'IcWalletCreditDebitDark' }, + { light: 'IcCashierInstantBankTransferLight', dark: 'IcCashierInstantBankTransferDark' }, + { light: 'IcCashierEwalletLight', dark: 'IcCashierEwalletDark' }, + { light: 'IcCashierLocalPaymentMethodsLight', dark: 'IcCashierLocalPaymentMethodsDark' }, +]; + +const CashierOnboardingFiatCard: React.FC = observer(() => { + const { client, ui } = useStore(); + const { general_store } = useCashierStore(); + const { is_crypto } = client; + const { openRealAccountSignup } = ui; + const { setDepositTarget, setIsDeposit } = general_store; + const has_fiat_currency = useHasFiatCurrency(); + const can_switch_to_fiat_account = is_crypto() && has_fiat_currency; + const [is_dialog_visible, setIsDialogVisible] = useState(false); + + const onClick = () => { + setDepositTarget(routes.cashier_deposit); + + if (can_switch_to_fiat_account) { + setIsDialogVisible(true); + } else if (is_crypto()) { + openRealAccountSignup('add_fiat'); + } else { + setIsDeposit(true); + } + }; + + const onSwitchDone = () => { + setIsDialogVisible(false); + setIsDeposit(true); + }; + + return ( + + + {can_switch_to_fiat_account && ( + setIsDialogVisible(false)} + onSwitchDone={onSwitchDone} + /> + )} + + ); +}); + +export default CashierOnboardingFiatCard; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/index.ts new file mode 100644 index 000000000000..acb3772f794f --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-fiat-card/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingFiatCard from './cashier-onboarding-fiat-card'; + +export { CashierOnboardingFiatCard }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/__test__/cashier-onboarding-icon-marquee.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/__test__/cashier-onboarding-icon-marquee.test.tsx new file mode 100644 index 000000000000..8a5d83c57ef1 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/__test__/cashier-onboarding-icon-marquee.test.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import CashierOnboardingIconMarquee from '../cashier-onboarding-icon-marquee'; + +describe('CashierOnboardingIconMarquee', () => { + test('should render cashier onboarding icon marquee', async () => { + const mock = mockStore({}); + const props: React.ComponentProps = { + icons: [{ light: 'light', dark: 'dark' }], + }; + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_icon-marquee'); + + expect(container).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/cashier-onboarding-icon-marquee.scss b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/cashier-onboarding-icon-marquee.scss new file mode 100644 index 000000000000..d1869b88e33c --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/cashier-onboarding-icon-marquee.scss @@ -0,0 +1,4 @@ +.cashier-onboarding-icon-marquee { + display: flex; + gap: 0.8rem; +} diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/cashier-onboarding-icon-marquee.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/cashier-onboarding-icon-marquee.tsx new file mode 100644 index 000000000000..48095a7a28c2 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/cashier-onboarding-icon-marquee.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Icon, NewsTicker } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; +import './cashier-onboarding-icon-marquee.scss'; + +type TIcon = { + dark: string; + light: string; +}; + +type TProps = { + icons: TIcon[]; +}; + +const CashierOnboardingIconMarquee: React.FC = observer(({ icons }) => { + const { ui } = useStore(); + const { is_dark_mode_on } = ui; + + return ( +
+ +
+ {icons.map((icon, index) => ( + + ))} +
+
+
+ ); +}); + +export default CashierOnboardingIconMarquee; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/index.ts new file mode 100644 index 000000000000..bf3c4dda41c2 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-icon-marquee/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingIconMarquee from './cashier-onboarding-icon-marquee'; + +export { CashierOnboardingIconMarquee }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/__test__/cashier-onboarding-onramp-card.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/__test__/cashier-onboarding-onramp-card.test.tsx new file mode 100644 index 000000000000..2914fdac29e5 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/__test__/cashier-onboarding-onramp-card.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { fireEvent, render, screen } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import CashierOnboardingOnrampCard from '../cashier-onboarding-onramp-card'; + +describe('CashierOnboardingOnrampCard', () => { + test('should call the onClick callback when clicked', async () => { + const mock = mockStore({ + client: { + is_crypto: () => false, + }, + modules: { + cashier: { + general_store: { + setDepositTarget: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_card'); + + fireEvent.click(container); + + expect(mock.modules.cashier.general_store.setDepositTarget).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/cashier-onboarding-onramp-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/cashier-onboarding-onramp-card.tsx new file mode 100644 index 000000000000..563ca1791a1c --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/cashier-onboarding-onramp-card.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { useHasCryptoCurrency } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useCashierStore } from '../../../../stores/useCashierStores'; +import { CashierOnboardingCard } from '../cashier-onboarding-card'; +import { CashierOnboardingIconMarquee } from '../cashier-onboarding-icon-marquee'; + +const icons: React.ComponentProps['icons'] = [ + { light: 'IcCashierBanxaLight', dark: 'IcCashierBanxaDark' }, +]; + +const CashierOnboardingOnrampCard: React.FC = observer(() => { + const { client, ui } = useStore(); + const { general_store } = useCashierStore(); + const { is_crypto } = client; + const { openRealAccountSignup, shouldNavigateAfterChooseCrypto } = ui; + const { setDepositTarget } = general_store; + const has_crypto_account = useHasCryptoCurrency(); + + const onClick = () => { + setDepositTarget(routes.cashier_onramp); + if (is_crypto() || has_crypto_account) { + openRealAccountSignup('choose'); + shouldNavigateAfterChooseCrypto(routes.cashier_onramp); + } else { + openRealAccountSignup('add_crypto'); + } + }; + + return ( + + + + ); +}); + +export default CashierOnboardingOnrampCard; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/index.ts new file mode 100644 index 000000000000..833c8dffdee8 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-onramp-card/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingOnrampCard from './cashier-onboarding-onramp-card'; + +export { CashierOnboardingOnrampCard }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/__test__/cashier-onboarding-p2p-card.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/__test__/cashier-onboarding-p2p-card.test.tsx new file mode 100644 index 000000000000..43083201222d --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/__test__/cashier-onboarding-p2p-card.test.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { fireEvent, render, screen } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import CashierOnboardingP2PCard from '../cashier-onboarding-p2p-card'; + +jest.mock('react-router', () => ({ + ...jest.requireActual('react-router'), + useHistory: () => ({ push: jest.fn() }), +})); + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + useHasUSDCurrency: () => true, + useIsP2PEnabled: () => ({ data: true }), +})); + +describe('CashierOnboardingP2PCard', () => { + test('should call the onClick callback when clicked', async () => { + const mock = mockStore({ + client: { + is_crypto: () => false, + }, + modules: { + cashier: { + general_store: { + setDepositTarget: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_card'); + + fireEvent.click(container); + + expect(mock.modules.cashier.general_store.setDepositTarget).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/cashier-onboarding-p2p-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/cashier-onboarding-p2p-card.tsx new file mode 100644 index 000000000000..9a51a46bb822 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/cashier-onboarding-p2p-card.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { useHasFiatCurrency, useHasUSDCurrency, useIsP2PEnabled } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useHistory } from 'react-router'; +import { useCashierStore } from '../../../../stores/useCashierStores'; +import { CashierOnboardingCard } from '../cashier-onboarding-card'; +import { SwitchToFiatAccountDialog } from '../switch-to-fiat-account-dialog'; + +const CashierOnboardingP2PCard: React.FC = observer(() => { + const { client, ui } = useStore(); + const { general_store } = useCashierStore(); + const { is_crypto } = client; + const { openRealAccountSignup } = ui; + const { setDepositTarget } = general_store; + const history = useHistory(); + const { data: is_p2p_enabled } = useIsP2PEnabled(); + const has_usd_currency = useHasUSDCurrency(); + const has_fiat_currency = useHasFiatCurrency(); + const should_show_p2p_card = is_p2p_enabled || has_usd_currency; + const can_switch_to_fiat_account = is_crypto() && has_fiat_currency; + const [is_dialog_visible, setIsDialogVisible] = useState(false); + + const onClick = () => { + setDepositTarget(routes.cashier_p2p); + + if (can_switch_to_fiat_account) { + setIsDialogVisible(true); + } else if (is_crypto()) { + openRealAccountSignup('add_fiat'); + } else { + history.push(routes.cashier_p2p); + } + }; + + const onSwitchDone = () => { + setIsDialogVisible(false); + history.push(routes.cashier_p2p); + }; + + if (!should_show_p2p_card) return null; + + return ( + + {can_switch_to_fiat_account && ( + setIsDialogVisible(false)} + onSwitchDone={onSwitchDone} + /> + )} + + ); +}); + +export default CashierOnboardingP2PCard; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/index.ts new file mode 100644 index 000000000000..4a26e4ab3894 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-p2p-card/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingP2PCard from './cashier-onboarding-p2p-card'; + +export { CashierOnboardingP2PCard }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/__test__/cashier-onboarding-payment-agent-card.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/__test__/cashier-onboarding-payment-agent-card.test.tsx new file mode 100644 index 000000000000..1ba55a08934c --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/__test__/cashier-onboarding-payment-agent-card.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { fireEvent, render, screen } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import CashierOnboardingPaymentAgentCard from '../cashier-onboarding-payment-agent-card'; + +jest.mock('@deriv/hooks', () => ({ + ...jest.requireActual('@deriv/hooks'), + usePaymentAgentList: () => ({ data: ['PA1', 'PA2'] }), +})); + +describe('CashierOnboardingPaymentAgentCard', () => { + test('should call the onClick callback when clicked', async () => { + const mock = mockStore({ + modules: { + cashier: { + general_store: { + setDepositTarget: jest.fn(), + setShouldShowAllAvailableCurrencies: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const container = screen.getByTestId('dt_cashier_onboarding_card'); + + fireEvent.click(container); + + expect(mock.modules.cashier.general_store.setDepositTarget).toBeCalledTimes(1); + expect(mock.modules.cashier.general_store.setShouldShowAllAvailableCurrencies).toBeCalledTimes(1); + expect(mock.ui.openRealAccountSignup).toBeCalledTimes(1); + expect(mock.ui.shouldNavigateAfterChooseCrypto).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/cashier-onboarding-payment-agent-card.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/cashier-onboarding-payment-agent-card.tsx new file mode 100644 index 000000000000..bb74642739d4 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/cashier-onboarding-payment-agent-card.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { usePaymentAgentList } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useCashierStore } from '../../../../stores/useCashierStores'; +import { CashierOnboardingCard } from '../cashier-onboarding-card'; + +const CashierOnboardingPaymentAgentCard: React.FC = observer(() => { + const { ui } = useStore(); + const { general_store } = useCashierStore(); + const { openRealAccountSignup, shouldNavigateAfterChooseCrypto } = ui; + const { setDepositTarget, setShouldShowAllAvailableCurrencies } = general_store; + const { data: all_payment_agent_list } = usePaymentAgentList(); + const is_payment_agent_visible_in_onboarding = Boolean(all_payment_agent_list?.length); + + const onClick = () => { + setShouldShowAllAvailableCurrencies(true); + setDepositTarget(routes.cashier_pa); + openRealAccountSignup('choose'); + shouldNavigateAfterChooseCrypto(routes.cashier_pa); + }; + + if (!is_payment_agent_visible_in_onboarding) return null; + + return ( + + ); +}); + +export default CashierOnboardingPaymentAgentCard; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/index.ts new file mode 100644 index 000000000000..345e02d33f04 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-payment-agent-card/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingPaymentAgentCard from './cashier-onboarding-payment-agent-card'; + +export { CashierOnboardingPaymentAgentCard }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/__test__/cashier-onboarding-side-notes.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/__test__/cashier-onboarding-side-notes.test.tsx new file mode 100644 index 000000000000..70a9fd9e213b --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/__test__/cashier-onboarding-side-notes.test.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import CashierOnboardingSideNotes from '../cashier-onboarding-side-notes'; + +describe('CashierOnboardingSideNotes', () => { + test('should render CashierOnboardingSideNoteFiat on mobile if is_crypto is false', async () => { + const setSideNotes = jest.fn(); + const mock = mockStore({ ui: { is_mobile: true } }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + expect(screen.getByText(/If you want to change your account currency/i)).toBeInTheDocument(); + }); + + test('should render CashierOnboardingSideNoteCrypto on mobile on mobile if is_crypto is true', async () => { + const setSideNotes = jest.fn(); + const mock = mockStore({ + ui: { is_mobile: true }, + client: { is_crypto: () => true }, + modules: { + cashier: { + general_store: { + setDepositTarget: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + expect(screen.getByText(/You can open another cryptocurrency account/i)).toBeInTheDocument(); + }); + + test('should call setSideNotes on desktop', async () => { + const setSideNotes = jest.fn(); + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + expect(setSideNotes).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-note-crypto.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-note-crypto.tsx new file mode 100644 index 000000000000..eef70e81168b --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-note-crypto.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Icon, Text } from '@deriv/components'; +import { getCurrencyDisplayCode, routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { SideNoteCard } from '../../../../components/side-note-card'; +import { useCashierStore } from '../../../../stores/useCashierStores'; + +const CashierOnboardingSideNoteCrypto: React.FC = observer(() => { + const { client, ui } = useStore(); + const { general_store } = useCashierStore(); + const { currency, loginid } = client; + const { openRealAccountSignup, is_mobile } = ui; + const { setDepositTarget } = general_store; + const currency_code = getCurrencyDisplayCode(currency); + + const onClick = () => { + setDepositTarget(routes.cashier_deposit); + openRealAccountSignup('add_crypto'); + }; + + return ( + +
+ + {localize('Manage your accounts')} + + +
+
+ ); +}); + +export default CashierOnboardingSideNoteCrypto; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-note-fiat.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-note-fiat.tsx new file mode 100644 index 000000000000..4b5d68af2d36 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-note-fiat.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { getCurrencyDisplayCode } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize, localize } from '@deriv/translations'; +import { SideNoteCard } from '../../../../components/side-note-card'; + +const CashierOnboardingSideNoteFiat: React.FC = observer(() => { + const { client, ui } = useStore(); + const { currency, loginid, is_eu, is_low_risk } = client; + const { is_mobile } = ui; + const currency_code = getCurrencyDisplayCode(currency); + const regulation_text = is_eu ? 'EU' : 'non-EU'; + + return ( + window.LC_API.open_chat_window()} + />, + ]} + /> + } + /> + ); +}); + +export default CashierOnboardingSideNoteFiat; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-notes.scss b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-notes.scss new file mode 100644 index 000000000000..32cdf8624d31 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-notes.scss @@ -0,0 +1,15 @@ +.cashier-onboarding-side-notes { + &__link-container { + display: flex; + align-items: center; + gap: 0.4rem; + } + + &__link { + cursor: pointer; + + &:hover { + text-decoration: underline var(--brand-red-coral); + } + } +} diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-notes.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-notes.tsx new file mode 100644 index 000000000000..0dee91a39ff7 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/cashier-onboarding-side-notes.tsx @@ -0,0 +1,33 @@ +import React, { useEffect } from 'react'; +import { observer, useStore } from '@deriv/stores'; +import CashierOnboardingSideNoteCrypto from './cashier-onboarding-side-note-crypto'; +import CashierOnboardingSideNoteFiat from './cashier-onboarding-side-note-fiat'; +import './cashier-onboarding-side-notes.scss'; + +type TProps = { + setSideNotes: React.Dispatch>; +}; + +const CashierOnboardingSideNotes: React.FC = observer(({ setSideNotes }) => { + const { client, ui } = useStore(); + const { is_crypto } = client; + const { is_mobile } = ui; + + useEffect(() => { + if (!is_mobile) { + if (is_crypto()) { + setSideNotes([]); + } else { + setSideNotes([]); + } + } + }, [setSideNotes, is_crypto, is_mobile]); + + if (!is_mobile) return null; + + if (is_crypto()) return ; + + return ; +}); + +export default CashierOnboardingSideNotes; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/index.ts new file mode 100644 index 000000000000..4a1b30cd0249 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-side-notes/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingSideNotes from './cashier-onboarding-side-notes'; + +export { CashierOnboardingSideNotes }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/__test__/cashier-onboarding-title.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/__test__/cashier-onboarding-title.test.tsx new file mode 100644 index 000000000000..13eda240baad --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/__test__/cashier-onboarding-title.test.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import CashierOnboardingTitle from '../cashier-onboarding-title'; + +describe('CashierOnboardingTitle', () => { + test('should render cashier onboarding title', async () => { + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const title = screen.queryByTestId('dt_cashier_onboarding_title'); + + expect(title).toBeInTheDocument(); + }); + + test('should not render learn more link on desktop', async () => { + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const learn_more = screen.queryByTestId('dt_cashier_onboarding_title_learn_more'); + + expect(learn_more).not.toBeInTheDocument(); + }); + + test('should render cashier onboarding title with learn more link when on mobile', async () => { + const mock = mockStore({ ui: { is_mobile: true } }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const learn_more = screen.queryByTestId('dt_cashier_onboarding_title_learn_more'); + + expect(learn_more).toBeInTheDocument(); + }); + + test('should not learn more link when on mobile and is from DerivGO', async () => { + const mock = mockStore({ ui: { is_mobile: true }, common: { is_from_derivgo: true } }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const learn_more = screen.queryByTestId('dt_cashier_onboarding_title_learn_more'); + + expect(learn_more).not.toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/cashier-onboarding-title.scss b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/cashier-onboarding-title.scss new file mode 100644 index 000000000000..389d0d601201 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/cashier-onboarding-title.scss @@ -0,0 +1,10 @@ +.cashier-onboarding-title { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.6rem; + + @include mobile { + gap: 2.4rem; + } +} diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/cashier-onboarding-title.tsx b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/cashier-onboarding-title.tsx new file mode 100644 index 000000000000..378f8728c56b --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/cashier-onboarding-title.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Text } from '@deriv/components'; +import { getStaticUrl } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import './cashier-onboarding-title.scss'; + +const CashierOnboardingTitle: React.FC = observer(() => { + const { ui, common } = useStore(); + const { is_mobile } = ui; + const { is_from_derivgo } = common; + const should_show_learn_more = is_mobile && !is_from_derivgo; + + return ( +
+ + {localize('Choose a way to fund your account')} + + + {localize('Please note that some payment methods might not be available in your country.')} + + {should_show_learn_more && ( + window.open(getStaticUrl('/payment-methods'))} + size='xs' + color='red' + align='center' + > + {localize('Learn more about payment methods')} + + )} +
+ ); +}); + +export default CashierOnboardingTitle; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/index.ts new file mode 100644 index 000000000000..460f66fc231b --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/cashier-onboarding-title/index.ts @@ -0,0 +1,3 @@ +import CashierOnboardingTitle from './cashier-onboarding-title'; + +export { CashierOnboardingTitle }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/index.ts new file mode 100644 index 000000000000..18f3ac99b9e9 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/index.ts @@ -0,0 +1,10 @@ +export * from './cashier-onboarding-card'; +export * from './cashier-onboarding-crypto-card'; +export * from './cashier-onboarding-fiat-card'; +export * from './cashier-onboarding-icon-marquee'; +export * from './cashier-onboarding-onramp-card'; +export * from './cashier-onboarding-p2p-card'; +export * from './cashier-onboarding-payment-agent-card'; +export * from './cashier-onboarding-side-notes'; +export * from './cashier-onboarding-title'; +export * from './switch-to-fiat-account-dialog'; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/__test__/switch-to-fiat-account-dialog.test.tsx b/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/__test__/switch-to-fiat-account-dialog.test.tsx new file mode 100644 index 000000000000..b3036e67b745 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/__test__/switch-to-fiat-account-dialog.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { mockStore } from '@deriv/stores'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import CashierProviders from '../../../../../cashier-providers'; +import SwitchToFiatAccountDialog from '../switch-to-fiat-account-dialog'; + +describe('SwitchToFiatAccountDialog', () => { + test('should call the onSwitchDone callback when clicked on confirm button', async () => { + const mock = mockStore({ + client: { + account_list: [ + { title: 'BTC', is_virtual: false, loginid: 'CR123' }, + { title: 'USD', is_virtual: false, loginid: 'CR123' }, + ], + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + const onSwitchDone = jest.fn(); + const onCancel = jest.fn(); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + render(, { wrapper }); + + const confirm = screen.getByText('Switch account'); + + fireEvent.click(confirm); + + await waitFor(() => expect(onSwitchDone).toBeCalledTimes(1)); + expect(onSwitchDone).toBeCalledTimes(1); + }); +}); diff --git a/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/index.ts b/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/index.ts new file mode 100644 index 000000000000..e68b0ddad7af --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/index.ts @@ -0,0 +1,3 @@ +import SwitchToFiatAccountDialog from './switch-to-fiat-account-dialog'; + +export { SwitchToFiatAccountDialog }; diff --git a/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/switch-to-fiat-account-dialog.tsx b/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/switch-to-fiat-account-dialog.tsx new file mode 100644 index 000000000000..e84b28dd2b09 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/components/switch-to-fiat-account-dialog/switch-to-fiat-account-dialog.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Dialog } from '@deriv/components'; +import { useFiatAccountList } from '@deriv/hooks'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; + +type TProps = { + is_visible: boolean; + onCancel: VoidFunction; + onSwitchDone: VoidFunction; +}; + +const SwitchToFiatAccountDialog: React.FC = observer(({ is_visible = false, onCancel, onSwitchDone }) => { + const { client } = useStore(); + const { switchAccount } = client; + const fiat_account_list = useFiatAccountList(); + const fiat_account_loginid = fiat_account_list?.[0].loginid; + const fiat_account_currency = fiat_account_list?.[0].title; + + const onConfirm = async () => { + await switchAccount(fiat_account_loginid); + + onSwitchDone(); + }; + + return ( + + {localize('To deposit money, please switch to your {{currency_symbol}} account.', { + currency_symbol: fiat_account_currency, + })} + + ); +}); + +export default SwitchToFiatAccountDialog; diff --git a/packages/cashier/src/modules/cashier-onboarding/index.tsx b/packages/cashier/src/modules/cashier-onboarding/index.tsx new file mode 100644 index 000000000000..69ea0a9dbf57 --- /dev/null +++ b/packages/cashier/src/modules/cashier-onboarding/index.tsx @@ -0,0 +1,59 @@ +import React, { useEffect } from 'react'; +import { useHasSetCurrency } from '@deriv/hooks'; +import { routes } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { useHistory } from 'react-router'; +import { PageContainer } from '../../components/page-container'; +import { useCashierStore } from '../../stores/useCashierStores'; +import { + CashierOnboardingCryptoCard, + CashierOnboardingFiatCard, + CashierOnboardingOnrampCard, + CashierOnboardingP2PCard, + CashierOnboardingPaymentAgentCard, + CashierOnboardingSideNotes, + CashierOnboardingTitle, +} from './components'; + +type TProps = { + setSideNotes: React.Dispatch>; +}; + +const CashierOnboarding: React.FC = observer(({ setSideNotes }) => { + const history = useHistory(); + const { client, ui } = useStore(); + const { general_store } = useCashierStore(); + const { setIsCashierOnboarding } = general_store; + const { toggleSetCurrencyModal } = ui; + const { can_change_fiat_currency, available_crypto_currencies, is_crypto } = client; + const is_fiat_user = !is_crypto() && !can_change_fiat_currency; + const is_crypto_user = is_crypto() && available_crypto_currencies.length > 0; + const should_show_side_notes = is_fiat_user || is_crypto_user; + const has_set_currency = useHasSetCurrency(); + + useEffect(() => { + setIsCashierOnboarding(true); + + return () => { + setIsCashierOnboarding(false); + if (!has_set_currency && window.location.pathname.includes(routes.cashier)) { + history.push(routes.trade); + toggleSetCurrencyModal(); + } + }; + }, [has_set_currency, history, setIsCashierOnboarding, toggleSetCurrencyModal]); + + return ( + + + + + + + + {should_show_side_notes && } + + ); +}); + +export default CashierOnboarding; diff --git a/packages/cashier/src/modules/index.ts b/packages/cashier/src/modules/index.ts new file mode 100644 index 000000000000..d44bb6bc53cf --- /dev/null +++ b/packages/cashier/src/modules/index.ts @@ -0,0 +1 @@ +export { default as CashierOnboardingModule } from './cashier-onboarding'; diff --git a/packages/cashier/src/pages/deposit/__tests__/deposit.spec.tsx b/packages/cashier/src/pages/deposit/__tests__/deposit.spec.tsx index 0040c833821f..e344fe62db7f 100644 --- a/packages/cashier/src/pages/deposit/__tests__/deposit.spec.tsx +++ b/packages/cashier/src/pages/deposit/__tests__/deposit.spec.tsx @@ -53,11 +53,6 @@ jest.mock('Components/cashier-container/real', () => { return CashierContainerReal; }); -jest.mock('Components/cashier-onboarding/cashier-onboarding', () => { - const CashierOnboarding = () =>
CashierOnboarding
; - return CashierOnboarding; -}); - jest.mock('../deposit-locked', () => { const DepositLocked = () =>
DepositLocked
; return DepositLocked; @@ -484,50 +479,6 @@ describe('', () => { expect(screen.getByText('Real')).toBeInTheDocument(); }); - it('should render component', () => { - const mock_root_store = mockStore({ - client: { - mt5_login_list: [ - { - account_type: 'demo', - sub_account_type: 'financial_stp', - }, - ], - currency: 'USD', - can_change_fiat_currency: false, - current_currency_type: 'fiat', - is_switching: false, - is_virtual: false, - }, - modules: { - cashier: { - iframe: {}, - transaction_history: { - is_crypto_transactions_visible: false, - onMount: jest.fn(), - }, - deposit: { - error: { is_ask_uk_funds_protection: false, message: '', setErrorMessage: jest.fn() }, - onMountDeposit: jest.fn(), - }, - general_store: { - is_deposit: false, - is_loading: false, - setActiveTab: jest.fn(), - setIsDeposit: jest.fn(), - }, - }, - }, - traders_hub: { content_flag: ContentFlag.CR_DEMO }, - }); - - render(, { - wrapper: ({ children }) => {children}, - }); - - expect(screen.getByText('CashierOnboarding')).toBeInTheDocument(); - }); - it('should trigger "setSideNotes" callback', () => { const mock_root_store = mockStore({ client: { diff --git a/packages/cashier/src/pages/deposit/crypto-deposit/__tests__/crypto-deposit.spec.tsx b/packages/cashier/src/pages/deposit/crypto-deposit/__tests__/crypto-deposit.spec.tsx index e7f8be0bcd07..e442d4b56d9c 100644 --- a/packages/cashier/src/pages/deposit/crypto-deposit/__tests__/crypto-deposit.spec.tsx +++ b/packages/cashier/src/pages/deposit/crypto-deposit/__tests__/crypto-deposit.spec.tsx @@ -32,10 +32,12 @@ jest.mock('@deriv/api', () => { ...jest.requireActual('@deriv/api'), useFetch: jest.fn(() => ({ data: { - currencies_config: { - tUSDT: { - minimum_deposit: 2, - minimum_withdrawal: 4.54, + crypto_config: { + currencies_config: { + tUSDT: { + minimum_deposit: 2, + minimum_withdrawal: 4.54, + }, }, }, }, diff --git a/packages/cashier/src/pages/deposit/deposit.tsx b/packages/cashier/src/pages/deposit/deposit.tsx index 6b44b6350f8e..b5c80c5c7b51 100644 --- a/packages/cashier/src/pages/deposit/deposit.tsx +++ b/packages/cashier/src/pages/deposit/deposit.tsx @@ -3,7 +3,6 @@ import { Loading } from '@deriv/components'; import { useCashierLocked, useDepositLocked, useIsSystemMaintenance } from '@deriv/hooks'; import { useStore, observer } from '@deriv/stores'; import { Real, Virtual } from '../../components/cashier-container'; -import { CashierOnboarding, CashierOnboardingSideNote } from '../../components/cashier-onboarding'; import CashierLocked from '../../components/cashier-locked'; import CryptoTransactionsHistory from '../../components/crypto-transactions-history'; import Error from '../../components/error'; @@ -14,6 +13,8 @@ import CryptoDeposit from './crypto-deposit'; import DepositLocked from './deposit-locked'; import SideNote from '../../components/side-note'; import { useCashierStore } from '../../stores/useCashierStores'; +import { CashierOnboardingModule } from '../../modules'; +import { CashierOnboardingSideNotes } from '../../modules/cashier-onboarding/components'; type TDeposit = { setSideNotes: (notes: object | null) => void; @@ -88,18 +89,11 @@ const Deposit = observer(({ setSideNotes }: TDeposit) => { ]); } } - if (is_fiat_currency_banner_visible_for_MF_clients) { - setSideNotes([ - - - , - ]); - } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currency, tab_index, crypto_transactions, crypto_transactions?.length, is_cashier_onboarding, iframe_height]); - if ((is_switching || (is_loading && !iframe_url)) && !is_crypto_transactions_visible) { + if (!is_cashier_onboarding && (is_switching || (is_loading && !iframe_url)) && !is_crypto_transactions_visible) { return ; } if (is_virtual) { @@ -134,15 +128,13 @@ const Deposit = observer(({ setSideNotes }: TDeposit) => { return ( <> {is_fiat_currency_banner_visible_for_MF_clients && ( - - - + )} ); } - return ; + return ; }); export default Deposit; diff --git a/packages/cashier/src/stores/__tests__/account-prompt-dialog-store.spec.ts b/packages/cashier/src/stores/__tests__/account-prompt-dialog-store.spec.ts deleted file mode 100644 index 2c51116cc907..000000000000 --- a/packages/cashier/src/stores/__tests__/account-prompt-dialog-store.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { routes } from '@deriv/shared'; -import { mockStore } from '@deriv/stores'; -import AccountPromptDialogStore from '../account-prompt-dialog-store'; -import { TRootStore } from '../../types'; - -describe('AccountPromptDialogStore', () => { - let account_prompt_dialog_store: AccountPromptDialogStore; - const root_store = mockStore({ - common: { - routeTo: jest.fn(), - }, - client: { - accounts: { - CR90000001: { - is_virtual: 0, - currency: 'USD', - }, - CR90000002: { - is_virtual: 0, - currency: 'BTC', - }, - }, - currency: 'BTC', - switchAccount: jest.fn(), - }, - modules: { - cashier: { - general_store: { - setIsDeposit: jest.fn(), - }, - }, - }, - }); - - beforeEach(() => { - account_prompt_dialog_store = new AccountPromptDialogStore(root_store as TRootStore); - }); - - it('should show the dialog', () => { - account_prompt_dialog_store.shouldNavigateAfterPrompt(routes.cashier_deposit, 'deposit'); - - expect(account_prompt_dialog_store.last_location).toBe(routes.cashier_deposit); - expect(account_prompt_dialog_store.should_show).toBeTruthy(); - expect(account_prompt_dialog_store.current_location).toBe('deposit'); - }); - - it('should reset last_location', () => { - account_prompt_dialog_store.resetLastLocation(); - - expect(account_prompt_dialog_store.last_location).toBeNull(); - }); - - it('should reset is_confirmed', () => { - account_prompt_dialog_store.resetIsConfirmed(); - - expect(account_prompt_dialog_store.is_confirmed).toBeFalsy(); - }); - - it('should hide the dialog then switch to fiat account if the client is on crypto account upon confirm', async () => { - account_prompt_dialog_store.shouldNavigateAfterPrompt(routes.cashier_deposit, 'deposit'); - await account_prompt_dialog_store.onConfirm(); - - expect(account_prompt_dialog_store.should_show).toBeFalsy(); - expect(account_prompt_dialog_store.is_confirmed).toBeTruthy(); - expect(account_prompt_dialog_store.root_store.client.switchAccount).toHaveBeenCalledWith('CR90000001'); - expect(account_prompt_dialog_store.root_store.modules.cashier.general_store.setIsDeposit).toHaveBeenCalledWith( - true - ); - - account_prompt_dialog_store.continueRoute(); - expect(account_prompt_dialog_store.root_store.common.routeTo).toHaveBeenCalledWith(routes.cashier_deposit); - }); - - it('should hide the dialog then stay on page if the client is on fiat account upon confirm', async () => { - account_prompt_dialog_store.root_store.client.currency = 'USD'; - - account_prompt_dialog_store.shouldNavigateAfterPrompt(routes.cashier_deposit, 'deposit'); - await account_prompt_dialog_store.onConfirm(); - - expect(account_prompt_dialog_store.should_show).toBeFalsy(); - expect(account_prompt_dialog_store.is_confirmed).toBeTruthy(); - expect(account_prompt_dialog_store.root_store.client.switchAccount).not.toHaveBeenCalled(); - }); - - it('should hide the dialog on cancel', () => { - account_prompt_dialog_store.onCancel(); - - expect(account_prompt_dialog_store.should_show).toBeFalsy(); - }); -}); diff --git a/packages/cashier/src/stores/__tests__/general-store.spec.ts b/packages/cashier/src/stores/__tests__/general-store.spec.ts index d08c1691e2bb..f40f4155b2e4 100644 --- a/packages/cashier/src/stores/__tests__/general-store.spec.ts +++ b/packages/cashier/src/stores/__tests__/general-store.spec.ts @@ -34,10 +34,6 @@ beforeEach(() => { }, modules: { cashier: { - account_prompt_dialog: { - last_location: null, - resetIsConfirmed: jest.fn(), - }, account_transfer: { accounts_list: [], container: 'account_transfer', @@ -99,32 +95,6 @@ describe('GeneralStore', () => { expect(general_store.is_crypto).toBeTruthy(); }); - it('should set has_set_currency equal to true if the client has real USD account', () => { - general_store.setHasSetCurrency(); - - expect(general_store.has_set_currency).toBeTruthy(); - }); - - it('should set has_set_currency equal to false if the client has real account with account.title = "Real"', () => { - general_store.root_store.client.account_list = [{ is_virtual: 1, title: 'Real' }]; - general_store.root_store.client.has_active_real_account = true; - general_store.setHasSetCurrency(); - - expect(general_store.has_set_currency).toBeFalsy(); - }); - - it('should perform proper cashier onboarding mounting', async () => { - general_store.has_set_currency = false; - const spySetHasSetCurrency = jest.spyOn(general_store, 'setHasSetCurrency'); - const spySetIsCashierOnboarding = jest.spyOn(general_store, 'setIsCashierOnboarding'); - const { account_prompt_dialog } = general_store.root_store.modules.cashier; - await general_store.onMountCashierOnboarding(); - - expect(spySetHasSetCurrency).toHaveBeenCalledTimes(1); - expect(spySetIsCashierOnboarding).toHaveBeenCalledWith(true); - expect(account_prompt_dialog.resetIsConfirmed).toHaveBeenCalledTimes(1); - }); - it('should calculate proper percentage for account transfer container', () => { general_store.root_store.modules.cashier.crypto_fiat_converter.converter_from_amount = '500'; general_store.root_store.modules.cashier.account_transfer.selected_from.balance = 10000; @@ -169,12 +139,6 @@ describe('GeneralStore', () => { expect(general_store.should_show_all_available_currencies).toBeTruthy(); }); - it('should change value of the variable is_cashier_onboarding', () => { - general_store.setIsCashierOnboarding(true); - - expect(general_store.is_cashier_onboarding).toBeTruthy(); - }); - it('should set deposit target', () => { general_store.setDepositTarget('/cashier/payment-agent'); diff --git a/packages/cashier/src/stores/account-prompt-dialog-store.ts b/packages/cashier/src/stores/account-prompt-dialog-store.ts deleted file mode 100644 index b18da9808383..000000000000 --- a/packages/cashier/src/stores/account-prompt-dialog-store.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { observable, action, makeObservable } from 'mobx'; -import { isCryptocurrency } from '@deriv/shared'; -import { TRootStore } from '../types'; - -export default class AccountPromptDialogStore { - constructor(public root_store: TRootStore) { - makeObservable(this, { - should_show: observable, - is_confirmed: observable, - last_location: observable, - current_location: observable, - shouldNavigateAfterPrompt: action.bound, - resetLastLocation: action.bound, - resetIsConfirmed: action.bound, - onConfirm: action.bound, - onCancel: action.bound, - continueRoute: action.bound, - }); - } - - should_show = false; - is_confirmed = false; - last_location: string | null = null; - current_location: string | null = null; - - shouldNavigateAfterPrompt(next_location: string, current_location: string) { - if (!this.is_confirmed) { - this.last_location = next_location; - this.should_show = true; - this.current_location = current_location; - } - } - - resetLastLocation() { - this.last_location = null; - } - - resetIsConfirmed() { - this.is_confirmed = false; - } - - async onConfirm() { - const { client } = this.root_store; - - this.should_show = false; - this.is_confirmed = true; - - const has_fiat_account = Object.values(client.accounts).some( - acc_settings => !acc_settings.is_virtual && !isCryptocurrency(acc_settings.currency || '') - ); - if (isCryptocurrency(client?.currency) && has_fiat_account) await this.doSwitch(); - } - - async doSwitch() { - const { client, modules } = this.root_store; - const { general_store } = modules.cashier; - - const non_crypto_account_loginid = Object.entries(client.accounts).reduce( - (initial_value, [loginid, settings]) => { - return !settings.is_virtual && !isCryptocurrency(settings.currency || '') && loginid.startsWith('CR') - ? loginid - : initial_value; - }, - '' - ); - await client.switchAccount(non_crypto_account_loginid); - - if (this.current_location === 'deposit') { - general_store.setIsDeposit(true); - } - } - - onCancel() { - this.should_show = false; - } - - continueRoute() { - if (this.is_confirmed && this.last_location) { - this.root_store.common.routeTo(this.last_location); - } - } -} diff --git a/packages/cashier/src/stores/cashier-store.ts b/packages/cashier/src/stores/cashier-store.ts index 988356a65784..babe286b5ea3 100644 --- a/packages/cashier/src/stores/cashier-store.ts +++ b/packages/cashier/src/stores/cashier-store.ts @@ -1,4 +1,3 @@ -import AccountPromptDialogStore from './account-prompt-dialog-store'; import AccountTransferStore from './account-transfer-store'; import CryptoFiatConverterStore from './crypto-fiat-converter-store'; import DepositStore from './deposit-store'; @@ -14,7 +13,6 @@ import WithdrawStore from './withdraw-store'; import type { TRootStore, TWebSocket } from '../types'; export default class CashierStore { - account_prompt_dialog: AccountPromptDialogStore; account_transfer: AccountTransferStore; crypto_fiat_converter: CryptoFiatConverterStore; deposit: DepositStore; @@ -29,7 +27,6 @@ export default class CashierStore { withdraw: WithdrawStore; constructor(public root_store: TRootStore, public WS: TWebSocket) { - this.account_prompt_dialog = new AccountPromptDialogStore(root_store); this.account_transfer = new AccountTransferStore(WS, root_store); this.crypto_fiat_converter = new CryptoFiatConverterStore(WS, root_store); this.deposit = new DepositStore(WS, root_store); diff --git a/packages/cashier/src/stores/general-store.ts b/packages/cashier/src/stores/general-store.ts index d8b9bd7ef8b5..a48b341fc38b 100644 --- a/packages/cashier/src/stores/general-store.ts +++ b/packages/cashier/src/stores/general-store.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, reaction, when, makeObservable } from 'mobx'; +import { action, computed, observable, reaction, makeObservable } from 'mobx'; import { isCryptocurrency, routes } from '@deriv/shared'; import Constants from 'Constants/constants'; import BaseStore from './base-store'; @@ -13,13 +13,11 @@ export default class GeneralStore extends BaseStore { calculatePercentage: action.bound, cashier_route_tab_index: observable, deposit_target: observable, - has_set_currency: observable, init: action.bound, is_cashier_onboarding: observable, is_crypto: computed, is_deposit: observable, is_loading: observable, - onMountCashierOnboarding: action.bound, onMountCommon: action.bound, onRemount: observable, percentage: observable, @@ -29,7 +27,6 @@ export default class GeneralStore extends BaseStore { setActiveTab: action.bound, setCashierTabIndex: action.bound, setDepositTarget: action.bound, - setHasSetCurrency: action.bound, setIsCashierOnboarding: action.bound, setIsDeposit: action.bound, setLoading: action.bound, @@ -39,13 +36,6 @@ export default class GeneralStore extends BaseStore { should_show_all_available_currencies: observable, }); - when( - () => this.root_store.client.is_logged_in, - () => { - this.setHasSetCurrency(); - } - ); - reaction( () => [ this.root_store.client.switched, @@ -61,7 +51,6 @@ export default class GeneralStore extends BaseStore { active_container: keyof typeof Constants.containers = Constants.containers.deposit; cashier_route_tab_index = 0; deposit_target = ''; - has_set_currency = false; is_cashier_onboarding = true; is_deposit = false; is_loading = false; @@ -81,24 +70,6 @@ export default class GeneralStore extends BaseStore { return !!currency && isCryptocurrency(currency); } - setHasSetCurrency(): void { - const { account_list, has_active_real_account } = this.root_store.client; - - this.has_set_currency = - account_list.filter(account => !account.is_virtual).some(account => account.title !== 'Real') || - !has_active_real_account; - } - - async onMountCashierOnboarding() { - const { account_prompt_dialog } = this.root_store.modules.cashier; - - if (!this.has_set_currency) { - this.setHasSetCurrency(); - } - this.setIsCashierOnboarding(true); - account_prompt_dialog.resetIsConfirmed(); - } - calculatePercentage(amount = this.root_store.modules.cashier.crypto_fiat_converter.converter_from_amount): void { const { client, modules } = this.root_store; const { account_transfer } = modules.cashier; diff --git a/packages/core/src/App/Containers/SetAccountCurrencyModal/set-currency-modal.jsx b/packages/core/src/App/Containers/SetAccountCurrencyModal/set-currency-modal.jsx index 636875d8de33..7fcf33702e8b 100644 --- a/packages/core/src/App/Containers/SetAccountCurrencyModal/set-currency-modal.jsx +++ b/packages/core/src/App/Containers/SetAccountCurrencyModal/set-currency-modal.jsx @@ -1,13 +1,18 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Button, Modal } from '@deriv/components'; import { useHasSetCurrency } from '@deriv/hooks'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; - +import { observer, useStore } from '@deriv/stores'; import 'Sass/set-currency-modal.scss'; -const SetAccountCurrencyModal = ({ is_visible, is_virtual, setCurrency, toggleModal }) => { +const SetAccountCurrencyModal = observer(() => { + const { client, ui } = useStore(); + const { is_virtual } = client; + const { + is_set_currency_modal_visible: is_visible, + openRealAccountSignup: setCurrency, + toggleSetCurrencyModal: toggleModal, + } = ui; const has_set_currency = useHasSetCurrency(); return ( @@ -49,18 +54,6 @@ const SetAccountCurrencyModal = ({ is_visible, is_virtual, setCurrency, toggleMo ); -}; - -SetAccountCurrencyModal.propTypes = { - is_virtual: PropTypes.bool, - is_visible: PropTypes.bool, - setCurrency: PropTypes.func, - toggleModal: PropTypes.func, -}; +}); -export default connect(({ client, ui }) => ({ - is_virtual: client.is_virtual, - is_visible: ui.is_set_currency_modal_visible, - setCurrency: ui.openRealAccountSignup, - toggleModal: ui.toggleSetCurrencyModal, -}))(SetAccountCurrencyModal); +export default SetAccountCurrencyModal; diff --git a/packages/hooks/jest.config.js b/packages/hooks/jest.config.js index f53d37480f70..207910d7e732 100644 --- a/packages/hooks/jest.config.js +++ b/packages/hooks/jest.config.js @@ -1 +1,5 @@ -module.exports = require('../../jest.config.base'); +const baseConfigForPackages = require('../../jest.config.base'); + +module.exports = { + ...baseConfigForPackages, +}; diff --git a/packages/hooks/src/__tests__/useCountdown.spec.tsx b/packages/hooks/src/__tests__/useCountdown.spec.tsx index 90998c35f546..d7957d7aa15d 100644 --- a/packages/hooks/src/__tests__/useCountdown.spec.tsx +++ b/packages/hooks/src/__tests__/useCountdown.spec.tsx @@ -1,4 +1,4 @@ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react-hooks'; import useCountdown from '../useCountdown'; jest.setTimeout(30000); diff --git a/packages/hooks/src/__tests__/useFiatAccountList.spec.tsx b/packages/hooks/src/__tests__/useFiatAccountList.spec.tsx new file mode 100644 index 000000000000..e35bc2d7676e --- /dev/null +++ b/packages/hooks/src/__tests__/useFiatAccountList.spec.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useFiatAccountList from '../useFiatAccountList'; + +describe('useFiatAccountList', () => { + test('should return an empty list if client has no account', async () => { + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useFiatAccountList(), { wrapper }); + + expect(result.current).toStrictEqual([]); + }); + + test('should return an empty list if client has no fiat account', async () => { + const mock = mockStore({ + client: { + account_list: [{ title: 'BTC', is_virtual: false, loginid: 'CR123' }], + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useFiatAccountList(), { wrapper }); + + expect(result.current).toStrictEqual([]); + }); + + test('should return list of fiat accounts if client any fiat account', async () => { + const mock = mockStore({ + client: { + account_list: [ + { title: 'BTC', is_virtual: false, loginid: 'CR123' }, + { title: 'USD', is_virtual: false, loginid: 'CR123' }, + ], + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useFiatAccountList(), { wrapper }); + + expect(result.current).toStrictEqual([{ title: 'USD', is_virtual: false, loginid: 'CR123' }]); + }); +}); diff --git a/packages/hooks/src/__tests__/useHasCryptoCurrency.spec.tsx b/packages/hooks/src/__tests__/useHasCryptoCurrency.spec.tsx new file mode 100644 index 000000000000..91c2230b9a48 --- /dev/null +++ b/packages/hooks/src/__tests__/useHasCryptoCurrency.spec.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useHasCryptoCurrency from '../useHasCryptoCurrency'; + +describe('useHasCryptoCurrency', () => { + test('should return false if client has no account', async () => { + const mock = mockStore({}); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useHasCryptoCurrency(), { wrapper }); + + expect(result.current).toBe(false); + }); + + test('should return false if client has no crypto account', async () => { + const mock = mockStore({ + client: { + account_list: [{ title: 'USD' }], + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useHasCryptoCurrency(), { wrapper }); + + expect(result.current).toBe(false); + }); + + test('should return true if client has crypto account', async () => { + const mock = mockStore({ + client: { + account_list: [{ title: 'BTC' }], + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useHasCryptoCurrency(), { wrapper }); + + expect(result.current).toBe(true); + }); + + test('should return true if client has at least one crypto account', async () => { + const mock = mockStore({ + client: { + account_list: [{ title: 'BTC' }, { title: 'USD' }], + is_crypto: (currency: string) => currency === 'BTC', + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useHasCryptoCurrency(), { wrapper }); + + expect(result.current).toBe(true); + }); +}); diff --git a/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx b/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx index 68ed68ea2796..861279dc30b0 100644 --- a/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx +++ b/packages/hooks/src/__tests__/useIsP2PEnabled.spec.tsx @@ -16,7 +16,7 @@ describe('useIsP2PEnabled', () => { const mock = mockStore({ client: { currency: 'AUD' } }); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { p2p_config: { supported_currencies: ['usd'] } } }); + mockUseFetch.mockReturnValue({ data: { website_status: { p2p_config: { supported_currencies: ['usd'] } } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( @@ -38,7 +38,7 @@ describe('useIsP2PEnabled', () => { }); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { p2p_config: { supported_currencies: ['usd'] } } }); + mockUseFetch.mockReturnValue({ data: { website_status: { p2p_config: { supported_currencies: ['usd'] } } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( @@ -59,7 +59,7 @@ describe('useIsP2PEnabled', () => { }); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { p2p_config: { supported_currencies: ['usd'] } } }); + mockUseFetch.mockReturnValue({ data: { website_status: { p2p_config: { supported_currencies: ['usd'] } } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( @@ -83,7 +83,7 @@ describe('useIsP2PEnabled', () => { }); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { p2p_config: { supported_currencies: ['usd'] } } }); + mockUseFetch.mockReturnValue({ data: { website_status: { p2p_config: { supported_currencies: ['usd'] } } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( diff --git a/packages/hooks/src/__tests__/usePaymentAgentList.spec.tsx b/packages/hooks/src/__tests__/usePaymentAgentList.spec.tsx index c842ca388693..9bd2ac0609d3 100644 --- a/packages/hooks/src/__tests__/usePaymentAgentList.spec.tsx +++ b/packages/hooks/src/__tests__/usePaymentAgentList.spec.tsx @@ -33,7 +33,7 @@ describe('usePaymentAgentList', () => { const mock = mockStore({}); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { list: [] } }); + mockUseFetch.mockReturnValue({ data: { paymentagent_list: { list: [] } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( @@ -50,7 +50,7 @@ describe('usePaymentAgentList', () => { const mock = mockStore({}); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { list: ['PAYMENT AGENT 1', 'PAYMENT AGENT 2'] } }); + mockUseFetch.mockReturnValue({ data: { paymentagent_list: { list: ['PAYMENT AGENT 1', 'PAYMENT AGENT 2'] } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( diff --git a/packages/hooks/src/__tests__/usePaymentAgentTransferVisible.spec.tsx b/packages/hooks/src/__tests__/usePaymentAgentTransferVisible.spec.tsx index 00c2f347c20c..5e21191f69de 100644 --- a/packages/hooks/src/__tests__/usePaymentAgentTransferVisible.spec.tsx +++ b/packages/hooks/src/__tests__/usePaymentAgentTransferVisible.spec.tsx @@ -16,7 +16,7 @@ describe('usePaymentAgentTransferVisible', () => { const mock = mockStore({}); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { is_authenticated_payment_agent: 0 } }); + mockUseFetch.mockReturnValue({ data: { get_settings: { is_authenticated_payment_agent: 0 } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( @@ -33,7 +33,7 @@ describe('usePaymentAgentTransferVisible', () => { const mock = mockStore({}); // @ts-expect-error need to come up with a way to mock the return type of useFetch - mockUseFetch.mockReturnValue({ data: { is_authenticated_payment_agent: 1 } }); + mockUseFetch.mockReturnValue({ data: { get_settings: { is_authenticated_payment_agent: 1 } } }); const wrapper = ({ children }: { children: JSX.Element }) => ( diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index d60373811cbb..d114601b9ad6 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -7,7 +7,9 @@ export { default as useCashierLocked } from './useCashierLocked'; export { default as useCountdown } from './useCountdown'; export { default as useDepositLocked } from './useDepositLocked'; export { default as useExchangeRate } from './useExchangeRate'; +export { default as useFiatAccountList } from './useFiatAccountList'; export { default as useHasActiveRealAccount } from './useHasActiveRealAccount'; +export { default as useHasCryptoCurrency } from './useHasCryptoCurrency'; export { default as useHasFiatCurrency } from './useHasFiatCurrency'; export { default as useHasMaltaInvestAccount } from './useHasMaltaInvestAccount'; export { default as useHasSetCurrency } from './useHasSetCurrency'; diff --git a/packages/hooks/src/useFiatAccountList.ts b/packages/hooks/src/useFiatAccountList.ts new file mode 100644 index 000000000000..07f623e3c62d --- /dev/null +++ b/packages/hooks/src/useFiatAccountList.ts @@ -0,0 +1,14 @@ +import { useStore } from '@deriv/stores'; + +const useFiatAccountList = () => { + const { client } = useStore(); + const { account_list, is_crypto } = client; + + const fiat_account_list = account_list.filter( + account => !account.is_virtual && !is_crypto(account.title || '') && account.loginid?.startsWith('CR') + ); + + return fiat_account_list; +}; + +export default useFiatAccountList; diff --git a/packages/hooks/src/useHasCryptoCurrency.ts b/packages/hooks/src/useHasCryptoCurrency.ts new file mode 100644 index 000000000000..b7c824814cca --- /dev/null +++ b/packages/hooks/src/useHasCryptoCurrency.ts @@ -0,0 +1,12 @@ +import { useStore } from '@deriv/stores'; + +const useHasCryptoCurrency = () => { + const { client } = useStore(); + const { account_list, is_crypto } = client; + + const has_crypto_currency = account_list.some(account => is_crypto(account.title || 'USD')); + + return has_crypto_currency; +}; + +export default useHasCryptoCurrency; diff --git a/packages/hooks/src/useIsRealAccountNeededForCashier.ts b/packages/hooks/src/useIsRealAccountNeededForCashier.ts index 493ec7d5f38f..468eddf6bdcf 100644 --- a/packages/hooks/src/useIsRealAccountNeededForCashier.ts +++ b/packages/hooks/src/useIsRealAccountNeededForCashier.ts @@ -1,6 +1,6 @@ import { useStore } from '@deriv/stores'; -import useHasSvgAccount from './useHasSvgAccount'; import useHasMaltaInvestAccount from './useHasMaltaInvestAccount'; +import useHasSvgAccount from './useHasSvgAccount'; const useIsRealAccountNeededForCashier = () => { const { traders_hub } = useStore(); diff --git a/packages/stores/jest.config.js b/packages/stores/jest.config.js index f53d37480f70..207910d7e732 100644 --- a/packages/stores/jest.config.js +++ b/packages/stores/jest.config.js @@ -1 +1,5 @@ -module.exports = require('../../jest.config.base'); +const baseConfigForPackages = require('../../jest.config.base'); + +module.exports = { + ...baseConfigForPackages, +};