diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index 6cce4cb55..bc94b3e44 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -698,7 +698,7 @@ "ui/components/app/signature-request/signature-request-header/signature-request-header.component.js", "ui/components/app/signature-request/signature-request-header/signature-request-header.stories.js", "ui/components/app/signature-request/signature-request-message/index.js", - "ui/components/app/signature-request/signature-request-message/signature-request-message.component.js", + "ui/components/app/signature-request/signature-request-message/signature-request-message.js", "ui/components/app/signature-request/signature-request.component.js", "ui/components/app/signature-request/signature-request.component.test.js", "ui/components/app/signature-request/signature-request.container.js", diff --git a/test/e2e/tests/signature-request.spec.js b/test/e2e/tests/signature-request.spec.js index 38141527b..9fa2a08d4 100644 --- a/test/e2e/tests/signature-request.spec.js +++ b/test/e2e/tests/signature-request.spec.js @@ -56,7 +56,7 @@ describe('Sign Typed Data V4 Signature Request', function () { const origin = content[0]; const address = content[1]; const message = await driver.findElement( - '.signature-request-message--node-value', + '.signature-request-data__node__value', ); assert.equal(await title.getText(), 'Signature request'); assert.equal(await name.getText(), 'Ether Mail'); @@ -140,7 +140,7 @@ describe('Sign Typed Data V3 Signature Request', function () { const origin = content[0]; const address = content[1]; const messages = await driver.findElements( - '.signature-request-message--node-value', + '.signature-request-data__node__value', ); assert.equal(await title.getText(), 'Signature request'); assert.equal(await name.getText(), 'Ether Mail'); @@ -154,6 +154,10 @@ describe('Sign Typed Data V3 Signature Request', function () { assert.equal(await messages[4].getText(), 'Hello, Bob!'); // Approve signing typed data + await driver.clickElement( + '[data-testid="signature-request-scroll-button"]', + ); + await driver.delay(regularDelayMs); await driver.clickElement({ text: 'Sign', tag: 'button' }); await driver.waitUntilXWindowHandles(2); windowHandles = await driver.getAllWindowHandles(); diff --git a/ui/components/app/confirm-page-container/confirm-page-container.container.js b/ui/components/app/confirm-page-container/confirm-page-container.container.js index 8d247971c..0ce2790fc 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container.container.js +++ b/ui/components/app/confirm-page-container/confirm-page-container.container.js @@ -7,6 +7,7 @@ import { getSwapsDefaultToken, getMetadataContractName, getAccountName, + getMetaMaskIdentities, } from '../../../selectors'; import ConfirmPageContainer from './confirm-page-container.component'; @@ -17,8 +18,9 @@ function mapStateToProps(state, ownProps) { const networkIdentifier = getNetworkIdentifier(state); const defaultToken = getSwapsDefaultToken(state); const accountBalance = defaultToken.string; - const toName = getAccountName(state, to); - const toMetadataName = getMetadataContractName(to); + const identities = getMetaMaskIdentities(state); + const toName = getAccountName(identities, to); + const toMetadataName = getMetadataContractName(state, to); return { isBuyableChain, diff --git a/ui/components/app/signature-request/index.scss b/ui/components/app/signature-request/index.scss index aacb6a2f9..f89b3c667 100644 --- a/ui/components/app/signature-request/index.scss +++ b/ui/components/app/signature-request/index.scss @@ -1,6 +1,7 @@ @import 'signature-request-footer/index'; @import 'signature-request-header/index'; @import 'signature-request-message/index'; +@import 'signature-request-data/index'; .signature-request { display: flex; diff --git a/ui/components/app/signature-request/signature-request-data/index.js b/ui/components/app/signature-request/signature-request-data/index.js new file mode 100644 index 000000000..64ed87d93 --- /dev/null +++ b/ui/components/app/signature-request/signature-request-data/index.js @@ -0,0 +1 @@ +export { default } from './signature-request-data'; diff --git a/ui/components/app/signature-request/signature-request-data/index.scss b/ui/components/app/signature-request/signature-request-data/index.scss new file mode 100644 index 000000000..6b81bd84d --- /dev/null +++ b/ui/components/app/signature-request/signature-request-data/index.scss @@ -0,0 +1,26 @@ +.signature-request-data { + &__node { + &__value { + white-space: pre-line; + overflow: hidden; + word-wrap: break-word; + + &__address { + [dir='rtl'] & { + /*rtl:ignore*/ + direction: ltr; + + /*rtl:ignore*/ + text-align: right; + + span { + display: block; + + /*rtl:ignore*/ + direction: rtl; + } + } + } + } + } +} diff --git a/ui/components/app/signature-request/signature-request-data/signature-request-data.js b/ui/components/app/signature-request/signature-request-data/signature-request-data.js new file mode 100644 index 000000000..6685f1c56 --- /dev/null +++ b/ui/components/app/signature-request/signature-request-data/signature-request-data.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import { getMetaMaskIdentities, getAccountName } from '../../../../selectors'; +import Address from '../../transaction-decoding/components/decoding/address'; +import { + isValidHexAddress, + toChecksumHexAddress, +} from '../../../../../shared/modules/hexstring-utils'; +import Box from '../../../ui/box'; +import Typography from '../../../ui/typography'; +import { + DISPLAY, + COLORS, + FONT_WEIGHT, + TYPOGRAPHY, +} from '../../../../helpers/constants/design-system'; + +export default function SignatureRequestData({ data }) { + const identities = useSelector(getMetaMaskIdentities); + + return ( + + {Object.entries(data).map(([label, value], i) => ( + + + {label.charAt(0).toUpperCase() + label.slice(1)}:{' '} + + {typeof value === 'object' && value !== null ? ( + + ) : ( + + {isValidHexAddress(value, { + mixedCaseUseChecksum: true, + }) ? ( + +
+ + ) : ( + `${value}` + )} + + )} + + ))} + + ); +} + +SignatureRequestData.propTypes = { + data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired, +}; diff --git a/ui/components/app/signature-request/signature-request-message/index.js b/ui/components/app/signature-request/signature-request-message/index.js index 5fe8ddb1c..569576fdd 100644 --- a/ui/components/app/signature-request/signature-request-message/index.js +++ b/ui/components/app/signature-request/signature-request-message/index.js @@ -1 +1 @@ -export { default } from './signature-request-message.component'; +export { default } from './signature-request-message'; diff --git a/ui/components/app/signature-request/signature-request-message/index.scss b/ui/components/app/signature-request/signature-request-message/index.scss index b0b5c67ae..54fdb59d4 100644 --- a/ui/components/app/signature-request/signature-request-message/index.scss +++ b/ui/components/app/signature-request/signature-request-message/index.scss @@ -1,75 +1,24 @@ .signature-request-message { flex: 1 60%; - display: flex; max-height: 231px; - flex-direction: column; position: relative; - &__title { - @include H6; - - font-weight: 500; - color: var(--color-text-alternative); - margin-left: 12px; - } - - h2 { - @include H6; - - flex: 1 1 0; - text-align: left; - border-bottom: 1px solid var(--color-border-default); - padding: 0.5rem; - margin: 0; - color: var(--color-text-alternative); - } - - &--root { + &__root { flex: 1 100%; - background-color: var(--color-background-alternative); - padding-bottom: 0.5rem; overflow: auto; - padding-left: 12px; - padding-right: 12px; @include screen-sm-min { width: auto; } } - &--node, - &--node-leaf { - padding-left: 0.3rem; - - &-label { - color: var(--color-text-alternative); - margin-left: 0.5rem; - } - - &-value { - color: var(--color-text-default); - margin-left: 0.5rem; - white-space: pre-line; - overflow: hidden; - word-wrap: break-word; - } - } - - &--node-leaf { - display: flex; - } - &__scroll-button { - display: flex; - align-items: center; - justify-content: center; - border: 1px solid var(--color-border-default); - background: var(--color-background-alternative); - color: var(--color-icon-default); position: absolute; - right: 24px; + right: 28px; bottom: 12px; border-radius: 50%; + height: 24px; + width: 24px; cursor: pointer; } } diff --git a/ui/components/app/signature-request/signature-request-message/signature-request-message.component.js b/ui/components/app/signature-request/signature-request-message/signature-request-message.component.js deleted file mode 100644 index 641931919..000000000 --- a/ui/components/app/signature-request/signature-request-message/signature-request-message.component.js +++ /dev/null @@ -1,103 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { debounce } from 'lodash'; -import classnames from 'classnames'; - -export default class SignatureRequestMessage extends PureComponent { - static propTypes = { - data: PropTypes.object.isRequired, - onMessageScrolled: PropTypes.func, - setMessageRootRef: PropTypes.func, - messageRootRef: PropTypes.object, - messageIsScrollable: PropTypes.bool, - }; - - static contextTypes = { - t: PropTypes.func, - }; - - state = { - messageIsScrolled: false, - }; - - setMessageIsScrolled = () => { - if (!this.props.messageRootRef || this.state.messageIsScrolled) { - return; - } - - const { scrollTop, offsetHeight, scrollHeight } = this.props.messageRootRef; - const isAtBottom = Math.round(scrollTop) + offsetHeight >= scrollHeight; - - if (isAtBottom) { - this.setState({ messageIsScrolled: true }); - this.props.onMessageScrolled(); - } - }; - - onScroll = debounce(this.setMessageIsScrolled, 25); - - renderNode(data) { - return ( -
- {Object.entries(data).map(([label, value], i) => ( -
- - {label}:{' '} - - {typeof value === 'object' && value !== null ? ( - this.renderNode(value) - ) : ( - - {`${value}`} - - )} -
- ))} -
- ); - } - - renderScrollButton() { - return ( -
{ - this.setState({ messageIsScrolled: true }); - this.props.onMessageScrolled(); - this.props.messageRootRef.scrollTo( - 0, - this.props.messageRootRef.scrollHeight, - ); - }} - className="signature-request-message__scroll-button" - data-testid="signature-request-scroll-button" - > - -
- ); - } - - render() { - const { data, messageIsScrollable } = this.props; - - return ( -
- {messageIsScrollable ? this.renderScrollButton() : null} -
- {this.context.t('signatureRequest1')} -
-
- {this.renderNode(data)} -
-
- ); - } -} diff --git a/ui/components/app/signature-request/signature-request-message/signature-request-message.js b/ui/components/app/signature-request/signature-request-message/signature-request-message.js new file mode 100644 index 000000000..e86dfba3d --- /dev/null +++ b/ui/components/app/signature-request/signature-request-message/signature-request-message.js @@ -0,0 +1,98 @@ +import React, { useContext, useState } from 'react'; +import PropTypes from 'prop-types'; +import { debounce } from 'lodash'; +import { I18nContext } from '../../../../contexts/i18n'; +import Box from '../../../ui/box'; +import Typography from '../../../ui/typography'; +import { + DISPLAY, + ALIGN_ITEMS, + JUSTIFY_CONTENT, + COLORS, + FONT_WEIGHT, + FLEX_DIRECTION, + SIZES, +} from '../../../../helpers/constants/design-system'; +import SignatureRequestData from '../signature-request-data'; + +export default function SignatureRequestMessage({ + data, + onMessageScrolled, + setMessageRootRef, + messageRootRef, + messageIsScrollable, +}) { + const t = useContext(I18nContext); + const [messageIsScrolled, setMessageIsScrolled] = useState(false); + const setMessageIsScrolledAtBottom = () => { + if (!messageRootRef || messageIsScrolled) { + return; + } + + const { scrollTop, offsetHeight, scrollHeight } = messageRootRef; + const isAtBottom = Math.round(scrollTop) + offsetHeight >= scrollHeight; + + if (isAtBottom) { + setMessageIsScrolled(true); + onMessageScrolled(); + } + }; + + return ( + + {messageIsScrollable ? ( + { + setMessageIsScrolled(true); + onMessageScrolled(); + messageRootRef?.scrollTo(0, messageRootRef?.scrollHeight); + }} + className="signature-request-message__scroll-button" + data-testid="signature-request-scroll-button" + > + + + ) : null} + + + {t('signatureRequest1')} + + + + + ); +} + +SignatureRequestMessage.propTypes = { + data: PropTypes.object.isRequired, + onMessageScrolled: PropTypes.func, + setMessageRootRef: PropTypes.func, + messageRootRef: PropTypes.object, + messageIsScrollable: PropTypes.bool, +}; diff --git a/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js b/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js new file mode 100644 index 000000000..9d3743af0 --- /dev/null +++ b/ui/components/app/signature-request/signature-request-message/signature-request-message.stories.js @@ -0,0 +1,58 @@ +import React from 'react'; +import SignatureRequestMessage from './signature-request-message'; + +export default { + title: 'Components/App/SignatureRequestMessage', + id: __filename, + component: SignatureRequestMessage, + argTypes: { + data: { control: 'object' }, + onMessageScrolled: { action: 'onMessageScrolled' }, + setMessageRootRef: { action: 'setMessageRootRef' }, + messageRootRef: { control: 'object' }, + messageIsScrollable: { control: 'boolean' }, + }, +}; + +export const DefaultStory = (args) => { + return ; +}; + +DefaultStory.storyName = 'Default'; + +DefaultStory.args = { + data: JSON.parse( + JSON.stringify({ + domain: { + name: 'happydapp.website', + }, + message: { + string: 'haay wuurl', + number: 42, + }, + primaryType: 'Mail', + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, + }), + ), + messageIsScrollable: true, +}; diff --git a/ui/components/app/signature-request/signature-request.container.test.js b/ui/components/app/signature-request/signature-request.container.test.js index 551e9645e..085b117d4 100644 --- a/ui/components/app/signature-request/signature-request.container.test.js +++ b/ui/components/app/signature-request/signature-request.container.test.js @@ -8,6 +8,46 @@ import SignatureRequest from './signature-request.container'; describe('Signature Request', () => { const mockStore = { metamask: { + tokenList: { + '0x514910771af9ca656af840dff83e8264ecf986ca': { + address: '0x514910771af9ca656af840dff83e8264ecf986ca', + symbol: 'LINK', + decimals: 18, + name: 'ChainLink Token', + iconUrl: + 'https://crypto.com/price/coin-data/icon/LINK/color_icon.png', + aggregators: [ + 'Aave', + 'Bancor', + 'CMC', + 'Crypto.com', + 'CoinGecko', + '1inch', + 'Paraswap', + 'PMM', + 'Zapper', + 'Zerion', + '0x', + ], + occurrences: 12, + unlisted: false, + }, + }, + identities: { + '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e': { + name: 'Account 2', + address: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e', + }, + }, + addressBook: { + undefined: { + 0: { + address: '0x39a4e4Af7cCB654dB9500F258c64781c8FbD39F0', + name: '', + isEns: false, + }, + }, + }, provider: { type: 'rpc', }, diff --git a/ui/components/app/transaction-decoding/components/decoding/address/address.component.js b/ui/components/app/transaction-decoding/components/decoding/address/address.component.js index 7e6358634..ddb4d5cd7 100644 --- a/ui/components/app/transaction-decoding/components/decoding/address/address.component.js +++ b/ui/components/app/transaction-decoding/components/decoding/address/address.component.js @@ -5,7 +5,10 @@ import copyToClipboard from 'copy-to-clipboard'; import { shortenAddress } from '../../../../../../helpers/utils/util'; import Identicon from '../../../../../ui/identicon'; import { useI18nContext } from '../../../../../../hooks/useI18nContext'; -import { getAddressBook } from '../../../../../../selectors'; +import { + getMetadataContractName, + getAddressBook, +} from '../../../../../../selectors'; import NicknamePopovers from '../../../../modals/nickname-popovers'; const Address = ({ @@ -20,15 +23,25 @@ const Address = ({ const addressBook = useSelector(getAddressBook); const addressBookEntryObject = addressBook.find( - (entry) => entry.address === checksummedRecipientAddress, + (entry) => + entry.address.toLowerCase() === checksummedRecipientAddress.toLowerCase(), ); const recipientNickname = addressBookEntryObject?.name; + const recipientMetadataName = useSelector((state) => + getMetadataContractName(state, checksummedRecipientAddress), + ); const recipientToRender = addressOnly - ? recipientNickname || + ? recipientName || + recipientNickname || + recipientMetadataName || recipientEns || shortenAddress(checksummedRecipientAddress) - : recipientNickname || recipientEns || recipientName || t('newContract'); + : recipientName || + recipientNickname || + recipientMetadataName || + recipientEns || + t('newContract'); return (
{ recipientEns = getEnsResolutionByAddress(state, address); } const addressBook = getAddressBook(state); - const recipientName = getAccountName(state, recipientAddress); - const recipientMetadataName = getMetadataContractName(recipientAddress); + const identities = getMetaMaskIdentities(state); + const recipientName = getAccountName(identities, recipientAddress); + const recipientMetadataName = getMetadataContractName( + state, + recipientAddress, + ); const getNickName = (address) => { const entry = addressBook.find((contact) => { diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 7356a1dfc..a4ce54be8 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -412,15 +412,16 @@ export function getAddressBookEntryOrAccountName(state, address) { return entry && entry.name !== '' ? entry.name : address; } -export function getAccountName(state, address) { - const entry = Object.values(state.metamask.identities).find((identity) => +export function getAccountName(identities, address) { + const entry = Object.values(identities).find((identity) => isEqualCaseInsensitive(identity.address, toChecksumHexAddress(address)), ); return entry && entry.name !== '' ? entry.name : ''; } -export function getMetadataContractName(address) { - const entry = Object.values(STATIC_MAINNET_TOKEN_LIST).find((identity) => +export function getMetadataContractName(state, address) { + const tokenList = getTokenList(state); + const entry = Object.values(tokenList).find((identity) => isEqualCaseInsensitive(identity.address, toChecksumHexAddress(address)), ); return entry && entry.name !== '' ? entry.name : '';