mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Swaps token sources/verification messaging update (#10346)
* Update standard swaps build quote screen token verification message * Add actionable warning token verification message to swaps build quote screen * Simplify swapTokenVerification translations * Use original verifyThisTokenOn message instead of swapsConfirmTokenAddressOnEtherscan * Restore verifyThisTokenOn message to hi locale * Support type and the withRightButton option as parameters on the actionable message component * Use 'continue' in place of swapPriceDifferenceAcknowledgementNoFiat message * Use wrapperClassName property on infotooltip in actionable-message * Remove unnecessary change * Lint fix
This commit is contained in:
parent
6a6b27a04d
commit
33ab480fbe
@ -364,6 +364,9 @@
|
|||||||
"contactsSettingsDescription": {
|
"contactsSettingsDescription": {
|
||||||
"message": "Add, edit, remove, and manage your contacts"
|
"message": "Add, edit, remove, and manage your contacts"
|
||||||
},
|
},
|
||||||
|
"continue": {
|
||||||
|
"message": "Continue"
|
||||||
|
},
|
||||||
"continueToWyre": {
|
"continueToWyre": {
|
||||||
"message": "Continue to Wyre"
|
"message": "Continue to Wyre"
|
||||||
},
|
},
|
||||||
@ -1735,9 +1738,6 @@
|
|||||||
"swapPriceDifferenceAcknowledgement": {
|
"swapPriceDifferenceAcknowledgement": {
|
||||||
"message": "I'm aware"
|
"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."
|
||||||
@ -1845,6 +1845,17 @@
|
|||||||
"message": "Swap $1 to $2",
|
"message": "Swap $1 to $2",
|
||||||
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
|
"description": "Used in the transaction display list to describe a swap. $1 and $2 are the symbols of tokens in involved in a swap."
|
||||||
},
|
},
|
||||||
|
"swapTokenVerificationMessage": {
|
||||||
|
"message": "Always confirm the token address on $1.",
|
||||||
|
"description": "Points the user to Etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"Etherscan\" followed by an info icon that shows more info on hover."
|
||||||
|
},
|
||||||
|
"swapTokenVerificationOnlyOneSource": {
|
||||||
|
"message": "Only verified on 1 source."
|
||||||
|
},
|
||||||
|
"swapTokenVerificationSources": {
|
||||||
|
"message": "Verified on $1 sources.",
|
||||||
|
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
|
||||||
|
},
|
||||||
"swapTransactionComplete": {
|
"swapTransactionComplete": {
|
||||||
"message": "Transaction complete"
|
"message": "Transaction complete"
|
||||||
},
|
},
|
||||||
|
@ -1,15 +1,42 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||||
|
|
||||||
|
const CLASSNAME_WARNING = 'actionable-message--warning';
|
||||||
|
const CLASSNAME_DANGER = 'actionable-message--danger';
|
||||||
|
const CLASSNAME_WITH_RIGHT_BUTTON = 'actionable-message--with-right-button';
|
||||||
|
|
||||||
|
const typeHash = {
|
||||||
|
warning: CLASSNAME_WARNING,
|
||||||
|
danger: CLASSNAME_DANGER,
|
||||||
|
};
|
||||||
|
|
||||||
export default function ActionableMessage({
|
export default function ActionableMessage({
|
||||||
message = '',
|
message = '',
|
||||||
primaryAction = null,
|
primaryAction = null,
|
||||||
secondaryAction = null,
|
secondaryAction = null,
|
||||||
className = '',
|
className = '',
|
||||||
|
infoTooltipText = '',
|
||||||
|
withRightButton = false,
|
||||||
|
type = false,
|
||||||
}) {
|
}) {
|
||||||
|
const actionableMessageClassName = classnames(
|
||||||
|
'actionable-message',
|
||||||
|
typeHash[type],
|
||||||
|
withRightButton ? CLASSNAME_WITH_RIGHT_BUTTON : null,
|
||||||
|
className,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('actionable-message', className)}>
|
<div className={actionableMessageClassName}>
|
||||||
|
{infoTooltipText && (
|
||||||
|
<InfoTooltip
|
||||||
|
position="left"
|
||||||
|
contentText={infoTooltipText}
|
||||||
|
wrapperClassName="actionable-message__info-tooltip-wrapper"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="actionable-message__message">{message}</div>
|
<div className="actionable-message__message">{message}</div>
|
||||||
{(primaryAction || secondaryAction) && (
|
{(primaryAction || secondaryAction) && (
|
||||||
<div className="actionable-message__actions">
|
<div className="actionable-message__actions">
|
||||||
@ -52,4 +79,7 @@ ActionableMessage.propTypes = {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
}),
|
}),
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
type: PropTypes.string,
|
||||||
|
withRightButton: PropTypes.boolean,
|
||||||
|
infoTooltipText: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
@include H7;
|
@include H7;
|
||||||
|
|
||||||
@ -29,6 +30,12 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__info-tooltip-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
&--warning {
|
&--warning {
|
||||||
background: $Yellow-100;
|
background: $Yellow-100;
|
||||||
border: 1px solid $Yellow-500;
|
border: 1px solid $Yellow-500;
|
||||||
@ -57,9 +64,40 @@
|
|||||||
&--left-aligned {
|
&--left-aligned {
|
||||||
.actionable-message__message,
|
.actionable-message__message,
|
||||||
.actionable-message__actions {
|
.actionable-message__actions {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--with-right-button {
|
||||||
|
padding: 12px;
|
||||||
|
|
||||||
|
.actionable-message__message {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actionable-message__actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionable-message__action {
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 42px;
|
||||||
|
min-width: 72px;
|
||||||
|
height: 18px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@include H8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionable-message--warning.actionable-message--with-right-button {
|
||||||
|
.actionable-message__action {
|
||||||
|
background: $Yellow-500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import DropdownSearchList from '../dropdown-search-list';
|
|||||||
import SlippageButtons from '../slippage-buttons';
|
import SlippageButtons from '../slippage-buttons';
|
||||||
import { getTokens } from '../../../ducks/metamask/metamask';
|
import { getTokens } from '../../../ducks/metamask/metamask';
|
||||||
import InfoTooltip from '../../../components/ui/info-tooltip';
|
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||||
|
import ActionableMessage from '../actionable-message';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchQuotesAndSetQuoteState,
|
fetchQuotesAndSetQuoteState,
|
||||||
@ -65,6 +66,7 @@ export default function BuildQuote({
|
|||||||
const [fetchedTokenExchangeRate, setFetchedTokenExchangeRate] = useState(
|
const [fetchedTokenExchangeRate, setFetchedTokenExchangeRate] = useState(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
const [verificationClicked, setVerificationClicked] = useState(false);
|
||||||
|
|
||||||
const balanceError = useSelector(getBalanceError);
|
const balanceError = useSelector(getBalanceError);
|
||||||
const fetchParams = useSelector(getFetchParams);
|
const fetchParams = useSelector(getFetchParams);
|
||||||
@ -108,6 +110,9 @@ export default function BuildQuote({
|
|||||||
const selectedToToken =
|
const selectedToToken =
|
||||||
tokensToSearch.find(({ address }) => address === toToken?.address) ||
|
tokensToSearch.find(({ address }) => address === toToken?.address) ||
|
||||||
toToken;
|
toToken;
|
||||||
|
const toTokenIsNotEth =
|
||||||
|
selectedToToken?.address &&
|
||||||
|
selectedToToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
address: fromTokenAddress,
|
address: fromTokenAddress,
|
||||||
@ -195,6 +200,7 @@ export default function BuildQuote({
|
|||||||
dispatch(removeToken(toAddress));
|
dispatch(removeToken(toAddress));
|
||||||
}
|
}
|
||||||
dispatch(setSwapToToken(token));
|
dispatch(setSwapToToken(token));
|
||||||
|
setVerificationClicked(false);
|
||||||
},
|
},
|
||||||
[dispatch, destinationTokenAddedForSwap, toAddress],
|
[dispatch, destinationTokenAddedForSwap, toAddress],
|
||||||
);
|
);
|
||||||
@ -369,10 +375,52 @@ export default function BuildQuote({
|
|||||||
defaultToAll
|
defaultToAll
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{selectedToToken?.address &&
|
{toTokenIsNotEth &&
|
||||||
selectedToToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address && (
|
(selectedToToken.occurances === 1 ? (
|
||||||
|
<ActionableMessage
|
||||||
|
message={
|
||||||
|
<div className="build-quote__token-verification-warning-message">
|
||||||
|
<div className="build-quote__bold">
|
||||||
|
{t('swapTokenVerificationOnlyOneSource')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{t('verifyThisTokenOn', [
|
||||||
|
<a
|
||||||
|
className="build-quote__token-etherscan-link build-quote__underline"
|
||||||
|
key="build-quote-etherscan-link"
|
||||||
|
href={`https://etherscan.io/token/${selectedToToken.address}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t('etherscan')}
|
||||||
|
</a>,
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
primaryAction={
|
||||||
|
verificationClicked
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
label: t('continue'),
|
||||||
|
onClick: () => setVerificationClicked(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type="warning"
|
||||||
|
withRightButton
|
||||||
|
infoTooltipText={t('swapVerifyTokenExplanation')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<div className="build-quote__token-message">
|
<div className="build-quote__token-message">
|
||||||
{t('verifyThisTokenOn', [
|
<span
|
||||||
|
className="build-quote__bold"
|
||||||
|
key="token-verification-bold-text"
|
||||||
|
>
|
||||||
|
{t('swapTokenVerificationSources', [
|
||||||
|
selectedToToken.occurances,
|
||||||
|
])}
|
||||||
|
</span>
|
||||||
|
{t('swapTokenVerificationMessage', [
|
||||||
<a
|
<a
|
||||||
className="build-quote__token-etherscan-link"
|
className="build-quote__token-etherscan-link"
|
||||||
key="build-quote-etherscan-link"
|
key="build-quote-etherscan-link"
|
||||||
@ -387,9 +435,10 @@ export default function BuildQuote({
|
|||||||
position="top"
|
position="top"
|
||||||
contentText={t('swapVerifyTokenExplanation')}
|
contentText={t('swapVerifyTokenExplanation')}
|
||||||
containerClassName="build-quote__token-tooltip-container"
|
containerClassName="build-quote__token-tooltip-container"
|
||||||
|
key="token-verification-info-tooltip"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
<div className="build-quote__slippage-buttons-container">
|
<div className="build-quote__slippage-buttons-container">
|
||||||
<SlippageButtons
|
<SlippageButtons
|
||||||
onSelect={(newSlippage) => {
|
onSelect={(newSlippage) => {
|
||||||
@ -415,7 +464,10 @@ export default function BuildQuote({
|
|||||||
!Number(inputValue) ||
|
!Number(inputValue) ||
|
||||||
!selectedToToken?.address ||
|
!selectedToToken?.address ||
|
||||||
Number(maxSlippage) === 0 ||
|
Number(maxSlippage) === 0 ||
|
||||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE
|
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
||||||
|
(toTokenIsNotEth &&
|
||||||
|
selectedToToken.occurances === 1 &&
|
||||||
|
!verificationClicked)
|
||||||
}
|
}
|
||||||
hideCancel
|
hideCancel
|
||||||
showTermsOfService
|
showTermsOfService
|
||||||
|
@ -122,14 +122,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
color: $Grey-500;
|
color: $Grey-500;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
.info-tooltip {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__token-etherscan-link {
|
&__token-etherscan-link {
|
||||||
color: $Blue-500;
|
color: $Blue-500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__token-tooltip-container {
|
&__token-tooltip-container {
|
||||||
@ -137,6 +138,14 @@
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Prevents the swaps "Swap to" field from overflowing */
|
/* Prevents the swaps "Swap to" field from overflowing */
|
||||||
.dropdown-input-pair__to .dropdown-search-list {
|
.dropdown-input-pair__to .dropdown-search-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -30,9 +30,7 @@ export default function ViewQuotePriceDifference(props) {
|
|||||||
// 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(
|
priceDifferenceAcknowledgementText = t('continue');
|
||||||
'swapPriceDifferenceAcknowledgementNoFiat',
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
priceDifferenceTitle = t('swapPriceDifferenceTitle', [
|
priceDifferenceTitle = t('swapPriceDifferenceTitle', [
|
||||||
priceDifferencePercentage,
|
priceDifferencePercentage,
|
||||||
|
Loading…
Reference in New Issue
Block a user