Replaced addresses by the address component on SignTypedData v4 signatures (#16018)
* Replaced addresses by the address component on SignTypedData v4 signatures * Fixing signature-request e2e tests * Modified scss file for signature-request message * Using address component for rendering the addresses and bold label where hex address is not valid * Modify the address component * Added proper BEM syntax for class names and used Box and Typography * FIxing e2e tests * Commited requested changes from George and added storybook * Review requested changes * Created new component for rendering data in signature-request-message.js * Fixing proper usage for getAccountName and getMetadataContractName selectors * Fixing e2e testsfeature/default_network_editable
parent
4a247a95cb
commit
42be5a07d7
@ -0,0 +1 @@ |
|||||||
|
export { default } from './signature-request-data'; |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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 ( |
||||||
|
<Box className="signature-request-data__node"> |
||||||
|
{Object.entries(data).map(([label, value], i) => ( |
||||||
|
<Box |
||||||
|
className="signature-request-data__node" |
||||||
|
key={i} |
||||||
|
paddingLeft={2} |
||||||
|
display={ |
||||||
|
typeof value !== 'object' || value === null ? DISPLAY.FLEX : null |
||||||
|
} |
||||||
|
> |
||||||
|
<Typography |
||||||
|
as="span" |
||||||
|
color={COLORS.TEXT_DEFAULT} |
||||||
|
marginLeft={4} |
||||||
|
fontWeight={ |
||||||
|
typeof value === 'object' ? FONT_WEIGHT.BOLD : FONT_WEIGHT.NORMAL |
||||||
|
} |
||||||
|
> |
||||||
|
{label.charAt(0).toUpperCase() + label.slice(1)}:{' '} |
||||||
|
</Typography> |
||||||
|
{typeof value === 'object' && value !== null ? ( |
||||||
|
<SignatureRequestData data={value} /> |
||||||
|
) : ( |
||||||
|
<Typography |
||||||
|
as="span" |
||||||
|
color={COLORS.TEXT_DEFAULT} |
||||||
|
marginLeft={4} |
||||||
|
className="signature-request-data__node__value" |
||||||
|
> |
||||||
|
{isValidHexAddress(value, { |
||||||
|
mixedCaseUseChecksum: true, |
||||||
|
}) ? ( |
||||||
|
<Typography |
||||||
|
variant={TYPOGRAPHY.H7} |
||||||
|
color={COLORS.INFO_DEFAULT} |
||||||
|
className="signature-request-data__node__value__address" |
||||||
|
> |
||||||
|
<Address |
||||||
|
addressOnly |
||||||
|
checksummedRecipientAddress={toChecksumHexAddress(value)} |
||||||
|
recipientName={getAccountName(identities, value)} |
||||||
|
/> |
||||||
|
</Typography> |
||||||
|
) : ( |
||||||
|
`${value}` |
||||||
|
)} |
||||||
|
</Typography> |
||||||
|
)} |
||||||
|
</Box> |
||||||
|
))} |
||||||
|
</Box> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
SignatureRequestData.propTypes = { |
||||||
|
data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired, |
||||||
|
}; |
@ -1 +1 @@ |
|||||||
export { default } from './signature-request-message.component'; |
export { default } from './signature-request-message'; |
||||||
|
@ -1,75 +1,24 @@ |
|||||||
.signature-request-message { |
.signature-request-message { |
||||||
flex: 1 60%; |
flex: 1 60%; |
||||||
display: flex; |
|
||||||
max-height: 231px; |
max-height: 231px; |
||||||
flex-direction: column; |
|
||||||
position: relative; |
position: relative; |
||||||
|
|
||||||
&__title { |
&__root { |
||||||
@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 { |
|
||||||
flex: 1 100%; |
flex: 1 100%; |
||||||
background-color: var(--color-background-alternative); |
|
||||||
padding-bottom: 0.5rem; |
|
||||||
overflow: auto; |
overflow: auto; |
||||||
padding-left: 12px; |
|
||||||
padding-right: 12px; |
|
||||||
|
|
||||||
@include screen-sm-min { |
@include screen-sm-min { |
||||||
width: auto; |
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 { |
&__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; |
position: absolute; |
||||||
right: 24px; |
right: 28px; |
||||||
bottom: 12px; |
bottom: 12px; |
||||||
border-radius: 50%; |
border-radius: 50%; |
||||||
|
height: 24px; |
||||||
|
width: 24px; |
||||||
cursor: pointer; |
cursor: pointer; |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -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 ( |
|
||||||
<div className="signature-request-message--node"> |
|
||||||
{Object.entries(data).map(([label, value], i) => ( |
|
||||||
<div |
|
||||||
className={classnames('signature-request-message--node', { |
|
||||||
'signature-request-message--node-leaf': |
|
||||||
typeof value !== 'object' || value === null, |
|
||||||
})} |
|
||||||
key={i} |
|
||||||
> |
|
||||||
<span className="signature-request-message--node-label"> |
|
||||||
{label}:{' '} |
|
||||||
</span> |
|
||||||
{typeof value === 'object' && value !== null ? ( |
|
||||||
this.renderNode(value) |
|
||||||
) : ( |
|
||||||
<span className="signature-request-message--node-value"> |
|
||||||
{`${value}`} |
|
||||||
</span> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
))} |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
renderScrollButton() { |
|
||||||
return ( |
|
||||||
<div |
|
||||||
onClick={() => { |
|
||||||
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" |
|
||||||
> |
|
||||||
<i className="fa fa-arrow-down" title={this.context.t('scrollDown')} /> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
render() { |
|
||||||
const { data, messageIsScrollable } = this.props; |
|
||||||
|
|
||||||
return ( |
|
||||||
<div onScroll={this.onScroll} className="signature-request-message"> |
|
||||||
{messageIsScrollable ? this.renderScrollButton() : null} |
|
||||||
<div className="signature-request-message__title"> |
|
||||||
{this.context.t('signatureRequest1')} |
|
||||||
</div> |
|
||||||
<div |
|
||||||
className="signature-request-message--root" |
|
||||||
ref={this.props.setMessageRootRef} |
|
||||||
> |
|
||||||
{this.renderNode(data)} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -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 ( |
||||||
|
<Box |
||||||
|
display={DISPLAY.FLEX} |
||||||
|
flexDirection={FLEX_DIRECTION.COLUMN} |
||||||
|
onScroll={debounce(setMessageIsScrolledAtBottom, 25)} |
||||||
|
className="signature-request-message" |
||||||
|
> |
||||||
|
{messageIsScrollable ? ( |
||||||
|
<Box |
||||||
|
display={DISPLAY.FLEX} |
||||||
|
alignItems={ALIGN_ITEMS.CENTER} |
||||||
|
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||||
|
borderColor={COLORS.BORDER_DEFAULT} |
||||||
|
backgroundColor={COLORS.BACKGROUND_DEFAULT} |
||||||
|
color={COLORS.ICON_DEFAULT} |
||||||
|
onClick={() => { |
||||||
|
setMessageIsScrolled(true); |
||||||
|
onMessageScrolled(); |
||||||
|
messageRootRef?.scrollTo(0, messageRootRef?.scrollHeight); |
||||||
|
}} |
||||||
|
className="signature-request-message__scroll-button" |
||||||
|
data-testid="signature-request-scroll-button" |
||||||
|
> |
||||||
|
<i className="fa fa-arrow-down" aria-label={t('scrollDown')} /> |
||||||
|
</Box> |
||||||
|
) : null} |
||||||
|
<Box |
||||||
|
backgroundColor={COLORS.BACKGROUND_DEFAULT} |
||||||
|
paddingBottom={3} |
||||||
|
paddingTop={3} |
||||||
|
paddingRight={3} |
||||||
|
margin={2} |
||||||
|
borderRadius={SIZES.XL} |
||||||
|
borderColor={COLORS.BORDER_MUTED} |
||||||
|
className="signature-request-message__root" |
||||||
|
ref={setMessageRootRef} |
||||||
|
> |
||||||
|
<Typography |
||||||
|
fontWeight={FONT_WEIGHT.BOLD} |
||||||
|
color={COLORS.TEXT_DEFAULT} |
||||||
|
marginLeft={4} |
||||||
|
className="signature-request-message__title" |
||||||
|
> |
||||||
|
{t('signatureRequest1')} |
||||||
|
</Typography> |
||||||
|
<SignatureRequestData data={data} /> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
SignatureRequestMessage.propTypes = { |
||||||
|
data: PropTypes.object.isRequired, |
||||||
|
onMessageScrolled: PropTypes.func, |
||||||
|
setMessageRootRef: PropTypes.func, |
||||||
|
messageRootRef: PropTypes.object, |
||||||
|
messageIsScrollable: PropTypes.bool, |
||||||
|
}; |
@ -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 <SignatureRequestMessage {...args} />; |
||||||
|
}; |
||||||
|
|
||||||
|
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, |
||||||
|
}; |
Loading…
Reference in new issue