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 { |
||||
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; |
||||
} |
||||
} |
||||
|
@ -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