mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Review spending cap screen (#15919)
This commit is contained in:
parent
d97b9c7eef
commit
a993509afc
12
app/_locales/en/messages.json
generated
12
app/_locales/en/messages.json
generated
@ -2927,6 +2927,9 @@
|
||||
"revealTheSeedPhrase": {
|
||||
"message": "Reveal seed phrase"
|
||||
},
|
||||
"reviewSpendingCap": {
|
||||
"message": "Review your spending cap"
|
||||
},
|
||||
"revokeAllTokensTitle": {
|
||||
"message": "Revoke permission to access all of your $1?",
|
||||
"description": "$1 is the symbol of the token for which the user is revoking approval"
|
||||
@ -3122,6 +3125,9 @@
|
||||
"message": "Approve $1 with no spend limit",
|
||||
"description": "The token symbol that is being approved"
|
||||
},
|
||||
"setSpendingCap": {
|
||||
"message": "Set a spending cap for your"
|
||||
},
|
||||
"settings": {
|
||||
"message": "Settings"
|
||||
},
|
||||
@ -4230,6 +4236,9 @@
|
||||
"userName": {
|
||||
"message": "Username"
|
||||
},
|
||||
"verifyContractDetails": {
|
||||
"message": "Verify contract details"
|
||||
},
|
||||
"verifyThisTokenDecimalOn": {
|
||||
"message": "Token decimal can be found 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\""
|
||||
@ -4251,6 +4260,9 @@
|
||||
"viewContact": {
|
||||
"message": "View contact"
|
||||
},
|
||||
"viewDetails": {
|
||||
"message": "View details"
|
||||
},
|
||||
"viewFullTransactionDetails": {
|
||||
"message": "View full transaction details"
|
||||
},
|
||||
|
@ -96,3 +96,4 @@
|
||||
@import 'detected-token/detected-token-ignored-popover/index';
|
||||
@import 'detected-token/detected-token-selection-popover/index';
|
||||
@import 'network-account-balance-header/index';
|
||||
@import 'approve-content-card/index';
|
||||
|
241
ui/components/app/approve-content-card/approve-content-card.js
Normal file
241
ui/components/app/approve-content-card/approve-content-card.js
Normal file
@ -0,0 +1,241 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Box from '../../ui/box/box';
|
||||
import Button from '../../ui/button';
|
||||
import EditGasFeeButton from '../edit-gas-fee-button/edit-gas-fee-button';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import {
|
||||
ALIGN_ITEMS,
|
||||
BLOCK_SIZES,
|
||||
COLORS,
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
FONT_WEIGHT,
|
||||
JUSTIFY_CONTENT,
|
||||
TEXT_ALIGN,
|
||||
TYPOGRAPHY,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { I18nContext } from '../../../../.storybook/i18n';
|
||||
import GasDetailsItem from '../gas-details-item/gas-details-item';
|
||||
import MultiLayerFeeMessage from '../multilayer-fee-message/multi-layer-fee-message';
|
||||
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
|
||||
|
||||
export default function ApproveContentCard({
|
||||
showHeader = true,
|
||||
symbol,
|
||||
title,
|
||||
showEdit,
|
||||
showAdvanceGasFeeOptions = false,
|
||||
onEditClick,
|
||||
footer,
|
||||
noBorder,
|
||||
supportsEIP1559V2,
|
||||
renderTransactionDetailsContent,
|
||||
renderDataContent,
|
||||
isMultiLayerFeeNetwork,
|
||||
ethTransactionTotal,
|
||||
nativeCurrency,
|
||||
fullTxData,
|
||||
hexTransactionTotal,
|
||||
fiatTransactionTotal,
|
||||
currentCurrency,
|
||||
isSetApproveForAll,
|
||||
isApprovalOrRejection,
|
||||
data,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classnames({
|
||||
'approve-content-card-container__card': !noBorder,
|
||||
'approve-content-card-container__card--no-border': noBorder,
|
||||
})}
|
||||
>
|
||||
{showHeader && (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
justifyContent={JUSTIFY_CONTENT.FLEX_END}
|
||||
className="approve-content-card-container__card-header"
|
||||
>
|
||||
{supportsEIP1559V2 && title === t('transactionFee') ? null : (
|
||||
<>
|
||||
<Box className="approve-content-card-container__card-header__symbol">
|
||||
{symbol}
|
||||
</Box>
|
||||
<Box
|
||||
marginLeft={4}
|
||||
className="approve-content-card-container__card-header__title"
|
||||
>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{showEdit && (!showAdvanceGasFeeOptions || !supportsEIP1559V2) && (
|
||||
<Box width={BLOCK_SIZES.ONE_SIXTH}>
|
||||
<Button type="link" onClick={() => onEditClick()}>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.PRIMARY_DEFAULT}
|
||||
>
|
||||
{t('edit')}
|
||||
</Typography>
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559V2 && (
|
||||
<EditGasFeeButton />
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
marginTop={1}
|
||||
marginBottom={3}
|
||||
className="approve-content-card-container__card-content"
|
||||
>
|
||||
{renderTransactionDetailsContent &&
|
||||
(!isMultiLayerFeeNetwork && supportsEIP1559V2 ? (
|
||||
<GasDetailsItem />
|
||||
) : (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
>
|
||||
{isMultiLayerFeeNetwork ? (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="approve-content-card-container__transaction-details-extra-content"
|
||||
>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
color={COLORS.TEXT_MUTED}
|
||||
>
|
||||
<span>{t('transactionDetailLayer2GasHeading')}</span>
|
||||
{`${ethTransactionTotal} ${nativeCurrency}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<MultiLayerFeeMessage
|
||||
transaction={fullTxData}
|
||||
layer2fee={hexTransactionTotal}
|
||||
nativeCurrency={nativeCurrency}
|
||||
plainStyle
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<Box>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
>
|
||||
{t('feeAssociatedRequest')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
alignItems={ALIGN_ITEMS.FLEX_END}
|
||||
textAlign={TEXT_ALIGN.RIGHT}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H4}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
color={COLORS.TEXT_DEFAULT}
|
||||
>
|
||||
{formatCurrency(fiatTransactionTotal, currentCurrency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
color={COLORS.TEXT_MUTED}
|
||||
>
|
||||
{`${ethTransactionTotal} ${nativeCurrency}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
{renderDataContent && (
|
||||
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.COLUMN}>
|
||||
<Box>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
>
|
||||
{isSetApproveForAll
|
||||
? t('functionSetApprovalForAll')
|
||||
: t('functionApprove')}
|
||||
</Typography>
|
||||
</Box>
|
||||
{isSetApproveForAll && isApprovalOrRejection !== undefined ? (
|
||||
<Box>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
>
|
||||
{`${t('parameters')}: ${isApprovalOrRejection}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : null}
|
||||
<Box
|
||||
marginRight={4}
|
||||
className="approve-content-card-container__data__data-block"
|
||||
>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
>
|
||||
{data}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{footer}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
ApproveContentCard.propTypes = {
|
||||
showHeader: PropTypes.bool,
|
||||
symbol: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
showEdit: PropTypes.bool,
|
||||
showAdvanceGasFeeOptions: PropTypes.bool,
|
||||
onEditClick: PropTypes.func,
|
||||
footer: PropTypes.node,
|
||||
noBorder: PropTypes.bool,
|
||||
supportsEIP1559V2: PropTypes.bool,
|
||||
renderTransactionDetailsContent: PropTypes.bool,
|
||||
renderDataContent: PropTypes.bool,
|
||||
isMultiLayerFeeNetwork: PropTypes.bool,
|
||||
ethTransactionTotal: PropTypes.string,
|
||||
nativeCurrency: PropTypes.string,
|
||||
fullTxData: PropTypes.object,
|
||||
hexTransactionTotal: PropTypes.string,
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
currentCurrency: PropTypes.string,
|
||||
isSetApproveForAll: PropTypes.bool,
|
||||
isApprovalOrRejection: PropTypes.bool,
|
||||
data: PropTypes.string,
|
||||
};
|
1
ui/components/app/approve-content-card/index.js
Normal file
1
ui/components/app/approve-content-card/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './approve-content-card';
|
51
ui/components/app/approve-content-card/index.scss
Normal file
51
ui/components/app/approve-content-card/index.scss
Normal file
@ -0,0 +1,51 @@
|
||||
.approve-content-card-container {
|
||||
&__card,
|
||||
&__card--no-border {
|
||||
border-bottom: 1px solid var(--color-border-default);
|
||||
position: relative;
|
||||
padding-inline-start: 24px;
|
||||
padding-inline-end: 24px;
|
||||
}
|
||||
|
||||
&__card--no-border {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&__card-header {
|
||||
position: relative;
|
||||
|
||||
&__symbol {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__symbol--aligned {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__title--aligned {
|
||||
margin-inline-start: 27px;
|
||||
position: absolute;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__card-content--aligned {
|
||||
margin-inline-start: 42px;
|
||||
}
|
||||
|
||||
&__transaction-details-extra-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__data {
|
||||
width: 100%;
|
||||
|
||||
&__data-block {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getAccountLink } from '@metamask/etherscan-link';
|
||||
import { useSelector } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import Box from '../../../ui/box';
|
||||
import IconCopy from '../../../ui/icon/icon-copy';
|
||||
import IconBlockExplorer from '../../../ui/icon/icon-block-explorer';
|
||||
@ -19,9 +22,27 @@ import {
|
||||
SIZES,
|
||||
BORDER_STYLE,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard';
|
||||
import UrlIcon from '../../../ui/url-icon/url-icon';
|
||||
import { getAddressBookEntry } from '../../../../selectors';
|
||||
|
||||
export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
export default function ContractDetailsModal({
|
||||
onClose,
|
||||
tokenName,
|
||||
tokenAddress,
|
||||
toAddress,
|
||||
chainId,
|
||||
rpcPrefs,
|
||||
origin,
|
||||
siteImage,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const [copiedTokenAddress, handleCopyTokenAddress] = useCopyToClipboard();
|
||||
const [copiedToAddress, handleCopyToAddress] = useCopyToClipboard();
|
||||
|
||||
const addressBookEntry = useSelector((state) => ({
|
||||
data: getAddressBookEntry(state, toAddress),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Popover className="contract-details-modal">
|
||||
@ -65,7 +86,7 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
>
|
||||
<Identicon
|
||||
className="contract-details-modal__content__contract__identicon"
|
||||
address={address}
|
||||
address={tokenAddress}
|
||||
diameter={24}
|
||||
/>
|
||||
<Box data-testid="recipient">
|
||||
@ -74,7 +95,7 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
variant={TYPOGRAPHY.H5}
|
||||
marginTop={4}
|
||||
>
|
||||
{tokenName || ellipsify(address)}
|
||||
{tokenName || ellipsify(tokenAddress)}
|
||||
</Typography>
|
||||
{tokenName && (
|
||||
<Typography
|
||||
@ -82,7 +103,7 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
display={DISPLAY.FLEX}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
>
|
||||
{ellipsify(address)}
|
||||
{ellipsify(tokenAddress)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@ -91,10 +112,20 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
className="contract-details-modal__content__contract__buttons"
|
||||
>
|
||||
<Box marginTop={4} marginRight={5}>
|
||||
<Tooltip position="top" title={t('copyToClipboard')}>
|
||||
<Tooltip
|
||||
position="top"
|
||||
title={
|
||||
copiedTokenAddress
|
||||
? t('copiedExclamation')
|
||||
: t('copyToClipboard')
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__copy"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
handleCopyTokenAddress(tokenAddress);
|
||||
}}
|
||||
>
|
||||
<IconCopy color="var(--color-icon-muted)" />
|
||||
</Button>
|
||||
@ -105,6 +136,19 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__block-explorer"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
const blockExplorerTokenLink = getAccountLink(
|
||||
tokenAddress,
|
||||
chainId,
|
||||
{
|
||||
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
global.platform.openTab({
|
||||
url: blockExplorerTokenLink,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconBlockExplorer
|
||||
size={16}
|
||||
@ -131,10 +175,21 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
borderColor={COLORS.BORDER_DEFAULT}
|
||||
className="contract-details-modal__content__contract"
|
||||
>
|
||||
<Identicon
|
||||
className="contract-details-modal__content__contract__identicon"
|
||||
address={address}
|
||||
diameter={24}
|
||||
<UrlIcon
|
||||
className={classnames({
|
||||
'contract-details-modal__content__contract__identicon-for-unknown-contact':
|
||||
addressBookEntry?.data?.name === undefined,
|
||||
'contract-details-modal__content__contract__identicon':
|
||||
addressBookEntry?.data?.name !== undefined,
|
||||
})}
|
||||
fallbackClassName={classnames({
|
||||
'contract-details-modal__content__contract__identicon-for-unknown-contact':
|
||||
addressBookEntry?.data?.name === undefined,
|
||||
'contract-details-modal__content__contract__identicon':
|
||||
addressBookEntry?.data?.name !== undefined,
|
||||
})}
|
||||
name={origin}
|
||||
url={siteImage}
|
||||
/>
|
||||
<Box data-testid="recipient">
|
||||
<Typography
|
||||
@ -142,15 +197,15 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
variant={TYPOGRAPHY.H5}
|
||||
marginTop={4}
|
||||
>
|
||||
{tokenName || ellipsify(address)}
|
||||
{addressBookEntry?.data?.name || ellipsify(toAddress)}
|
||||
</Typography>
|
||||
{tokenName && (
|
||||
{addressBookEntry?.data?.name && (
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
display={DISPLAY.FLEX}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
>
|
||||
{ellipsify(address)}
|
||||
{ellipsify(toAddress)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@ -159,10 +214,20 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
className="contract-details-modal__content__contract__buttons"
|
||||
>
|
||||
<Box marginTop={4} marginRight={5}>
|
||||
<Tooltip position="top" title={t('copyToClipboard')}>
|
||||
<Tooltip
|
||||
position="top"
|
||||
title={
|
||||
copiedToAddress
|
||||
? t('copiedExclamation')
|
||||
: t('copyToClipboard')
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__copy"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
handleCopyToAddress(toAddress);
|
||||
}}
|
||||
>
|
||||
<IconCopy color="var(--color-icon-muted)" />
|
||||
</Button>
|
||||
@ -173,6 +238,19 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__block-explorer"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
const blockExplorerTokenLink = getAccountLink(
|
||||
toAddress,
|
||||
chainId,
|
||||
{
|
||||
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
global.platform.openTab({
|
||||
url: blockExplorerTokenLink,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconBlockExplorer
|
||||
size={16}
|
||||
@ -190,24 +268,46 @@ export default function ContractDetailsModal({ onClose, address, tokenName }) {
|
||||
paddingRight={4}
|
||||
paddingBottom={6}
|
||||
paddingLeft={4}
|
||||
className="contract-details-modal__footer"
|
||||
>
|
||||
<Button
|
||||
type="secondary"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('cancel')}
|
||||
<Button type="primary" onClick={() => onClose()}>
|
||||
{t('recoveryPhraseReminderConfirm')}
|
||||
</Button>
|
||||
<Button type="primary">{t('confirm')}</Button>
|
||||
</Box>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
ContractDetailsModal.propTypes = {
|
||||
/**
|
||||
* Function that should close the modal
|
||||
*/
|
||||
onClose: PropTypes.func,
|
||||
address: PropTypes.string,
|
||||
/**
|
||||
* Name of the token that is waiting to be allowed
|
||||
*/
|
||||
tokenName: PropTypes.string,
|
||||
/**
|
||||
* Address of the token that is waiting to be allowed
|
||||
*/
|
||||
tokenAddress: PropTypes.string,
|
||||
/**
|
||||
* Contract address requesting spending cap
|
||||
*/
|
||||
toAddress: PropTypes.string,
|
||||
/**
|
||||
* Current network chainId
|
||||
*/
|
||||
chainId: PropTypes.string,
|
||||
/**
|
||||
* RPC prefs of the current network
|
||||
*/
|
||||
rpcPrefs: PropTypes.object,
|
||||
/**
|
||||
* Dapp URL
|
||||
*/
|
||||
origin: PropTypes.string,
|
||||
/**
|
||||
* Dapp image
|
||||
*/
|
||||
siteImage: PropTypes.string,
|
||||
};
|
||||
|
@ -1,23 +1,44 @@
|
||||
import React, { useState } from 'react';
|
||||
import Button from '../../../ui/button';
|
||||
import React from 'react';
|
||||
import ContractDetailsModal from './contract-details-modal';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/Modals/ContractDetailsModal',
|
||||
id: __filename,
|
||||
argTypes: {
|
||||
onClosePopover: {
|
||||
action: 'Close Contract Details',
|
||||
},
|
||||
onOpenPopover: {
|
||||
action: 'Open Contract Details',
|
||||
onClose: {
|
||||
action: 'onClose',
|
||||
},
|
||||
tokenName: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
address: {
|
||||
tokenAddress: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
toAddress: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
chainId: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
rpcPrefs: {
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
origin: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
siteImage: {
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
@ -25,33 +46,17 @@ export default {
|
||||
},
|
||||
args: {
|
||||
tokenName: 'DAI',
|
||||
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
||||
tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
||||
toAddress: '0x9bc5baf874d2da8d216ae9f137804184ee5afef4',
|
||||
chainId: '0x3',
|
||||
rpcPrefs: {},
|
||||
origin: 'https://metamask.github.io',
|
||||
siteImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => {
|
||||
const [showContractDetails, setshowContractDetails] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
args.onOpenPopover();
|
||||
setshowContractDetails(true);
|
||||
}}
|
||||
>
|
||||
Verify contract details
|
||||
</Button>
|
||||
{showContractDetails && (
|
||||
<ContractDetailsModal
|
||||
onClose={() => {
|
||||
args.onClosePopover();
|
||||
setshowContractDetails(false);
|
||||
}}
|
||||
{...args}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return <ContractDetailsModal {...args} />;
|
||||
};
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
@ -9,6 +9,10 @@
|
||||
margin: 16px 16px 38px 16px;
|
||||
}
|
||||
|
||||
&__identicon-for-unknown-contact {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
flex-grow: 1;
|
||||
|
||||
@ -22,10 +26,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
button + button {
|
||||
margin-inline-start: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getAccountLink } from '@metamask/etherscan-link';
|
||||
import IconCopy from '../icon/icon-copy';
|
||||
import IconBlockExplorer from '../icon/icon-block-explorer';
|
||||
import Box from '../box/box';
|
||||
@ -18,7 +19,12 @@ import {
|
||||
import Button from '../button';
|
||||
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
|
||||
|
||||
export default function ContractTokenValues({ address, tokenName }) {
|
||||
export default function ContractTokenValues({
|
||||
address,
|
||||
tokenName,
|
||||
chainId,
|
||||
rpcPrefs,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const [copied, handleCopy] = useCopyToClipboard();
|
||||
|
||||
@ -62,6 +68,19 @@ export default function ContractTokenValues({ address, tokenName }) {
|
||||
<Button
|
||||
type="link"
|
||||
className="contract-token-values__block-explorer__button"
|
||||
onClick={() => {
|
||||
const blockExplorerTokenLink = getAccountLink(
|
||||
address,
|
||||
chainId,
|
||||
{
|
||||
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
global.platform.openTab({
|
||||
url: blockExplorerTokenLink,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconBlockExplorer size={24} color="var(--color-icon-muted)" />
|
||||
</Button>
|
||||
@ -80,4 +99,12 @@ ContractTokenValues.propTypes = {
|
||||
* Displayed the token name currently tracked in state
|
||||
*/
|
||||
tokenName: PropTypes.string,
|
||||
/**
|
||||
* Current network chainId
|
||||
*/
|
||||
chainId: PropTypes.string,
|
||||
/**
|
||||
* RPC prefs
|
||||
*/
|
||||
rpcPrefs: PropTypes.object,
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
width: 180px;
|
||||
|
||||
&__warning-icon {
|
||||
color: var(--color-warning-default);
|
||||
color: var(--color-error-default);
|
||||
}
|
||||
|
||||
&__question-icon {
|
||||
|
@ -70,7 +70,7 @@ export default function ReviewSpendingCap({
|
||||
key="tooltip-text"
|
||||
variant={TYPOGRAPHY.H7}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
color={COLORS.WARNING_DEFAULT}
|
||||
color={COLORS.ERROR_DEFAULT}
|
||||
>
|
||||
<i className="fa fa-exclamation-circle" />{' '}
|
||||
{t('beCareful')}
|
||||
@ -110,7 +110,7 @@ export default function ReviewSpendingCap({
|
||||
as={TYPOGRAPHY.H6}
|
||||
color={
|
||||
tokenValue > currentTokenBalance
|
||||
? COLORS.WARNING_DEFAULT
|
||||
? COLORS.ERROR_DEFAULT
|
||||
: COLORS.TEXT_DEFAULT
|
||||
}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
|
@ -36,6 +36,7 @@ import Loading from '../../components/ui/loading-screen';
|
||||
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
|
||||
import { ERC1155, ERC20, ERC721 } from '../../../shared/constants/transaction';
|
||||
import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils';
|
||||
import TokenAllowance from '../token-allowance/token-allowance';
|
||||
import { getCustomTxParamsData } from './confirm-approve.util';
|
||||
import ConfirmApproveContent from './confirm-approve-content';
|
||||
|
||||
@ -157,130 +158,174 @@ export default function ConfirmApprove({
|
||||
parseStandardTokenTransactionData(transactionData);
|
||||
const isApprovalOrRejection = getTokenApprovedParam(parsedTransactionData);
|
||||
|
||||
return tokenSymbol === undefined && assetName === undefined ? (
|
||||
<Loading />
|
||||
) : (
|
||||
!process.env.TOKEN_ALLOWANCE_IMPROVEMENTS && (
|
||||
if (tokenSymbol === undefined && assetName === undefined) {
|
||||
return <Loading />;
|
||||
}
|
||||
if (process.env.TOKEN_ALLOWANCE_IMPROVEMENTS && assetStandard === ERC20) {
|
||||
return (
|
||||
<GasFeeContextProvider transaction={transaction}>
|
||||
<ConfirmTransactionBase
|
||||
toAddress={toAddress}
|
||||
identiconAddress={toAddress}
|
||||
showAccountInHeader
|
||||
title={tokensText}
|
||||
customTokenAmount={String(customPermissionAmount)}
|
||||
dappProposedTokenAmount={tokenAmount}
|
||||
currentTokenBalance={tokenBalance}
|
||||
isApprovalOrRejection={isApprovalOrRejection}
|
||||
contentComponent={
|
||||
<TransactionModalContextProvider>
|
||||
<ConfirmApproveContent
|
||||
userAddress={userAddress}
|
||||
isSetApproveForAll={isSetApproveForAll}
|
||||
isApprovalOrRejection={isApprovalOrRejection}
|
||||
decimals={decimals}
|
||||
siteImage={siteImage}
|
||||
setCustomAmount={setCustomPermissionAmount}
|
||||
customTokenAmount={String(customPermissionAmount)}
|
||||
tokenAmount={tokenAmount}
|
||||
origin={formattedOrigin}
|
||||
tokenSymbol={tokenSymbol}
|
||||
tokenImage={tokenImage}
|
||||
tokenBalance={tokenBalance}
|
||||
tokenId={tokenId}
|
||||
assetName={assetName}
|
||||
assetStandard={assetStandard}
|
||||
tokenAddress={tokenAddress}
|
||||
showCustomizeGasModal={approveTransaction}
|
||||
showEditApprovalPermissionModal={({
|
||||
/* eslint-disable no-shadow */
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
/* eslint-enable no-shadow */
|
||||
}) =>
|
||||
dispatch(
|
||||
showModal({
|
||||
name: 'EDIT_APPROVAL_PERMISSION',
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
tokenId,
|
||||
assetStandard,
|
||||
}),
|
||||
)
|
||||
}
|
||||
data={customData || transactionData}
|
||||
toAddress={toAddress}
|
||||
currentCurrency={currentCurrency}
|
||||
nativeCurrency={nativeCurrency}
|
||||
ethTransactionTotal={ethTransactionTotal}
|
||||
fiatTransactionTotal={fiatTransactionTotal}
|
||||
hexTransactionTotal={hexTransactionTotal}
|
||||
useNonceField={useNonceField}
|
||||
nextNonce={nextNonce}
|
||||
customNonceValue={customNonceValue}
|
||||
updateCustomNonce={(value) => {
|
||||
dispatch(updateCustomNonce(value));
|
||||
}}
|
||||
getNextNonce={() => dispatch(getNextNonce())}
|
||||
showCustomizeNonceModal={({
|
||||
/* eslint-disable no-shadow */
|
||||
useNonceField,
|
||||
nextNonce,
|
||||
customNonceValue,
|
||||
updateCustomNonce,
|
||||
getNextNonce,
|
||||
/* eslint-disable no-shadow */
|
||||
}) =>
|
||||
dispatch(
|
||||
showModal({
|
||||
name: 'CUSTOMIZE_NONCE',
|
||||
useNonceField,
|
||||
nextNonce,
|
||||
customNonceValue,
|
||||
updateCustomNonce,
|
||||
getNextNonce,
|
||||
}),
|
||||
)
|
||||
}
|
||||
warning={submitWarning}
|
||||
txData={transaction}
|
||||
fromAddressIsLedger={fromAddressIsLedger}
|
||||
chainId={chainId}
|
||||
rpcPrefs={rpcPrefs}
|
||||
isContract={isContract}
|
||||
isMultiLayerFeeNetwork={isMultiLayerFeeNetwork}
|
||||
supportsEIP1559V2={supportsEIP1559V2}
|
||||
/>
|
||||
{showCustomizeGasPopover && !supportsEIP1559V2 && (
|
||||
<EditGasPopover
|
||||
onClose={closeCustomizeGasPopover}
|
||||
mode={EDIT_GAS_MODES.MODIFY_IN_PLACE}
|
||||
transaction={transaction}
|
||||
/>
|
||||
)}
|
||||
{supportsEIP1559V2 && (
|
||||
<>
|
||||
<EditGasFeePopover />
|
||||
<AdvancedGasFeePopover />
|
||||
</>
|
||||
)}
|
||||
</TransactionModalContextProvider>
|
||||
}
|
||||
hideSenderToRecipient
|
||||
customTxParamsData={customData}
|
||||
assetStandard={assetStandard}
|
||||
/>
|
||||
<TransactionModalContextProvider>
|
||||
<TokenAllowance
|
||||
origin={formattedOrigin}
|
||||
siteImage={siteImage}
|
||||
showCustomizeGasModal={approveTransaction}
|
||||
useNonceField={useNonceField}
|
||||
currentCurrency={currentCurrency}
|
||||
nativeCurrency={nativeCurrency}
|
||||
ethTransactionTotal={ethTransactionTotal}
|
||||
fiatTransactionTotal={fiatTransactionTotal}
|
||||
hexTransactionTotal={hexTransactionTotal}
|
||||
txData={transaction}
|
||||
isMultiLayerFeeNetwork={isMultiLayerFeeNetwork}
|
||||
supportsEIP1559V2={supportsEIP1559V2}
|
||||
userAddress={userAddress}
|
||||
tokenAddress={tokenAddress}
|
||||
data={customData || transactionData}
|
||||
isSetApproveForAll={isSetApproveForAll}
|
||||
isApprovalOrRejection={isApprovalOrRejection}
|
||||
customTxParamsData={customData}
|
||||
dappProposedTokenAmount={tokenAmount}
|
||||
currentTokenBalance={tokenBalance}
|
||||
toAddress={toAddress}
|
||||
tokenSymbol={tokenSymbol}
|
||||
/>
|
||||
{showCustomizeGasPopover && !supportsEIP1559V2 && (
|
||||
<EditGasPopover
|
||||
onClose={closeCustomizeGasPopover}
|
||||
mode={EDIT_GAS_MODES.MODIFY_IN_PLACE}
|
||||
transaction={transaction}
|
||||
/>
|
||||
)}
|
||||
{supportsEIP1559V2 && (
|
||||
<>
|
||||
<EditGasFeePopover />
|
||||
<AdvancedGasFeePopover />
|
||||
</>
|
||||
)}
|
||||
</TransactionModalContextProvider>
|
||||
</GasFeeContextProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<GasFeeContextProvider transaction={transaction}>
|
||||
<ConfirmTransactionBase
|
||||
toAddress={toAddress}
|
||||
identiconAddress={toAddress}
|
||||
showAccountInHeader
|
||||
title={tokensText}
|
||||
customTokenAmount={String(customPermissionAmount)}
|
||||
dappProposedTokenAmount={tokenAmount}
|
||||
currentTokenBalance={tokenBalance}
|
||||
isApprovalOrRejection={isApprovalOrRejection}
|
||||
contentComponent={
|
||||
<TransactionModalContextProvider>
|
||||
<ConfirmApproveContent
|
||||
userAddress={userAddress}
|
||||
isSetApproveForAll={isSetApproveForAll}
|
||||
isApprovalOrRejection={isApprovalOrRejection}
|
||||
decimals={decimals}
|
||||
siteImage={siteImage}
|
||||
setCustomAmount={setCustomPermissionAmount}
|
||||
customTokenAmount={String(customPermissionAmount)}
|
||||
tokenAmount={tokenAmount}
|
||||
origin={formattedOrigin}
|
||||
tokenSymbol={tokenSymbol}
|
||||
tokenImage={tokenImage}
|
||||
tokenBalance={tokenBalance}
|
||||
tokenId={tokenId}
|
||||
assetName={assetName}
|
||||
assetStandard={assetStandard}
|
||||
tokenAddress={tokenAddress}
|
||||
showCustomizeGasModal={approveTransaction}
|
||||
showEditApprovalPermissionModal={({
|
||||
/* eslint-disable no-shadow */
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
/* eslint-enable no-shadow */
|
||||
}) =>
|
||||
dispatch(
|
||||
showModal({
|
||||
name: 'EDIT_APPROVAL_PERMISSION',
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
tokenId,
|
||||
assetStandard,
|
||||
}),
|
||||
)
|
||||
}
|
||||
data={customData || transactionData}
|
||||
toAddress={toAddress}
|
||||
currentCurrency={currentCurrency}
|
||||
nativeCurrency={nativeCurrency}
|
||||
ethTransactionTotal={ethTransactionTotal}
|
||||
fiatTransactionTotal={fiatTransactionTotal}
|
||||
hexTransactionTotal={hexTransactionTotal}
|
||||
useNonceField={useNonceField}
|
||||
nextNonce={nextNonce}
|
||||
customNonceValue={customNonceValue}
|
||||
updateCustomNonce={(value) => {
|
||||
dispatch(updateCustomNonce(value));
|
||||
}}
|
||||
getNextNonce={() => dispatch(getNextNonce())}
|
||||
showCustomizeNonceModal={({
|
||||
/* eslint-disable no-shadow */
|
||||
useNonceField,
|
||||
nextNonce,
|
||||
customNonceValue,
|
||||
updateCustomNonce,
|
||||
getNextNonce,
|
||||
/* eslint-disable no-shadow */
|
||||
}) =>
|
||||
dispatch(
|
||||
showModal({
|
||||
name: 'CUSTOMIZE_NONCE',
|
||||
useNonceField,
|
||||
nextNonce,
|
||||
customNonceValue,
|
||||
updateCustomNonce,
|
||||
getNextNonce,
|
||||
}),
|
||||
)
|
||||
}
|
||||
warning={submitWarning}
|
||||
txData={transaction}
|
||||
fromAddressIsLedger={fromAddressIsLedger}
|
||||
chainId={chainId}
|
||||
rpcPrefs={rpcPrefs}
|
||||
isContract={isContract}
|
||||
isMultiLayerFeeNetwork={isMultiLayerFeeNetwork}
|
||||
supportsEIP1559V2={supportsEIP1559V2}
|
||||
/>
|
||||
{showCustomizeGasPopover && !supportsEIP1559V2 && (
|
||||
<EditGasPopover
|
||||
onClose={closeCustomizeGasPopover}
|
||||
mode={EDIT_GAS_MODES.MODIFY_IN_PLACE}
|
||||
transaction={transaction}
|
||||
/>
|
||||
)}
|
||||
{supportsEIP1559V2 && (
|
||||
<>
|
||||
<EditGasFeePopover />
|
||||
<AdvancedGasFeePopover />
|
||||
</>
|
||||
)}
|
||||
</TransactionModalContextProvider>
|
||||
}
|
||||
hideSenderToRecipient
|
||||
customTxParamsData={customData}
|
||||
assetStandard={assetStandard}
|
||||
/>
|
||||
</GasFeeContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
@import 'send/send';
|
||||
@import 'settings/index';
|
||||
@import 'swaps/index';
|
||||
@import 'token-allowance/index';
|
||||
@import 'token-details/index';
|
||||
@import 'unlock-page/index';
|
||||
@import 'onboarding-flow/index';
|
||||
|
1
ui/pages/token-allowance/index.js
Normal file
1
ui/pages/token-allowance/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './token-allowance';
|
41
ui/pages/token-allowance/index.scss
Normal file
41
ui/pages/token-allowance/index.scss
Normal file
@ -0,0 +1,41 @@
|
||||
.token-allowance-container {
|
||||
&__icon-display-content {
|
||||
width: fit-content;
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100px;
|
||||
position: relative;
|
||||
|
||||
&__siteimage-identicon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.token-allowance-container__verify-link {
|
||||
width: fit-content;
|
||||
margin-inline-start: 96px;
|
||||
margin-inline-end: 96px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a.token-allowance-container__view-details {
|
||||
width: fit-content;
|
||||
margin-inline-start: 108px;
|
||||
margin-inline-end: 108px;
|
||||
}
|
||||
|
||||
&__card-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__data {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__full-tx-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
437
ui/pages/token-allowance/token-allowance.js
Normal file
437
ui/pages/token-allowance/token-allowance.js
Normal file
@ -0,0 +1,437 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import Box from '../../components/ui/box/box';
|
||||
import NetworkAccountBalanceHeader from '../../components/app/network-account-balance-header/network-account-balance-header';
|
||||
import UrlIcon from '../../components/ui/url-icon/url-icon';
|
||||
import Typography from '../../components/ui/typography/typography';
|
||||
import {
|
||||
ALIGN_ITEMS,
|
||||
BORDER_STYLE,
|
||||
COLORS,
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
FONT_WEIGHT,
|
||||
JUSTIFY_CONTENT,
|
||||
TEXT_ALIGN,
|
||||
TYPOGRAPHY,
|
||||
} from '../../helpers/constants/design-system';
|
||||
import { I18nContext } from '../../contexts/i18n';
|
||||
import ContractTokenValues from '../../components/ui/contract-token-values/contract-token-values';
|
||||
import Button from '../../components/ui/button';
|
||||
import ReviewSpendingCap from '../../components/ui/review-spending-cap/review-spending-cap';
|
||||
import { PageContainerFooter } from '../../components/ui/page-container';
|
||||
import ContractDetailsModal from '../../components/app/modals/contract-details-modal/contract-details-modal';
|
||||
import {
|
||||
getCurrentAccountWithSendEtherInfo,
|
||||
getNetworkIdentifier,
|
||||
transactionFeeSelector,
|
||||
getKnownMethodData,
|
||||
getRpcPrefsForCurrentProvider,
|
||||
} from '../../selectors';
|
||||
import { NETWORK_TO_NAME_MAP } from '../../../shared/constants/network';
|
||||
import {
|
||||
cancelTx,
|
||||
updateAndApproveTx,
|
||||
updateCustomNonce,
|
||||
} from '../../store/actions';
|
||||
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import ApproveContentCard from '../../components/app/approve-content-card/approve-content-card';
|
||||
|
||||
export default function TokenAllowance({
|
||||
origin,
|
||||
siteImage,
|
||||
showCustomizeGasModal,
|
||||
useNonceField,
|
||||
currentCurrency,
|
||||
nativeCurrency,
|
||||
ethTransactionTotal,
|
||||
fiatTransactionTotal,
|
||||
hexTransactionTotal,
|
||||
txData,
|
||||
isMultiLayerFeeNetwork,
|
||||
supportsEIP1559V2,
|
||||
userAddress,
|
||||
tokenAddress,
|
||||
data,
|
||||
isSetApproveForAll,
|
||||
isApprovalOrRejection,
|
||||
customTxParamsData,
|
||||
dappProposedTokenAmount,
|
||||
currentTokenBalance,
|
||||
toAddress,
|
||||
tokenSymbol,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
||||
|
||||
const [showContractDetails, setShowContractDetails] = useState(false);
|
||||
const [showFullTxDetails, setShowFullTxDetails] = useState(false);
|
||||
const [isFirstPage, setIsFirstPage] = useState(false);
|
||||
|
||||
const currentAccount = useSelector(getCurrentAccountWithSendEtherInfo);
|
||||
const networkIdentifier = useSelector(getNetworkIdentifier);
|
||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
||||
|
||||
let fullTxData = { ...txData };
|
||||
|
||||
if (customTxParamsData) {
|
||||
fullTxData = {
|
||||
...fullTxData,
|
||||
txParams: {
|
||||
...fullTxData.txParams,
|
||||
data: customTxParamsData,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fee = useSelector((state) => transactionFeeSelector(state, fullTxData));
|
||||
const methodData = useSelector((state) => getKnownMethodData(state, data));
|
||||
|
||||
const networkName =
|
||||
NETWORK_TO_NAME_MAP[fullTxData.chainId] || networkIdentifier;
|
||||
|
||||
const customNonceValue = '';
|
||||
const customNonceMerge = (transactionData) =>
|
||||
customNonceValue
|
||||
? {
|
||||
...transactionData,
|
||||
customNonceValue,
|
||||
}
|
||||
: transactionData;
|
||||
|
||||
const handleReject = () => {
|
||||
dispatch(cancelTx(fullTxData)).then(() => {
|
||||
dispatch(clearConfirmTransaction());
|
||||
dispatch(updateCustomNonce(''));
|
||||
history.push(mostRecentOverviewPage);
|
||||
});
|
||||
};
|
||||
|
||||
const handleApprove = () => {
|
||||
const { name } = methodData;
|
||||
|
||||
if (fee.gasEstimationObject.baseFeePerGas) {
|
||||
fullTxData.estimatedBaseFee = fee.gasEstimationObject.baseFeePerGas;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
fullTxData.contractMethodName = name;
|
||||
}
|
||||
|
||||
if (dappProposedTokenAmount) {
|
||||
fullTxData.dappProposedTokenAmount = dappProposedTokenAmount;
|
||||
fullTxData.originalApprovalAmount = dappProposedTokenAmount;
|
||||
}
|
||||
|
||||
if (currentTokenBalance) {
|
||||
fullTxData.currentTokenBalance = currentTokenBalance;
|
||||
}
|
||||
|
||||
dispatch(updateAndApproveTx(customNonceMerge(fullTxData))).then(() => {
|
||||
dispatch(clearConfirmTransaction());
|
||||
dispatch(updateCustomNonce(''));
|
||||
history.push(mostRecentOverviewPage);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="token-allowance-container page-container">
|
||||
<Box
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
>
|
||||
<Box>
|
||||
{!isFirstPage && (
|
||||
<Button type="inline" onClick={() => setIsFirstPage(true)}>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
color={COLORS.TEXT_MUTED}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
>
|
||||
{'<'} {t('back')}
|
||||
</Typography>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<Box textAlign={TEXT_ALIGN.END}>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.TEXT_MUTED}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
>
|
||||
{isFirstPage ? 1 : 2} {t('ofTextNofM')} 2
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<NetworkAccountBalanceHeader
|
||||
networkName={networkName}
|
||||
accountName={currentAccount.name}
|
||||
accountBalance={currentTokenBalance}
|
||||
tokenName={tokenSymbol}
|
||||
accountAddress={userAddress}
|
||||
/>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
marginTop={6}
|
||||
marginRight={12}
|
||||
marginBottom={8}
|
||||
marginLeft={12}
|
||||
paddingTop={2}
|
||||
paddingRight={4}
|
||||
paddingBottom={2}
|
||||
paddingLeft={2}
|
||||
borderColor={COLORS.BORDER_MUTED}
|
||||
borderStyle={BORDER_STYLE.SOLID}
|
||||
borderWidth={1}
|
||||
className="token-allowance-container__icon-display-content"
|
||||
>
|
||||
<UrlIcon
|
||||
className="token-allowance-container__icon-display-content__siteimage-identicon"
|
||||
fallbackClassName="token-allowance-container__icon-display-content__siteimage-identicon"
|
||||
name={origin}
|
||||
url={siteImage}
|
||||
/>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
boxProps={{ marginLeft: 1, marginTop: 2 }}
|
||||
>
|
||||
{origin}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box marginBottom={5}>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H3}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
align={TEXT_ALIGN.CENTER}
|
||||
>
|
||||
{isFirstPage ? t('setSpendingCap') : t('reviewSpendingCap')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<ContractTokenValues
|
||||
tokenName={tokenSymbol}
|
||||
address={tokenAddress}
|
||||
chainId={fullTxData.chainId}
|
||||
rpcPrefs={rpcPrefs}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
marginTop={1}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => setShowContractDetails(true)}
|
||||
className="token-allowance-container__verify-link"
|
||||
>
|
||||
<Typography variant={TYPOGRAPHY.H6} color={COLORS.PRIMARY_DEFAULT}>
|
||||
{t('verifyContractDetails')}
|
||||
</Typography>
|
||||
</Button>
|
||||
</Box>
|
||||
<Box margin={[4, 4, 3, 4]}>
|
||||
<ReviewSpendingCap
|
||||
tokenName={tokenSymbol}
|
||||
currentTokenBalance={parseFloat(currentTokenBalance)}
|
||||
tokenValue={10}
|
||||
onEdit={() => setIsFirstPage(true)}
|
||||
/>
|
||||
</Box>
|
||||
{!isFirstPage && (
|
||||
<Box className="token-allowance-container__card-wrapper">
|
||||
<ApproveContentCard
|
||||
symbol={<i className="fa fa-tag" />}
|
||||
title={t('transactionFee')}
|
||||
showEdit
|
||||
showAdvanceGasFeeOptions
|
||||
onEditClick={showCustomizeGasModal}
|
||||
renderTransactionDetailsContent
|
||||
noBorder={useNonceField || !showFullTxDetails}
|
||||
supportsEIP1559V2={supportsEIP1559V2}
|
||||
isMultiLayerFeeNetwork={isMultiLayerFeeNetwork}
|
||||
ethTransactionTotal={ethTransactionTotal}
|
||||
nativeCurrency={nativeCurrency}
|
||||
fullTxData={fullTxData}
|
||||
hexTransactionTotal={hexTransactionTotal}
|
||||
fiatTransactionTotal={fiatTransactionTotal}
|
||||
currentCurrency={currentCurrency}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.ROW}
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => setShowFullTxDetails(!showFullTxDetails)}
|
||||
className="token-allowance-container__view-details"
|
||||
>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
color={COLORS.PRIMARY_DEFAULT}
|
||||
marginRight={1}
|
||||
>
|
||||
{t('viewDetails')}
|
||||
</Typography>
|
||||
{showFullTxDetails ? (
|
||||
<i className="fa fa-sm fa-angle-up" />
|
||||
) : (
|
||||
<i className="fa fa-sm fa-angle-down" />
|
||||
)}
|
||||
</Button>
|
||||
</Box>
|
||||
{showFullTxDetails ? (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
className="token-allowance-container__full-tx-content"
|
||||
>
|
||||
<Box className="token-allowance-container__data">
|
||||
<ApproveContentCard
|
||||
symbol={<i className="fa fa-file" />}
|
||||
title={t('data')}
|
||||
renderDataContent
|
||||
noBorder
|
||||
supportsEIP1559V2={supportsEIP1559V2}
|
||||
isSetApproveForAll={isSetApproveForAll}
|
||||
isApprovalOrRejection={isApprovalOrRejection}
|
||||
data={data}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
<PageContainerFooter
|
||||
cancelText={t('reject')}
|
||||
submitText={isFirstPage ? t('next') : t('approveButtonText')}
|
||||
onCancel={() => handleReject()}
|
||||
onSubmit={() => (isFirstPage ? setIsFirstPage(false) : handleApprove())}
|
||||
/>
|
||||
{showContractDetails && (
|
||||
<ContractDetailsModal
|
||||
tokenName={tokenSymbol}
|
||||
onClose={() => setShowContractDetails(false)}
|
||||
tokenAddress={tokenAddress}
|
||||
toAddress={toAddress}
|
||||
chainId={fullTxData.chainId}
|
||||
rpcPrefs={rpcPrefs}
|
||||
origin={origin}
|
||||
siteImage={siteImage}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
TokenAllowance.propTypes = {
|
||||
/**
|
||||
* Dapp URL
|
||||
*/
|
||||
origin: PropTypes.string,
|
||||
/**
|
||||
* Dapp image
|
||||
*/
|
||||
siteImage: PropTypes.string,
|
||||
/**
|
||||
* Function that is supposed to open the customized gas modal
|
||||
*/
|
||||
showCustomizeGasModal: PropTypes.func,
|
||||
/**
|
||||
* Whether nonce field should be used or not
|
||||
*/
|
||||
useNonceField: PropTypes.bool,
|
||||
/**
|
||||
* Current fiat currency (e.g. USD)
|
||||
*/
|
||||
currentCurrency: PropTypes.string,
|
||||
/**
|
||||
* Current native currency (e.g. RopstenETH)
|
||||
*/
|
||||
nativeCurrency: PropTypes.string,
|
||||
/**
|
||||
* Total sum of the transaction in native currency
|
||||
*/
|
||||
ethTransactionTotal: PropTypes.string,
|
||||
/**
|
||||
* Total sum of the transaction in fiat currency
|
||||
*/
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
/**
|
||||
* Total sum of the transaction converted to hex value
|
||||
*/
|
||||
hexTransactionTotal: PropTypes.string,
|
||||
/**
|
||||
* Current transaction
|
||||
*/
|
||||
txData: PropTypes.object,
|
||||
/**
|
||||
* Is multi-layer fee network or not
|
||||
*/
|
||||
isMultiLayerFeeNetwork: PropTypes.bool,
|
||||
/**
|
||||
* Is the enhanced gas fee enabled or not
|
||||
*/
|
||||
supportsEIP1559V2: PropTypes.bool,
|
||||
/**
|
||||
* User's address
|
||||
*/
|
||||
userAddress: PropTypes.string,
|
||||
/**
|
||||
* Address of the token that is waiting to be allowed
|
||||
*/
|
||||
tokenAddress: PropTypes.string,
|
||||
/**
|
||||
* Current transaction data
|
||||
*/
|
||||
data: PropTypes.string,
|
||||
/**
|
||||
* Is set approve for all or not
|
||||
*/
|
||||
isSetApproveForAll: PropTypes.bool,
|
||||
/**
|
||||
* Whether a current set approval for all transaction will approve or revoke access
|
||||
*/
|
||||
isApprovalOrRejection: PropTypes.bool,
|
||||
/**
|
||||
* Custom transaction parameters data made by the user (fees)
|
||||
*/
|
||||
customTxParamsData: PropTypes.object,
|
||||
/**
|
||||
* Token amount proposed by the Dapp
|
||||
*/
|
||||
dappProposedTokenAmount: PropTypes.string,
|
||||
/**
|
||||
* Token balance of the current account
|
||||
*/
|
||||
currentTokenBalance: PropTypes.string,
|
||||
/**
|
||||
* Contract address requesting spending cap
|
||||
*/
|
||||
toAddress: PropTypes.string,
|
||||
/**
|
||||
* Symbol of the token that is waiting to be allowed
|
||||
*/
|
||||
tokenSymbol: PropTypes.string,
|
||||
};
|
201
ui/pages/token-allowance/token-allowance.stories.js
Normal file
201
ui/pages/token-allowance/token-allowance.stories.js
Normal file
@ -0,0 +1,201 @@
|
||||
import React from 'react';
|
||||
import TokenAllowance from './token-allowance';
|
||||
|
||||
export default {
|
||||
title: 'Pages/TokenAllowance',
|
||||
id: __filename,
|
||||
argTypes: {
|
||||
origin: {
|
||||
control: 'text',
|
||||
},
|
||||
siteImage: {
|
||||
control: 'text',
|
||||
},
|
||||
showCustomizeGasModal: {
|
||||
action: 'showCustomizeGasModal',
|
||||
},
|
||||
useNonceField: {
|
||||
control: 'boolean',
|
||||
},
|
||||
currentCurrency: {
|
||||
control: 'text',
|
||||
},
|
||||
nativeCurrency: {
|
||||
control: 'text',
|
||||
},
|
||||
ethTransactionTotal: {
|
||||
control: 'text',
|
||||
},
|
||||
fiatTransactionTotal: {
|
||||
control: 'text',
|
||||
},
|
||||
hexTransactionTotal: {
|
||||
control: 'text',
|
||||
},
|
||||
isMultiLayerFeeNetwork: {
|
||||
control: 'text',
|
||||
},
|
||||
supportsEIP1559V2: {
|
||||
control: 'boolean',
|
||||
},
|
||||
userAddress: {
|
||||
control: 'text',
|
||||
},
|
||||
tokenAddress: {
|
||||
control: 'text',
|
||||
},
|
||||
data: {
|
||||
control: 'text',
|
||||
},
|
||||
isSetApproveForAll: {
|
||||
control: 'boolean',
|
||||
},
|
||||
setApproveForAllArg: {
|
||||
control: 'boolean',
|
||||
},
|
||||
customTxParamsData: {
|
||||
control: 'object',
|
||||
},
|
||||
dappProposedTokenAmount: {
|
||||
control: 'text',
|
||||
},
|
||||
currentTokenBalance: {
|
||||
control: 'text',
|
||||
},
|
||||
toAddress: {
|
||||
control: 'text',
|
||||
},
|
||||
tokenSymbol: {
|
||||
control: 'text',
|
||||
},
|
||||
txData: {
|
||||
control: 'object',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
origin: 'https://metamask.github.io',
|
||||
siteImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
||||
useNonceField: false,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'RopstenETH',
|
||||
ethTransactionTotal: '0.0012',
|
||||
fiatTransactionTotal: '1.6',
|
||||
hexTransactionTotal: '0x44364c5bb0000',
|
||||
isMultiLayerFeeNetwork: false,
|
||||
supportsEIP1559V2: false,
|
||||
userAddress: '0xdd34b35ca1de17dfcdc07f79ff1f8f94868c40a1',
|
||||
tokenAddress: '0x55797717b9947b31306f4aac7ad1365c6e3923bd',
|
||||
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
|
||||
isSetApproveForAll: false,
|
||||
setApproveForAllArg: false,
|
||||
customTxParamsData: {},
|
||||
dappProposedTokenAmount: '7',
|
||||
currentTokenBalance: '10',
|
||||
toAddress: '0x9bc5baf874d2da8d216ae9f137804184ee5afef4',
|
||||
tokenSymbol: 'TST',
|
||||
txData: {
|
||||
id: 3049568294499567,
|
||||
time: 1664449552289,
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: '3',
|
||||
originalGasEstimate: '0xea60',
|
||||
userEditedGasLimit: false,
|
||||
chainId: '0x3',
|
||||
loadingDefaults: false,
|
||||
dappSuggestedGasFees: {
|
||||
gasPrice: '0x4a817c800',
|
||||
gas: '0xea60',
|
||||
},
|
||||
sendFlowHistory: [],
|
||||
txParams: {
|
||||
from: '0xdd34b35ca1de17dfcdc07f79ff1f8f94868c40a1',
|
||||
to: '0x55797717b9947b31306f4aac7ad1365c6e3923bd',
|
||||
value: '0x0',
|
||||
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
|
||||
gas: '0xea60',
|
||||
maxFeePerGas: '0x4a817c800',
|
||||
maxPriorityFeePerGas: '0x4a817c800',
|
||||
},
|
||||
origin: 'https://metamask.github.io',
|
||||
type: 'approve',
|
||||
history: [
|
||||
{
|
||||
id: 3049568294499567,
|
||||
time: 1664449552289,
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: '3',
|
||||
originalGasEstimate: '0xea60',
|
||||
userEditedGasLimit: false,
|
||||
chainId: '0x3',
|
||||
loadingDefaults: true,
|
||||
dappSuggestedGasFees: {
|
||||
gasPrice: '0x4a817c800',
|
||||
gas: '0xea60',
|
||||
},
|
||||
sendFlowHistory: [],
|
||||
txParams: {
|
||||
from: '0xdd34b35ca1de17dfcdc07f79ff1f8f94868c40a1',
|
||||
to: '0x55797717b9947b31306f4aac7ad1365c6e3923bd',
|
||||
value: '0x0',
|
||||
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
|
||||
gas: '0xea60',
|
||||
gasPrice: '0x4a817c800',
|
||||
},
|
||||
origin: 'https://metamask.github.io',
|
||||
type: 'approve',
|
||||
},
|
||||
[
|
||||
{
|
||||
op: 'remove',
|
||||
path: '/txParams/gasPrice',
|
||||
note: 'Added new unapproved transaction.',
|
||||
timestamp: 1664449553939,
|
||||
},
|
||||
{
|
||||
op: 'add',
|
||||
path: '/txParams/maxFeePerGas',
|
||||
value: '0x4a817c800',
|
||||
},
|
||||
{
|
||||
op: 'add',
|
||||
path: '/txParams/maxPriorityFeePerGas',
|
||||
value: '0x4a817c800',
|
||||
},
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/loadingDefaults',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
op: 'add',
|
||||
path: '/userFeeLevel',
|
||||
value: 'custom',
|
||||
},
|
||||
{
|
||||
op: 'add',
|
||||
path: '/defaultGasEstimates',
|
||||
value: {
|
||||
estimateType: 'custom',
|
||||
gas: '0xea60',
|
||||
maxFeePerGas: '0x4a817c800',
|
||||
maxPriorityFeePerGas: '0x4a817c800',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
userFeeLevel: 'custom',
|
||||
defaultGasEstimates: {
|
||||
estimateType: 'custom',
|
||||
gas: '0xea60',
|
||||
maxFeePerGas: '0x4a817c800',
|
||||
maxPriorityFeePerGas: '0x4a817c800',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => {
|
||||
return <TokenAllowance {...args} />;
|
||||
};
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
Loading…
Reference in New Issue
Block a user