diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index beffe06bc..89123a67c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -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" }, diff --git a/ui/components/app/modals/contract-details-modal/contract-details-modal.js b/ui/components/app/modals/contract-details-modal/contract-details-modal.js new file mode 100644 index 000000000..5ac65d8b2 --- /dev/null +++ b/ui/components/app/modals/contract-details-modal/contract-details-modal.js @@ -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 ( + + + + {t('contractTitle')} + + + {t('contractDescription')} + + + {t('contractToken')} + + + + + + {tokenName || ellipsify(address)} + + {tokenName && ( + + {ellipsify(address)} + + )} + + + + + + + + + + + + + + + + + {t('contractRequestingSpendingCap')} + + + + + + {tokenName || ellipsify(address)} + + {tokenName && ( + + {ellipsify(address)} + + )} + + + + + + + + + + + + + + + + + + + + + ); +} + +ContractDetailsModal.propTypes = { + onClose: PropTypes.func, + address: PropTypes.string, + tokenName: PropTypes.string, +}; diff --git a/ui/components/app/modals/contract-details-modal/contract-details-modal.stories.js b/ui/components/app/modals/contract-details-modal/contract-details-modal.stories.js new file mode 100644 index 000000000..8cf6272fc --- /dev/null +++ b/ui/components/app/modals/contract-details-modal/contract-details-modal.stories.js @@ -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 ( + <> + + {showContractDetails && ( + { + args.onClosePopover(); + setshowContractDetails(false); + }} + {...args} + /> + )} + + ); +}; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/modals/contract-details-modal/index.js b/ui/components/app/modals/contract-details-modal/index.js new file mode 100644 index 000000000..004083585 --- /dev/null +++ b/ui/components/app/modals/contract-details-modal/index.js @@ -0,0 +1 @@ +export { default } from './contract-details-modal'; diff --git a/ui/components/app/modals/contract-details-modal/index.scss b/ui/components/app/modals/contract-details-modal/index.scss new file mode 100644 index 000000000..b36649fd7 --- /dev/null +++ b/ui/components/app/modals/contract-details-modal/index.scss @@ -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; + } + } +} diff --git a/ui/components/app/modals/index.scss b/ui/components/app/modals/index.scss index 286322a76..49e0063ab 100644 --- a/ui/components/app/modals/index.scss +++ b/ui/components/app/modals/index.scss @@ -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; diff --git a/ui/components/ui/icon/icon-block-explorer.js b/ui/components/ui/icon/icon-block-explorer.js new file mode 100644 index 000000000..ce09dee9a --- /dev/null +++ b/ui/components/ui/icon/icon-block-explorer.js @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const IconBlockExplorer = ({ + size = 24, + color = 'currentColor', + ariaLabel, + className, + onClick, +}) => ( + + + +); + +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; diff --git a/ui/components/ui/icon/icon-copy.js b/ui/components/ui/icon/icon-copy.js new file mode 100644 index 000000000..df4825fcf --- /dev/null +++ b/ui/components/ui/icon/icon-copy.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const IconCopy = ({ + size = 24, + color = 'currentColor', + ariaLabel, + className, + onClick, +}) => ( + + + +); + +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;