parent
d733bd34fb
commit
5ee40675b9
@ -1,23 +1,35 @@ |
||||
@import './app-header/index'; |
||||
|
||||
@import './button-group/index'; |
||||
|
||||
@import './export-text-container/index'; |
||||
@import './confirm-page-container/index'; |
||||
|
||||
@import './selected-account/index'; |
||||
@import './export-text-container/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'; |
||||
|
@ -1,3 +0,0 @@ |
||||
export const TOKEN_METHOD_TRANSFER = 'transfer' |
||||
export const TOKEN_METHOD_APPROVE = 'approve' |
||||
export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' |
@ -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) |
||||
} |
||||
|
@ -0,0 +1 @@ |
||||
export { default } from './token-view-balance.container' |
@ -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) |
@ -0,0 +1 @@ |
||||
export { default } from './token-view.component' |
@ -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; |
||||
} |
||||
} |
||||
} |
@ -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> |
||||
) |
||||
} |
||||
} |
@ -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) |
@ -0,0 +1 @@ |
||||
export { default } from './transaction-list-item.container' |
@ -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) |
@ -0,0 +1 @@ |
||||
export { default } from './transaction-list.container' |
@ -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) |
@ -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; |
||||
} |
||||
} |
@ -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, |
||||
}) |
||||
} |
@ -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, |
||||
} |
||||
} |
@ -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