From eeca0af5b930c7db04cae0684da7e4024a3ebf27 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Thu, 4 Feb 2021 09:58:46 -0600 Subject: [PATCH] Implement price impact acknowledgement button (#10347) --- app/_locales/en/messages.json | 6 ++ ui/app/pages/swaps/view-quote/index.scss | 37 +++++++-- .../tests/view-quote-price-difference.test.js | 2 +- .../view-quote/view-quote-price-difference.js | 77 ++++++++----------- ui/app/pages/swaps/view-quote/view-quote.js | 75 +++++++++++++++--- 5 files changed, 139 insertions(+), 58 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index affb60904..39e111cb7 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1735,6 +1735,12 @@ "message": "You are about to swap $1 $2 (~$3) for $4 $5 (~$6).", "description": "This message represents the price slippage for the swap. $1 and $4 are a number (ex: 2.89), $2 and $5 are symbols (ex: ETH), and $3 and $6 are fiat currency amounts." }, + "swapPriceDifferenceAcknowledgement": { + "message": "I'm aware" + }, + "swapPriceDifferenceAcknowledgementNoFiat": { + "message": "Continue" + }, "swapPriceDifferenceTitle": { "message": "Price difference of ~$1%", "description": "$1 is a number (ex: 1.23) that represents the price difference." diff --git a/ui/app/pages/swaps/view-quote/index.scss b/ui/app/pages/swaps/view-quote/index.scss index 397748c2a..20c2ea0b4 100644 --- a/ui/app/pages/swaps/view-quote/index.scss +++ b/ui/app/pages/swaps/view-quote/index.scss @@ -15,6 +15,11 @@ padding-right: 20px; justify-content: space-between; + &_modal > div:not(.view-quote__warning-wrapper) { + opacity: 0.6; + pointer-events: none; + } + @media screen and (max-width: 576px) { overflow-y: auto; max-height: 428px; @@ -98,14 +103,30 @@ .actionable-message__message { color: inherit; } + + button { + background: $Yellow-500; + border-radius: 42px; + } } - &.high .actionable-message { - border-color: $Red-500; - background: $Red-100; + &.high { + .actionable-message { + border-color: $Red-500; + background: $Red-100; - .actionable-message__message { - color: $Red-500; + .actionable-message__message { + color: $Red-500; + } + } + + button { + background: $Red-500; + color: #fff; + border-radius: 42px; + + /* Offsets the width of ActionableMessage icon */ + margin-right: -22px; } } @@ -118,11 +139,17 @@ &-contents { display: flex; + text-align: left; &-title { font-weight: bold; } + &-actions { + text-align: end; + padding-top: 10px; + } + i { margin-inline-start: 10px; } diff --git a/ui/app/pages/swaps/view-quote/tests/view-quote-price-difference.test.js b/ui/app/pages/swaps/view-quote/tests/view-quote-price-difference.test.js index bf37e0061..1fc66284b 100644 --- a/ui/app/pages/swaps/view-quote/tests/view-quote-price-difference.test.js +++ b/ui/app/pages/swaps/view-quote/tests/view-quote-price-difference.test.js @@ -140,7 +140,7 @@ describe('View Price Quote Difference', function () { }) it('displays a fiat error when calculationError is present', function () { - const props = { ...DEFAULT_PROPS } + const props = { ...DEFAULT_PROPS, priceSlippageUnknownFiatValue: true } props.usedQuote.priceSlippage.calculationError = 'Could not determine price.' diff --git a/ui/app/pages/swaps/view-quote/view-quote-price-difference.js b/ui/app/pages/swaps/view-quote/view-quote-price-difference.js index 5a3be3cac..bf229b8da 100644 --- a/ui/app/pages/swaps/view-quote/view-quote-price-difference.js +++ b/ui/app/pages/swaps/view-quote/view-quote-price-difference.js @@ -2,64 +2,37 @@ import React, { useContext } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -import BigNumber from 'bignumber.js' -import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount' import { I18nContext } from '../../../contexts/i18n' import ActionableMessage from '../actionable-message' import Tooltip from '../../../components/ui/tooltip' export default function ViewQuotePriceDifference(props) { - const { usedQuote, sourceTokenValue, destinationTokenValue } = props + const { + usedQuote, + sourceTokenValue, + destinationTokenValue, + onAcknowledgementClick, + acknowledged, + priceSlippageFromSource, + priceSlippageFromDestination, + priceDifferencePercentage, + priceSlippageUnknownFiatValue, + } = props const t = useContext(I18nContext) - const priceSlippageFromSource = useEthFiatAmount( - usedQuote?.priceSlippage?.sourceAmountInETH || 0, - ) - const priceSlippageFromDestination = useEthFiatAmount( - usedQuote?.priceSlippage?.destinationAmountInEth || 0, - ) - - if (!usedQuote || !usedQuote.priceSlippage) { - return null - } - - const { priceSlippage } = usedQuote - - // We cannot present fiat value if there is a calculation error or no slippage - // from source or destination - const priceSlippageUnknownFiatValue = - !priceSlippageFromSource || - !priceSlippageFromDestination || - priceSlippage.calculationError - - let priceDifferencePercentage = 0 - if (priceSlippage.ratio) { - priceDifferencePercentage = parseFloat( - new BigNumber(priceSlippage.ratio, 10) - .minus(1, 10) - .times(100, 10) - .toFixed(2), - 10, - ) - } - - const shouldShowPriceDifferenceWarning = - ['high', 'medium'].includes(priceSlippage.bucket) || - priceSlippageUnknownFiatValue - - if (!shouldShowPriceDifferenceWarning) { - return null - } - let priceDifferenceTitle = '' let priceDifferenceMessage = '' let priceDifferenceClass = '' + let priceDifferenceAcknowledgementText = '' if (priceSlippageUnknownFiatValue) { // A calculation error signals we cannot determine dollar value priceDifferenceMessage = t('swapPriceDifferenceUnavailable') priceDifferenceClass = 'fiat-error' + priceDifferenceAcknowledgementText = t( + 'swapPriceDifferenceAcknowledgementNoFiat', + ) } else { priceDifferenceTitle = t('swapPriceDifferenceTitle', [ priceDifferencePercentage, @@ -72,7 +45,8 @@ export default function ViewQuotePriceDifference(props) { usedQuote.destinationTokenInfo.symbol, // Destination token symbol, priceSlippageFromDestination, // Destination tokens total value ]) - priceDifferenceClass = priceSlippage.bucket + priceDifferenceClass = usedQuote.priceSlippage.bucket + priceDifferenceAcknowledgementText = t('swapPriceDifferenceAcknowledgement') } return ( @@ -92,6 +66,17 @@ export default function ViewQuotePriceDifference(props) { )} {priceDifferenceMessage} + {!acknowledged && ( +
+ +
+ )} { @@ -474,20 +480,70 @@ export default function ViewQuote() { : 'ETH', ]) - const viewQuotePriceDifferenceComponent = ( - + // Price difference warning + let viewQuotePriceDifferenceComponent = null + const priceSlippageFromSource = useEthFiatAmount( + usedQuote?.priceSlippage?.sourceAmountInETH || 0, + ) + const priceSlippageFromDestination = useEthFiatAmount( + usedQuote?.priceSlippage?.destinationAmountInEth || 0, ) + // We cannot present fiat value if there is a calculation error or no slippage + // from source or destination + const priceSlippageUnknownFiatValue = + !priceSlippageFromSource || + !priceSlippageFromDestination || + usedQuote?.priceSlippage?.calculationError + + let priceDifferencePercentage = 0 + if (usedQuote?.priceSlippage?.ratio) { + priceDifferencePercentage = parseFloat( + new BigNumber(usedQuote.priceSlippage.ratio, 10) + .minus(1, 10) + .times(100, 10) + .toFixed(2), + 10, + ) + } + + const shouldShowPriceDifferenceWarning = + !showInsufficientWarning && + usedQuote && + (['high', 'medium'].includes(usedQuote.priceSlippage.bucket) || + priceSlippageUnknownFiatValue) + + if (shouldShowPriceDifferenceWarning) { + viewQuotePriceDifferenceComponent = ( + { + setAcknowledgedPriceDifference(true) + }} + acknowledged={acknowledgedPriceDifference} + /> + ) + } + + const disableSubmissionDueToPriceWarning = + shouldShowPriceDifferenceWarning && !acknowledgedPriceDifference + const isShowingWarning = - showInsufficientWarning || viewQuotePriceDifferenceComponent !== null + showInsufficientWarning || shouldShowPriceDifferenceWarning return (
-
+
{selectQuotePopoverShown && ( - {!showInsufficientWarning && viewQuotePriceDifferenceComponent} + {viewQuotePriceDifferenceComponent} {showInsufficientWarning && (