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 (