diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 536f45cb63bf..b24b5499e1b2 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -851,9 +851,15 @@ "bridge": { "message": "Bridge" }, + "bridgeCalculatingAmount": { + "message": "Calculating..." + }, "bridgeDontSend": { "message": "Bridge, don't send" }, + "bridgeEnterAmount": { + "message": "Enter amount" + }, "bridgeFrom": { "message": "Bridge from" }, @@ -863,6 +869,9 @@ "bridgeSelectNetwork": { "message": "Select network" }, + "bridgeSelectTokenAndAmount": { + "message": "Select token and amount" + }, "bridgeTimingMinutes": { "message": "$1 minutes", "description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase" diff --git a/ui/pages/bridge/__snapshots__/index.test.tsx.snap b/ui/pages/bridge/__snapshots__/index.test.tsx.snap index cebca14e93bb..b9a6a4c83797 100644 --- a/ui/pages/bridge/__snapshots__/index.test.tsx.snap +++ b/ui/pages/bridge/__snapshots__/index.test.tsx.snap @@ -61,7 +61,7 @@ exports[`Bridge renders the component with initial props 1`] = ` data-theme="light" disabled="" > - Select token + Select token and amount diff --git a/ui/pages/bridge/prepare/bridge-cta-button.test.tsx b/ui/pages/bridge/prepare/bridge-cta-button.test.tsx index 5e42823c885b..e5af065acf15 100644 --- a/ui/pages/bridge/prepare/bridge-cta-button.test.tsx +++ b/ui/pages/bridge/prepare/bridge-cta-button.test.tsx @@ -3,6 +3,10 @@ import { renderWithProvider } from '../../../../test/jest'; import configureStore from '../../../store/store'; import { createBridgeMockStore } from '../../../../test/jest/mock-store'; import { CHAIN_IDS } from '../../../../shared/constants/network'; +import { mockBridgeQuotesNativeErc20 } from '../../../../test/data/bridge/mock-quotes-native-erc20'; +// TODO: Remove restricted import +// eslint-disable-next-line import/no-restricted-paths +import { RequestStatus } from '../../../../app/scripts/controllers/bridge/constants'; import { BridgeCTAButton } from './bridge-cta-button'; describe('BridgeCTAButton', () => { @@ -25,6 +29,52 @@ describe('BridgeCTAButton', () => { expect(getByRole('button')).toBeDisabled(); }); + it('should render the component when amount is missing', () => { + const mockStore = createBridgeMockStore( + { + srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], + destNetworkAllowlist: [CHAIN_IDS.LINEA_MAINNET], + }, + { + fromTokenInputValue: null, + fromToken: 'ETH', + toToken: 'ETH', + toChainId: CHAIN_IDS.LINEA_MAINNET, + }, + {}, + ); + const { getByText, getByRole } = renderWithProvider( + , + configureStore(mockStore), + ); + + expect(getByText('Enter amount')).toBeInTheDocument(); + expect(getByRole('button')).toBeDisabled(); + }); + + it('should render the component when amount and dest token is missing', () => { + const mockStore = createBridgeMockStore( + { + srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], + destNetworkAllowlist: [CHAIN_IDS.LINEA_MAINNET], + }, + { + fromTokenInputValue: null, + fromToken: 'ETH', + toToken: null, + toChainId: CHAIN_IDS.LINEA_MAINNET, + }, + {}, + ); + const { getByText, getByRole } = renderWithProvider( + , + configureStore(mockStore), + ); + + expect(getByText('Select token and amount')).toBeInTheDocument(); + expect(getByRole('button')).toBeDisabled(); + }); + it('should render the component when tx is submittable', () => { const mockStore = createBridgeMockStore( { @@ -37,14 +87,72 @@ describe('BridgeCTAButton', () => { toToken: 'ETH', toChainId: CHAIN_IDS.LINEA_MAINNET, }, - {}, + { + quotes: mockBridgeQuotesNativeErc20, + quotesLastFetched: Date.now(), + quotesLoadingStatus: RequestStatus.FETCHED, + }, + ); + const { getByText, getByRole } = renderWithProvider( + , + configureStore(mockStore), + ); + + expect(getByText('Confirm')).toBeInTheDocument(); + expect(getByRole('button')).not.toBeDisabled(); + }); + + it('should disable the component when quotes are loading and there are no existing quotes', () => { + const mockStore = createBridgeMockStore( + { + srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], + destNetworkAllowlist: [CHAIN_IDS.LINEA_MAINNET], + }, + { + fromTokenInputValue: 1, + fromToken: 'ETH', + toToken: 'ETH', + toChainId: CHAIN_IDS.LINEA_MAINNET, + }, + { + quotes: [], + quotesLastFetched: Date.now(), + quotesLoadingStatus: RequestStatus.LOADING, + }, + ); + const { getByText, getByRole } = renderWithProvider( + , + configureStore(mockStore), + ); + + expect(getByText('Fetching quotes...')).toBeInTheDocument(); + expect(getByRole('button')).toBeDisabled(); + }); + + it('should enable the component when quotes are loading and there are existing quotes', () => { + const mockStore = createBridgeMockStore( + { + srcNetworkAllowlist: [CHAIN_IDS.MAINNET, CHAIN_IDS.OPTIMISM], + destNetworkAllowlist: [CHAIN_IDS.LINEA_MAINNET], + }, + { + fromTokenInputValue: 1, + fromToken: 'ETH', + toToken: 'ETH', + toChainId: CHAIN_IDS.LINEA_MAINNET, + }, + { + quotes: mockBridgeQuotesNativeErc20, + quotesLastFetched: Date.now(), + quotesLoadingStatus: RequestStatus.LOADING, + }, ); const { getByText, getByRole } = renderWithProvider( , configureStore(mockStore), ); - expect(getByText('Bridge')).toBeInTheDocument(); + expect(getByText('Confirm')).toBeInTheDocument(); expect(getByRole('button')).not.toBeDisabled(); }); }); diff --git a/ui/pages/bridge/prepare/bridge-cta-button.tsx b/ui/pages/bridge/prepare/bridge-cta-button.tsx index fedcf4d4606a..28a1a2c1fbd6 100644 --- a/ui/pages/bridge/prepare/bridge-cta-button.tsx +++ b/ui/pages/bridge/prepare/bridge-cta-button.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { Button } from '../../../components/component-library'; import { + getBridgeQuotes, getFromAmount, getFromChain, getFromToken, @@ -22,16 +23,29 @@ export const BridgeCTAButton = () => { const fromAmount = useSelector(getFromAmount); const toAmount = useSelector(getToAmount); + const { isLoading } = useSelector(getBridgeQuotes); + const isTxSubmittable = fromToken && toToken && fromChain && toChain && fromAmount && toAmount; const label = useMemo(() => { + if (isLoading && !isTxSubmittable) { + return t('swapFetchingQuotes'); + } + + if (!fromAmount) { + if (!toToken) { + return t('bridgeSelectTokenAndAmount'); + } + return t('bridgeEnterAmount'); + } + if (isTxSubmittable) { - return t('bridge'); + return t('confirm'); } return t('swapSelectToken'); - }, [isTxSubmittable]); + }, [isLoading, fromAmount, toToken, isTxSubmittable]); return (