From 44d4b5b5db021646ca26026d91ab2ef39153af37 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 15 Sep 2018 23:50:17 -0700 Subject: [PATCH 01/11] Refactor ConfirmPageContainerError to ErrorMessage --- .../confirm-page-container-content.component.js | 9 +++------ .../confirm-page-container-error/index.js | 1 - .../confirm-page-container-content/index.js | 1 - .../confirm-page-container-content/index.scss | 2 -- .../error-message.component.js} | 14 +++++++------- ui/app/components/error-message/index.js | 1 + .../index.scss | 2 +- ui/app/components/index.scss | 2 ++ 8 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js rename ui/app/components/{confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js => error-message/error-message.component.js} (52%) create mode 100644 ui/app/components/error-message/index.js rename ui/app/components/{confirm-page-container/confirm-page-container-content/confirm-page-container-error => error-message}/index.scss (91%) diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index de9aa6eb7..74e95ece6 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -2,11 +2,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' import { Tabs, Tab } from '../../tabs' -import { - ConfirmPageContainerSummary, - ConfirmPageContainerError, - ConfirmPageContainerWarning, -} from './' +import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from './' +import ErrorMessage from '../../error-message' export default class ConfirmPageContainerContent extends Component { static propTypes = { @@ -95,7 +92,7 @@ export default class ConfirmPageContainerContent extends Component { { (errorKey || errorMessage) && (
- diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js deleted file mode 100644 index 4ac95d0e3..000000000 --- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './confirm-page-container-error.component' diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.js b/ui/app/components/confirm-page-container/confirm-page-container-content/index.js index 1469dd438..4dfd89d92 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.js +++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.js @@ -1,4 +1,3 @@ export { default } from './confirm-page-container-content.component' export { default as ConfirmPageContainerSummary } from './confirm-page-container-summary' -export { default as ConfirmPageContainerError } from './confirm-page-container-error' export { default as ConfirmPageContainerWarning } from './confirm-page-container-warning' diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss index 39797a43f..698e624f4 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss +++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss @@ -1,5 +1,3 @@ -@import './confirm-page-container-error/index'; - @import './confirm-page-container-warning/index'; @import './confirm-page-container-summary/index'; diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js b/ui/app/components/error-message/error-message.component.js similarity index 52% rename from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js rename to ui/app/components/error-message/error-message.component.js index 4965d7b4e..b4464c33b 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js +++ b/ui/app/components/error-message/error-message.component.js @@ -1,30 +1,30 @@ import React from 'react' import PropTypes from 'prop-types' -const ConfirmPageContainerError = (props, context) => { +const ErrorMessage = (props, context) => { const { errorMessage, errorKey } = props const error = errorKey ? context.t(errorKey) : errorMessage return ( -
+
-
+
{ `ALERT: ${error}` }
) } -ConfirmPageContainerError.propTypes = { +ErrorMessage.propTypes = { errorMessage: PropTypes.string, errorKey: PropTypes.string, } -ConfirmPageContainerError.contextTypes = { +ErrorMessage.contextTypes = { t: PropTypes.func, } -export default ConfirmPageContainerError +export default ErrorMessage diff --git a/ui/app/components/error-message/index.js b/ui/app/components/error-message/index.js new file mode 100644 index 000000000..1c97a9955 --- /dev/null +++ b/ui/app/components/error-message/index.js @@ -0,0 +1 @@ +export { default } from './error-message.component' diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss b/ui/app/components/error-message/index.scss similarity index 91% rename from ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss rename to ui/app/components/error-message/index.scss index 89ff25578..5915e21cf 100644 --- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss +++ b/ui/app/components/error-message/index.scss @@ -1,4 +1,4 @@ -.confirm-page-container-error { +.error-message { min-height: 32px; border: 1px solid $monzo; color: $monzo; diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index 983d6b98a..e252bf07d 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -6,6 +6,8 @@ @import './confirm-page-container/index'; +@import './error-message/index'; + @import './export-text-container/index'; @import './info-box/index'; From d0d0103bb52cbc032a3b3ea2a2ff5edbf67b0d19 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 09:56:59 -0700 Subject: [PATCH 02/11] Add Modal component --- ui/app/components/index.scss | 2 + ui/app/components/modal/index.js | 2 + ui/app/components/modal/index.scss | 60 ++++++++++++++ .../components/modal/modal-content/index.js | 1 + .../components/modal/modal-content/index.scss | 19 +++++ .../modal-content/modal-content.component.js | 24 ++++++ ui/app/components/modal/modal.component.js | 78 +++++++++++++++++++ 7 files changed, 186 insertions(+) create mode 100644 ui/app/components/modal/index.js create mode 100644 ui/app/components/modal/index.scss create mode 100644 ui/app/components/modal/modal-content/index.js create mode 100644 ui/app/components/modal/modal-content/index.scss create mode 100644 ui/app/components/modal/modal-content/modal-content.component.js create mode 100644 ui/app/components/modal/modal.component.js diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index e252bf07d..21b65bf55 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -14,6 +14,8 @@ @import './menu-bar/index'; +@import './modal/index'; + @import './modals/index'; @import './network-display/index'; diff --git a/ui/app/components/modal/index.js b/ui/app/components/modal/index.js new file mode 100644 index 000000000..58309abbe --- /dev/null +++ b/ui/app/components/modal/index.js @@ -0,0 +1,2 @@ +export { default } from './modal.component' +export { default as ModalContent } from './modal-content' diff --git a/ui/app/components/modal/index.scss b/ui/app/components/modal/index.scss new file mode 100644 index 000000000..e57156d71 --- /dev/null +++ b/ui/app/components/modal/index.scss @@ -0,0 +1,60 @@ +@import './modal-content/index'; + +.modal-container { + width: 100%; + height: 100%; + background-color: #fff; + display: flex; + flex-flow: column; + border-radius: 8px; + + &__content { + overflow-y: auto; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 32px; + + @media screen and (max-width: 575px) { + justify-content: center; + padding: 28px 20px; + } + } + + &__header { + position: relative; + display: flex; + padding: 12px; + justify-content: center; + border-bottom: 1px solid #d2d8dd; + } + + &__header-close::after { + content: '\00D7'; + font-size: 40px; + color: $dusty-gray; + position: absolute; + top: -5px; + right: 10px; + cursor: pointer; + } + + &__footer { + display: flex; + flex-flow: row; + justify-content: center; + border-top: 1px solid #d2d8dd; + padding: 16px; + flex: 0 0 auto; + + &-button { + min-width: 0; + margin-right: 16px; + + &:last-of-type { + margin-right: 0; + } + } + } +} diff --git a/ui/app/components/modal/modal-content/index.js b/ui/app/components/modal/modal-content/index.js new file mode 100644 index 000000000..733cfb3b8 --- /dev/null +++ b/ui/app/components/modal/modal-content/index.js @@ -0,0 +1 @@ +export { default } from './modal-content.component' diff --git a/ui/app/components/modal/modal-content/index.scss b/ui/app/components/modal/modal-content/index.scss new file mode 100644 index 000000000..560505b84 --- /dev/null +++ b/ui/app/components/modal/modal-content/index.scss @@ -0,0 +1,19 @@ +.modal-content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 0; + + &__title { + font-size: 1.5rem; + font-weight: 500; + padding: 16px 0; + text-align: center; + } + + &__description { + text-align: center; + font-size: .875rem; + } +} diff --git a/ui/app/components/modal/modal-content/modal-content.component.js b/ui/app/components/modal/modal-content/modal-content.component.js new file mode 100644 index 000000000..8beb854e0 --- /dev/null +++ b/ui/app/components/modal/modal-content/modal-content.component.js @@ -0,0 +1,24 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' + +export default class ModalContent extends PureComponent { + static propTypes = { + title: PropTypes.string, + description: PropTypes.string, + } + + render () { + const { title, description } = this.props + + return ( +
+
+ { title } +
+
+ { description } +
+
+ ) + } +} diff --git a/ui/app/components/modal/modal.component.js b/ui/app/components/modal/modal.component.js new file mode 100644 index 000000000..81bdd0010 --- /dev/null +++ b/ui/app/components/modal/modal.component.js @@ -0,0 +1,78 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Button from '../button' + +export default class Modal extends PureComponent { + static propTypes = { + children: PropTypes.node, + // Header text + headerText: PropTypes.string, + // Submit button (right button) + onSubmit: PropTypes.func, + submitType: PropTypes.string, + submitText: PropTypes.string, + // Cancel button (left button) + onCancel: PropTypes.func, + cancelType: PropTypes.string, + cancelText: PropTypes.string, + } + + static defaultProps = { + submitType: 'primary', + cancelType: 'default', + } + + render () { + const { + children, + headerText, + onSubmit, + submitType, + submitText, + onCancel, + cancelType, + cancelText, + } = this.props + + return ( +
+ { + headerText && ( +
+
+ { headerText } +
+
onCancel()} + /> +
+ ) + } +
+ { children } +
+
+ { + onCancel && ( + + ) + } + +
+
+ ) + } +} From 04ec3f0b6b3c66b8d75b7d3da257680a97453915 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 10:09:48 -0700 Subject: [PATCH 03/11] Fix Transaction view styles --- .../transaction-action/transaction-action.component.js | 4 ++-- ui/app/components/transaction-activity-log/index.scss | 4 ++++ ui/app/components/transaction-list/index.scss | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js index 81a1e96d0..1729b878c 100644 --- a/ui/app/components/transaction-action/transaction-action.component.js +++ b/ui/app/components/transaction-action/transaction-action.component.js @@ -4,7 +4,7 @@ import { getTransactionActionKey } from '../../helpers/transactions.util' export default class TransactionAction extends PureComponent { static contextTypes = { - tOrDefault: PropTypes.func, + t: PropTypes.func, } static propTypes = { @@ -35,7 +35,7 @@ export default class TransactionAction extends PureComponent { } const actionKey = await getTransactionActionKey(transaction, data) - const action = actionKey && this.context.tOrDefault(actionKey) + const action = actionKey && this.context.t(actionKey) this.setState({ transactionAction: action }) } diff --git a/ui/app/components/transaction-activity-log/index.scss b/ui/app/components/transaction-activity-log/index.scss index 2324d44b1..a9933dac6 100644 --- a/ui/app/components/transaction-activity-log/index.scss +++ b/ui/app/components/transaction-activity-log/index.scss @@ -33,6 +33,10 @@ &:last-child::after { height: 50%; } + + &:first-child:last-child::after { + display: none; + } } &__activity-icon { diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss index d944ef20e..777f701f9 100644 --- a/ui/app/components/transaction-list/index.scss +++ b/ui/app/components/transaction-list/index.scss @@ -3,6 +3,8 @@ flex-direction: column; flex: 1; overflow-y: hidden; + margin-top: 8px; + border-top: 1px solid $geyser; &__completed-transactions { display: flex; @@ -15,7 +17,7 @@ font-size: .875rem; color: $dusty-gray; border-bottom: 1px solid $geyser; - padding: 16px 0 8px 20px; + padding: 8px 0 8px 20px; @media screen and (max-width: $break-small) { padding: 8px 0 8px 16px; From 3e470fee8a5560b605f7ad1060fb1eebafefd21e Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 10:12:31 -0700 Subject: [PATCH 04/11] Add withModalProps HOC --- .../with-modal-props/index.js | 1 + .../with-modal-props/with-modal-props.js | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 ui/app/higher-order-components/with-modal-props/index.js create mode 100644 ui/app/higher-order-components/with-modal-props/with-modal-props.js diff --git a/ui/app/higher-order-components/with-modal-props/index.js b/ui/app/higher-order-components/with-modal-props/index.js new file mode 100644 index 000000000..e476b51d2 --- /dev/null +++ b/ui/app/higher-order-components/with-modal-props/index.js @@ -0,0 +1 @@ +export { default } from './with-modal-props' diff --git a/ui/app/higher-order-components/with-modal-props/with-modal-props.js b/ui/app/higher-order-components/with-modal-props/with-modal-props.js new file mode 100644 index 000000000..02f3855af --- /dev/null +++ b/ui/app/higher-order-components/with-modal-props/with-modal-props.js @@ -0,0 +1,21 @@ +import { connect } from 'react-redux' +import { hideModal } from '../../actions' + +const mapStateToProps = state => { + const { appState } = state + const { props: modalProps } = appState.modal.modalState + + return { + ...modalProps, + } +} + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => dispatch(hideModal()), + } +} + +export default function withModalProps (Component) { + return connect(mapStateToProps, mapDispatchToProps)(Component) +} From 5a6c333506e4000602c1a1106cee6d06fe83afa8 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 10:34:29 -0700 Subject: [PATCH 05/11] Switch existing modals from using Notification to Modal. Remove Notification component. Add CancelTransaction modal --- app/_locales/en/messages.json | 15 +++ app/scripts/controllers/transactions/index.js | 5 +- .../cancel-transaction-gas-fee.component.js | 32 +++++ .../cancel-transaction-gas-fee/index.js | 1 + .../cancel-transaction-gas-fee/index.scss | 17 +++ .../cancel-transaction.component.js | 71 ++++++++++++ .../cancel-transaction.container.js | 55 +++++++++ .../modals/cancel-transaction/index.js | 1 + .../modals/cancel-transaction/index.scss | 18 +++ .../confirm-remove-account.component.js | 67 +++++------ .../confirm-remove-account.container.js | 8 +- .../modals/confirm-remove-account/index.js | 3 +- .../modals/confirm-remove-account/index.scss | 58 ++++++++++ .../confirm-reset-account.component.js | 48 +++----- .../confirm-reset-account.container.js | 11 +- .../modals/confirm-reset-account/index.js | 3 +- ui/app/components/modals/index.scss | 109 +----------------- ui/app/components/modals/modal.js | 40 ++++--- .../components/modals/notification/index.js | 2 - .../notification/notification.component.js | 30 ----- .../notification/notification.container.js | 38 ------ .../modals/transaction-confirmed/index.js | 3 +- .../modals/transaction-confirmed/index.scss | 22 ++++ .../transaction-confirmed.component.js | 61 ++++++---- .../transaction-confirmed.container.js | 4 + .../components/modals/welcome-beta/index.js | 3 +- .../welcome-beta/welcome-beta.component.js | 23 ++-- .../welcome-beta/welcome-beta.container.js | 4 + .../confirm-transaction-base.component.js | 5 +- .../confirm-transaction-base.container.js | 4 +- ...transaction-list-item-details.component.js | 22 +++- .../transaction-list-item.component.js | 30 +++-- .../transaction-list-item.container.js | 5 +- .../transaction-list.component.js | 5 +- ui/app/conf-tx.js | 2 +- ui/app/constants/transactions.js | 1 + ui/app/helpers/conversions.util.js | 12 ++ ui/app/helpers/transactions.util.js | 7 +- 38 files changed, 520 insertions(+), 325 deletions(-) create mode 100644 ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js create mode 100644 ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js create mode 100644 ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss create mode 100644 ui/app/components/modals/cancel-transaction/cancel-transaction.component.js create mode 100644 ui/app/components/modals/cancel-transaction/cancel-transaction.container.js create mode 100644 ui/app/components/modals/cancel-transaction/index.js create mode 100644 ui/app/components/modals/cancel-transaction/index.scss create mode 100644 ui/app/components/modals/confirm-remove-account/index.scss delete mode 100644 ui/app/components/modals/notification/index.js delete mode 100644 ui/app/components/modals/notification/notification.component.js delete mode 100644 ui/app/components/modals/notification/notification.container.js create mode 100644 ui/app/components/modals/transaction-confirmed/index.scss create mode 100644 ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js create mode 100644 ui/app/components/modals/welcome-beta/welcome-beta.container.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2cfd15f50..ed2e7a119 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -61,6 +61,12 @@ "attemptingConnect": { "message": "Attempting to connect to blockchain." }, + "attemptToCancel": { + "message": "Attempt to Cancel?" + }, + "attemptToCancelDescription": { + "message": "Attempting to cancel does not guarantee your original transaction will be cancelled. If cancelled, you are still required to pay a transaction fee to the network." + }, "attributions": { "message": "Attributions" }, @@ -116,6 +122,12 @@ "cancel": { "message": "Cancel" }, + "cancelAttempt": { + "message": "Cancel Attempt" + }, + "cancellationGasFee": { + "message": "Cancellation Gas Fee" + }, "classicInterface": { "message": "Use classic interface" }, @@ -1228,6 +1240,9 @@ "whatsThis": { "message": "What's this?" }, + "yesLetsTry": { + "message": "yes, let's try" + }, "youNeedToAllowCameraAccess": { "message": "You need to allow camera access to use this feature." }, diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 59e30cdde..e2965ceb6 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -18,7 +18,7 @@ const { TRANSACTION_STATUS_APPROVED, } = require('./enums') -const { hexToBn, bnToHex } = require('../../lib/util') +const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util') /** Transaction Controller is an aggregate of sub-controllers and trackers @@ -244,7 +244,8 @@ class TransactionController extends EventEmitter { const originalTxMeta = this.txStateManager.getTx(originalTxId) const { txParams } = originalTxMeta const { gasPrice: lastGasPrice, from, nonce } = txParams - const newGasPrice = customGasPrice || bnToHex(hexToBn(lastGasPrice).mul(1.1)) + + const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10)) const newTxMeta = this.txStateManager.generateTxMeta({ txParams: { from, diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js new file mode 100644 index 000000000..56765698e --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js @@ -0,0 +1,32 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import CurrencyDisplay from '../../../currency-display' +import { ETH } from '../../../../constants/common' + +export default class CancelTransaction extends PureComponent { + static propTypes = { + className: PropTypes.string, + value: PropTypes.string, + } + + render () { + const { className, value } = this.props + console.log('VALUE', value) + + return ( +
+ + +
+ ) + } +} diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js new file mode 100644 index 000000000..1a9ae2e07 --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.js @@ -0,0 +1 @@ +export { default } from './cancel-transaction-gas-fee.component' diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss new file mode 100644 index 000000000..ce81dd448 --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/index.scss @@ -0,0 +1,17 @@ +.cancel-transaction-gas-fee { + background: #F1F4F9; + padding: 16px; + display: flex; + flex-direction: column; + align-items: center; + padding: 12px; + + &__eth { + font-size: 1.5rem; + font-weight: 500; + } + + &__fiat { + font-size: .75rem; + } +} diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js new file mode 100644 index 000000000..f5f0ea783 --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js @@ -0,0 +1,71 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal from '../../modal' +import CancelTransactionGasFee from './cancel-transaction-gas-fee' +import { SUBMITTED_STATUS } from '../../../constants/transactions' +import { decimalToHex } from '../../../helpers/conversions.util' +import { getHexGasTotal } from '../../../helpers/confirm-transaction/util' + +export default class CancelTransaction extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + createCancelTransaction: PropTypes.func, + hideModal: PropTypes.func, + showTransactionConfirmedModal: PropTypes.func, + transactionStatus: PropTypes.string, + defaultNewGasPrice: PropTypes.string, + } + + componentDidUpdate () { + const { transactionStatus, showTransactionConfirmedModal } = this.props + + if (transactionStatus !== SUBMITTED_STATUS) { + showTransactionConfirmedModal() + return + } + } + + handleSubmit = async () => { + const { createCancelTransaction, hideModal } = this.props + + await createCancelTransaction() + hideModal() + } + + handleCancel = () => { + this.props.hideModal() + } + + render () { + const { t } = this.context + const { defaultNewGasPrice: gasPrice } = this.props + const newGasFee = getHexGasTotal({ gasPrice, gasLimit: decimalToHex(21000) }) + + return ( + +
+
+ { t('cancellationGasFee') } +
+ +
+ { t('attemptToCancelDescription') } +
+
+
+ ) + } +} diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js b/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js new file mode 100644 index 000000000..15bff4bc6 --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction.container.js @@ -0,0 +1,55 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import R from 'ramda' +import { multiplyCurrencies } from '../../../conversion-util' +import { bnToHex } from '../../../helpers/conversions.util' +import withModalProps from '../../../higher-order-components/with-modal-props' +import CancelTransaction from './cancel-transaction.component' +import { showModal, hideModal, createCancelTransaction } from '../../../actions' + +const mapStateToProps = (state, ownProps) => { + const { metamask } = state + const { transactionId, originalGasPrice } = ownProps + const { selectedAddressTxList } = metamask + const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList) + const transactionStatus = transaction ? transaction.status : '' + + const defaultNewGasPrice = bnToHex(multiplyCurrencies(originalGasPrice, 1.1)) + + return { + transactionId, + transactionStatus, + originalGasPrice, + defaultNewGasPrice, + } +} + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => dispatch(hideModal()), + createCancelTransaction: txId => dispatch(createCancelTransaction(txId)), + showTransactionConfirmedModal: () => dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })), + } +} + +const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { transactionId, ...restStateProps } = stateProps + const { + createCancelTransaction: dispatchCreateCancelTransaction, + ...restDispatchProps + } = dispatchProps + + return { + ...restStateProps, + ...restDispatchProps, + ...ownProps, + createCancelTransaction: newGasPrice => { + return dispatchCreateCancelTransaction(transactionId, newGasPrice) + }, + } +} + +export default compose( + withModalProps, + connect(mapStateToProps, mapDispatchToProps, mergeProps), +)(CancelTransaction) diff --git a/ui/app/components/modals/cancel-transaction/index.js b/ui/app/components/modals/cancel-transaction/index.js new file mode 100644 index 000000000..7abc871ee --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/index.js @@ -0,0 +1 @@ +export { default } from './cancel-transaction.container' diff --git a/ui/app/components/modals/cancel-transaction/index.scss b/ui/app/components/modals/cancel-transaction/index.scss new file mode 100644 index 000000000..62e8e36fd --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/index.scss @@ -0,0 +1,18 @@ +@import './cancel-transaction-gas-fee/index'; + +.cancel-transaction { + &__title { + font-weight: 500; + padding-bottom: 16px; + text-align: center; + } + + &__description { + text-align: center; + font-size: .875rem; + } + + &__cancel-transaction-gas-fee-container { + margin-bottom: 16px; + } +} \ No newline at end of file diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js index 5a9f0f289..483c7062f 100644 --- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js +++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Button from '../../button' +import Modal from '../../modal' import { addressSummary } from '../../../util' import Identicon from '../../identicon' import genAccountLink from '../../../../lib/account-link' @@ -25,22 +25,22 @@ class ConfirmRemoveAccount extends Component { renderSelectedAccount () { const { identity } = this.props return ( -
-
+
+
-
- Name - {identity.name} +
+ Name + {identity.name}
-
- Public Address - { addressSummary(identity.address, 4, 4) } +
+ Public Address + { addressSummary(identity.address, 4, 4) }
-
+
-
-
- { `${t('removeAccount')}` }? -
- { this.renderSelectedAccount() } -
+ ) } } diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js index 4b194c995..59d48400d 100644 --- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js +++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js @@ -1,11 +1,12 @@ import { connect } from 'react-redux' +import { compose } from 'recompose' import ConfirmRemoveAccount from './confirm-remove-account.component' +import withModalProps from '../../../higher-order-components/with-modal-props' const { hideModal, removeAccount } = require('../../../actions') const mapStateToProps = state => { return { - identity: state.appState.modal.modalState.props.identity, network: state.metamask.network, } } @@ -17,4 +18,7 @@ const mapDispatchToProps = dispatch => { } } -export default connect(mapStateToProps, mapDispatchToProps)(ConfirmRemoveAccount) +export default compose( + withModalProps, + connect(mapStateToProps, mapDispatchToProps) +)(ConfirmRemoveAccount) diff --git a/ui/app/components/modals/confirm-remove-account/index.js b/ui/app/components/modals/confirm-remove-account/index.js index 9763fbe05..ecb5f7790 100644 --- a/ui/app/components/modals/confirm-remove-account/index.js +++ b/ui/app/components/modals/confirm-remove-account/index.js @@ -1,2 +1 @@ -import ConfirmRemoveAccount from './confirm-remove-account.container' -module.exports = ConfirmRemoveAccount +export { default } from './confirm-remove-account.container' diff --git a/ui/app/components/modals/confirm-remove-account/index.scss b/ui/app/components/modals/confirm-remove-account/index.scss new file mode 100644 index 000000000..3be3a1967 --- /dev/null +++ b/ui/app/components/modals/confirm-remove-account/index.scss @@ -0,0 +1,58 @@ +.confirm-remove-account { + &__description { + text-align: center; + font-size: .875rem; + } + + &__account { + border: 1px solid #b7b7b7; + border-radius: 4px; + padding: 10px; + display: flex; + margin-top: 10px; + margin-bottom: 20px; + width: 100%; + + &__identicon { + margin-right: 10px; + } + + &__name, + &__address { + margin-right: 10px; + font-size: 14px; + } + + &__name { + width: 100px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__label { + font-size: 11px; + display: block; + color: #9b9b9b; + } + + &__link { + margin-top: 14px; + + img { + width: 15px; + height: 15px; + } + } + + @media screen and (max-width: 575px) { + &__name { + width: 90px; + } + } + } + + &__link { + color: #2f9ae0; + } +} \ No newline at end of file diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js index 14a4da62a..f1a4542ac 100644 --- a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js +++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js @@ -1,8 +1,8 @@ -import React, { Component } from 'react' +import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import Button from '../../button' +import Modal, { ModalContent } from '../../modal' -class ConfirmResetAccount extends Component { +export default class ConfirmResetAccount extends PureComponent { static propTypes = { hideModal: PropTypes.func.isRequired, resetAccount: PropTypes.func.isRequired, @@ -12,7 +12,7 @@ class ConfirmResetAccount extends Component { t: PropTypes.func, } - handleReset () { + handleReset = () => { this.props.resetAccount() .then(() => this.props.hideModal()) } @@ -21,34 +21,18 @@ class ConfirmResetAccount extends Component { const { t } = this.context return ( -
-
-
- { `${t('resetAccount')}?` } -
-
- { t('resetAccountDescription') } -
-
-
- - -
-
+ this.props.hideModal()} + submitText={t('reset')} + cancelText={t('nevermind')} + submitType="secondary" + > + + ) } } - -export default ConfirmResetAccount diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js index 9630a5593..c8a7b8478 100644 --- a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js +++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js @@ -1,13 +1,16 @@ import { connect } from 'react-redux' +import { compose } from 'recompose' +import withModalProps from '../../../higher-order-components/with-modal-props' import ConfirmResetAccount from './confirm-reset-account.component' - -const { hideModal, resetAccount } = require('../../../actions') +import { resetAccount } from '../../../actions' const mapDispatchToProps = dispatch => { return { - hideModal: () => dispatch(hideModal()), resetAccount: () => dispatch(resetAccount()), } } -export default connect(null, mapDispatchToProps)(ConfirmResetAccount) +export default compose( + withModalProps, + connect(null, mapDispatchToProps) +)(ConfirmResetAccount) diff --git a/ui/app/components/modals/confirm-reset-account/index.js b/ui/app/components/modals/confirm-reset-account/index.js index c812ffc55..ca4d9c5bf 100644 --- a/ui/app/components/modals/confirm-reset-account/index.js +++ b/ui/app/components/modals/confirm-reset-account/index.js @@ -1,2 +1 @@ -import ConfirmResetAccount from './confirm-reset-account.container' -module.exports = ConfirmResetAccount +export { default } from './confirm-reset-account.container' diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss index 0acccf172..45453a582 100644 --- a/ui/app/components/modals/index.scss +++ b/ui/app/components/modals/index.scss @@ -1,108 +1,9 @@ +@import './cancel-transaction/index'; + +@import './confirm-remove-account/index'; + @import './customize-gas/index'; @import './qr-scanner/index'; -.modal-container { - width: 100%; - height: 100%; - background-color: #fff; - display: flex; - flex-flow: column; - border-radius: 8px; - - &__title { - font-size: 1.5rem; - font-weight: 500; - padding: 16px 0; - text-align: center; - } - - &__description { - text-align: center; - font-size: .875rem; - } - - &__account { - border: 1px solid #b7b7b7; - border-radius: 4px; - padding: 10px; - display: flex; - margin-top: 10px; - margin-bottom: 20px; - width: 100%; - - &__identicon { - margin-right: 10px; - } - - &__name, - &__address { - margin-right: 10px; - font-size: 14px; - } - - &__name { - width: 100px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - &__label { - font-size: 11px; - display: block; - color: #9b9b9b; - } - - &__link { - margin-top: 14px; - - img { - width: 15px; - height: 15px; - } - } - - @media screen and (max-width: 575px) { - &__name { - width: 90px; - } - } - } - - &__link { - color: #2f9ae0; - } - - &__content { - overflow-y: auto; - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - padding: 32px; - - @media screen and (max-width: 575px) { - justify-content: center; - padding: 28px 20px; - } - } - - &__footer { - display: flex; - flex-flow: row; - justify-content: center; - border-top: 1px solid #d2d8dd; - padding: 16px; - flex: 0 0 auto; - - &-button { - min-width: 0; - margin-right: 16px; - - &:last-of-type { - margin-right: 0; - } - } - } -} +@import './transaction-confirmed/index'; diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 5dda50e52..2aec89326 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -19,14 +19,14 @@ const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js') const HideTokenConfirmationModal = require('./hide-token-confirmation-modal') const CustomizeGasModal = require('../customize-gas-modal') const NotifcationModal = require('./notification-modal') -const ConfirmResetAccount = require('./confirm-reset-account') -const ConfirmRemoveAccount = require('./confirm-remove-account') const QRScanner = require('./qr-scanner') -const TransactionConfirmed = require('./transaction-confirmed') -const WelcomeBeta = require('./welcome-beta') -const Notification = require('./notification') +import ConfirmRemoveAccount from './confirm-remove-account' +import ConfirmResetAccount from './confirm-reset-account' +import TransactionConfirmed from './transaction-confirmed' import ConfirmCustomizeGasModal from './customize-gas' +import CancelTransaction from './cancel-transaction' +import WelcomeBeta from './welcome-beta' const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', @@ -199,11 +199,7 @@ const MODALS = { }, BETA_UI_NOTIFICATION_MODAL: { - contents: [ - h(Notification, [ - h(WelcomeBeta), - ]), - ], + contents: h(WelcomeBeta), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -307,9 +303,7 @@ const MODALS = { }, CONFIRM_CUSTOMIZE_GAS: { - contents: [ - h(ConfirmCustomizeGasModal), - ], + contents: h(ConfirmCustomizeGasModal), mobileModalStyle: { width: '100vw', height: '100vh', @@ -332,11 +326,7 @@ const MODALS = { TRANSACTION_CONFIRMED: { disableBackdropClick: true, - contents: [ - h(Notification, [ - h(TransactionConfirmed), - ]), - ], + contents: h(TransactionConfirmed), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -347,6 +337,7 @@ const MODALS = { borderRadius: '8px', }, }, + QR_SCANNER: { contents: h(QRScanner), mobileModalStyle: { @@ -360,6 +351,19 @@ const MODALS = { }, }, + CANCEL_TRANSACTION: { + contents: h(CancelTransaction), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + DEFAULT: { contents: [], mobileModalStyle: {}, diff --git a/ui/app/components/modals/notification/index.js b/ui/app/components/modals/notification/index.js deleted file mode 100644 index d60a3129b..000000000 --- a/ui/app/components/modals/notification/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import Notification from './notification.container' -module.exports = Notification diff --git a/ui/app/components/modals/notification/notification.component.js b/ui/app/components/modals/notification/notification.component.js deleted file mode 100644 index 1af2f3ca8..000000000 --- a/ui/app/components/modals/notification/notification.component.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Button from '../../button' - -const Notification = (props, context) => { - return ( -
- { props.children } -
- -
-
- ) -} - -Notification.propTypes = { - onHide: PropTypes.func.isRequired, - children: PropTypes.element, -} - -Notification.contextTypes = { - t: PropTypes.func, -} - -export default Notification diff --git a/ui/app/components/modals/notification/notification.container.js b/ui/app/components/modals/notification/notification.container.js deleted file mode 100644 index 5b98714da..000000000 --- a/ui/app/components/modals/notification/notification.container.js +++ /dev/null @@ -1,38 +0,0 @@ -import { connect } from 'react-redux' -import Notification from './notification.component' - -const { hideModal } = require('../../../actions') - -const mapStateToProps = state => { - const { appState: { modal: { modalState: { props } } } } = state - const { onHide } = props - return { - onHide, - } -} - -const mapDispatchToProps = dispatch => { - return { - hideModal: () => dispatch(hideModal()), - } -} - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { onHide, ...otherStateProps } = stateProps - const { hideModal, ...otherDispatchProps } = dispatchProps - - return { - ...otherStateProps, - ...otherDispatchProps, - ...ownProps, - onHide: () => { - hideModal() - - if (onHide && typeof onHide === 'function') { - onHide() - } - }, - } -} - -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notification) diff --git a/ui/app/components/modals/transaction-confirmed/index.js b/ui/app/components/modals/transaction-confirmed/index.js index cee8da7f8..7776b969e 100644 --- a/ui/app/components/modals/transaction-confirmed/index.js +++ b/ui/app/components/modals/transaction-confirmed/index.js @@ -1,2 +1 @@ -import TransactionConfirmed from './transaction-confirmed.component' -module.exports = TransactionConfirmed +export { default } from './transaction-confirmed.container' diff --git a/ui/app/components/modals/transaction-confirmed/index.scss b/ui/app/components/modals/transaction-confirmed/index.scss new file mode 100644 index 000000000..c97371fb6 --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/index.scss @@ -0,0 +1,22 @@ +.transaction-confirmed { + &__title { + font-size: 1.5rem; + font-weight: 500; + padding: 16px 0; + text-align: center; + } + + &__description { + text-align: center; + font-size: .875rem; + } + + &__content { + overflow-y: auto; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; + } +} diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js index c1c8a2976..0a98eb1a1 100644 --- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js +++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js @@ -1,24 +1,45 @@ -import React from 'react' +import React, { PureComponent } from 'react' import PropTypes from 'prop-types' +import Modal from '../../modal' -const TransactionConfirmed = (props, context) => { - const { t } = context +export default class TransactionConfirmed extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } - return ( -
- -
- { `${t('confirmed')}!` } -
-
- { t('initialTransactionConfirmed') } -
-
- ) + static propTypes = { + onSubmit: PropTypes.func, + hideModal: PropTypes.func, + } + + handleSubmit = () => { + const { hideModal, onSubmit } = this.props + + hideModal() + + if (onSubmit && typeof onSubmit === 'function') { + onSubmit() + } + } + + render () { + const { t } = this.context + + return ( + +
+ +
+ { `${t('confirmed')}!` } +
+
+ { t('initialTransactionConfirmed') } +
+
+
+ ) + } } - -TransactionConfirmed.contextTypes = { - t: PropTypes.func, -} - -export default TransactionConfirmed diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js new file mode 100644 index 000000000..d4e39681a --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js @@ -0,0 +1,4 @@ +import TransactionConfirmed from './transaction-confirmed.component' +import withModalProps from '../../../higher-order-components/with-modal-props' + +export default withModalProps(TransactionConfirmed) diff --git a/ui/app/components/modals/welcome-beta/index.js b/ui/app/components/modals/welcome-beta/index.js index 515c9cdaf..49e45b9d7 100644 --- a/ui/app/components/modals/welcome-beta/index.js +++ b/ui/app/components/modals/welcome-beta/index.js @@ -1,2 +1 @@ -import WelcomeBeta from './welcome-beta.component' -module.exports = WelcomeBeta +export { default } from './welcome-beta.container' diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.component.js b/ui/app/components/modals/welcome-beta/welcome-beta.component.js index 61571723a..ef1799164 100644 --- a/ui/app/components/modals/welcome-beta/welcome-beta.component.js +++ b/ui/app/components/modals/welcome-beta/welcome-beta.component.js @@ -1,18 +1,21 @@ import React from 'react' import PropTypes from 'prop-types' +import Modal, { ModalContent } from '../../modal' const TransactionConfirmed = (props, context) => { const { t } = context + const { hideModal } = props return ( -
-
- { `${t('uiWelcome')}` } -
-
- { t('uiWelcomeMessage') } -
-
+ hideModal()} + submitText={t('ok')} + > + + ) } @@ -20,4 +23,8 @@ TransactionConfirmed.contextTypes = { t: PropTypes.func, } +TransactionConfirmed.propTypes = { + hideModal: PropTypes.func, +} + export default TransactionConfirmed diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.container.js b/ui/app/components/modals/welcome-beta/welcome-beta.container.js new file mode 100644 index 000000000..c5123ad47 --- /dev/null +++ b/ui/app/components/modals/welcome-beta/welcome-beta.container.js @@ -0,0 +1,4 @@ +import WelcomeBeta from './welcome-beta.component' +import withModalProps from '../../../higher-order-components/with-modal-props' + +export default withModalProps(WelcomeBeta) diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index 56cfbccc8..40d8faf50 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -8,6 +8,7 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY, TRANSACTION_ERROR_KEY, } from '../../../constants/error-keys' +import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions' export default class ConfirmTransactionBase extends Component { static contextTypes = { @@ -85,9 +86,9 @@ export default class ConfirmTransactionBase extends Component { clearConfirmTransaction, } = this.props - if (transactionStatus === 'dropped') { + if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) { showTransactionConfirmedModal({ - onHide: () => { + onSubmit: () => { clearConfirmTransaction() history.push(DEFAULT_ROUTE) }, diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js index 8f54c8040..ae31eba17 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -97,8 +97,8 @@ const mapDispatchToProps = dispatch => { return { clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), clearSend: () => dispatch(clearSend()), - showTransactionConfirmedModal: ({ onHide }) => { - return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onHide })) + showTransactionConfirmedModal: ({ onSubmit }) => { + return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onSubmit })) }, showCustomizeGasModal: ({ txData, onSubmit, validate }) => { return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate })) diff --git a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js index f65ff4d55..13cb51349 100644 --- a/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js +++ b/ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js @@ -13,7 +13,9 @@ export default class TransactionListItemDetails extends PureComponent { } static propTypes = { + onCancel: PropTypes.func, onRetry: PropTypes.func, + showCancel: PropTypes.bool, showRetry: PropTypes.bool, transaction: PropTypes.object, } @@ -27,6 +29,13 @@ export default class TransactionListItemDetails extends PureComponent { this.setState({ showTransactionDetails: true }) } + handleCancel = event => { + const { onCancel } = this.props + + event.stopPropagation() + onCancel() + } + handleRetry = event => { const { onRetry } = this.props @@ -36,7 +45,7 @@ export default class TransactionListItemDetails extends PureComponent { render () { const { t } = this.context - const { transaction, showRetry } = this.props + const { transaction, showCancel, showRetry } = this.props const { txParams: { to, from } = {} } = transaction return ( @@ -55,6 +64,17 @@ export default class TransactionListItemDetails extends PureComponent { ) } + { + showCancel && ( + + ) + }
{ pendingTransactions.map((transaction, index) => ( - this.renderTransaction(transaction, index) + this.renderTransaction(transaction, index, true) )) }
@@ -78,7 +78,7 @@ export default class TransactionList extends PureComponent { ) } - renderTransaction (transaction, index) { + renderTransaction (transaction, index, showCancel) { const { selectedToken, assetImages } = this.props return transaction.key === TRANSACTION_TYPE_SHAPESHIFT @@ -92,6 +92,7 @@ export default class TransactionList extends PureComponent { transaction={transaction} key={transaction.id} showRetry={this.shouldShowRetry(transaction)} + showCancel={showCancel} token={selectedToken} assetImages={assetImages} /> diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 112ea6bca..0784a872e 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -104,7 +104,7 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { if (prevTx && prevTx.status === 'dropped') { this.props.dispatch(actions.showModal({ name: 'TRANSACTION_CONFIRMED', - onHide: () => history.push(DEFAULT_ROUTE), + onSubmit: () => history.push(DEFAULT_ROUTE), })) return diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js index df6c4c8a4..2dc061091 100644 --- a/ui/app/constants/transactions.js +++ b/ui/app/constants/transactions.js @@ -18,5 +18,6 @@ export const SEND_TOKEN_ACTION_KEY = 'sentTokens' export const TRANSFER_FROM_ACTION_KEY = 'transferFrom' export const SIGNATURE_REQUEST_KEY = 'signatureRequest' export const UNKNOWN_FUNCTION_KEY = 'unknownFunction' +export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt' export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift' diff --git a/ui/app/helpers/conversions.util.js b/ui/app/helpers/conversions.util.js index 5204faa1f..20ef9e35b 100644 --- a/ui/app/helpers/conversions.util.js +++ b/ui/app/helpers/conversions.util.js @@ -1,6 +1,11 @@ +import ethUtil from 'ethereumjs-util' import { conversionUtil } from '../conversion-util' import { ETH, GWEI, WEI } from '../constants/common' +export function bnToHex (inputBn) { + return ethUtil.addHexPrefix(inputBn.toString(16)) +} + export function hexToDecimal (hexValue) { return conversionUtil(hexValue, { fromNumericBase: 'hex', @@ -8,6 +13,13 @@ export function hexToDecimal (hexValue) { }) } +export function decimalToHex (decimal) { + return conversionUtil(decimal, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }) +} + export function getEthConversionFromWeiHex ({ value, conversionRate, numberOfDecimals = 6 }) { const denominations = [ETH, GWEI, WEI] diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js index 54bb3bcb9..8b87bb538 100644 --- a/ui/app/helpers/transactions.util.js +++ b/ui/app/helpers/transactions.util.js @@ -14,6 +14,7 @@ import { TRANSFER_FROM_ACTION_KEY, SIGNATURE_REQUEST_KEY, UNKNOWN_FUNCTION_KEY, + CANCEL_ATTEMPT_ACTION_KEY, } from '../constants/transactions' import { addCurrencies } from '../conversion-util' @@ -44,7 +45,11 @@ export function isConfirmDeployContract (txData = {}) { } export async function getTransactionActionKey (transaction, methodData) { - const { txParams: { data, to } = {}, msgParams } = transaction + const { txParams: { data, to } = {}, msgParams, type } = transaction + + if (type === 'cancel') { + return CANCEL_ATTEMPT_ACTION_KEY + } if (msgParams) { return SIGNATURE_REQUEST_KEY From 95e1eff4ca3d784d6fcba21035a535f8f3398cdc Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 18:32:35 -0700 Subject: [PATCH 06/11] Add TransactionDetails modal --- app/_locales/en/messages.json | 3 ++ ui/app/components/modal/index.scss | 8 ++-- ui/app/components/modal/modal.component.js | 18 +++++++- ui/app/components/modals/modal.js | 14 ++++++ .../modals/transaction-details/index.js | 1 + .../transaction-details.component.js | 43 +++++++++++++++++++ .../transaction-details.container.js | 4 ++ .../transaction-activity-log/index.scss | 9 ++-- .../transaction-list-item.component.js | 24 ++++++++++- .../transaction-list-item.container.js | 10 +++++ 10 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 ui/app/components/modals/transaction-details/index.js create mode 100644 ui/app/components/modals/transaction-details/transaction-details.component.js create mode 100644 ui/app/components/modals/transaction-details/transaction-details.container.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index ed2e7a119..570284126 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1112,6 +1112,9 @@ "transactionCreated": { "message": "Transaction created with a value of $1 on $2." }, + "transactionWithNonce": { + "message": "Transaction $1" + }, "transactionDropped": { "message": "Transaction dropped on $2." }, diff --git a/ui/app/components/modal/index.scss b/ui/app/components/modal/index.scss index e57156d71..2beb14633 100644 --- a/ui/app/components/modal/index.scss +++ b/ui/app/components/modal/index.scss @@ -8,12 +8,13 @@ flex-flow: column; border-radius: 8px; + @media screen and (max-width: 575px) { + max-height: 450px; + } + &__content { overflow-y: auto; flex: 1; - display: flex; - flex-direction: column; - align-items: center; padding: 16px 32px; @media screen and (max-width: 575px) { @@ -28,6 +29,7 @@ padding: 12px; justify-content: center; border-bottom: 1px solid #d2d8dd; + flex: 0 0 auto; } &__header-close::after { diff --git a/ui/app/components/modal/modal.component.js b/ui/app/components/modal/modal.component.js index 81bdd0010..f9d8c5867 100644 --- a/ui/app/components/modal/modal.component.js +++ b/ui/app/components/modal/modal.component.js @@ -22,6 +22,22 @@ export default class Modal extends PureComponent { cancelType: 'default', } + handleClose = () => { + const { onCancel, onSubmit } = this.props + + /** + * The close button should be used to dismiss the modal, without performing any actions, which + * is typically what props.onCancel does. However, if props.onCancel is undefined, that should + * mean that the modal is a simple notification modal and props.onSubmit can be used to dismiss + * it. + */ + if (onCancel && typeof onCancel === 'function') { + onCancel() + } else { + onSubmit() + } + } + render () { const { children, @@ -44,7 +60,7 @@ export default class Modal extends PureComponent {
onCancel()} + onClick={this.handleClose} />
) diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 2aec89326..6054002c8 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -27,6 +27,7 @@ import TransactionConfirmed from './transaction-confirmed' import ConfirmCustomizeGasModal from './customize-gas' import CancelTransaction from './cancel-transaction' import WelcomeBeta from './welcome-beta' +import TransactionDetails from './transaction-details' const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', @@ -364,6 +365,19 @@ const MODALS = { }, }, + TRANSACTION_DETAILS: { + contents: h(TransactionDetails), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + DEFAULT: { contents: [], mobileModalStyle: {}, diff --git a/ui/app/components/modals/transaction-details/index.js b/ui/app/components/modals/transaction-details/index.js new file mode 100644 index 000000000..1fc42c662 --- /dev/null +++ b/ui/app/components/modals/transaction-details/index.js @@ -0,0 +1 @@ +export { default } from './transaction-details.container' diff --git a/ui/app/components/modals/transaction-details/transaction-details.component.js b/ui/app/components/modals/transaction-details/transaction-details.component.js new file mode 100644 index 000000000..7eec028fe --- /dev/null +++ b/ui/app/components/modals/transaction-details/transaction-details.component.js @@ -0,0 +1,43 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal from '../../modal' +import TransactionListItemDetails from '../../transaction-list-item-details' +import { hexToDecimal } from '../../../helpers/conversions.util' + +export default class TransactionConfirmed extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + hideModal: PropTypes.func, + transaction: PropTypes.object, + onRetry: PropTypes.func, + showRetry: PropTypes.bool, + onCancel: PropTypes.func, + showCancel: PropTypes.bool, + } + + render () { + const { t } = this.context + const { transaction, onRetry, showRetry, onCancel, showCancel, hideModal } = this.props + const { txParams: { nonce } = {} } = transaction + const decimalNonce = nonce && hexToDecimal(nonce) + + return ( + hideModal()} + submitText={t('ok')} + headerText={t('transactionWithNonce', [`#${decimalNonce}`])} + > + onRetry()} + showRetry={showRetry} + onCancel={() => onCancel()} + showCancel={showCancel} + /> + + ) + } +} diff --git a/ui/app/components/modals/transaction-details/transaction-details.container.js b/ui/app/components/modals/transaction-details/transaction-details.container.js new file mode 100644 index 000000000..f212920bb --- /dev/null +++ b/ui/app/components/modals/transaction-details/transaction-details.container.js @@ -0,0 +1,4 @@ +import TransactionDetails from './transaction-details.component' +import withModalProps from '../../../higher-order-components/with-modal-props' + +export default withModalProps(TransactionDetails) diff --git a/ui/app/components/transaction-activity-log/index.scss b/ui/app/components/transaction-activity-log/index.scss index a9933dac6..27f3006b3 100644 --- a/ui/app/components/transaction-activity-log/index.scss +++ b/ui/app/components/transaction-activity-log/index.scss @@ -51,9 +51,12 @@ &__activity-text { color: $scorpion; font-size: .75rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + @media screen and (min-width: $break-large) { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } } &__value { diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 72389c95b..799562638 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -9,6 +9,7 @@ import TransactionListItemDetails from '../transaction-list-item-details' import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions' import { ETH } from '../../constants/common' +import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums' export default class TransactionListItem extends PureComponent { static propTypes = { @@ -21,6 +22,7 @@ export default class TransactionListItem extends PureComponent { showCancelModal: PropTypes.func, showCancel: PropTypes.bool, showRetry: PropTypes.bool, + showTransactionDetailsModal: PropTypes.func, token: PropTypes.object, tokenData: PropTypes.object, transaction: PropTypes.object, @@ -32,16 +34,34 @@ export default class TransactionListItem extends PureComponent { } handleClick = () => { - const { transaction, history } = this.props + const { + transaction, + history, + showTransactionDetailsModal, + methodData, + showCancel, + showRetry, + } = this.props const { id, status } = transaction const { showTransactionDetails } = this.state + const windowType = window.METAMASK_UI_TYPE if (status === UNAPPROVED_STATUS) { history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`) return } - this.setState({ showTransactionDetails: !showTransactionDetails }) + if (windowType === ENVIRONMENT_TYPE_FULLSCREEN) { + this.setState({ showTransactionDetails: !showTransactionDetails }) + } else { + showTransactionDetailsModal({ + transaction, + onRetry: this.handleRetry, + showRetry: showRetry && methodData.done, + onCancel: this.handleCancel, + showCancel, + }) + } } handleCancel = () => { diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js index 62ed7a73f..72f5f5d61 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.container.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js @@ -28,6 +28,16 @@ const mapDispatchToProps = dispatch => { showCancelModal: (transactionId, originalGasPrice) => { return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice })) }, + showTransactionDetailsModal: ({ transaction, onRetry, showRetry, onCancel, showCancel }) => { + return dispatch(showModal({ + name: 'TRANSACTION_DETAILS', + transaction, + onRetry, + showRetry, + onCancel, + showCancel, + })) + }, } } From 27799363177bd1c5bc390d5903c1915d77bf82aa Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 19:35:47 -0700 Subject: [PATCH 07/11] Add transition effect to TransactionListItemDetails expander --- .../transaction-list-item/index.scss | 11 +++++++ .../transaction-list-item.component.js | 31 +++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss index 427686c29..df513bb0c 100644 --- a/ui/app/components/transaction-list-item/index.scss +++ b/ui/app/components/transaction-list-item/index.scss @@ -6,6 +6,7 @@ justify-content: center; align-items: center; flex-direction: column; + background: $white; &__grid { cursor: pointer; @@ -117,4 +118,14 @@ background: #f3f4f7; width: 100%; } + + &__expander { + max-height: 0px; + width: 100%; + + &--show { + max-height: 1000px; + transition: max-height 300ms ease-out; + } + } } diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js index 799562638..9dfba875e 100644 --- a/ui/app/components/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' +import classnames from 'classnames' import Identicon from '../identicon' import TransactionStatus from '../transaction-status' import TransactionAction from '../transaction-action' @@ -176,19 +177,23 @@ export default class TransactionListItem extends PureComponent { { this.renderPrimaryCurrency() } { this.renderSecondaryCurrency() }
- { - showTransactionDetails && ( -
- -
- ) - } +
+ { + showTransactionDetails && ( +
+ +
+ ) + } +
) } From 6b3a4e8259dbf45b207f57e8619c4aaa5bd1c35e Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 20:05:02 -0700 Subject: [PATCH 08/11] Fix unit tests --- .../tests/transaction-action.component.test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/transaction-action/tests/transaction-action.component.test.js index 218792847..9352c7b43 100644 --- a/ui/app/components/transaction-action/tests/transaction-action.component.test.js +++ b/ui/app/components/transaction-action/tests/transaction-action.component.test.js @@ -5,10 +5,9 @@ import sinon from 'sinon' import TransactionAction from '../transaction-action.component' describe('TransactionAction Component', () => { - const tOrDefault = key => key + const t = key => key global.eth = { getCode: sinon.stub().callsFake(address => { - console.log('CALLED') const code = address === 'approveAddress' ? 'contract' : '0x' return Promise.resolve(code) }), @@ -36,7 +35,7 @@ describe('TransactionAction Component', () => { methodData={methodData} transaction={transaction} className="transaction-action" - />, { context: { tOrDefault }}) + />, { context: { t }}) assert.equal(wrapper.find('.transaction-action').length, 1) assert.equal(wrapper.text(), '--') @@ -63,7 +62,7 @@ describe('TransactionAction Component', () => { methodData={methodData} transaction={transaction} className="transaction-action" - />, { context: { tOrDefault }}) + />, { context: { t }}) assert.equal(wrapper.find('.transaction-action').length, 1) wrapper.setState({ transactionAction: 'sentEther' }) @@ -102,7 +101,7 @@ describe('TransactionAction Component', () => { methodData={methodData} transaction={transaction} className="transaction-action" - />, { context: { tOrDefault }}) + />, { context: { t }}) assert.equal(wrapper.find('.transaction-action').length, 1) wrapper.setState({ transactionAction: 'approve' }) From 77e8eac4b380b35f4ab2e6abd82fe929ec7f7c1b Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 21:23:54 -0700 Subject: [PATCH 09/11] Fix integration tests --- test/integration/lib/tx-list-items.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js index 8f291c7b4..f64a8d7de 100644 --- a/test/integration/lib/tx-list-items.js +++ b/test/integration/lib/tx-list-items.js @@ -34,7 +34,7 @@ async function runTxListItemsTest (assert, done) { const retryTxGrid = await findAsync($(txListItems[1]), '.transaction-list-item__grid') retryTxGrid[0].click() - const retryTxDetails = await findAsync($(txListItems[1]), '.transaction-list-item-details') + const retryTxDetails = await findAsync($, '.transaction-list-item-details') const headerButtons = await findAsync($(retryTxDetails[0]), '.transaction-list-item-details__header-button') assert.equal(headerButtons[0].textContent, 'speed up') From 2cfdc95eebc3e0a878017090f22e5136cff709a6 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 19 Sep 2018 14:30:52 -0700 Subject: [PATCH 10/11] Add unit tests --- .../tests/error-message.component.test.js | 36 ++++++ .../modal-content/modal-content.component.js | 20 ++- .../tests/modal-content.component.test.js | 44 +++++++ .../modal/tests/modal.component.test.js | 117 ++++++++++++++++++ .../cancel-transaction-gas-fee.component.js | 7 +- ...ncel-transaction-gas-fee.component.test.js | 27 ++++ .../cancel-transaction.component.js | 7 +- .../cancel-transaction.component.test.js | 56 +++++++++ .../tests/with-modal-props.test.js | 43 +++++++ 9 files changed, 342 insertions(+), 15 deletions(-) create mode 100644 ui/app/components/error-message/tests/error-message.component.test.js create mode 100644 ui/app/components/modal/modal-content/tests/modal-content.component.test.js create mode 100644 ui/app/components/modal/tests/modal.component.test.js create mode 100644 ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js create mode 100644 ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js create mode 100644 ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js diff --git a/ui/app/components/error-message/tests/error-message.component.test.js b/ui/app/components/error-message/tests/error-message.component.test.js new file mode 100644 index 000000000..8c5347173 --- /dev/null +++ b/ui/app/components/error-message/tests/error-message.component.test.js @@ -0,0 +1,36 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import ErrorMessage from '../error-message.component' + +describe('ErrorMessage Component', () => { + const t = key => `translate ${key}` + + it('should render a message from props.errorMessage', () => { + const wrapper = shallow( + , + { context: { t }} + ) + + assert.ok(wrapper) + assert.equal(wrapper.find('.error-message').length, 1) + assert.equal(wrapper.find('.error-message__icon').length, 1) + assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: This is an error.') + }) + + it('should render a message translated from props.errorKey', () => { + const wrapper = shallow( + , + { context: { t }} + ) + + assert.ok(wrapper) + assert.equal(wrapper.find('.error-message').length, 1) + assert.equal(wrapper.find('.error-message__icon').length, 1) + assert.equal(wrapper.find('.error-message__text').text(), 'ALERT: translate testKey') + }) +}) diff --git a/ui/app/components/modal/modal-content/modal-content.component.js b/ui/app/components/modal/modal-content/modal-content.component.js index 8beb854e0..ecec0ee5b 100644 --- a/ui/app/components/modal/modal-content/modal-content.component.js +++ b/ui/app/components/modal/modal-content/modal-content.component.js @@ -12,12 +12,20 @@ export default class ModalContent extends PureComponent { return (
-
- { title } -
-
- { description } -
+ { + title && ( +
+ { title } +
+ ) + } + { + description && ( +
+ { description } +
+ ) + }
) } diff --git a/ui/app/components/modal/modal-content/tests/modal-content.component.test.js b/ui/app/components/modal/modal-content/tests/modal-content.component.test.js new file mode 100644 index 000000000..17af09f45 --- /dev/null +++ b/ui/app/components/modal/modal-content/tests/modal-content.component.test.js @@ -0,0 +1,44 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import ModalContent from '../modal-content.component' + +describe('ModalContent Component', () => { + it('should render a title', () => { + const wrapper = shallow( + + ) + + assert.equal(wrapper.find('.modal-content__title').length, 1) + assert.equal(wrapper.find('.modal-content__title').text(), 'Modal Title') + assert.equal(wrapper.find('.modal-content__description').length, 0) + }) + + it('should render a description', () => { + const wrapper = shallow( + + ) + + assert.equal(wrapper.find('.modal-content__title').length, 0) + assert.equal(wrapper.find('.modal-content__description').length, 1) + assert.equal(wrapper.find('.modal-content__description').text(), 'Modal Description') + }) + + it('should render both a title and a description', () => { + const wrapper = shallow( + + ) + + assert.equal(wrapper.find('.modal-content__title').length, 1) + assert.equal(wrapper.find('.modal-content__title').text(), 'Modal Title') + assert.equal(wrapper.find('.modal-content__description').length, 1) + assert.equal(wrapper.find('.modal-content__description').text(), 'Modal Description') + }) +}) diff --git a/ui/app/components/modal/tests/modal.component.test.js b/ui/app/components/modal/tests/modal.component.test.js new file mode 100644 index 000000000..31457751f --- /dev/null +++ b/ui/app/components/modal/tests/modal.component.test.js @@ -0,0 +1,117 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import Modal from '../modal.component' +import Button from '../../button' + +describe('Modal Component', () => { + it('should render a modal with a submit button', () => { + const wrapper = shallow() + + assert.equal(wrapper.find('.modal-container').length, 1) + const buttons = wrapper.find(Button) + assert.equal(buttons.length, 1) + assert.equal(buttons.at(0).props().type, 'primary') + }) + + it('should render a modal with a cancel and a submit button', () => { + const handleCancel = sinon.spy() + const handleSubmit = sinon.spy() + const wrapper = shallow( + + ) + + const buttons = wrapper.find(Button) + assert.equal(buttons.length, 2) + const cancelButton = buttons.at(0) + const submitButton = buttons.at(1) + + assert.equal(cancelButton.props().type, 'default') + assert.equal(cancelButton.props().children, 'Cancel') + assert.equal(handleCancel.callCount, 0) + cancelButton.simulate('click') + assert.equal(handleCancel.callCount, 1) + + assert.equal(submitButton.props().type, 'primary') + assert.equal(submitButton.props().children, 'Submit') + assert.equal(handleSubmit.callCount, 0) + submitButton.simulate('click') + assert.equal(handleSubmit.callCount, 1) + }) + + it('should render a modal with different button types', () => { + const wrapper = shallow( + {}} + cancelText="Cancel" + cancelType="secondary" + onSubmit={() => {}} + submitText="Submit" + submitType="confirm" + /> + ) + + const buttons = wrapper.find(Button) + assert.equal(buttons.length, 2) + assert.equal(buttons.at(0).props().type, 'secondary') + assert.equal(buttons.at(1).props().type, 'confirm') + }) + + it('should render a modal with children', () => { + const wrapper = shallow( + {}} + cancelText="Cancel" + onSubmit={() => {}} + submitText="Submit" + > +
+ + ) + + assert.ok(wrapper.find('.test-class')) + }) + + it('should render a modal with a header', () => { + const handleCancel = sinon.spy() + const handleSubmit = sinon.spy() + const wrapper = shallow( + + ) + + assert.ok(wrapper.find('.modal-container__header')) + assert.equal(wrapper.find('.modal-container__header-text').text(), 'My Header') + assert.equal(handleCancel.callCount, 0) + assert.equal(handleSubmit.callCount, 0) + wrapper.find('.modal-container__header-close').simulate('click') + assert.equal(handleCancel.callCount, 1) + assert.equal(handleSubmit.callCount, 0) + }) + + it('should call onSubmit when onCancel is undefined and the header close button is clicked', () => { + const handleSubmit = sinon.spy() + const wrapper = shallow( + + ) + + assert.equal(handleSubmit.callCount, 0) + wrapper.find('.modal-container__header-close').simulate('click') + assert.equal(handleSubmit.callCount, 1) + }) +}) diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js index 56765698e..b082db1d0 100644 --- a/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js @@ -1,21 +1,18 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import classnames from 'classnames' import CurrencyDisplay from '../../../currency-display' import { ETH } from '../../../../constants/common' export default class CancelTransaction extends PureComponent { static propTypes = { - className: PropTypes.string, value: PropTypes.string, } render () { - const { className, value } = this.props - console.log('VALUE', value) + const { value } = this.props return ( -
+
{ + it('should render', () => { + const wrapper = shallow( + + ) + + assert.ok(wrapper) + assert.equal(wrapper.find(CurrencyDisplay).length, 2) + const ethDisplay = wrapper.find(CurrencyDisplay).at(0) + const fiatDisplay = wrapper.find(CurrencyDisplay).at(1) + + assert.equal(ethDisplay.props().value, '0x3b9aca00') + assert.equal(ethDisplay.props().currency, 'ETH') + assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth') + + assert.equal(fiatDisplay.props().value, '0x3b9aca00') + assert.equal(fiatDisplay.props().className, 'cancel-transaction-gas-fee__fiat') + }) +}) diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js index f5f0ea783..a30fbea96 100644 --- a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js @@ -57,10 +57,9 @@ export default class CancelTransaction extends PureComponent {
{ t('cancellationGasFee') }
- +
+ +
{ t('attemptToCancelDescription') }
diff --git a/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js b/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js new file mode 100644 index 000000000..053223467 --- /dev/null +++ b/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js @@ -0,0 +1,56 @@ +import React from 'react' +import assert from 'assert' +import { shallow } from 'enzyme' +import sinon from 'sinon' +import CancelTransaction from '../cancel-transaction.component' +import CancelTransactionGasFee from '../cancel-transaction-gas-fee' +import Modal from '../../../modal' + +describe('CancelTransaction Component', () => { + const t = key => key + + it('should render a CancelTransaction modal', () => { + const wrapper = shallow( + , + { context: { t }} + ) + + assert.ok(wrapper) + assert.equal(wrapper.find(Modal).length, 1) + assert.equal(wrapper.find(CancelTransactionGasFee).length, 1) + assert.equal(wrapper.find(CancelTransactionGasFee).props().value, '0x1319718a5000') + assert.equal(wrapper.find('.cancel-transaction__title').text(), 'cancellationGasFee') + assert.equal(wrapper.find('.cancel-transaction__description').text(), 'attemptToCancelDescription') + }) + + it('should pass the correct props to the Modal component', async () => { + const createCancelTransactionSpy = sinon.stub().callsFake(() => Promise.resolve()) + const hideModalSpy = sinon.spy() + + const wrapper = shallow( + , + { context: { t }} + ) + + assert.equal(wrapper.find(Modal).length, 1) + const modalProps = wrapper.find(Modal).props() + + assert.equal(modalProps.headerText, 'attemptToCancel') + assert.equal(modalProps.submitText, 'yesLetsTry') + assert.equal(modalProps.cancelText, 'nevermind') + + assert.equal(createCancelTransactionSpy.callCount, 0) + assert.equal(hideModalSpy.callCount, 0) + await modalProps.onSubmit() + assert.equal(createCancelTransactionSpy.callCount, 1) + assert.equal(hideModalSpy.callCount, 1) + modalProps.onCancel() + assert.equal(hideModalSpy.callCount, 2) + }) +}) diff --git a/ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js new file mode 100644 index 000000000..654e7062a --- /dev/null +++ b/ui/app/higher-order-components/with-modal-props/tests/with-modal-props.test.js @@ -0,0 +1,43 @@ + +import assert from 'assert' +import configureMockStore from 'redux-mock-store' +import { mount } from 'enzyme' +import React from 'react' +import withModalProps from '../with-modal-props' + +const mockState = { + appState: { + modal: { + modalState: { + props: { + prop1: 'prop1', + prop2: 2, + prop3: true, + }, + }, + }, + }, +} + +describe('withModalProps', () => { + it('should return a component wrapped with modal state props', () => { + const TestComponent = props => ( +
Testing
+ ) + const WrappedComponent = withModalProps(TestComponent) + const store = configureMockStore()(mockState) + const wrapper = mount( + + ) + + assert.ok(wrapper) + const testComponent = wrapper.find(TestComponent).at(0) + assert.equal(testComponent.length, 1) + assert.equal(testComponent.find('.test').text(), 'Testing') + const testComponentProps = testComponent.props() + assert.equal(testComponentProps.prop1, 'prop1') + assert.equal(testComponentProps.prop2, 2) + assert.equal(testComponentProps.prop3, true) + assert.equal(typeof testComponentProps.hideModal, 'function') + }) +}) From 431beb943675f2e9b7b5e5ce9c7f55d45f10905f Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 20 Sep 2018 19:35:45 -0700 Subject: [PATCH 11/11] Fix multiplyCurrencies. Add onClose prop for Modal component. Remove hideModal from modal components. --- app/_locales/en/messages.json | 2 +- ui/app/components/modal/modal.component.js | 20 +++--------------- .../modal/tests/modal.component.test.js | 16 +------------- .../cancel-transaction.component.js | 8 +++---- .../cancel-transaction.container.js | 21 ++++++++++++------- .../cancel-transaction.component.test.js | 2 +- .../confirm-remove-account.component.js | 15 +++++++------ .../confirm-remove-account.container.js | 4 +--- .../transaction-details.component.js | 9 ++++++-- .../transaction-list-item/index.scss | 2 +- 10 files changed, 41 insertions(+), 58 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 570284126..bcd897bba 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1244,7 +1244,7 @@ "message": "What's this?" }, "yesLetsTry": { - "message": "yes, let's try" + "message": "Yes, let's try" }, "youNeedToAllowCameraAccess": { "message": "You need to allow camera access to use this feature." diff --git a/ui/app/components/modal/modal.component.js b/ui/app/components/modal/modal.component.js index f9d8c5867..2a75b559b 100644 --- a/ui/app/components/modal/modal.component.js +++ b/ui/app/components/modal/modal.component.js @@ -7,6 +7,7 @@ export default class Modal extends PureComponent { children: PropTypes.node, // Header text headerText: PropTypes.string, + onClose: PropTypes.func, // Submit button (right button) onSubmit: PropTypes.func, submitType: PropTypes.string, @@ -22,26 +23,11 @@ export default class Modal extends PureComponent { cancelType: 'default', } - handleClose = () => { - const { onCancel, onSubmit } = this.props - - /** - * The close button should be used to dismiss the modal, without performing any actions, which - * is typically what props.onCancel does. However, if props.onCancel is undefined, that should - * mean that the modal is a simple notification modal and props.onSubmit can be used to dismiss - * it. - */ - if (onCancel && typeof onCancel === 'function') { - onCancel() - } else { - onSubmit() - } - } - render () { const { children, headerText, + onClose, onSubmit, submitType, submitText, @@ -60,7 +46,7 @@ export default class Modal extends PureComponent {
) diff --git a/ui/app/components/modal/tests/modal.component.test.js b/ui/app/components/modal/tests/modal.component.test.js index 31457751f..8cce1a808 100644 --- a/ui/app/components/modal/tests/modal.component.test.js +++ b/ui/app/components/modal/tests/modal.component.test.js @@ -88,6 +88,7 @@ describe('Modal Component', () => { onSubmit={handleSubmit} submitText="Submit" headerText="My Header" + onClose={handleCancel} /> ) @@ -99,19 +100,4 @@ describe('Modal Component', () => { assert.equal(handleCancel.callCount, 1) assert.equal(handleSubmit.callCount, 0) }) - - it('should call onSubmit when onCancel is undefined and the header close button is clicked', () => { - const handleSubmit = sinon.spy() - const wrapper = shallow( - - ) - - assert.equal(handleSubmit.callCount, 0) - wrapper.find('.modal-container__header-close').simulate('click') - assert.equal(handleSubmit.callCount, 1) - }) }) diff --git a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js index a30fbea96..8b00cb9b9 100644 --- a/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js +++ b/ui/app/components/modals/cancel-transaction/cancel-transaction.component.js @@ -3,8 +3,6 @@ import PropTypes from 'prop-types' import Modal from '../../modal' import CancelTransactionGasFee from './cancel-transaction-gas-fee' import { SUBMITTED_STATUS } from '../../../constants/transactions' -import { decimalToHex } from '../../../helpers/conversions.util' -import { getHexGasTotal } from '../../../helpers/confirm-transaction/util' export default class CancelTransaction extends PureComponent { static contextTypes = { @@ -16,7 +14,7 @@ export default class CancelTransaction extends PureComponent { hideModal: PropTypes.func, showTransactionConfirmedModal: PropTypes.func, transactionStatus: PropTypes.string, - defaultNewGasPrice: PropTypes.string, + newGasFee: PropTypes.string, } componentDidUpdate () { @@ -41,12 +39,12 @@ export default class CancelTransaction extends PureComponent { render () { const { t } = this.context - const { defaultNewGasPrice: gasPrice } = this.props - const newGasFee = getHexGasTotal({ gasPrice, gasLimit: decimalToHex(21000) }) + const { newGasFee } = this.props return ( { const { metamask } = state const { transactionId, originalGasPrice } = ownProps const { selectedAddressTxList } = metamask - const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList) + const transaction = selectedAddressTxList.find(({ id }) => id === transactionId) const transactionStatus = transaction ? transaction.status : '' - const defaultNewGasPrice = bnToHex(multiplyCurrencies(originalGasPrice, 1.1)) + const defaultNewGasPrice = ethUtil.addHexPrefix( + multiplyCurrencies(originalGasPrice, 1.1, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 10, + }) + ) + + const newGasFee = getHexGasTotal({ gasPrice: defaultNewGasPrice, gasLimit: '0x5208' }) return { transactionId, transactionStatus, originalGasPrice, - defaultNewGasPrice, + newGasFee, } } const mapDispatchToProps = dispatch => { return { - hideModal: () => dispatch(hideModal()), createCancelTransaction: txId => dispatch(createCancelTransaction(txId)), showTransactionConfirmedModal: () => dispatch(showModal({ name: 'TRANSACTION_CONFIRMED' })), } diff --git a/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js b/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js index 053223467..858fb01a8 100644 --- a/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js +++ b/ui/app/components/modals/cancel-transaction/tests/cancel-transaction.component.test.js @@ -12,7 +12,7 @@ describe('CancelTransaction Component', () => { it('should render a CancelTransaction modal', () => { const wrapper = shallow( , { context: { t }} ) diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js index 483c7062f..eff94a54a 100644 --- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js +++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.component.js @@ -5,7 +5,7 @@ import { addressSummary } from '../../../util' import Identicon from '../../identicon' import genAccountLink from '../../../../lib/account-link' -class ConfirmRemoveAccount extends Component { +export default class ConfirmRemoveAccount extends Component { static propTypes = { hideModal: PropTypes.func.isRequired, removeAccount: PropTypes.func.isRequired, @@ -17,11 +17,15 @@ class ConfirmRemoveAccount extends Component { t: PropTypes.func, } - handleRemove () { + handleRemove = () => { this.props.removeAccount(this.props.identity.address) .then(() => this.props.hideModal()) } + handleCancel = () => { + this.props.hideModal() + } + renderSelectedAccount () { const { identity } = this.props return ( @@ -60,8 +64,9 @@ class ConfirmRemoveAccount extends Component { return ( this.handleRemove()} - onCancel={() => this.props.hideModal()} + onClose={this.handleCancel} + onSubmit={this.handleRemove} + onCancel={this.handleCancel} submitText={t('remove')} cancelText={t('nevermind')} submitType="secondary" @@ -82,5 +87,3 @@ class ConfirmRemoveAccount extends Component { ) } } - -export default ConfirmRemoveAccount diff --git a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js index 59d48400d..45c6654ab 100644 --- a/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js +++ b/ui/app/components/modals/confirm-remove-account/confirm-remove-account.container.js @@ -2,8 +2,7 @@ import { connect } from 'react-redux' import { compose } from 'recompose' import ConfirmRemoveAccount from './confirm-remove-account.component' import withModalProps from '../../../higher-order-components/with-modal-props' - -const { hideModal, removeAccount } = require('../../../actions') +import { removeAccount } from '../../../actions' const mapStateToProps = state => { return { @@ -13,7 +12,6 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - hideModal: () => dispatch(hideModal()), removeAccount: (address) => dispatch(removeAccount(address)), } } diff --git a/ui/app/components/modals/transaction-details/transaction-details.component.js b/ui/app/components/modals/transaction-details/transaction-details.component.js index 7eec028fe..ef438d01f 100644 --- a/ui/app/components/modals/transaction-details/transaction-details.component.js +++ b/ui/app/components/modals/transaction-details/transaction-details.component.js @@ -18,15 +18,20 @@ export default class TransactionConfirmed extends PureComponent { showCancel: PropTypes.bool, } + handleSubmit = () => { + this.props.hideModal() + } + render () { const { t } = this.context - const { transaction, onRetry, showRetry, onCancel, showCancel, hideModal } = this.props + const { transaction, onRetry, showRetry, onCancel, showCancel } = this.props const { txParams: { nonce } = {} } = transaction const decimalNonce = nonce && hexToDecimal(nonce) return ( hideModal()} + onSubmit={this.handleSubmit} + onClose={this.handleSubmit} submitText={t('ok')} headerText={t('transactionWithNonce', [`#${decimalNonce}`])} > diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss index df513bb0c..9d694546b 100644 --- a/ui/app/components/transaction-list-item/index.scss +++ b/ui/app/components/transaction-list-item/index.scss @@ -125,7 +125,7 @@ &--show { max-height: 1000px; - transition: max-height 300ms ease-out; + transition: max-height 700ms ease-out; } } }