New signature request v3 UI (#6891)
* Refactoring signature-request out to a new component. Wip * Styling polish and a better message display. * Update signature request header to no longer use account dropdown mini * Clean up code and styles * Code cleanup for signature request redesign branch * Fix signature request design for full screen * Replace makenode with object.entries in signature-request-message.component.js * Remove unused accounts prop from signature-request.component.js * Use beforeunload instead of window.onbeforeunload in signature-requestfeature/default_network_editable
parent
eed4a9ed65
commit
57a29668f3
@ -0,0 +1 @@ |
|||||||
|
export { default } from './signature-request.container' |
@ -0,0 +1,96 @@ |
|||||||
|
@import 'signature-request-footer/index'; |
||||||
|
@import 'signature-request-header/index'; |
||||||
|
@import 'signature-request-message/index'; |
||||||
|
|
||||||
|
.signature-request { |
||||||
|
display: flex; |
||||||
|
flex: 1 1 auto; |
||||||
|
flex-direction: column; |
||||||
|
min-width: 0; |
||||||
|
|
||||||
|
@media screen and (min-width: 576px) { |
||||||
|
flex: initial; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.signature-request-header { |
||||||
|
flex: 1; |
||||||
|
|
||||||
|
.network-display__container { |
||||||
|
padding: 0; |
||||||
|
justify-content: flex-end; |
||||||
|
} |
||||||
|
|
||||||
|
.network-display__name { |
||||||
|
font-size: 12px; |
||||||
|
white-space: nowrap; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.signature-request-content { |
||||||
|
flex: 1 40%; |
||||||
|
margin-top: 1rem; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
flex-direction: column; |
||||||
|
margin-bottom: 25px; |
||||||
|
min-height: min-content; |
||||||
|
|
||||||
|
&__title { |
||||||
|
font-family: Roboto; |
||||||
|
font-style: normal; |
||||||
|
font-weight: 500; |
||||||
|
font-size: 18px; |
||||||
|
} |
||||||
|
|
||||||
|
&__identicon-container { |
||||||
|
padding: 1rem; |
||||||
|
flex: 1; |
||||||
|
position: relative; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__identicon-border { |
||||||
|
height: 75px; |
||||||
|
width: 75px; |
||||||
|
border-radius: 50%; |
||||||
|
border: 1px solid white; |
||||||
|
position: absolute; |
||||||
|
box-shadow: 0 2px 2px 0.5px rgba(0, 0, 0, 0.19); |
||||||
|
} |
||||||
|
|
||||||
|
&__identicon-initial { |
||||||
|
position: absolute; |
||||||
|
font-family: Roboto; |
||||||
|
font-style: normal; |
||||||
|
font-weight: 500; |
||||||
|
font-size: 60px; |
||||||
|
color: white; |
||||||
|
z-index: 1; |
||||||
|
text-shadow: 0px 4px 6px rgba(0, 0, 0, 0.422); |
||||||
|
} |
||||||
|
|
||||||
|
&__info { |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
&__info--bolded { |
||||||
|
font-size: 16px; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
color: #999999; |
||||||
|
font-size: 0.8rem; |
||||||
|
} |
||||||
|
|
||||||
|
.identicon {} |
||||||
|
} |
||||||
|
|
||||||
|
.signature-request-footer { |
||||||
|
flex: 1 1 auto; |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './signature-request-footer.component' |
@ -0,0 +1,18 @@ |
|||||||
|
.signature-request-footer { |
||||||
|
display: flex; |
||||||
|
border-top: 1px solid #d2d8dd; |
||||||
|
|
||||||
|
button { |
||||||
|
text-transform: uppercase; |
||||||
|
flex: 1; |
||||||
|
margin: 1rem 0.5rem; |
||||||
|
border-radius: 3px; |
||||||
|
} |
||||||
|
|
||||||
|
button:first-child() { |
||||||
|
margin-left: 1rem; |
||||||
|
} |
||||||
|
button:last-child() { |
||||||
|
margin-right: 1rem; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Button from '../../../ui/button' |
||||||
|
|
||||||
|
export default class SignatureRequestFooter extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
cancelAction: PropTypes.func.isRequired, |
||||||
|
signAction: PropTypes.func.isRequired, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { cancelAction, signAction } = this.props |
||||||
|
return ( |
||||||
|
<div className="signature-request-footer"> |
||||||
|
<Button onClick={cancelAction} type="default" large>{this.context.t('cancel')}</Button> |
||||||
|
<Button onClick={signAction} type="primary" large>{this.context.t('sign')}</Button> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './signature-request-header.component' |
@ -0,0 +1,25 @@ |
|||||||
|
.signature-request-header { |
||||||
|
display: flex; |
||||||
|
padding: 1rem; |
||||||
|
border-bottom: 1px solid $geyser; |
||||||
|
justify-content: space-between; |
||||||
|
font-size: .75rem; |
||||||
|
|
||||||
|
&--account, &--network { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
|
||||||
|
&--account { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
.account-list-item__account-name { |
||||||
|
font-size: 12px; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.account-list-item__top-row { |
||||||
|
margin: 0px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component' |
||||||
|
import NetworkDisplay from '../../network-display' |
||||||
|
|
||||||
|
export default class SignatureRequestHeader extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
selectedAccount: PropTypes.object.isRequired, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { selectedAccount } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="signature-request-header"> |
||||||
|
<div className="signature-request-header--account"> |
||||||
|
{selectedAccount && <AccountListItem |
||||||
|
displayBalance={false} |
||||||
|
account={selectedAccount} |
||||||
|
/>} |
||||||
|
{name} |
||||||
|
</div> |
||||||
|
<div className="signature-request-header--network"> |
||||||
|
<NetworkDisplay colored={false} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './signature-request-message.component' |
@ -0,0 +1,67 @@ |
|||||||
|
.signature-request-message { |
||||||
|
flex: 1 60%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
|
||||||
|
&__title { |
||||||
|
font-weight: 500; |
||||||
|
font-size: 14px; |
||||||
|
color: #636778; |
||||||
|
margin-left: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
h2 { |
||||||
|
flex: 1 1 0; |
||||||
|
text-align: left; |
||||||
|
font-size: 0.8rem; |
||||||
|
border-bottom: 1px solid #d2d8dd; |
||||||
|
padding: 0.5rem; |
||||||
|
margin: 0; |
||||||
|
color: #ccc; |
||||||
|
} |
||||||
|
|
||||||
|
&--root { |
||||||
|
flex: 1 100%; |
||||||
|
background-color: #f8f9fb; |
||||||
|
padding-bottom: 0.5rem; |
||||||
|
overflow: auto; |
||||||
|
padding-left: 12px; |
||||||
|
padding-right: 12px; |
||||||
|
width: 360px; |
||||||
|
font-family: monospace; |
||||||
|
|
||||||
|
@media screen and (min-width: 576px) { |
||||||
|
width: auto; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__type-title { |
||||||
|
font-family: monospace; |
||||||
|
font-style: normal; |
||||||
|
font-weight: normal; |
||||||
|
font-size: 14px; |
||||||
|
margin-left: 12px; |
||||||
|
margin-top: 6px; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
&--node, &--node-leaf { |
||||||
|
padding-left: 0.8rem; |
||||||
|
|
||||||
|
&-label { |
||||||
|
color: #5B5D67; |
||||||
|
} |
||||||
|
|
||||||
|
&-value { |
||||||
|
color: black; |
||||||
|
margin-left: 0.5rem; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--node-leaf { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
|
||||||
|
export default class SignatureRequestMessage extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
data: PropTypes.object.isRequired, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
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> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
render () { |
||||||
|
const { data } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="signature-request-message"> |
||||||
|
<div className="signature-request-message__title">{this.context.t('signatureRequest1')}</div> |
||||||
|
<div className="signature-request-message--root"> |
||||||
|
<div className="signature-request-message__type-title">{this.context.t('signatureRequest1')}</div> |
||||||
|
{this.renderNode(data)} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Header from './signature-request-header' |
||||||
|
import Footer from './signature-request-footer' |
||||||
|
import Message from './signature-request-message' |
||||||
|
import { ENVIRONMENT_TYPE_NOTIFICATION } from './signature-request.constants' |
||||||
|
import { getEnvironmentType } from '../../../../../app/scripts/lib/util' |
||||||
|
import Identicon from '../../ui/identicon' |
||||||
|
|
||||||
|
export default class SignatureRequest extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
txData: PropTypes.object.isRequired, |
||||||
|
selectedAccount: PropTypes.shape({ |
||||||
|
address: PropTypes.string, |
||||||
|
balance: PropTypes.string, |
||||||
|
name: PropTypes.string, |
||||||
|
}).isRequired, |
||||||
|
|
||||||
|
clearConfirmTransaction: PropTypes.func.isRequired, |
||||||
|
cancel: PropTypes.func.isRequired, |
||||||
|
sign: PropTypes.func.isRequired, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount () { |
||||||
|
const { clearConfirmTransaction, cancel } = this.props |
||||||
|
const { metricsEvent } = this.context |
||||||
|
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { |
||||||
|
window.addEventListener('beforeunload', (event) => { |
||||||
|
metricsEvent({ |
||||||
|
eventOpts: { |
||||||
|
category: 'Transactions', |
||||||
|
action: 'Sign Request', |
||||||
|
name: 'Cancel Sig Request Via Notification Close', |
||||||
|
}, |
||||||
|
}) |
||||||
|
clearConfirmTransaction() |
||||||
|
cancel(event) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
formatWallet (wallet) { |
||||||
|
return `${wallet.slice(0, 8)}...${wallet.slice(wallet.length - 8, wallet.length)}` |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
selectedAccount, |
||||||
|
txData: { msgParams: { data, origin, from: senderWallet }}, |
||||||
|
cancel, |
||||||
|
sign, |
||||||
|
} = this.props |
||||||
|
const { message } = JSON.parse(data) |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="signature-request page-container"> |
||||||
|
<Header selectedAccount={selectedAccount} /> |
||||||
|
<div className="signature-request-content"> |
||||||
|
<div className="signature-request-content__title">{this.context.t('sigRequest')}</div> |
||||||
|
<div className="signature-request-content__identicon-container"> |
||||||
|
<div className="signature-request-content__identicon-initial" >{ message.from.name && message.from.name[0] }</div> |
||||||
|
<div className="signature-request-content__identicon-border" /> |
||||||
|
<Identicon |
||||||
|
address={message.from.wallet} |
||||||
|
diameter={70} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="signature-request-content__info--bolded">{message.from.name}</div> |
||||||
|
<div className="signature-request-content__info">{origin}</div> |
||||||
|
<div className="signature-request-content__info">{this.formatWallet(senderWallet)}</div> |
||||||
|
</div> |
||||||
|
<Message data={message} /> |
||||||
|
<Footer cancelAction={cancel} signAction={sign} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' |
||||||
|
|
||||||
|
export { ENVIRONMENT_TYPE_NOTIFICATION } |
@ -0,0 +1,72 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import SignatureRequest from './signature-request.component' |
||||||
|
import { goHome } from '../../../store/actions' |
||||||
|
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck' |
||||||
|
import { |
||||||
|
getSelectedAccount, |
||||||
|
getCurrentAccountWithSendEtherInfo, |
||||||
|
getSelectedAddress, |
||||||
|
accountsWithSendEtherInfoSelector, |
||||||
|
conversionRateSelector, |
||||||
|
} from '../../../selectors/selectors.js' |
||||||
|
|
||||||
|
function mapStateToProps (state) { |
||||||
|
return { |
||||||
|
balance: getSelectedAccount(state).balance, |
||||||
|
selectedAccount: getCurrentAccountWithSendEtherInfo(state), |
||||||
|
selectedAddress: getSelectedAddress(state), |
||||||
|
accounts: accountsWithSendEtherInfoSelector(state), |
||||||
|
conversionRate: conversionRateSelector(state), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) { |
||||||
|
return { |
||||||
|
goHome: () => dispatch(goHome()), |
||||||
|
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function mergeProps (stateProps, dispatchProps, ownProps) { |
||||||
|
const { |
||||||
|
signPersonalMessage, |
||||||
|
signTypedMessage, |
||||||
|
cancelPersonalMessage, |
||||||
|
cancelTypedMessage, |
||||||
|
signMessage, |
||||||
|
cancelMessage, |
||||||
|
txData, |
||||||
|
} = ownProps |
||||||
|
|
||||||
|
const { type } = txData |
||||||
|
|
||||||
|
let cancel |
||||||
|
let sign |
||||||
|
|
||||||
|
if (type === 'personal_sign') { |
||||||
|
cancel = cancelPersonalMessage |
||||||
|
sign = signPersonalMessage |
||||||
|
} else if (type === 'eth_signTypedData') { |
||||||
|
cancel = cancelTypedMessage |
||||||
|
sign = signTypedMessage |
||||||
|
} else if (type === 'eth_sign') { |
||||||
|
cancel = cancelMessage |
||||||
|
sign = signMessage |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
...stateProps, |
||||||
|
...dispatchProps, |
||||||
|
...ownProps, |
||||||
|
txData, |
||||||
|
cancel, |
||||||
|
sign, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps, mergeProps) |
||||||
|
)(SignatureRequest) |
@ -0,0 +1,25 @@ |
|||||||
|
import React from 'react' |
||||||
|
import assert from 'assert' |
||||||
|
import shallow from '../../../../../lib/shallow-with-context' |
||||||
|
import SignatureRequest from '../signature-request.component' |
||||||
|
|
||||||
|
|
||||||
|
describe('Signature Request Component', function () { |
||||||
|
let wrapper |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
wrapper = shallow(<SignatureRequest txData={{ |
||||||
|
msgParams: { |
||||||
|
data: '{"message": {"from": {"name": "hello"}}}', |
||||||
|
from: '0x123456789abcdef', |
||||||
|
} }} />) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('render', () => { |
||||||
|
it('should render a div with one child', () => { |
||||||
|
assert(wrapper.is('div')) |
||||||
|
assert.equal(wrapper.length, 1) |
||||||
|
assert(wrapper.hasClass('signature-request')) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
Loading…
Reference in new issue