diff --git a/packages/appstore/package.json b/packages/appstore/package.json index 044ee50f2da5..ee529eb90634 100644 --- a/packages/appstore/package.json +++ b/packages/appstore/package.json @@ -33,7 +33,6 @@ "@deriv/stores": "^1.0.0", "@deriv/trader": "^3.8.0", "@deriv/translations": "^1.0.0", - "@deriv/ui": "^0.0.15", "classnames": "^2.2.6", "formik": "^2.1.4", "mobx": "^6.6.1", diff --git a/packages/cashier/package.json b/packages/cashier/package.json index 2f98879db66a..826a3383f240 100644 --- a/packages/cashier/package.json +++ b/packages/cashier/package.json @@ -44,6 +44,7 @@ "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", + "@deriv/ui": "^0.6.0", "classnames": "^2.2.6", "formik": "^2.1.4", "loadjs": "^4.2.0", diff --git a/packages/cashier/src/app-content.tsx b/packages/cashier/src/app-content.tsx new file mode 100644 index 000000000000..76a4160a938b --- /dev/null +++ b/packages/cashier/src/app-content.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import Routes from 'Containers/routes'; +import { observer, useStore } from '@deriv/stores'; +import { useTheme } from '@deriv/ui'; + +const AppContent = observer(() => { + const { ui } = useStore(); + const { is_dark_mode_on, notification_messages_ui: Notifications } = ui; + const { setColorMode } = useTheme(); + + React.useEffect(() => { + const theme = (is_dark_mode_on ? 'dark' : 'light') as Parameters[0]; + setColorMode(theme); + }, [is_dark_mode_on, setColorMode]); + + return ( + <> + {Notifications && } + + + ); +}); + +export default AppContent; diff --git a/packages/cashier/src/app.jsx b/packages/cashier/src/app.jsx index 7c666ea47006..a5b2620f6947 100644 --- a/packages/cashier/src/app.jsx +++ b/packages/cashier/src/app.jsx @@ -1,12 +1,10 @@ import React from 'react'; import { setWebsocket } from '@deriv/shared'; import { init } from 'Utils/server_time'; -import Routes from 'Containers/routes'; import CashierProviders from './cashier-providers'; +import AppContent from './app-content'; const App = ({ passthrough: { WS, root_store } }) => { - const { notification_messages_ui: Notifications } = root_store.ui; - React.useEffect(() => { setWebsocket(WS); init(); @@ -15,8 +13,7 @@ const App = ({ passthrough: { WS, root_store } }) => { return ( - {Notifications && } - + ); }; diff --git a/packages/cashier/src/cashier-providers.tsx b/packages/cashier/src/cashier-providers.tsx index 897139270e70..01f2b459d1bf 100644 --- a/packages/cashier/src/cashier-providers.tsx +++ b/packages/cashier/src/cashier-providers.tsx @@ -1,12 +1,15 @@ import React from 'react'; import { StoreProvider } from '@deriv/stores'; +import { ThemeProvider } from '@deriv/ui'; import { CashierStoreProvider } from './stores/useCashierStores'; import { TRootStore } from 'Types'; const CashierProviders = ({ children, store }: React.PropsWithChildren<{ store: TRootStore }>) => { return ( - {children} + + {children} + ); }; diff --git a/packages/cashier/src/components/cashier-breadcrumb/__tests__/cashier-breadcrumb.spec.tsx b/packages/cashier/src/components/cashier-breadcrumb/__tests__/cashier-breadcrumb.spec.tsx new file mode 100644 index 000000000000..ea0a73cfcc75 --- /dev/null +++ b/packages/cashier/src/components/cashier-breadcrumb/__tests__/cashier-breadcrumb.spec.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import CashierBreadcrumb from '../cashier-breadcrumb'; + +jest.mock('Stores/useCashierStores', () => ({ + ...jest.requireActual('Stores/useCashierStores'), + useCashierStore: jest.fn(() => ({ + general_store: { setIsDeposit: jest.fn() }, + })), +})); + +describe('', () => { + it('should render proper crumbs for crypto deposit page', () => { + render(); + + expect(screen.getByText(/cashier/i)).toBeInTheDocument(); + expect(screen.getByText(/deposit cryptocurrencies/i)).toBeInTheDocument(); + }); + + it('should render proper crumbs for fiat deposit page', () => { + render(); + + expect(screen.getByText(/cashier/i)).toBeInTheDocument(); + expect(screen.getByText(/deposit via bank wire, credit card, and e-wallet/i)).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.scss b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.scss new file mode 100644 index 000000000000..cf39a7c89ba2 --- /dev/null +++ b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.scss @@ -0,0 +1,16 @@ +.cashier-breadcrumb { + width: 100%; + margin-bottom: 1.6rem; + + ul > *:not(:last-child) { + cursor: pointer; + } + + li { + text-align: left; + } + + @include mobile { + margin-top: 1.6rem; + } +} diff --git a/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx new file mode 100644 index 000000000000..205bc1173604 --- /dev/null +++ b/packages/cashier/src/components/cashier-breadcrumb/cashier-breadcrumb.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { localize } from '@deriv/translations'; +import { Breadcrumb } from '@deriv/ui'; +import { useCashierStore } from '../../stores/useCashierStores'; +import './cashier-breadcrumb.scss'; + +const CashierBreadcrumb = ({ is_crypto_deposit = false }: { is_crypto_deposit?: boolean }) => { + const { + general_store: { setIsDeposit }, + } = useCashierStore(); + + const crypto_deposit_crumbs: React.ComponentProps['items'] = [ + { value: 0, text: localize('Cashier') }, + { value: 1, text: localize('Deposit cryptocurrencies') }, + ]; + + const fiat_deposit_crumbs: React.ComponentProps['items'] = [ + { value: 0, text: localize('Cashier') }, + { value: 1, text: localize('Deposit via bank wire, credit card, and e-wallet') }, + ]; + + const onBreadcrumbHandler: React.ComponentProps['handleOnClick'] = item => { + switch (item.value) { + case 0: + setIsDeposit(false); + break; + default: + } + }; + + // TODO: improve Breadcrumb component in deriv-ui project that it can accept custom classnames + return ( +
+ +
+ ); +}; + +export default CashierBreadcrumb; diff --git a/packages/cashier/src/components/cashier-breadcrumb/index.ts b/packages/cashier/src/components/cashier-breadcrumb/index.ts new file mode 100644 index 000000000000..8a2f99e4f8c7 --- /dev/null +++ b/packages/cashier/src/components/cashier-breadcrumb/index.ts @@ -0,0 +1,3 @@ +import CashierBreadcrumb from './cashier-breadcrumb'; + +export default CashierBreadcrumb; diff --git a/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx b/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx index 907fecb28b06..7e694cd94e32 100644 --- a/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx +++ b/packages/cashier/src/components/cashier-container/real/__tests__/real.spec.tsx @@ -1,41 +1,144 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { useCashierStore } from '../../../../stores/useCashierStores'; +import userEvent from '@testing-library/user-event'; import Real from '../real'; jest.mock('@deriv/components', () => ({ - ...(jest.requireActual('@deriv/components') as any), + ...jest.requireActual('@deriv/components'), Loading: () =>
Loading
, })); +let mocked_cashier_store: DeepPartial>; + +jest.mock('Stores/useCashierStores', () => ({ + ...jest.requireActual('Stores/useCashierStores'), + useCashierStore: jest.fn(() => mocked_cashier_store), +})); + describe('', () => { - const props = { - iframe_url: 'https://www.test_url.com', - clearIframe: jest.fn(), - iframe_height: '', - is_loading: false, - }; - - it('should render the component with iframe when iframe_url value is passed', () => { - render(); - const el_loader = screen.queryByText('Loading'); - - expect(el_loader).not.toBeInTheDocument(); - expect(screen.queryByTestId('dt_doughflow_section')).toBeInTheDocument(); + beforeEach(() => { + mocked_cashier_store = { + iframe: { + clearIframe: jest.fn(), + iframe_height: 100, + iframe_url: 'https://www.test_url.com', + }, + deposit: { + onMountDeposit: jest.fn(), + }, + general_store: { + is_loading: false, + }, + }; }); - it('should render the loading when is_loading is true', () => { - render(); - const el_loader = screen.queryByText('Loading'); + const mockRootStore = mockStore({}); + + it('should show loader when is_loading is true or iframe_height is equal to 0', () => { + (useCashierStore as jest.Mock).mockReturnValueOnce({ + ...mocked_cashier_store, + iframe: { ...mocked_cashier_store.iframe, iframe_height: 0 }, + }); + + const { rerender } = render( + + + + ); + + expect(screen.getByText('Loading')).toBeInTheDocument(); - expect(el_loader).toBeInTheDocument(); - expect(screen.queryByTestId('dt_doughflow_section')).not.toBeInTheDocument(); + (useCashierStore as jest.Mock).mockReturnValueOnce({ + ...mocked_cashier_store, + general_store: { is_loading: true }, + }); + + rerender( + + + + ); + + expect(screen.getByText('Loading')).toBeInTheDocument(); }); - it('will display doughflow and loader if all props are provided', () => { - render(); - const el_loader = screen.queryByText('Loading'); + it('should render an iframe if iframe_url is not an empty string', () => { + render( + + + + ); - expect(el_loader).toBeInTheDocument(); expect(screen.queryByTestId('dt_doughflow_section')).toBeInTheDocument(); }); + + describe('Breadcrumb visibility', () => { + it('should show breadcrumbs only on deposit page and only for non EU clients', () => { + render( + + + + ); + + expect(screen.getByText(/cashier/i)).toBeInTheDocument(); + expect(screen.getByText(/deposit via bank wire, credit card, and e-wallet/i)).toBeInTheDocument(); + }); + + it('should not show breadcrumbs on deposit page if iframe_height is equal to 0', () => { + (useCashierStore as jest.Mock).mockReturnValueOnce({ + ...mocked_cashier_store, + iframe: { ...mocked_cashier_store.iframe, iframe_height: 0 }, + }); + + render( + + + + ); + + expect(screen.queryByText(/cashier/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/deposit via bank wire, credit card, and e-wallet/i)).not.toBeInTheDocument(); + }); + + it('should not show breadcrumbs on withdraw page', () => { + render( + + + + ); + + expect(screen.queryByText(/cashier/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/deposit via bank wire, credit card, and e-wallet/i)).not.toBeInTheDocument(); + }); + }); + + it('should trigger setIsDeposit callback when the user clicks on Cashier breadcrumb', () => { + render( + + + + ); + + const el_breadcrumb_cashier = screen.queryByText(/cashier/i); + + if (el_breadcrumb_cashier) { + userEvent.click(el_breadcrumb_cashier); + expect(mocked_cashier_store.general_store?.setIsDeposit).toHaveBeenCalledWith(false); + } + }); + + it('should trigger clearIframe and onMountDeposit callbacks when component is destroyed on deposit page', () => { + const { unmount } = render( + + + + ); + + unmount(); + + expect(mocked_cashier_store.iframe?.clearIframe).toHaveBeenCalled(); + expect(mocked_cashier_store.deposit?.onMountDeposit).toHaveBeenCalled(); + }); }); diff --git a/packages/cashier/src/components/cashier-container/real/real.scss b/packages/cashier/src/components/cashier-container/real/real.scss new file mode 100644 index 000000000000..156134338ee5 --- /dev/null +++ b/packages/cashier/src/components/cashier-container/real/real.scss @@ -0,0 +1,6 @@ +.real { + &__loader { + width: 100%; + height: 80vh; + } +} diff --git a/packages/cashier/src/components/cashier-container/real/real.tsx b/packages/cashier/src/components/cashier-container/real/real.tsx index c9bc2a8ff0d7..5559e237f72f 100644 --- a/packages/cashier/src/components/cashier-container/real/real.tsx +++ b/packages/cashier/src/components/cashier-container/real/real.tsx @@ -1,23 +1,37 @@ import React from 'react'; import { Loading } from '@deriv/components'; +import { useStore } from '@deriv/stores'; +import CashierBreadcrumb from '../../cashier-breadcrumb'; +import { useCashierStore } from '../../../stores/useCashierStores'; +import './real.scss'; -type TRealProps = { - iframe_height: number | string; - iframe_url: string; - clearIframe: VoidFunction; - is_loading: boolean; -}; +const Real = ({ is_deposit = false }: { is_deposit?: boolean }) => { + const { + traders_hub: { is_low_risk_cr_eu_real }, + } = useStore(); + + const { iframe, deposit, general_store } = useCashierStore(); + + const { clearIframe, iframe_height, iframe_url } = iframe; + + const { is_loading } = general_store; + + const { onMountDeposit } = deposit; + + const should_show_breadcrumbs = !is_low_risk_cr_eu_real && is_deposit && Boolean(iframe_height); + const should_show_loader = is_loading || !iframe_height; -const Real = ({ iframe_height, iframe_url, clearIframe, is_loading }: TRealProps) => { React.useEffect(() => { return () => { clearIframe(); + onMountDeposit?.(); }; - }, [clearIframe]); + }, [clearIframe, onMountDeposit]); return ( -
- {is_loading && } +
+ {should_show_loader && } + {should_show_breadcrumbs && } {iframe_url && (