From 2673eef3c4f88876d095d0e20e0e21bc312a2af7 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 5 Nov 2019 11:43:48 -0330 Subject: [PATCH] Redesign approve screen (#7271) * Redesign approve screen * Add translations to approve screen components * Show account in header of approve screen * Use state prop bool for unlimited vs custom check in edit-approval-permission * Set option to custom on input change in edit-approval-permission * Allow setting of approval amount to unlimited in edit-approval-permission * Fix height of confirm-approval popup * Ensure decimals prop passted to confirm-approve.component is correct type * Ensure first param passed to calcTokenValue in confirm-approve.util is the correct type * Fix e2e test of permission editing * Remove unused code from edit-approval-permission.container --- app/_locales/en/messages.json | 50 +++ app/images/user-check.svg | 3 + test/e2e/metamask-ui.spec.js | 71 ++-- .../confirm-page-container-content/index.scss | 1 + ...confirm-page-container-header.component.js | 53 ++- .../confirm-page-container-header/index.scss | 13 + .../confirm-page-container.component.js | 27 +- .../app/confirm-page-container/index.scss | 6 + .../components/app/modal/modal.component.js | 9 +- .../edit-approval-permission.component.js | 170 ++++++++++ .../edit-approval-permission.container.js | 18 ++ .../modals/edit-approval-permission/index.js | 1 + .../edit-approval-permission/index.scss | 167 ++++++++++ ui/app/components/app/modals/index.scss | 2 + ui/app/components/app/modals/modal.js | 26 ++ ui/app/css/itcss/tools/utilities.scss | 4 +- .../with-token-tracker.component.js | 9 +- ui/app/helpers/utils/token-util.js | 5 + .../confirm-approve-content.component.js | 223 +++++++++++++ .../confirm-approve-content/index.js | 1 + .../confirm-approve-content/index.scss | 306 ++++++++++++++++++ .../confirm-approve.component.js | 99 +++++- .../confirm-approve.container.js | 97 +++++- .../confirm-approve/confirm-approve.util.js | 28 ++ ui/app/pages/confirm-approve/index.scss | 1 + .../confirm-transaction-base.component.js | 6 + .../confirm-transaction-base.container.js | 15 +- ui/app/pages/index.scss | 2 + 28 files changed, 1340 insertions(+), 73 deletions(-) create mode 100644 app/images/user-check.svg create mode 100644 ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js create mode 100644 ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js create mode 100644 ui/app/components/app/modals/edit-approval-permission/index.js create mode 100644 ui/app/components/app/modals/edit-approval-permission/index.scss create mode 100644 ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js create mode 100644 ui/app/pages/confirm-approve/confirm-approve-content/index.js create mode 100644 ui/app/pages/confirm-approve/confirm-approve-content/index.scss create mode 100644 ui/app/pages/confirm-approve/confirm-approve.util.js create mode 100644 ui/app/pages/confirm-approve/index.scss diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index c6b0063e4..53d534a72 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -56,6 +56,10 @@ "acceleratingATransaction": { "message": "* Accelerating a transaction by using a higher gas price increases its chances of getting processed by the network faster, but it is not always guaranteed." }, + "accessAndSpendNotice": { + "message": "$1 may access and spend up to this max amount", + "description": "$1 is the url of the site requesting ability to spend" + }, "accessingYourCamera": { "message": "Accessing your camera..." }, @@ -113,9 +117,20 @@ "addAcquiredTokens": { "message": "Add the tokens you've acquired using MetaMask" }, + "allowOriginSpendToken": { + "message": "Allow $1 to spend your $2?", + "description": "$1 is the url of the site and $2 is the symbol of the token they are requesting to spend" + }, + "allowWithdrawAndSpend": { + "message": "Allow $1 to withdraw and spend up to the following amount:", + "description": "The url of the site that requested permission to 'withdraw and spend'" + }, "amount": { "message": "Amount" }, + "amountWithColon": { + "message": "Amount:" + }, "appDescription": { "message": "An Ethereum Wallet in your Browser", "description": "The description of the application" @@ -384,6 +399,9 @@ "customRPC": { "message": "Custom RPC" }, + "customSpendLimit": { + "message": "Custom Spend Limit" + }, "dataBackupFoundInfo": { "message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts and tokens. Would you like to restore this data now?" }, @@ -444,6 +462,9 @@ "editContact": { "message": "Edit Contact" }, + "editPermission": { + "message": "Edit Permission" + }, "emailUs": { "message": "Email us!" }, @@ -486,6 +507,9 @@ "enterAnAlias": { "message": "Enter an alias" }, + "enterMaxSpendLimit": { + "message": "Enter Max Spend Limit" + }, "enterPassword": { "message": "Enter password" }, @@ -516,6 +540,9 @@ "faster": { "message": "Faster" }, + "feeAssociatedRequest": { + "message": "A fee is associated with this request." + }, "fiat": { "message": "Fiat", "description": "Exchange type" @@ -533,6 +560,9 @@ "fromShapeShift": { "message": "From ShapeShift" }, + "functionApprove": { + "message": "Function: Approve" + }, "functionType": { "message": "Function Type" }, @@ -953,6 +983,9 @@ "privateNetwork": { "message": "Private Network" }, + "proposedApprovalLimit": { + "message": "Proposed Approval Limit" + }, "qrCode": { "message": "Show QR Code" }, @@ -1212,6 +1245,13 @@ "speedUpTransaction": { "message": "Speed up this transaction" }, + "spendLimitPermission": { + "message": "Spend limit permission" + }, + "spendLimitRequestedBy": { + "message": "Spend limit requested by $1", + "description": "Origin of the site requesting the spend limit" + }, "switchNetworks": { "message": "Switch Networks" }, @@ -1308,6 +1348,9 @@ "to": { "message": "To" }, + "toWithColon": { + "message": "To:" + }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", "description": "system will fill in deposit type in start of message" @@ -1382,6 +1425,10 @@ "message": "We had trouble loading your token balances. You can view them ", "description": "Followed by a link (here) to view token balances" }, + "trustSiteApprovePermission": { + "message": "Do you trust this site? By granting this permission, you’re allowing $1 to withdraw your $2 and automate transactions for you.", + "description": "$1 is the url requesting permission and $2 is the symbol of the currency that the request is for" + }, "tryAgain": { "message": "Try again" }, @@ -1409,6 +1456,9 @@ "unknownCameraError": { "message": "There was an error while trying to access your camera. Please try again..." }, + "unlimited": { + "message": "Unlimited" + }, "unlock": { "message": "Unlock" }, diff --git a/app/images/user-check.svg b/app/images/user-check.svg new file mode 100644 index 000000000..8ba739338 --- /dev/null +++ b/app/images/user-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 868028cd1..143e274cd 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -1124,8 +1124,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() await driver.switchTo().window(extension) await delay(regularDelayMs) @@ -1143,31 +1143,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')) @@ -1198,14 +1189,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 editButtons = await findElements(driver, By.css('.confirm-approve-content__small-blue-text.cursor-pointer')) + await editButtons[1].click() + await delay(regularDelayMs) + + const permissionModal = await driver.findElement(By.css('span .modal')) + + 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 TST') }) it('submits the transaction', async function () { @@ -1221,7 +1232,7 @@ describe('MetaMask', function () { }, 10000) const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) - await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/)) + await driver.wait(until.elementTextMatches(txValues[0], /-5\s*TST/)) const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/)) }) @@ -1305,9 +1316,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 () { diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss index 602a46848..ebc252e73 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/index.scss @@ -4,6 +4,7 @@ .confirm-page-container-content { overflow-y: auto; + height: 100%; flex: 1; &__error-container { diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index 4314d21eb..898d59068 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -5,6 +5,8 @@ import { ENVIRONMENT_TYPE_NOTIFICATION, } from '../../../../../../app/scripts/lib/enums' import NetworkDisplay from '../../network-display' +import Identicon from '../../../ui/identicon' +import { addressSlicer } from '../../../../helpers/utils/util' export default class ConfirmPageContainerHeader extends Component { static contextTypes = { @@ -12,13 +14,15 @@ export default class ConfirmPageContainerHeader extends Component { } static propTypes = { + accountAddress: PropTypes.string, + showAccountInHeader: PropTypes.bool, showEdit: PropTypes.bool, onEdit: PropTypes.func, children: PropTypes.node, } renderTop () { - const { onEdit, showEdit } = this.props + const { onEdit, showEdit, accountAddress, showAccountInHeader } = this.props const windowType = window.METAMASK_UI_TYPE const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && windowType !== ENVIRONMENT_TYPE_POPUP @@ -29,22 +33,39 @@ export default class ConfirmPageContainerHeader extends Component { return (
-
- - onEdit()} + { !showAccountInHeader + ?
- { this.context.t('edit') } - -
+ + onEdit()} + > + { this.context.t('edit') } + +
+ : null + } + { showAccountInHeader + ?
+
+ +
+
+ { addressSlicer(accountAddress) } +
+
+ : null + } { !isFullScreen && }
) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss index 44c721446..fb24feb58 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/index.scss @@ -9,6 +9,7 @@ border-bottom: 1px solid $geyser; padding: 4px 13px 4px 13px; flex: 0 0 auto; + align-items: center; } &__back-button-container { @@ -28,4 +29,16 @@ font-weight: 400; padding-left: 5px; } + + &__address-container { + display: flex; + align-items: center; + margin-top: 2px; + margin-bottom: 2px; + } + + &__address { + margin-left: 6px; + font-size: 14px; + } } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js index 41d9d5952..86ce2bff7 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js @@ -19,6 +19,8 @@ export default class ConfirmPageContainer extends Component { subtitleComponent: PropTypes.node, title: PropTypes.string, titleComponent: PropTypes.node, + hideSenderToRecipient: PropTypes.bool, + showAccountInHeader: PropTypes.bool, // Sender to Recipient fromAddress: PropTypes.string, fromName: PropTypes.string, @@ -104,6 +106,8 @@ export default class ConfirmPageContainer extends Component { lastTx, ofText, requestsWaitingText, + hideSenderToRecipient, + showAccountInHeader, } = this.props const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress) @@ -124,16 +128,21 @@ export default class ConfirmPageContainer extends Component { onEdit()} + showAccountInHeader={showAccountInHeader} + accountAddress={fromAddress} > - + { hideSenderToRecipient + ? null + : + } { contentComponent || ( diff --git a/ui/app/components/app/confirm-page-container/index.scss b/ui/app/components/app/confirm-page-container/index.scss index c0277eff5..3fc72c3a6 100644 --- a/ui/app/components/app/confirm-page-container/index.scss +++ b/ui/app/components/app/confirm-page-container/index.scss @@ -5,3 +5,9 @@ @import 'confirm-detail-row/index'; @import 'confirm-page-container-navigation/index'; + +.page-container { + &__content-component-wrapper { + height: 100%; + } +} diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index 44b180ac8..f0fdd3bd5 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -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, @@ -36,10 +39,12 @@ export default class Modal extends PureComponent { onCancel, cancelType, cancelText, + contentClass, + containerClass, } = this.props return ( -
+
{ headerText && (
@@ -53,7 +58,7 @@ export default class Modal extends PureComponent {
) } -
+
{ children }
diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js new file mode 100644 index 000000000..53ff473e4 --- /dev/null +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -0,0 +1,170 @@ +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, + origin: PropTypes.string, + } + + static contextTypes = { + t: PropTypes.func, + } + + state = { + customSpendLimit: this.props.customTokenAmount, + selectedOptionIsUnlimited: !this.props.customTokenAmount, + } + + renderModalContent () { + const { t } = this.context + const { + hideModal, + selectedIdentity, + tokenAmount, + tokenSymbol, + tokenBalance, + customTokenAmount, + origin, + } = this.props + const { name, address } = selectedIdentity || {} + const { selectedOptionIsUnlimited } = this.state + + return ( +
+
+
+ { t('editPermission') } +
+
hideModal()} + /> +
+
+
+ +
{ name }
+
{ t('balance') }
+
+
+ {`${tokenBalance} ${tokenSymbol}`} +
+
+
+
+ { t('spendLimitPermission') } +
+
+ { t('allowWithdrawAndSpend', [origin]) } +
+
+
this.setState({ selectedOptionIsUnlimited: true })} + > +
+
+ { selectedOptionIsUnlimited &&
} +
+
+
+ { + tokenAmount < tokenBalance + ? t('proposedApprovalLimit') + : t('unlimited') + } +
+
+ { t('spendLimitRequestedBy', [origin]) } +
+
+ {`${tokenAmount} ${tokenSymbol}`} +
+
+
+
+
this.setState({ selectedOptionIsUnlimited: false })} + > +
+
+ { !selectedOptionIsUnlimited &&
} +
+
+
+ { t('customSpendLimit') } +
+
+ { t('enterMaxSpendLimit') } +
+
+ { + this.setState({ customSpendLimit: event.target.value }) + if (selectedOptionIsUnlimited) { + this.setState({ selectedOptionIsUnlimited: false }) + } + }} + fullWidth + margin="dense" + value={ this.state.customSpendLimit } + /> +
+
+
+
+
+ ) + } + + render () { + const { t } = this.context + const { setCustomAmount, hideModal, customTokenAmount } = this.props + const { selectedOptionIsUnlimited, customSpendLimit } = this.state + return ( + { + setCustomAmount(!selectedOptionIsUnlimited ? customSpendLimit : '') + hideModal() + }} + submitText={t('save')} + submitType="primary" + contentClass="edit-approval-permission-modal-content" + containerClass="edit-approval-permission-modal-container" + submitDisabled={ (customSpendLimit === customTokenAmount) && !selectedOptionIsUnlimited } + > + { this.renderModalContent() } + + ) + } +} diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js new file mode 100644 index 000000000..ac25fa149 --- /dev/null +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.container.js @@ -0,0 +1,18 @@ +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 { getSelectedIdentity } from '../../../../selectors/selectors' + +const mapStateToProps = (state) => { + const modalStateProps = state.appState.modal.modalState.props || {} + return { + selectedIdentity: getSelectedIdentity(state), + ...modalStateProps, + } +} + +export default compose( + withModalProps, + connect(mapStateToProps) +)(EditApprovalPermission) diff --git a/ui/app/components/app/modals/edit-approval-permission/index.js b/ui/app/components/app/modals/edit-approval-permission/index.js new file mode 100644 index 000000000..3f50d3e99 --- /dev/null +++ b/ui/app/components/app/modals/edit-approval-permission/index.js @@ -0,0 +1 @@ +export { default } from './edit-approval-permission.container' diff --git a/ui/app/components/app/modals/edit-approval-permission/index.scss b/ui/app/components/app/modals/edit-approval-permission/index.scss new file mode 100644 index 000000000..f400da4c1 --- /dev/null +++ b/ui/app/components/app/modals/edit-approval-permission/index.scss @@ -0,0 +1,167 @@ +.edit-approval-permission { + width: 100%; + + &__header, + &__account-info { + display: flex; + justify-content: center; + align-items: center; + position: relative; + border-bottom: 1px solid #d2d8dd; + } + + &__header { + padding: 24px; + + &__close { + position: absolute; + right: 24px; + background-image: url("/images/close-gray.svg"); + width: .75rem; + height: .75rem; + cursor: pointer; + } + } + + &__title { + font-weight: bold; + font-size: 18px; + line-height: 25px; + } + + &__account-info { + justify-content: space-between; + padding: 8px 24px; + + &__account, + &__balance { + font-weight: normal; + font-size: 14px; + color: #24292E; + } + + &__account { + display: flex; + align-items: center; + } + + &__name { + margin-left: 8px; + margin-right: 8px; + } + + &__balance { + color: #6A737D; + } + } + + &__edit-section { + padding: 24px; + + &__title { + font-weight: bold; + font-size: 14px; + line-height: 20px; + color: #24292E; + } + + &__description { + font-weight: normal; + font-size: 12px; + line-height: 17px; + color: #6A737D; + margin-top: 8px; + } + + &__option { + display: flex; + align-items: flex-start; + margin-top: 20px; + } + + &__radio-button { + width: 18px; + } + + &__option-text { + display: flex; + flex-direction: column; + } + + &__option-label, + &__option-label--selected { + font-weight: normal; + font-size: 14px; + line-height: 20px; + color: #474B4D; + } + + &__option-label--selected { + color: #037DD6; + } + + &__option-description { + font-weight: normal; + font-size: 12px; + line-height: 17px; + color: #6A737D; + margin-top: 8px; + margin-bottom: 6px; + } + + &__option-value { + font-weight: normal; + font-size: 18px; + line-height: 25px; + color: #24292E; + } + + &__radio-button { + position: relative; + width: 18px; + height: 18px; + display: flex; + justify-content: center; + align-items: center; + margin-right: 4px; + } + + &__radio-button-outline, + &__radio-button-outline--selected { + width: 18px; + height: 18px; + background: #DADCDD; + border-radius: 9px; + position: absolute; + } + + &__radio-button-outline--selected { + background: #037DD6; + } + + &__radio-button-fill { + width: 14px; + height: 14px; + background: white; + border-radius: 7px; + position: absolute; + } + + &__radio-button-dot { + width: 8px; + height: 8px; + background: #037DD6; + border-radius: 4px; + position: absolute; + } + } +} + +.edit-approval-permission-modal-content { + padding: 0px; +} + +.edit-approval-permission-modal-container { + max-height: 550px; + width: 100%; +} diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index d93a41140..da7a27b84 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -9,3 +9,5 @@ @import 'metametrics-opt-in-modal/index'; @import './add-to-addressbook-modal/index'; + +@import './edit-approval-permission/index'; diff --git a/ui/app/components/app/modals/modal.js b/ui/app/components/app/modals/modal.js index c901d6db8..ada758b99 100644 --- a/ui/app/components/app/modals/modal.js +++ b/ui/app/components/app/modals/modal.js @@ -28,6 +28,7 @@ import ClearApprovedOrigins from './clear-approved-origins' import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container' import ConfirmDeleteNetwork from './confirm-delete-network' import AddToAddressBookModal from './add-to-addressbook-modal' +import EditApprovalPermission from './edit-approval-permission' const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', @@ -304,6 +305,31 @@ const MODALS = { }, }, + EDIT_APPROVAL_PERMISSION: { + contents: h(EditApprovalPermission), + mobileModalStyle: { + width: '95vw', + height: '100vh', + top: '50px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + }, + laptopModalStyle: { + width: 'auto', + height: '0px', + top: '80px', + left: '0px', + transform: 'none', + margin: '0 auto', + position: 'relative', + }, + contentStyle: { + borderRadius: '8px', + }, + }, + TRANSACTION_CONFIRMED: { disableBackdropClick: true, contents: h(TransactionConfirmed), diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss index 209614c6b..81eb18d06 100644 --- a/ui/app/css/itcss/tools/utilities.scss +++ b/ui/app/css/itcss/tools/utilities.scss @@ -141,11 +141,11 @@ } .cursor-pointer:hover { - transform: scale(1.1); + transform: scale(1.05); } .cursor-pointer:active { - transform: scale(.95); + transform: scale(.97); } .cursor-disabled { diff --git a/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js b/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js index 36f6a6efd..8025dd5bc 100644 --- a/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js +++ b/ui/app/helpers/higher-order-components/with-token-tracker/with-token-tracker.component.js @@ -15,6 +15,7 @@ export default function withTokenTracker (WrappedComponent) { this.state = { string: '', symbol: '', + balance: '', error: null, } @@ -78,8 +79,8 @@ export default function withTokenTracker (WrappedComponent) { if (!this.tracker.running) { return } - const [{ string, symbol }] = tokens - this.setState({ string, symbol, error: null }) + const [{ string, symbol, balance }] = tokens + this.setState({ string, symbol, error: null, balance }) } removeListeners () { @@ -91,13 +92,13 @@ export default function withTokenTracker (WrappedComponent) { } render () { - const { string, symbol, error } = this.state - + const { balance, string, symbol, error } = this.state return ( ) diff --git a/ui/app/helpers/utils/token-util.js b/ui/app/helpers/utils/token-util.js index 831d85131..2c4f67fd0 100644 --- a/ui/app/helpers/utils/token-util.js +++ b/ui/app/helpers/utils/token-util.js @@ -128,6 +128,11 @@ export function calcTokenAmount (value, decimals) { return new BigNumber(String(value)).div(multiplier) } +export function calcTokenValue (value, decimals) { + const multiplier = Math.pow(10, Number(decimals || 0)) + return new BigNumber(String(value)).times(multiplier) +} + export function getTokenValue (tokenParams = []) { const valueData = tokenParams.find(param => param.name === '_value') return valueData && valueData.value diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js new file mode 100644 index 000000000..9f11fbb2f --- /dev/null +++ b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -0,0 +1,223 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import Identicon from '../../../components/ui/identicon' +import { + addressSummary, +} from '../../../helpers/utils/util' + +export default class ConfirmApproveContent extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + amount: PropTypes.string, + txFeeTotal: PropTypes.string, + tokenAmount: PropTypes.string, + customTokenAmount: PropTypes.string, + tokenSymbol: PropTypes.string, + siteImage: PropTypes.string, + tokenAddress: PropTypes.string, + showCustomizeGasModal: PropTypes.func, + showEditApprovalPermissionModal: PropTypes.func, + origin: PropTypes.string, + setCustomAmount: PropTypes.func, + tokenBalance: PropTypes.string, + data: PropTypes.string, + toAddress: PropTypes.string, + fiatTransactionTotal: PropTypes.string, + ethTransactionTotal: PropTypes.string, + } + + state = { + showFullTxDetails: false, + } + + renderApproveContentCard ({ + symbol, + title, + showEdit, + onEditClick, + content, + footer, + noBorder, + }) { + return ( +
+
+
{ symbol }
+
{ title }
+ { showEdit &&
onEditClick()} + >Edit
} +
+
+ { content } +
+ { footer } +
+ ) + } + + // TODO: Add "Learn Why" with link to the feeAssociatedRequest text + renderTransactionDetailsContent () { + const { t } = this.context + const { + ethTransactionTotal, + fiatTransactionTotal, + } = this.props + return ( +
+
+ { t('feeAssociatedRequest') } +
+
+
+ { fiatTransactionTotal } +
+
+ { ethTransactionTotal } +
+
+
+ ) + } + + renderPermissionContent () { + const { t } = this.context + const { customTokenAmount, tokenAmount, tokenSymbol, origin, toAddress } = this.props + + return ( +
+
{ t('accessAndSpendNotice', [origin]) }
+
+
{ t('amountWithColon') }
+
{ `${customTokenAmount || tokenAmount} ${tokenSymbol}` }
+
+
+
{ t('toWithColon') }
+
{ addressSummary(toAddress) }
+
+
+ ) + } + + renderDataContent () { + const { t } = this.context + const { data } = this.props + return ( +
+
{ t('functionApprove') }
+
{ data }
+
+ ) + } + + render () { + const { t } = this.context + const { + siteImage, + tokenAmount, + customTokenAmount, + origin, + tokenSymbol, + showCustomizeGasModal, + showEditApprovalPermissionModal, + setCustomAmount, + tokenBalance, + } = this.props + const { showFullTxDetails } = this.state + + return ( +
+
+ +
+
+ { t('allowOriginSpendToken', [origin, tokenSymbol]) } +
+
+ { t('trustSiteApprovePermission', [origin, tokenSymbol]) } +
+
+
showEditApprovalPermissionModal({ customTokenAmount, tokenAmount, tokenSymbol, setCustomAmount, tokenBalance, origin })} + > + { t('editPermission') } +
+
+
+ {this.renderApproveContentCard({ + symbol: , + title: 'Transaction Fee', + showEdit: true, + onEditClick: showCustomizeGasModal, + content: this.renderTransactionDetailsContent(), + noBorder: !showFullTxDetails, + footer:
this.setState({ showFullTxDetails: !this.state.showFullTxDetails })} + > +
+
+ View full transaction details +
+ +
+
, + })} +
+ + { + showFullTxDetails + ? ( +
+
+ {this.renderApproveContentCard({ + symbol: , + title: 'Permission', + content: this.renderPermissionContent(), + showEdit: true, + onEditClick: () => showEditApprovalPermissionModal({ + customTokenAmount, + tokenAmount, + tokenSymbol, + tokenBalance, + setCustomAmount, + }), + })} +
+
+ {this.renderApproveContentCard({ + symbol: , + title: 'Data', + content: this.renderDataContent(), + noBorder: true, + })} +
+
+ ) + : null + } +
+ ) + } +} diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/index.js b/ui/app/pages/confirm-approve/confirm-approve-content/index.js new file mode 100644 index 000000000..8f225387a --- /dev/null +++ b/ui/app/pages/confirm-approve/confirm-approve-content/index.js @@ -0,0 +1 @@ +export { default } from './confirm-approve-content.component' diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/index.scss b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss new file mode 100644 index 000000000..7d3018f6e --- /dev/null +++ b/ui/app/pages/confirm-approve/confirm-approve-content/index.scss @@ -0,0 +1,306 @@ +.confirm-approve-content { + display: flex; + flex-flow: column; + align-items: center; + width: 100%; + height: 100%; + + font-family: Roboto; + font-style: normal; + + &__identicon-wrapper { + display: flex; + width: 100%; + justify-content: center; + margin-top: 22px; + padding-left: 24px; + padding-right: 24px; + } + + &__full-tx-content { + display: flex; + flex-flow: column; + align-items: center; + width: 390px; + font-family: Roboto; + font-style: normal; + padding-left: 24px; + padding-right: 24px; + } + + &__card-wrapper { + width: 100%; + } + + &__title { + font-weight: normal; + font-size: 24px; + line-height: 34px; + width: 100%; + display: flex; + justify-content: center; + text-align: center; + margin-top: 22px; + padding-left: 24px; + padding-right: 24px; + } + + &__description { + font-weight: normal; + font-size: 14px; + line-height: 20px; + margin-top: 16px; + margin-bottom: 16px; + color: #6A737D; + text-align: center; + padding-left: 24px; + padding-right: 24px; + } + + &__card, + &__card--no-border { + display: flex; + flex-flow: column; + border-bottom: 1px solid #D2D8DD; + position: relative; + padding-left: 24px; + padding-right: 24px; + + &__bold-text { + font-weight: bold; + font-size: 14px; + line-height: 20px; + } + + &__thin-text { + font-weight: normal; + font-size: 12px; + line-height: 17px; + color: #6A737D; + } + } + + &__card--no-border { + border-bottom: none; + } + + &__card-header { + display: flex; + flex-flow: row; + margin-top: 20px; + align-items: center; + position: relative; + + &__symbol { + width: auto; + } + + &__symbol--aligned { + width: 100%; + } + + &__title, &__title-value { + font-weight: bold; + font-size: 14px; + line-height: 20px; + } + + &__title { + width: 100%; + margin-left: 16px; + } + + &__title--aligned { + margin-left: 27px; + position: absolute; + width: auto; + } + } + + &__card-content { + margin-top: 6px; + margin-bottom: 12px; + } + + &__card-content--aligned { + margin-left: 42px; + } + + &__transaction-total-symbol { + width: 16px; + display: flex; + justify-content: center; + align-items: center; + height: 16px; + + &__x { + display: flex; + justify-content: center; + align-items: center; + + div { + width: 22px; + height: 2px; + background: #037DD6; + position: absolute; + } + + div:first-of-type { + transform: rotate(45deg); + } + + div:last-of-type { + transform: rotate(-45deg); + } + } + + &__circle { + width: 14px; + height: 14px; + border: 2px solid #037DD6; + border-radius: 50%; + background: white; + position: absolute; + } + } + + &__transaction-details-content { + display: flex; + flex-flow: row; + justify-content: space-between; + + .confirm-approve-content__small-text { + width: 160px; + } + + &__fee { + display: flex; + flex-flow: column; + align-items: flex-end; + text-align: right; + } + + &__primary-fee { + font-weight: bold; + font-size: 18px; + line-height: 25px; + color: #000000; + } + + &__secondary-fee { + font-weight: normal; + font-size: 14px; + line-height: 20px; + color: #8C8E94; + } + } + + &__view-full-tx-button-wrapper { + display: flex; + flex-flow: row; + margin-bottom: 16px; + justify-content: center; + + i { + margin-left: 6px; + display: flex; + color: #3099f2; + align-items: center; + } + } + + &__view-full-tx-button { + display: flex; + flex-flow: row; + } + + &__edit-submission-button-container { + display: flex; + flex-flow: row; + padding-top: 15px; + padding-bottom: 30px; + border-bottom: 1px solid #D2D8DD; + width: 100%; + justify-content: center; + padding-left: 24px; + padding-right: 24px; + } + + &__large-text { + font-size: 18px; + line-height: 25px; + color: #24292E; + } + + &__medium-link-text { + font-size: 14px; + line-height: 20px; + font-weight: 500; + color: #037DD6; + } + + &__medium-text, + &__label { + font-weight: normal; + font-size: 14px; + line-height: 20px; + color: #24292E; + } + + &__label { + font-weight: bold; + margin-right: 4px; + } + + &__small-text, &__small-blue-text, &__info-row { + font-weight: normal; + font-size: 12px; + line-height: 17px; + color: #6A737D; + } + + &__small-blue-text { + color: #037DD6; + } + + &__info-row { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + } + + &__data, + &__permission { + width: 100%; + } + + &__permission { + .flex-row { + margin-top: 14px; + } + } + + &__data { + &__data-block { + overflow-wrap: break-word; + margin-right: 16px; + margin-top: 12px; + } + } + + &__footer { + display: flex; + align-items: flex-end; + margin-top: 16px; + padding-left: 34px; + padding-right: 24px; + + .confirm-approve-content__small-text { + margin-left: 16px; + } + } +} + +.confirm-approve-content--full { + height: auto; +} diff --git a/ui/app/pages/confirm-approve/confirm-approve.component.js b/ui/app/pages/confirm-approve/confirm-approve.component.js index b71eaa1d4..2a40cfa96 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve.component.js @@ -1,20 +1,109 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import ConfirmTokenTransactionBase from '../confirm-token-transaction-base' +import ConfirmTransactionBase from '../confirm-transaction-base' +import ConfirmApproveContent from './confirm-approve-content' +import { getCustomTxParamsData } from './confirm-approve.util' +import { + calcTokenAmount, +} from '../../helpers/utils/token-util' export default class ConfirmApprove extends Component { + static contextTypes = { + t: PropTypes.func, + } + static propTypes = { + tokenAddress: PropTypes.string, + toAddress: PropTypes.string, tokenAmount: PropTypes.number, tokenSymbol: PropTypes.string, + fiatTransactionTotal: PropTypes.string, + ethTransactionTotal: PropTypes.string, + contractExchangeRate: PropTypes.number, + conversionRate: PropTypes.number, + currentCurrency: PropTypes.string, + showCustomizeGasModal: PropTypes.func, + showEditApprovalPermissionModal: PropTypes.func, + origin: PropTypes.string, + siteImage: PropTypes.string, + tokenTrackerBalance: PropTypes.string, + data: PropTypes.string, + decimals: PropTypes.number, + txData: PropTypes.object, + } + + static defaultProps = { + tokenAmount: 0, + } + + state = { + customPermissionAmount: '', + } + + componentDidUpdate (prevProps) { + const { tokenAmount } = this.props + + if (tokenAmount !== prevProps.tokenAmount) { + this.setState({ customPermissionAmount: tokenAmount }) + } } render () { - const { tokenAmount, tokenSymbol } = this.props + const { + toAddress, + tokenAddress, + tokenSymbol, + tokenAmount, + showCustomizeGasModal, + showEditApprovalPermissionModal, + origin, + siteImage, + tokenTrackerBalance, + data, + decimals, + txData, + ethTransactionTotal, + fiatTransactionTotal, + ...restProps + } = this.props + const { customPermissionAmount } = this.state + + const tokensText = `${tokenAmount} ${tokenSymbol}` + + const tokenBalance = tokenTrackerBalance + ? Number(calcTokenAmount(tokenTrackerBalance, decimals)).toPrecision(9) + : '' return ( - { + this.setState({ customPermissionAmount: newAmount }) + }} + customTokenAmount={String(customPermissionAmount)} + tokenAmount={String(tokenAmount)} + origin={origin} + tokenSymbol={tokenSymbol} + tokenBalance={tokenBalance} + showCustomizeGasModal={() => showCustomizeGasModal(txData)} + showEditApprovalPermissionModal={showEditApprovalPermissionModal} + data={data} + toAddress={toAddress} + ethTransactionTotal={ethTransactionTotal} + fiatTransactionTotal={fiatTransactionTotal} + />} + hideSenderToRecipient={true} + customTxParamsData={customPermissionAmount + ? getCustomTxParamsData(data, { customPermissionAmount, tokenAmount, decimals }) + : null + } + {...restProps} /> ) } diff --git a/ui/app/pages/confirm-approve/confirm-approve.container.js b/ui/app/pages/confirm-approve/confirm-approve.container.js index 5f8bb8f0b..43f5aab90 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.container.js +++ b/ui/app/pages/confirm-approve/confirm-approve.container.js @@ -1,15 +1,102 @@ import { connect } from 'react-redux' +import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' +import { + contractExchangeRateSelector, + transactionFeeSelector, +} from '../../selectors/confirm-transaction' +import { showModal } from '../../store/actions' +import { tokenSelector } from '../../selectors/tokens' +import { + getTokenData, +} from '../../helpers/utils/transactions.util' +import withTokenTracker from '../../helpers/higher-order-components/with-token-tracker' +import { + calcTokenAmount, + getTokenToAddress, + getTokenValue, +} from '../../helpers/utils/token-util' import ConfirmApprove from './confirm-approve.component' -import { approveTokenAmountAndToAddressSelector } from '../../selectors/confirm-transaction' -const mapStateToProps = state => { - const { confirmTransaction: { tokenProps: { tokenSymbol } = {} } } = state - const { tokenAmount } = approveTokenAmountAndToAddressSelector(state) +const mapStateToProps = (state, ownProps) => { + const { match: { params = {} } } = ownProps + const { id: paramsTransactionId } = params + const { + confirmTransaction, + metamask: { currentCurrency, conversionRate, selectedAddressTxList, approvedOrigins, selectedAddress }, + } = state + const { + txData: { id: transactionId, txParams: { to: tokenAddress, data } = {} } = {}, + } = confirmTransaction + + const transaction = selectedAddressTxList.find(({ id }) => id === (Number(paramsTransactionId) || transactionId)) || {} + + const { + ethTransactionTotal, + fiatTransactionTotal, + } = transactionFeeSelector(state, transaction) + const tokens = tokenSelector(state) + const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address) + const { decimals, symbol: tokenSymbol } = currentToken || {} + + const tokenData = getTokenData(data) + const tokenValue = tokenData && getTokenValue(tokenData.params) + const toAddress = tokenData && getTokenToAddress(tokenData.params) + const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toNumber() + const contractExchangeRate = contractExchangeRateSelector(state) + + const { origin } = transaction + const formattedOrigin = origin + ? origin[0].toUpperCase() + origin.slice(1) + : '' + + const { siteImage } = approvedOrigins[origin] || {} return { + toAddress, + tokenAddress, tokenAmount, + currentCurrency, + conversionRate, + contractExchangeRate, + fiatTransactionTotal, + ethTransactionTotal, tokenSymbol, + siteImage, + token: { address: tokenAddress }, + userAddress: selectedAddress, + origin: formattedOrigin, + data, + decimals: Number(decimals), + txData: transaction, } } -export default connect(mapStateToProps)(ConfirmApprove) +const mapDispatchToProps = (dispatch) => { + return { + showCustomizeGasModal: (txData) => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })), + showEditApprovalPermissionModal: ({ + tokenAmount, + customTokenAmount, + tokenSymbol, + tokenBalance, + setCustomAmount, + origin, + }) => dispatch(showModal({ + name: 'EDIT_APPROVAL_PERMISSION', + tokenAmount, + customTokenAmount, + tokenSymbol, + tokenBalance, + setCustomAmount, + origin, + })), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps), + withTokenTracker, +)(ConfirmApprove) + diff --git a/ui/app/pages/confirm-approve/confirm-approve.util.js b/ui/app/pages/confirm-approve/confirm-approve.util.js new file mode 100644 index 000000000..be77c65f9 --- /dev/null +++ b/ui/app/pages/confirm-approve/confirm-approve.util.js @@ -0,0 +1,28 @@ +import { decimalToHex } from '../../helpers/utils/conversions.util' +import { calcTokenValue } from '../../helpers/utils/token-util.js' + +export function getCustomTxParamsData (data, { customPermissionAmount, tokenAmount, decimals }) { + if (customPermissionAmount) { + const tokenValue = decimalToHex(calcTokenValue(tokenAmount, decimals)) + + const re = new RegExp('(^.+)' + tokenValue + '$') + const matches = re.exec(data) + + if (!matches || !matches[1]) { + return data + } + let dataWithoutCurrentAmount = matches[1] + const customPermissionValue = decimalToHex(calcTokenValue(Number(customPermissionAmount), decimals)) + + const differenceInLengths = customPermissionValue.length - tokenValue.length + const zeroModifier = dataWithoutCurrentAmount.length - differenceInLengths + if (differenceInLengths > 0) { + dataWithoutCurrentAmount = dataWithoutCurrentAmount.slice(0, zeroModifier) + } else if (differenceInLengths < 0) { + dataWithoutCurrentAmount = dataWithoutCurrentAmount.padEnd(zeroModifier, 0) + } + + const customTxParamsData = dataWithoutCurrentAmount + customPermissionValue + return customTxParamsData + } +} diff --git a/ui/app/pages/confirm-approve/index.scss b/ui/app/pages/confirm-approve/index.scss new file mode 100644 index 000000000..18d7c29e8 --- /dev/null +++ b/ui/app/pages/confirm-approve/index.scss @@ -0,0 +1 @@ +@import 'confirm-approve-content/index'; diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js index 25725381d..0b46fe9c9 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -105,6 +105,8 @@ export default class ConfirmTransactionBase extends Component { getNextNonce: PropTypes.func, nextNonce: PropTypes.number, tryReverseResolveAddress: PropTypes.func.isRequired, + hideSenderToRecipient: PropTypes.bool, + showAccountInHeader: PropTypes.bool, } state = { @@ -645,6 +647,8 @@ export default class ConfirmTransactionBase extends Component { warning, unapprovedTxCount, transactionCategory, + hideSenderToRecipient, + showAccountInHeader, } = this.props const { submitting, submitError, submitWarning } = this.state @@ -655,6 +659,7 @@ export default class ConfirmTransactionBase extends Component { this.handleCancelAll()} onCancel={() => this.handleCancel()} onSubmit={() => this.handleSubmit()} + hideSenderToRecipient={hideSenderToRecipient} /> ) } diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js index 19602611c..9a238e780 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -46,7 +46,7 @@ const customNonceMerge = txData => customNonceValue ? ({ }) : txData const mapStateToProps = (state, ownProps) => { - const { toAddress: propsToAddress, match: { params = {} } } = ownProps + const { toAddress: propsToAddress, customTxParamsData, match: { params = {} } } = ownProps const { id: paramsTransactionId } = params const { showFiatInTestnets } = preferencesSelector(state) const isMainnet = getIsMainnet(state) @@ -133,6 +133,17 @@ const mapStateToProps = (state, ownProps) => { const methodData = getKnownMethodData(state, data) || {} + let fullTxData = { ...txData, ...transaction } + if (customTxParamsData) { + fullTxData = { + ...fullTxData, + txParams: { + ...fullTxData.txParams, + data: customTxParamsData, + }, + } + } + return { balance, fromAddress, @@ -150,7 +161,7 @@ const mapStateToProps = (state, ownProps) => { hexTransactionAmount, hexTransactionFee, hexTransactionTotal, - txData: { ...txData, ...transaction }, + txData: fullTxData, tokenData, methodData, tokenProps, diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss index e7242392b..d79b7c28d 100644 --- a/ui/app/pages/index.scss +++ b/ui/app/pages/index.scss @@ -11,3 +11,5 @@ @import 'first-time-flow/index'; @import 'keychains/index'; + +@import 'confirm-approve/index';