Skip to content

Commit

Permalink
Redesign approve screen
Browse files Browse the repository at this point in the history
  • Loading branch information
danjm committed Oct 10, 2019
1 parent 8b5ac93 commit f7a19e0
Show file tree
Hide file tree
Showing 25 changed files with 1,222 additions and 55 deletions.
3 changes: 3 additions & 0 deletions app/images/user-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 42 additions & 27 deletions test/e2e/metamask-ui.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1188,8 +1188,8 @@ describe('MetaMask', function () {
await driver.switchTo().window(dapp)
await delay(tinyDelayMs)

const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`))
await transferTokens.click()
const approveTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`))
await approveTokens.click()

if (process.env.SELENIUM_BROWSER !== 'firefox') {
await closeAllWindowHandlesExcept(driver, [extension, dapp])
Expand All @@ -1210,31 +1210,22 @@ describe('MetaMask', function () {
})

it('displays the token approval data', async () => {
const dataTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Data')]`))
dataTab.click()
const fullTxDataButton = await findElement(driver, By.css('.confirm-approve-content__view-full-tx-button'))
await fullTxDataButton.click()
await delay(regularDelayMs)

const functionType = await findElement(driver, By.css('.confirm-page-container-content__function-type'))
const functionType = await findElement(driver, By.css('.confirm-approve-content__data .confirm-approve-content__small-text'))
const functionTypeText = await functionType.getText()
assert.equal(functionTypeText, 'Approve')
assert.equal(functionTypeText, 'Function: Approve')

const confirmDataDiv = await findElement(driver, By.css('.confirm-page-container-content__data-box'))
const confirmDataDiv = await findElement(driver, By.css('.confirm-approve-content__data__data-block'))
const confirmDataText = await confirmDataDiv.getText()
assert(confirmDataText.match(/0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef4/))

const detailsTab = await findElement(driver, By.xpath(`//li[contains(text(), 'Details')]`))
detailsTab.click()
await delay(regularDelayMs)

const approvalWarning = await findElement(driver, By.css('.confirm-page-container-warning__warning'))
const approvalWarningText = await approvalWarning.getText()
assert(approvalWarningText.match(/By approving this/))
await delay(regularDelayMs)
})

it('opens the gas edit modal', async () => {
const configureGas = await driver.wait(until.elementLocated(By.css('.confirm-detail-row__header-text--edit')))
await configureGas.click()
const editButtons = await findElements(driver, By.css('.confirm-approve-content__small-blue-text.cursor-pointer'))
await editButtons[0].click()
await delay(regularDelayMs)

gasModal = await driver.findElement(By.css('span .modal'))
Expand Down Expand Up @@ -1276,14 +1267,34 @@ describe('MetaMask', function () {
await save.click()
await driver.wait(until.stalenessOf(gasModal))

const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
const gasFeeInEth = await findElement(driver, By.css('.confirm-approve-content__transaction-details-content__secondary-fee'))
assert.equal(await gasFeeInEth.getText(), '0.0006')
})

it('shows the correct recipient', async function () {
const senderToRecipientDivs = await findElements(driver, By.css('.sender-to-recipient__name'))
const recipientDiv = senderToRecipientDivs[1]
assert.equal(await recipientDiv.getText(), '0x9bc5...fEF4')
it('edits the permission', async () => {
const permissionModal = await driver.findElement(By.css('span .modal'))

const editButtons = await findElements(driver, By.css('.confirm-approve-content__small-blue-text.cursor-pointer'))
await editButtons[1].click()
await delay(regularDelayMs)

const radioButtons = await findElements(driver, By.css('.edit-approval-permission__edit-section__radio-button'))
await radioButtons[1].click()

const customInput = await findElement(driver, By.css('input'))
await delay(50)
await customInput.sendKeys('5')
await delay(regularDelayMs)

const saveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await saveButton.click()
await delay(regularDelayMs)

await driver.wait(until.stalenessOf(permissionModal))

const permissionInfo = await findElements(driver, By.css('.confirm-approve-content__medium-text'))
const amountDiv = permissionInfo[0]
assert.equal(await amountDiv.getText(), '5 DAI')
})

it('submits the transaction', async function () {
Expand Down Expand Up @@ -1401,9 +1412,13 @@ describe('MetaMask', function () {
})

it('shows the correct recipient', async function () {
const senderToRecipientDivs = await findElements(driver, By.css('.sender-to-recipient__name'))
const recipientDiv = senderToRecipientDivs[1]
assert.equal(await recipientDiv.getText(), 'Account 2')
const fullTxDataButton = await findElement(driver, By.css('.confirm-approve-content__view-full-tx-button'))
await fullTxDataButton.click()
await delay(regularDelayMs)

const permissionInfo = await findElements(driver, By.css('.confirm-approve-content__medium-text'))
const recipientDiv = permissionInfo[1]
assert.equal(await recipientDiv.getText(), '0x2f318C33...C970')
})

it('submits the transaction', async function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

.confirm-page-container-content {
overflow-y: auto;
height: 100%;
flex: 1;

&__error-container {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default class ConfirmPageContainer extends Component {
subtitleComponent: PropTypes.node,
title: PropTypes.string,
titleComponent: PropTypes.node,
hideSenderToRecipient: PropTypes.bool,
// Sender to Recipient
fromAddress: PropTypes.string,
fromName: PropTypes.string,
Expand Down Expand Up @@ -102,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
lastTx,
ofText,
requestsWaitingText,
hideSenderToRecipient,
} = this.props
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)

Expand All @@ -123,14 +125,17 @@ export default class ConfirmPageContainer extends Component {
showEdit={showEdit}
onEdit={() => onEdit()}
>
<SenderToRecipient
senderName={fromName}
senderAddress={fromAddress}
recipientName={toName}
recipientAddress={toAddress}
recipientNickname={toNickname}
assetImage={renderAssetImage ? assetImage : undefined}
/>
{ hideSenderToRecipient
? null
: <SenderToRecipient
senderName={fromName}
senderAddress={fromAddress}
recipientName={toName}
recipientAddress={toAddress}
recipientNickname={toNickname}
assetImage={renderAssetImage ? assetImage : undefined}
/>
}
</ConfirmPageContainerHeader>
{
contentComponent || (
Expand Down
6 changes: 6 additions & 0 deletions ui/app/components/app/confirm-page-container/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
@import 'confirm-detail-row/index';

@import 'confirm-page-container-navigation/index';

.page-container {
&__content-component-wrapper {
height: 100%;
}
}
9 changes: 7 additions & 2 deletions ui/app/components/app/modal/modal.component.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Button from '../../ui/button'
import classnames from 'classnames'

export default class Modal extends PureComponent {
static propTypes = {
children: PropTypes.node,
contentClass: PropTypes.string,
containerClass: PropTypes.string,
// Header text
headerText: PropTypes.string,
onClose: PropTypes.func,
Expand Down Expand Up @@ -36,10 +39,12 @@ export default class Modal extends PureComponent {
onCancel,
cancelType,
cancelText,
contentClass,
containerClass,
} = this.props

return (
<div className="modal-container">
<div className={classnames('modal-container', containerClass)}>
{
headerText && (
<div className="modal-container__header">
Expand All @@ -53,7 +58,7 @@ export default class Modal extends PureComponent {
</div>
)
}
<div className="modal-container__content">
<div className={classnames('modal-container__content', contentClass)}>
{ children }
</div>
<div className="modal-container__footer">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Modal from '../../modal'
import Identicon from '../../../ui/identicon'
import TextField from '../../../ui/text-field'
import classnames from 'classnames'

export default class EditApprovalPermission extends PureComponent {
static propTypes = {
hideModal: PropTypes.func.isRequired,
selectedIdentity: PropTypes.object,
tokenAmount: PropTypes.string,
customTokenAmount: PropTypes.string,
tokenSymbol: PropTypes.string,
tokenBalance: PropTypes.string,
setCustomAmount: PropTypes.func,
}

static contextTypes = {
t: PropTypes.func,
}

state = {
customSpendLimit: this.props.customTokenAmount,
selectedOption: this.props.customTokenAmount ? 'custom' : 'unlimited',
}

renderModalContent () {
const {
hideModal,
selectedIdentity,
tokenAmount,
tokenSymbol,
tokenBalance,
customTokenAmount,
} = this.props
const { name, address } = selectedIdentity || {}

return (
<div className="edit-approval-permission">
<div className="edit-approval-permission__header">
<div className="edit-approval-permission__title">
Edit Permission
</div>
<div
className="edit-approval-permission__header__close"
onClick={() => hideModal()}
/>
</div>
<div className="edit-approval-permission__account-info">
<div className="edit-approval-permission__account-info__account">
<Identicon
address={address}
diameter={32}
/>
<div className="edit-approval-permission__account-info__name">{ name }</div>
<div>Balance</div>
</div>
<div className="edit-approval-permission__account-info__balance">
{`${tokenBalance} ${tokenSymbol}`}
</div>
</div>
<div className="edit-approval-permission__edit-section">
<div className="edit-approval-permission__edit-section__title">
Spend limit permission
</div>
<div className="edit-approval-permission__edit-section__description">
Allow Uniswap to withdraw and spend up to the following amount:
</div>
<div className="edit-approval-permission__edit-section__option">
<div
className="edit-approval-permission__edit-section__radio-button"
onClick={() => this.setState({ selectedOption: 'unlimited' })}
>
<div className={classnames({
'edit-approval-permission__edit-section__radio-button-outline': this.state.selectedOption !== 'unlimited',
'edit-approval-permission__edit-section__radio-button-outline--selected': this.state.selectedOption === 'unlimited',
})} />
<div className="edit-approval-permission__edit-section__radio-button-fill" />
{this.state.selectedOption === 'unlimited' && <div className="edit-approval-permission__edit-section__radio-button-dot" />}
</div>
<div className="edit-approval-permission__edit-section__option-text">
<div className={classnames({
'edit-approval-permission__edit-section__option-label': this.state.selectedOption !== 'unlimited',
'edit-approval-permission__edit-section__option-label--selected': this.state.selectedOption === 'unlimited',
})}>
{
tokenAmount < tokenBalance
? -'Proposed Approval Limit'
: 'Unlimited'
}
</div>
<div className="edit-approval-permission__edit-section__option-description" >
Spend limit requested by Uniswap
</div>
<div className="edit-approval-permission__edit-section__option-value" >
{`${tokenAmount} ${tokenSymbol}`}
</div>
</div>
</div>
<div className="edit-approval-permission__edit-section__option">
<div
className="edit-approval-permission__edit-section__radio-button"
onClick={() => this.setState({ selectedOption: 'custom' })}
>
<div className={classnames({
'edit-approval-permission__edit-section__radio-button-outline': this.state.selectedOption !== 'custom',
'edit-approval-permission__edit-section__radio-button-outline--selected': this.state.selectedOption === 'custom',
})} />
<div className="edit-approval-permission__edit-section__radio-button-fill" />
{this.state.selectedOption === 'custom' && <div className="edit-approval-permission__edit-section__radio-button-dot" />}
</div>
<div className="edit-approval-permission__edit-section__option-text">
<div className={classnames({
'edit-approval-permission__edit-section__option-label': this.state.selectedOption !== 'custom',
'edit-approval-permission__edit-section__option-label--selected': this.state.selectedOption === 'custom',
})}>
Custom spend limit
</div>
<div className="edit-approval-permission__edit-section__option-description" >
Enter a max spend limit
</div>
<div className="edit-approval-permission__edit-section__option-input" >
<TextField
type="number"
min="0"
placeholder={ `${customTokenAmount || tokenAmount} ${tokenSymbol}` }
onChange={(event) => this.setState({ customSpendLimit: event.target.value })}
fullWidth
margin="dense"
value={ this.state.customSpendLimit }
/>
</div>
</div>
</div>
</div>
</div>
)
}

render () {
const { t } = this.context
const { setCustomAmount, hideModal } = this.props

return (
<Modal
onSubmit={() => {
setCustomAmount(Number(this.state.customSpendLimit))
hideModal()
}}
submitText={t('save')}
submitType="primary"
contentClass="edit-approval-permission-modal-content"
containerClass="edit-approval-permission-modal-container"
submitDisabled={ this.state.customSpendLimit === this.props.customTokenAmount }
>
{ this.renderModalContent() }
</Modal>
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import EditApprovalPermission from './edit-approval-permission.component'
import { resetAccount } from '../../../../store/actions'
import { getSelectedIdentity } from '../../../../selectors/selectors'

const mapStateToProps = (state) => {
const modalStateProps = state.appState.modal.modalState.props || {}
return {
selectedIdentity: getSelectedIdentity(state),
...modalStateProps,
}
}


const mapDispatchToProps = dispatch => {
return {
resetAccount: () => dispatch(resetAccount()),
}
}

export default compose(
withModalProps,
connect(mapStateToProps, mapDispatchToProps)
)(EditApprovalPermission)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './edit-approval-permission.container'
Loading

0 comments on commit f7a19e0

Please sign in to comment.