Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions app/_locales/en_GB/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions test/e2e/page-objects/pages/bridge/quote-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,22 @@ class BridgeQuotePage {
await this.driver.clickElement(this.warningModalProceedButton);
};

approveModalIfPresent = async () => {
try {
// Wait for an *enabled* proceed button. Using :not([disabled]) means:
// - No modal present → no match → 3 s timeout → catch (no-op)
// - Modal open, tx in-flight → button has [disabled] attr → no match
// → 3 s timeout → catch (no-op)
// - Modal open, tx not yet submitted → button is enabled → match → click
await this.driver.waitForSelector(
`${this.warningModalProceedButton}:not([disabled])`,
);
await this.driver.clickElement(this.warningModalProceedButton);
} catch {
// No confirmation modal with an enabled proceed button — nothing to do
}
};
Comment thread
GeorgeGkas marked this conversation as resolved.

rejectModal = async () => {
await this.driver.clickElement(this.warningModalCancelButton);
};
Expand All @@ -282,6 +298,10 @@ class BridgeQuotePage {
submitQuoteAndDismiss = async () => {
await this.submitQuote();

// If no price data is available a confirmation modal appears before submission.
// Dismiss it so the transaction can proceed to the status page.
await this.approveModalIfPresent();

await this.dismissStatusPageIfPresent();
};

Expand Down
2 changes: 1 addition & 1 deletion test/jest/console-baseline-unit.json
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@
"Reselect: Input stability warnings": 1
},
"ui/pages/bridge/prepare/components/bridge-alert-banner-list.test.tsx": {
"React: Act warnings (component updates not wrapped)": 36,
"React: Act warnings (component updates not wrapped)": 44,
"Reselect: Identity function warnings": 1,
"Reselect: Input stability warnings": 1,
"Third-party: Bindings failed, using pure JS": 1,
Expand Down
3 changes: 3 additions & 0 deletions ui/ducks/bridge/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ export const getFromAmountInCurrency = createSelector(

export const getTxAlerts = (state: BridgeAppState) => state.bridge.txAlert;

export const getActiveQuotePriceData = (state: BridgeAppState) =>
getBridgeQuotes(state).activeQuote?.quote?.priceData;

export const getPriceImpact = createSelector(
[
(state: BridgeAppState) =>
Expand Down
2 changes: 1 addition & 1 deletion ui/pages/bridge/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ exports[`Bridge renders the component with initial props 1`] = `
</div>
</div>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-inline-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column mm-box--width-full mm-box--background-color-background-default"
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-right-4 mm-box--padding-bottom-4 mm-box--padding-left-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column mm-box--width-full mm-box--background-color-background-default"
data-testid="bridge-banner-alerts"
/>
<div
Expand Down
18 changes: 18 additions & 0 deletions ui/pages/bridge/hooks/useBridgeAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { shallowEqual, useSelector } from 'react-redux';
import { useMemo } from 'react';
import {
type BridgeAppState,
getActiveQuotePriceData,
getBridgeUnavailableQuoteReason,
getFormattedPriceImpactFiat,
getFormattedPriceImpactPercentage,
Expand Down Expand Up @@ -52,6 +53,8 @@ export const useBridgeAlerts = () => {
const { tokenAlerts, txAlert } = useSecurityAlerts();
const { openBuyCryptoInPdapp } = useRamps();

const activeQuotePriceData = useSelector(getActiveQuotePriceData);

const { isLoading, activeQuote: unvalidatedQuote } =
useSelector(getBridgeQuotes);
const activeQuote = isQuoteExpiredOrInvalid({
Expand Down Expand Up @@ -133,6 +136,19 @@ export const useBridgeAlerts = () => {
});
}

if (unvalidatedQuote && !activeQuotePriceData) {
categorizeAlert({
id: 'price-data-unavailable',
severity: 'danger',
title: t('bridgeNoPriceInfoTitle'),
description: t('bridgePriceDataUnavailableError'),
isConfirmationAlert: true,
bannerAlertProps: {
severity: BannerAlertSeverity.Danger,
},
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale quote triggers false "no price" alert alongside "no quotes"

Medium Severity

The price-data-unavailable alert condition checks unvalidatedQuote && !activeQuotePriceData, but unvalidatedQuote can be a stale quote from a previous fetch. When isNoQuotesAvailable is true (current fetch returned zero quotes) or isStockMarketClosed is true, a leftover unvalidatedQuote without priceData causes both the "No quotes available" / "Market closed" banner AND the "No price information" banner to appear simultaneously. The latter refers to a stale, non-actionable quote and is misleading. The condition likely needs to also guard against isNoQuotesAvailable and isStockMarketClosed to avoid showing a price-data alert for a quote the user can no longer submit.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 09cb6ba. Configure here.


if (
!isLoading &&
activeQuote &&
Expand Down Expand Up @@ -194,6 +210,8 @@ export const useBridgeAlerts = () => {
};
}, [
activeQuote,
unvalidatedQuote,
activeQuotePriceData,
bridgeUnavailableQuotesReason,
formattedPriceImpactPercentage,
formattedPriceImpactFiat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ exports[`PrepareBridgePage should render the component, with initial state 1`] =
</div>
</div>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-inline-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column mm-box--width-full mm-box--background-color-background-default"
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-right-4 mm-box--padding-bottom-4 mm-box--padding-left-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column mm-box--width-full mm-box--background-color-background-default"
data-testid="bridge-banner-alerts"
/>
<div
Expand Down Expand Up @@ -443,7 +443,7 @@ exports[`PrepareBridgePage should render the component, with inputs set 1`] = `
</div>
</div>
<div
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-inline-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column mm-box--width-full mm-box--background-color-background-default"
class="mm-box mm-container mm-container--max-width-undefined mm-box--padding-right-4 mm-box--padding-bottom-4 mm-box--padding-left-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column mm-box--width-full mm-box--background-color-background-default"
data-testid="bridge-banner-alerts"
/>
<div
Expand Down
Loading
Loading