mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Refactor and redesign confirm transaction views
This commit is contained in:
parent
b4aaf30d6f
commit
ea9d51e427
@ -40,6 +40,9 @@
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Approve"
|
||||
},
|
||||
"approved": {
|
||||
"message": "Approved"
|
||||
},
|
||||
@ -89,6 +92,9 @@
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin."
|
||||
},
|
||||
"bytes": {
|
||||
"message": "Bytes"
|
||||
},
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
},
|
||||
@ -149,6 +155,9 @@
|
||||
"copyContractAddress": {
|
||||
"message": "Copy Contract Address"
|
||||
},
|
||||
"copyAddress": {
|
||||
"message": "Copy address to clipboard"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"message": "Copy to clipboard"
|
||||
},
|
||||
@ -318,6 +327,9 @@
|
||||
"fromShapeShift": {
|
||||
"message": "From ShapeShift"
|
||||
},
|
||||
"functionType": {
|
||||
"message": "Function Type"
|
||||
},
|
||||
"gas": {
|
||||
"message": "Gas",
|
||||
"description": "Short indication of gas cost"
|
||||
@ -370,6 +382,9 @@
|
||||
"hereList": {
|
||||
"message": "Here's a list!!!!"
|
||||
},
|
||||
"hexData": {
|
||||
"message": "Hex Data"
|
||||
},
|
||||
"hide": {
|
||||
"message": "Hide"
|
||||
},
|
||||
@ -582,6 +597,9 @@
|
||||
"message": "or",
|
||||
"description": "choice between creating or importing a new account"
|
||||
},
|
||||
"origin": {
|
||||
"message": "Origin"
|
||||
},
|
||||
"password": {
|
||||
"message": "Password"
|
||||
},
|
||||
@ -911,6 +929,9 @@
|
||||
"transactionNumber": {
|
||||
"message": "Transaction Number"
|
||||
},
|
||||
"transfer": {
|
||||
"message": "Transfer"
|
||||
},
|
||||
"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 LOCALHOST = 'localhost'
|
||||
|
||||
const MAINNET_CODE = 1
|
||||
const ROPSTEN_CODE = 3
|
||||
const RINKEYBY_CODE = 4
|
||||
const KOVAN_CODE = 42
|
||||
@ -13,13 +14,13 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby'
|
||||
const KOVAN_DISPLAY_NAME = 'Kovan'
|
||||
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
||||
|
||||
|
||||
module.exports = {
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
LOCALHOST,
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEYBY_CODE,
|
||||
KOVAN_CODE,
|
||||
|
@ -399,7 +399,6 @@ class TransactionStateManager extends EventEmitter {
|
||||
_setTxStatus (txId, status) {
|
||||
const txMeta = this.getTx(txId)
|
||||
txMeta.status = status
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||
this.emit(`${txMeta.id}:${status}`, txId)
|
||||
@ -411,7 +410,6 @@ class TransactionStateManager extends EventEmitter {
|
||||
} 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-thunk": "^2.2.0",
|
||||
"request-promise": "^4.2.1",
|
||||
"reselect": "^3.0.1",
|
||||
"sandwich-expando": "^1.1.3",
|
||||
"semaphore": "^1.0.5",
|
||||
"semver": "^5.4.1",
|
||||
|
@ -704,11 +704,10 @@ function signTypedMsg (msgData) {
|
||||
|
||||
function signTx (txData) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
global.ethQuery.sendTransaction(txData, (err, data) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
dispatch(actions.hideWarning())
|
||||
if (err) {
|
||||
return dispatch(actions.displayWarning(err.message))
|
||||
}
|
||||
})
|
||||
dispatch(actions.showConfTxPage({}))
|
||||
}
|
||||
@ -910,29 +909,41 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
|
||||
|
||||
function updateTransaction (txData) {
|
||||
log.info('actions: updateTx: ' + JSON.stringify(txData))
|
||||
return (dispatch) => {
|
||||
return dispatch => {
|
||||
log.debug(`actions calling background.updateTx`)
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
background.updateTransaction(txData, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
||||
if (err) {
|
||||
dispatch(actions.txError(err))
|
||||
dispatch(actions.goHome())
|
||||
return log.error(err.message)
|
||||
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) {
|
||||
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
|
||||
return (dispatch) => {
|
||||
return dispatch => {
|
||||
log.debug(`actions calling background.updateAndApproveTx`)
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
background.updateAndApproveTransaction(txData, err => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
|
||||
dispatch(actions.clearSend())
|
||||
|
||||
@ -943,10 +954,17 @@ function updateAndApproveTx (txData) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
dispatch(actions.completedTx(txData.id))
|
||||
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,12 +1056,24 @@ function cancelTypedMsg (msgData) {
|
||||
function cancelTx (txData) {
|
||||
return dispatch => {
|
||||
log.debug(`background.cancelTransaction`)
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
background.cancelTransaction(txData.id, () => {
|
||||
background.cancelTransaction(txData.id, err => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
.then(() => updateMetamaskStateFromBackground())
|
||||
.then(newState => dispatch(actions.updateMetamaskState(newState)))
|
||||
.then(() => {
|
||||
dispatch(actions.clearSend())
|
||||
dispatch(actions.completedTx(txData.id))
|
||||
resolve(txData)
|
||||
})
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
return txData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ const log = require('loglevel')
|
||||
const InitializeScreen = require('../../mascara/src/app/first-time').default
|
||||
// accounts
|
||||
const SendTransactionScreen = require('./components/send_/send.container')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
const ConfirmTransaction = require('./components/pages/confirm-transaction')
|
||||
|
||||
// slideout menu
|
||||
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: SETTINGS_ROUTE, component: Settings }),
|
||||
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: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
||||
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_PRIMARY = 'btn-primary'
|
||||
const CLASSNAME_SECONDARY = 'btn-secondary'
|
||||
const CLASSNAME_CONFIRM = 'btn-confirm'
|
||||
const CLASSNAME_LARGE = 'btn--large'
|
||||
|
||||
const typeHash = {
|
||||
default: CLASSNAME_DEFAULT,
|
||||
primary: CLASSNAME_PRIMARY,
|
||||
secondary: CLASSNAME_SECONDARY,
|
||||
confirm: CLASSNAME_CONFIRM,
|
||||
}
|
||||
|
||||
export default class Button extends Component {
|
||||
static propTypes = {
|
||||
type: PropTypes.string,
|
||||
large: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.string,
|
||||
}
|
||||
|
||||
class Button extends Component {
|
||||
render () {
|
||||
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,
|
||||
isSelected,
|
||||
innerBorder = 'none',
|
||||
diameter = '12',
|
||||
} = this.props
|
||||
|
||||
return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
|
||||
@ -22,6 +23,8 @@ NetworkDropdownIcon.prototype.render = function () {
|
||||
style: {
|
||||
background: backgroundColor,
|
||||
border: innerBorder,
|
||||
height: `${diameter}px`,
|
||||
width: `${diameter}px`,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
@ -4,6 +4,16 @@
|
||||
|
||||
@import './info-box/index';
|
||||
|
||||
@import './network-display/index';
|
||||
|
||||
@import './confirm-page-container/index';
|
||||
|
||||
@import './page-container/index';
|
||||
|
||||
@import './pages/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 inherits = require('util').inherits
|
||||
const {
|
||||
addCurrencies,
|
||||
conversionGTE,
|
||||
conversionLTE,
|
||||
subtractCurrencies,
|
||||
} = require('../conversion-util')
|
||||
|
||||
module.exports = InputNumber
|
||||
@ -51,7 +49,11 @@ InputNumber.prototype.setValue = function (newValue) {
|
||||
}
|
||||
|
||||
InputNumber.prototype.render = function () {
|
||||
<<<<<<< HEAD
|
||||
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', {}, [
|
||||
h('input', {
|
||||
@ -67,11 +69,19 @@ InputNumber.prototype.render = function () {
|
||||
h('span.gas-tooltip-input-detail', {}, [unitLabel]),
|
||||
h('div.gas-tooltip-input-arrows', {}, [
|
||||
h('i.fa.fa-angle-up', {
|
||||
<<<<<<< HEAD
|
||||
onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })),
|
||||
}),
|
||||
h('i.fa.fa-angle-down', {
|
||||
style: { cursor: 'pointer' },
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -24,6 +24,8 @@ const TransactionConfirmed = require('./transaction-confirmed')
|
||||
const WelcomeBeta = require('./welcome-beta')
|
||||
const Notification = require('./notification')
|
||||
|
||||
import ConfirmCustomizeGasModal from './customize-gas'
|
||||
|
||||
const modalContainerBaseStyle = {
|
||||
transform: 'translate3d(-50%, 0, 0px)',
|
||||
border: '1px solid #CCCFD1',
|
||||
@ -267,7 +269,31 @@ const MODALS = {
|
||||
|
||||
CUSTOMIZE_GAS: {
|
||||
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: {
|
||||
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 { 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,
|
||||
submitText: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
submitButtonType: PropTypes.string,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
@ -23,6 +24,7 @@ export default class PageContainerFooter extends Component {
|
||||
onSubmit,
|
||||
submitText,
|
||||
disabled,
|
||||
submitButtonType,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
@ -30,16 +32,16 @@ export default class PageContainerFooter extends Component {
|
||||
|
||||
<Button
|
||||
type="default"
|
||||
large={true}
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => onCancel()}
|
||||
onClick={e => onCancel(e)}
|
||||
>
|
||||
{ cancelText || this.context.t('cancel') }
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
large={true}
|
||||
type={submitButtonType || 'primary'}
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
disabled={disabled}
|
||||
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 {
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
onClose: PropTypes.func,
|
||||
showBackButton: PropTypes.bool,
|
||||
onBackButtonClick: PropTypes.func,
|
||||
backButtonStyles: PropTypes.object,
|
||||
backButtonString: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
renderHeaderRow () {
|
||||
@ -30,25 +31,33 @@ export default class PageContainerHeader extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { title, subtitle, onClose } = this.props
|
||||
const { title, subtitle, onClose, children } = this.props
|
||||
|
||||
return (
|
||||
<div className="page-container__header">
|
||||
|
||||
{ this.renderHeaderRow() }
|
||||
|
||||
<div className="page-container__title">
|
||||
{ children }
|
||||
|
||||
{
|
||||
title && <div className="page-container__title">
|
||||
{ title }
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="page-container__subtitle">
|
||||
{
|
||||
subtitle && <div className="page-container__subtitle">
|
||||
{ subtitle }
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
{
|
||||
onClose && <div
|
||||
className="page-container__header-close"
|
||||
onClick={() => onClose()}
|
||||
/>
|
||||
}
|
||||
|
||||
</div>
|
||||
)
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
|
||||
export default class ConfirmApprove extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
tokenAddress: PropTypes.string,
|
||||
toAddress: PropTypes.string,
|
||||
tokenAmount: PropTypes.string,
|
||||
tokenSymbol: PropTypes.string,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { toAddress, tokenAddress, tokenAmount, tokenSymbol } = this.props
|
||||
|
||||
return (
|
||||
<ConfirmTransactionBase
|
||||
toAddress={toAddress}
|
||||
identiconAddress={tokenAddress}
|
||||
title={`${tokenAmount} ${tokenSymbol}`}
|
||||
warning={`By approving this action, you grant permission for this contract to spend up to ${tokenAmount} of your ${tokenSymbol}.`}
|
||||
hideSubtitle
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { connect } from 'react-redux'
|
||||
import ConfirmApprove from './confirm-approve.component'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { confirmTransaction } = state
|
||||
const {
|
||||
tokenData = {},
|
||||
txData: { txParams: { to: tokenAddress } = {} } = {},
|
||||
tokenProps: { tokenSymbol } = {},
|
||||
} = confirmTransaction
|
||||
const { params = [] } = tokenData
|
||||
|
||||
let toAddress = ''
|
||||
let tokenAmount = ''
|
||||
|
||||
if (params && params.length === 2) {
|
||||
[{ value: toAddress }, { value: tokenAmount }] = params
|
||||
}
|
||||
|
||||
return {
|
||||
toAddress,
|
||||
tokenAddress,
|
||||
tokenAmount,
|
||||
tokenSymbol,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ConfirmApprove)
|
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
|
||||
switch (currentView.name) {
|
||||
|
||||
@ -135,59 +90,10 @@ class Home extends Component {
|
||||
log.debug('rendering main container')
|
||||
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':
|
||||
log.debug('rendering new keychain screen')
|
||||
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':
|
||||
log.debug('rendering buy ether screen')
|
||||
return h(BuyView, {key: 'buyEthView'})
|
||||
|
@ -3,3 +3,5 @@
|
||||
@import './add-token/index';
|
||||
|
||||
@import './confirm-add-token/index';
|
||||
|
||||
@import './confirm-send-token/index';
|
||||
|
@ -48,6 +48,7 @@ export default class SendFooter extends Component {
|
||||
// updateTx,
|
||||
update,
|
||||
toAccounts,
|
||||
history,
|
||||
} = this.props
|
||||
|
||||
// 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
|
||||
addToAddressBookIfNew(to, toAccounts)
|
||||
|
||||
editingTransactionId
|
||||
const promise = editingTransactionId
|
||||
? update({
|
||||
amount,
|
||||
editingTransactionId,
|
||||
@ -73,7 +74,8 @@ export default class SendFooter extends Component {
|
||||
})
|
||||
: sign({ selectedToken, to, amount, from, gas, gasPrice })
|
||||
|
||||
this.props.history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||
Promise.resolve(promise)
|
||||
.then(() => history.push(CONFIRM_TRANSACTION_ROUTE))
|
||||
}
|
||||
|
||||
formShouldBeDisabled () {
|
||||
|
@ -87,7 +87,7 @@ function mapDispatchToProps (dispatch) {
|
||||
unapprovedTxs,
|
||||
})
|
||||
|
||||
dispatch(updateTransaction(editingTx))
|
||||
return dispatch(updateTransaction(editingTx))
|
||||
},
|
||||
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
|
||||
const hexPrefixedAddress = ethUtil.addHexPrefix(newAddress)
|
||||
|
@ -37,7 +37,7 @@ module.exports = {
|
||||
removeLeadingZeroes,
|
||||
}
|
||||
|
||||
function calcGasTotal (gasLimit, gasPrice) {
|
||||
function calcGasTotal (gasLimit = '0', gasPrice = '0') {
|
||||
return multiplyCurrencies(gasLimit, gasPrice, {
|
||||
toNumericBase: 'hex',
|
||||
multiplicandBase: 16,
|
||||
@ -47,9 +47,9 @@ function calcGasTotal (gasLimit, gasPrice) {
|
||||
|
||||
function isBalanceSufficient ({
|
||||
amount = '0x0',
|
||||
amountConversionRate = 0,
|
||||
balance,
|
||||
conversionRate,
|
||||
amountConversionRate = 1,
|
||||
balance = '0x0',
|
||||
conversionRate = 1,
|
||||
gasTotal = '0x0',
|
||||
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;
|
||||
border-bottom: 1px solid $geyser;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
&__tooltip-wrapper {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__tooltip-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&__sender,
|
||||
@ -14,7 +24,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
padding: 10px 20px;
|
||||
padding: 0 16px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -22,11 +32,16 @@
|
||||
|
||||
&__sender {
|
||||
padding-right: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__recipient {
|
||||
border-left: 1px solid $geyser;
|
||||
padding-left: 30px;
|
||||
border-left: 1px solid $geyser;
|
||||
|
||||
&--with-address {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__arrow-container {
|
||||
@ -42,17 +57,18 @@
|
||||
padding: 5px;
|
||||
border: 1px solid $geyser;
|
||||
border-radius: 20px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__name {
|
||||
padding-left: 5px;
|
||||
padding-left: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
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 () {
|
||||
const props = this.props
|
||||
const { position, title, children, wrapperClassName } = props
|
||||
const { position, title, children, wrapperClassName, containerClassName, onHidden } = props
|
||||
|
||||
return h('div', {
|
||||
className: wrapperClassName,
|
||||
@ -25,6 +25,8 @@ Tooltip.prototype.render = function () {
|
||||
hideOnClick: false,
|
||||
size: 'small',
|
||||
arrow: true,
|
||||
className: containerClassName,
|
||||
onHidden,
|
||||
}, children),
|
||||
|
||||
])
|
||||
|
@ -28,6 +28,8 @@ const BN = ethUtil.BN
|
||||
const R = require('ramda')
|
||||
const { stripHexPrefix } = require('ethereumjs-util')
|
||||
|
||||
global.BigNumber = BigNumber
|
||||
|
||||
BigNumber.config({
|
||||
ROUNDING_MODE: BigNumber.ROUND_HALF_DOWN,
|
||||
})
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
.btn-default,
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
.btn-secondary,
|
||||
.btn-confirm {
|
||||
height: 44px;
|
||||
background: $white;
|
||||
display: flex;
|
||||
@ -13,13 +14,14 @@
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
transition: border-color .3s ease;
|
||||
padding: 0 16px;
|
||||
min-width: 140px;
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
outline: none;
|
||||
font-family: Roboto;
|
||||
|
||||
&--disabled,
|
||||
&[disabled] {
|
||||
@ -71,6 +73,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
color: $white;
|
||||
border: 2px solid $curious-blue;
|
||||
background-color: $curious-blue;
|
||||
}
|
||||
|
||||
.btn--large {
|
||||
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
|
||||
// div.wallet-btn {
|
||||
// border: 1px solid rgb(91, 93, 103);
|
||||
|
@ -58,6 +58,4 @@
|
||||
|
||||
@import './welcome-screen.scss';
|
||||
|
||||
@import './sender-to-recipient.scss';
|
||||
|
||||
@import '../../../components/index';
|
||||
|
@ -159,15 +159,3 @@
|
||||
.network-caret {
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
padding-bottom: 10px;
|
||||
font-weight: 400;
|
||||
|
@ -55,6 +55,7 @@ $dodger-blue: #3099f2;
|
||||
$zumthor: #edf7ff;
|
||||
$ecstasy: #f7861c;
|
||||
$linen: #fdf4f4;
|
||||
$oslo-gray: #8C8E94;
|
||||
|
||||
/*
|
||||
Z-Indicies
|
||||
|
@ -165,7 +165,7 @@
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.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