diff --git a/packages/cashier/src/components/alert-banner/__tests__/alert-banner.spec.tsx b/packages/cashier/src/components/alert-banner/__tests__/alert-banner.spec.tsx new file mode 100644 index 000000000000..0182a5656440 --- /dev/null +++ b/packages/cashier/src/components/alert-banner/__tests__/alert-banner.spec.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import AlertBanner from '../alert-banner'; + +describe('', () => { + it('should render the icon and message props passed for the banner', () => { + const props: React.ComponentProps = { + icon: 'IcAlertWarningDark', + message: 'Alert banner test message.', + }; + render(); + const dt_alert_banner_icon = screen.getByTestId('dt_alert_banner_icon'); + expect(dt_alert_banner_icon).toBeInTheDocument(); + expect(screen.getByText(props.message)).toBeInTheDocument(); + }); +}); diff --git a/packages/cashier/src/components/alert-banner/alert-banner.scss b/packages/cashier/src/components/alert-banner/alert-banner.scss new file mode 100644 index 000000000000..02a81acfc4a0 --- /dev/null +++ b/packages/cashier/src/components/alert-banner/alert-banner.scss @@ -0,0 +1,13 @@ +.alert-banner { + display: grid; + grid-template-columns: auto auto; + width: auto; + padding: 0.8rem; + margin: 2.4rem 0; + border-radius: $BORDER_RADIUS; + background-color: $alpha-color-yellow-1; + + svg { + margin: 0.3rem 0.3rem 0 0; + } +} diff --git a/packages/cashier/src/components/alert-banner/alert-banner.tsx b/packages/cashier/src/components/alert-banner/alert-banner.tsx new file mode 100644 index 000000000000..f2d5537ead7f --- /dev/null +++ b/packages/cashier/src/components/alert-banner/alert-banner.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Icon, Text } from '@deriv/components'; +import './alert-banner.scss'; + +type TAlertBanner = { + className?: string; + icon: string; + message: string; +}; + +const AlertBanner = ({ className, icon, message }: TAlertBanner) => { + return ( +
+ + + {message} + +
+ ); +}; + +export default AlertBanner; diff --git a/packages/cashier/src/components/crypto-transactions-history/__tests__/crypto-transactions-renderer.spec.tsx b/packages/cashier/src/components/crypto-transactions-history/__tests__/crypto-transactions-renderer.spec.tsx index 290c38cab97a..fb9882e9f5af 100644 --- a/packages/cashier/src/components/crypto-transactions-history/__tests__/crypto-transactions-renderer.spec.tsx +++ b/packages/cashier/src/components/crypto-transactions-history/__tests__/crypto-transactions-renderer.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { isMobile } from '@deriv/shared'; import CryptoTransactionsRenderer from '../crypto-transactions-renderer'; import CashierProviders from '../../../cashier-providers'; @@ -135,4 +136,68 @@ describe('', () => { mockRootStore.modules.cashier.transaction_history.showCryptoTransactionsCancelModal ).toHaveBeenCalledTimes(1); }); + + it('should display popover when hovering on tooltip for third-party transactions (CoinsPaid)', () => { + (isMobile as jest.Mock).mockReturnValue(false); + const tooltip_props = { + row: { + address_hash: 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt', + address_url: 'https://explorer.coinspaid.com/CP:Abcd1234', + amount: 0.005, + id: '3', + is_valid_to_cancel: 1, + status_code: 'LOCKED', + status_message: + "We're reviewing your withdrawal request. You may still cancel this transaction if you wish. Once we start processing, you won't be able to cancel.", + submit_date: 1640603927, + transaction_type: 'withdrawal', + transaction_url: 'CP:Abcd1234', + }, + }; + + render(, { + wrapper: ({ children }) => {children}, + }); + + const crypto_transactions_history_table_tooltip = screen.getByTestId( + 'dt_crypto_transactions_history_table_tooltip' + ); + + expect(crypto_transactions_history_table_tooltip).toBeInTheDocument(); + + userEvent.hover(crypto_transactions_history_table_tooltip); + expect(screen.getByText('The details of this transaction is available on CoinsPaid.')).toBeInTheDocument(); + userEvent.unhover(crypto_transactions_history_table_tooltip); + }); + + it('should check whether the tooltip is clickable for third-party transactions (CoinsPaid)', () => { + (isMobile as jest.Mock).mockReturnValue(true); + const tooltip_props = { + row: { + address_hash: 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt', + address_url: 'https://explorer.coinspaid.com/CP:Abcd1234', + amount: 0.005, + id: '3', + is_valid_to_cancel: 1, + status_code: 'LOCKED', + status_message: + "We're reviewing your withdrawal request. You may still cancel this transaction if you wish. Once we start processing, you won't be able to cancel.", + submit_date: 1640603927, + transaction_type: 'withdrawal', + transaction_url: 'CP:Abcd1234', + }, + onTooltipClick: jest.fn(), + }; + + render(, { + wrapper: ({ children }) => {children}, + }); + + const crypto_transactions_history_table_tooltip_mobile = screen.getByTestId( + 'dt_crypto_transactions_history_table_tooltip_mobile' + ); + + expect(crypto_transactions_history_table_tooltip_mobile).toBeInTheDocument(); + fireEvent.click(crypto_transactions_history_table_tooltip_mobile); + }); }); diff --git a/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.scss b/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.scss index 6889bd3d18fa..727705856d3f 100644 --- a/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.scss +++ b/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.scss @@ -156,6 +156,14 @@ justify-content: center; } } + + &-tooltip { + margin-left: 0.4rem; + svg { + display: flex; + align-items: center; + } + } } &__data-list { flex: 1; @@ -186,4 +194,8 @@ position: static; } } + + &-body { + font-size: var(--text-size-xxs); + } } diff --git a/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.tsx b/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.tsx index f86d43b8495f..f1eb9a105393 100644 --- a/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.tsx +++ b/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-history.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DataList, Icon, Loading, MobileWrapper, Table, Text } from '@deriv/components'; +import { DataList, Icon, Loading, MobileWrapper, Modal, Table, Text } from '@deriv/components'; import { isDesktop, isMobile, routes } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { useStore, observer } from '@deriv/stores'; @@ -29,6 +29,7 @@ const CryptoTransactionsHistory = observer(() => { const { crypto_transactions, is_loading, setIsCryptoTransactionsVisible } = transaction_history; const { setIsDeposit } = general_store; const { currency } = client; + const [is_modal_visible, setIsModalVisible] = React.useState(false); React.useEffect(() => { return () => setIsCryptoTransactionsVisible(false); @@ -79,7 +80,10 @@ const CryptoTransactionsHistory = observer(() => { // TODO: CHECK THIS TYPE ERROR data_source={crypto_transactions} rowRenderer={(row_props: TCryptoTransactionDetailsRow) => ( - + setIsModalVisible(true)} + /> )} keyMapper={(row: TCryptoTransactionDetails) => row.id} row_gap={isMobile() ? 8 : 0} @@ -95,6 +99,19 @@ const CryptoTransactionsHistory = observer(() => { )} + setIsModalVisible(old => !old)} + width='44rem' + height='14rem' + className='crypto-transactions-history__modal' + > + + {localize('The details of this transaction is available on CoinsPaid.')} + + ); }); diff --git a/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-renderer.tsx b/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-renderer.tsx index 2891b398837d..00770b9e8a8c 100644 --- a/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-renderer.tsx +++ b/packages/cashier/src/components/crypto-transactions-history/crypto-transactions-renderer.tsx @@ -10,9 +10,10 @@ import { useCashierStore } from '../../stores/useCashierStores'; type TCryptoTransactionsRendererProps = { row: TCryptoTransactionDetails; + onTooltipClick: VoidFunction; }; -const CryptoTransactionsRenderer = observer(({ row: crypto }: TCryptoTransactionsRendererProps) => { +const CryptoTransactionsRenderer = observer(({ row: crypto, onTooltipClick }: TCryptoTransactionsRendererProps) => { const { client } = useStore(); const { transaction_history } = useCashierStore(); const { cancelCryptoTransaction, showCryptoTransactionsCancelModal, showCryptoTransactionsStatusModal } = @@ -61,6 +62,8 @@ const CryptoTransactionsRenderer = observer(({ row: crypto }: TCryptoTransaction showCryptoTransactionsStatusModal(description, name); }; + const is_third_party_transaction = transaction_url?.includes('CP:'); + if (isMobile()) { return (
@@ -130,6 +133,15 @@ const CryptoTransactionsRenderer = observer(({ row: crypto }: TCryptoTransaction {localize('Transaction hash')} + {is_third_party_transaction && ( + + )} {transaction_url ? ( - - + - - {status.transaction_hash} - - - + + + {status.transaction_hash} + + + + {is_third_party_transaction && ( + + + + )} + ) : ( {status.transaction_hash} 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 c2ac74412861..e32e9fa80906 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 @@ -6,6 +6,7 @@ import { getCurrencyName, isMobile } from '@deriv/shared'; import CryptoDeposit from '../crypto-deposit'; import { TRootStore } from 'Types'; import CashierProviders from '../../../../cashier-providers'; +import { mockStore } from '@deriv/stores'; jest.mock('@deriv/components', () => ({ ...jest.requireActual('@deriv/components'), @@ -27,6 +28,24 @@ jest.mock('Components/recent-transaction', () => { return RecentTransactions; }); +jest.mock('@deriv/api', () => { + return { + ...jest.requireActual('@deriv/api'), + useFetch: jest.fn(() => ({ + data: { + currencies_config: { + tUSDT: { + minimum_deposit: 2, + minimum_withdrawal: 4.54, + }, + }, + }, + isLoading: false, + isSuccess: true, + })), + }; +}); + describe('', () => { let history: ReturnType; const renderWithRouter = (component: JSX.Element, mockRootStore: TRootStore) => { @@ -437,4 +456,45 @@ describe('', () => { expect(screen.getByText('RecentTransactions')).toBeInTheDocument(); }); + + it('should show AlertBanner for minimum deposit when third-party payment processor is used (CoinsPaid)', () => { + const minimum_deposit = 2; + const currency = 'tUSDT'; + const mock = mockStore({ + client: { + currency: 'tUSDT', + }, + modules: { + cashier: { + onramp: { + is_deposit_address_loading: false, + api_error: '', + deposit_address: 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt', + pollApiForDepositAddress: jest.fn(), + }, + transaction_history: { + crypto_transactions: [{}], + onMount: jest.fn(), + }, + general_store: { + setIsDeposit: jest.fn(), + }, + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => { + return ( + + {children}; + + ); + }; + render(, { wrapper }); + expect( + screen.getByText( + `A minimum deposit value of ${minimum_deposit} ${currency} is required. Otherwise, the funds will be lost and cannot be recovered.` + ) + ).toBeInTheDocument(); + }); }); diff --git a/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.scss b/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.scss index f4b558219df0..1856aab2b9e6 100644 --- a/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.scss +++ b/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.scss @@ -25,24 +25,24 @@ flex-direction: column; align-items: center; - &__text { - display: flex; - justify-content: space-between; - width: auto; - padding: 0.8rem; - margin: 2.4rem 0; - border-radius: $BORDER_RADIUS; - background-color: $alpha-color-yellow-1; - - svg { - margin-right: 0.3rem; - } - } .dc-btn { max-width: 6rem; padding: 0.3rem 0.8rem; } } + + .crypto-third-party-alert { + background-color: $color-yellow; + + .dc-text { + background-color: $color-yellow; + color: $color-black-1; + } + + .dc-icon { + --fill-color1: $color-black-1; + } + } } &__transaction-currency { margin: 2.4rem 0 0.8rem; diff --git a/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.tsx b/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.tsx index daf1fa20be0e..2709c417697e 100644 --- a/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.tsx +++ b/packages/cashier/src/pages/deposit/crypto-deposit/crypto-deposit.tsx @@ -3,9 +3,11 @@ import { Button, ButtonLink, Clipboard, Dropdown, Icon, Loading, Text } from '@d import { localize, Localize } from '@deriv/translations'; import { CryptoConfig, getCurrencyName, isCryptocurrency, isMobile } from '@deriv/shared'; import { useStore, observer } from '@deriv/stores'; +import { useFetch } from '@deriv/api'; import CashierBreadcrumb from '../../../components/cashier-breadcrumb'; import QRCode from 'qrcode.react'; import RecentTransaction from '../../../components/recent-transaction'; +import AlertBanner from '../../../components/alert-banner/alert-banner'; import { useCashierStore } from '../../../stores/useCashierStores'; import './crypto-deposit.scss'; @@ -17,6 +19,9 @@ const CryptoDeposit = observer(() => { const { crypto_transactions, onMount: recentTransactionOnMount } = transaction_history; const { setIsDeposit } = general_store; + const { data } = useFetch('crypto_config', { payload: { currency_code: currency } }); + const minimum_deposit = data?.currencies_config[currency]?.minimum_deposit; + React.useEffect(() => { recentTransactionOnMount(); }, [recentTransactionOnMount]); @@ -150,10 +155,12 @@ const CryptoDeposit = observer(() => { {api_error ? (
- - - - +
) : ( <> - - {qrcode_header || header_note} - + {minimum_deposit ? ( + + ) : ( + + {qrcode_header || header_note} + + )} { <> {((currency === 'ETH' && option_list_value !== option_list[4].value) || diff --git a/packages/components/src/components/icon/common/ic-alert-warning-dark.svg b/packages/components/src/components/icon/common/ic-alert-warning-dark.svg new file mode 100644 index 000000000000..022a7a984c67 --- /dev/null +++ b/packages/components/src/components/icon/common/ic-alert-warning-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/components/src/components/icon/icons.js b/packages/components/src/components/icon/icons.js index bea7e8994bc4..c51f50ca4e17 100644 --- a/packages/components/src/components/icon/icons.js +++ b/packages/components/src/components/icon/icons.js @@ -255,6 +255,7 @@ import './common/ic-alert-danger.svg'; import './common/ic-alert-info.svg'; import './common/ic-alert-success.svg'; import './common/ic-alert-trustpilot.svg'; +import './common/ic-alert-warning-dark.svg'; import './common/ic-alert-warning.svg'; import './common/ic-amplifier.svg'; import './common/ic-archive.svg';