Implement price impact acknowledgement button (#10347)

feature/default_network_editable
David Walsh 4 years ago committed by GitHub
parent efd280172f
commit eeca0af5b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      app/_locales/en/messages.json
  2. 29
      ui/app/pages/swaps/view-quote/index.scss
  3. 2
      ui/app/pages/swaps/view-quote/tests/view-quote-price-difference.test.js
  4. 77
      ui/app/pages/swaps/view-quote/view-quote-price-difference.js
  5. 65
      ui/app/pages/swaps/view-quote/view-quote.js

@ -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."

@ -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,9 +103,15 @@
.actionable-message__message {
color: inherit;
}
button {
background: $Yellow-500;
border-radius: 42px;
}
}
&.high .actionable-message {
&.high {
.actionable-message {
border-color: $Red-500;
background: $Red-100;
@ -109,6 +120,16 @@
}
}
button {
background: $Red-500;
color: #fff;
border-radius: 42px;
/* Offsets the width of ActionableMessage icon */
margin-right: -22px;
}
}
/* Hides info tooltip if there's a fiat error message */
&.fiat-error div[data-tooltipped] {
/* !important overrides style being applied directly to tooltip by component */
@ -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;
}

@ -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.'

@ -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) {
</div>
)}
{priceDifferenceMessage}
{!acknowledged && (
<div className="view-quote__price-difference-warning-contents-actions">
<button
onClick={() => {
onAcknowledgementClick()
}}
>
{priceDifferenceAcknowledgementText}
</button>
</div>
)}
</div>
<Tooltip
position="bottom"
@ -111,4 +96,10 @@ ViewQuotePriceDifference.propTypes = {
usedQuote: PropTypes.object,
sourceTokenValue: PropTypes.string,
destinationTokenValue: PropTypes.string,
onAcknowledgementClick: PropTypes.func,
acknowledged: PropTypes.bool,
priceSlippageFromSource: PropTypes.string,
priceSlippageFromDestination: PropTypes.string,
priceDifferencePercentage: PropTypes.number,
priceSlippageUnknownFiatValue: PropTypes.bool,
}

@ -6,6 +6,7 @@ import { isEqual } from 'lodash'
import classnames from 'classnames'
import { I18nContext } from '../../../contexts/i18n'
import SelectQuotePopover from '../select-quote-popover'
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'
import { useEqualityCheck } from '../../../hooks/useEqualityCheck'
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'
import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken'
@ -88,6 +89,11 @@ export default function ViewQuote() {
const [warningHidden, setWarningHidden] = useState(false)
const [originalApproveAmount, setOriginalApproveAmount] = useState(null)
const [
acknowledgedPriceDifference,
setAcknowledgedPriceDifference,
] = useState(false)
const routeState = useSelector(getBackgroundSwapRouteState)
const quotes = useSelector(getQuotes, isEqual)
useEffect(() => {
@ -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 = (
<ViewQuotePriceDifference
usedQuote={usedQuote}
sourceTokenValue={sourceTokenValue}
destinationTokenValue={destinationTokenValue}
priceSlippageFromSource={priceSlippageFromSource}
priceSlippageFromDestination={priceSlippageFromDestination}
priceDifferencePercentage={priceDifferencePercentage}
priceSlippageUnknownFiatValue={priceSlippageUnknownFiatValue}
onAcknowledgementClick={() => {
setAcknowledgedPriceDifference(true)
}}
acknowledged={acknowledgedPriceDifference}
/>
)
}
const disableSubmissionDueToPriceWarning =
shouldShowPriceDifferenceWarning && !acknowledgedPriceDifference
const isShowingWarning =
showInsufficientWarning || viewQuotePriceDifferenceComponent !== null
showInsufficientWarning || shouldShowPriceDifferenceWarning
return (
<div className="view-quote">
<div className="view-quote__content">
<div
className={classnames('view-quote__content', {
'view-quote__content_modal': disableSubmissionDueToPriceWarning,
})}
>
{selectQuotePopoverShown && (
<SelectQuotePopover
quoteDataRows={renderablePopoverData}
@ -503,7 +559,7 @@ export default function ViewQuote() {
'view-quote__warning-wrapper--thin': !isShowingWarning,
})}
>
{!showInsufficientWarning && viewQuotePriceDifferenceComponent}
{viewQuotePriceDifferenceComponent}
{showInsufficientWarning && (
<ActionableMessage
message={actionableInsufficientMessage}
@ -585,6 +641,7 @@ export default function ViewQuote() {
disabled={
submitClicked ||
balanceError ||
disableSubmissionDueToPriceWarning ||
gasPrice === null ||
gasPrice === undefined
}

Loading…
Cancel
Save