Created a new contract details modal (#15549)

Co-authored-by: Brad Decker <bhdecker84@gmail.com>
feature/default_network_editable
Adnan Sahovic 2 years ago committed by GitHub
parent 47d61c6832
commit 69b5505a1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      app/_locales/en/messages.json
  2. 213
      ui/components/app/modals/contract-details-modal/contract-details-modal.js
  3. 57
      ui/components/app/modals/contract-details-modal/contract-details-modal.stories.js
  4. 1
      ui/components/app/modals/contract-details-modal/index.js
  5. 31
      ui/components/app/modals/contract-details-modal/index.scss
  6. 1
      ui/components/app/modals/index.scss
  7. 48
      ui/components/ui/icon/icon-block-explorer.js
  8. 47
      ui/components/ui/icon/icon-copy.js

@ -746,9 +746,21 @@
"contractDeployment": {
"message": "Contract deployment"
},
"contractDescription": {
"message": "To protect yourself against scammers, take a moment to verify contract details."
},
"contractInteraction": {
"message": "Contract interaction"
},
"contractRequestingSpendingCap": {
"message": "Contract requesting spending cap"
},
"contractTitle": {
"message": "Contract details"
},
"contractToken": {
"message": "Token contract"
},
"convertTokenToNFTDescription": {
"message": "We've detected that this asset is an NFT. MetaMask now has full native support for NFTs. Would you like to remove it from your token list and add it as an NFT?"
},
@ -2498,6 +2510,9 @@
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
},
"openInBlockExplorer": {
"message": "Open in block explorer"
},
"optional": {
"message": "Optional"
},

@ -0,0 +1,213 @@
import React from 'react';
import PropTypes from 'prop-types';
import Box from '../../../ui/box';
import IconCopy from '../../../ui/icon/icon-copy';
import IconBlockExplorer from '../../../ui/icon/icon-block-explorer';
import Button from '../../../ui/button/button.component';
import Tooltip from '../../../ui/tooltip/tooltip';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import Identicon from '../../../ui/identicon/identicon.component';
import { ellipsify } from '../../../../pages/send/send.utils';
import Popover from '../../../ui/popover';
import Typography from '../../../ui/typography';
import {
FONT_WEIGHT,
TYPOGRAPHY,
DISPLAY,
COLORS,
JUSTIFY_CONTENT,
SIZES,
BORDER_STYLE,
} from '../../../../helpers/constants/design-system';
export default function ContractDetailsModal({ onClose, address, tokenName }) {
const t = useI18nContext();
return (
<Popover className="contract-details-modal">
<Box
paddingTop={6}
paddingRight={4}
paddingBottom={8}
paddingLeft={4}
className="contract-details-modal__content"
>
<Typography
fontWeight={FONT_WEIGHT.BOLD}
variant={TYPOGRAPHY.H5}
display={DISPLAY.FLEX}
boxProps={{ marginTop: 0, marginBottom: 0 }}
>
{t('contractTitle')}
</Typography>
<Typography
variant={TYPOGRAPHY.H7}
display={DISPLAY.FLEX}
color={COLORS.TEXT_ALTERNATIVE}
boxProps={{ marginTop: 2, marginBottom: 0 }}
>
{t('contractDescription')}
</Typography>
<Typography
variant={TYPOGRAPHY.H6}
display={DISPLAY.FLEX}
marginTop={4}
marginBottom={2}
>
{t('contractToken')}
</Typography>
<Box
display={DISPLAY.FLEX}
borderRadius={SIZES.SM}
borderStyle={BORDER_STYLE.SOLID}
borderColor={COLORS.BORDER_DEFAULT}
className="contract-details-modal__content__contract"
>
<Identicon
className="contract-details-modal__content__contract__identicon"
address={address}
diameter={24}
/>
<Box data-testid="recipient">
<Typography
fontWeight={FONT_WEIGHT.BOLD}
variant={TYPOGRAPHY.H5}
marginTop={4}
>
{tokenName || ellipsify(address)}
</Typography>
{tokenName && (
<Typography
variant={TYPOGRAPHY.H6}
display={DISPLAY.FLEX}
color={COLORS.TEXT_ALTERNATIVE}
>
{ellipsify(address)}
</Typography>
)}
</Box>
<Box
justifyContent={JUSTIFY_CONTENT.FLEX_END}
className="contract-details-modal__content__contract__buttons"
>
<Box marginTop={4} marginRight={5}>
<Tooltip position="top" title={t('copyToClipboard')}>
<Button
className="contract-details-modal__content__contract__buttons__copy"
type="link"
>
<IconCopy color="var(--color-icon-muted)" />
</Button>
</Tooltip>
</Box>
<Box marginTop={5} marginRight={5}>
<Tooltip position="top" title={t('openInBlockExplorer')}>
<Button
className="contract-details-modal__content__contract__buttons__block-explorer"
type="link"
>
<IconBlockExplorer
size={16}
color="var(--color-icon-muted)"
/>
</Button>
</Tooltip>
</Box>
</Box>
</Box>
<Typography
variant={TYPOGRAPHY.H6}
display={DISPLAY.FLEX}
marginTop={4}
marginBottom={2}
>
{t('contractRequestingSpendingCap')}
</Typography>
<Box
display={DISPLAY.FLEX}
borderRadius={SIZES.SM}
borderStyle={BORDER_STYLE.SOLID}
borderColor={COLORS.BORDER_DEFAULT}
className="contract-details-modal__content__contract"
>
<Identicon
className="contract-details-modal__content__contract__identicon"
address={address}
diameter={24}
/>
<Box data-testid="recipient">
<Typography
fontWeight={FONT_WEIGHT.BOLD}
variant={TYPOGRAPHY.H5}
marginTop={4}
>
{tokenName || ellipsify(address)}
</Typography>
{tokenName && (
<Typography
variant={TYPOGRAPHY.H6}
display={DISPLAY.FLEX}
color={COLORS.TEXT_ALTERNATIVE}
>
{ellipsify(address)}
</Typography>
)}
</Box>
<Box
justifyContent={JUSTIFY_CONTENT.FLEX_END}
className="contract-details-modal__content__contract__buttons"
>
<Box marginTop={4} marginRight={5}>
<Tooltip position="top" title={t('copyToClipboard')}>
<Button
className="contract-details-modal__content__contract__buttons__copy"
type="link"
>
<IconCopy color="var(--color-icon-muted)" />
</Button>
</Tooltip>
</Box>
<Box marginTop={5} marginRight={5}>
<Tooltip position="top" title={t('openInBlockExplorer')}>
<Button
className="contract-details-modal__content__contract__buttons__block-explorer"
type="link"
>
<IconBlockExplorer
size={16}
color="var(--color-icon-muted)"
/>
</Button>
</Tooltip>
</Box>
</Box>
</Box>
</Box>
<Box
display={DISPLAY.FLEX}
paddingTop={6}
paddingRight={4}
paddingBottom={6}
paddingLeft={4}
className="contract-details-modal__footer"
>
<Button
type="secondary"
onClick={() => {
onClose();
}}
>
{t('cancel')}
</Button>
<Button type="primary">{t('confirm')}</Button>
</Box>
</Popover>
);
}
ContractDetailsModal.propTypes = {
onClose: PropTypes.func,
address: PropTypes.string,
tokenName: PropTypes.string,
};

@ -0,0 +1,57 @@
import React, { useState } from 'react';
import Button from '../../../ui/button';
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',
},
tokenName: {
control: {
type: 'text',
},
},
address: {
control: {
type: 'text',
},
},
},
args: {
tokenName: 'DAI',
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
},
};
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}
/>
)}
</>
);
};
DefaultStory.storyName = 'Default';

@ -0,0 +1 @@
export { default } from './contract-details-modal';

@ -0,0 +1,31 @@
.contract-details-modal {
width: 360px !important;
&__content {
border-bottom: 1px solid var(--color-border-muted);
&__contract {
&__identicon {
margin: 16px 16px 38px 16px;
}
&__buttons {
flex-grow: 1;
&__copy.btn-link {
padding: 0;
}
&__block-explorer.btn-link {
padding: 0;
}
}
}
}
&__footer {
button + button {
margin-inline-start: 1rem;
}
}
}

@ -12,6 +12,7 @@
@import 'transaction-confirmed/index';
@import 'customize-nonce/index';
@import 'convert-token-to-nft-modal/index';
@import 'contract-details-modal/index';
.modal {
z-index: 1050;

@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';
const IconBlockExplorer = ({
size = 24,
color = 'currentColor',
ariaLabel,
className,
onClick,
}) => (
<svg
width={size}
height={size}
fill={color}
className={className}
aria-label={ariaLabel}
onClick={onClick}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path d="m328 482l-128 0c-115 0-168-52-168-169l0-128c0-115 53-168 168-168l43 0c5 0 10 2 14 6 3 3 5 8 5 13 0 5-2 10-5 14-4 3-9 5-14 5l-43 0c-96 0-130 34-130 130l0 128c0 96 34 131 130 131l128 0c96 0 130-35 130-131l0-42c0-5 3-10 6-14 4-3 9-5 14-5 5 0 10 2 13 5 4 4 6 9 6 14l0 42c0 117-52 169-169 169z m-42-235c-5 0-10-2-14-5-3-4-5-9-5-14 0-5 2-10 5-13l159-160-56 0c-5 0-10-2-13-5-4-4-6-9-6-14 0-5 2-10 6-13 3-4 8-6 13-6l103 0c2 0 5 0 7 1 2 1 5 3 6 5 2 1 4 3 5 6 0 2 1 5 1 7l0 103c0 5-2 10-6 13-3 4-8 6-13 6-5 0-10-2-14-6-3-3-6-8-6-13l0-56-159 159c-3 3-8 5-13 5z" />
</svg>
);
IconBlockExplorer.propTypes = {
/**
* The size of the icon in pixels. Should follow 8px grid 16, 24, 32, etc
*/
size: PropTypes.number,
/**
* The color of the icon accepts design token css variables
*/
color: PropTypes.string,
/**
* An additional className to assign the Icon
*/
className: PropTypes.string,
/**
* The onClick handler
*/
onClick: PropTypes.func,
/**
* The aria-label of the icon for accessibility purposes
*/
ariaLabel: PropTypes.string,
};
export default IconBlockExplorer;

@ -0,0 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
const IconCopy = ({
size = 24,
color = 'currentColor',
ariaLabel,
className,
onClick,
}) => (
<svg
width={size}
height={size}
fill={color}
className={className}
aria-label={ariaLabel}
onClick={onClick}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path d="m333 274l0 86c0 72-28 101-100 101l-86 0c-72 0-100-29-100-101l0-86c0-71 28-100 100-100l86 0c72 0 100 29 100 100z m23-223l-86 0c-63 0-93 23-99 77-1 11 8 20 20 20l42 0c86 0 126 40 126 126l0 43c0 11 9 21 21 19 54-6 76-35 76-98l0-86c0-72-28-101-100-101z" />
</svg>
);
IconCopy.propTypes = {
/**
* The size of the icon in pixels. Should follow 8px grid 16, 24, 32, etc
*/
size: PropTypes.number,
/**
* The color of the icon accepts design token css variables
*/
color: PropTypes.string,
/**
* An additional className to assign the Icon
*/
className: PropTypes.string,
/**
* The onClick handler
*/
onClick: PropTypes.func,
/**
* The aria-label of the icon for accessibility purposes
*/
ariaLabel: PropTypes.string,
};
export default IconCopy;
Loading…
Cancel
Save