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. 37
      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. 75
      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).", "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." "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": { "swapPriceDifferenceTitle": {
"message": "Price difference of ~$1%", "message": "Price difference of ~$1%",
"description": "$1 is a number (ex: 1.23) that represents the price difference." "description": "$1 is a number (ex: 1.23) that represents the price difference."

@ -15,6 +15,11 @@
padding-right: 20px; padding-right: 20px;
justify-content: space-between; justify-content: space-between;
&_modal > div:not(.view-quote__warning-wrapper) {
opacity: 0.6;
pointer-events: none;
}
@media screen and (max-width: 576px) { @media screen and (max-width: 576px) {
overflow-y: auto; overflow-y: auto;
max-height: 428px; max-height: 428px;
@ -98,14 +103,30 @@
.actionable-message__message { .actionable-message__message {
color: inherit; color: inherit;
} }
button {
background: $Yellow-500;
border-radius: 42px;
}
} }
&.high .actionable-message { &.high {
border-color: $Red-500; .actionable-message {
background: $Red-100; border-color: $Red-500;
background: $Red-100;
.actionable-message__message { .actionable-message__message {
color: $Red-500; 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 { &-contents {
display: flex; display: flex;
text-align: left;
&-title { &-title {
font-weight: bold; font-weight: bold;
} }
&-actions {
text-align: end;
padding-top: 10px;
}
i { i {
margin-inline-start: 10px; margin-inline-start: 10px;
} }

@ -140,7 +140,7 @@ describe('View Price Quote Difference', function () {
}) })
it('displays a fiat error when calculationError is present', 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 = props.usedQuote.priceSlippage.calculationError =
'Could not determine price.' 'Could not determine price.'

@ -2,64 +2,37 @@ import React, { useContext } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import BigNumber from 'bignumber.js'
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'
import { I18nContext } from '../../../contexts/i18n' import { I18nContext } from '../../../contexts/i18n'
import ActionableMessage from '../actionable-message' import ActionableMessage from '../actionable-message'
import Tooltip from '../../../components/ui/tooltip' import Tooltip from '../../../components/ui/tooltip'
export default function ViewQuotePriceDifference(props) { 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 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 priceDifferenceTitle = ''
let priceDifferenceMessage = '' let priceDifferenceMessage = ''
let priceDifferenceClass = '' let priceDifferenceClass = ''
let priceDifferenceAcknowledgementText = ''
if (priceSlippageUnknownFiatValue) { if (priceSlippageUnknownFiatValue) {
// A calculation error signals we cannot determine dollar value // A calculation error signals we cannot determine dollar value
priceDifferenceMessage = t('swapPriceDifferenceUnavailable') priceDifferenceMessage = t('swapPriceDifferenceUnavailable')
priceDifferenceClass = 'fiat-error' priceDifferenceClass = 'fiat-error'
priceDifferenceAcknowledgementText = t(
'swapPriceDifferenceAcknowledgementNoFiat',
)
} else { } else {
priceDifferenceTitle = t('swapPriceDifferenceTitle', [ priceDifferenceTitle = t('swapPriceDifferenceTitle', [
priceDifferencePercentage, priceDifferencePercentage,
@ -72,7 +45,8 @@ export default function ViewQuotePriceDifference(props) {
usedQuote.destinationTokenInfo.symbol, // Destination token symbol, usedQuote.destinationTokenInfo.symbol, // Destination token symbol,
priceSlippageFromDestination, // Destination tokens total value priceSlippageFromDestination, // Destination tokens total value
]) ])
priceDifferenceClass = priceSlippage.bucket priceDifferenceClass = usedQuote.priceSlippage.bucket
priceDifferenceAcknowledgementText = t('swapPriceDifferenceAcknowledgement')
} }
return ( return (
@ -92,6 +66,17 @@ export default function ViewQuotePriceDifference(props) {
</div> </div>
)} )}
{priceDifferenceMessage} {priceDifferenceMessage}
{!acknowledged && (
<div className="view-quote__price-difference-warning-contents-actions">
<button
onClick={() => {
onAcknowledgementClick()
}}
>
{priceDifferenceAcknowledgementText}
</button>
</div>
)}
</div> </div>
<Tooltip <Tooltip
position="bottom" position="bottom"
@ -111,4 +96,10 @@ ViewQuotePriceDifference.propTypes = {
usedQuote: PropTypes.object, usedQuote: PropTypes.object,
sourceTokenValue: PropTypes.string, sourceTokenValue: PropTypes.string,
destinationTokenValue: 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 classnames from 'classnames'
import { I18nContext } from '../../../contexts/i18n' import { I18nContext } from '../../../contexts/i18n'
import SelectQuotePopover from '../select-quote-popover' import SelectQuotePopover from '../select-quote-popover'
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'
import { useEqualityCheck } from '../../../hooks/useEqualityCheck' import { useEqualityCheck } from '../../../hooks/useEqualityCheck'
import { useNewMetricEvent } from '../../../hooks/useMetricEvent' import { useNewMetricEvent } from '../../../hooks/useMetricEvent'
import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken' import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken'
@ -88,6 +89,11 @@ export default function ViewQuote() {
const [warningHidden, setWarningHidden] = useState(false) const [warningHidden, setWarningHidden] = useState(false)
const [originalApproveAmount, setOriginalApproveAmount] = useState(null) const [originalApproveAmount, setOriginalApproveAmount] = useState(null)
const [
acknowledgedPriceDifference,
setAcknowledgedPriceDifference,
] = useState(false)
const routeState = useSelector(getBackgroundSwapRouteState) const routeState = useSelector(getBackgroundSwapRouteState)
const quotes = useSelector(getQuotes, isEqual) const quotes = useSelector(getQuotes, isEqual)
useEffect(() => { useEffect(() => {
@ -474,20 +480,70 @@ export default function ViewQuote() {
: 'ETH', : 'ETH',
]) ])
const viewQuotePriceDifferenceComponent = ( // Price difference warning
<ViewQuotePriceDifference let viewQuotePriceDifferenceComponent = null
usedQuote={usedQuote} const priceSlippageFromSource = useEthFiatAmount(
sourceTokenValue={sourceTokenValue} usedQuote?.priceSlippage?.sourceAmountInETH || 0,
destinationTokenValue={destinationTokenValue} )
/> 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 = const isShowingWarning =
showInsufficientWarning || viewQuotePriceDifferenceComponent !== null showInsufficientWarning || shouldShowPriceDifferenceWarning
return ( return (
<div className="view-quote"> <div className="view-quote">
<div className="view-quote__content"> <div
className={classnames('view-quote__content', {
'view-quote__content_modal': disableSubmissionDueToPriceWarning,
})}
>
{selectQuotePopoverShown && ( {selectQuotePopoverShown && (
<SelectQuotePopover <SelectQuotePopover
quoteDataRows={renderablePopoverData} quoteDataRows={renderablePopoverData}
@ -503,7 +559,7 @@ export default function ViewQuote() {
'view-quote__warning-wrapper--thin': !isShowingWarning, 'view-quote__warning-wrapper--thin': !isShowingWarning,
})} })}
> >
{!showInsufficientWarning && viewQuotePriceDifferenceComponent} {viewQuotePriceDifferenceComponent}
{showInsufficientWarning && ( {showInsufficientWarning && (
<ActionableMessage <ActionableMessage
message={actionableInsufficientMessage} message={actionableInsufficientMessage}
@ -585,6 +641,7 @@ export default function ViewQuote() {
disabled={ disabled={
submitClicked || submitClicked ||
balanceError || balanceError ||
disableSubmissionDueToPriceWarning ||
gasPrice === null || gasPrice === null ||
gasPrice === undefined gasPrice === undefined
} }

Loading…
Cancel
Save