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