mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Adding Token transaction detail screen
This commit is contained in:
parent
690ddf5ed7
commit
f1fb9e10a0
@ -83,6 +83,8 @@ var actions = {
|
||||
hideWarning: hideWarning,
|
||||
// accounts screen
|
||||
SET_SELECTED_ACCOUNT: 'SET_SELECTED_ACCOUNT',
|
||||
SET_SELECTED_TOKEN: 'SET_SELECTED_TOKEN',
|
||||
setSelectedToken,
|
||||
SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL',
|
||||
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
|
||||
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
|
||||
@ -585,6 +587,13 @@ function setCurrentAccountTab (newTabName) {
|
||||
return callBackgroundThenUpdateNoSpinner(background.setCurrentAccountTab, newTabName)
|
||||
}
|
||||
|
||||
function setSelectedToken (tokenAddress) {
|
||||
return {
|
||||
type: actions.SET_SELECTED_TOKEN,
|
||||
value: tokenAddress || null,
|
||||
}
|
||||
}
|
||||
|
||||
function showAccountDetail (address) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
@ -2,13 +2,19 @@ const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenBalance = require('./token-balance')
|
||||
|
||||
const { formatBalance, generateBalanceObject } = require('../util')
|
||||
|
||||
module.exports = connect(mapStateToProps)(BalanceComponent)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const accounts = state.metamask.accounts
|
||||
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
|
||||
const account = accounts[selectedAddress]
|
||||
|
||||
return {
|
||||
account,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
}
|
||||
@ -21,9 +27,8 @@ function BalanceComponent () {
|
||||
|
||||
BalanceComponent.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { balanceValue } = props
|
||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||
// const { balanceValue } = props
|
||||
const { token } = props
|
||||
|
||||
return h('div.balance-container', {}, [
|
||||
|
||||
@ -33,13 +38,24 @@ BalanceComponent.prototype.render = function () {
|
||||
style: {},
|
||||
}),
|
||||
|
||||
this.renderBalance(formattedBalance),
|
||||
token ? this.renderTokenBalance() : this.renderBalance(),
|
||||
])
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.renderBalance = function (formattedBalance) {
|
||||
BalanceComponent.prototype.renderTokenBalance = function () {
|
||||
const { token } = this.props
|
||||
|
||||
return h('div.flex-column.balance-display', [
|
||||
h('div.token-amount', [ h(TokenBalance, { token }) ]),
|
||||
])
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.renderBalance = function () {
|
||||
const props = this.props
|
||||
const { shorten } = props
|
||||
const { shorten, account } = props
|
||||
const balanceValue = account && account.balance
|
||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (formattedBalance === 'None' || formattedBalance === '...') {
|
||||
|
104
ui/app/components/token-balance.js
Normal file
104
ui/app/components/token-balance.js
Normal file
@ -0,0 +1,104 @@
|
||||
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')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
userAddress: selectors.getSelectedAddress(state),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(TokenBalance)
|
||||
|
||||
|
||||
inherits(TokenBalance, Component)
|
||||
function TokenBalance () {
|
||||
this.state = {
|
||||
balance: '',
|
||||
isLoading: true,
|
||||
error: null,
|
||||
}
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TokenBalance.prototype.render = function () {
|
||||
const state = this.state
|
||||
const { balance, isLoading } = state
|
||||
|
||||
return isLoading
|
||||
? h('span', '')
|
||||
: h('span', balance)
|
||||
}
|
||||
|
||||
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,
|
||||
} = this.props
|
||||
const {
|
||||
userAddress: newAddress,
|
||||
} = nextProps
|
||||
|
||||
if (!oldAddress || !newAddress) return
|
||||
if (oldAddress === newAddress) return
|
||||
|
||||
this.setState({ isLoading: true })
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
TokenBalance.prototype.updateBalance = function (tokens = []) {
|
||||
const [{ string }] = tokens
|
||||
this.setState({
|
||||
balance: string,
|
||||
isLoading: false,
|
||||
})
|
||||
}
|
||||
|
||||
TokenBalance.prototype.componentWillUnmount = function () {
|
||||
if (!this.tracker) return
|
||||
this.tracker.stop()
|
||||
}
|
||||
|
@ -1,10 +1,27 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const Identicon = require('./identicon')
|
||||
const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
|
||||
const selectors = require('../selectors')
|
||||
const actions = require('../actions')
|
||||
|
||||
module.exports = TokenCell
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
selectedTokenAddress: state.metamask.selectedTokenAddress,
|
||||
userAddress: selectors.getSelectedAddress(state),
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
setSelectedToken: address => dispatch(actions.setSelectedToken(address)),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenCell)
|
||||
|
||||
inherits(TokenCell, Component)
|
||||
function TokenCell () {
|
||||
@ -18,13 +35,17 @@ TokenCell.prototype.render = function () {
|
||||
symbol,
|
||||
string,
|
||||
network,
|
||||
setSelectedToken,
|
||||
selectedTokenAddress,
|
||||
// userAddress,
|
||||
} = props
|
||||
|
||||
return (
|
||||
h('div.token-list-item', {
|
||||
style: { cursor: network === '1' ? 'pointer' : 'default' },
|
||||
className: `token-list-item ${selectedTokenAddress ? 'token-list-item--active' : ''}`,
|
||||
// style: { cursor: network === '1' ? 'pointer' : 'default' },
|
||||
// onClick: this.view.bind(this, address, userAddress, network),
|
||||
onClick: () => setSelectedToken(address),
|
||||
}, [
|
||||
|
||||
h(Identicon, {
|
||||
|
@ -8,7 +8,6 @@ const connect = require('react-redux').connect
|
||||
const selectors = require('../selectors')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
tokens: state.metamask.tokens,
|
||||
@ -42,7 +41,6 @@ function TokenList () {
|
||||
TokenList.prototype.render = function () {
|
||||
const state = this.state
|
||||
const { tokens, isLoading, error } = state
|
||||
const { userAddress, network } = this.props
|
||||
|
||||
if (isLoading) {
|
||||
return this.message('Loading Tokens...')
|
||||
@ -53,13 +51,7 @@ TokenList.prototype.render = function () {
|
||||
return this.message('There was a problem loading your token balances.')
|
||||
}
|
||||
|
||||
const tokenViews = tokens.map((tokenData) => {
|
||||
tokenData.network = network
|
||||
tokenData.userAddress = userAddress
|
||||
return h(TokenCell, tokenData)
|
||||
})
|
||||
|
||||
return h('div', tokenViews)
|
||||
return h('div', tokens.map((tokenData) => h(TokenCell, tokenData)))
|
||||
}
|
||||
|
||||
TokenList.prototype.message = function (body) {
|
||||
|
@ -4,10 +4,12 @@ const h = require('react-hyperscript')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const inherits = require('util').inherits
|
||||
const actions = require('../actions')
|
||||
const selectors = require('../selectors')
|
||||
|
||||
const BalanceComponent = require('./balance-component')
|
||||
const TxList = require('./tx-list')
|
||||
const Identicon = require('./identicon')
|
||||
const TokenBalance = require('./token-balance')
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView)
|
||||
|
||||
@ -16,6 +18,7 @@ function mapStateToProps (state) {
|
||||
|
||||
const identities = state.metamask.identities
|
||||
const accounts = state.metamask.accounts
|
||||
const selectedTokenAddress = state.metamask.selectedTokenAddress
|
||||
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
|
||||
const checksumAddress = selectedAddress && ethUtil.toChecksumAddress(selectedAddress)
|
||||
const identity = identities[selectedAddress]
|
||||
@ -25,6 +28,8 @@ function mapStateToProps (state) {
|
||||
sidebarOpen,
|
||||
selectedAddress,
|
||||
checksumAddress,
|
||||
selectedTokenAddress,
|
||||
selectedToken: selectors.getSelectedToken(state),
|
||||
identity,
|
||||
account,
|
||||
}
|
||||
@ -44,9 +49,41 @@ function TxView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TxView.prototype.renderHeroBalance = function () {
|
||||
const {account, selectedToken, showModal, showSendPage } = this.props
|
||||
|
||||
return h('div.hero-balance', {}, [
|
||||
|
||||
h(BalanceComponent, {
|
||||
balanceValue: account && account.balance,
|
||||
token: selectedToken,
|
||||
}),
|
||||
|
||||
h('div.flex-row.flex-center.hero-balance-buttons', {}, [
|
||||
h('button.btn-clear', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
onClick: () => showModal({
|
||||
name: 'BUY',
|
||||
}),
|
||||
}, 'BUY'),
|
||||
|
||||
h('button.btn-clear', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
marginLeft: '0.8em',
|
||||
},
|
||||
onClick: showSendPage,
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
TxView.prototype.render = function () {
|
||||
|
||||
const { selectedAddress, identity, account } = this.props
|
||||
const { selectedAddress, identity } = this.props
|
||||
|
||||
return h('div.tx-view.flex-column', {
|
||||
style: {},
|
||||
@ -87,41 +124,7 @@ TxView.prototype.render = function () {
|
||||
|
||||
]),
|
||||
|
||||
h('div.hero-balance', {
|
||||
style: {},
|
||||
}, [
|
||||
|
||||
h(BalanceComponent, {
|
||||
balanceValue: account && account.balance,
|
||||
style: {},
|
||||
}),
|
||||
|
||||
h('div.flex-row.flex-center.hero-balance-buttons', {
|
||||
style: {},
|
||||
}, [
|
||||
h('button.btn-clear', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
onClick: () => {
|
||||
this.props.showModal({
|
||||
name: 'BUY',
|
||||
})
|
||||
},
|
||||
}, 'BUY'),
|
||||
|
||||
h('button.btn-clear', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
marginLeft: '0.8em',
|
||||
},
|
||||
onClick: () => {
|
||||
this.props.showSendPage()
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
]),
|
||||
this.renderHeroBalance(),
|
||||
|
||||
h(TxList, {}),
|
||||
|
||||
|
@ -22,6 +22,7 @@ function mapStateToProps (state) {
|
||||
selectedAddress: selectors.getSelectedAddress(state),
|
||||
selectedIdentity: selectors.getSelectedIdentity(state),
|
||||
selectedAccount: selectors.getSelectedAccount(state),
|
||||
selectedTokenAddress: state.metamask.selectedTokenAddress,
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +30,7 @@ function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
showSendPage: () => { dispatch(actions.showSendPage()) },
|
||||
hideSidebar: () => { dispatch(actions.hideSidebar()) },
|
||||
unsetSelectedToken: () => dispatch(actions.setSelectedToken()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,15 +39,26 @@ function WalletView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
WalletView.prototype.renderTokenBalances = function () {
|
||||
// const { tokens = [] } = this.props
|
||||
// return tokens.map(({ address, decimals, symbol }) => (
|
||||
// h(BalanceComponent, {
|
||||
// balanceValue: 0,
|
||||
// style: {},
|
||||
// })
|
||||
// ))
|
||||
return h(TokenList)
|
||||
WalletView.prototype.renderWalletBalance = function () {
|
||||
const { selectedTokenAddress, selectedAccount, unsetSelectedToken } = this.props
|
||||
const selectedClass = selectedTokenAddress
|
||||
? ''
|
||||
: 'wallet-balance-wrapper--active'
|
||||
const className = `flex-column wallet-balance-wrapper ${selectedClass}`
|
||||
|
||||
return h('div', { className }, [
|
||||
h('div.wallet-balance',
|
||||
{
|
||||
onClick: () => unsetSelectedToken(),
|
||||
},
|
||||
[
|
||||
h(BalanceComponent, {
|
||||
balanceValue: selectedAccount.balance,
|
||||
style: {},
|
||||
}),
|
||||
]
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
WalletView.prototype.render = function () {
|
||||
@ -139,22 +152,9 @@ WalletView.prototype.render = function () {
|
||||
]),
|
||||
]),
|
||||
|
||||
// Wallet Balances
|
||||
h('div.flex-column.wallet-balance-wrapper.wallet-balance-wrapper-active', {}, [
|
||||
this.renderWalletBalance(),
|
||||
|
||||
h('div.wallet-balance', {}, [
|
||||
|
||||
h(BalanceComponent, {
|
||||
balanceValue: selectedAccount.balance,
|
||||
style: {},
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
|
||||
]),
|
||||
|
||||
this.renderTokenBalances(),
|
||||
h(TokenList),
|
||||
|
||||
])
|
||||
}
|
||||
|
@ -1,9 +1,22 @@
|
||||
$wallet-balance-breakpoint: 890px;
|
||||
$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
|
||||
|
||||
.token-list-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
cursor: pointer;
|
||||
transition: linear 200ms;
|
||||
background-color: rgba($wallet-balance-bg, 0);
|
||||
|
||||
@media #{$wallet-balance-breakpoint-range} {
|
||||
padding: 10% 4%;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: rgba($wallet-balance-bg, 1);
|
||||
}
|
||||
|
||||
&__identicon {
|
||||
margin-right: 15px;
|
||||
|
@ -4,6 +4,12 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
|
||||
|
||||
.wallet-balance-wrapper {
|
||||
flex: 0 0 auto;
|
||||
transition: linear 200ms;
|
||||
background: rgba($wallet-balance-bg, 0);
|
||||
|
||||
&--active {
|
||||
background: rgba($wallet-balance-bg, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-balance {
|
||||
@ -62,7 +68,3 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
|
||||
border: 1px solid $alto;
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-balance-wrapper-active {
|
||||
background: $wallet-balance-bg;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ function reduceMetamask (state, action) {
|
||||
lastUnreadNotice: undefined,
|
||||
frequentRpcList: [],
|
||||
addressBook: [],
|
||||
selectedTokenAddress: null,
|
||||
}, state.metamask)
|
||||
|
||||
switch (action.type) {
|
||||
@ -115,6 +116,11 @@ function reduceMetamask (state, action) {
|
||||
delete newState.seedWords
|
||||
return newState
|
||||
|
||||
case actions.SET_SELECTED_TOKEN:
|
||||
return extend(metamaskState, {
|
||||
selectedTokenAddress: action.value,
|
||||
})
|
||||
|
||||
case actions.SAVE_ACCOUNT_LABEL:
|
||||
const account = action.value.account
|
||||
const name = action.value.label
|
||||
|
@ -4,6 +4,7 @@ const selectors = {
|
||||
getSelectedAddress,
|
||||
getSelectedIdentity,
|
||||
getSelectedAccount,
|
||||
getSelectedToken,
|
||||
conversionRateSelector,
|
||||
transactionsSelector,
|
||||
}
|
||||
@ -31,6 +32,14 @@ function getSelectedAccount (state) {
|
||||
return accounts[selectedAddress]
|
||||
}
|
||||
|
||||
function getSelectedToken (state) {
|
||||
const tokens = state.metamask.tokens || []
|
||||
const selectedTokenAddress = state.metamask.selectedTokenAddress
|
||||
const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
|
||||
|
||||
return selectedToken || null
|
||||
}
|
||||
|
||||
function conversionRateSelector (state) {
|
||||
return state.metamask.conversionRate
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user