feature/default_network_editable
commit
07d8bfaec5
After Width: | Height: | Size: 774 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 992 B |
@ -0,0 +1,35 @@ |
|||||||
|
// next version number
|
||||||
|
const version = 27 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
normalizes txParams on unconfirmed txs |
||||||
|
|
||||||
|
*/ |
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: async function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
return versionedData |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
|
||||||
|
if (newState.TransactionController) { |
||||||
|
if (newState.TransactionController.transactions) { |
||||||
|
const transactions = newState.TransactionController.transactions |
||||||
|
newState.TransactionController.transactions = transactions.filter((txMeta) => txMeta.status !== 'rejected') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return newState |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -1,8 +1,33 @@ |
|||||||
<html> |
<html> |
||||||
|
<head> |
||||||
|
<title>E2E Test Dapp</title> |
||||||
|
</head> |
||||||
<body> |
<body> |
||||||
<button id="deployButton">Deploy Contract</button> |
<div style="display: flex; flex-flow: column;"> |
||||||
<button id="depositButton">Deposit</button> |
<div style="display: flex; font-size: 1.25rem;">Contract</div> |
||||||
<button id="withdrawButton">Withdraw</button> |
<div style="display: flex;"> |
||||||
</body> |
<button id="deployButton">Deploy Contract</button> |
||||||
|
<button id="depositButton">Deposit</button> |
||||||
|
<button id="withdrawButton">Withdraw</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div style="display: flex; flex-flow: column;"> |
||||||
|
<div style="display: flex; font-size: 1.25rem;">Send eth</div> |
||||||
|
<div style="display: flex;"> |
||||||
|
<button id="sendButton">Send</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div style="display: flex; flex-flow: column;"> |
||||||
|
<div style="display: flex; font-size: 1.25rem;">Send tokens</div> |
||||||
|
<div id="tokenAddress"></div> |
||||||
|
<div style="display: flex;"> |
||||||
|
<button id="createToken">Create Token</button> |
||||||
|
<button id="transferTokens">Transfer Tokens</button> |
||||||
|
<button id="approveTokens">Approve Tokens</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
<script src="contract.js"></script> |
<script src="contract.js"></script> |
||||||
|
</body> |
||||||
|
|
||||||
</html> |
</html> |
@ -0,0 +1,50 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const migration27 = require('../../../app/scripts/migrations/027') |
||||||
|
|
||||||
|
const oldStorage = { |
||||||
|
'meta': {}, |
||||||
|
'data': { |
||||||
|
'TransactionController': { |
||||||
|
'transactions': [ |
||||||
|
], |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const transactions = [] |
||||||
|
|
||||||
|
|
||||||
|
while (transactions.length < 9) { |
||||||
|
transactions.push({status: 'rejected'}) |
||||||
|
transactions.push({status: 'unapproved'}) |
||||||
|
transactions.push({status: 'approved'}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
oldStorage.data.TransactionController.transactions = transactions |
||||||
|
|
||||||
|
describe('migration #27', () => { |
||||||
|
it('should remove rejected transactions', (done) => { |
||||||
|
migration27.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
const newTransactions = newStorage.data.TransactionController.transactions |
||||||
|
assert.equal(newTransactions.length, 6, 'transactions is expected to have the length of 6') |
||||||
|
newTransactions.forEach((txMeta) => { |
||||||
|
if (txMeta.status === 'rejected') done(new Error('transaction was found with a status of rejected')) |
||||||
|
}) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should successfully migrate first time state', (done) => { |
||||||
|
migration27.migrate({ |
||||||
|
meta: {}, |
||||||
|
data: require('../../../app/scripts/first-time-state'), |
||||||
|
}) |
||||||
|
.then((migratedData) => { |
||||||
|
assert.equal(migratedData.meta.version, migration27.version) |
||||||
|
done() |
||||||
|
}).catch(done) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,52 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
|
||||||
|
const ConfirmDetailRow = props => { |
||||||
|
const { |
||||||
|
label, |
||||||
|
fiatFee, |
||||||
|
ethFee, |
||||||
|
onHeaderClick, |
||||||
|
fiatFeeColor, |
||||||
|
headerText, |
||||||
|
headerTextClassName, |
||||||
|
} = props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="confirm-detail-row"> |
||||||
|
<div className="confirm-detail-row__label"> |
||||||
|
{ label } |
||||||
|
</div> |
||||||
|
<div className="confirm-detail-row__details"> |
||||||
|
<div |
||||||
|
className={classnames('confirm-detail-row__header-text', headerTextClassName)} |
||||||
|
onClick={() => onHeaderClick && onHeaderClick()} |
||||||
|
> |
||||||
|
{ headerText } |
||||||
|
</div> |
||||||
|
<div |
||||||
|
className="confirm-detail-row__fiat" |
||||||
|
style={{ color: fiatFeeColor }} |
||||||
|
> |
||||||
|
{ fiatFee } |
||||||
|
</div> |
||||||
|
<div className="confirm-detail-row__eth"> |
||||||
|
{ `\u2666 ${ethFee}` } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ConfirmDetailRow.propTypes = { |
||||||
|
label: PropTypes.string, |
||||||
|
fiatFee: PropTypes.string, |
||||||
|
ethFee: PropTypes.string, |
||||||
|
fiatFeeColor: PropTypes.string, |
||||||
|
onHeaderClick: PropTypes.func, |
||||||
|
headerText: PropTypes.string, |
||||||
|
headerTextClassName: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
export default ConfirmDetailRow |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-detail-row.component' |
@ -0,0 +1,43 @@ |
|||||||
|
.confirm-detail-row { |
||||||
|
padding: 14px 0; |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
&__label { |
||||||
|
font-size: .75rem; |
||||||
|
font-weight: 500; |
||||||
|
color: $scorpion; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
|
||||||
|
&__details { |
||||||
|
flex: 1; |
||||||
|
text-align: end; |
||||||
|
} |
||||||
|
|
||||||
|
&__fiat { |
||||||
|
font-size: 1.5rem; |
||||||
|
} |
||||||
|
|
||||||
|
&__eth { |
||||||
|
color: $oslo-gray; |
||||||
|
} |
||||||
|
|
||||||
|
&__header-text { |
||||||
|
font-size: .75rem; |
||||||
|
text-transform: uppercase; |
||||||
|
margin-bottom: 6px; |
||||||
|
color: $scorpion; |
||||||
|
|
||||||
|
&--edit { |
||||||
|
color: $curious-blue; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
&--total { |
||||||
|
font-size: .625rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
import { Tabs, Tab } from '../../tabs' |
||||||
|
import { |
||||||
|
ConfirmPageContainerSummary, |
||||||
|
ConfirmPageContainerError, |
||||||
|
ConfirmPageContainerWarning, |
||||||
|
} from './' |
||||||
|
|
||||||
|
export default class ConfirmPageContainerContent extends Component { |
||||||
|
static propTypes = { |
||||||
|
action: PropTypes.string, |
||||||
|
dataComponent: PropTypes.node, |
||||||
|
detailsComponent: PropTypes.node, |
||||||
|
errorKey: PropTypes.string, |
||||||
|
errorMessage: PropTypes.string, |
||||||
|
hideSubtitle: PropTypes.bool, |
||||||
|
identiconAddress: PropTypes.string, |
||||||
|
nonce: PropTypes.string, |
||||||
|
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
||||||
|
summaryComponent: PropTypes.node, |
||||||
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
||||||
|
titleComponent: PropTypes.func, |
||||||
|
warning: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
renderContent () { |
||||||
|
const { detailsComponent, dataComponent } = this.props |
||||||
|
|
||||||
|
if (detailsComponent && dataComponent) { |
||||||
|
return this.renderTabs() |
||||||
|
} else { |
||||||
|
return detailsComponent || dataComponent |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
renderTabs () { |
||||||
|
const { detailsComponent, dataComponent } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<Tabs> |
||||||
|
<Tab name="Details"> |
||||||
|
{ detailsComponent } |
||||||
|
</Tab> |
||||||
|
<Tab name="Data"> |
||||||
|
{ dataComponent } |
||||||
|
</Tab> |
||||||
|
</Tabs> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
action, |
||||||
|
errorKey, |
||||||
|
errorMessage, |
||||||
|
title, |
||||||
|
subtitle, |
||||||
|
hideSubtitle, |
||||||
|
identiconAddress, |
||||||
|
nonce, |
||||||
|
summaryComponent, |
||||||
|
detailsComponent, |
||||||
|
dataComponent, |
||||||
|
warning, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="confirm-page-container-content"> |
||||||
|
{ |
||||||
|
warning && ( |
||||||
|
<ConfirmPageContainerWarning warning={warning} /> |
||||||
|
) |
||||||
|
} |
||||||
|
{ |
||||||
|
summaryComponent || ( |
||||||
|
<ConfirmPageContainerSummary |
||||||
|
className={classnames({ |
||||||
|
'confirm-page-container-summary--border': !detailsComponent || !dataComponent, |
||||||
|
})} |
||||||
|
action={action} |
||||||
|
title={title} |
||||||
|
subtitle={subtitle} |
||||||
|
hideSubtitle={hideSubtitle} |
||||||
|
identiconAddress={identiconAddress} |
||||||
|
nonce={nonce} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
{ this.renderContent() } |
||||||
|
{ |
||||||
|
(errorKey || errorMessage) && ( |
||||||
|
<div className="confirm-page-container-content__error-container"> |
||||||
|
<ConfirmPageContainerError |
||||||
|
errorMessage={errorMessage} |
||||||
|
errorKey={errorKey} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
const ConfirmPageContainerError = (props, context) => { |
||||||
|
const { errorMessage, errorKey } = props |
||||||
|
const error = errorKey ? context.t(errorKey) : errorMessage |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="confirm-page-container-error"> |
||||||
|
<img |
||||||
|
src="/images/alert-red.svg" |
||||||
|
className="confirm-page-container-error__icon" |
||||||
|
/> |
||||||
|
{ `ALERT: ${error}` } |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ConfirmPageContainerError.propTypes = { |
||||||
|
errorMessage: PropTypes.string, |
||||||
|
errorKey: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
ConfirmPageContainerError.contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
export default ConfirmPageContainerError |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-page-container-error.component' |
@ -0,0 +1,17 @@ |
|||||||
|
.confirm-page-container-error { |
||||||
|
height: 32px; |
||||||
|
border: 1px solid $monzo; |
||||||
|
color: $monzo; |
||||||
|
background: lighten($monzo, 56%); |
||||||
|
border-radius: 4px; |
||||||
|
font-size: .75rem; |
||||||
|
display: flex; |
||||||
|
justify-content: flex-start; |
||||||
|
align-items: center; |
||||||
|
padding-left: 16px; |
||||||
|
|
||||||
|
&__icon { |
||||||
|
margin-right: 8px; |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
import Identicon from '../../../identicon' |
||||||
|
|
||||||
|
const ConfirmPageContainerSummary = props => { |
||||||
|
const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce } = props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={classnames('confirm-page-container-summary', className)}> |
||||||
|
<div className="confirm-page-container-summary__action-row"> |
||||||
|
<div className="confirm-page-container-summary__action"> |
||||||
|
{ action } |
||||||
|
</div> |
||||||
|
{ |
||||||
|
nonce && ( |
||||||
|
<div className="confirm-page-container-summary__nonce"> |
||||||
|
{ `#${nonce}` } |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-summary__title"> |
||||||
|
{ |
||||||
|
identiconAddress && ( |
||||||
|
<Identicon |
||||||
|
className="confirm-page-container-summary__identicon" |
||||||
|
diameter={36} |
||||||
|
address={identiconAddress} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
<div className="confirm-page-container-summary__title-text"> |
||||||
|
{ title } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{ |
||||||
|
hideSubtitle || <div className="confirm-page-container-summary__subtitle"> |
||||||
|
{ subtitle } |
||||||
|
</div> |
||||||
|
} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ConfirmPageContainerSummary.propTypes = { |
||||||
|
action: PropTypes.string, |
||||||
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
||||||
|
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
||||||
|
hideSubtitle: PropTypes.bool, |
||||||
|
className: PropTypes.string, |
||||||
|
identiconAddress: PropTypes.string, |
||||||
|
nonce: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
export default ConfirmPageContainerSummary |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-page-container-summary.component' |
@ -0,0 +1,54 @@ |
|||||||
|
.confirm-page-container-summary { |
||||||
|
padding: 16px 24px 0; |
||||||
|
background-color: #f9fafa; |
||||||
|
height: 133px; |
||||||
|
box-sizing: border-box; |
||||||
|
|
||||||
|
&__action-row { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
|
||||||
|
&__action { |
||||||
|
text-transform: uppercase; |
||||||
|
color: $oslo-gray; |
||||||
|
font-size: .75rem; |
||||||
|
padding: 3px 8px; |
||||||
|
border: 1px solid $oslo-gray; |
||||||
|
border-radius: 4px; |
||||||
|
display: inline-block; |
||||||
|
} |
||||||
|
|
||||||
|
&__nonce { |
||||||
|
color: $oslo-gray; |
||||||
|
} |
||||||
|
|
||||||
|
&__title { |
||||||
|
padding: 4px 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__identicon { |
||||||
|
flex: 0 0 auto; |
||||||
|
margin-right: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
&__title-text { |
||||||
|
font-size: 2.25rem; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
} |
||||||
|
|
||||||
|
&__subtitle { |
||||||
|
color: $oslo-gray; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
} |
||||||
|
|
||||||
|
&--border { |
||||||
|
border-bottom: 1px solid $geyser; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
import React from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
const ConfirmPageContainerWarning = props => { |
||||||
|
return ( |
||||||
|
<div className="confirm-page-container-warning"> |
||||||
|
<img |
||||||
|
className="confirm-page-container-warning__icon" |
||||||
|
src="/images/alert.svg" |
||||||
|
/> |
||||||
|
<div className="confirm-page-container-warning__warning"> |
||||||
|
{ props.warning } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
ConfirmPageContainerWarning.propTypes = { |
||||||
|
warning: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
export default ConfirmPageContainerWarning |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-page-container-warning.component' |
@ -0,0 +1,18 @@ |
|||||||
|
.confirm-page-container-warning { |
||||||
|
background-color: #fffcdb; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
border-bottom: 1px solid $geyser; |
||||||
|
padding: 12px 24px; |
||||||
|
|
||||||
|
&__icon { |
||||||
|
flex: 0 0 auto; |
||||||
|
margin-right: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
&__warning { |
||||||
|
font-size: .75rem; |
||||||
|
color: #5f5922; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
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' |
@ -0,0 +1,66 @@ |
|||||||
|
@import './confirm-page-container-error/index'; |
||||||
|
|
||||||
|
@import './confirm-page-container-warning/index'; |
||||||
|
|
||||||
|
@import './confirm-page-container-summary/index'; |
||||||
|
|
||||||
|
.confirm-page-container-content { |
||||||
|
overflow-y: auto; |
||||||
|
flex: 1; |
||||||
|
|
||||||
|
&__error-container { |
||||||
|
padding: 0 16px 16px 16px; |
||||||
|
} |
||||||
|
|
||||||
|
&__details { |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 0 24px; |
||||||
|
} |
||||||
|
|
||||||
|
&__data { |
||||||
|
padding: 16px; |
||||||
|
color: $oslo-gray; |
||||||
|
} |
||||||
|
|
||||||
|
&__data-box { |
||||||
|
background-color: #f9fafa; |
||||||
|
padding: 12px; |
||||||
|
font-size: .75rem; |
||||||
|
margin-bottom: 16px; |
||||||
|
word-wrap: break-word; |
||||||
|
max-height: 200px; |
||||||
|
overflow-y: auto; |
||||||
|
|
||||||
|
&-label { |
||||||
|
text-transform: uppercase; |
||||||
|
padding: 8px 0 12px; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__data-field { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
|
||||||
|
&-label { |
||||||
|
font-weight: 500; |
||||||
|
padding-right: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
&:not(:last-child) { |
||||||
|
margin-bottom: 5px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__gas-fee { |
||||||
|
border-bottom: 1px solid $geyser; |
||||||
|
} |
||||||
|
|
||||||
|
&__function-type { |
||||||
|
font-size: .875rem; |
||||||
|
font-weight: 500; |
||||||
|
text-transform: capitalize; |
||||||
|
color: $black; |
||||||
|
padding-left: 5px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import { |
||||||
|
ENVIRONMENT_TYPE_POPUP, |
||||||
|
ENVIRONMENT_TYPE_NOTIFICATION, |
||||||
|
} from '../../../../../app/scripts/lib/enums' |
||||||
|
import NetworkDisplay from '../../network-display' |
||||||
|
|
||||||
|
export default class ConfirmPageContainer extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
showEdit: PropTypes.bool, |
||||||
|
onEdit: PropTypes.func, |
||||||
|
children: PropTypes.node, |
||||||
|
} |
||||||
|
|
||||||
|
renderTop () { |
||||||
|
const { onEdit, showEdit } = this.props |
||||||
|
const windowType = window.METAMASK_UI_TYPE |
||||||
|
const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && |
||||||
|
windowType !== ENVIRONMENT_TYPE_POPUP |
||||||
|
|
||||||
|
if (!showEdit && isFullScreen) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="confirm-page-container-header__row"> |
||||||
|
<div |
||||||
|
className="confirm-page-container-header__back-button-container" |
||||||
|
style={{ |
||||||
|
visibility: showEdit ? 'initial' : 'hidden', |
||||||
|
}} |
||||||
|
> |
||||||
|
<img |
||||||
|
src="/images/caret-left.svg" |
||||||
|
/> |
||||||
|
<span |
||||||
|
className="confirm-page-container-header__back-button" |
||||||
|
onClick={() => onEdit()} |
||||||
|
> |
||||||
|
{ this.context.t('edit') } |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
{ !isFullScreen && <NetworkDisplay /> } |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { children } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="confirm-page-container-header"> |
||||||
|
{ this.renderTop() } |
||||||
|
{ children } |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-page-container-header.component' |
@ -0,0 +1,27 @@ |
|||||||
|
.confirm-page-container-header { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
flex: 0 0 auto; |
||||||
|
|
||||||
|
&__row { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
border-bottom: 1px solid $geyser; |
||||||
|
padding: 13px 13px 13px 24px; |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
&__back-button-container { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__back-button { |
||||||
|
color: #2f9ae0; |
||||||
|
font-size: 1rem; |
||||||
|
cursor: pointer; |
||||||
|
font-weight: 400; |
||||||
|
padding-left: 5px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import SenderToRecipient from '../sender-to-recipient' |
||||||
|
import { PageContainerFooter } from '../page-container' |
||||||
|
import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './' |
||||||
|
|
||||||
|
export default class ConfirmPageContainer extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
// Header
|
||||||
|
action: PropTypes.string, |
||||||
|
hideSubtitle: PropTypes.bool, |
||||||
|
onEdit: PropTypes.func, |
||||||
|
showEdit: PropTypes.bool, |
||||||
|
subtitle: PropTypes.string, |
||||||
|
title: PropTypes.string, |
||||||
|
titleComponent: PropTypes.func, |
||||||
|
// Sender to Recipient
|
||||||
|
fromAddress: PropTypes.string, |
||||||
|
fromName: PropTypes.string, |
||||||
|
toAddress: PropTypes.string, |
||||||
|
toName: PropTypes.string, |
||||||
|
// Content
|
||||||
|
contentComponent: PropTypes.node, |
||||||
|
errorKey: PropTypes.string, |
||||||
|
errorMessage: PropTypes.string, |
||||||
|
fiatTransactionAmount: PropTypes.string, |
||||||
|
fiatTransactionFee: PropTypes.string, |
||||||
|
fiatTransactionTotal: PropTypes.string, |
||||||
|
ethTransactionAmount: PropTypes.string, |
||||||
|
ethTransactionFee: PropTypes.string, |
||||||
|
ethTransactionTotal: PropTypes.string, |
||||||
|
onEditGas: PropTypes.func, |
||||||
|
dataComponent: PropTypes.node, |
||||||
|
detailsComponent: PropTypes.node, |
||||||
|
identiconAddress: PropTypes.string, |
||||||
|
nonce: PropTypes.string, |
||||||
|
summaryComponent: PropTypes.node, |
||||||
|
warning: PropTypes.string, |
||||||
|
// Footer
|
||||||
|
onCancel: PropTypes.func, |
||||||
|
onSubmit: PropTypes.func, |
||||||
|
valid: PropTypes.bool, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
showEdit, |
||||||
|
onEdit, |
||||||
|
fromName, |
||||||
|
fromAddress, |
||||||
|
toName, |
||||||
|
toAddress, |
||||||
|
valid, |
||||||
|
errorKey, |
||||||
|
errorMessage, |
||||||
|
contentComponent, |
||||||
|
action, |
||||||
|
title, |
||||||
|
titleComponent, |
||||||
|
subtitle, |
||||||
|
hideSubtitle, |
||||||
|
summaryComponent, |
||||||
|
detailsComponent, |
||||||
|
dataComponent, |
||||||
|
onCancel, |
||||||
|
onSubmit, |
||||||
|
identiconAddress, |
||||||
|
nonce, |
||||||
|
warning, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="page-container"> |
||||||
|
<ConfirmPageContainerHeader |
||||||
|
showEdit={showEdit} |
||||||
|
onEdit={() => onEdit()} |
||||||
|
> |
||||||
|
<SenderToRecipient |
||||||
|
senderName={fromName} |
||||||
|
senderAddress={fromAddress} |
||||||
|
recipientName={toName} |
||||||
|
recipientAddress={toAddress} |
||||||
|
/> |
||||||
|
</ConfirmPageContainerHeader> |
||||||
|
{ |
||||||
|
contentComponent || ( |
||||||
|
<ConfirmPageContainerContent |
||||||
|
action={action} |
||||||
|
title={title} |
||||||
|
titleComponent={titleComponent} |
||||||
|
subtitle={subtitle} |
||||||
|
hideSubtitle={hideSubtitle} |
||||||
|
summaryComponent={summaryComponent} |
||||||
|
detailsComponent={detailsComponent} |
||||||
|
dataComponent={dataComponent} |
||||||
|
errorMessage={errorMessage} |
||||||
|
errorKey={errorKey} |
||||||
|
identiconAddress={identiconAddress} |
||||||
|
nonce={nonce} |
||||||
|
warning={warning} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
<PageContainerFooter |
||||||
|
onCancel={() => onCancel()} |
||||||
|
onSubmit={() => onSubmit()} |
||||||
|
submitText={this.context.t('confirm')} |
||||||
|
submitButtonType="confirm" |
||||||
|
disabled={!valid} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
export { default } from './confirm-page-container.component' |
||||||
|
export { default as ConfirmPageContainerHeader } from './confirm-page-container-header' |
||||||
|
export { default as ConfirmDetailRow } from './confirm-detail-row' |
||||||
|
export { |
||||||
|
default as ConfirmPageContainerContent, |
||||||
|
ConfirmPageContainerSummary, |
||||||
|
ConfirmPageContainerError, |
||||||
|
} from './confirm-page-container-content' |
@ -0,0 +1,5 @@ |
|||||||
|
@import './confirm-page-container-content/index'; |
||||||
|
|
||||||
|
@import './confirm-page-container-header/index'; |
||||||
|
|
||||||
|
@import './confirm-detail-row/index'; |
@ -0,0 +1,140 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import GasModalCard from '../../customize-gas-modal/gas-modal-card' |
||||||
|
import { MIN_GAS_PRICE_GWEI } from '../../send_/send.constants' |
||||||
|
|
||||||
|
import { |
||||||
|
getDecimalGasLimit, |
||||||
|
getDecimalGasPrice, |
||||||
|
getPrefixedHexGasLimit, |
||||||
|
getPrefixedHexGasPrice, |
||||||
|
} from './customize-gas.util' |
||||||
|
|
||||||
|
export default class CustomizeGas extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
txData: PropTypes.object.isRequired, |
||||||
|
hideModal: PropTypes.func, |
||||||
|
validate: PropTypes.func, |
||||||
|
onSubmit: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
state = { |
||||||
|
gasPrice: 0, |
||||||
|
gasLimit: 0, |
||||||
|
originalGasPrice: 0, |
||||||
|
originalGasLimit: 0, |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount () { |
||||||
|
const { txData = {} } = this.props |
||||||
|
const { txParams: { gas: hexGasLimit, gasPrice: hexGasPrice } = {} } = txData |
||||||
|
|
||||||
|
const gasLimit = getDecimalGasLimit(hexGasLimit) |
||||||
|
const gasPrice = getDecimalGasPrice(hexGasPrice) |
||||||
|
|
||||||
|
this.setState({ |
||||||
|
gasPrice, |
||||||
|
gasLimit, |
||||||
|
originalGasPrice: gasPrice, |
||||||
|
originalGasLimit: gasLimit, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
handleRevert () { |
||||||
|
const { originalGasPrice, originalGasLimit } = this.state |
||||||
|
|
||||||
|
this.setState({ |
||||||
|
gasPrice: originalGasPrice, |
||||||
|
gasLimit: originalGasLimit, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
handleSave () { |
||||||
|
const { onSubmit, hideModal } = this.props |
||||||
|
const { gasLimit, gasPrice } = this.state |
||||||
|
const prefixedHexGasPrice = getPrefixedHexGasPrice(gasPrice) |
||||||
|
const prefixedHexGasLimit = getPrefixedHexGasLimit(gasLimit) |
||||||
|
|
||||||
|
Promise.resolve(onSubmit({ gasPrice: prefixedHexGasPrice, gasLimit: prefixedHexGasLimit })) |
||||||
|
.then(() => hideModal()) |
||||||
|
} |
||||||
|
|
||||||
|
validate () { |
||||||
|
const { gasLimit, gasPrice } = this.state |
||||||
|
return this.props.validate({ |
||||||
|
gasPrice: getPrefixedHexGasPrice(gasPrice), |
||||||
|
gasLimit: getPrefixedHexGasLimit(gasLimit), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { t } = this.context |
||||||
|
const { hideModal } = this.props |
||||||
|
const { gasPrice, gasLimit } = this.state |
||||||
|
const { valid, errorKey } = this.validate() |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="customize-gas"> |
||||||
|
<div className="customize-gas__content"> |
||||||
|
<div className="customize-gas__header"> |
||||||
|
<div className="customize-gas__title"> |
||||||
|
{ this.context.t('customGas') } |
||||||
|
</div> |
||||||
|
<div |
||||||
|
className="customize-gas__close" |
||||||
|
onClick={() => hideModal()} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="customize-gas__body"> |
||||||
|
<GasModalCard |
||||||
|
value={gasPrice} |
||||||
|
min={MIN_GAS_PRICE_GWEI} |
||||||
|
step={1} |
||||||
|
onChange={value => this.setState({ gasPrice: value })} |
||||||
|
title={t('gasPrice')} |
||||||
|
copy={t('gasPriceCalculation')} |
||||||
|
/> |
||||||
|
<GasModalCard |
||||||
|
value={gasLimit} |
||||||
|
min={1} |
||||||
|
step={1} |
||||||
|
onChange={value => this.setState({ gasLimit: value })} |
||||||
|
title={t('gasLimit')} |
||||||
|
copy={t('gasLimitCalculation')} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className="customize-gas__footer"> |
||||||
|
{ !valid && <div className="customize-gas__error-message">{ t(errorKey) }</div> } |
||||||
|
<div |
||||||
|
className="customize-gas__revert" |
||||||
|
onClick={() => this.handleRevert()} |
||||||
|
> |
||||||
|
{ t('revert') } |
||||||
|
</div> |
||||||
|
<div className="customize-gas__buttons"> |
||||||
|
<button |
||||||
|
className="btn-default customize-gas__cancel" |
||||||
|
onClick={() => hideModal()} |
||||||
|
style={{ marginRight: '10px' }} |
||||||
|
> |
||||||
|
{ t('cancel') } |
||||||
|
</button> |
||||||
|
<button |
||||||
|
className="btn-primary customize-gas__save" |
||||||
|
onClick={() => this.handleSave()} |
||||||
|
style={{ marginRight: '10px' }} |
||||||
|
disabled={!valid} |
||||||
|
> |
||||||
|
{ t('save') } |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import CustomizeGas from './customize-gas.component' |
||||||
|
import { hideModal } from '../../../actions' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { appState: { modal: { modalState: { props } } } } = state |
||||||
|
const { txData, onSubmit, validate } = props |
||||||
|
|
||||||
|
return { |
||||||
|
txData, |
||||||
|
onSubmit, |
||||||
|
validate, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
hideModal: () => dispatch(hideModal()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(CustomizeGas) |
@ -0,0 +1,34 @@ |
|||||||
|
import ethUtil from 'ethereumjs-util' |
||||||
|
import { conversionUtil } from '../../../conversion-util' |
||||||
|
|
||||||
|
export function getDecimalGasLimit (hexGasLimit) { |
||||||
|
return conversionUtil(hexGasLimit, { |
||||||
|
fromNumericBase: 'hex', |
||||||
|
toNumericBase: 'dec', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function getDecimalGasPrice (hexGasPrice) { |
||||||
|
return conversionUtil(hexGasPrice, { |
||||||
|
fromNumericBase: 'hex', |
||||||
|
toNumericBase: 'dec', |
||||||
|
fromDenomination: 'WEI', |
||||||
|
toDenomination: 'GWEI', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function getPrefixedHexGasLimit (gasLimit) { |
||||||
|
return ethUtil.addHexPrefix(conversionUtil(gasLimit, { |
||||||
|
fromNumericBase: 'dec', |
||||||
|
toNumericBase: 'hex', |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
export function getPrefixedHexGasPrice (gasPrice) { |
||||||
|
return ethUtil.addHexPrefix(conversionUtil(gasPrice, { |
||||||
|
fromNumericBase: 'dec', |
||||||
|
toNumericBase: 'hex', |
||||||
|
fromDenomination: 'GWEI', |
||||||
|
toDenomination: 'WEI', |
||||||
|
})) |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './customize-gas.container' |
@ -0,0 +1,110 @@ |
|||||||
|
.customize-gas { |
||||||
|
border: 1px solid #D8D8D8; |
||||||
|
border-radius: 4px; |
||||||
|
background-color: #FFFFFF; |
||||||
|
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14); |
||||||
|
font-family: Roboto; |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
|
||||||
|
@media screen and (max-width: $break-small) { |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
&__header { |
||||||
|
height: 52px; |
||||||
|
border-bottom: 1px solid $alto; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
font-size: 22px; |
||||||
|
|
||||||
|
@media screen and (max-width: $break-small) { |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__title { |
||||||
|
margin-left: 19.25px; |
||||||
|
} |
||||||
|
|
||||||
|
&__close::after { |
||||||
|
content: '\00D7'; |
||||||
|
font-size: 1.8em; |
||||||
|
color: $dusty-gray; |
||||||
|
font-family: sans-serif; |
||||||
|
cursor: pointer; |
||||||
|
margin-right: 19.25px; |
||||||
|
} |
||||||
|
|
||||||
|
&__content { |
||||||
|
display: flex; |
||||||
|
flex-flow: column nowrap; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
&__body { |
||||||
|
display: flex; |
||||||
|
margin-bottom: 24px; |
||||||
|
|
||||||
|
@media screen and (max-width: $break-small) { |
||||||
|
flex-flow: column; |
||||||
|
flex: 1 1 auto; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__footer { |
||||||
|
height: 75px; |
||||||
|
border-top: 1px solid $alto; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
font-size: 22px; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
@media screen and (max-width: $break-small) { |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__buttons { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
margin-right: 21.25px; |
||||||
|
} |
||||||
|
|
||||||
|
&__revert, &__cancel, &__save, &__save__error { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
padding: 0 3px; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
&__revert { |
||||||
|
color: $silver-chalice; |
||||||
|
font-size: 16px; |
||||||
|
margin-left: 21.25px; |
||||||
|
} |
||||||
|
|
||||||
|
&__cancel, &__save, &__save__error { |
||||||
|
width: 85.74px; |
||||||
|
min-width: initial; |
||||||
|
} |
||||||
|
|
||||||
|
&__save__error { |
||||||
|
opacity: 0.5; |
||||||
|
cursor: auto; |
||||||
|
} |
||||||
|
|
||||||
|
&__error-message { |
||||||
|
display: block; |
||||||
|
position: absolute; |
||||||
|
top: 4px; |
||||||
|
right: 4px; |
||||||
|
font-size: 12px; |
||||||
|
line-height: 12px; |
||||||
|
color: $red; |
||||||
|
} |
||||||
|
} |
@ -1,56 +0,0 @@ |
|||||||
const { Component } = require('react') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const connect = require('react-redux').connect |
|
||||||
const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon') |
|
||||||
|
|
||||||
const networkToColorHash = { |
|
||||||
1: '#038789', |
|
||||||
3: '#e91550', |
|
||||||
42: '#690496', |
|
||||||
4: '#ebb33f', |
|
||||||
} |
|
||||||
|
|
||||||
class NetworkDisplay extends Component { |
|
||||||
renderNetworkIcon () { |
|
||||||
const { network } = this.props |
|
||||||
const networkColor = networkToColorHash[network] |
|
||||||
|
|
||||||
return networkColor |
|
||||||
? h(NetworkDropdownIcon, { backgroundColor: networkColor }) |
|
||||||
: h('i.fa.fa-question-circle.fa-med', { |
|
||||||
style: { |
|
||||||
margin: '0 4px', |
|
||||||
color: 'rgb(125, 128, 130)', |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
render () { |
|
||||||
const { provider: { type } } = this.props |
|
||||||
return h('.network-display__container', [ |
|
||||||
this.renderNetworkIcon(), |
|
||||||
h('.network-name', this.context.t(type)), |
|
||||||
]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
NetworkDisplay.propTypes = { |
|
||||||
network: PropTypes.string, |
|
||||||
provider: PropTypes.object, |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask: { network, provider } }) => { |
|
||||||
return { |
|
||||||
network, |
|
||||||
provider, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
NetworkDisplay.contextTypes = { |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(NetworkDisplay) |
|
||||||
|
|
@ -0,0 +1,2 @@ |
|||||||
|
import NetworkDisplay from './network-display.container' |
||||||
|
module.exports = NetworkDisplay |
@ -0,0 +1,54 @@ |
|||||||
|
.network-display { |
||||||
|
&__container { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: flex-start; |
||||||
|
background-color: lighten(rgb(125, 128, 130), 45%); |
||||||
|
padding: 0 10px; |
||||||
|
border-radius: 4px; |
||||||
|
height: 25px; |
||||||
|
|
||||||
|
&--mainnet { |
||||||
|
background-color: lighten($blue-lagoon, 45%); |
||||||
|
} |
||||||
|
|
||||||
|
&--ropsten { |
||||||
|
background-color: lighten($crimson, 45%); |
||||||
|
} |
||||||
|
|
||||||
|
&--kovan { |
||||||
|
background-color: lighten($purple, 45%); |
||||||
|
} |
||||||
|
|
||||||
|
&--rinkeby { |
||||||
|
background-color: lighten($tulip-tree, 45%); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__name { |
||||||
|
font-size: .75rem; |
||||||
|
padding-left: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
&__icon { |
||||||
|
height: 10px; |
||||||
|
width: 10px; |
||||||
|
border-radius: 10px; |
||||||
|
|
||||||
|
&--mainnet { |
||||||
|
background-color: $blue-lagoon; |
||||||
|
} |
||||||
|
|
||||||
|
&--ropsten { |
||||||
|
background-color: $crimson; |
||||||
|
} |
||||||
|
|
||||||
|
&--kovan { |
||||||
|
background-color: $purple; |
||||||
|
} |
||||||
|
|
||||||
|
&--rinkeby { |
||||||
|
background-color: $tulip-tree; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
import { |
||||||
|
MAINNET_CODE, |
||||||
|
ROPSTEN_CODE, |
||||||
|
RINKEYBY_CODE, |
||||||
|
KOVAN_CODE, |
||||||
|
} from '../../../../app/scripts/controllers/network/enums' |
||||||
|
|
||||||
|
const networkToClassHash = { |
||||||
|
[MAINNET_CODE]: 'mainnet', |
||||||
|
[ROPSTEN_CODE]: 'ropsten', |
||||||
|
[RINKEYBY_CODE]: 'rinkeby', |
||||||
|
[KOVAN_CODE]: 'kovan', |
||||||
|
} |
||||||
|
|
||||||
|
export default class NetworkDisplay extends Component { |
||||||
|
static propTypes = { |
||||||
|
network: PropTypes.string, |
||||||
|
provider: PropTypes.object, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
renderNetworkIcon () { |
||||||
|
const { network } = this.props |
||||||
|
const networkClass = networkToClassHash[network] |
||||||
|
|
||||||
|
return networkClass |
||||||
|
? <div className={`network-display__icon network-display__icon--${networkClass}`} /> |
||||||
|
: <div |
||||||
|
className="i fa fa-question-circle fa-med" |
||||||
|
style={{ |
||||||
|
margin: '0 4px', |
||||||
|
color: 'rgb(125, 128, 130)', |
||||||
|
}} |
||||||
|
/> |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { network, provider: { type } } = this.props |
||||||
|
const networkClass = networkToClassHash[network] |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={classnames( |
||||||
|
'network-display__container', |
||||||
|
networkClass && ('network-display__container--' + networkClass) |
||||||
|
)}> |
||||||
|
{ |
||||||
|
networkClass |
||||||
|
? <div className={`network-display__icon network-display__icon--${networkClass}`} /> |
||||||
|
: <div |
||||||
|
className="i fa fa-question-circle fa-med" |
||||||
|
style={{ |
||||||
|
margin: '0 4px', |
||||||
|
color: 'rgb(125, 128, 130)', |
||||||
|
}} |
||||||
|
/> |
||||||
|
} |
||||||
|
<div className="network-display__name"> |
||||||
|
{ this.context.t(type) } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import NetworkDisplay from './network-display.component' |
||||||
|
|
||||||
|
const mapStateToProps = ({ metamask: { network, provider } }) => { |
||||||
|
return { |
||||||
|
network, |
||||||
|
provider, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(NetworkDisplay) |
@ -1 +1,4 @@ |
|||||||
|
import PageContainerHeader from './page-container-header' |
||||||
|
import PageContainerFooter from './page-container-footer' |
||||||
export { default } from './page-container.component' |
export { default } from './page-container.component' |
||||||
|
export { PageContainerHeader, PageContainerFooter } |
||||||
|
@ -0,0 +1,186 @@ |
|||||||
|
.page-container { |
||||||
|
width: 408px; |
||||||
|
background-color: $white; |
||||||
|
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); |
||||||
|
z-index: 25; |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
border-radius: 8px; |
||||||
|
|
||||||
|
&__header { |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
border-bottom: 1px solid $geyser; |
||||||
|
padding: 16px; |
||||||
|
flex: 0 0 auto; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
&--no-padding-bottom { |
||||||
|
padding-bottom: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__header-close { |
||||||
|
color: $tundora; |
||||||
|
position: absolute; |
||||||
|
top: 16px; |
||||||
|
right: 16px; |
||||||
|
cursor: pointer; |
||||||
|
overflow: hidden; |
||||||
|
|
||||||
|
&::after { |
||||||
|
content: '\00D7'; |
||||||
|
font-size: 40px; |
||||||
|
line-height: 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__header-row { |
||||||
|
padding-bottom: 10px; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
|
||||||
|
&__footer { |
||||||
|
display: flex; |
||||||
|
flex-flow: row; |
||||||
|
justify-content: center; |
||||||
|
border-top: 1px solid $geyser; |
||||||
|
padding: 16px; |
||||||
|
flex: 0 0 auto; |
||||||
|
|
||||||
|
.btn-default, |
||||||
|
.btn-confirm { |
||||||
|
font-size: 1rem; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__footer-button { |
||||||
|
height: 55px; |
||||||
|
font-size: 1rem; |
||||||
|
text-transform: uppercase; |
||||||
|
margin-right: 16px; |
||||||
|
|
||||||
|
&:last-of-type { |
||||||
|
margin-right: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__back-button { |
||||||
|
color: #2f9ae0; |
||||||
|
font-size: 1rem; |
||||||
|
cursor: pointer; |
||||||
|
font-weight: 400; |
||||||
|
} |
||||||
|
|
||||||
|
&__title { |
||||||
|
color: $black; |
||||||
|
font-size: 2rem; |
||||||
|
font-weight: 500; |
||||||
|
line-height: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
&__subtitle { |
||||||
|
padding-top: .5rem; |
||||||
|
line-height: initial; |
||||||
|
font-size: .9rem; |
||||||
|
color: $gray; |
||||||
|
} |
||||||
|
|
||||||
|
&__tabs { |
||||||
|
display: flex; |
||||||
|
margin-top: 16px; |
||||||
|
} |
||||||
|
|
||||||
|
&__tab { |
||||||
|
min-width: 5rem; |
||||||
|
padding: 8px; |
||||||
|
color: $dusty-gray; |
||||||
|
font-family: Roboto; |
||||||
|
font-size: 1rem; |
||||||
|
text-align: center; |
||||||
|
cursor: pointer; |
||||||
|
border-bottom: none; |
||||||
|
margin-right: 16px; |
||||||
|
|
||||||
|
&:last-of-type { |
||||||
|
margin-right: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&--selected { |
||||||
|
color: $curious-blue; |
||||||
|
border-bottom: 3px solid $curious-blue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--full-width { |
||||||
|
width: 100% !important; |
||||||
|
} |
||||||
|
|
||||||
|
&--full-height { |
||||||
|
height: 100% !important; |
||||||
|
max-height: initial !important; |
||||||
|
min-height: initial !important; |
||||||
|
} |
||||||
|
|
||||||
|
&__content { |
||||||
|
overflow-y: auto; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
|
||||||
|
&__warning-container { |
||||||
|
background: $linen; |
||||||
|
padding: 20px; |
||||||
|
display: flex; |
||||||
|
align-items: start; |
||||||
|
} |
||||||
|
|
||||||
|
&__warning-message { |
||||||
|
padding-left: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
&__warning-title { |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
&__warning-icon { |
||||||
|
padding-top: 5px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media screen and (max-width: 250px) { |
||||||
|
.page-container { |
||||||
|
&__footer { |
||||||
|
flex-flow: column-reverse; |
||||||
|
} |
||||||
|
|
||||||
|
&__footer-button { |
||||||
|
width: 100%; |
||||||
|
margin-bottom: 1rem; |
||||||
|
margin-right: 0; |
||||||
|
|
||||||
|
&:first-of-type { |
||||||
|
margin-bottom: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media screen and (max-width: 575px) { |
||||||
|
.page-container { |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
overflow-y: auto; |
||||||
|
background-color: $white; |
||||||
|
border-radius: 0; |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@media screen and (min-width: 576px) { |
||||||
|
.page-container { |
||||||
|
max-height: 82vh; |
||||||
|
min-height: 570px; |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
} |
@ -1,35 +0,0 @@ |
|||||||
import React, { Component } from 'react' |
|
||||||
import PropTypes from 'prop-types' |
|
||||||
|
|
||||||
export default class PageContainerHeader extends Component { |
|
||||||
|
|
||||||
static propTypes = { |
|
||||||
title: PropTypes.string, |
|
||||||
subtitle: PropTypes.string, |
|
||||||
onClose: PropTypes.func, |
|
||||||
}; |
|
||||||
|
|
||||||
render () { |
|
||||||
const { title, subtitle, onClose } = this.props |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="page-container__header"> |
|
||||||
|
|
||||||
<div className="page-container__title"> |
|
||||||
{title} |
|
||||||
</div> |
|
||||||
|
|
||||||
<div className="page-container__subtitle"> |
|
||||||
{subtitle} |
|
||||||
</div> |
|
||||||
|
|
||||||
<div |
|
||||||
className="page-container__header-close" |
|
||||||
onClick={() => onClose()} |
|
||||||
/> |
|
||||||
|
|
||||||
</div> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,30 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import ConfirmTransactionBase from '../confirm-transaction-base' |
||||||
|
|
||||||
|
export default class ConfirmApprove extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
tokenAddress: PropTypes.string, |
||||||
|
toAddress: PropTypes.string, |
||||||
|
tokenAmount: PropTypes.string, |
||||||
|
tokenSymbol: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { toAddress, tokenAddress, tokenAmount, tokenSymbol } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<ConfirmTransactionBase |
||||||
|
toAddress={toAddress} |
||||||
|
identiconAddress={tokenAddress} |
||||||
|
title={`${tokenAmount} ${tokenSymbol}`} |
||||||
|
warning={`By approving this action, you grant permission for this contract to spend up to ${tokenAmount} of your ${tokenSymbol}.`} |
||||||
|
hideSubtitle |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import ConfirmApprove from './confirm-approve.component' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { confirmTransaction } = state |
||||||
|
const { |
||||||
|
tokenData = {}, |
||||||
|
txData: { txParams: { to: tokenAddress } = {} } = {}, |
||||||
|
tokenProps: { tokenSymbol } = {}, |
||||||
|
} = confirmTransaction |
||||||
|
const { params = [] } = tokenData |
||||||
|
|
||||||
|
let toAddress = '' |
||||||
|
let tokenAmount = '' |
||||||
|
|
||||||
|
if (params && params.length === 2) { |
||||||
|
[{ value: toAddress }, { value: tokenAmount }] = params |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
toAddress, |
||||||
|
tokenAddress, |
||||||
|
tokenAmount, |
||||||
|
tokenSymbol, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ConfirmApprove) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-approve.container' |
@ -0,0 +1,64 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import ethUtil from 'ethereumjs-util' |
||||||
|
import ConfirmTransactionBase from '../confirm-transaction-base' |
||||||
|
|
||||||
|
export default class ConfirmDeployContract extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
txData: PropTypes.object, |
||||||
|
} |
||||||
|
|
||||||
|
renderData () { |
||||||
|
const { t } = this.context |
||||||
|
const { |
||||||
|
txData: { |
||||||
|
origin, |
||||||
|
txParams: { |
||||||
|
data, |
||||||
|
} = {}, |
||||||
|
} = {}, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="confirm-page-container-content__data"> |
||||||
|
<div className="confirm-page-container-content__data-box"> |
||||||
|
<div className="confirm-page-container-content__data-field"> |
||||||
|
<div className="confirm-page-container-content__data-field-label"> |
||||||
|
{ `${t('origin')}:` } |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
{ origin } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-content__data-field"> |
||||||
|
<div className="confirm-page-container-content__data-field-label"> |
||||||
|
{ `${t('bytes')}:` } |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
{ ethUtil.toBuffer(data).length } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-content__data-box-label"> |
||||||
|
{ `${t('hexData')}:` } |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-content__data-box"> |
||||||
|
{ data } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
return ( |
||||||
|
<ConfirmTransactionBase |
||||||
|
action={this.context.t('contractDeployment')} |
||||||
|
dataComponent={this.renderData()} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import ConfirmDeployContract from './confirm-deploy-contract.component' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { confirmTransaction: { txData } = {} } = state |
||||||
|
|
||||||
|
return { |
||||||
|
txData, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ConfirmDeployContract) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-deploy-contract.container' |
@ -0,0 +1,39 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import ConfirmTransactionBase from '../confirm-transaction-base' |
||||||
|
import { SEND_ROUTE } from '../../../routes' |
||||||
|
|
||||||
|
export default class ConfirmSendEther extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
editTransaction: PropTypes.func, |
||||||
|
history: PropTypes.object, |
||||||
|
txParams: PropTypes.object, |
||||||
|
} |
||||||
|
|
||||||
|
handleEdit ({ txData }) { |
||||||
|
const { editTransaction, history } = this.props |
||||||
|
editTransaction(txData) |
||||||
|
history.push(SEND_ROUTE) |
||||||
|
} |
||||||
|
|
||||||
|
shouldHideData () { |
||||||
|
const { txParams = {} } = this.props |
||||||
|
return !txParams.data |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const hideData = this.shouldHideData() |
||||||
|
|
||||||
|
return ( |
||||||
|
<ConfirmTransactionBase |
||||||
|
action={this.context.t('confirm')} |
||||||
|
hideData={hideData} |
||||||
|
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import { updateSend } from '../../../actions' |
||||||
|
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck' |
||||||
|
import ConfirmSendEther from './confirm-send-ether.component' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { confirmTransaction: { txData: { txParams } = {} } } = state |
||||||
|
|
||||||
|
return { |
||||||
|
txParams, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
editTransaction: txData => { |
||||||
|
const { id, txParams } = txData |
||||||
|
const { |
||||||
|
gas: gasLimit, |
||||||
|
gasPrice, |
||||||
|
to, |
||||||
|
value: amount, |
||||||
|
} = txParams |
||||||
|
|
||||||
|
dispatch(updateSend({ |
||||||
|
gasLimit, |
||||||
|
gasPrice, |
||||||
|
gasTotal: null, |
||||||
|
to, |
||||||
|
amount, |
||||||
|
errors: { to: null, amount: null }, |
||||||
|
editingTransactionId: id && id.toString(), |
||||||
|
})) |
||||||
|
|
||||||
|
dispatch(clearConfirmTransaction()) |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps) |
||||||
|
)(ConfirmSendEther) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-send-ether.container' |
@ -0,0 +1,39 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import ConfirmTransactionBase from '../confirm-transaction-base' |
||||||
|
import { SEND_ROUTE } from '../../../routes' |
||||||
|
|
||||||
|
export default class ConfirmSendToken extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object, |
||||||
|
tokenAddress: PropTypes.string, |
||||||
|
toAddress: PropTypes.string, |
||||||
|
numberOfTokens: PropTypes.number, |
||||||
|
tokenSymbol: PropTypes.string, |
||||||
|
editTransaction: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
handleEdit (confirmTransactionData) { |
||||||
|
const { editTransaction, history } = this.props |
||||||
|
editTransaction(confirmTransactionData) |
||||||
|
history.push(SEND_ROUTE) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { toAddress, tokenAddress, tokenSymbol, numberOfTokens } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<ConfirmTransactionBase |
||||||
|
toAddress={toAddress} |
||||||
|
identiconAddress={tokenAddress} |
||||||
|
title={`${numberOfTokens} ${tokenSymbol}`} |
||||||
|
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)} |
||||||
|
hideSubtitle |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import ConfirmSendToken from './confirm-send-token.component' |
||||||
|
import { calcTokenAmount } from '../../../token-util' |
||||||
|
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck' |
||||||
|
import { setSelectedToken, updateSend, showSendTokenPage } from '../../../actions' |
||||||
|
import { conversionUtil } from '../../../conversion-util' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { confirmTransaction } = state |
||||||
|
const { |
||||||
|
tokenData = {}, |
||||||
|
tokenProps: { tokenSymbol, tokenDecimals } = {}, |
||||||
|
txData: { txParams: { to: tokenAddress } = {} } = {}, |
||||||
|
} = confirmTransaction |
||||||
|
const { params = [] } = tokenData |
||||||
|
|
||||||
|
let toAddress = '' |
||||||
|
let tokenAmount = '' |
||||||
|
|
||||||
|
if (params && params.length === 2) { |
||||||
|
[{ value: toAddress }, { value: tokenAmount }] = params |
||||||
|
} |
||||||
|
|
||||||
|
const numberOfTokens = tokenAmount && tokenDecimals |
||||||
|
? calcTokenAmount(tokenAmount, tokenDecimals) |
||||||
|
: 0 |
||||||
|
|
||||||
|
return { |
||||||
|
toAddress, |
||||||
|
tokenAddress, |
||||||
|
tokenSymbol, |
||||||
|
numberOfTokens, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
editTransaction: ({ txData, tokenData, tokenProps }) => { |
||||||
|
const { txParams: { to: tokenAddress, gas: gasLimit, gasPrice } = {}, id } = txData |
||||||
|
const { params = [] } = tokenData |
||||||
|
const { value: to } = params[0] || {} |
||||||
|
const { value: tokenAmountInDec } = params[1] || {} |
||||||
|
const tokenAmountInHex = conversionUtil(tokenAmountInDec, { |
||||||
|
fromNumericBase: 'dec', |
||||||
|
toNumericBase: 'hex', |
||||||
|
}) |
||||||
|
dispatch(setSelectedToken(tokenAddress)) |
||||||
|
dispatch(updateSend({ |
||||||
|
gasLimit, |
||||||
|
gasPrice, |
||||||
|
gasTotal: null, |
||||||
|
to, |
||||||
|
amount: tokenAmountInHex, |
||||||
|
errors: { to: null, amount: null }, |
||||||
|
editingTransactionId: id && id.toString(), |
||||||
|
token: { |
||||||
|
...tokenProps, |
||||||
|
address: tokenAddress, |
||||||
|
}, |
||||||
|
})) |
||||||
|
dispatch(clearConfirmTransaction()) |
||||||
|
dispatch(showSendTokenPage()) |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps) |
||||||
|
)(ConfirmSendToken) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-send-token.container' |
@ -0,0 +1,19 @@ |
|||||||
|
.confirm-send-token { |
||||||
|
&__title { |
||||||
|
padding: 4px 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__identicon { |
||||||
|
flex: 0 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
&__title-text { |
||||||
|
font-size: 2.25rem; |
||||||
|
white-space: nowrap; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
padding-left: 8px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,320 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container' |
||||||
|
import { formatCurrency } from '../../../helpers/confirm-transaction/util' |
||||||
|
import { isBalanceSufficient } from '../../send_/send.utils' |
||||||
|
import { DEFAULT_ROUTE } from '../../../routes' |
||||||
|
import { |
||||||
|
INSUFFICIENT_FUNDS_ERROR_KEY, |
||||||
|
TRANSACTION_ERROR_KEY, |
||||||
|
} from '../../../constants/error-keys' |
||||||
|
|
||||||
|
export default class ConfirmTransactionBase extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
// react-router props
|
||||||
|
match: PropTypes.object, |
||||||
|
history: PropTypes.object, |
||||||
|
// Redux props
|
||||||
|
balance: PropTypes.string, |
||||||
|
cancelTransaction: PropTypes.func, |
||||||
|
clearConfirmTransaction: PropTypes.func, |
||||||
|
clearSend: PropTypes.func, |
||||||
|
conversionRate: PropTypes.number, |
||||||
|
currentCurrency: PropTypes.string, |
||||||
|
editTransaction: PropTypes.func, |
||||||
|
ethTransactionAmount: PropTypes.string, |
||||||
|
ethTransactionFee: PropTypes.string, |
||||||
|
ethTransactionTotal: PropTypes.string, |
||||||
|
fiatTransactionAmount: PropTypes.string, |
||||||
|
fiatTransactionFee: PropTypes.string, |
||||||
|
fiatTransactionTotal: PropTypes.string, |
||||||
|
fromAddress: PropTypes.string, |
||||||
|
fromName: PropTypes.string, |
||||||
|
hexGasTotal: PropTypes.string, |
||||||
|
isTxReprice: PropTypes.bool, |
||||||
|
methodData: PropTypes.object, |
||||||
|
nonce: PropTypes.string, |
||||||
|
sendTransaction: PropTypes.func, |
||||||
|
showCustomizeGasModal: PropTypes.func, |
||||||
|
showTransactionConfirmedModal: PropTypes.func, |
||||||
|
toAddress: PropTypes.string, |
||||||
|
tokenData: PropTypes.object, |
||||||
|
tokenProps: PropTypes.object, |
||||||
|
toName: PropTypes.string, |
||||||
|
transactionStatus: PropTypes.string, |
||||||
|
txData: PropTypes.object, |
||||||
|
// Component props
|
||||||
|
action: PropTypes.string, |
||||||
|
contentComponent: PropTypes.node, |
||||||
|
dataComponent: PropTypes.node, |
||||||
|
detailsComponent: PropTypes.node, |
||||||
|
errorKey: PropTypes.string, |
||||||
|
errorMessage: PropTypes.string, |
||||||
|
hideData: PropTypes.bool, |
||||||
|
hideDetails: PropTypes.bool, |
||||||
|
hideSubtitle: PropTypes.bool, |
||||||
|
identiconAddress: PropTypes.string, |
||||||
|
onCancel: PropTypes.func, |
||||||
|
onEdit: PropTypes.func, |
||||||
|
onEditGas: PropTypes.func, |
||||||
|
onSubmit: PropTypes.func, |
||||||
|
subtitle: PropTypes.string, |
||||||
|
summaryComponent: PropTypes.node, |
||||||
|
title: PropTypes.string, |
||||||
|
valid: PropTypes.bool, |
||||||
|
warning: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
componentDidUpdate () { |
||||||
|
const { |
||||||
|
transactionStatus, |
||||||
|
showTransactionConfirmedModal, |
||||||
|
history, |
||||||
|
clearConfirmTransaction, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
if (transactionStatus === 'dropped') { |
||||||
|
showTransactionConfirmedModal({ |
||||||
|
onHide: () => { |
||||||
|
clearConfirmTransaction() |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getErrorKey () { |
||||||
|
const { |
||||||
|
balance, |
||||||
|
conversionRate, |
||||||
|
hexGasTotal, |
||||||
|
txData: { |
||||||
|
simulationFails, |
||||||
|
txParams: { |
||||||
|
value: amount, |
||||||
|
} = {}, |
||||||
|
} = {}, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
const insufficientBalance = balance && !isBalanceSufficient({ |
||||||
|
amount, |
||||||
|
gasTotal: hexGasTotal || '0x0', |
||||||
|
balance, |
||||||
|
conversionRate, |
||||||
|
}) |
||||||
|
|
||||||
|
if (insufficientBalance) { |
||||||
|
return { |
||||||
|
valid: false, |
||||||
|
errorKey: INSUFFICIENT_FUNDS_ERROR_KEY, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (simulationFails) { |
||||||
|
return { |
||||||
|
valid: false, |
||||||
|
errorKey: TRANSACTION_ERROR_KEY, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
valid: true, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handleEditGas () { |
||||||
|
const { onEditGas, showCustomizeGasModal } = this.props |
||||||
|
|
||||||
|
if (onEditGas) { |
||||||
|
onEditGas() |
||||||
|
} else { |
||||||
|
showCustomizeGasModal() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
renderDetails () { |
||||||
|
const { |
||||||
|
detailsComponent, |
||||||
|
fiatTransactionFee, |
||||||
|
ethTransactionFee, |
||||||
|
currentCurrency, |
||||||
|
fiatTransactionTotal, |
||||||
|
ethTransactionTotal, |
||||||
|
hideDetails, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
if (hideDetails) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
detailsComponent || ( |
||||||
|
<div className="confirm-page-container-content__details"> |
||||||
|
<div className="confirm-page-container-content__gas-fee"> |
||||||
|
<ConfirmDetailRow |
||||||
|
label="Gas Fee" |
||||||
|
fiatFee={formatCurrency(fiatTransactionFee, currentCurrency)} |
||||||
|
ethFee={ethTransactionFee} |
||||||
|
headerText="Edit" |
||||||
|
headerTextClassName="confirm-detail-row__header-text--edit" |
||||||
|
onHeaderClick={() => this.handleEditGas()} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<ConfirmDetailRow |
||||||
|
label="Total" |
||||||
|
fiatFee={formatCurrency(fiatTransactionTotal, currentCurrency)} |
||||||
|
ethFee={ethTransactionTotal} |
||||||
|
headerText="Amount + Gas Fee" |
||||||
|
headerTextClassName="confirm-detail-row__header-text--total" |
||||||
|
fiatFeeColor="#2f9ae0" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
renderData () { |
||||||
|
const { t } = this.context |
||||||
|
const { |
||||||
|
txData: { |
||||||
|
txParams: { |
||||||
|
data, |
||||||
|
} = {}, |
||||||
|
} = {}, |
||||||
|
methodData: { |
||||||
|
name, |
||||||
|
params, |
||||||
|
} = {}, |
||||||
|
hideData, |
||||||
|
dataComponent, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
if (hideData) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
return dataComponent || ( |
||||||
|
<div className="confirm-page-container-content__data"> |
||||||
|
<div className="confirm-page-container-content__data-box-label"> |
||||||
|
{`${t('functionType')}:`} |
||||||
|
<span className="confirm-page-container-content__function-type"> |
||||||
|
{ name } |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-content__data-box"> |
||||||
|
<div className="confirm-page-container-content__data-field-label"> |
||||||
|
{ `${t('parameters')}:` } |
||||||
|
</div> |
||||||
|
<div> |
||||||
|
<pre>{ JSON.stringify(params, null, 2) }</pre> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-content__data-box-label"> |
||||||
|
{`${t('hexData')}:`} |
||||||
|
</div> |
||||||
|
<div className="confirm-page-container-content__data-box"> |
||||||
|
{ data } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
handleEdit () { |
||||||
|
const { txData, tokenData, tokenProps, onEdit } = this.props |
||||||
|
onEdit({ txData, tokenData, tokenProps }) |
||||||
|
} |
||||||
|
|
||||||
|
handleCancel () { |
||||||
|
const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction } = this.props |
||||||
|
|
||||||
|
if (onCancel) { |
||||||
|
onCancel(txData) |
||||||
|
} else { |
||||||
|
cancelTransaction(txData) |
||||||
|
.then(() => { |
||||||
|
clearConfirmTransaction() |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handleSubmit () { |
||||||
|
const { sendTransaction, clearConfirmTransaction, txData, history, onSubmit } = this.props |
||||||
|
|
||||||
|
if (onSubmit) { |
||||||
|
onSubmit(txData) |
||||||
|
} else { |
||||||
|
sendTransaction(txData) |
||||||
|
.then(() => { |
||||||
|
clearConfirmTransaction() |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
isTxReprice, |
||||||
|
fromName, |
||||||
|
fromAddress, |
||||||
|
toName, |
||||||
|
toAddress, |
||||||
|
methodData, |
||||||
|
ethTransactionAmount, |
||||||
|
fiatTransactionAmount, |
||||||
|
valid: propsValid, |
||||||
|
errorMessage, |
||||||
|
errorKey: propsErrorKey, |
||||||
|
currentCurrency, |
||||||
|
action, |
||||||
|
title, |
||||||
|
subtitle, |
||||||
|
hideSubtitle, |
||||||
|
identiconAddress, |
||||||
|
summaryComponent, |
||||||
|
contentComponent, |
||||||
|
onEdit, |
||||||
|
nonce, |
||||||
|
warning, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
const { name } = methodData |
||||||
|
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency) |
||||||
|
const { valid, errorKey } = this.getErrorKey() |
||||||
|
|
||||||
|
return ( |
||||||
|
<ConfirmPageContainer |
||||||
|
fromName={fromName} |
||||||
|
fromAddress={fromAddress} |
||||||
|
toName={toName} |
||||||
|
toAddress={toAddress} |
||||||
|
showEdit={onEdit && !isTxReprice} |
||||||
|
action={action || name} |
||||||
|
title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`} |
||||||
|
subtitle={subtitle || `\u2666 ${ethTransactionAmount}`} |
||||||
|
hideSubtitle={hideSubtitle} |
||||||
|
summaryComponent={summaryComponent} |
||||||
|
detailsComponent={this.renderDetails()} |
||||||
|
dataComponent={this.renderData()} |
||||||
|
contentComponent={contentComponent} |
||||||
|
nonce={nonce} |
||||||
|
identiconAddress={identiconAddress} |
||||||
|
errorMessage={errorMessage} |
||||||
|
errorKey={propsErrorKey || errorKey} |
||||||
|
warning={warning} |
||||||
|
valid={propsValid || valid} |
||||||
|
onEdit={() => this.handleEdit()} |
||||||
|
onCancel={() => this.handleCancel()} |
||||||
|
onSubmit={() => this.handleSubmit()} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,169 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import R from 'ramda' |
||||||
|
import ConfirmTransactionBase from './confirm-transaction-base.component' |
||||||
|
import { |
||||||
|
clearConfirmTransaction, |
||||||
|
updateGasAndCalculate, |
||||||
|
} from '../../../ducks/confirm-transaction.duck' |
||||||
|
import { clearSend, cancelTx, updateAndApproveTx, showModal } from '../../../actions' |
||||||
|
import { |
||||||
|
INSUFFICIENT_FUNDS_ERROR_KEY, |
||||||
|
GAS_LIMIT_TOO_LOW_ERROR_KEY, |
||||||
|
} from '../../../constants/error-keys' |
||||||
|
import { getHexGasTotal } from '../../../helpers/confirm-transaction/util' |
||||||
|
import { isBalanceSufficient } from '../../send_/send.utils' |
||||||
|
import { conversionGreaterThan } from '../../../conversion-util' |
||||||
|
import { MIN_GAS_LIMIT_DEC } from '../../send_/send.constants' |
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => { |
||||||
|
const { toAddress: propsToAddress } = props |
||||||
|
const { confirmTransaction, metamask } = state |
||||||
|
const { |
||||||
|
ethTransactionAmount, |
||||||
|
ethTransactionFee, |
||||||
|
ethTransactionTotal, |
||||||
|
fiatTransactionAmount, |
||||||
|
fiatTransactionFee, |
||||||
|
fiatTransactionTotal, |
||||||
|
hexGasTotal, |
||||||
|
tokenData, |
||||||
|
methodData, |
||||||
|
txData, |
||||||
|
tokenProps, |
||||||
|
nonce, |
||||||
|
} = confirmTransaction |
||||||
|
const { txParams = {}, lastGasPrice, id: transactionId } = txData |
||||||
|
const { from: fromAddress, to: txParamsToAddress } = txParams |
||||||
|
const { |
||||||
|
conversionRate, |
||||||
|
identities, |
||||||
|
currentCurrency, |
||||||
|
accounts, |
||||||
|
selectedAddress, |
||||||
|
selectedAddressTxList, |
||||||
|
} = metamask |
||||||
|
|
||||||
|
const { balance } = accounts[selectedAddress] |
||||||
|
const { name: fromName } = identities[selectedAddress] |
||||||
|
const toAddress = propsToAddress || txParamsToAddress |
||||||
|
const toName = identities[toAddress] && identities[toAddress].name |
||||||
|
const isTxReprice = Boolean(lastGasPrice) |
||||||
|
|
||||||
|
const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList) |
||||||
|
const transactionStatus = transaction ? transaction.status : '' |
||||||
|
|
||||||
|
return { |
||||||
|
balance, |
||||||
|
fromAddress, |
||||||
|
fromName, |
||||||
|
toAddress, |
||||||
|
toName, |
||||||
|
ethTransactionAmount, |
||||||
|
ethTransactionFee, |
||||||
|
ethTransactionTotal, |
||||||
|
fiatTransactionAmount, |
||||||
|
fiatTransactionFee, |
||||||
|
fiatTransactionTotal, |
||||||
|
hexGasTotal, |
||||||
|
txData, |
||||||
|
tokenData, |
||||||
|
methodData, |
||||||
|
tokenProps, |
||||||
|
isTxReprice, |
||||||
|
currentCurrency, |
||||||
|
conversionRate, |
||||||
|
transactionStatus, |
||||||
|
nonce, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), |
||||||
|
clearSend: () => dispatch(clearSend()), |
||||||
|
showTransactionConfirmedModal: ({ onHide }) => { |
||||||
|
return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onHide })) |
||||||
|
}, |
||||||
|
showCustomizeGasModal: ({ txData, onSubmit, validate }) => { |
||||||
|
return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate })) |
||||||
|
}, |
||||||
|
updateGasAndCalculate: ({ gasLimit, gasPrice }) => { |
||||||
|
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice })) |
||||||
|
}, |
||||||
|
cancelTransaction: ({ id }) => dispatch(cancelTx({ id })), |
||||||
|
sendTransaction: txData => dispatch(updateAndApproveTx(txData)), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const getValidateEditGas = ({ balance, conversionRate, txData }) => { |
||||||
|
const { txParams: { value: amount } = {} } = txData |
||||||
|
|
||||||
|
return ({ gasLimit, gasPrice }) => { |
||||||
|
const gasTotal = getHexGasTotal({ gasLimit, gasPrice }) |
||||||
|
const hasSufficientBalance = isBalanceSufficient({ |
||||||
|
amount, |
||||||
|
gasTotal, |
||||||
|
balance, |
||||||
|
conversionRate, |
||||||
|
}) |
||||||
|
|
||||||
|
if (!hasSufficientBalance) { |
||||||
|
return { |
||||||
|
valid: false, |
||||||
|
errorKey: INSUFFICIENT_FUNDS_ERROR_KEY, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const gasLimitTooLow = gasLimit && conversionGreaterThan( |
||||||
|
{ |
||||||
|
value: MIN_GAS_LIMIT_DEC, |
||||||
|
fromNumericBase: 'dec', |
||||||
|
conversionRate, |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: gasLimit, |
||||||
|
fromNumericBase: 'hex', |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
if (gasLimitTooLow) { |
||||||
|
return { |
||||||
|
valid: false, |
||||||
|
errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
valid: true, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mergeProps = (stateProps, dispatchProps, ownProps) => { |
||||||
|
const { balance, conversionRate, txData } = stateProps |
||||||
|
const { |
||||||
|
showCustomizeGasModal: dispatchShowCustomizeGasModal, |
||||||
|
updateGasAndCalculate: dispatchUpdateGasAndCalculate, |
||||||
|
...otherDispatchProps |
||||||
|
} = dispatchProps |
||||||
|
|
||||||
|
const validateEditGas = getValidateEditGas({ balance, conversionRate, txData }) |
||||||
|
|
||||||
|
return { |
||||||
|
...stateProps, |
||||||
|
...otherDispatchProps, |
||||||
|
...ownProps, |
||||||
|
showCustomizeGasModal: () => dispatchShowCustomizeGasModal({ |
||||||
|
txData, |
||||||
|
onSubmit: txData => dispatchUpdateGasAndCalculate(txData), |
||||||
|
validate: validateEditGas, |
||||||
|
}), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps, mergeProps) |
||||||
|
)(ConfirmTransactionBase) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './confirm-transaction-base.container' |
@ -0,0 +1,77 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import { Redirect } from 'react-router-dom' |
||||||
|
import Loading from '../../loading-screen' |
||||||
|
import { |
||||||
|
CONFIRM_TRANSACTION_ROUTE, |
||||||
|
CONFIRM_DEPLOY_CONTRACT_PATH, |
||||||
|
CONFIRM_SEND_ETHER_PATH, |
||||||
|
CONFIRM_SEND_TOKEN_PATH, |
||||||
|
CONFIRM_APPROVE_PATH, |
||||||
|
CONFIRM_TOKEN_METHOD_PATH, |
||||||
|
SIGNATURE_REQUEST_PATH, |
||||||
|
} from '../../../routes' |
||||||
|
import { isConfirmDeployContract } from './confirm-transaction-switch.util' |
||||||
|
import { TOKEN_METHOD_TRANSFER, TOKEN_METHOD_APPROVE } from './confirm-transaction-switch.constants' |
||||||
|
|
||||||
|
export default class ConfirmTransactionSwitch extends Component { |
||||||
|
static propTypes = { |
||||||
|
txData: PropTypes.object, |
||||||
|
methodData: PropTypes.object, |
||||||
|
fetchingMethodData: PropTypes.bool, |
||||||
|
} |
||||||
|
|
||||||
|
redirectToTransaction () { |
||||||
|
const { |
||||||
|
txData, |
||||||
|
methodData: { name }, |
||||||
|
fetchingMethodData, |
||||||
|
} = this.props |
||||||
|
const { id } = txData |
||||||
|
|
||||||
|
|
||||||
|
if (isConfirmDeployContract(txData)) { |
||||||
|
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}` |
||||||
|
return <Redirect to={{ pathname }} /> |
||||||
|
} |
||||||
|
|
||||||
|
if (fetchingMethodData) { |
||||||
|
return <Loading /> |
||||||
|
} |
||||||
|
|
||||||
|
if (name) { |
||||||
|
const methodName = name.toLowerCase() |
||||||
|
|
||||||
|
switch (methodName.toLowerCase()) { |
||||||
|
case TOKEN_METHOD_TRANSFER: { |
||||||
|
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}` |
||||||
|
return <Redirect to={{ pathname }} /> |
||||||
|
} |
||||||
|
case TOKEN_METHOD_APPROVE: { |
||||||
|
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}` |
||||||
|
return <Redirect to={{ pathname }} /> |
||||||
|
} |
||||||
|
default: { |
||||||
|
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}` |
||||||
|
return <Redirect to={{ pathname }} /> |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}` |
||||||
|
return <Redirect to={{ pathname }} /> |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { txData } = this.props |
||||||
|
|
||||||
|
if (txData.txParams) { |
||||||
|
return this.redirectToTransaction() |
||||||
|
} else if (txData.msgParams) { |
||||||
|
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}` |
||||||
|
return <Redirect to={{ pathname }} /> |
||||||
|
} |
||||||
|
|
||||||
|
return <Loading /> |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
export const TOKEN_METHOD_TRANSFER = 'transfer' |
||||||
|
export const TOKEN_METHOD_APPROVE = 'approve' |
@ -0,0 +1,20 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import ConfirmTransactionSwitch from './confirm-transaction-switch.component' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { |
||||||
|
confirmTransaction: { |
||||||
|
txData, |
||||||
|
methodData, |
||||||
|
fetchingMethodData, |
||||||
|
}, |
||||||
|
} = state |
||||||
|
|
||||||
|
return { |
||||||
|
txData, |
||||||
|
methodData, |
||||||
|
fetchingMethodData, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ConfirmTransactionSwitch) |
@ -0,0 +1,4 @@ |
|||||||
|
export function isConfirmDeployContract (txData = {}) { |
||||||
|
const { txParams = {} } = txData |
||||||
|
return !txParams.to |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
import ConfirmTransactionSwitch from './confirm-transaction-switch.container' |
||||||
|
module.exports = ConfirmTransactionSwitch |
@ -0,0 +1,150 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import { Switch, Route } from 'react-router-dom' |
||||||
|
import Loading from '../../loading-screen' |
||||||
|
import ConfirmTransactionSwitch from '../confirm-transaction-switch' |
||||||
|
import ConfirmTransactionBase from '../confirm-transaction-base' |
||||||
|
import ConfirmSendEther from '../confirm-send-ether' |
||||||
|
import ConfirmSendToken from '../confirm-send-token' |
||||||
|
import ConfirmDeployContract from '../confirm-deploy-contract' |
||||||
|
import ConfirmApprove from '../confirm-approve' |
||||||
|
import ConfTx from '../../../conf-tx' |
||||||
|
import { |
||||||
|
DEFAULT_ROUTE, |
||||||
|
CONFIRM_TRANSACTION_ROUTE, |
||||||
|
CONFIRM_DEPLOY_CONTRACT_PATH, |
||||||
|
CONFIRM_SEND_ETHER_PATH, |
||||||
|
CONFIRM_SEND_TOKEN_PATH, |
||||||
|
CONFIRM_APPROVE_PATH, |
||||||
|
CONFIRM_TOKEN_METHOD_PATH, |
||||||
|
SIGNATURE_REQUEST_PATH, |
||||||
|
} from '../../../routes' |
||||||
|
|
||||||
|
export default class ConfirmTransaction extends Component { |
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object.isRequired, |
||||||
|
totalUnapprovedCount: PropTypes.number.isRequired, |
||||||
|
match: PropTypes.object, |
||||||
|
send: PropTypes.object, |
||||||
|
unconfirmedTransactions: PropTypes.array, |
||||||
|
setTransactionToConfirm: PropTypes.func, |
||||||
|
confirmTransaction: PropTypes.object, |
||||||
|
clearConfirmTransaction: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
getParamsTransactionId () { |
||||||
|
const { match: { params: { id } = {} } } = this.props |
||||||
|
return id || null |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount () { |
||||||
|
const { |
||||||
|
totalUnapprovedCount = 0, |
||||||
|
send = {}, |
||||||
|
history, |
||||||
|
confirmTransaction: { txData: { id: transactionId } = {} }, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
if (!totalUnapprovedCount && !send.to) { |
||||||
|
history.replace(DEFAULT_ROUTE) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (!transactionId) { |
||||||
|
this.setTransactionToConfirm() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
componentDidUpdate () { |
||||||
|
const { |
||||||
|
setTransactionToConfirm, |
||||||
|
confirmTransaction: { txData: { id: transactionId } = {} }, |
||||||
|
clearConfirmTransaction, |
||||||
|
} = this.props |
||||||
|
const paramsTransactionId = this.getParamsTransactionId() |
||||||
|
|
||||||
|
if (paramsTransactionId && transactionId && paramsTransactionId !== transactionId + '') { |
||||||
|
clearConfirmTransaction() |
||||||
|
setTransactionToConfirm(paramsTransactionId) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (!transactionId) { |
||||||
|
this.setTransactionToConfirm() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setTransactionToConfirm () { |
||||||
|
const { |
||||||
|
history, |
||||||
|
unconfirmedTransactions, |
||||||
|
setTransactionToConfirm, |
||||||
|
} = this.props |
||||||
|
const paramsTransactionId = this.getParamsTransactionId() |
||||||
|
|
||||||
|
if (paramsTransactionId) { |
||||||
|
// Check to make sure params ID is valid
|
||||||
|
const tx = unconfirmedTransactions.find(({ id }) => id + '' === paramsTransactionId) |
||||||
|
|
||||||
|
if (!tx) { |
||||||
|
history.replace(DEFAULT_ROUTE) |
||||||
|
} else { |
||||||
|
setTransactionToConfirm(paramsTransactionId) |
||||||
|
} |
||||||
|
} else if (unconfirmedTransactions.length) { |
||||||
|
const totalUnconfirmed = unconfirmedTransactions.length |
||||||
|
const transaction = unconfirmedTransactions[totalUnconfirmed - 1] |
||||||
|
const { id: transactionId, loadingDefaults } = transaction |
||||||
|
|
||||||
|
if (!loadingDefaults) { |
||||||
|
setTransactionToConfirm(transactionId) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { confirmTransaction: { txData: { id } } = {} } = this.props |
||||||
|
const paramsTransactionId = this.getParamsTransactionId() |
||||||
|
|
||||||
|
// Show routes when state.confirmTransaction has been set and when either the ID in the params
|
||||||
|
// isn't specified or is specified and matches the ID in state.confirmTransaction in order to
|
||||||
|
// support URLs of /confirm-transaction or /confirm-transaction/<transactionId>
|
||||||
|
return id && (!paramsTransactionId || paramsTransactionId === id + '') |
||||||
|
? ( |
||||||
|
<Switch> |
||||||
|
<Route |
||||||
|
exact |
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_DEPLOY_CONTRACT_PATH}`} |
||||||
|
component={ConfirmDeployContract} |
||||||
|
/> |
||||||
|
<Route |
||||||
|
exact |
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TOKEN_METHOD_PATH}`} |
||||||
|
component={ConfirmTransactionBase} |
||||||
|
/> |
||||||
|
<Route |
||||||
|
exact |
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_ETHER_PATH}`} |
||||||
|
component={ConfirmSendEther} |
||||||
|
/> |
||||||
|
<Route |
||||||
|
exact |
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`} |
||||||
|
component={ConfirmSendToken} |
||||||
|
/> |
||||||
|
<Route |
||||||
|
exact |
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`} |
||||||
|
component={ConfirmApprove} |
||||||
|
/> |
||||||
|
<Route |
||||||
|
exact |
||||||
|
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`} |
||||||
|
component={ConfTx} |
||||||
|
/> |
||||||
|
<Route path="*" component={ConfirmTransactionSwitch} /> |
||||||
|
</Switch> |
||||||
|
) |
||||||
|
: <Loading /> |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import { |
||||||
|
setTransactionToConfirm, |
||||||
|
clearConfirmTransaction, |
||||||
|
} from '../../../ducks/confirm-transaction.duck' |
||||||
|
import ConfirmTransaction from './confirm-transaction.component' |
||||||
|
import { getTotalUnapprovedCount } from '../../../selectors' |
||||||
|
import { unconfirmedTransactionsListSelector } from '../../../selectors/confirm-transaction' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { metamask: { send }, confirmTransaction } = state |
||||||
|
|
||||||
|
return { |
||||||
|
totalUnapprovedCount: getTotalUnapprovedCount(state), |
||||||
|
send, |
||||||
|
confirmTransaction, |
||||||
|
unconfirmedTransactions: unconfirmedTransactionsListSelector(state), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)), |
||||||
|
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps), |
||||||
|
)(ConfirmTransaction) |
@ -0,0 +1,2 @@ |
|||||||
|
import ConfirmTransaction from './confirm-transaction.container' |
||||||
|
module.exports = ConfirmTransaction |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue