1
0
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:
Chi Kei Chan 2017-09-06 03:17:49 -07:00
parent 690ddf5ed7
commit f1fb9e10a0
11 changed files with 256 additions and 81 deletions

View File

@ -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())

View File

@ -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 === '...') {

View 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()
}

View File

@ -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, {

View File

@ -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) {

View File

@ -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, {}),

View File

@ -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),
])
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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
}