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": {
|
||||
"message": "Add, edit, remove, and manage your contacts"
|
||||
},
|
||||
"continue": {
|
||||
"message": "Continue"
|
||||
},
|
||||
"continueToWyre": {
|
||||
"message": "Continue to Wyre"
|
||||
},
|
||||
@ -1735,9 +1738,6 @@
|
||||
"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."
|
||||
@ -1845,6 +1845,17 @@
|
||||
"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."
|
||||
},
|
||||
"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": {
|
||||
"message": "Transaction complete"
|
||||
},
|
||||
|
@ -1,15 +1,42 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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({
|
||||
message = '',
|
||||
primaryAction = null,
|
||||
secondaryAction = null,
|
||||
className = '',
|
||||
infoTooltipText = '',
|
||||
withRightButton = false,
|
||||
type = false,
|
||||
}) {
|
||||
const actionableMessageClassName = classnames(
|
||||
'actionable-message',
|
||||
typeHash[type],
|
||||
withRightButton ? CLASSNAME_WITH_RIGHT_BUTTON : null,
|
||||
className,
|
||||
);
|
||||
|
||||
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>
|
||||
{(primaryAction || secondaryAction) && (
|
||||
<div className="actionable-message__actions">
|
||||
@ -52,4 +79,7 @@ ActionableMessage.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
}),
|
||||
className: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
withRightButton: PropTypes.boolean,
|
||||
infoTooltipText: PropTypes.string,
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
@include H7;
|
||||
|
||||
@ -29,6 +30,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__info-tooltip-wrapper {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: $Yellow-100;
|
||||
border: 1px solid $Yellow-500;
|
||||
@ -57,9 +64,40 @@
|
||||
&--left-aligned {
|
||||
.actionable-message__message,
|
||||
.actionable-message__actions {
|
||||
}
|
||||
}
|
||||
|
||||
&--with-right-button {
|
||||
padding: 12px;
|
||||
|
||||
.actionable-message__message {
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
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 { getTokens } from '../../../ducks/metamask/metamask';
|
||||
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||
import ActionableMessage from '../actionable-message';
|
||||
|
||||
import {
|
||||
fetchQuotesAndSetQuoteState,
|
||||
@ -65,6 +66,7 @@ export default function BuildQuote({
|
||||
const [fetchedTokenExchangeRate, setFetchedTokenExchangeRate] = useState(
|
||||
undefined,
|
||||
);
|
||||
const [verificationClicked, setVerificationClicked] = useState(false);
|
||||
|
||||
const balanceError = useSelector(getBalanceError);
|
||||
const fetchParams = useSelector(getFetchParams);
|
||||
@ -108,6 +110,9 @@ export default function BuildQuote({
|
||||
const selectedToToken =
|
||||
tokensToSearch.find(({ address }) => address === toToken?.address) ||
|
||||
toToken;
|
||||
const toTokenIsNotEth =
|
||||
selectedToToken?.address &&
|
||||
selectedToToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address;
|
||||
|
||||
const {
|
||||
address: fromTokenAddress,
|
||||
@ -195,6 +200,7 @@ export default function BuildQuote({
|
||||
dispatch(removeToken(toAddress));
|
||||
}
|
||||
dispatch(setSwapToToken(token));
|
||||
setVerificationClicked(false);
|
||||
},
|
||||
[dispatch, destinationTokenAddedForSwap, toAddress],
|
||||
);
|
||||
@ -369,10 +375,52 @@ export default function BuildQuote({
|
||||
defaultToAll
|
||||
/>
|
||||
</div>
|
||||
{selectedToToken?.address &&
|
||||
selectedToToken?.address !== ETH_SWAPS_TOKEN_OBJECT.address && (
|
||||
{toTokenIsNotEth &&
|
||||
(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">
|
||||
{t('verifyThisTokenOn', [
|
||||
<span
|
||||
className="build-quote__bold"
|
||||
key="token-verification-bold-text"
|
||||
>
|
||||
{t('swapTokenVerificationSources', [
|
||||
selectedToToken.occurances,
|
||||
])}
|
||||
</span>
|
||||
{t('swapTokenVerificationMessage', [
|
||||
<a
|
||||
className="build-quote__token-etherscan-link"
|
||||
key="build-quote-etherscan-link"
|
||||
@ -387,9 +435,10 @@ export default function BuildQuote({
|
||||
position="top"
|
||||
contentText={t('swapVerifyTokenExplanation')}
|
||||
containerClassName="build-quote__token-tooltip-container"
|
||||
key="token-verification-info-tooltip"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
<div className="build-quote__slippage-buttons-container">
|
||||
<SlippageButtons
|
||||
onSelect={(newSlippage) => {
|
||||
@ -415,7 +464,10 @@ export default function BuildQuote({
|
||||
!Number(inputValue) ||
|
||||
!selectedToToken?.address ||
|
||||
Number(maxSlippage) === 0 ||
|
||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE
|
||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
||||
(toTokenIsNotEth &&
|
||||
selectedToToken.occurances === 1 &&
|
||||
!verificationClicked)
|
||||
}
|
||||
hideCancel
|
||||
showTermsOfService
|
||||
|
@ -122,14 +122,15 @@
|
||||
width: 100%;
|
||||
color: $Grey-500;
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.info-tooltip {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__token-etherscan-link {
|
||||
color: $Blue-500;
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&__token-tooltip-container {
|
||||
@ -137,6 +138,14 @@
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
&__bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Prevents the swaps "Swap to" field from overflowing */
|
||||
.dropdown-input-pair__to .dropdown-search-list {
|
||||
width: 100%;
|
||||
|
@ -30,9 +30,7 @@ export default function ViewQuotePriceDifference(props) {
|
||||
// A calculation error signals we cannot determine dollar value
|
||||
priceDifferenceMessage = t('swapPriceDifferenceUnavailable');
|
||||
priceDifferenceClass = 'fiat-error';
|
||||
priceDifferenceAcknowledgementText = t(
|
||||
'swapPriceDifferenceAcknowledgementNoFiat',
|
||||
);
|
||||
priceDifferenceAcknowledgementText = t('continue');
|
||||
} else {
|
||||
priceDifferenceTitle = t('swapPriceDifferenceTitle', [
|
||||
priceDifferencePercentage,
|
||||
|
Loading…
Reference in New Issue
Block a user