1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +01:00

Add notification for dropped retry transactions (#4363)

This commit is contained in:
Alexander Tseung 2018-05-29 10:23:06 -07:00 committed by GitHub
parent e3c9629130
commit 41e38fe553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 319 additions and 73 deletions

View File

@ -402,6 +402,9 @@
"infoHelp": {
"message": "Info & Help"
},
"initialTransactionConfirmed": {
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
},
"insufficientFunds": {
"message": "Insufficient funds."
},
@ -701,10 +704,10 @@
"save": {
"message": "Save"
},
"reprice_title": {
"message": "Reprice Transaction"
"speedUpTitle": {
"message": "Speed Up Transaction"
},
"reprice_subtitle": {
"speedUpSubtitle": {
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
},
"saveAsCsvFile": {

17
app/images/check-icon.svg Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>76BCDB09-52B0-41CB-908F-12F9087A2F1B</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Confirm-TX-screen" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="confirmed-alert" transform="translate(-144.000000, -53.000000)" stroke="#61BA00" stroke-width="4">
<g id="Group-17-Copy" transform="translate(22.000000, 20.000000)">
<g id="check-icon" transform="translate(124.000000, 35.000000)">
<circle id="Oval-5" cx="48" cy="48" r="48"></circle>
<polyline id="Path-3" stroke-linecap="round" points="29.76 52.8 41.0023819 64.32 71.04 34.56"></polyline>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1397,16 +1397,24 @@ function markAccountsFound () {
function retryTransaction (txId) {
log.debug(`background.retryTransaction`)
let newTxId
return (dispatch) => {
background.retryTransaction(txId, (err, newState) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
const { selectedAddressTxList } = newState
const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1]
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.viewPendingTx(newTxId))
return new Promise((resolve, reject) => {
background.retryTransaction(txId, (err, newState) => {
if (err) {
dispatch(actions.displayWarning(err.message))
reject(err)
}
const { selectedAddressTxList } = newState
const { id } = selectedAddressTxList[selectedAddressTxList.length - 1]
newTxId = id
resolve(newState)
})
})
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => newTxId)
}
}

View File

@ -76,7 +76,7 @@ class App extends Component {
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
h(Authenticated, { path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`, component: ConfirmTxScreen }),
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),

View File

@ -3,3 +3,5 @@
@import './info-box/index';
@import './pages/index';
@import './modals/index';

View File

@ -0,0 +1 @@
@import './transaction-confirmed/index';

View File

@ -20,6 +20,7 @@ const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
const CustomizeGasModal = require('../customize-gas-modal')
const NotifcationModal = require('./notification-modal')
const ConfirmResetAccount = require('./notification-modals/confirm-reset-account')
const TransactionConfirmed = require('./transaction-confirmed')
const accountModalStyle = {
mobileModalStyle: {
@ -265,6 +266,37 @@ const MODALS = {
},
},
TRANSACTION_CONFIRMED: {
disableBackdropClick: true,
contents: [
h(TransactionConfirmed, {}, []),
],
mobileModalStyle: {
width: '100%',
height: '100%',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
top: '0',
display: 'flex',
},
laptopModalStyle: {
width: '344px',
transform: 'translate3d(-50%, 0, 0px)',
top: '15%',
border: '1px solid #CCCFD1',
borderRadius: '8px',
backgroundColor: '#FFFFFF',
boxShadow: '0 2px 22px 0 rgba(0,0,0,0.2)',
},
contentStyle: {
borderRadius: '8px',
height: '100%',
},
},
DEFAULT: {
contents: [],
mobileModalStyle: {},
@ -306,7 +338,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)
Modal.prototype.render = function () {
const modal = MODALS[this.props.modalState.name || 'DEFAULT']
const children = modal.contents
const { contents: children, disableBackdropClick = false } = modal
const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle']
const contentStyle = modal.contentStyle || {}
@ -326,6 +358,7 @@ Modal.prototype.render = function () {
modalStyle,
contentStyle,
backdropStyle: BACKDROPSTYLE,
closeOnClick: !disableBackdropClick,
},
children,
)

View File

@ -0,0 +1,2 @@
import TransactionConfirmed from './transaction-confirmed.container'
module.exports = TransactionConfirmed

View File

@ -0,0 +1,21 @@
.transaction-confirmed {
display: flex;
flex-direction: column;
align-items: center;
padding: 32px;
&__title {
font-size: 2rem;
padding: 16px 0;
}
&__description {
text-align: center;
font-size: .875rem;
line-height: 1.5rem;
}
@media screen and (max-width: 575px) {
justify-content: center;
}
}

View File

@ -0,0 +1,46 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Button from '../../button'
class TransactionConfirmed extends Component {
render () {
const { t } = this.context
return (
<div className="page-container page-container--full-width page-container--full-height">
<div className="page-container__content transaction-confirmed">
<img src="images/check-icon.svg" />
<div className="transaction-confirmed__title">
{ `${t('confirmed')}!` }
</div>
<div className="transaction-confirmed__description">
{ t('initialTransactionConfirmed') }
</div>
</div>
<div className="page-container__footer">
<Button
type="primary"
className="page-container__footer-button"
onClick={() => {
this.props.hideModal()
this.props.onHide()
}}
>
{ t('ok') }
</Button>
</div>
</div>
)
}
}
TransactionConfirmed.propTypes = {
hideModal: PropTypes.func.isRequired,
onHide: PropTypes.func.isRequired,
}
TransactionConfirmed.contextTypes = {
t: PropTypes.func,
}
export default TransactionConfirmed

View File

@ -0,0 +1,20 @@
import { connect } from 'react-redux'
import TransactionConfirmed from './transaction-confirmed.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()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TransactionConfirmed)

View File

@ -291,18 +291,48 @@ ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, curren
: value
}
ConfirmSendEther.prototype.editTransaction = function (txMeta) {
ConfirmSendEther.prototype.editTransaction = function () {
const { editTransaction, history } = this.props
const txMeta = this.gatherTxMeta()
editTransaction(txMeta)
history.push(SEND_ROUTE)
}
ConfirmSendEther.prototype.renderNetworkDisplay = function () {
ConfirmSendEther.prototype.renderHeaderRow = function (isTxReprice) {
const windowType = window.METAMASK_UI_TYPE
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
return (windowType === ENVIRONMENT_TYPE_NOTIFICATION || windowType === ENVIRONMENT_TYPE_POPUP)
? h(NetworkDisplay)
: null
if (isTxReprice && isFullScreen) {
return null
}
return (
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => this.editTransaction(),
style: {
visibility: isTxReprice ? 'hidden' : 'initial',
},
}, 'Edit'),
!isFullScreen && h(NetworkDisplay),
])
)
}
ConfirmSendEther.prototype.renderHeader = function (isTxReprice) {
const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm')
const subtitle = isTxReprice
? this.context.t('speedUpSubtitle')
: this.context.t('pleaseReviewTransaction')
return (
h('.page-container__header', [
this.renderHeaderRow(isTxReprice),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),
])
)
}
ConfirmSendEther.prototype.render = function () {
@ -320,6 +350,7 @@ ConfirmSendEther.prototype.render = function () {
},
} = this.props
const txMeta = this.gatherTxMeta()
const isTxReprice = Boolean(txMeta.lastGasPrice)
const txParams = txMeta.txParams || {}
const {
@ -338,11 +369,6 @@ ConfirmSendEther.prototype.render = function () {
totalInETH,
} = this.getData()
const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm'
const subtitle = txMeta.lastGasPrice
? 'Increase your gas fee to attempt to overwrite and speed up your transaction'
: 'Please review your transaction.'
const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency)
const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
@ -362,19 +388,7 @@ ConfirmSendEther.prototype.render = function () {
return (
// Main Send token Card
h('.page-container', [
h('.page-container__header', [
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => this.editTransaction(txMeta),
style: {
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
},
}, 'Edit'),
this.renderNetworkDisplay(),
]),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),
]),
this.renderHeader(isTxReprice),
h('.page-container__content', [
h(SenderToRecipient, {
senderName: fromName,

View File

@ -12,6 +12,7 @@ const actions = require('../../actions')
const clone = require('clone')
const Identicon = require('../identicon')
const GasFeeDisplay = require('../send/gas-fee-display-v2.js')
const NetworkDisplay = require('../network-display')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const {
@ -39,6 +40,11 @@ const {
} = require('../../selectors')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
} = require('../../../../app/scripts/lib/enums')
ConfirmSendToken.contextTypes = {
t: PropTypes.func,
}
@ -430,6 +436,43 @@ ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, curren
: value
}
ConfirmSendToken.prototype.renderHeaderRow = function (isTxReprice) {
const windowType = window.METAMASK_UI_TYPE
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
windowType !== ENVIRONMENT_TYPE_POPUP
if (isTxReprice && isFullScreen) {
return null
}
return (
h('.page-container__header-row', [
h('span.page-container__back-button', {
onClick: () => this.editTransaction(),
style: {
visibility: isTxReprice ? 'hidden' : 'initial',
},
}, 'Edit'),
!isFullScreen && h(NetworkDisplay),
])
)
}
ConfirmSendToken.prototype.renderHeader = function (isTxReprice) {
const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm')
const subtitle = isTxReprice
? this.context.t('speedUpSubtitle')
: this.context.t('pleaseReviewTransaction')
return (
h('.page-container__header', [
this.renderHeaderRow(isTxReprice),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),
])
)
}
ConfirmSendToken.prototype.render = function () {
const txMeta = this.gatherTxMeta()
const {
@ -443,25 +486,13 @@ ConfirmSendToken.prototype.render = function () {
},
} = this.getData()
this.inputs = []
const isTxReprice = Boolean(txMeta.lastGasPrice)
const title = isTxReprice ? this.context.t('reprice_title') : this.context.t('confirm')
const subtitle = isTxReprice
? this.context.t('reprice_subtitle')
: this.context.t('pleaseReviewTransaction')
return (
h('div.confirm-screen-container.confirm-send-token', [
// Main Send token Card
h('div.page-container', [
h('div.page-container__header', [
!txMeta.lastGasPrice && h('button.confirm-screen-back-button', {
onClick: () => this.editTransaction(txMeta),
}, this.context.t('edit')),
h('div.page-container__title', title),
h('div.page-container__subtitle', subtitle),
]),
this.renderHeader(isTxReprice),
h('.page-container__content', [
h('div.flex-row.flex-center.confirm-screen-identicons', [
h('div.confirm-screen-account-wrapper', [

View File

@ -1,5 +1,7 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const { compose } = require('recompose')
const { withRouter } = require('react-router-dom')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const inherits = require('util').inherits
@ -16,13 +18,16 @@ const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
const { calcTokenAmount } = require('../token-util')
const { getCurrentCurrency } = require('../selectors')
const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
TxListItem.contextTypes = {
t: PropTypes.func,
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem)
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(TxListItem)
function mapStateToProps (state) {
return {
@ -216,6 +221,7 @@ TxListItem.prototype.setSelectedToken = function (tokenAddress) {
TxListItem.prototype.resubmit = function () {
const { transactionId } = this.props
this.props.retryTransaction(transactionId)
.then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
}
TxListItem.prototype.render = function () {

View File

@ -7,6 +7,7 @@ const { compose } = require('recompose')
const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
const log = require('loglevel')
const R = require('ramda')
const PendingTx = require('./components/pending-tx')
const SignatureRequest = require('./components/signature-request')
@ -87,37 +88,74 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) {
network,
selectedAddressTxList,
send,
history,
match: { params: { id: transactionId } = {} },
} = this.props
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
const prevTxData = prevUnconfTxList[prevIndex] || {}
const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
let prevTx
if (transactionId) {
prevTx = R.find(({ id }) => id + '' === transactionId)(selectedAddressTxList)
} else {
const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps
const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network)
const prevTxData = prevUnconfTxList[prevIndex] || {}
prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {}
}
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network)
if (unconfTxList.length === 0 &&
(prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) {
if (prevTx.status === 'dropped') {
this.props.dispatch(actions.showModal({
name: 'TRANSACTION_CONFIRMED',
onHide: () => history.push(DEFAULT_ROUTE),
}))
return
}
if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) {
this.props.history.push(DEFAULT_ROUTE)
}
}
ConfirmTxScreen.prototype.getTxData = function () {
const {
network,
index,
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedTypedMessages,
match: { params: { id: transactionId } = {} },
} = this.props
const unconfTxList = txHelper(
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedTypedMessages,
network
)
log.info(`rendering a combined ${unconfTxList.length} unconf msgs & txs`)
return transactionId
? R.find(({ id }) => id + '' === transactionId)(unconfTxList)
: unconfTxList[index]
}
ConfirmTxScreen.prototype.render = function () {
const props = this.props
const {
network,
unapprovedTxs,
currentCurrency,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedTypedMessages,
conversionRate,
blockGasLimit,
// provider,
// computedBalances,
} = props
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
var txData = unconfTxList[props.index] || {}
var txData = this.getTxData() || {}
var txParams = txData.params || {}
// var isNotification = isPopupOrNotification() === 'notification'
@ -136,7 +174,6 @@ ConfirmTxScreen.prototype.render = function () {
]),
*/
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
return currentTxView({
// Properties

View File

@ -42,6 +42,7 @@ function reduceApp (state, action) {
open: false,
modalState: {
name: null,
props: {},
},
previousModalState: {
name: null,
@ -88,13 +89,17 @@ function reduceApp (state, action) {
// modal methods:
case actions.MODAL_OPEN:
const { name, ...modalProps } = action.payload
return extend(appState, {
modal: Object.assign(
state.appState.modal,
{ open: true },
{ modalState: action.payload },
{ previousModalState: appState.modal.modalState},
),
modal: {
open: true,
modalState: {
name: name,
props: { ...modalProps },
},
previousModalState: { ...appState.modal.modalState },
},
})
case actions.MODAL_CLOSE:
@ -102,7 +107,7 @@ function reduceApp (state, action) {
modal: Object.assign(
state.appState.modal,
{ open: false },
{ modalState: { name: null } },
{ modalState: { name: null, props: {} } },
{ previousModalState: appState.modal.modalState},
),
})