mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Refactor and redesign confirm transaction views
This commit is contained in:
parent
b4aaf30d6f
commit
ea9d51e427
@ -40,6 +40,9 @@
|
|||||||
"message": "MetaMask",
|
"message": "MetaMask",
|
||||||
"description": "The name of the application"
|
"description": "The name of the application"
|
||||||
},
|
},
|
||||||
|
"approve": {
|
||||||
|
"message": "Approve"
|
||||||
|
},
|
||||||
"approved": {
|
"approved": {
|
||||||
"message": "Approved"
|
"message": "Approved"
|
||||||
},
|
},
|
||||||
@ -89,6 +92,9 @@
|
|||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin."
|
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin."
|
||||||
},
|
},
|
||||||
|
"bytes": {
|
||||||
|
"message": "Bytes"
|
||||||
|
},
|
||||||
"ok": {
|
"ok": {
|
||||||
"message": "Ok"
|
"message": "Ok"
|
||||||
},
|
},
|
||||||
@ -149,6 +155,9 @@
|
|||||||
"copyContractAddress": {
|
"copyContractAddress": {
|
||||||
"message": "Copy Contract Address"
|
"message": "Copy Contract Address"
|
||||||
},
|
},
|
||||||
|
"copyAddress": {
|
||||||
|
"message": "Copy address to clipboard"
|
||||||
|
},
|
||||||
"copyToClipboard": {
|
"copyToClipboard": {
|
||||||
"message": "Copy to clipboard"
|
"message": "Copy to clipboard"
|
||||||
},
|
},
|
||||||
@ -318,6 +327,9 @@
|
|||||||
"fromShapeShift": {
|
"fromShapeShift": {
|
||||||
"message": "From ShapeShift"
|
"message": "From ShapeShift"
|
||||||
},
|
},
|
||||||
|
"functionType": {
|
||||||
|
"message": "Function Type"
|
||||||
|
},
|
||||||
"gas": {
|
"gas": {
|
||||||
"message": "Gas",
|
"message": "Gas",
|
||||||
"description": "Short indication of gas cost"
|
"description": "Short indication of gas cost"
|
||||||
@ -370,6 +382,9 @@
|
|||||||
"hereList": {
|
"hereList": {
|
||||||
"message": "Here's a list!!!!"
|
"message": "Here's a list!!!!"
|
||||||
},
|
},
|
||||||
|
"hexData": {
|
||||||
|
"message": "Hex Data"
|
||||||
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"message": "Hide"
|
"message": "Hide"
|
||||||
},
|
},
|
||||||
@ -582,6 +597,9 @@
|
|||||||
"message": "or",
|
"message": "or",
|
||||||
"description": "choice between creating or importing a new account"
|
"description": "choice between creating or importing a new account"
|
||||||
},
|
},
|
||||||
|
"origin": {
|
||||||
|
"message": "Origin"
|
||||||
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"message": "Password"
|
"message": "Password"
|
||||||
},
|
},
|
||||||
@ -911,6 +929,9 @@
|
|||||||
"transactionNumber": {
|
"transactionNumber": {
|
||||||
"message": "Transaction Number"
|
"message": "Transaction Number"
|
||||||
},
|
},
|
||||||
|
"transfer": {
|
||||||
|
"message": "Transfer"
|
||||||
|
},
|
||||||
"transfers": {
|
"transfers": {
|
||||||
"message": "Transfers"
|
"message": "Transfers"
|
||||||
},
|
},
|
||||||
|
14
app/images/alert-red.svg
Normal file
14
app/images/alert-red.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Artboard Copy</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Artboard-Copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group-48">
|
||||||
|
<circle id="Oval" fill="#D0021B" cx="8" cy="8" r="8"></circle>
|
||||||
|
<rect id="Rectangle-41" fill="#FFFFFF" x="7" y="3" width="2" height="7" rx="1"></rect>
|
||||||
|
<rect id="Rectangle-41" fill="#FFFFFF" x="7" y="11" width="2" height="2" rx="1"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 774 B |
19
app/images/alert.svg
Normal file
19
app/images/alert.svg
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="29px" height="29px" viewBox="0 0 29 29" 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>7414FFD8-B28A-4593-9D7E-19E73D687B50</title>
|
||||||
|
<desc>Created with sketchtool.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Approve---insufficient-amount" transform="translate(-69.000000, -166.000000)">
|
||||||
|
<g id="Group-7" transform="translate(53.000000, 51.000000)">
|
||||||
|
<g id="Group-34" transform="translate(0.000000, 91.000000)">
|
||||||
|
<g id="alert" transform="translate(16.000000, 24.000000)">
|
||||||
|
<circle id="Oval" fill="#605A1C" cx="14.5" cy="14.5" r="14.5"></circle>
|
||||||
|
<path d="M16,16.8282967 L14,16.8282967 L14,7 L16,7 L16,16.8282967 Z M16,21 L14,21 L14,19 L16,19 L16,21 Z" id="!" fill="#FFFCDB"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
18
app/images/caret-left.svg
Normal file
18
app/images/caret-left.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="9px" height="15px" viewBox="0 0 9 15" 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>8439120D-5704-4273-B416-FEE134322584</title>
|
||||||
|
<desc>Created with sketchtool.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Approve---insufficient-amount" transform="translate(-75.000000, -69.000000)" stroke="#3099F2" stroke-width="2">
|
||||||
|
<g id="Group-7" transform="translate(53.000000, 51.000000)">
|
||||||
|
<g id="cancel" transform="translate(24.000000, 14.000000)">
|
||||||
|
<g id="Group">
|
||||||
|
<polyline id="Path-8" points="6.1263881 18.0633906 0 11.6306831 6.31493631 5"></polyline>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 992 B |
@ -4,6 +4,7 @@ const KOVAN = 'kovan'
|
|||||||
const MAINNET = 'mainnet'
|
const MAINNET = 'mainnet'
|
||||||
const LOCALHOST = 'localhost'
|
const LOCALHOST = 'localhost'
|
||||||
|
|
||||||
|
const MAINNET_CODE = 1
|
||||||
const ROPSTEN_CODE = 3
|
const ROPSTEN_CODE = 3
|
||||||
const RINKEYBY_CODE = 4
|
const RINKEYBY_CODE = 4
|
||||||
const KOVAN_CODE = 42
|
const KOVAN_CODE = 42
|
||||||
@ -13,13 +14,13 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby'
|
|||||||
const KOVAN_DISPLAY_NAME = 'Kovan'
|
const KOVAN_DISPLAY_NAME = 'Kovan'
|
||||||
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ROPSTEN,
|
ROPSTEN,
|
||||||
RINKEBY,
|
RINKEBY,
|
||||||
KOVAN,
|
KOVAN,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
LOCALHOST,
|
LOCALHOST,
|
||||||
|
MAINNET_CODE,
|
||||||
ROPSTEN_CODE,
|
ROPSTEN_CODE,
|
||||||
RINKEYBY_CODE,
|
RINKEYBY_CODE,
|
||||||
KOVAN_CODE,
|
KOVAN_CODE,
|
||||||
|
@ -399,19 +399,17 @@ class TransactionStateManager extends EventEmitter {
|
|||||||
_setTxStatus (txId, status) {
|
_setTxStatus (txId, status) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.status = status
|
txMeta.status = status
|
||||||
setTimeout(() => {
|
try {
|
||||||
try {
|
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
this.emit(`${txMeta.id}:${status}`, txId)
|
||||||
this.emit(`${txMeta.id}:${status}`, txId)
|
this.emit(`tx:status-update`, txId, status)
|
||||||
this.emit(`tx:status-update`, txId, status)
|
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
||||||
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
|
||||||
}
|
|
||||||
this.emit('update:badge')
|
|
||||||
} catch (error) {
|
|
||||||
log.error(error)
|
|
||||||
}
|
}
|
||||||
})
|
this.emit('update:badge')
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
3467
package-lock.json
generated
3467
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -179,6 +179,7 @@
|
|||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"request-promise": "^4.2.1",
|
"request-promise": "^4.2.1",
|
||||||
|
"reselect": "^3.0.1",
|
||||||
"sandwich-expando": "^1.1.3",
|
"sandwich-expando": "^1.1.3",
|
||||||
"semaphore": "^1.0.5",
|
"semaphore": "^1.0.5",
|
||||||
"semver": "^5.4.1",
|
"semver": "^5.4.1",
|
||||||
|
@ -704,11 +704,10 @@ function signTypedMsg (msgData) {
|
|||||||
|
|
||||||
function signTx (txData) {
|
function signTx (txData) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
|
||||||
global.ethQuery.sendTransaction(txData, (err, data) => {
|
global.ethQuery.sendTransaction(txData, (err, data) => {
|
||||||
dispatch(actions.hideLoadingIndication())
|
if (err) {
|
||||||
if (err) return dispatch(actions.displayWarning(err.message))
|
return dispatch(actions.displayWarning(err.message))
|
||||||
dispatch(actions.hideWarning())
|
}
|
||||||
})
|
})
|
||||||
dispatch(actions.showConfTxPage({}))
|
dispatch(actions.showConfTxPage({}))
|
||||||
}
|
}
|
||||||
@ -910,29 +909,41 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
|
|||||||
|
|
||||||
function updateTransaction (txData) {
|
function updateTransaction (txData) {
|
||||||
log.info('actions: updateTx: ' + JSON.stringify(txData))
|
log.info('actions: updateTx: ' + JSON.stringify(txData))
|
||||||
return (dispatch) => {
|
return dispatch => {
|
||||||
log.debug(`actions calling background.updateTx`)
|
log.debug(`actions calling background.updateTx`)
|
||||||
background.updateTransaction(txData, (err) => {
|
dispatch(actions.showLoadingIndication())
|
||||||
dispatch(actions.hideLoadingIndication())
|
|
||||||
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
return new Promise((resolve, reject) => {
|
||||||
if (err) {
|
background.updateTransaction(txData, (err) => {
|
||||||
dispatch(actions.txError(err))
|
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
||||||
dispatch(actions.goHome())
|
if (err) {
|
||||||
return log.error(err.message)
|
dispatch(actions.txError(err))
|
||||||
}
|
dispatch(actions.goHome())
|
||||||
dispatch(actions.showConfTxPage({ id: txData.id }))
|
log.error(err.message)
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(txData)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
.then(() => updateMetamaskStateFromBackground())
|
||||||
|
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||||
|
.then(() => {
|
||||||
|
dispatch(actions.showConfTxPage({ id: txData.id }))
|
||||||
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
return txData
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAndApproveTx (txData) {
|
function updateAndApproveTx (txData) {
|
||||||
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
|
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
|
||||||
return (dispatch) => {
|
return dispatch => {
|
||||||
log.debug(`actions calling background.updateAndApproveTx`)
|
log.debug(`actions calling background.updateAndApproveTx`)
|
||||||
|
dispatch(actions.showLoadingIndication())
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
background.updateAndApproveTransaction(txData, err => {
|
background.updateAndApproveTransaction(txData, err => {
|
||||||
dispatch(actions.hideLoadingIndication())
|
|
||||||
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
||||||
dispatch(actions.clearSend())
|
dispatch(actions.clearSend())
|
||||||
|
|
||||||
@ -943,10 +954,17 @@ function updateAndApproveTx (txData) {
|
|||||||
reject(err)
|
reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(actions.completedTx(txData.id))
|
|
||||||
resolve(txData)
|
resolve(txData)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.then(() => updateMetamaskStateFromBackground())
|
||||||
|
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||||
|
.then(() => {
|
||||||
|
dispatch(actions.clearSend())
|
||||||
|
dispatch(actions.completedTx(txData.id))
|
||||||
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
return txData
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1038,13 +1056,25 @@ function cancelTypedMsg (msgData) {
|
|||||||
function cancelTx (txData) {
|
function cancelTx (txData) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
log.debug(`background.cancelTransaction`)
|
log.debug(`background.cancelTransaction`)
|
||||||
|
dispatch(actions.showLoadingIndication())
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
background.cancelTransaction(txData.id, () => {
|
background.cancelTransaction(txData.id, err => {
|
||||||
dispatch(actions.clearSend())
|
if (err) {
|
||||||
dispatch(actions.completedTx(txData.id))
|
return reject(err)
|
||||||
resolve(txData)
|
}
|
||||||
|
|
||||||
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.then(() => updateMetamaskStateFromBackground())
|
||||||
|
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||||
|
.then(() => {
|
||||||
|
dispatch(actions.clearSend())
|
||||||
|
dispatch(actions.completedTx(txData.id))
|
||||||
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
return txData
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const log = require('loglevel')
|
|||||||
const InitializeScreen = require('../../mascara/src/app/first-time').default
|
const InitializeScreen = require('../../mascara/src/app/first-time').default
|
||||||
// accounts
|
// accounts
|
||||||
const SendTransactionScreen = require('./components/send_/send.container')
|
const SendTransactionScreen = require('./components/send_/send.container')
|
||||||
const ConfirmTxScreen = require('./conf-tx')
|
const ConfirmTransaction = require('./components/pages/confirm-transaction')
|
||||||
|
|
||||||
// slideout menu
|
// slideout menu
|
||||||
const WalletView = require('./components/wallet-view')
|
const WalletView = require('./components/wallet-view')
|
||||||
@ -76,7 +76,10 @@ class App extends Component {
|
|||||||
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
|
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
|
||||||
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
|
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
|
||||||
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
|
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
|
||||||
h(Authenticated, { path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`, component: ConfirmTxScreen }),
|
h(Authenticated, {
|
||||||
|
path: `${CONFIRM_TRANSACTION_ROUTE}/:id?`,
|
||||||
|
component: ConfirmTransaction,
|
||||||
|
}),
|
||||||
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
|
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
|
||||||
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
||||||
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
|
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
|
||||||
|
@ -5,15 +5,24 @@ import classnames from 'classnames'
|
|||||||
const CLASSNAME_DEFAULT = 'btn-default'
|
const CLASSNAME_DEFAULT = 'btn-default'
|
||||||
const CLASSNAME_PRIMARY = 'btn-primary'
|
const CLASSNAME_PRIMARY = 'btn-primary'
|
||||||
const CLASSNAME_SECONDARY = 'btn-secondary'
|
const CLASSNAME_SECONDARY = 'btn-secondary'
|
||||||
|
const CLASSNAME_CONFIRM = 'btn-confirm'
|
||||||
const CLASSNAME_LARGE = 'btn--large'
|
const CLASSNAME_LARGE = 'btn--large'
|
||||||
|
|
||||||
const typeHash = {
|
const typeHash = {
|
||||||
default: CLASSNAME_DEFAULT,
|
default: CLASSNAME_DEFAULT,
|
||||||
primary: CLASSNAME_PRIMARY,
|
primary: CLASSNAME_PRIMARY,
|
||||||
secondary: CLASSNAME_SECONDARY,
|
secondary: CLASSNAME_SECONDARY,
|
||||||
|
confirm: CLASSNAME_CONFIRM,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Button extends Component {
|
export default class Button extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
type: PropTypes.string,
|
||||||
|
large: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
children: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { type, large, className, ...buttonProps } = this.props
|
const { type, large, className, ...buttonProps } = this.props
|
||||||
|
|
||||||
@ -31,13 +40,3 @@ class Button extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button.propTypes = {
|
|
||||||
type: PropTypes.string,
|
|
||||||
large: PropTypes.bool,
|
|
||||||
className: PropTypes.string,
|
|
||||||
children: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Button
|
|
||||||
|
|
||||||
|
@ -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,100 @@
|
|||||||
|
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,
|
||||||
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
titleComponent: PropTypes.func,
|
||||||
|
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
hideSubtitle: PropTypes.bool,
|
||||||
|
errorMessage: PropTypes.string,
|
||||||
|
summaryComponent: PropTypes.node,
|
||||||
|
detailsComponent: PropTypes.node,
|
||||||
|
dataComponent: PropTypes.node,
|
||||||
|
identiconAddress: PropTypes.string,
|
||||||
|
nonce: PropTypes.string,
|
||||||
|
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,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
hideSubtitle,
|
||||||
|
errorMessage,
|
||||||
|
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() }
|
||||||
|
{
|
||||||
|
errorMessage && (
|
||||||
|
<div className="confirm-page-container-content__error-container">
|
||||||
|
<ConfirmPageContainerError error={errorMessage} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
const ConfirmPageContainerError = props => {
|
||||||
|
return (
|
||||||
|
<div className="confirm-page-container-error">
|
||||||
|
<img
|
||||||
|
src="/images/alert-red.svg"
|
||||||
|
className="confirm-page-container-error__icon"
|
||||||
|
/>
|
||||||
|
{ `ALERT: ${props.error}` }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmPageContainerError.propTypes = {
|
||||||
|
error: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
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: $oslo-gray;
|
||||||
|
}
|
||||||
|
}
|
@ -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,116 @@
|
|||||||
|
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 = {
|
||||||
|
showEdit: PropTypes.bool,
|
||||||
|
onEdit: PropTypes.func,
|
||||||
|
// Sender to Recipient
|
||||||
|
fromName: PropTypes.string,
|
||||||
|
fromAddress: PropTypes.string,
|
||||||
|
toName: PropTypes.string,
|
||||||
|
toAddress: PropTypes.string,
|
||||||
|
|
||||||
|
valid: PropTypes.bool,
|
||||||
|
errorMessage: PropTypes.string,
|
||||||
|
// Header
|
||||||
|
action: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
titleComponent: PropTypes.func,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
hideSubtitle: PropTypes.bool,
|
||||||
|
// Content
|
||||||
|
summaryComponent: PropTypes.node,
|
||||||
|
contentComponent: PropTypes.node,
|
||||||
|
fiatTransactionAmount: PropTypes.string,
|
||||||
|
fiatTransactionFee: PropTypes.string,
|
||||||
|
fiatTransactionTotal: PropTypes.string,
|
||||||
|
ethTransactionAmount: PropTypes.string,
|
||||||
|
ethTransactionFee: PropTypes.string,
|
||||||
|
ethTransactionTotal: PropTypes.string,
|
||||||
|
onEditGas: PropTypes.func,
|
||||||
|
detailsComponent: PropTypes.node,
|
||||||
|
dataComponent: PropTypes.node,
|
||||||
|
identiconAddress: PropTypes.string,
|
||||||
|
nonce: PropTypes.string,
|
||||||
|
warning: PropTypes.string,
|
||||||
|
// Footer
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
showEdit,
|
||||||
|
onEdit,
|
||||||
|
fromName,
|
||||||
|
fromAddress,
|
||||||
|
toName,
|
||||||
|
toAddress,
|
||||||
|
valid,
|
||||||
|
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}
|
||||||
|
identiconAddress={identiconAddress}
|
||||||
|
nonce={nonce}
|
||||||
|
warning={warning}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<PageContainerFooter
|
||||||
|
onCancel={() => onCancel()}
|
||||||
|
onSubmit={() => onSubmit()}
|
||||||
|
submitText={this.context.t('confirm')}
|
||||||
|
submitButtonType="confirm"
|
||||||
|
disabled={!valid}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
8
ui/app/components/confirm-page-container/index.js
Normal file
8
ui/app/components/confirm-page-container/index.js
Normal file
@ -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'
|
5
ui/app/components/confirm-page-container/index.scss
Normal file
5
ui/app/components/confirm-page-container/index.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@import './confirm-page-container-content/index';
|
||||||
|
|
||||||
|
@import './confirm-page-container-header/index';
|
||||||
|
|
||||||
|
@import './confirm-detail-row/index';
|
@ -15,6 +15,7 @@ NetworkDropdownIcon.prototype.render = function () {
|
|||||||
backgroundColor,
|
backgroundColor,
|
||||||
isSelected,
|
isSelected,
|
||||||
innerBorder = 'none',
|
innerBorder = 'none',
|
||||||
|
diameter = '12',
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
|
return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
|
||||||
@ -22,6 +23,8 @@ NetworkDropdownIcon.prototype.render = function () {
|
|||||||
style: {
|
style: {
|
||||||
background: backgroundColor,
|
background: backgroundColor,
|
||||||
border: innerBorder,
|
border: innerBorder,
|
||||||
|
height: `${diameter}px`,
|
||||||
|
width: `${diameter}px`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,16 @@
|
|||||||
|
|
||||||
@import './info-box/index';
|
@import './info-box/index';
|
||||||
|
|
||||||
|
@import './network-display/index';
|
||||||
|
|
||||||
|
@import './confirm-page-container/index';
|
||||||
|
|
||||||
|
@import './page-container/index';
|
||||||
|
|
||||||
@import './pages/index';
|
@import './pages/index';
|
||||||
|
|
||||||
@import './modals/index';
|
@import './modals/index';
|
||||||
|
|
||||||
|
@import './sender-to-recipient/index';
|
||||||
|
|
||||||
|
@import './tabs/index';
|
||||||
|
@ -2,10 +2,8 @@ const Component = require('react').Component
|
|||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const {
|
const {
|
||||||
addCurrencies,
|
|
||||||
conversionGTE,
|
conversionGTE,
|
||||||
conversionLTE,
|
conversionLTE,
|
||||||
subtractCurrencies,
|
|
||||||
} = require('../conversion-util')
|
} = require('../conversion-util')
|
||||||
|
|
||||||
module.exports = InputNumber
|
module.exports = InputNumber
|
||||||
@ -51,7 +49,11 @@ InputNumber.prototype.setValue = function (newValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InputNumber.prototype.render = function () {
|
InputNumber.prototype.render = function () {
|
||||||
|
<<<<<<< HEAD
|
||||||
const { unitLabel, step = 1, placeholder, value } = this.props
|
const { unitLabel, step = 1, placeholder, value } = this.props
|
||||||
|
=======
|
||||||
|
const { unitLabel, step = 1, placeholder, value = 0, min = -1, max = Infinity } = this.props
|
||||||
|
>>>>>>> Refactor and redesign confirm transaction views
|
||||||
|
|
||||||
return h('div.customize-gas-input-wrapper', {}, [
|
return h('div.customize-gas-input-wrapper', {}, [
|
||||||
h('input', {
|
h('input', {
|
||||||
@ -67,11 +69,19 @@ InputNumber.prototype.render = function () {
|
|||||||
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
||||||
h('div.gas-tooltip-input-arrows', {}, [
|
h('div.gas-tooltip-input-arrows', {}, [
|
||||||
h('i.fa.fa-angle-up', {
|
h('i.fa.fa-angle-up', {
|
||||||
|
<<<<<<< HEAD
|
||||||
onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })),
|
onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })),
|
||||||
}),
|
}),
|
||||||
h('i.fa.fa-angle-down', {
|
h('i.fa.fa-angle-down', {
|
||||||
style: { cursor: 'pointer' },
|
style: { cursor: 'pointer' },
|
||||||
onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })),
|
onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })),
|
||||||
|
=======
|
||||||
|
onClick: () => this.setValue(Math.min(+value + step, max)),
|
||||||
|
}),
|
||||||
|
h('i.fa.fa-angle-down', {
|
||||||
|
style: { cursor: 'pointer' },
|
||||||
|
onClick: () => this.setValue(Math.max(+value - step, min)),
|
||||||
|
>>>>>>> Refactor and redesign confirm transaction views
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
|
@ -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, errorMessage } = 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">{ errorMessage }</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)
|
34
ui/app/components/modals/customize-gas/customize-gas.util.js
Normal file
34
ui/app/components/modals/customize-gas/customize-gas.util.js
Normal file
@ -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',
|
||||||
|
}))
|
||||||
|
}
|
1
ui/app/components/modals/customize-gas/index.js
Normal file
1
ui/app/components/modals/customize-gas/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './customize-gas.container'
|
110
ui/app/components/modals/customize-gas/index.scss
Normal file
110
ui/app/components/modals/customize-gas/index.scss
Normal file
@ -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,3 +1,5 @@
|
|||||||
|
@import './customize-gas/index';
|
||||||
|
|
||||||
.modal-container {
|
.modal-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -24,6 +24,8 @@ const TransactionConfirmed = require('./transaction-confirmed')
|
|||||||
const WelcomeBeta = require('./welcome-beta')
|
const WelcomeBeta = require('./welcome-beta')
|
||||||
const Notification = require('./notification')
|
const Notification = require('./notification')
|
||||||
|
|
||||||
|
import ConfirmCustomizeGasModal from './customize-gas'
|
||||||
|
|
||||||
const modalContainerBaseStyle = {
|
const modalContainerBaseStyle = {
|
||||||
transform: 'translate3d(-50%, 0, 0px)',
|
transform: 'translate3d(-50%, 0, 0px)',
|
||||||
border: '1px solid #CCCFD1',
|
border: '1px solid #CCCFD1',
|
||||||
@ -267,7 +269,31 @@ const MODALS = {
|
|||||||
|
|
||||||
CUSTOMIZE_GAS: {
|
CUSTOMIZE_GAS: {
|
||||||
contents: [
|
contents: [
|
||||||
h(CustomizeGasModal, {}, []),
|
h(CustomizeGasModal),
|
||||||
|
],
|
||||||
|
mobileModalStyle: {
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
top: '0',
|
||||||
|
transform: 'none',
|
||||||
|
left: '0',
|
||||||
|
right: '0',
|
||||||
|
margin: '0 auto',
|
||||||
|
},
|
||||||
|
laptopModalStyle: {
|
||||||
|
width: '720px',
|
||||||
|
height: '377px',
|
||||||
|
top: '80px',
|
||||||
|
transform: 'none',
|
||||||
|
left: '0',
|
||||||
|
right: '0',
|
||||||
|
margin: '0 auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
CONFIRM_CUSTOMIZE_GAS: {
|
||||||
|
contents: [
|
||||||
|
h(ConfirmCustomizeGasModal),
|
||||||
],
|
],
|
||||||
mobileModalStyle: {
|
mobileModalStyle: {
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
|
@ -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)
|
|
||||||
|
|
2
ui/app/components/network-display/index.js
Normal file
2
ui/app/components/network-display/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import NetworkDisplay from './network-display.container'
|
||||||
|
module.exports = NetworkDisplay
|
54
ui/app/components/network-display/index.scss
Normal file
54
ui/app/components/network-display/index.scss
Normal file
@ -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: .875rem;
|
||||||
|
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 }
|
||||||
|
186
ui/app/components/page-container/index.scss
Normal file
186
ui/app/components/page-container/index.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ export default class PageContainerFooter extends Component {
|
|||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
submitText: PropTypes.string,
|
submitText: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
submitButtonType: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -23,6 +24,7 @@ export default class PageContainerFooter extends Component {
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
submitText,
|
submitText,
|
||||||
disabled,
|
disabled,
|
||||||
|
submitButtonType,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -30,16 +32,16 @@ export default class PageContainerFooter extends Component {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
large={true}
|
large
|
||||||
className="page-container__footer-button"
|
className="page-container__footer-button"
|
||||||
onClick={() => onCancel()}
|
onClick={e => onCancel(e)}
|
||||||
>
|
>
|
||||||
{ cancelText || this.context.t('cancel') }
|
{ cancelText || this.context.t('cancel') }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type={submitButtonType || 'primary'}
|
||||||
large={true}
|
large
|
||||||
className="page-container__footer-button"
|
className="page-container__footer-button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={e => onSubmit(e)}
|
onClick={e => onSubmit(e)}
|
||||||
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -4,13 +4,14 @@ import PropTypes from 'prop-types'
|
|||||||
export default class PageContainerHeader extends Component {
|
export default class PageContainerHeader extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string,
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
showBackButton: PropTypes.bool,
|
showBackButton: PropTypes.bool,
|
||||||
onBackButtonClick: PropTypes.func,
|
onBackButtonClick: PropTypes.func,
|
||||||
backButtonStyles: PropTypes.object,
|
backButtonStyles: PropTypes.object,
|
||||||
backButtonString: PropTypes.string,
|
backButtonString: PropTypes.string,
|
||||||
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHeaderRow () {
|
renderHeaderRow () {
|
||||||
@ -30,25 +31,33 @@ export default class PageContainerHeader extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { title, subtitle, onClose } = this.props
|
const { title, subtitle, onClose, children } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container__header">
|
<div className="page-container__header">
|
||||||
|
|
||||||
{ this.renderHeaderRow() }
|
{ this.renderHeaderRow() }
|
||||||
|
|
||||||
<div className="page-container__title">
|
{ children }
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="page-container__subtitle">
|
{
|
||||||
{subtitle}
|
title && <div className="page-container__title">
|
||||||
</div>
|
{ title }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div
|
{
|
||||||
className="page-container__header-close"
|
subtitle && <div className="page-container__subtitle">
|
||||||
onClick={() => onClose()}
|
{ subtitle }
|
||||||
/>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
onClose && <div
|
||||||
|
className="page-container__header-close"
|
||||||
|
onClick={() => onClose()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</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)
|
1
ui/app/components/pages/confirm-approve/index.js
Normal file
1
ui/app/components/pages/confirm-approve/index.js
Normal file
@ -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)
|
1
ui/app/components/pages/confirm-deploy-contract/index.js
Normal file
1
ui/app/components/pages/confirm-deploy-contract/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './confirm-deploy-contract.container'
|
@ -0,0 +1,31 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEdit ({ txData }) {
|
||||||
|
const { editTransaction, history } = this.props
|
||||||
|
editTransaction(txData)
|
||||||
|
history.push(SEND_ROUTE)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<ConfirmTransactionBase
|
||||||
|
action={this.context.t('confirm')}
|
||||||
|
hideData
|
||||||
|
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
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 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(null, mapDispatchToProps)
|
||||||
|
)(ConfirmSendEther)
|
1
ui/app/components/pages/confirm-send-ether/index.js
Normal file
1
ui/app/components/pages/confirm-send-ether/index.js
Normal file
@ -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)
|
1
ui/app/components/pages/confirm-send-token/index.js
Normal file
1
ui/app/components/pages/confirm-send-token/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './confirm-send-token.container'
|
19
ui/app/components/pages/confirm-send-token/index.scss
Normal file
19
ui/app/components/pages/confirm-send-token/index.scss
Normal file
@ -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,382 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
|
||||||
|
import { formatCurrency, getHexGasTotal } from '../../../helpers/confirm-transaction/util'
|
||||||
|
import { isBalanceSufficient } from '../../send_/send.utils'
|
||||||
|
import { DEFAULT_ROUTE } from '../../../routes'
|
||||||
|
import { conversionGreaterThan } from '../../../conversion-util'
|
||||||
|
import { MIN_GAS_LIMIT_DEC } from '../../send_/send.constants'
|
||||||
|
|
||||||
|
export default class ConfirmTransactionBase extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
match: PropTypes.object,
|
||||||
|
history: PropTypes.object,
|
||||||
|
// Redux props
|
||||||
|
txData: PropTypes.object,
|
||||||
|
tokenData: PropTypes.object,
|
||||||
|
tokenProps: PropTypes.object,
|
||||||
|
isTxReprice: PropTypes.bool,
|
||||||
|
nonce: PropTypes.string,
|
||||||
|
fromName: PropTypes.string,
|
||||||
|
fromAddress: PropTypes.string,
|
||||||
|
toName: PropTypes.string,
|
||||||
|
toAddress: PropTypes.string,
|
||||||
|
transactionStatus: PropTypes.string,
|
||||||
|
ethTransactionAmount: PropTypes.string,
|
||||||
|
ethTransactionFee: PropTypes.string,
|
||||||
|
ethTransactionTotal: PropTypes.string,
|
||||||
|
fiatTransactionAmount: PropTypes.string,
|
||||||
|
fiatTransactionFee: PropTypes.string,
|
||||||
|
fiatTransactionTotal: PropTypes.string,
|
||||||
|
hexGasTotal: PropTypes.string,
|
||||||
|
balance: PropTypes.string,
|
||||||
|
currentCurrency: PropTypes.string,
|
||||||
|
conversionRate: PropTypes.number,
|
||||||
|
setTransactionToConfirm: PropTypes.func,
|
||||||
|
clearConfirmTransaction: PropTypes.func,
|
||||||
|
cancelTransaction: PropTypes.func,
|
||||||
|
clearSend: PropTypes.func,
|
||||||
|
sendTransaction: PropTypes.func,
|
||||||
|
editTransaction: PropTypes.func,
|
||||||
|
showCustomizeGasModal: PropTypes.func,
|
||||||
|
updateGasAndCalculate: PropTypes.func,
|
||||||
|
showTransactionConfirmedModal: PropTypes.func,
|
||||||
|
// Component props
|
||||||
|
action: PropTypes.string,
|
||||||
|
hideDetails: PropTypes.bool,
|
||||||
|
hideData: PropTypes.bool,
|
||||||
|
detailsComponent: PropTypes.node,
|
||||||
|
dataComponent: PropTypes.node,
|
||||||
|
summaryComponent: PropTypes.node,
|
||||||
|
contentComponent: PropTypes.node,
|
||||||
|
title: PropTypes.string,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
hideSubtitle: PropTypes.bool,
|
||||||
|
valid: PropTypes.bool,
|
||||||
|
errorMessage: PropTypes.string,
|
||||||
|
warning: PropTypes.string,
|
||||||
|
identiconAddress: PropTypes.string,
|
||||||
|
onEdit: PropTypes.func,
|
||||||
|
onEditGas: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { match: { params: { id } = {} }, setTransactionToConfirm } = this.props
|
||||||
|
setTransactionToConfirm(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
const {
|
||||||
|
transactionStatus,
|
||||||
|
showTransactionConfirmedModal,
|
||||||
|
history,
|
||||||
|
clearConfirmTransaction,
|
||||||
|
match: { params: { id } = {} },
|
||||||
|
setTransactionToConfirm,
|
||||||
|
txData,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (transactionStatus === 'dropped') {
|
||||||
|
showTransactionConfirmedModal({
|
||||||
|
onHide: () => {
|
||||||
|
clearConfirmTransaction()
|
||||||
|
history.push(DEFAULT_ROUTE)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id && id !== txData.id + '') {
|
||||||
|
setTransactionToConfirm(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getError () {
|
||||||
|
const INSUFFICIENT_FUNDS_ERROR = this.context.t('insufficientFunds')
|
||||||
|
const TRANSACTION_ERROR = this.context.t('transactionError')
|
||||||
|
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,
|
||||||
|
errorMessage: INSUFFICIENT_FUNDS_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simulationFails) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
errorMessage: TRANSACTION_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateEditGas ({ gasLimit, gasPrice }) {
|
||||||
|
const { t } = this.context
|
||||||
|
const {
|
||||||
|
balance,
|
||||||
|
conversionRate,
|
||||||
|
txData: {
|
||||||
|
txParams: {
|
||||||
|
value: amount,
|
||||||
|
} = {},
|
||||||
|
} = {},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const INSUFFICIENT_FUNDS_ERROR = t('insufficientFunds')
|
||||||
|
const GAS_LIMIT_TOO_LOW_ERROR = t('gasLimitTooLow')
|
||||||
|
|
||||||
|
const gasTotal = getHexGasTotal({ gasLimit, gasPrice })
|
||||||
|
const hasSufficientBalance = isBalanceSufficient({
|
||||||
|
amount,
|
||||||
|
gasTotal,
|
||||||
|
balance,
|
||||||
|
conversionRate,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!hasSufficientBalance) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
errorMessage: INSUFFICIENT_FUNDS_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const gasLimitTooLow = gasLimit && conversionGreaterThan(
|
||||||
|
{
|
||||||
|
value: MIN_GAS_LIMIT_DEC,
|
||||||
|
fromNumericBase: 'dec',
|
||||||
|
conversionRate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: gasLimit,
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (gasLimitTooLow) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
errorMessage: GAS_LIMIT_TOO_LOW_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEditGas () {
|
||||||
|
const { onEditGas, showCustomizeGasModal, txData, updateGasAndCalculate } = this.props
|
||||||
|
|
||||||
|
if (onEditGas) {
|
||||||
|
onEditGas()
|
||||||
|
} else {
|
||||||
|
showCustomizeGasModal({
|
||||||
|
txData,
|
||||||
|
onSubmit: txData => updateGasAndCalculate(txData),
|
||||||
|
validate: ({ gasLimit, gasPrice }) => this.validateEditGas({ gasLimit, gasPrice }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
} = {},
|
||||||
|
} = {},
|
||||||
|
tokenData: {
|
||||||
|
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">
|
||||||
|
{ JSON.stringify(params, null, 2) }
|
||||||
|
</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,
|
||||||
|
tokenData,
|
||||||
|
ethTransactionAmount,
|
||||||
|
fiatTransactionAmount,
|
||||||
|
valid: propsValid,
|
||||||
|
errorMessage: propsErrorMessage,
|
||||||
|
currentCurrency,
|
||||||
|
action,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
hideSubtitle,
|
||||||
|
identiconAddress,
|
||||||
|
summaryComponent,
|
||||||
|
contentComponent,
|
||||||
|
onEdit,
|
||||||
|
nonce,
|
||||||
|
warning,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const { name } = tokenData
|
||||||
|
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
|
||||||
|
const { valid, errorMessage } = this.getError()
|
||||||
|
|
||||||
|
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={propsErrorMessage || errorMessage}
|
||||||
|
warning={warning}
|
||||||
|
valid={propsValid || valid}
|
||||||
|
onEdit={() => this.handleEdit()}
|
||||||
|
onCancel={() => this.handleCancel()}
|
||||||
|
onSubmit={() => this.handleSubmit()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
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 {
|
||||||
|
setTransactionToConfirm,
|
||||||
|
clearConfirmTransaction,
|
||||||
|
updateGasAndCalculate,
|
||||||
|
} from '../../../ducks/confirm-transaction.duck'
|
||||||
|
import { clearSend, cancelTx, updateAndApproveTx, showModal } from '../../../actions'
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const { toAddress: propsToAddress } = props
|
||||||
|
const { confirmTransaction, metamask } = state
|
||||||
|
const {
|
||||||
|
ethTransactionAmount,
|
||||||
|
ethTransactionFee,
|
||||||
|
ethTransactionTotal,
|
||||||
|
fiatTransactionAmount,
|
||||||
|
fiatTransactionFee,
|
||||||
|
fiatTransactionTotal,
|
||||||
|
hexGasTotal,
|
||||||
|
tokenData,
|
||||||
|
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,
|
||||||
|
tokenProps,
|
||||||
|
isTxReprice,
|
||||||
|
currentCurrency,
|
||||||
|
conversionRate,
|
||||||
|
transactionStatus,
|
||||||
|
nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)),
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
)(ConfirmTransactionBase)
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './confirm-transaction-base.container'
|
@ -0,0 +1,71 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Redirect } from 'react-router-dom'
|
||||||
|
import R from 'ramda'
|
||||||
|
import Loading from '../../loading-screen'
|
||||||
|
import {
|
||||||
|
CONFIRM_DEPLOY_CONTRACT_ROUTE,
|
||||||
|
CONFIRM_SEND_ETHER_ROUTE,
|
||||||
|
CONFIRM_SEND_TOKEN_ROUTE,
|
||||||
|
CONFIRM_APPROVE_ROUTE,
|
||||||
|
CONFIRM_TOKEN_METHOD_ROUTE,
|
||||||
|
SIGNATURE_REQUEST_ROUTE,
|
||||||
|
} from '../../../routes'
|
||||||
|
import { isConfirmDeployContract, getTokenData } 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 = {
|
||||||
|
unconfirmedTransactions: PropTypes.array,
|
||||||
|
match: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransaction () {
|
||||||
|
const { unconfirmedTransactions, match } = this.props
|
||||||
|
const { params: { id: paramsTransactionId } = {} } = match
|
||||||
|
|
||||||
|
return paramsTransactionId
|
||||||
|
? R.find(({ id }) => id + '' === paramsTransactionId)(unconfirmedTransactions)
|
||||||
|
: unconfirmedTransactions[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectToTransaction (txData) {
|
||||||
|
const { id, txParams: { data } } = txData
|
||||||
|
|
||||||
|
if (isConfirmDeployContract(txData)) {
|
||||||
|
return <Redirect to={{ pathname: `${CONFIRM_DEPLOY_CONTRACT_ROUTE}/${id}` }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const tokenData = getTokenData(data)
|
||||||
|
const { name: tokenMethodName } = tokenData || {}
|
||||||
|
|
||||||
|
switch (tokenMethodName) {
|
||||||
|
case TOKEN_METHOD_TRANSFER:
|
||||||
|
return <Redirect to={{ pathname: `${CONFIRM_SEND_TOKEN_ROUTE}/${id}` }} />
|
||||||
|
case TOKEN_METHOD_APPROVE:
|
||||||
|
return <Redirect to={{ pathname: `${CONFIRM_APPROVE_ROUTE}/${id}` }} />
|
||||||
|
default:
|
||||||
|
return <Redirect to={{ pathname: `${CONFIRM_TOKEN_METHOD_ROUTE}/${id}` }} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Redirect to={{ pathname: `${CONFIRM_SEND_ETHER_ROUTE}/${id}` }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const txData = this.getTransaction()
|
||||||
|
|
||||||
|
if (!txData) {
|
||||||
|
return <Loading />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txData.txParams) {
|
||||||
|
return this.redirectToTransaction(txData)
|
||||||
|
} else if (txData.msgParams) {
|
||||||
|
return <Redirect to={{ pathname: SIGNATURE_REQUEST_ROUTE }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Loading />
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export const TOKEN_METHOD_TRANSFER = 'transfer'
|
||||||
|
export const TOKEN_METHOD_APPROVE = 'approve'
|
@ -0,0 +1,11 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import ConfirmTransactionSwitch from './confirm-transaction-switch.component'
|
||||||
|
import { unconfirmedTransactionsListSelector } from '../../../selectors/confirm-transaction'
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
unconfirmedTransactions: unconfirmedTransactionsListSelector(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ConfirmTransactionSwitch)
|
@ -0,0 +1,12 @@
|
|||||||
|
import abi from 'human-standard-token-abi'
|
||||||
|
import abiDecoder from 'abi-decoder'
|
||||||
|
abiDecoder.addABI(abi)
|
||||||
|
|
||||||
|
export function isConfirmDeployContract (txData = {}) {
|
||||||
|
const { txParams = {} } = txData
|
||||||
|
return !txParams.to
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenData (data = {}) {
|
||||||
|
return abiDecoder.decodeMethod(data)
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
import ConfirmTransactionSwitch from './confirm-transaction-switch.container'
|
||||||
|
module.exports = ConfirmTransactionSwitch
|
@ -0,0 +1,59 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Switch, Route } from 'react-router-dom'
|
||||||
|
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_ROUTE,
|
||||||
|
CONFIRM_SEND_ETHER_ROUTE,
|
||||||
|
CONFIRM_SEND_TOKEN_ROUTE,
|
||||||
|
CONFIRM_APPROVE_ROUTE,
|
||||||
|
CONFIRM_TOKEN_METHOD_ROUTE,
|
||||||
|
SIGNATURE_REQUEST_ROUTE,
|
||||||
|
} from '../../../routes'
|
||||||
|
|
||||||
|
export default class ConfirmTransaction extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
totalUnapprovedCount: PropTypes.number.isRequired,
|
||||||
|
match: PropTypes.object,
|
||||||
|
send: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { totalUnapprovedCount = 0, send = {}, history } = this.props
|
||||||
|
|
||||||
|
if (!totalUnapprovedCount && !send.to) {
|
||||||
|
history.replace(DEFAULT_ROUTE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${CONFIRM_DEPLOY_CONTRACT_ROUTE}/:id?`}
|
||||||
|
component={ConfirmDeployContract}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${CONFIRM_TOKEN_METHOD_ROUTE}/:id?`}
|
||||||
|
component={ConfirmTransactionBase}
|
||||||
|
/>
|
||||||
|
<Route exact path={`${CONFIRM_SEND_ETHER_ROUTE}/:id?`} component={ConfirmSendEther} />
|
||||||
|
<Route exact path={`${CONFIRM_SEND_TOKEN_ROUTE}/:id?`} component={ConfirmSendToken} />
|
||||||
|
<Route exact path={`${CONFIRM_APPROVE_ROUTE}/:id?`} component={ConfirmApprove} />
|
||||||
|
<Route exact path={SIGNATURE_REQUEST_ROUTE} component={ConfTx} />
|
||||||
|
<Route path={`${CONFIRM_TRANSACTION_ROUTE}/:id?`} component={ConfirmTransactionSwitch} />
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { compose } from 'recompose'
|
||||||
|
import { withRouter } from 'react-router-dom'
|
||||||
|
import ConfirmTransaction from './confirm-transaction.component'
|
||||||
|
import { getTotalUnapprovedCount } from '../../../selectors'
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const { metamask: { send } } = state
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalUnapprovedCount: getTotalUnapprovedCount(state),
|
||||||
|
send,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(mapStateToProps),
|
||||||
|
)(ConfirmTransaction)
|
2
ui/app/components/pages/confirm-transaction/index.js
Normal file
2
ui/app/components/pages/confirm-transaction/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import ConfirmTransaction from './confirm-transaction.container'
|
||||||
|
module.exports = ConfirmTransaction
|
@ -83,51 +83,6 @@ class Home extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!props.noActiveNotices) {
|
|
||||||
// log.debug('rendering notice screen for unread notices.')
|
|
||||||
// return h(NoticeScreen, {
|
|
||||||
// notice: props.nextUnreadNotice,
|
|
||||||
// key: 'NoticeScreen',
|
|
||||||
// onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
|
|
||||||
// })
|
|
||||||
// } else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
|
||||||
// log.debug('rendering notice screen for lost accounts view.')
|
|
||||||
// return h(NoticeScreen, {
|
|
||||||
// notice: generateLostAccountsNotice(props.lostAccounts),
|
|
||||||
// key: 'LostAccountsNotice',
|
|
||||||
// onConfirm: () => props.dispatch(actions.markAccountsFound()),
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (props.seedWords) {
|
|
||||||
// log.debug('rendering seed words')
|
|
||||||
// return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
|
|
||||||
// }
|
|
||||||
|
|
||||||
// show initialize screen
|
|
||||||
// if (!isInitialized || forgottenPassword) {
|
|
||||||
// // show current view
|
|
||||||
// log.debug('rendering an initialize screen')
|
|
||||||
// // switch (props.currentView.name) {
|
|
||||||
|
|
||||||
// // case 'restoreVault':
|
|
||||||
// // log.debug('rendering restore vault screen')
|
|
||||||
// // return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
|
||||||
|
|
||||||
// // default:
|
|
||||||
// // log.debug('rendering menu screen')
|
|
||||||
// // return h(InitializeScreen, {key: 'menuScreenInit'})
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // show unlock screen
|
|
||||||
// if (!props.isUnlocked) {
|
|
||||||
// return h(MainContainer, {
|
|
||||||
// currentViewName: props.currentView.name,
|
|
||||||
// isUnlocked: props.isUnlocked,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// show current view
|
// show current view
|
||||||
switch (currentView.name) {
|
switch (currentView.name) {
|
||||||
|
|
||||||
@ -135,59 +90,10 @@ class Home extends Component {
|
|||||||
log.debug('rendering main container')
|
log.debug('rendering main container')
|
||||||
return h(MainContainer, {key: 'account-detail'})
|
return h(MainContainer, {key: 'account-detail'})
|
||||||
|
|
||||||
// case 'sendTransaction':
|
|
||||||
// log.debug('rendering send tx screen')
|
|
||||||
|
|
||||||
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
|
|
||||||
// // const SendComponentToRender = checkFeatureToggle('send-v2')
|
|
||||||
// // ? SendTransactionScreen2
|
|
||||||
// // : SendTransactionScreen
|
|
||||||
|
|
||||||
// return h(SendTransactionScreen2, {key: 'send-transaction'})
|
|
||||||
|
|
||||||
// case 'sendToken':
|
|
||||||
// log.debug('rendering send token screen')
|
|
||||||
|
|
||||||
// // Going to leave this here until we are ready to delete SendTransactionScreen v1
|
|
||||||
// // const SendTokenComponentToRender = checkFeatureToggle('send-v2')
|
|
||||||
// // ? SendTransactionScreen2
|
|
||||||
// // : SendTokenScreen
|
|
||||||
|
|
||||||
// return h(SendTransactionScreen2, {key: 'sendToken'})
|
|
||||||
|
|
||||||
case 'newKeychain':
|
case 'newKeychain':
|
||||||
log.debug('rendering new keychain screen')
|
log.debug('rendering new keychain screen')
|
||||||
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
||||||
|
|
||||||
// case 'confTx':
|
|
||||||
// log.debug('rendering confirm tx screen')
|
|
||||||
// return h(Redirect, {
|
|
||||||
// to: {
|
|
||||||
// pathname: CONFIRM_TRANSACTION_ROUTE,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// return h(ConfirmTxScreen, {key: 'confirm-tx'})
|
|
||||||
|
|
||||||
// case 'add-token':
|
|
||||||
// log.debug('rendering add-token screen from unlock screen.')
|
|
||||||
// return h(AddTokenScreen, {key: 'add-token'})
|
|
||||||
|
|
||||||
// case 'config':
|
|
||||||
// log.debug('rendering config screen')
|
|
||||||
// return h(Settings, {key: 'config'})
|
|
||||||
|
|
||||||
// case 'import-menu':
|
|
||||||
// log.debug('rendering import screen')
|
|
||||||
// return h(Import, {key: 'import-menu'})
|
|
||||||
|
|
||||||
// case 'reveal-seed-conf':
|
|
||||||
// log.debug('rendering reveal seed confirmation screen')
|
|
||||||
// return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
|
|
||||||
|
|
||||||
// case 'info':
|
|
||||||
// log.debug('rendering info screen')
|
|
||||||
// return h(Settings, {key: 'info', tab: 'info'})
|
|
||||||
|
|
||||||
case 'buyEth':
|
case 'buyEth':
|
||||||
log.debug('rendering buy ether screen')
|
log.debug('rendering buy ether screen')
|
||||||
return h(BuyView, {key: 'buyEthView'})
|
return h(BuyView, {key: 'buyEthView'})
|
||||||
|
@ -3,3 +3,5 @@
|
|||||||
@import './add-token/index';
|
@import './add-token/index';
|
||||||
|
|
||||||
@import './confirm-add-token/index';
|
@import './confirm-add-token/index';
|
||||||
|
|
||||||
|
@import './confirm-send-token/index';
|
||||||
|
@ -48,6 +48,7 @@ export default class SendFooter extends Component {
|
|||||||
// updateTx,
|
// updateTx,
|
||||||
update,
|
update,
|
||||||
toAccounts,
|
toAccounts,
|
||||||
|
history,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
// Should not be needed because submit should be disabled if there are errors.
|
// Should not be needed because submit should be disabled if there are errors.
|
||||||
@ -60,7 +61,7 @@ export default class SendFooter extends Component {
|
|||||||
// TODO: add nickname functionality
|
// TODO: add nickname functionality
|
||||||
addToAddressBookIfNew(to, toAccounts)
|
addToAddressBookIfNew(to, toAccounts)
|
||||||
|
|
||||||
editingTransactionId
|
const promise = editingTransactionId
|
||||||
? update({
|
? update({
|
||||||
amount,
|
amount,
|
||||||
editingTransactionId,
|
editingTransactionId,
|
||||||
@ -73,7 +74,8 @@ export default class SendFooter extends Component {
|
|||||||
})
|
})
|
||||||
: sign({ selectedToken, to, amount, from, gas, gasPrice })
|
: sign({ selectedToken, to, amount, from, gas, gasPrice })
|
||||||
|
|
||||||
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
|
Promise.resolve(promise)
|
||||||
|
.then(() => history.push(CONFIRM_TRANSACTION_ROUTE))
|
||||||
}
|
}
|
||||||
|
|
||||||
formShouldBeDisabled () {
|
formShouldBeDisabled () {
|
||||||
|
@ -87,7 +87,7 @@ function mapDispatchToProps (dispatch) {
|
|||||||
unapprovedTxs,
|
unapprovedTxs,
|
||||||
})
|
})
|
||||||
|
|
||||||
dispatch(updateTransaction(editingTx))
|
return dispatch(updateTransaction(editingTx))
|
||||||
},
|
},
|
||||||
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
|
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
|
||||||
const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress)
|
const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress)
|
||||||
|
@ -37,7 +37,7 @@ module.exports = {
|
|||||||
removeLeadingZeroes,
|
removeLeadingZeroes,
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcGasTotal (gasLimit, gasPrice) {
|
function calcGasTotal (gasLimit = '0', gasPrice = '0') {
|
||||||
return multiplyCurrencies(gasLimit, gasPrice, {
|
return multiplyCurrencies(gasLimit, gasPrice, {
|
||||||
toNumericBase: 'hex',
|
toNumericBase: 'hex',
|
||||||
multiplicandBase: 16,
|
multiplicandBase: 16,
|
||||||
@ -47,9 +47,9 @@ function calcGasTotal (gasLimit, gasPrice) {
|
|||||||
|
|
||||||
function isBalanceSufficient ({
|
function isBalanceSufficient ({
|
||||||
amount = '0x0',
|
amount = '0x0',
|
||||||
amountConversionRate = 0,
|
amountConversionRate = 1,
|
||||||
balance,
|
balance = '0x0',
|
||||||
conversionRate,
|
conversionRate = 1,
|
||||||
gasTotal = '0x0',
|
gasTotal = '0x0',
|
||||||
primaryCurrency,
|
primaryCurrency,
|
||||||
}) {
|
}) {
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
const { Component } = require('react')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const connect = require('react-redux').connect
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const Identicon = require('./identicon')
|
|
||||||
|
|
||||||
class SenderToRecipient extends Component {
|
|
||||||
renderRecipientIcon () {
|
|
||||||
const { recipientAddress } = this.props
|
|
||||||
return (
|
|
||||||
recipientAddress
|
|
||||||
? h(Identicon, { address: recipientAddress, diameter: 20 })
|
|
||||||
: h('i.fa.fa-file-text-o')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRecipient () {
|
|
||||||
const { recipientName } = this.props
|
|
||||||
return (
|
|
||||||
h('.sender-to-recipient__recipient', [
|
|
||||||
this.renderRecipientIcon(),
|
|
||||||
h(
|
|
||||||
'.sender-to-recipient__name.sender-to-recipient__recipient-name',
|
|
||||||
recipientName || this.context.t('newContract')
|
|
||||||
),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { senderName, senderAddress } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
h('.sender-to-recipient__container', [
|
|
||||||
h('.sender-to-recipient__sender', [
|
|
||||||
h('.sender-to-recipient__sender-icon', [
|
|
||||||
h(Identicon, {
|
|
||||||
address: senderAddress,
|
|
||||||
diameter: 20,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
h('.sender-to-recipient__name.sender-to-recipient__sender-name', senderName),
|
|
||||||
]),
|
|
||||||
h('.sender-to-recipient__arrow-container', [
|
|
||||||
h('.sender-to-recipient__arrow-circle', [
|
|
||||||
h('img', {
|
|
||||||
height: 15,
|
|
||||||
width: 15,
|
|
||||||
src: './images/arrow-right.svg',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
this.renderRecipient(),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SenderToRecipient.propTypes = {
|
|
||||||
senderName: PropTypes.string,
|
|
||||||
senderAddress: PropTypes.string,
|
|
||||||
recipientName: PropTypes.string,
|
|
||||||
recipientAddress: PropTypes.string,
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
SenderToRecipient.contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect()(SenderToRecipient)
|
|
||||||
|
|
1
ui/app/components/sender-to-recipient/index.js
Normal file
1
ui/app/components/sender-to-recipient/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './sender-to-recipient.component'
|
@ -6,6 +6,16 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-bottom: 1px solid $geyser;
|
border-bottom: 1px solid $geyser;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip-wrapper {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tooltip-container {
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__sender,
|
&__sender,
|
||||||
@ -14,7 +24,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px 20px;
|
padding: 0 16px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -22,11 +32,16 @@
|
|||||||
|
|
||||||
&__sender {
|
&__sender {
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__recipient {
|
&__recipient {
|
||||||
border-left: 1px solid $geyser;
|
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
|
border-left: 1px solid $geyser;
|
||||||
|
|
||||||
|
&--with-address {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__arrow-container {
|
&__arrow-container {
|
||||||
@ -42,17 +57,18 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid $geyser;
|
border: 1px solid $geyser;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
height: 30px;
|
height: 32px;
|
||||||
width: 30px;
|
width: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
padding-left: 5px;
|
padding-left: 14px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Identicon from '../identicon'
|
||||||
|
import Tooltip from '../tooltip-v2'
|
||||||
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
|
|
||||||
|
export default class SenderToRecipient extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
senderName: PropTypes.string,
|
||||||
|
senderAddress: PropTypes.string,
|
||||||
|
recipientName: PropTypes.string,
|
||||||
|
recipientAddress: PropTypes.string,
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
senderAddressCopied: false,
|
||||||
|
recipientAddressCopied: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRecipientWithAddress () {
|
||||||
|
const { t } = this.context
|
||||||
|
const { recipientName, recipientAddress } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="sender-to-recipient__recipient sender-to-recipient__recipient--with-address"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ recipientAddressCopied: true })
|
||||||
|
copyToClipboard(recipientAddress)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="sender-to-recipient__sender-icon">
|
||||||
|
<Identicon
|
||||||
|
address={recipientAddress}
|
||||||
|
diameter={24}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
position="bottom"
|
||||||
|
title={this.state.recipientAddressCopied ? t('copiedExclamation') : t('copyAddress')}
|
||||||
|
wrapperClassName="sender-to-recipient__tooltip-wrapper"
|
||||||
|
containerClassName="sender-to-recipient__tooltip-container"
|
||||||
|
onHidden={() => this.setState({ recipientAddressCopied: false })}
|
||||||
|
>
|
||||||
|
<div className="sender-to-recipient__name sender-to-recipient__recipient-name">
|
||||||
|
{ recipientName || this.context.t('newContract') }
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRecipientWithoutAddress () {
|
||||||
|
return (
|
||||||
|
<div className="sender-to-recipient__recipient">
|
||||||
|
<i className="fa fa-file-text-o" />
|
||||||
|
<div className="sender-to-recipient__name sender-to-recipient__recipient-name">
|
||||||
|
{ this.context.t('newContract') }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { t } = this.context
|
||||||
|
const { senderName, senderAddress, recipientAddress } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sender-to-recipient__container">
|
||||||
|
<div
|
||||||
|
className="sender-to-recipient__sender"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ senderAddressCopied: true })
|
||||||
|
copyToClipboard(senderAddress)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="sender-to-recipient__sender-icon">
|
||||||
|
<Identicon
|
||||||
|
address={senderAddress}
|
||||||
|
diameter={24}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
position="bottom"
|
||||||
|
title={this.state.senderAddressCopied ? t('copiedExclamation') : t('copyAddress')}
|
||||||
|
wrapperClassName="sender-to-recipient__tooltip-wrapper"
|
||||||
|
containerClassName="sender-to-recipient__tooltip-container"
|
||||||
|
onHidden={() => this.setState({ senderAddressCopied: false })}
|
||||||
|
>
|
||||||
|
<div className="sender-to-recipient__name sender-to-recipient__sender-name">
|
||||||
|
{ senderName }
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="sender-to-recipient__arrow-container">
|
||||||
|
<div className="sender-to-recipient__arrow-circle">
|
||||||
|
<img
|
||||||
|
height={15}
|
||||||
|
width={15}
|
||||||
|
src="./images/arrow-right.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
recipientAddress
|
||||||
|
? this.renderRecipientWithAddress()
|
||||||
|
: this.renderRecipientWithoutAddress()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
3
ui/app/components/tabs/index.js
Normal file
3
ui/app/components/tabs/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Tabs from './tabs.component'
|
||||||
|
import Tab from './tab'
|
||||||
|
export { Tabs, Tab }
|
11
ui/app/components/tabs/index.scss
Normal file
11
ui/app/components/tabs/index.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@import './tab/index';
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
&__list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background-color: #f9fafa;
|
||||||
|
border-bottom: 1px solid $geyser;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
2
ui/app/components/tabs/tab/index.js
Normal file
2
ui/app/components/tabs/tab/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Tab from './tab.component'
|
||||||
|
module.exports = Tab
|
15
ui/app/components/tabs/tab/index.scss
Normal file
15
ui/app/components/tabs/tab/index.scss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.tab {
|
||||||
|
color: #8C8E94;
|
||||||
|
font-size: .75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin: 0 8px;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: $black;
|
||||||
|
border-bottom: 2px solid $curious-blue;
|
||||||
|
}
|
||||||
|
}
|
31
ui/app/components/tabs/tab/tab.component.js
Normal file
31
ui/app/components/tabs/tab/tab.component.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
const Tab = props => {
|
||||||
|
const { name, onClick, isActive, tabIndex } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames(
|
||||||
|
'tab',
|
||||||
|
isActive && 'tab--active',
|
||||||
|
)}
|
||||||
|
onClick={event => {
|
||||||
|
event.preventDefault()
|
||||||
|
onClick(tabIndex)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ name }
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
tabIndex: PropTypes.number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tab
|
63
ui/app/components/tabs/tabs.component.js
Normal file
63
ui/app/components/tabs/tabs.component.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
export default class Tabs extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
defaultActiveTabIndex: PropTypes.number,
|
||||||
|
children: PropTypes.node,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeTabIndex: props.defaultActiveTabIndex || 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabClick (tabIndex) {
|
||||||
|
const { activeTabIndex } = this.state
|
||||||
|
|
||||||
|
if (tabIndex !== activeTabIndex) {
|
||||||
|
this.setState({
|
||||||
|
activeTabIndex: tabIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTabs () {
|
||||||
|
// const { children } = this.props
|
||||||
|
const numberOfTabs = React.Children.count(this.props.children)
|
||||||
|
|
||||||
|
return React.Children.map(this.props.children, (child, index) => {
|
||||||
|
return child && React.cloneElement(child, {
|
||||||
|
onClick: index => this.handleTabClick(index),
|
||||||
|
tabIndex: index,
|
||||||
|
isActive: numberOfTabs > 1 && index === this.state.activeTabIndex,
|
||||||
|
key: index,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActiveTabContent () {
|
||||||
|
const { children } = this.props
|
||||||
|
const { activeTabIndex } = this.state
|
||||||
|
|
||||||
|
return children[activeTabIndex]
|
||||||
|
? children[activeTabIndex].props.children
|
||||||
|
: children.props.children
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className="tabs">
|
||||||
|
<ul className="tabs__list">
|
||||||
|
{ this.renderTabs() }
|
||||||
|
</ul>
|
||||||
|
<div className="tabs__content">
|
||||||
|
{ this.renderActiveTabContent() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ function Tooltip () {
|
|||||||
|
|
||||||
Tooltip.prototype.render = function () {
|
Tooltip.prototype.render = function () {
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const { position, title, children, wrapperClassName } = props
|
const { position, title, children, wrapperClassName, containerClassName, onHidden } = props
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
className: wrapperClassName,
|
className: wrapperClassName,
|
||||||
@ -25,6 +25,8 @@ Tooltip.prototype.render = function () {
|
|||||||
hideOnClick: false,
|
hideOnClick: false,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
arrow: true,
|
arrow: true,
|
||||||
|
className: containerClassName,
|
||||||
|
onHidden,
|
||||||
}, children),
|
}, children),
|
||||||
|
|
||||||
])
|
])
|
||||||
|
@ -28,6 +28,8 @@ const BN = ethUtil.BN
|
|||||||
const R = require('ramda')
|
const R = require('ramda')
|
||||||
const { stripHexPrefix } = require('ethereumjs-util')
|
const { stripHexPrefix } = require('ethereumjs-util')
|
||||||
|
|
||||||
|
global.BigNumber = BigNumber
|
||||||
|
|
||||||
BigNumber.config({
|
BigNumber.config({
|
||||||
ROUNDING_MODE: BigNumber.ROUND_HALF_DOWN,
|
ROUNDING_MODE: BigNumber.ROUND_HALF_DOWN,
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
.btn-default,
|
.btn-default,
|
||||||
.btn-primary,
|
.btn-primary,
|
||||||
.btn-secondary {
|
.btn-secondary,
|
||||||
|
.btn-confirm {
|
||||||
height: 44px;
|
height: 44px;
|
||||||
background: $white;
|
background: $white;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -13,13 +14,14 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
transition: border-color .3s ease;
|
transition: border-color .3s ease;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
font-family: Roboto;
|
||||||
|
|
||||||
&--disabled,
|
&--disabled,
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
@ -71,6 +73,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
color: $white;
|
||||||
|
border: 2px solid $curious-blue;
|
||||||
|
background-color: $curious-blue;
|
||||||
|
}
|
||||||
|
|
||||||
.btn--large {
|
.btn--large {
|
||||||
height: 54px;
|
height: 54px;
|
||||||
}
|
}
|
||||||
@ -119,19 +127,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-confirm {
|
|
||||||
background-color: $caribbean-green; // TODO: reusable color in colors.css
|
|
||||||
text-align: center;
|
|
||||||
padding: .8rem 1rem;
|
|
||||||
color: $white;
|
|
||||||
border: 2px solid $caribbean-green;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: .85rem;
|
|
||||||
font-weight: 400;
|
|
||||||
transition: border-color .3s ease;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No longer used in flat design, remove when modal buttons done
|
// No longer used in flat design, remove when modal buttons done
|
||||||
// div.wallet-btn {
|
// div.wallet-btn {
|
||||||
// border: 1px solid rgb(91, 93, 103);
|
// border: 1px solid rgb(91, 93, 103);
|
||||||
|
@ -58,6 +58,4 @@
|
|||||||
|
|
||||||
@import './welcome-screen.scss';
|
@import './welcome-screen.scss';
|
||||||
|
|
||||||
@import './sender-to-recipient.scss';
|
|
||||||
|
|
||||||
@import '../../../components/index';
|
@import '../../../components/index';
|
||||||
|
@ -159,15 +159,3 @@
|
|||||||
.network-caret {
|
.network-caret {
|
||||||
margin: 0 8px 2px;
|
margin: 0 8px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-display {
|
|
||||||
&__container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
@media screen and (min-width: 576px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -73,195 +73,6 @@ input.large-input {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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-clear,
|
|
||||||
.btn-cancel,
|
|
||||||
.btn-confirm {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer-button {
|
|
||||||
height: 55px;
|
|
||||||
font-size: 1rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-right: 16px;
|
|
||||||
border-radius: 2px;
|
|
||||||
|
|
||||||
&: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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-label {
|
.input-label {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -55,6 +55,7 @@ $dodger-blue: #3099f2;
|
|||||||
$zumthor: #edf7ff;
|
$zumthor: #edf7ff;
|
||||||
$ecstasy: #f7861c;
|
$ecstasy: #f7861c;
|
||||||
$linen: #fdf4f4;
|
$linen: #fdf4f4;
|
||||||
|
$oslo-gray: #8C8E94;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Z-Indicies
|
Z-Indicies
|
||||||
|
@ -165,7 +165,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: 700;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-transform-uppercase {
|
.text-transform-uppercase {
|
||||||
|
319
ui/app/ducks/confirm-transaction.duck.js
Normal file
319
ui/app/ducks/confirm-transaction.duck.js
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
import {
|
||||||
|
conversionRateSelector,
|
||||||
|
currentCurrencySelector,
|
||||||
|
unconfirmedTransactionsHashSelector,
|
||||||
|
} from '../selectors/confirm-transaction'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getTokenData,
|
||||||
|
getTransactionAmount,
|
||||||
|
getTransactionFee,
|
||||||
|
getHexGasTotal,
|
||||||
|
addFiat,
|
||||||
|
addEth,
|
||||||
|
increaseLastGasPrice,
|
||||||
|
hexGreaterThan,
|
||||||
|
} from '../helpers/confirm-transaction/util'
|
||||||
|
|
||||||
|
import { getSymbolAndDecimals } from '../token-util'
|
||||||
|
import { conversionUtil } from '../conversion-util'
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
const createActionType = action => `metamask/confirm-transaction/${action}`
|
||||||
|
|
||||||
|
const UPDATE_TX_DATA = createActionType('UPDATE_TX_DATA')
|
||||||
|
const CLEAR_TX_DATA = createActionType('CLEAR_TX_DATA')
|
||||||
|
const UPDATE_TOKEN_DATA = createActionType('UPDATE_TOKEN_DATA')
|
||||||
|
const CLEAR_TOKEN_DATA = createActionType('CLEAR_TOKEN_DATA')
|
||||||
|
const CLEAR_CONFIRM_TRANSACTION = createActionType('CLEAR_CONFIRM_TRANSACTION')
|
||||||
|
const UPDATE_TRANSACTION_AMOUNTS = createActionType('UPDATE_TRANSACTION_AMOUNTS')
|
||||||
|
const UPDATE_TRANSACTION_FEES = createActionType('UPDATE_TRANSACTION_FEES')
|
||||||
|
const UPDATE_TRANSACTION_TOTALS = createActionType('UPDATE_TRANSACTION_TOTALS')
|
||||||
|
const UPDATE_HEX_GAS_TOTAL = createActionType('UPDATE_HEX_GAS_TOTAL')
|
||||||
|
const UPDATE_TOKEN_PROPS = createActionType('UPDATE_TOKEN_PROPS')
|
||||||
|
const UPDATE_NONCE = createActionType('UPDATE_NONCE')
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
const initState = {
|
||||||
|
txData: {},
|
||||||
|
tokenData: {},
|
||||||
|
tokenProps: {
|
||||||
|
tokenDecimals: '',
|
||||||
|
tokenSymbol: '',
|
||||||
|
},
|
||||||
|
fiatTransactionAmount: '',
|
||||||
|
fiatTransactionFee: '',
|
||||||
|
fiatTransactionTotal: '',
|
||||||
|
ethTransactionAmount: '',
|
||||||
|
ethTransactionFee: '',
|
||||||
|
ethTransactionTotal: '',
|
||||||
|
hexGasTotal: '',
|
||||||
|
nonce: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reducer
|
||||||
|
export default function reducer ({ confirmTransaction: confirmState = initState }, action = {}) {
|
||||||
|
switch (action.type) {
|
||||||
|
case UPDATE_TX_DATA:
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
txData: {
|
||||||
|
...action.payload,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case CLEAR_TX_DATA:
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
txData: {},
|
||||||
|
}
|
||||||
|
case UPDATE_TOKEN_DATA:
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
tokenData: {
|
||||||
|
...action.payload,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case CLEAR_TOKEN_DATA:
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
tokenData: {},
|
||||||
|
}
|
||||||
|
case UPDATE_TRANSACTION_AMOUNTS:
|
||||||
|
const { fiatTransactionAmount, ethTransactionAmount } = action.payload
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
fiatTransactionAmount: fiatTransactionAmount || confirmState.fiatTransactionAmount,
|
||||||
|
ethTransactionAmount: ethTransactionAmount || confirmState.ethTransactionAmount,
|
||||||
|
}
|
||||||
|
case UPDATE_TRANSACTION_FEES:
|
||||||
|
const { fiatTransactionFee, ethTransactionFee } = action.payload
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
fiatTransactionFee: fiatTransactionFee || confirmState.fiatTransactionFee,
|
||||||
|
ethTransactionFee: ethTransactionFee || confirmState.ethTransactionFee,
|
||||||
|
}
|
||||||
|
case UPDATE_TRANSACTION_TOTALS:
|
||||||
|
const { fiatTransactionTotal, ethTransactionTotal } = action.payload
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
fiatTransactionTotal: fiatTransactionTotal || confirmState.fiatTransactionTotal,
|
||||||
|
ethTransactionTotal: ethTransactionTotal || confirmState.ethTransactionTotal,
|
||||||
|
}
|
||||||
|
case UPDATE_HEX_GAS_TOTAL:
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
hexGasTotal: action.payload,
|
||||||
|
}
|
||||||
|
case UPDATE_TOKEN_PROPS:
|
||||||
|
const { tokenSymbol = '', tokenDecimals = '' } = action.payload
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
tokenProps: {
|
||||||
|
...confirmState.tokenProps,
|
||||||
|
tokenSymbol,
|
||||||
|
tokenDecimals,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case UPDATE_NONCE:
|
||||||
|
return {
|
||||||
|
...confirmState,
|
||||||
|
nonce: action.payload,
|
||||||
|
}
|
||||||
|
case CLEAR_CONFIRM_TRANSACTION:
|
||||||
|
return initState
|
||||||
|
default:
|
||||||
|
return confirmState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action Creators
|
||||||
|
export function updateTxData (txData) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TX_DATA,
|
||||||
|
payload: txData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearTxData () {
|
||||||
|
return {
|
||||||
|
type: CLEAR_TX_DATA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTokenData (tokenData) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TOKEN_DATA,
|
||||||
|
payload: tokenData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearTokenData () {
|
||||||
|
return {
|
||||||
|
type: CLEAR_TOKEN_DATA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTransactionAmounts (amounts) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TRANSACTION_AMOUNTS,
|
||||||
|
payload: amounts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTransactionFees (fees) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TRANSACTION_FEES,
|
||||||
|
payload: fees,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTransactionTotals (totals) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TRANSACTION_TOTALS,
|
||||||
|
payload: totals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateHexGasTotal (hexGasTotal) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_HEX_GAS_TOTAL,
|
||||||
|
payload: hexGasTotal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTokenProps (tokenProps) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TOKEN_PROPS,
|
||||||
|
payload: tokenProps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateNonce (nonce) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_NONCE,
|
||||||
|
payload: nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateGasAndCalculate ({ gasLimit, gasPrice }) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { confirmTransaction: { txData } } = getState()
|
||||||
|
const newTxData = {
|
||||||
|
...txData,
|
||||||
|
txParams: {
|
||||||
|
...txData.txParams,
|
||||||
|
gas: gasLimit,
|
||||||
|
gasPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(updateTxDataAndCalculate(newTxData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseFromLastGasPrice (txData) {
|
||||||
|
const { lastGasPrice, txParams: { gasPrice: previousGasPrice } = {} } = txData
|
||||||
|
|
||||||
|
// Set the minimum to a 10% increase from the lastGasPrice.
|
||||||
|
const minimumGasPrice = increaseLastGasPrice(lastGasPrice)
|
||||||
|
const gasPriceBelowMinimum = hexGreaterThan(minimumGasPrice, previousGasPrice)
|
||||||
|
const gasPrice = (!previousGasPrice || gasPriceBelowMinimum) ? minimumGasPrice : previousGasPrice
|
||||||
|
|
||||||
|
return {
|
||||||
|
...txData,
|
||||||
|
txParams: {
|
||||||
|
...txData.txParams,
|
||||||
|
gasPrice,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTxDataAndCalculate (txData) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState()
|
||||||
|
const currentCurrency = currentCurrencySelector(state)
|
||||||
|
const conversionRate = conversionRateSelector(state)
|
||||||
|
|
||||||
|
dispatch(updateTxData(txData))
|
||||||
|
|
||||||
|
const { txParams: { value, gas: gasLimit, gasPrice } = {} } = txData
|
||||||
|
|
||||||
|
const fiatTransactionAmount = getTransactionAmount({
|
||||||
|
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
||||||
|
})
|
||||||
|
const ethTransactionAmount = getTransactionAmount({
|
||||||
|
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(updateTransactionAmounts({ fiatTransactionAmount, ethTransactionAmount }))
|
||||||
|
|
||||||
|
const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
|
||||||
|
|
||||||
|
dispatch(updateHexGasTotal(hexGasTotal))
|
||||||
|
|
||||||
|
const fiatTransactionFee = getTransactionFee({
|
||||||
|
value: hexGasTotal,
|
||||||
|
toCurrency: currentCurrency,
|
||||||
|
numberOfDecimals: 2,
|
||||||
|
conversionRate,
|
||||||
|
})
|
||||||
|
const ethTransactionFee = getTransactionFee({
|
||||||
|
value: hexGasTotal,
|
||||||
|
toCurrency: 'ETH',
|
||||||
|
numberOfDecimals: 6,
|
||||||
|
conversionRate,
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(updateTransactionFees({ fiatTransactionFee, ethTransactionFee }))
|
||||||
|
|
||||||
|
const fiatTransactionTotal = addFiat(fiatTransactionFee, fiatTransactionAmount)
|
||||||
|
const ethTransactionTotal = addEth(ethTransactionFee, ethTransactionAmount)
|
||||||
|
|
||||||
|
dispatch(updateTransactionTotals({ fiatTransactionTotal, ethTransactionTotal }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTransactionToConfirm (transactionId) {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const state = getState()
|
||||||
|
const unconfirmedTransactionsHash = unconfirmedTransactionsHashSelector(state)
|
||||||
|
const transaction = unconfirmedTransactionsHash[transactionId]
|
||||||
|
|
||||||
|
if (!transaction) {
|
||||||
|
console.error(`Transaction with id ${transactionId} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lastGasPrice } = transaction
|
||||||
|
const txData = lastGasPrice ? increaseFromLastGasPrice(transaction) : transaction
|
||||||
|
dispatch(updateTxDataAndCalculate(txData))
|
||||||
|
|
||||||
|
const { txParams } = transaction
|
||||||
|
|
||||||
|
if (txParams.data) {
|
||||||
|
const { tokens: existingTokens } = state
|
||||||
|
const { data, to: tokenAddress } = txParams
|
||||||
|
const tokenData = getTokenData(data)
|
||||||
|
dispatch(updateTokenData(tokenData))
|
||||||
|
|
||||||
|
const tokenSymbolData = await getSymbolAndDecimals(tokenAddress, existingTokens) || {}
|
||||||
|
const { symbol: tokenSymbol = '', decimals: tokenDecimals = '' } = tokenSymbolData
|
||||||
|
dispatch(updateTokenProps({ tokenSymbol, tokenDecimals }))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txParams.nonce) {
|
||||||
|
const nonce = conversionUtil(txParams.nonce, {
|
||||||
|
fromNumericBase: 'hex',
|
||||||
|
toNumericBase: 'dec',
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(updateNonce(nonce))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearConfirmTransaction () {
|
||||||
|
return {
|
||||||
|
type: CLEAR_CONFIRM_TRANSACTION,
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user