mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Refactor transactions list views. Add redesign components
This commit is contained in:
parent
d733bd34fb
commit
5ee40675b9
@ -451,6 +451,9 @@
|
||||
"hideTokenPrompt": {
|
||||
"message": "Hide Token?"
|
||||
},
|
||||
"history": {
|
||||
"message": "History"
|
||||
},
|
||||
"howToDeposit": {
|
||||
"message": "How would you like to deposit Ether?"
|
||||
},
|
||||
@ -651,7 +654,7 @@
|
||||
"message": "No transaction history."
|
||||
},
|
||||
"noTransactions": {
|
||||
"message": "No Transactions"
|
||||
"message": "You have no transactions"
|
||||
},
|
||||
"notFound": {
|
||||
"message": "Not Found"
|
||||
@ -702,6 +705,9 @@
|
||||
"pasteSeed": {
|
||||
"message": "Paste your seed phrase here!"
|
||||
},
|
||||
"pending": {
|
||||
"message": "Pending"
|
||||
},
|
||||
"personalAddressDetected": {
|
||||
"message": "Personal address detected. Input the token contract address."
|
||||
},
|
||||
@ -894,6 +900,9 @@
|
||||
"sendETH": {
|
||||
"message": "Send ETH"
|
||||
},
|
||||
"sendEther": {
|
||||
"message": "Send Ether"
|
||||
},
|
||||
"sendTokens": {
|
||||
"message": "Send Tokens"
|
||||
},
|
||||
|
@ -1,23 +1,35 @@
|
||||
@import './app-header/index';
|
||||
|
||||
@import './button-group/index';
|
||||
|
||||
@import './confirm-page-container/index';
|
||||
|
||||
@import './export-text-container/index';
|
||||
|
||||
@import './selected-account/index';
|
||||
|
||||
@import './info-box/index';
|
||||
|
||||
@import './network-display/index';
|
||||
@import './menu-bar/index';
|
||||
|
||||
@import './confirm-page-container/index';
|
||||
@import './modals/index';
|
||||
|
||||
@import './network-display/index';
|
||||
|
||||
@import './page-container/index';
|
||||
|
||||
@import './pages/index';
|
||||
|
||||
@import './modals/index';
|
||||
@import './selected-account/index';
|
||||
|
||||
@import './sender-to-recipient/index';
|
||||
|
||||
@import './tabs/index';
|
||||
|
||||
@import './app-header/index';
|
||||
@import './token-view/index';
|
||||
|
||||
@import './token-view-balance/index';
|
||||
|
||||
@import './transaction-list/index';
|
||||
|
||||
@import './transaction-list-item/index';
|
||||
|
||||
@import './transaction-status/index';
|
||||
|
@ -12,12 +12,12 @@ import {
|
||||
CONFIRM_TOKEN_METHOD_PATH,
|
||||
SIGNATURE_REQUEST_PATH,
|
||||
} from '../../../routes'
|
||||
import { isConfirmDeployContract } from './confirm-transaction-switch.util'
|
||||
import { isConfirmDeployContract } from '../../../helpers/transactions.util'
|
||||
import {
|
||||
TOKEN_METHOD_TRANSFER,
|
||||
TOKEN_METHOD_APPROVE,
|
||||
TOKEN_METHOD_TRANSFER_FROM,
|
||||
} from './confirm-transaction-switch.constants'
|
||||
} from '../../../constants/transactions'
|
||||
|
||||
export default class ConfirmTransactionSwitch extends Component {
|
||||
static propTypes = {
|
||||
|
@ -1,3 +0,0 @@
|
||||
export const TOKEN_METHOD_TRANSFER = 'transfer'
|
||||
export const TOKEN_METHOD_APPROVE = 'approve'
|
||||
export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'
|
@ -4,6 +4,7 @@ import Media from 'react-media'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import WalletView from '../../wallet-view'
|
||||
import TxView from '../../tx-view'
|
||||
import TokenView from '../../token-view'
|
||||
import {
|
||||
INITIALIZE_BACKUP_PHRASE_ROUTE,
|
||||
RESTORE_VAULT_ROUTE,
|
||||
@ -14,28 +15,17 @@ import {
|
||||
export default class Home extends PureComponent {
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
unapprovedTxs: PropTypes.object,
|
||||
unapprovedMsgCount: PropTypes.number,
|
||||
unapprovedPersonalMsgCount: PropTypes.number,
|
||||
unapprovedTypedMessagesCount: PropTypes.number,
|
||||
noActiveNotices: PropTypes.bool,
|
||||
lostAccounts: PropTypes.array,
|
||||
forgottenPassword: PropTypes.bool,
|
||||
seedWords: PropTypes.string,
|
||||
unconfirmedTransactionsCount: PropTypes.number,
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const {
|
||||
history,
|
||||
unapprovedTxs = {},
|
||||
unapprovedMsgCount = 0,
|
||||
unapprovedPersonalMsgCount = 0,
|
||||
unapprovedTypedMessagesCount = 0,
|
||||
} = this.props
|
||||
const { history, unconfirmedTransactionsCount = 0 } = this.props
|
||||
|
||||
// unapprovedTxs and unapproved messages
|
||||
if (Object.keys(unapprovedTxs).length ||
|
||||
unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) {
|
||||
if (unconfirmedTransactionsCount > 0) {
|
||||
history.push(CONFIRM_TRANSACTION_ROUTE)
|
||||
}
|
||||
}
|
||||
@ -69,7 +59,8 @@ export default class Home extends PureComponent {
|
||||
query="(min-width: 576px)"
|
||||
render={() => <WalletView />}
|
||||
/>
|
||||
<TxView />
|
||||
<TokenView />
|
||||
{/* <TxView /> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -2,14 +2,11 @@ import Home from './home.component'
|
||||
import { compose } from 'recompose'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { unconfirmedTransactionsCountSelector } from '../../../selectors/confirm-transaction'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask, appState } = state
|
||||
const {
|
||||
unapprovedTxs = {},
|
||||
unapprovedMsgCount = 0,
|
||||
unapprovedPersonalMsgCount = 0,
|
||||
unapprovedTypedMessagesCount = 0,
|
||||
noActiveNotices,
|
||||
lostAccounts,
|
||||
seedWords,
|
||||
@ -17,14 +14,11 @@ const mapStateToProps = state => {
|
||||
const { forgottenPassword } = appState
|
||||
|
||||
return {
|
||||
unapprovedTxs,
|
||||
unapprovedMsgCount,
|
||||
unapprovedPersonalMsgCount,
|
||||
unapprovedTypedMessagesCount,
|
||||
noActiveNotices,
|
||||
lostAccounts,
|
||||
forgottenPassword,
|
||||
seedWords,
|
||||
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,120 +0,0 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenTracker = require('eth-token-tracker')
|
||||
const connect = require('react-redux').connect
|
||||
const selectors = require('../selectors')
|
||||
const log = require('loglevel')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
userAddress: selectors.getSelectedAddress(state),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(TokenBalance)
|
||||
|
||||
|
||||
inherits(TokenBalance, Component)
|
||||
function TokenBalance () {
|
||||
this.state = {
|
||||
string: '',
|
||||
symbol: '',
|
||||
isLoading: true,
|
||||
error: null,
|
||||
}
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TokenBalance.prototype.render = function () {
|
||||
const state = this.state
|
||||
const { symbol, string, isLoading } = state
|
||||
const { balanceOnly } = this.props
|
||||
|
||||
return isLoading
|
||||
? h('span', '')
|
||||
: h('span.token-balance', [
|
||||
h('span.hide-text-overflow.token-balance__amount', string),
|
||||
!balanceOnly && h('span.token-balance__symbol', symbol),
|
||||
])
|
||||
}
|
||||
|
||||
TokenBalance.prototype.componentDidMount = function () {
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
TokenBalance.prototype.createFreshTokenTracker = function () {
|
||||
if (this.tracker) {
|
||||
// Clean up old trackers when refreshing:
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
if (!global.ethereumProvider) return
|
||||
const { userAddress, token } = this.props
|
||||
|
||||
this.tracker = new TokenTracker({
|
||||
userAddress,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: [token],
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
|
||||
// Set up listener instances for cleaning up
|
||||
this.balanceUpdater = this.updateBalance.bind(this)
|
||||
this.showError = error => {
|
||||
this.setState({ error, isLoading: false })
|
||||
}
|
||||
this.tracker.on('update', this.balanceUpdater)
|
||||
this.tracker.on('error', this.showError)
|
||||
|
||||
this.tracker.updateBalances()
|
||||
.then(() => {
|
||||
this.updateBalance(this.tracker.serialize())
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(`Problem updating balances`, reason)
|
||||
this.setState({ isLoading: false })
|
||||
})
|
||||
}
|
||||
|
||||
TokenBalance.prototype.componentDidUpdate = function (nextProps) {
|
||||
const {
|
||||
userAddress: oldAddress,
|
||||
token: { address: oldTokenAddress },
|
||||
} = this.props
|
||||
const {
|
||||
userAddress: newAddress,
|
||||
token: { address: newTokenAddress },
|
||||
} = nextProps
|
||||
|
||||
if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) return
|
||||
if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) return
|
||||
|
||||
this.setState({ isLoading: true })
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
TokenBalance.prototype.updateBalance = function (tokens = []) {
|
||||
if (!this.tracker.running) {
|
||||
return
|
||||
}
|
||||
|
||||
const [{ string, symbol }] = tokens
|
||||
|
||||
this.setState({
|
||||
string,
|
||||
symbol,
|
||||
isLoading: false,
|
||||
})
|
||||
}
|
||||
|
||||
TokenBalance.prototype.componentWillUnmount = function () {
|
||||
if (!this.tracker) return
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
1
ui/app/components/token-view-balance/index.js
Normal file
1
ui/app/components/token-view-balance/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './token-view-balance.container'
|
66
ui/app/components/token-view-balance/index.scss
Normal file
66
ui/app/components/token-view-balance/index.scss
Normal file
@ -0,0 +1,66 @@
|
||||
.token-view-balance {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
height: 54px;
|
||||
|
||||
&__balance {
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
align-items: center;
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__primary-balance {
|
||||
font-size: 1.5rem;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
margin-bottom: 12px;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__secondary-balance {
|
||||
font-size: 1.15rem;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
&__balance-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
min-width: initial;
|
||||
width: 100px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
flex-direction: column;
|
||||
height: initial
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Button from '../button'
|
||||
import Identicon from '../identicon'
|
||||
import TokenBalance from '../token-balance'
|
||||
import { SEND_ROUTE } from '../../routes'
|
||||
import { formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
export default class TokenViewBalance extends PureComponent {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
showDepositModal: PropTypes.func,
|
||||
selectedToken: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
network: PropTypes.string,
|
||||
ethBalance: PropTypes.string,
|
||||
fiatBalance: PropTypes.string,
|
||||
currentCurrency: PropTypes.string,
|
||||
}
|
||||
|
||||
renderBalance () {
|
||||
const { selectedToken, ethBalance, fiatBalance, currentCurrency } = this.props
|
||||
const formattedFiatBalance = formatCurrency(fiatBalance, currentCurrency)
|
||||
|
||||
return selectedToken
|
||||
? (
|
||||
<TokenBalance
|
||||
token={selectedToken}
|
||||
withSymbol
|
||||
className="token-view-balance__primary-balance"
|
||||
/>
|
||||
) : (
|
||||
<div className="token-view-balance__balance">
|
||||
<div className="token-view-balance__primary-balance">
|
||||
{ `${ethBalance} ETH` }
|
||||
</div>
|
||||
<div className="token-view-balance__secondary-balance">
|
||||
{ formattedFiatBalance }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
const { t } = this.context
|
||||
const { selectedToken, showDepositModal, history } = this.props
|
||||
|
||||
return (
|
||||
<div className="token-view-balance__buttons">
|
||||
{
|
||||
!selectedToken && (
|
||||
<Button
|
||||
type="primary"
|
||||
className="token-view-balance__button"
|
||||
onClick={() => showDepositModal()}
|
||||
>
|
||||
{ t('deposit') }
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
type="primary"
|
||||
className="token-view-balance__button"
|
||||
onClick={() => history.push(SEND_ROUTE)}
|
||||
>
|
||||
{ t('send') }
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { network, selectedToken } = this.props
|
||||
|
||||
return (
|
||||
<div className="token-view-balance">
|
||||
<div className="token-view-balance__balance-container">
|
||||
<Identicon
|
||||
diameter={50}
|
||||
address={selectedToken && selectedToken.address}
|
||||
network={network}
|
||||
/>
|
||||
{ this.renderBalance() }
|
||||
</div>
|
||||
{ this.renderButtons() }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { compose } from 'recompose'
|
||||
import TokenViewBalance from './token-view-balance.component'
|
||||
import { getSelectedToken, getSelectedAddress } from '../../selectors'
|
||||
import { showModal } from '../../actions'
|
||||
import { getValueFromWeiHex } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const selectedAddress = getSelectedAddress(state)
|
||||
const { metamask } = state
|
||||
const { network, accounts, currentCurrency, conversionRate } = metamask
|
||||
const account = accounts[selectedAddress]
|
||||
const { balance: value } = account
|
||||
|
||||
const ethBalance = getValueFromWeiHex({
|
||||
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 3,
|
||||
})
|
||||
|
||||
const fiatBalance = getValueFromWeiHex({
|
||||
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
||||
})
|
||||
|
||||
return {
|
||||
selectedToken: getSelectedToken(state),
|
||||
network,
|
||||
ethBalance,
|
||||
fiatBalance,
|
||||
currentCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(TokenViewBalance)
|
1
ui/app/components/token-view/index.js
Normal file
1
ui/app/components/token-view/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './token-view.component'
|
27
ui/app/components/token-view/index.scss
Normal file
27
ui/app/components/token-view/index.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.token-view {
|
||||
flex: 1 1 66.5%;
|
||||
background: $white;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__balance-wrapper {
|
||||
@media screen and (max-width: $break-small) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin: 2.3em 2.37em .8em;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
28
ui/app/components/token-view/token-view.component.js
Normal file
28
ui/app/components/token-view/token-view.component.js
Normal file
@ -0,0 +1,28 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Media from 'react-media'
|
||||
import MenuBar from '../menu-bar'
|
||||
import TokenViewBalance from '../token-view-balance'
|
||||
// import TransactionList from '../tx-list'
|
||||
import TransactionList from '../transaction-list'
|
||||
|
||||
export default class TokenView extends PureComponent {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className="token-view">
|
||||
<Media
|
||||
query="(max-width: 575px)"
|
||||
render={() => <MenuBar />}
|
||||
/>
|
||||
<div className="token-view__balance-wrapper">
|
||||
<TokenViewBalance />
|
||||
</div>
|
||||
<TransactionList />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
1
ui/app/components/transaction-action/index.js
Normal file
1
ui/app/components/transaction-action/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './transaction-action.container'
|
@ -0,0 +1,52 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getTransactionActionKey } from '../../helpers/transactions.util'
|
||||
|
||||
export default class TransactionAction extends PureComponent {
|
||||
static contextTypes = {
|
||||
tOrDefault: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
transaction: PropTypes.object,
|
||||
methodData: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
transactionAction: '',
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.getTransactionAction()
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.getTransactionAction()
|
||||
}
|
||||
|
||||
getTransactionAction () {
|
||||
const { transactionAction } = this.state
|
||||
const { transaction, methodData } = this.props
|
||||
const { data, isFetching } = methodData
|
||||
|
||||
if (isFetching || transactionAction) {
|
||||
return
|
||||
}
|
||||
|
||||
const actionKey = getTransactionActionKey(transaction, data)
|
||||
const action = actionKey && this.context.tOrDefault(actionKey)
|
||||
this.setState({ transactionAction: action })
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className } = this.props
|
||||
const { transactionAction } = this.state
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{ transactionAction || '--' }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import withMethodData from '../../higher-order-components/with-method-data'
|
||||
import TransactionAction from './transaction-action.component'
|
||||
|
||||
export default withMethodData(TransactionAction)
|
1
ui/app/components/transaction-list-item/index.js
Normal file
1
ui/app/components/transaction-list-item/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './transaction-list-item.container'
|
71
ui/app/components/transaction-list-item/index.scss
Normal file
71
ui/app/components/transaction-list-item/index.scss
Normal file
@ -0,0 +1,71 @@
|
||||
.transaction-list-item {
|
||||
box-sizing: border-box;
|
||||
height: 74px;
|
||||
padding: 0 21px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $geyser;
|
||||
cursor: pointer;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
&__identicon-wrapper {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
&__action-block {
|
||||
padding: 0 8px 0 12px;
|
||||
width: 180px;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: 0 8px;
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
text-transform: capitalize;
|
||||
padding-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding-bottom: 0;
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__nonce {
|
||||
font-size: .75rem;
|
||||
color: #5e6064;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__transaction-amounts {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__primary-transaction-amount {
|
||||
text-align: end;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
font-size: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__secondary-transaction-amount {
|
||||
text-align: end;
|
||||
font-size: .75rem;
|
||||
color: #5e6064;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba($alto, .2);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Media from 'react-media'
|
||||
import Identicon from '../identicon'
|
||||
import TransactionStatus from '../transaction-status'
|
||||
import TransactionAction from '../transaction-action'
|
||||
import { formatDate } from '../../util'
|
||||
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
|
||||
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
|
||||
import { UNAPPROVED_STATUS } from '../../constants/transactions'
|
||||
import { hexToDecimal } from '../../helpers/conversions.util'
|
||||
|
||||
export default class TransactionListItem extends PureComponent {
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
methodData: PropTypes.object,
|
||||
transaction: PropTypes.object,
|
||||
ethTransactionAmount: PropTypes.string,
|
||||
fiatDisplayValue: PropTypes.string,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
const { transaction, history } = this.props
|
||||
const { id, status, hash, metamaskNetworkId } = transaction
|
||||
|
||||
if (status === UNAPPROVED_STATUS) {
|
||||
history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)
|
||||
} else if (hash) {
|
||||
const prefix = prefixForNetwork(metamaskNetworkId)
|
||||
const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
|
||||
global.platform.openWindow({ url: etherscanUrl })
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
transaction,
|
||||
ethTransactionAmount,
|
||||
fiatDisplayValue,
|
||||
} = this.props
|
||||
const { txParams = {} } = transaction
|
||||
const nonce = hexToDecimal(txParams.nonce)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="transaction-list-item"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div className="transaction-list-item__identicon-wrapper">
|
||||
<Media query="(max-width: 575px)">
|
||||
{
|
||||
matches => (
|
||||
<Identicon
|
||||
address={txParams.to}
|
||||
diameter={matches ? 26 : 34}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Media>
|
||||
</div>
|
||||
<div className="transaction-list-item__action-block">
|
||||
<TransactionAction
|
||||
transaction={transaction}
|
||||
className="transaction-list-item__action"
|
||||
/>
|
||||
<div className="transaction-list-item__nonce">
|
||||
{ `#${nonce} - ${formatDate(transaction.time)}` }
|
||||
</div>
|
||||
</div>
|
||||
<TransactionStatus status={transaction.status} />
|
||||
<div className="transaction-list-item__transaction-amounts">
|
||||
<div className="transaction-list-item__primary-transaction-amount">
|
||||
{ `-${fiatDisplayValue}` }
|
||||
</div>
|
||||
<div className="transaction-list-item__secondary-transaction-amount">
|
||||
{ `-${ethTransactionAmount} ETH` }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { compose } from 'recompose'
|
||||
import TransactionListItem from './transaction-list-item.component'
|
||||
import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
|
||||
import { formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { metamask } = state
|
||||
const { currentCurrency, conversionRate } = metamask
|
||||
const { transaction: { txParams: { value } = {} } = {} } = ownProps
|
||||
const ethTransactionAmount = getEthFromWeiHex({ value, conversionRate })
|
||||
const fiatTransactionAmount = getValueFromWeiHex({
|
||||
value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2,
|
||||
})
|
||||
const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
|
||||
const fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}`
|
||||
|
||||
return {
|
||||
ethTransactionAmount,
|
||||
fiatDisplayValue,
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps),
|
||||
)(TransactionListItem)
|
1
ui/app/components/transaction-list/index.js
Normal file
1
ui/app/components/transaction-list/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './transaction-list.container'
|
40
ui/app/components/transaction-list/index.scss
Normal file
40
ui/app/components/transaction-list/index.scss
Normal file
@ -0,0 +1,40 @@
|
||||
.transaction-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
|
||||
&__header {
|
||||
flex: 0 0 auto;
|
||||
font-size: .875rem;
|
||||
color: $dusty-gray;
|
||||
border-bottom: 1px solid $geyser;
|
||||
padding: 16px 0 8px 20px;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: 8px 0 8px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__transactions {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__pending-transactions {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-rows: 35% 1fr;
|
||||
}
|
||||
|
||||
&__empty-text {
|
||||
grid-row-start: 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: $silver;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import TransactionListItem from '../transaction-list-item'
|
||||
|
||||
export default class TransactionList extends PureComponent {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
pendingTransactions: [],
|
||||
completedTransactions: [],
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
pendingTransactions: PropTypes.array,
|
||||
completedTransactions: PropTypes.array,
|
||||
}
|
||||
|
||||
renderTransactions () {
|
||||
const { t } = this.context
|
||||
const { pendingTransactions, completedTransactions } = this.props
|
||||
|
||||
return (
|
||||
<div className="transaction-list__transactions">
|
||||
{
|
||||
pendingTransactions.length > 0 && (
|
||||
<div className="transaction-list__pending-transactions">
|
||||
<div className="transaction-list__header">
|
||||
{ `${t('pending')} (${pendingTransactions.length})` }
|
||||
</div>
|
||||
{
|
||||
pendingTransactions.map(transaction => {
|
||||
return (
|
||||
<TransactionListItem
|
||||
transaction={transaction}
|
||||
key={transaction.id}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="transaction-list__completed-transactions">
|
||||
<div className="transaction-list__header">
|
||||
{ t('history') }
|
||||
</div>
|
||||
{
|
||||
completedTransactions.length > 0
|
||||
? (
|
||||
completedTransactions.map(transaction => {
|
||||
return (
|
||||
<TransactionListItem
|
||||
transaction={transaction}
|
||||
key={transaction.id}
|
||||
/>
|
||||
)
|
||||
})
|
||||
)
|
||||
: this.renderEmpty()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderEmpty () {
|
||||
return (
|
||||
<div className="transaction-list__empty">
|
||||
<div className="transaction-list__empty-text">
|
||||
{ this.context.t('noTransactions') }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className="transaction-list">
|
||||
{
|
||||
this.renderTransactions()
|
||||
// pendingTransactions.length + completedTransactions.length > 0
|
||||
// ? this.renderTransactions()
|
||||
// : this.renderEmpty()
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { compose } from 'recompose'
|
||||
import TransactionList from './transaction-list.component'
|
||||
import {
|
||||
pendingTransactionsSelector,
|
||||
completedTransactionsSelector,
|
||||
} from '../../selectors/transactions'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
pendingTransactions: pendingTransactionsSelector(state),
|
||||
completedTransactions: completedTransactionsSelector(state),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps)
|
||||
)(TransactionList)
|
@ -10,6 +10,12 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
height: 24px;
|
||||
width: 74px;
|
||||
font-size: .5rem;
|
||||
}
|
||||
|
||||
&--confirmed {
|
||||
background-color: #eafad7;
|
||||
color: #609a1c;
|
||||
|
@ -15,6 +15,9 @@ const Tooltip = require('./tooltip')
|
||||
const TxList = require('./tx-list')
|
||||
const SelectedAccount = require('./selected-account')
|
||||
|
||||
import Media from 'react-media'
|
||||
import MenuBar from './menu-bar'
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
@ -104,49 +107,11 @@ TxView.prototype.renderButtons = function () {
|
||||
}
|
||||
|
||||
TxView.prototype.render = function () {
|
||||
const { hideSidebar, isMascara, showSidebar, sidebarOpen } = this.props
|
||||
const { t } = this.context
|
||||
|
||||
return h('div.tx-view.flex-column', {
|
||||
style: {},
|
||||
}, [
|
||||
|
||||
h('div.flex-row.phone-visible', {
|
||||
style: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flex: '0 0 auto',
|
||||
marginBottom: '16px',
|
||||
padding: '5px',
|
||||
borderBottom: '1px solid #e5e5e5',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(Tooltip, {
|
||||
title: t('menu'),
|
||||
position: 'bottom',
|
||||
}, [
|
||||
h('div.fa.fa-bars', {
|
||||
style: {
|
||||
fontSize: '1.3em',
|
||||
cursor: 'pointer',
|
||||
padding: '10px',
|
||||
},
|
||||
onClick: () => sidebarOpen ? hideSidebar() : showSidebar(),
|
||||
return h('div.tx-view.flex-column', [
|
||||
h(Media, {
|
||||
query: '(max-width: 575px)',
|
||||
render: () => h(MenuBar),
|
||||
}),
|
||||
]),
|
||||
|
||||
h(SelectedAccount),
|
||||
|
||||
!isMascara && h(Tooltip, {
|
||||
title: t('openInTab'),
|
||||
position: 'bottom',
|
||||
}, [
|
||||
h('div.open-in-browser', {
|
||||
onClick: () => global.platform.openExtensionInBrowser(),
|
||||
}, [h('img', { src: 'images/popout.svg' })]),
|
||||
]),
|
||||
]),
|
||||
|
||||
this.renderHeroBalance(),
|
||||
|
||||
|
18
ui/app/constants/transactions.js
Normal file
18
ui/app/constants/transactions.js
Normal file
@ -0,0 +1,18 @@
|
||||
export const UNAPPROVED_STATUS = 'unapproved'
|
||||
export const REJECTED_STATUS = 'rejected'
|
||||
export const APPROVED_STATUS = 'approved'
|
||||
export const SIGNED_STATUS = 'signed'
|
||||
export const SUBMITTED_STATUS = 'submitted'
|
||||
export const CONFIRMED_STATUS = 'confirmed'
|
||||
export const FAILED_STATUS = 'failed'
|
||||
export const DROPPED_STATUS = 'dropped'
|
||||
|
||||
export const TOKEN_METHOD_TRANSFER = 'transfer'
|
||||
export const TOKEN_METHOD_APPROVE = 'approve'
|
||||
export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'
|
||||
|
||||
export const SEND_ETHER_ACTION_KEY = 'sendEther'
|
||||
export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment'
|
||||
export const APPROVE_ACTION_KEY = 'approve'
|
||||
export const SEND_TOKEN_ACTION_KEY = 'sendToken'
|
||||
export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
|
@ -1,130 +0,0 @@
|
||||
.hero-balance {
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin: 2.3em 2.37em .8em;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.balance-container {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
flex-direction: column;
|
||||
flex: 0 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
flex-direction: row;
|
||||
flex-grow: 3;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.balance-display {
|
||||
.token-amount {
|
||||
color: $black;
|
||||
max-width: 100%;
|
||||
|
||||
.token-balance {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
|
||||
.token-amount {
|
||||
font-size: 1.75rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
.token-balance {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.fiat-amount {
|
||||
font-size: 115%;
|
||||
margin-top: 8.5%;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
margin: 0 .8em;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
|
||||
.token-amount {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.fiat-amount {
|
||||
margin-top: .25%;
|
||||
font-size: 105%;
|
||||
}
|
||||
}
|
||||
|
||||
@media #{$sub-mid-size-breakpoint-range} {
|
||||
margin-left: .4em;
|
||||
margin-right: .4em;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
.token-amount {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.fiat-amount {
|
||||
margin-top: .25%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hero-balance-buttons {
|
||||
|
||||
@media screen and (max-width: $break-small) {
|
||||
width: 100%;
|
||||
// height: 100px; // needed a round number to set the heights of the buttons inside
|
||||
flex: 0 0 auto;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
flex-grow: 2;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hero-balance-button {
|
||||
min-width: initial;
|
||||
width: 6rem;
|
||||
|
||||
@media #{$sub-mid-size-breakpoint-range} {
|
||||
padding: .4rem;
|
||||
width: 4rem;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
@ -19,8 +19,6 @@
|
||||
@import './loading-overlay.scss';
|
||||
|
||||
// Balances
|
||||
@import './hero-balance.scss';
|
||||
|
||||
@import './wallet-balance.scss';
|
||||
|
||||
// Tx List and Sections
|
||||
|
@ -49,13 +49,6 @@ $wallet-view-bg: $alabaster;
|
||||
}
|
||||
}
|
||||
|
||||
.open-in-browser {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
// wallet view and sidebar
|
||||
|
||||
.wallet-view {
|
||||
|
@ -6,8 +6,7 @@ import {
|
||||
|
||||
import {
|
||||
getTokenData,
|
||||
getMethodData,
|
||||
getTransactionAmount,
|
||||
getValueFromWeiHex,
|
||||
getTransactionFee,
|
||||
getHexGasTotal,
|
||||
addFiat,
|
||||
@ -17,6 +16,7 @@ import {
|
||||
isSmartContractAddress,
|
||||
} from '../helpers/confirm-transaction/util'
|
||||
|
||||
import { getMethodData } from '../helpers/transactions.util'
|
||||
import { getSymbolAndDecimals } from '../token-util'
|
||||
import { conversionUtil } from '../conversion-util'
|
||||
|
||||
@ -301,10 +301,10 @@ export function updateTxDataAndCalculate (txData) {
|
||||
|
||||
const { txParams: { value, gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData
|
||||
|
||||
const fiatTransactionAmount = getTransactionAmount({
|
||||
const fiatTransactionAmount = getValueFromWeiHex({
|
||||
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
||||
})
|
||||
const ethTransactionAmount = getTransactionAmount({
|
||||
const ethTransactionAmount = getValueFromWeiHex({
|
||||
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
|
||||
})
|
||||
|
||||
|
@ -7,9 +7,6 @@ import BigNumber from 'bignumber.js'
|
||||
|
||||
abiDecoder.addABI(abi)
|
||||
|
||||
import MethodRegistry from 'eth-method-registry'
|
||||
const registry = new MethodRegistry({ provider: global.ethereumProvider })
|
||||
|
||||
import {
|
||||
conversionUtil,
|
||||
addCurrencies,
|
||||
@ -23,18 +20,6 @@ export function getTokenData (data = {}) {
|
||||
return abiDecoder.decodeMethod(data)
|
||||
}
|
||||
|
||||
export async function getMethodData (data = {}) {
|
||||
const prefixedData = ethUtil.addHexPrefix(data)
|
||||
const fourBytePrefix = prefixedData.slice(0, 10)
|
||||
const sig = await registry.lookup(fourBytePrefix)
|
||||
const parsedResult = registry.parse(sig)
|
||||
|
||||
return {
|
||||
name: parsedResult.name,
|
||||
params: parsedResult.args,
|
||||
}
|
||||
}
|
||||
|
||||
export function increaseLastGasPrice (lastGasPrice) {
|
||||
return ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
|
||||
multiplicandBase: 16,
|
||||
@ -76,7 +61,7 @@ export function addFiat (...args) {
|
||||
})
|
||||
}
|
||||
|
||||
export function getTransactionAmount ({
|
||||
export function getValueFromWeiHex ({
|
||||
value,
|
||||
toCurrency,
|
||||
conversionRate,
|
||||
|
@ -92,9 +92,9 @@ describe('Confirm Transaction utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTransactionAmount', () => {
|
||||
describe('getValueFromWeiHex', () => {
|
||||
it('should get the transaction amount in ETH', () => {
|
||||
const ethTransactionAmount = utils.getTransactionAmount({
|
||||
const ethTransactionAmount = utils.getValueFromWeiHex({
|
||||
value: '0xde0b6b3a7640000', toCurrency: 'ETH', conversionRate: 468.58, numberOfDecimals: 6,
|
||||
})
|
||||
|
||||
@ -102,7 +102,7 @@ describe('Confirm Transaction utils', () => {
|
||||
})
|
||||
|
||||
it('should get the transaction amount in fiat', () => {
|
||||
const fiatTransactionAmount = utils.getTransactionAmount({
|
||||
const fiatTransactionAmount = utils.getValueFromWeiHex({
|
||||
value: '0xde0b6b3a7640000', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2,
|
||||
})
|
||||
|
||||
|
37
ui/app/helpers/conversions.util.js
Normal file
37
ui/app/helpers/conversions.util.js
Normal file
@ -0,0 +1,37 @@
|
||||
import { conversionUtil } from '../conversion-util'
|
||||
|
||||
export function hexToDecimal (hexValue) {
|
||||
return conversionUtil(hexValue, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
})
|
||||
}
|
||||
|
||||
export function getEthFromWeiHex ({
|
||||
value,
|
||||
conversionRate,
|
||||
}) {
|
||||
return getValueFromWeiHex({
|
||||
value,
|
||||
conversionRate,
|
||||
toCurrency: 'ETH',
|
||||
numberOfDecimals: 6,
|
||||
})
|
||||
}
|
||||
|
||||
export function getValueFromWeiHex ({
|
||||
value,
|
||||
toCurrency,
|
||||
conversionRate,
|
||||
numberOfDecimals,
|
||||
}) {
|
||||
return conversionUtil(value, {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
fromCurrency: 'ETH',
|
||||
toCurrency,
|
||||
numberOfDecimals,
|
||||
fromDenomination: 'WEI',
|
||||
conversionRate,
|
||||
})
|
||||
}
|
57
ui/app/helpers/transactions.util.js
Normal file
57
ui/app/helpers/transactions.util.js
Normal file
@ -0,0 +1,57 @@
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import MethodRegistry from 'eth-method-registry'
|
||||
const registry = new MethodRegistry({ provider: global.ethereumProvider })
|
||||
|
||||
import {
|
||||
TOKEN_METHOD_TRANSFER,
|
||||
TOKEN_METHOD_APPROVE,
|
||||
TOKEN_METHOD_TRANSFER_FROM,
|
||||
SEND_ETHER_ACTION_KEY,
|
||||
DEPLOY_CONTRACT_ACTION_KEY,
|
||||
APPROVE_ACTION_KEY,
|
||||
SEND_TOKEN_ACTION_KEY,
|
||||
TRANSFER_FROM_ACTION_KEY,
|
||||
} from '../constants/transactions'
|
||||
|
||||
export function isConfirmDeployContract (txData = {}) {
|
||||
const { txParams = {} } = txData
|
||||
return !txParams.to
|
||||
}
|
||||
|
||||
export function getTransactionActionKey (transaction, methodData) {
|
||||
const { txParams: { data } = {} } = transaction
|
||||
|
||||
if (isConfirmDeployContract(transaction)) {
|
||||
return DEPLOY_CONTRACT_ACTION_KEY
|
||||
}
|
||||
|
||||
if (data) {
|
||||
const { name } = methodData
|
||||
const methodName = name && name.toLowerCase()
|
||||
|
||||
switch (methodName) {
|
||||
case TOKEN_METHOD_TRANSFER:
|
||||
return SEND_TOKEN_ACTION_KEY
|
||||
case TOKEN_METHOD_APPROVE:
|
||||
return APPROVE_ACTION_KEY
|
||||
case TOKEN_METHOD_TRANSFER_FROM:
|
||||
return TRANSFER_FROM_ACTION_KEY
|
||||
default:
|
||||
return name
|
||||
}
|
||||
} else {
|
||||
return SEND_ETHER_ACTION_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMethodData (data = {}) {
|
||||
const prefixedData = ethUtil.addHexPrefix(data)
|
||||
const fourBytePrefix = prefixedData.slice(0, 10)
|
||||
const sig = await registry.lookup(fourBytePrefix)
|
||||
const parsedResult = registry.parse(sig)
|
||||
|
||||
return {
|
||||
name: parsedResult.name,
|
||||
params: parsedResult.args,
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getMethodData } from '../../helpers/confirm-transaction/util'
|
||||
import { getMethodData } from '../../helpers/transactions.util'
|
||||
|
||||
export default function withMethodData (WrappedComponent) {
|
||||
return class MethodDataWrappedComponent extends PureComponent {
|
||||
@ -13,7 +13,11 @@ export default function withMethodData (WrappedComponent) {
|
||||
}
|
||||
|
||||
state = {
|
||||
methodData: {},
|
||||
methodData: {
|
||||
data: {},
|
||||
},
|
||||
isFetching: false,
|
||||
error: null,
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@ -25,18 +29,24 @@ export default function withMethodData (WrappedComponent) {
|
||||
const { txParams: { data = '' } = {} } = transaction
|
||||
|
||||
if (data) {
|
||||
this.setState({ isFetching: true })
|
||||
|
||||
try {
|
||||
const methodData = await getMethodData(data)
|
||||
this.setState({ methodData })
|
||||
this.setState({ methodData, isFetching: false })
|
||||
} catch (error) {
|
||||
this.setState({ isFetching: false, error })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { methodData } = this.state
|
||||
const { methodData, isFetching, error } = this.state
|
||||
|
||||
return (
|
||||
<WrappedComponent
|
||||
{ ...this.props }
|
||||
methodData={methodData}
|
||||
methodData={{ data: methodData, isFetching, error }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ class I18nProvider extends Component {
|
||||
t (key, ...args) {
|
||||
return t(current, key, ...args) || t(en, key, ...args) || `[${key}]`
|
||||
},
|
||||
tOrDefault (key, ...args) {
|
||||
return t(current, key, ...args) || t(en, key, ...args) || key
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +31,7 @@ I18nProvider.propTypes = {
|
||||
|
||||
I18nProvider.childContextTypes = {
|
||||
t: PropTypes.func,
|
||||
tOrDefault: PropTypes.func,
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
|
@ -1,5 +1,9 @@
|
||||
const valuesFor = require('./util').valuesFor
|
||||
const abi = require('human-standard-token-abi')
|
||||
import { createSelector } from 'reselect'
|
||||
|
||||
import {
|
||||
transactionsSelector,
|
||||
} from './selectors/transactions'
|
||||
|
||||
const {
|
||||
multiplyCurrencies,
|
||||
@ -101,21 +105,49 @@ function getCurrentAccountWithSendEtherInfo (state) {
|
||||
return accounts.find(({ address }) => address === currentAddress)
|
||||
}
|
||||
|
||||
function transactionsSelector (state) {
|
||||
const { network, selectedTokenAddress } = state.metamask
|
||||
const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
|
||||
const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined
|
||||
const transactions = state.metamask.selectedAddressTxList || []
|
||||
const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
|
||||
// // function shapeShiftTxListSelector (state) {
|
||||
// // return state.metamask.shapeShiftTxList || []
|
||||
// // }
|
||||
|
||||
// const transactionsSelector = createSelector(
|
||||
// selectedTokenAddressSelector,
|
||||
// unapprovedMsgsSelector,
|
||||
// shapeShiftTxListSelector,
|
||||
// selectedAddressTxListSelector,
|
||||
// (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => {
|
||||
// const unapprovedMsgsList = valuesFor(unapprovedMsgs)
|
||||
// const txsToRender = transactions.concat(unapprovedMsgsList, shapeShiftTxList)
|
||||
|
||||
// return selectedTokenAddress
|
||||
// ? txsToRender
|
||||
// .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
|
||||
// .sort((a, b) => b.time - a.time)
|
||||
// : txsToRender
|
||||
// .sort((a, b) => b.time - a.time)
|
||||
// }
|
||||
// )
|
||||
|
||||
// // function transactionsSelector (state) {
|
||||
// // const { selectedTokenAddress } = state.metamask
|
||||
// // const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
|
||||
// // const shapeShiftTxList = shapeShiftTxListSelector(state)
|
||||
// // const transactions = state.metamask.selectedAddressTxList || []
|
||||
// // const txsToRender = transactions.concat(unapprovedMsgs, shapeShiftTxList)
|
||||
|
||||
// // return selectedTokenAddress
|
||||
// // ? txsToRender
|
||||
// // .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
|
||||
// // .sort((a, b) => b.time - a.time)
|
||||
// // : txsToRender
|
||||
// // .sort((a, b) => b.time - a.time)
|
||||
// // }
|
||||
|
||||
export const pendingTransactionsSelector = createSelector(
|
||||
transactionsSelector,
|
||||
transactions => {
|
||||
|
||||
// console.log({txsToRender, selectedTokenAddress})
|
||||
return selectedTokenAddress
|
||||
? txsToRender
|
||||
.filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
|
||||
.sort((a, b) => b.time - a.time)
|
||||
: txsToRender
|
||||
.sort((a, b) => b.time - a.time)
|
||||
}
|
||||
)
|
||||
|
||||
function getGasIsLoading (state) {
|
||||
return state.appState.gasIsLoading
|
||||
|
50
ui/app/selectors/transactions.js
Normal file
50
ui/app/selectors/transactions.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { createSelector } from 'reselect'
|
||||
import { valuesFor } from '../util'
|
||||
import {
|
||||
UNAPPROVED_STATUS,
|
||||
APPROVED_STATUS,
|
||||
SUBMITTED_STATUS,
|
||||
} from '../constants/transactions'
|
||||
|
||||
export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList
|
||||
export const selectedTokenAddressSelector = state => state.metamask.selectedTokenAddress
|
||||
export const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
|
||||
export const selectedAddressTxListSelector = state => state.metamask.selectedAddressTxList
|
||||
|
||||
const pendingStatusHash = {
|
||||
[UNAPPROVED_STATUS]: true,
|
||||
[APPROVED_STATUS]: true,
|
||||
[SUBMITTED_STATUS]: true,
|
||||
}
|
||||
|
||||
export const transactionsSelector = createSelector(
|
||||
selectedTokenAddressSelector,
|
||||
unapprovedMsgsSelector,
|
||||
shapeShiftTxListSelector,
|
||||
selectedAddressTxListSelector,
|
||||
(selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => {
|
||||
const unapprovedMsgsList = valuesFor(unapprovedMsgs)
|
||||
const txsToRender = transactions.concat(unapprovedMsgsList, shapeShiftTxList)
|
||||
|
||||
return selectedTokenAddress
|
||||
? txsToRender
|
||||
.filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
|
||||
.sort((a, b) => b.time - a.time)
|
||||
: txsToRender
|
||||
.sort((a, b) => b.time - a.time)
|
||||
}
|
||||
)
|
||||
|
||||
export const pendingTransactionsSelector = createSelector(
|
||||
transactionsSelector,
|
||||
(transactions = []) => (
|
||||
transactions.filter(transaction => transaction.status in pendingStatusHash)
|
||||
)
|
||||
)
|
||||
|
||||
export const completedTransactionsSelector = createSelector(
|
||||
transactionsSelector,
|
||||
(transactions = []) => (
|
||||
transactions.filter(transaction => !(transaction.status in pendingStatusHash))
|
||||
)
|
||||
)
|
Loading…
Reference in New Issue
Block a user