aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components')
-rw-r--r--ui/app/components/balance-component.js43
-rw-r--r--ui/app/components/buy-button-subview.js267
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js4
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss8
-rw-r--r--ui/app/components/currency-display/currency-display.component.js26
-rw-r--r--ui/app/components/currency-display/currency-display.container.js19
-rw-r--r--ui/app/components/currency-display/index.js1
-rw-r--r--ui/app/components/currency-display/tests/currency-display.component.test.js27
-rw-r--r--ui/app/components/currency-display/tests/currency-display.container.test.js61
-rw-r--r--ui/app/components/custom-radio-list.js60
-rw-r--r--ui/app/components/identicon.js64
-rw-r--r--ui/app/components/index.scss24
-rw-r--r--ui/app/components/menu-bar/index.js1
-rw-r--r--ui/app/components/menu-bar/index.scss23
-rw-r--r--ui/app/components/menu-bar/menu-bar.component.js52
-rw-r--r--ui/app/components/menu-bar/menu-bar.container.js21
-rw-r--r--ui/app/components/modals/hide-token-confirmation-modal.js5
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js126
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js29
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/index.js2
-rw-r--r--ui/app/components/pages/confirm-add-token/confirm-add-token.component.js4
-rw-r--r--ui/app/components/pages/confirm-add-token/token-balance/index.js2
-rw-r--r--ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js16
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js11
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js4
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js3
-rw-r--r--ui/app/components/pages/home.js239
-rw-r--r--ui/app/components/pages/home/home.component.js77
-rw-r--r--ui/app/components/pages/home/home.container.js30
-rw-r--r--ui/app/components/pages/home/index.js1
-rw-r--r--ui/app/components/pending-msg-details.js56
-rw-r--r--ui/app/components/pending-msg.js73
-rw-r--r--ui/app/components/shift-list-item.js3
-rw-r--r--ui/app/components/token-balance.js120
-rw-r--r--ui/app/components/token-balance/index.js1
-rw-r--r--ui/app/components/token-balance/token-balance.component.js23
-rw-r--r--ui/app/components/token-balance/token-balance.container.js (renamed from ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js)4
-rw-r--r--ui/app/components/token-cell.js3
-rw-r--r--ui/app/components/token-currency-display/index.js1
-rw-r--r--ui/app/components/token-currency-display/token-currency-display.component.js54
-rw-r--r--ui/app/components/token-list.js9
-rw-r--r--ui/app/components/transaction-action/index.js1
-rw-r--r--ui/app/components/transaction-action/tests/transaction-action.component.test.js112
-rw-r--r--ui/app/components/transaction-action/transaction-action.component.js52
-rw-r--r--ui/app/components/transaction-list-item/index.js1
-rw-r--r--ui/app/components/transaction-list-item/index.scss117
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.component.js148
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js32
-rw-r--r--ui/app/components/transaction-list/index.js1
-rw-r--r--ui/app/components/transaction-list/index.scss46
-rw-r--r--ui/app/components/transaction-list/transaction-list.component.js117
-rw-r--r--ui/app/components/transaction-list/transaction-list.container.js50
-rw-r--r--ui/app/components/transaction-status/index.js1
-rw-r--r--ui/app/components/transaction-status/index.scss28
-rw-r--r--ui/app/components/transaction-status/transaction-status.component.js51
-rw-r--r--ui/app/components/transaction-view-balance/index.js1
-rw-r--r--ui/app/components/transaction-view-balance/index.scss76
-rw-r--r--ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js71
-rw-r--r--ui/app/components/transaction-view-balance/transaction-view-balance.component.js94
-rw-r--r--ui/app/components/transaction-view-balance/transaction-view-balance.container.js30
-rw-r--r--ui/app/components/transaction-view/index.js1
-rw-r--r--ui/app/components/transaction-view/index.scss27
-rw-r--r--ui/app/components/transaction-view/transaction-view.component.js27
-rw-r--r--ui/app/components/tx-list-item.js356
-rw-r--r--ui/app/components/tx-list.js171
-rw-r--r--ui/app/components/tx-view.js156
-rw-r--r--ui/app/components/wallet-view.js7
67 files changed, 1764 insertions, 1607 deletions
diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js
index e31552f2d..753a27b06 100644
--- a/ui/app/components/balance-component.js
+++ b/ui/app/components/balance-component.js
@@ -4,8 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenBalance = require('./token-balance')
const Identicon = require('./identicon')
-const currencyFormatter = require('currency-formatter')
-const currencies = require('currency-formatter/currencies')
+import CurrencyDisplay from './currency-display'
const { formatBalance, generateBalanceObject } = require('../util')
@@ -22,6 +21,7 @@ function mapStateToProps (state) {
network,
conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency,
+ assetImages: state.metamask.assetImages,
}
}
@@ -32,7 +32,9 @@ function BalanceComponent () {
BalanceComponent.prototype.render = function () {
const props = this.props
- const { token, network } = props
+ const { token, network, assetImages } = props
+ const address = token && token.address
+ const image = assetImages && address ? assetImages[token.address] : undefined
return h('div.balance-container', {}, [
@@ -43,8 +45,9 @@ BalanceComponent.prototype.render = function () {
// }),
h(Identicon, {
diameter: 50,
- address: token && token.address,
+ address,
network,
+ image,
}),
token ? this.renderTokenBalance() : this.renderBalance(),
@@ -80,38 +83,12 @@ BalanceComponent.prototype.renderBalance = function () {
style: {},
}, this.getTokenBalance(formattedBalance, shorten)),
- showFiat ? this.renderFiatValue(formattedBalance) : null,
+ showFiat && h(CurrencyDisplay, {
+ value: balanceValue,
+ }),
])
}
-BalanceComponent.prototype.renderFiatValue = function (formattedBalance) {
-
- const { conversionRate, currentCurrency } = this.props
-
- const fiatDisplayNumber = this.getFiatDisplayNumber(formattedBalance, conversionRate)
-
- const fiatPrefix = currentCurrency === 'USD' ? '$' : ''
-
- return this.renderFiatAmount(fiatDisplayNumber, currentCurrency, fiatPrefix)
-}
-
-BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) {
- const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
- if (shouldNotRenderFiat) return null
-
- const upperCaseFiatSuffix = fiatSuffix.toUpperCase()
-
- const display = currencies.find(currency => currency.code === upperCaseFiatSuffix)
- ? currencyFormatter.format(Number(fiatDisplayNumber), {
- code: upperCaseFiatSuffix,
- })
- : `${fiatPrefix}${fiatDisplayNumber} ${upperCaseFiatSuffix}`
-
- return h('div.fiat-amount', {
- style: {},
- }, display)
-}
-
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
deleted file mode 100644
index c6957d2aa..000000000
--- a/ui/app/components/buy-button-subview.js
+++ /dev/null
@@ -1,267 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const connect = require('react-redux').connect
-const actions = require('../actions')
-const CoinbaseForm = require('./coinbase-form')
-const ShapeshiftForm = require('./shapeshift-form')
-const Loading = require('./loading-screen')
-const AccountPanel = require('./account-panel')
-const RadioList = require('./custom-radio-list')
-const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util')
-
-BuyButtonSubview.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(BuyButtonSubview)
-
-
-function mapStateToProps (state) {
- return {
- identity: state.appState.identity,
- account: state.metamask.accounts[state.appState.buyView.buyAddress],
- warning: state.appState.warning,
- buyView: state.appState.buyView,
- network: state.metamask.network,
- provider: state.metamask.provider,
- context: state.appState.currentView.context,
- isSubLoading: state.appState.isSubLoading,
- }
-}
-
-inherits(BuyButtonSubview, Component)
-function BuyButtonSubview () {
- Component.call(this)
-}
-
-BuyButtonSubview.prototype.render = function () {
- return (
- h('div', {
- style: {
- width: '100%',
- },
- }, [
- this.headerSubview(),
- this.primarySubview(),
- ])
- )
-}
-
-BuyButtonSubview.prototype.headerSubview = function () {
- const props = this.props
- const isLoading = props.isSubLoading
- return (
-
- h('.flex-column', {
- style: {
- alignItems: 'center',
- },
- }, [
-
- // header bar (back button, label)
- h('.flex-row', {
- style: {
- alignItems: 'center',
- justifyContent: 'center',
- },
- }, [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
- onClick: this.backButtonContext.bind(this),
- style: {
- position: 'absolute',
- left: '10px',
- },
- }),
- h('h2.text-transform-uppercase.flex-center', {
- style: {
- width: '100vw',
- background: 'rgb(235, 235, 235)',
- color: 'rgb(174, 174, 174)',
- paddingTop: '4px',
- paddingBottom: '4px',
- },
- }, this.context.t('depositEth')),
- ]),
-
- // loading indication
- h('div', {
- style: {
- position: 'absolute',
- top: '57vh',
- left: '49vw',
- },
- }, [
- isLoading && h(Loading),
- ]),
-
- // account panel
- h('div', {
- style: {
- width: '80%',
- },
- }, [
- h(AccountPanel, {
- showFullAddress: true,
- identity: props.identity,
- account: props.account,
- }),
- ]),
-
- h('.flex-row', {
- style: {
- alignItems: 'center',
- justifyContent: 'center',
- },
- }, [
- h('h3.text-transform-uppercase.flex-center', {
- style: {
- paddingLeft: '15px',
- width: '100vw',
- background: 'rgb(235, 235, 235)',
- color: 'rgb(174, 174, 174)',
- paddingTop: '4px',
- paddingBottom: '4px',
- },
- }, this.context.t('selectService')),
- ]),
-
- ])
-
- )
-}
-
-
-BuyButtonSubview.prototype.primarySubview = function () {
- const props = this.props
- const network = props.network
-
- switch (network) {
- case 'loading':
- return
-
- case '1':
- return this.mainnetSubview()
-
- // Ropsten, Rinkeby, Kovan
- case '3':
- case '4':
- case '42':
- const networkName = getNetworkDisplayName(network)
- const label = `${networkName} ${this.context.t('testFaucet')}`
- return (
- h('div.flex-column', {
- style: {
- alignItems: 'center',
- margin: '20px 50px',
- },
- }, [
- h('button.text-transform-uppercase', {
- onClick: () => this.props.dispatch(actions.buyEth({ network })),
- style: {
- marginTop: '15px',
- },
- }, label),
- // Kovan only: Dharma loans beta
- network === '42' ? (
- h('button.text-transform-uppercase', {
- onClick: () => this.navigateTo('https://borrow.dharma.io/'),
- style: {
- marginTop: '15px',
- },
- }, this.context.t('borrowDharma'))
- ) : null,
- ])
- )
-
- default:
- return (
- h('h2.error', this.context.t('unknownNetworkId'))
- )
-
- }
-}
-
-BuyButtonSubview.prototype.mainnetSubview = function () {
- const props = this.props
-
- return (
-
- h('.flex-column', {
- style: {
- alignItems: 'center',
- },
- }, [
-
- h('.flex-row.selected-exchange', {
- style: {
- position: 'relative',
- right: '35px',
- marginTop: '20px',
- marginBottom: '20px',
- },
- }, [
- h(RadioList, {
- defaultFocus: props.buyView.subview,
- labels: [
- 'Coinbase',
- 'ShapeShift',
- ],
- subtext: {
- 'Coinbase': `${this.context.t('crypto')}/${this.context.t('fiat')} (${this.context.t('usaOnly')})`,
- 'ShapeShift': this.context.t('crypto'),
- },
- onClick: this.radioHandler.bind(this),
- }),
- ]),
-
- h('h3.text-transform-uppercase', {
- style: {
- paddingLeft: '15px',
- fontFamily: 'Montserrat Light',
- width: '100vw',
- background: 'rgb(235, 235, 235)',
- color: 'rgb(174, 174, 174)',
- paddingTop: '4px',
- paddingBottom: '4px',
- },
- }, props.buyView.subview),
-
- this.formVersionSubview(),
- ])
-
- )
-}
-
-BuyButtonSubview.prototype.formVersionSubview = function () {
- const network = this.props.network
- if (network === '1') {
- if (this.props.buyView.formView.coinbase) {
- return h(CoinbaseForm, this.props)
- } else if (this.props.buyView.formView.shapeshift) {
- return h(ShapeshiftForm, this.props)
- }
- }
-}
-
-BuyButtonSubview.prototype.navigateTo = function (url) {
- global.platform.openWindow({ url })
-}
-
-BuyButtonSubview.prototype.backButtonContext = function () {
- if (this.props.context === 'confTx') {
- this.props.dispatch(actions.showConfTxPage({transForward: false}))
- } else {
- this.props.dispatch(actions.goHome())
- }
-}
-
-BuyButtonSubview.prototype.radioHandler = function (event) {
- switch (event.target.title) {
- case 'Coinbase':
- return this.props.dispatch(actions.coinBaseSubview())
- case 'ShapeShift':
- return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type))
- }
-}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js
index 70ebdeb20..4965d7b4e 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/confirm-page-container-error.component.js
@@ -11,7 +11,9 @@ const ConfirmPageContainerError = (props, context) => {
src="/images/alert-red.svg"
className="confirm-page-container-error__icon"
/>
- { `ALERT: ${error}` }
+ <div className="confirm-page-container-error__text">
+ { `ALERT: ${error}` }
+ </div>
</div>
)
}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss
index e99b0f631..89ff25578 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-error/index.scss
@@ -1,5 +1,5 @@
.confirm-page-container-error {
- height: 32px;
+ min-height: 32px;
border: 1px solid $monzo;
color: $monzo;
background: lighten($monzo, 56%);
@@ -8,10 +8,14 @@
display: flex;
justify-content: flex-start;
align-items: center;
- padding-left: 16px;
+ padding: 8px 16px;
&__icon {
margin-right: 8px;
flex: 0 0 auto;
}
+
+ &__text {
+ overflow: auto;
+ }
}
diff --git a/ui/app/components/currency-display/currency-display.component.js b/ui/app/components/currency-display/currency-display.component.js
new file mode 100644
index 000000000..389791b42
--- /dev/null
+++ b/ui/app/components/currency-display/currency-display.component.js
@@ -0,0 +1,26 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { ETH } from '../../constants/common'
+
+export default class CurrencyDisplay extends PureComponent {
+ static propTypes = {
+ className: PropTypes.string,
+ displayValue: PropTypes.string,
+ prefix: PropTypes.string,
+ currency: PropTypes.oneOf([ETH]),
+ }
+
+ render () {
+ const { className, displayValue, prefix } = this.props
+ const text = `${prefix || ''}${displayValue}`
+
+ return (
+ <div
+ className={className}
+ title={text}
+ >
+ { text }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/currency-display/currency-display.container.js b/ui/app/components/currency-display/currency-display.container.js
new file mode 100644
index 000000000..b8a738c65
--- /dev/null
+++ b/ui/app/components/currency-display/currency-display.container.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import CurrencyDisplay from './currency-display.component'
+import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
+
+const mapStateToProps = (state, ownProps) => {
+ const { value, numberOfDecimals = 2, currency } = ownProps
+ const { metamask: { currentCurrency, conversionRate } } = state
+
+ const toCurrency = currency || currentCurrency
+ const convertedValue = getValueFromWeiHex({ value, toCurrency, conversionRate, numberOfDecimals })
+ const formattedValue = formatCurrency(convertedValue, toCurrency)
+ const displayValue = `${formattedValue} ${toCurrency.toUpperCase()}`
+
+ return {
+ displayValue,
+ }
+}
+
+export default connect(mapStateToProps)(CurrencyDisplay)
diff --git a/ui/app/components/currency-display/index.js b/ui/app/components/currency-display/index.js
new file mode 100644
index 000000000..38f08765f
--- /dev/null
+++ b/ui/app/components/currency-display/index.js
@@ -0,0 +1 @@
+export { default } from './currency-display.container'
diff --git a/ui/app/components/currency-display/tests/currency-display.component.test.js b/ui/app/components/currency-display/tests/currency-display.component.test.js
new file mode 100644
index 000000000..d9ef052f1
--- /dev/null
+++ b/ui/app/components/currency-display/tests/currency-display.component.test.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import CurrencyDisplay from '../currency-display.component'
+
+describe('CurrencyDisplay Component', () => {
+ it('should render text with a className', () => {
+ const wrapper = shallow(<CurrencyDisplay
+ displayValue="$123.45"
+ className="currency-display"
+ />)
+
+ assert.ok(wrapper.hasClass('currency-display'))
+ assert.equal(wrapper.text(), '$123.45')
+ })
+
+ it('should render text with a prefix', () => {
+ const wrapper = shallow(<CurrencyDisplay
+ displayValue="$123.45"
+ className="currency-display"
+ prefix="-"
+ />)
+
+ assert.ok(wrapper.hasClass('currency-display'))
+ assert.equal(wrapper.text(), '-$123.45')
+ })
+})
diff --git a/ui/app/components/currency-display/tests/currency-display.container.test.js b/ui/app/components/currency-display/tests/currency-display.container.test.js
new file mode 100644
index 000000000..474ce5378
--- /dev/null
+++ b/ui/app/components/currency-display/tests/currency-display.container.test.js
@@ -0,0 +1,61 @@
+import assert from 'assert'
+import proxyquire from 'proxyquire'
+
+let mapStateToProps
+
+proxyquire('../currency-display.container.js', {
+ 'react-redux': {
+ connect: ms => {
+ mapStateToProps = ms
+ return () => ({})
+ },
+ },
+})
+
+describe('CurrencyDisplay container', () => {
+ describe('mapStateToProps()', () => {
+ it('should return the correct props', () => {
+ const mockState = {
+ metamask: {
+ conversionRate: 280.45,
+ currentCurrency: 'usd',
+ },
+ }
+
+ const tests = [
+ {
+ props: {
+ value: '0x2386f26fc10000',
+ numberOfDecimals: 2,
+ currency: 'usd',
+ },
+ result: {
+ displayValue: '$2.80 USD',
+ },
+ },
+ {
+ props: {
+ value: '0x2386f26fc10000',
+ },
+ result: {
+ displayValue: '$2.80 USD',
+ },
+ },
+ {
+ props: {
+ value: '0x1193461d01595930',
+ currency: 'ETH',
+ numberOfDecimals: 3,
+ },
+ result: {
+ displayValue: '1.266 ETH',
+ },
+ },
+ ]
+
+ tests.forEach(({ props, result }) => {
+ assert.deepEqual(mapStateToProps(mockState, props), result)
+ })
+ })
+ })
+})
diff --git a/ui/app/components/custom-radio-list.js b/ui/app/components/custom-radio-list.js
deleted file mode 100644
index a4c525396..000000000
--- a/ui/app/components/custom-radio-list.js
+++ /dev/null
@@ -1,60 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-
-module.exports = RadioList
-
-inherits(RadioList, Component)
-function RadioList () {
- Component.call(this)
-}
-
-RadioList.prototype.render = function () {
- const props = this.props
- const activeClass = '.custom-radio-selected'
- const inactiveClass = '.custom-radio-inactive'
- const {
- labels,
- defaultFocus,
- } = props
-
-
- return (
- h('.flex-row', {
- style: {
- fontSize: '12px',
- },
- }, [
- h('.flex-column.custom-radios', {
- style: {
- marginRight: '5px',
- },
- },
- labels.map((lable, i) => {
- let isSelcted = (this.state !== null)
- isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable)
- return h(isSelcted ? activeClass : inactiveClass, {
- title: lable,
- onClick: (event) => {
- this.setState({selected: event.target.title})
- props.onClick(event)
- },
- })
- })
- ),
- h('.text', {},
- labels.map((lable) => {
- if (props.subtext) {
- return h('.flex-row', {}, [
- h('.radio-titles', lable),
- h('.radio-titles-subtext', `- ${props.subtext[lable]}`),
- ])
- } else {
- return h('.radio-titles', lable)
- }
- })
- ),
- ])
- )
-}
-
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
index 424048745..076e65b81 100644
--- a/ui/app/components/identicon.js
+++ b/ui/app/components/identicon.js
@@ -26,36 +26,42 @@ function mapStateToProps (state) {
IdenticonComponent.prototype.render = function () {
var props = this.props
- const { className = '', address } = props
+ const { className = '', address, image } = props
var diameter = props.diameter || this.defaultDiameter
-
- return address
- ? (
- h('div', {
- className: `${className} identicon`,
- key: 'identicon-' + address,
- style: {
- display: 'flex',
- flexShrink: 0,
- alignItems: 'center',
- justifyContent: 'center',
- height: diameter,
- width: diameter,
- borderRadius: diameter / 2,
- overflow: 'hidden',
- },
- })
- )
- : (
- h('img.balance-icon', {
- src: './images/eth_logo.svg',
- style: {
- height: diameter,
- width: diameter,
- borderRadius: diameter / 2,
- },
- })
- )
+ const style = {
+ height: diameter,
+ width: diameter,
+ borderRadius: diameter / 2,
+ }
+ if (image) {
+ return h('img', {
+ className: `${className} identicon`,
+ src: image,
+ style: {
+ ...style,
+ },
+ })
+ } else if (address) {
+ return h('div', {
+ className: `${className} identicon`,
+ key: 'identicon-' + address,
+ style: {
+ display: 'flex',
+ flexShrink: 0,
+ alignItems: 'center',
+ justifyContent: 'center',
+ ...style,
+ overflow: 'hidden',
+ },
+ })
+ } else {
+ return h('img.balance-icon', {
+ src: './images/eth_logo.svg',
+ style: {
+ ...style,
+ },
+ })
+ }
}
IdenticonComponent.prototype.componentDidMount = function () {
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 35d38e2a3..bdcb5626c 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -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 './transaction-view/index';
+
+@import './transaction-view-balance/index';
+
+@import './transaction-list/index';
+
+@import './transaction-list-item/index';
+
+@import './transaction-status/index';
diff --git a/ui/app/components/menu-bar/index.js b/ui/app/components/menu-bar/index.js
new file mode 100644
index 000000000..c5760847f
--- /dev/null
+++ b/ui/app/components/menu-bar/index.js
@@ -0,0 +1 @@
+export { default } from './menu-bar.container'
diff --git a/ui/app/components/menu-bar/index.scss b/ui/app/components/menu-bar/index.scss
new file mode 100644
index 000000000..f699f4090
--- /dev/null
+++ b/ui/app/components/menu-bar/index.scss
@@ -0,0 +1,23 @@
+.menu-bar {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ flex: 0 0 auto;
+ margin-bottom: 16px;
+ padding: 5px;
+ border-bottom: 1px solid #e5e5e5;
+
+ &__sidebar-button {
+ font-size: 1.25rem;
+ cursor: pointer;
+ padding: 10px;
+ }
+
+ &__open-in-browser {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ padding: 10px;
+ }
+}
diff --git a/ui/app/components/menu-bar/menu-bar.component.js b/ui/app/components/menu-bar/menu-bar.component.js
new file mode 100644
index 000000000..eee9feebb
--- /dev/null
+++ b/ui/app/components/menu-bar/menu-bar.component.js
@@ -0,0 +1,52 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Tooltip from '../tooltip'
+import SelectedAccount from '../selected-account'
+
+export default class MenuBar extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ hideSidebar: PropTypes.func,
+ isMascara: PropTypes.bool,
+ sidebarOpen: PropTypes.bool,
+ showSidebar: PropTypes.func,
+ }
+
+ render () {
+ const { t } = this.context
+ const { isMascara, sidebarOpen, hideSidebar, showSidebar } = this.props
+
+ return (
+ <div className="menu-bar">
+ <Tooltip
+ title={t('menu')}
+ position="bottom"
+ >
+ <div
+ className="fa fa-bars menu-bar__sidebar-button"
+ onClick={() => sidebarOpen ? hideSidebar() : showSidebar()}
+ />
+ </Tooltip>
+ <SelectedAccount />
+ {
+ !isMascara && (
+ <Tooltip
+ title={t('openInTab')}
+ position="bottom"
+ >
+ <div
+ className="menu-bar__open-in-browser"
+ onClick={() => global.platform.openExtensionInBrowser()}
+ >
+ <img src="images/popout.svg" />
+ </div>
+ </Tooltip>
+ )
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/menu-bar/menu-bar.container.js b/ui/app/components/menu-bar/menu-bar.container.js
new file mode 100644
index 000000000..2bd0ed6ed
--- /dev/null
+++ b/ui/app/components/menu-bar/menu-bar.container.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import MenuBar from './menu-bar.component'
+import { showSidebar, hideSidebar } from '../../actions'
+
+const mapStateToProps = state => {
+ const { appState: { sidebarOpen, isMascara } } = state
+
+ return {
+ sidebarOpen,
+ isMascara,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ showSidebar: () => dispatch(showSidebar()),
+ hideSidebar: () => dispatch(hideSidebar()),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(MenuBar)
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js
index 1518fa9a0..fb38516d3 100644
--- a/ui/app/components/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/modals/hide-token-confirmation-modal.js
@@ -10,6 +10,7 @@ function mapStateToProps (state) {
return {
network: state.metamask.network,
token: state.appState.modal.modalState.props.token,
+ assetImages: state.metamask.assetImages,
}
}
@@ -40,8 +41,9 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmat
HideTokenConfirmationModal.prototype.render = function () {
- const { token, network, hideToken, hideModal } = this.props
+ const { token, network, hideToken, hideModal, assetImages } = this.props
const { symbol, address } = token
+ const image = assetImages[address]
return h('div.hide-token-confirmation', {}, [
h('div.hide-token-confirmation__container', {
@@ -55,6 +57,7 @@ HideTokenConfirmationModal.prototype.render = function () {
diameter: 45,
address,
network,
+ image,
}),
h('div.hide-token-confirmation__symbol', {}, symbol),
diff --git a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
new file mode 100644
index 000000000..c24e1e0ea
--- /dev/null
+++ b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
@@ -0,0 +1,126 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { DEFAULT_ROUTE } from '../../../routes'
+import Button from '../../button'
+import Identicon from '../../../components/identicon'
+import TokenBalance from '../../token-balance'
+
+export default class ConfirmAddSuggestedToken extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ history: PropTypes.object,
+ clearPendingTokens: PropTypes.func,
+ addToken: PropTypes.func,
+ pendingTokens: PropTypes.object,
+ removeSuggestedTokens: PropTypes.func,
+ }
+
+ componentDidMount () {
+ const { pendingTokens = {}, history } = this.props
+
+ if (Object.keys(pendingTokens).length === 0) {
+ history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ getTokenName (name, symbol) {
+ return typeof name === 'undefined'
+ ? symbol
+ : `${name} (${symbol})`
+ }
+
+ render () {
+ const { addToken, pendingTokens, removeSuggestedTokens, history } = this.props
+ const pendingTokenKey = Object.keys(pendingTokens)[0]
+ const pendingToken = pendingTokens[pendingTokenKey]
+
+ return (
+ <div className="page-container">
+ <div className="page-container__header">
+ <div className="page-container__title">
+ { this.context.t('addSuggestedTokens') }
+ </div>
+ <div className="page-container__subtitle">
+ { this.context.t('likeToAddTokens') }
+ </div>
+ </div>
+ <div className="page-container__content">
+ <div className="confirm-add-token">
+ <div className="confirm-add-token__header">
+ <div className="confirm-add-token__token">
+ { this.context.t('token') }
+ </div>
+ <div className="confirm-add-token__balance">
+ { this.context.t('balance') }
+ </div>
+ </div>
+ <div className="confirm-add-token__token-list">
+ {
+ Object.entries(pendingTokens)
+ .map(([ address, token ]) => {
+ const { name, symbol, image } = token
+
+ return (
+ <div
+ className="confirm-add-token__token-list-item"
+ key={address}
+ >
+ <div className="confirm-add-token__token confirm-add-token__data">
+ <Identicon
+ className="confirm-add-token__token-icon"
+ diameter={48}
+ address={address}
+ image={image}
+ />
+ <div className="confirm-add-token__name">
+ { this.getTokenName(name, symbol) }
+ </div>
+ </div>
+ <div className="confirm-add-token__balance">
+ <TokenBalance token={token} />
+ </div>
+ </div>
+ )
+ })
+ }
+ </div>
+ </div>
+ </div>
+ <div className="page-container__footer">
+ <Button
+ type="default"
+ large
+ className="page-container__footer-button"
+ onClick={() => {
+ removeSuggestedTokens()
+ .then(() => {
+ history.push(DEFAULT_ROUTE)
+ })
+ }}
+ >
+ { this.context.t('cancel') }
+ </Button>
+ <Button
+ type="primary"
+ large
+ className="page-container__footer-button"
+ onClick={() => {
+ addToken(pendingToken)
+ .then(() => {
+ removeSuggestedTokens()
+ .then(() => {
+ history.push(DEFAULT_ROUTE)
+ })
+ })
+ }}
+ >
+ { this.context.t('addToken') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
new file mode 100644
index 000000000..1f2737e52
--- /dev/null
+++ b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
@@ -0,0 +1,29 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component'
+import { withRouter } from 'react-router-dom'
+
+const extend = require('xtend')
+
+const { addToken, removeSuggestedTokens } = require('../../../actions')
+
+const mapStateToProps = ({ metamask }) => {
+ const { pendingTokens, suggestedTokens } = metamask
+ const params = extend(pendingTokens, suggestedTokens)
+
+ return {
+ pendingTokens: params,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)),
+ removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(ConfirmAddSuggestedToken)
diff --git a/ui/app/components/pages/confirm-add-suggested-token/index.js b/ui/app/components/pages/confirm-add-suggested-token/index.js
new file mode 100644
index 000000000..2ca56b43c
--- /dev/null
+++ b/ui/app/components/pages/confirm-add-suggested-token/index.js
@@ -0,0 +1,2 @@
+import ConfirmAddSuggestedToken from './confirm-add-suggested-token.container'
+module.exports = ConfirmAddSuggestedToken
diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
index 65d654b92..3dcc8cda9 100644
--- a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
+++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
@@ -2,8 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { DEFAULT_ROUTE, ADD_TOKEN_ROUTE } from '../../../routes'
import Button from '../../button'
-import Identicon from '../../../components/identicon'
-import TokenBalance from './token-balance'
+import Identicon from '../../identicon'
+import TokenBalance from '../../token-balance'
export default class ConfirmAddToken extends Component {
static contextTypes = {
diff --git a/ui/app/components/pages/confirm-add-token/token-balance/index.js b/ui/app/components/pages/confirm-add-token/token-balance/index.js
deleted file mode 100644
index 6fb5c8223..000000000
--- a/ui/app/components/pages/confirm-add-token/token-balance/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import TokenBalance from './token-balance.container'
-module.exports = TokenBalance
diff --git a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js
deleted file mode 100644
index 976788d4c..000000000
--- a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-export default class TokenBalance extends Component {
- static propTypes = {
- string: PropTypes.string,
- symbol: PropTypes.string,
- error: PropTypes.string,
- }
-
- render () {
- return (
- <div className="hide-text-overflow">{ this.props.string }</div>
- )
- }
-}
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 961aa304e..3216d01c3 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -73,6 +73,7 @@ export default class ConfirmTransactionBase extends Component {
state = {
submitting: false,
+ submitError: null,
}
componentDidUpdate () {
@@ -268,7 +269,7 @@ export default class ConfirmTransactionBase extends Component {
return
}
- this.setState({ submitting: true })
+ this.setState({ submitting: true, submitError: null })
if (onSubmit) {
Promise.resolve(onSubmit(txData))
@@ -280,7 +281,9 @@ export default class ConfirmTransactionBase extends Component {
this.setState({ submitting: false })
history.push(DEFAULT_ROUTE)
})
- .catch(() => this.setState({ submitting: false }))
+ .catch(error => {
+ this.setState({ submitting: false, submitError: error.message })
+ })
}
}
@@ -309,7 +312,7 @@ export default class ConfirmTransactionBase extends Component {
nonce,
warning,
} = this.props
- const { submitting } = this.state
+ const { submitting, submitError } = this.state
const { name } = methodData
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
@@ -332,7 +335,7 @@ export default class ConfirmTransactionBase extends Component {
contentComponent={contentComponent}
nonce={nonce}
identiconAddress={identiconAddress}
- errorMessage={errorMessage}
+ errorMessage={errorMessage || submitError}
errorKey={propsErrorKey || errorKey}
warning={warning}
disabled={!propsValid || !valid || submitting}
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
index d494977cd..2c44b6094 100644
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
@@ -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 = {
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
deleted file mode 100644
index 9db4a2f96..000000000
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.constants.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const TOKEN_METHOD_TRANSFER = 'transfer'
-export const TOKEN_METHOD_APPROVE = 'approve'
-export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'
diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js
deleted file mode 100644
index 5e3fdc9af..000000000
--- a/ui/app/components/pages/home.js
+++ /dev/null
@@ -1,239 +0,0 @@
-const { Component } = require('react')
-const { connect } = require('react-redux')
-const PropTypes = require('prop-types')
-const { Redirect, withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const h = require('react-hyperscript')
-const actions = require('../../actions')
-const log = require('loglevel')
-
-// init
-const NewKeyChainScreen = require('../../new-keychain')
-// mascara
-const MascaraBuyEtherScreen = require('../../../../mascara/src/app/first-time/buy-ether-screen').default
-
-// accounts
-const MainContainer = require('../../main-container')
-
-// other views
-const BuyView = require('../../components/buy-button-subview')
-const QrView = require('../../components/qr-code')
-
-// Routes
-const {
- INITIALIZE_BACKUP_PHRASE_ROUTE,
- RESTORE_VAULT_ROUTE,
- CONFIRM_TRANSACTION_ROUTE,
- NOTICE_ROUTE,
-} = require('../../routes')
-
-const { unconfirmedTransactionsCountSelector } = require('../../selectors/confirm-transaction')
-
-class Home extends Component {
- componentDidMount () {
- const {
- history,
- unconfirmedTransactionsCount = 0,
- } = this.props
-
- // unapprovedTxs and unapproved messages
- if (unconfirmedTransactionsCount > 0) {
- history.push(CONFIRM_TRANSACTION_ROUTE)
- }
- }
-
- render () {
- log.debug('rendering primary')
- const {
- noActiveNotices,
- lostAccounts,
- forgottenPassword,
- currentView,
- activeAddress,
- seedWords,
- } = this.props
-
- // notices
- if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) {
- return h(Redirect, {
- to: {
- pathname: NOTICE_ROUTE,
- },
- })
- }
-
- // seed words
- if (seedWords) {
- log.debug('rendering seed words')
- return h(Redirect, {
- to: {
- pathname: INITIALIZE_BACKUP_PHRASE_ROUTE,
- },
- })
- }
-
- if (forgottenPassword) {
- log.debug('rendering restore vault screen')
- return h(Redirect, {
- to: {
- pathname: RESTORE_VAULT_ROUTE,
- },
- })
- }
-
- // show current view
- switch (currentView.name) {
-
- case 'accountDetail':
- log.debug('rendering main container')
- return h(MainContainer, {key: 'account-detail'})
-
- case 'newKeychain':
- log.debug('rendering new keychain screen')
- return h(NewKeyChainScreen, {key: 'new-keychain'})
-
- case 'buyEth':
- log.debug('rendering buy ether screen')
- return h(BuyView, {key: 'buyEthView'})
-
- case 'onboardingBuyEth':
- log.debug('rendering onboarding buy ether screen')
- return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
-
- case 'qr':
- log.debug('rendering show qr screen')
- return h('div', {
- style: {
- position: 'absolute',
- height: '100%',
- top: '0px',
- left: '0px',
- },
- }, [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
- onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)),
- style: {
- marginLeft: '10px',
- marginTop: '50px',
- },
- }),
- h('div', {
- style: {
- position: 'absolute',
- left: '44px',
- width: '285px',
- },
- }, [
- h(QrView, {key: 'qr'}),
- ]),
- ])
-
- default:
- log.debug('rendering default, account detail screen')
- return h(MainContainer, {key: 'account-detail'})
- }
- }
-}
-
-Home.propTypes = {
- currentCurrency: PropTypes.string,
- isLoading: PropTypes.bool,
- loadingMessage: PropTypes.string,
- network: PropTypes.string,
- provider: PropTypes.object,
- frequentRpcList: PropTypes.array,
- currentView: PropTypes.object,
- sidebarOpen: PropTypes.bool,
- isMascara: PropTypes.bool,
- isOnboarding: PropTypes.bool,
- isUnlocked: PropTypes.bool,
- networkDropdownOpen: PropTypes.bool,
- history: PropTypes.object,
- dispatch: PropTypes.func,
- selectedAddress: PropTypes.string,
- noActiveNotices: PropTypes.bool,
- lostAccounts: PropTypes.array,
- isInitialized: PropTypes.bool,
- forgottenPassword: PropTypes.bool,
- activeAddress: PropTypes.string,
- unapprovedTxs: PropTypes.object,
- seedWords: PropTypes.string,
- unapprovedMsgCount: PropTypes.number,
- unapprovedPersonalMsgCount: PropTypes.number,
- unapprovedTypedMessagesCount: PropTypes.number,
- welcomeScreenSeen: PropTypes.bool,
- isPopup: PropTypes.bool,
- isMouseUser: PropTypes.bool,
- t: PropTypes.func,
- unconfirmedTransactionsCount: PropTypes.number,
-}
-
-function mapStateToProps (state) {
- const { appState, metamask } = state
- const {
- networkDropdownOpen,
- sidebarOpen,
- isLoading,
- loadingMessage,
- } = appState
-
- const {
- accounts,
- address,
- isInitialized,
- noActiveNotices,
- seedWords,
- unapprovedTxs,
- nextUnreadNotice,
- lostAccounts,
- unapprovedMsgCount,
- unapprovedPersonalMsgCount,
- unapprovedTypedMessagesCount,
- } = metamask
- const selected = address || Object.keys(accounts)[0]
-
- return {
- // state from plugin
- networkDropdownOpen,
- sidebarOpen,
- isLoading,
- loadingMessage,
- noActiveNotices,
- isInitialized,
- isUnlocked: state.metamask.isUnlocked,
- selectedAddress: state.metamask.selectedAddress,
- currentView: state.appState.currentView,
- activeAddress: state.appState.activeAddress,
- transForward: state.appState.transForward,
- isMascara: state.metamask.isMascara,
- isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
- isPopup: state.metamask.isPopup,
- seedWords: state.metamask.seedWords,
- unapprovedTxs,
- unapprovedMsgs: state.metamask.unapprovedMsgs,
- unapprovedMsgCount,
- unapprovedPersonalMsgCount,
- unapprovedTypedMessagesCount,
- menuOpen: state.appState.menuOpen,
- network: state.metamask.network,
- provider: state.metamask.provider,
- forgottenPassword: state.appState.forgottenPassword,
- nextUnreadNotice,
- lostAccounts,
- frequentRpcList: state.metamask.frequentRpcList || [],
- currentCurrency: state.metamask.currentCurrency,
- isMouseUser: state.appState.isMouseUser,
- isRevealingSeedWords: state.metamask.isRevealingSeedWords,
- Qr: state.appState.Qr,
- welcomeScreenSeen: state.metamask.welcomeScreenSeen,
-
- // state needed to get account dropdown temporarily rendering from app bar
- selected,
- unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
- }
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps)
-)(Home)
diff --git a/ui/app/components/pages/home/home.component.js b/ui/app/components/pages/home/home.component.js
new file mode 100644
index 000000000..d3c71c4f6
--- /dev/null
+++ b/ui/app/components/pages/home/home.component.js
@@ -0,0 +1,77 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Media from 'react-media'
+import { Redirect } from 'react-router-dom'
+import WalletView from '../../wallet-view'
+import TransactionView from '../../transaction-view'
+import {
+ INITIALIZE_BACKUP_PHRASE_ROUTE,
+ RESTORE_VAULT_ROUTE,
+ CONFIRM_TRANSACTION_ROUTE,
+ NOTICE_ROUTE,
+ CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
+} from '../../../routes'
+
+export default class Home extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ noActiveNotices: PropTypes.bool,
+ lostAccounts: PropTypes.array,
+ forgottenPassword: PropTypes.bool,
+ seedWords: PropTypes.string,
+ suggestedTokens: PropTypes.object,
+ unconfirmedTransactionsCount: PropTypes.number,
+ }
+
+ componentDidMount () {
+ const {
+ history,
+ suggestedTokens = {},
+ unconfirmedTransactionsCount = 0,
+ } = this.props
+
+ // suggested new tokens
+ if (Object.keys(suggestedTokens).length > 0) {
+ history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE)
+ }
+
+ if (unconfirmedTransactionsCount > 0) {
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
+ }
+
+ render () {
+ const {
+ noActiveNotices,
+ lostAccounts,
+ forgottenPassword,
+ seedWords,
+ } = this.props
+
+ // notices
+ if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) {
+ return <Redirect to={{ pathname: NOTICE_ROUTE }} />
+ }
+
+ // seed words
+ if (seedWords) {
+ return <Redirect to={{ pathname: INITIALIZE_BACKUP_PHRASE_ROUTE }}/>
+ }
+
+ if (forgottenPassword) {
+ return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} />
+ }
+
+ return (
+ <div className="main-container">
+ <div className="account-and-transaction-details">
+ <Media
+ query="(min-width: 576px)"
+ render={() => <WalletView />}
+ />
+ <TransactionView />
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js
new file mode 100644
index 000000000..58001df6b
--- /dev/null
+++ b/ui/app/components/pages/home/home.container.js
@@ -0,0 +1,30 @@
+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 {
+ noActiveNotices,
+ lostAccounts,
+ seedWords,
+ suggestedTokens,
+ } = metamask
+ const { forgottenPassword } = appState
+
+ return {
+ noActiveNotices,
+ lostAccounts,
+ forgottenPassword,
+ seedWords,
+ suggestedTokens,
+ unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps)
+)(Home)
diff --git a/ui/app/components/pages/home/index.js b/ui/app/components/pages/home/index.js
new file mode 100644
index 000000000..4474ba5b8
--- /dev/null
+++ b/ui/app/components/pages/home/index.js
@@ -0,0 +1 @@
+export { default } from './home.container'
diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js
deleted file mode 100644
index f16fcb1c7..000000000
--- a/ui/app/components/pending-msg-details.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const connect = require('react-redux').connect
-
-const AccountPanel = require('./account-panel')
-
-PendingMsgDetails.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect()(PendingMsgDetails)
-
-
-inherits(PendingMsgDetails, Component)
-function PendingMsgDetails () {
- Component.call(this)
-}
-
-PendingMsgDetails.prototype.render = function () {
- var state = this.props
- var msgData = state.txData
-
- var msgParams = msgData.msgParams || {}
- var address = msgParams.from || state.selectedAddress
- var identity = state.identities[address] || { address: address }
- var account = state.accounts[address] || { address: address }
-
- return (
- h('div', {
- key: msgData.id,
- style: {
- margin: '10px 20px',
- },
- }, [
-
- // account that will sign
- h(AccountPanel, {
- showFullAddress: true,
- identity: identity,
- account: account,
- imageifyIdenticons: state.imageifyIdenticons,
- }),
-
- // message data
- h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
- h('.flex-column.flex-space-between', [
- h('label.font-small.allcaps', this.context.t('message')),
- h('span.font-small', msgParams.data),
- ]),
- ]),
-
- ])
- )
-}
diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js
deleted file mode 100644
index 21a7864e4..000000000
--- a/ui/app/components/pending-msg.js
+++ /dev/null
@@ -1,73 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const PendingTxDetails = require('./pending-msg-details')
-const connect = require('react-redux').connect
-
-PendingMsg.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect()(PendingMsg)
-
-
-inherits(PendingMsg, Component)
-function PendingMsg () {
- Component.call(this)
-}
-
-PendingMsg.prototype.render = function () {
- var state = this.props
- var msgData = state.txData
-
- return (
-
- h('div', {
- key: msgData.id,
- style: {
- maxWidth: '350px',
- },
- }, [
-
- // header
- h('h3', {
- style: {
- fontWeight: 'bold',
- textAlign: 'center',
- },
- }, this.context.t('signMessage')),
-
- h('.error', {
- style: {
- margin: '10px',
- },
- }, [
- this.context.t('signNotice'),
- h('a', {
- href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527',
- style: { color: 'rgb(247, 134, 28)' },
- onClick: (event) => {
- event.preventDefault()
- const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527'
- global.platform.openWindow({ url })
- },
- }, this.context.t('readMore')),
- ]),
-
- // message details
- h(PendingTxDetails, state),
-
- // sign + cancel
- h('.flex-row.flex-space-around', [
- h('button', {
- onClick: state.cancelMessage,
- }, this.context.t('cancel')),
- h('button', {
- onClick: state.signMessage,
- }, this.context.t('sign')),
- ]),
- ])
-
- )
-}
diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js
index 4334aacba..b87bf959e 100644
--- a/ui/app/components/shift-list-item.js
+++ b/ui/app/components/shift-list-item.js
@@ -35,12 +35,13 @@ function ShiftListItem () {
}
ShiftListItem.prototype.render = function () {
- return h('div.tx-list-item.tx-list-clickable', {
+ return h('div.transaction-list-item.tx-list-clickable', {
style: {
paddingTop: '20px',
paddingBottom: '20px',
justifyContent: 'space-around',
alignItems: 'center',
+ flexDirection: 'row',
},
}, [
h('div', {
diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js
deleted file mode 100644
index 99ca7335c..000000000
--- a/ui/app/components/token-balance.js
+++ /dev/null
@@ -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)
-}
-
diff --git a/ui/app/components/token-balance/index.js b/ui/app/components/token-balance/index.js
new file mode 100644
index 000000000..f7da15cf8
--- /dev/null
+++ b/ui/app/components/token-balance/index.js
@@ -0,0 +1 @@
+export { default } from './token-balance.container'
diff --git a/ui/app/components/token-balance/token-balance.component.js b/ui/app/components/token-balance/token-balance.component.js
new file mode 100644
index 000000000..2b4f73980
--- /dev/null
+++ b/ui/app/components/token-balance/token-balance.component.js
@@ -0,0 +1,23 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+export default class TokenBalance extends PureComponent {
+ static propTypes = {
+ string: PropTypes.string,
+ symbol: PropTypes.string,
+ error: PropTypes.string,
+ className: PropTypes.string,
+ withSymbol: PropTypes.bool,
+ }
+
+ render () {
+ const { className, string, withSymbol, symbol } = this.props
+
+ return (
+ <div className={classnames('hide-text-overflow', className)}>
+ { string + (withSymbol ? ` ${symbol}` : '') }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js b/ui/app/components/token-balance/token-balance.container.js
index bc1289ce1..adc001f83 100644
--- a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js
+++ b/ui/app/components/token-balance/token-balance.container.js
@@ -1,8 +1,8 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
-import withTokenTracker from '../../../../helpers/with-token-tracker'
+import withTokenTracker from '../../higher-order-components/with-token-tracker'
import TokenBalance from './token-balance.component'
-import selectors from '../../../../selectors'
+import selectors from '../../selectors'
const mapStateToProps = state => {
return {
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index 4100d76a5..58a30228d 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -56,8 +56,8 @@ TokenCell.prototype.render = function () {
sidebarOpen,
currentCurrency,
// userAddress,
+ image,
} = props
-
let currentTokenToFiatRate
let currentTokenInFiat
let formattedFiat = ''
@@ -97,6 +97,7 @@ TokenCell.prototype.render = function () {
diameter: 50,
address,
network,
+ image,
}),
h('div.token-list-item__balance-ellipsis', null, [
diff --git a/ui/app/components/token-currency-display/index.js b/ui/app/components/token-currency-display/index.js
new file mode 100644
index 000000000..6065cae1f
--- /dev/null
+++ b/ui/app/components/token-currency-display/index.js
@@ -0,0 +1 @@
+export { default } from './token-currency-display.component'
diff --git a/ui/app/components/token-currency-display/token-currency-display.component.js b/ui/app/components/token-currency-display/token-currency-display.component.js
new file mode 100644
index 000000000..957aec376
--- /dev/null
+++ b/ui/app/components/token-currency-display/token-currency-display.component.js
@@ -0,0 +1,54 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import CurrencyDisplay from '../currency-display/currency-display.component'
+import { getTokenData } from '../../helpers/transactions.util'
+import { calcTokenAmount } from '../../token-util'
+
+export default class TokenCurrencyDisplay extends PureComponent {
+ static propTypes = {
+ transactionData: PropTypes.string,
+ token: PropTypes.object,
+ }
+
+ state = {
+ displayValue: '',
+ }
+
+ componentDidMount () {
+ this.setDisplayValue()
+ }
+
+ componentDidUpdate (prevProps) {
+ const { transactionData } = this.props
+ const { transactionData: prevTransactionData } = prevProps
+
+ if (transactionData !== prevTransactionData) {
+ this.setDisplayValue()
+ }
+ }
+
+ setDisplayValue () {
+ const { transactionData: data, token } = this.props
+ const { decimals = '', symbol = '' } = token
+ const tokenData = getTokenData(data)
+
+ let displayValue
+
+ if (tokenData.params && tokenData.params.length === 2) {
+ const tokenValue = tokenData.params[1].value
+ const tokenAmount = calcTokenAmount(tokenValue, decimals)
+ displayValue = `${tokenAmount} ${symbol}`
+ }
+
+ this.setState({ displayValue })
+ }
+
+ render () {
+ return (
+ <CurrencyDisplay
+ {...this.props}
+ displayValue={this.state.displayValue}
+ />
+ )
+ }
+}
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 42351cf89..6a88f30bf 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -13,6 +13,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
tokens: state.metamask.tokens,
userAddress: selectors.getSelectedAddress(state),
+ assetImages: state.metamask.assetImages,
}
}
@@ -44,10 +45,9 @@ function TokenList () {
}
TokenList.prototype.render = function () {
- const { userAddress } = this.props
+ const { userAddress, assetImages } = this.props
const state = this.state
const { tokens, isLoading, error } = state
-
if (isLoading) {
return this.message(this.context.t('loadingTokens'))
}
@@ -74,7 +74,10 @@ TokenList.prototype.render = function () {
])
}
- return h('div', tokens.map((tokenData) => h(TokenCell, tokenData)))
+ return h('div', tokens.map((tokenData) => {
+ tokenData.image = assetImages[tokenData.address]
+ return h(TokenCell, tokenData)
+ }))
}
diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/transaction-action/index.js
new file mode 100644
index 000000000..a6e9097f1
--- /dev/null
+++ b/ui/app/components/transaction-action/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-action.component'
diff --git a/ui/app/components/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/transaction-action/tests/transaction-action.component.test.js
new file mode 100644
index 000000000..218792847
--- /dev/null
+++ b/ui/app/components/transaction-action/tests/transaction-action.component.test.js
@@ -0,0 +1,112 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import TransactionAction from '../transaction-action.component'
+
+describe('TransactionAction Component', () => {
+ const tOrDefault = key => key
+ global.eth = {
+ getCode: sinon.stub().callsFake(address => {
+ console.log('CALLED')
+ const code = address === 'approveAddress' ? 'contract' : '0x'
+ return Promise.resolve(code)
+ }),
+ }
+
+ describe('Outgoing transaction', () => {
+ it('should render -- when methodData is still fetching', () => {
+ const methodData = { data: {}, done: false, error: null }
+ const transaction = {
+ id: 1,
+ status: 'confirmed',
+ submittedTime: 1534045442919,
+ time: 1534045440641,
+ txParams: {
+ from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0x96',
+ to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706',
+ value: '0x2386f26fc10000',
+ },
+ }
+
+ const wrapper = shallow(<TransactionAction
+ methodData={methodData}
+ transaction={transaction}
+ className="transaction-action"
+ />, { context: { tOrDefault }})
+
+ assert.equal(wrapper.find('.transaction-action').length, 1)
+ assert.equal(wrapper.text(), '--')
+ })
+
+ it('should render Sent Ether', () => {
+ const methodData = { data: {}, done: true, error: null }
+ const transaction = {
+ id: 1,
+ status: 'confirmed',
+ submittedTime: 1534045442919,
+ time: 1534045440641,
+ txParams: {
+ from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0x96',
+ to: 'sentEtherAddress',
+ value: '0x2386f26fc10000',
+ },
+ }
+
+ const wrapper = shallow(<TransactionAction
+ methodData={methodData}
+ transaction={transaction}
+ className="transaction-action"
+ />, { context: { tOrDefault }})
+
+ assert.equal(wrapper.find('.transaction-action').length, 1)
+ wrapper.setState({ transactionAction: 'sentEther' })
+ assert.equal(wrapper.text(), 'sentEther')
+ })
+
+ it('should render Approved', () => {
+ const methodData = {
+ data: {
+ name: 'Approve',
+ params: [
+ { type: 'address' },
+ { type: 'uint256' },
+ ],
+ },
+ done: true,
+ error: null,
+ }
+ const transaction = {
+ id: 1,
+ status: 'confirmed',
+ submittedTime: 1534045442919,
+ time: 1534045440641,
+ txParams: {
+ from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
+ gas: '0x5208',
+ gasPrice: '0x3b9aca00',
+ nonce: '0x96',
+ to: 'approveAddress',
+ value: '0x2386f26fc10000',
+ data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003',
+ },
+ }
+
+ const wrapper = shallow(<TransactionAction
+ methodData={methodData}
+ transaction={transaction}
+ className="transaction-action"
+ />, { context: { tOrDefault }})
+
+ assert.equal(wrapper.find('.transaction-action').length, 1)
+ wrapper.setState({ transactionAction: 'approve' })
+ assert.equal(wrapper.text(), 'approve')
+ })
+ })
+})
diff --git a/ui/app/components/transaction-action/transaction-action.component.js b/ui/app/components/transaction-action/transaction-action.component.js
new file mode 100644
index 000000000..81a1e96d0
--- /dev/null
+++ b/ui/app/components/transaction-action/transaction-action.component.js
@@ -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()
+ }
+
+ async getTransactionAction () {
+ const { transactionAction } = this.state
+ const { transaction, methodData } = this.props
+ const { data, done } = methodData
+
+ if (!done || transactionAction) {
+ return
+ }
+
+ const actionKey = await getTransactionActionKey(transaction, data)
+ const action = actionKey && this.context.tOrDefault(actionKey)
+ this.setState({ transactionAction: action })
+ }
+
+ render () {
+ const { className, methodData: { done } } = this.props
+ const { transactionAction } = this.state
+
+ return (
+ <div className={className}>
+ { (done && transactionAction) || '--' }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-list-item/index.js b/ui/app/components/transaction-list-item/index.js
new file mode 100644
index 000000000..697cc55e9
--- /dev/null
+++ b/ui/app/components/transaction-list-item/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-list-item.container'
diff --git a/ui/app/components/transaction-list-item/index.scss b/ui/app/components/transaction-list-item/index.scss
new file mode 100644
index 000000000..9c53c8960
--- /dev/null
+++ b/ui/app/components/transaction-list-item/index.scss
@@ -0,0 +1,117 @@
+.transaction-list-item {
+ box-sizing: border-box;
+ min-height: 74px;
+ padding: 8px 20px;
+ border-bottom: 1px solid $geyser;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+
+ @media screen and (max-width: $break-small) {
+ padding: 8px 20px 12px;
+ }
+
+ &:hover {
+ background: rgba($alto, .2);
+ }
+
+ &__grid {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 45px 1fr 1fr 1fr;
+ grid-template-areas:
+ "identicon action status primary-amount"
+ "identicon nonce status secondary-amount";
+
+ @media screen and (max-width: $break-small) {
+ grid-template-columns: 45px 5fr 3fr;
+ grid-template-areas:
+ "nonce nonce nonce"
+ "identicon action primary-amount"
+ "identicon status secondary-amount";
+ }
+ }
+
+ &__identicon {
+ grid-area: identicon;
+ grid-row: 1 / span 2;
+ align-self: center;
+
+ @media screen and (max-width: $break-small) {
+ grid-row: 2 / span 2;
+ }
+ }
+
+ &__action {
+ text-transform: capitalize;
+ padding: 0 8px 2px 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ grid-area: action;
+ align-self: end;
+ }
+
+ &__status {
+ grid-area: status;
+ grid-row: 1 / span 2;
+ align-self: center;
+
+ @media screen and (max-width: $break-small) {
+ grid-row: 3;
+ }
+ }
+
+ &__nonce {
+ font-size: .75rem;
+ color: #5e6064;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ grid-area: nonce;
+ align-self: start;
+
+ @media screen and (max-width: $break-small) {
+ padding-bottom: 4px;
+ }
+ }
+
+ &__amount {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &--primary {
+ text-align: end;
+ grid-area: primary-amount;
+ align-self: end;
+
+ @media screen and (max-width: $break-small) {
+ padding-bottom: 2px;
+ }
+ }
+
+ &--secondary {
+ text-align: end;
+ font-size: .75rem;
+ color: #5e6064;
+ grid-area: secondary-amount;
+ align-self: start;
+ }
+ }
+
+ &__retry {
+ background: #d1edff;
+ border-radius: 12px;
+ font-size: .75rem;
+ padding: 4px 12px;
+ cursor: pointer;
+ margin-top: 8px;
+
+ @media screen and (max-width: $break-small) {
+ font-size: .5rem;
+ }
+ }
+}
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.component.js b/ui/app/components/transaction-list-item/transaction-list-item.component.js
new file mode 100644
index 000000000..d9e63d6e0
--- /dev/null
+++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js
@@ -0,0 +1,148 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Identicon from '../identicon'
+import TransactionStatus from '../transaction-status'
+import TransactionAction from '../transaction-action'
+import CurrencyDisplay from '../currency-display'
+import TokenCurrencyDisplay from '../token-currency-display'
+import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
+import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
+import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
+import { ETH } from '../../constants/common'
+
+export default class TransactionListItem extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ transaction: PropTypes.object,
+ value: PropTypes.string,
+ methodData: PropTypes.object,
+ showRetry: PropTypes.bool,
+ retryTransaction: PropTypes.func,
+ setSelectedToken: PropTypes.func,
+ nonceAndDate: PropTypes.string,
+ token: PropTypes.object,
+ }
+
+ 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 })
+ }
+ }
+
+ handleRetryClick = event => {
+ event.stopPropagation()
+
+ const {
+ transaction: { txParams: { to } = {} },
+ methodData: { name } = {},
+ setSelectedToken,
+ } = this.props
+
+ if (name === TOKEN_METHOD_TRANSFER) {
+ setSelectedToken(to)
+ }
+
+ this.resubmit()
+ }
+
+ resubmit () {
+ const { transaction: { id }, retryTransaction, history } = this.props
+ retryTransaction(id)
+ .then(id => history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
+ }
+
+ renderPrimaryCurrency () {
+ const { token, transaction: { txParams: { data } = {} } = {}, value } = this.props
+
+ return token
+ ? (
+ <TokenCurrencyDisplay
+ className="transaction-list-item__amount transaction-list-item__amount--primary"
+ token={token}
+ transactionData={data}
+ prefix="-"
+ />
+ ) : (
+ <CurrencyDisplay
+ className="transaction-list-item__amount transaction-list-item__amount--primary"
+ value={value}
+ prefix="-"
+ />
+ )
+ }
+
+ renderSecondaryCurrency () {
+ const { token, value } = this.props
+
+ return token
+ ? null
+ : (
+ <CurrencyDisplay
+ className="transaction-list-item__amount transaction-list-item__amount--secondary"
+ prefix="-"
+ value={value}
+ numberOfDecimals={2}
+ currency={ETH}
+ />
+ )
+ }
+
+ render () {
+ const {
+ transaction,
+ methodData,
+ showRetry,
+ nonceAndDate,
+ } = this.props
+ const { txParams = {} } = transaction
+
+ return (
+ <div
+ className="transaction-list-item"
+ onClick={this.handleClick}
+ >
+ <div className="transaction-list-item__grid">
+ <Identicon
+ className="transaction-list-item__identicon"
+ address={txParams.to}
+ diameter={34}
+ />
+ <TransactionAction
+ transaction={transaction}
+ methodData={methodData}
+ className="transaction-list-item__action"
+ />
+ <div
+ className="transaction-list-item__nonce"
+ title={nonceAndDate}
+ >
+ { nonceAndDate }
+ </div>
+ <TransactionStatus
+ className="transaction-list-item__status"
+ statusKey={transaction.status}
+ />
+ { this.renderPrimaryCurrency() }
+ { this.renderSecondaryCurrency() }
+ </div>
+ {
+ showRetry && methodData.done && (
+ <div
+ className="transaction-list-item__retry"
+ onClick={this.handleRetryClick}
+ >
+ <span>Taking too long? Increase the gas price on your transaction</span>
+ </div>
+ )
+ }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-list-item/transaction-list-item.container.js b/ui/app/components/transaction-list-item/transaction-list-item.container.js
new file mode 100644
index 000000000..47644241a
--- /dev/null
+++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js
@@ -0,0 +1,32 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import withMethodData from '../../higher-order-components/with-method-data'
+import TransactionListItem from './transaction-list-item.component'
+import { setSelectedToken, retryTransaction } from '../../actions'
+import { hexToDecimal } from '../../helpers/conversions.util'
+import { formatDate } from '../../util'
+
+const mapStateToProps = (state, ownProps) => {
+ const { transaction: { txParams: { value, nonce } = {}, time } = {} } = ownProps
+
+ const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
+
+ return {
+ value,
+ nonceAndDate,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)),
+ retryTransaction: transactionId => dispatch(retryTransaction(transactionId)),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps),
+ withMethodData,
+)(TransactionListItem)
diff --git a/ui/app/components/transaction-list/index.js b/ui/app/components/transaction-list/index.js
new file mode 100644
index 000000000..688994367
--- /dev/null
+++ b/ui/app/components/transaction-list/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-list.container'
diff --git a/ui/app/components/transaction-list/index.scss b/ui/app/components/transaction-list/index.scss
new file mode 100644
index 000000000..0e8db485c
--- /dev/null
+++ b/ui/app/components/transaction-list/index.scss
@@ -0,0 +1,46 @@
+.transaction-list {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow-y: hidden;
+
+ &__completed-transactions {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ }
+
+ &__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;
+ }
+}
diff --git a/ui/app/components/transaction-list/transaction-list.component.js b/ui/app/components/transaction-list/transaction-list.component.js
new file mode 100644
index 000000000..e30476d8c
--- /dev/null
+++ b/ui/app/components/transaction-list/transaction-list.component.js
@@ -0,0 +1,117 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import TransactionListItem from '../transaction-list-item'
+import ShapeShiftTransactionListItem from '../shift-list-item'
+import { TRANSACTION_TYPE_SHAPESHIFT } from '../../constants/transactions'
+
+export default class TransactionList extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static defaultProps = {
+ pendingTransactions: [],
+ completedTransactions: [],
+ transactionToRetry: {},
+ }
+
+ static propTypes = {
+ pendingTransactions: PropTypes.array,
+ completedTransactions: PropTypes.array,
+ transactionToRetry: PropTypes.object,
+ selectedToken: PropTypes.object,
+ updateNetworkNonce: PropTypes.func,
+ }
+
+ componentDidMount () {
+ this.props.updateNetworkNonce()
+ }
+
+ componentDidUpdate (prevProps) {
+ const { pendingTransactions: prevPendingTransactions = [] } = prevProps
+ const { pendingTransactions = [], updateNetworkNonce } = this.props
+
+ if (pendingTransactions.length > prevPendingTransactions.length) {
+ updateNetworkNonce()
+ }
+ }
+
+ shouldShowRetry = transaction => {
+ const { transactionToRetry } = this.props
+ const { id, submittedTime } = transaction
+ return id === transactionToRetry.id && Date.now() - submittedTime > 30000
+ }
+
+ 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('queue')} (${pendingTransactions.length})` }
+ </div>
+ {
+ pendingTransactions.map((transaction, index) => (
+ this.renderTransaction(transaction, index)
+ ))
+ }
+ </div>
+ )
+ }
+ <div className="transaction-list__completed-transactions">
+ <div className="transaction-list__header">
+ { t('history') }
+ </div>
+ {
+ completedTransactions.length > 0
+ ? completedTransactions.map((transaction, index) => (
+ this.renderTransaction(transaction, index)
+ ))
+ : this.renderEmpty()
+ }
+ </div>
+ </div>
+ )
+ }
+
+ renderTransaction (transaction, index) {
+ const { selectedToken } = this.props
+
+ return transaction.key === TRANSACTION_TYPE_SHAPESHIFT
+ ? (
+ <ShapeShiftTransactionListItem
+ { ...transaction }
+ key={`shapeshift${index}`}
+ />
+ ) : (
+ <TransactionListItem
+ transaction={transaction}
+ key={transaction.id}
+ showRetry={this.shouldShowRetry(transaction)}
+ token={selectedToken}
+ />
+ )
+ }
+
+ 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() }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-list/transaction-list.container.js b/ui/app/components/transaction-list/transaction-list.container.js
new file mode 100644
index 000000000..1ec1f9ccf
--- /dev/null
+++ b/ui/app/components/transaction-list/transaction-list.container.js
@@ -0,0 +1,50 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import TransactionList from './transaction-list.component'
+import {
+ pendingTransactionsSelector,
+ submittedPendingTransactionsSelector,
+ completedTransactionsSelector,
+} from '../../selectors/transactions'
+import { getSelectedAddress } from '../../selectors'
+import { selectedTokenSelector } from '../../selectors/tokens'
+import { getLatestSubmittedTxWithNonce } from '../../helpers/transactions.util'
+import { updateNetworkNonce } from '../../actions'
+
+const mapStateToProps = state => {
+ const pendingTransactions = pendingTransactionsSelector(state)
+ const submittedPendingTransactions = submittedPendingTransactionsSelector(state)
+ const networkNonce = state.appState.networkNonce
+
+ return {
+ completedTransactions: completedTransactionsSelector(state),
+ pendingTransactions,
+ transactionToRetry: getLatestSubmittedTxWithNonce(submittedPendingTransactions, networkNonce),
+ selectedToken: selectedTokenSelector(state),
+ selectedAddress: getSelectedAddress(state),
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ updateNetworkNonce: address => dispatch(updateNetworkNonce(address)),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { selectedAddress, ...restStateProps } = stateProps
+ const { updateNetworkNonce, ...restDispatchProps } = dispatchProps
+
+ return {
+ ...restStateProps,
+ ...restDispatchProps,
+ ...ownProps,
+ updateNetworkNonce: () => updateNetworkNonce(selectedAddress),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps, mergeProps)
+)(TransactionList)
diff --git a/ui/app/components/transaction-status/index.js b/ui/app/components/transaction-status/index.js
new file mode 100644
index 000000000..dece41e9c
--- /dev/null
+++ b/ui/app/components/transaction-status/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-status.component'
diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss
new file mode 100644
index 000000000..35be550f7
--- /dev/null
+++ b/ui/app/components/transaction-status/index.scss
@@ -0,0 +1,28 @@
+.transaction-status {
+ height: 26px;
+ width: 81px;
+ border-radius: 4px;
+ background-color: #f0f0f0;
+ color: #5e6064;
+ font-size: .625rem;
+ text-transform: uppercase;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ @media screen and (max-width: $break-small) {
+ height: 16px;
+ width: 70px;
+ font-size: .5rem;
+ }
+
+ &--confirmed {
+ background-color: #eafad7;
+ color: #609a1c;
+ }
+
+ &--approved, &--submitted {
+ background-color: #FFF2DB;
+ color: #CA810A;
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/transaction-status/transaction-status.component.js b/ui/app/components/transaction-status/transaction-status.component.js
new file mode 100644
index 000000000..a4c827ae8
--- /dev/null
+++ b/ui/app/components/transaction-status/transaction-status.component.js
@@ -0,0 +1,51 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import {
+ UNAPPROVED_STATUS,
+ REJECTED_STATUS,
+ APPROVED_STATUS,
+ SIGNED_STATUS,
+ SUBMITTED_STATUS,
+ CONFIRMED_STATUS,
+ FAILED_STATUS,
+ DROPPED_STATUS,
+} from '../../constants/transactions'
+
+const statusToClassNameHash = {
+ [UNAPPROVED_STATUS]: 'transaction-status--unapproved',
+ [REJECTED_STATUS]: 'transaction-status--rejected',
+ [APPROVED_STATUS]: 'transaction-status--approved',
+ [SIGNED_STATUS]: 'transaction-status--signed',
+ [SUBMITTED_STATUS]: 'transaction-status--submitted',
+ [CONFIRMED_STATUS]: 'transaction-status--confirmed',
+ [FAILED_STATUS]: 'transaction-status--failed',
+ [DROPPED_STATUS]: 'transaction-status--dropped',
+}
+
+const statusToTextHash = {
+ [APPROVED_STATUS]: 'pending',
+ [SUBMITTED_STATUS]: 'pending',
+}
+
+export default class TransactionStatus extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ statusKey: PropTypes.string,
+ className: PropTypes.string,
+ }
+
+ render () {
+ const { className, statusKey } = this.props
+ const statusText = this.context.t(statusToTextHash[statusKey] || statusKey)
+
+ return (
+ <div className={classnames('transaction-status', className, statusToClassNameHash[statusKey])}>
+ { statusText }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-view-balance/index.js b/ui/app/components/transaction-view-balance/index.js
new file mode 100644
index 000000000..8824737f7
--- /dev/null
+++ b/ui/app/components/transaction-view-balance/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-view-balance.container'
diff --git a/ui/app/components/transaction-view-balance/index.scss b/ui/app/components/transaction-view-balance/index.scss
new file mode 100644
index 000000000..12045ab6d
--- /dev/null
+++ b/ui/app/components/transaction-view-balance/index.scss
@@ -0,0 +1,76 @@
+.transaction-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;
+ }
+ }
+
+ &__token-balance {
+ margin-left: 12px;
+ font-size: 1.5rem;
+
+ @media screen and (max-width: $break-small) {
+ margin-bottom: 12px;
+ font-size: 1.75rem;
+ }
+ }
+
+ &__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
+ }
+}
diff --git a/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
new file mode 100644
index 000000000..bb95cb27e
--- /dev/null
+++ b/ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
@@ -0,0 +1,71 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import TokenBalance from '../../token-balance'
+import CurrencyDisplay from '../../currency-display'
+import { SEND_ROUTE } from '../../../routes'
+import TransactionViewBalance from '../transaction-view-balance.component'
+
+const propsMethodSpies = {
+ showDepositModal: sinon.spy(),
+}
+
+const historySpies = {
+ push: sinon.spy(),
+}
+
+const t = (str1, str2) => str2 ? str1 + str2 : str1
+
+describe('TransactionViewBalance Component', () => {
+ afterEach(() => {
+ propsMethodSpies.showDepositModal.resetHistory()
+ historySpies.push.resetHistory()
+ })
+
+ it('should render ETH balance properly', () => {
+ const wrapper = shallow(<TransactionViewBalance
+ showDepositModal={propsMethodSpies.showDepositModal}
+ history={historySpies}
+ network="3"
+ ethBalance={123}
+ fiatBalance={456}
+ currentCurrency="usd"
+ />, { context: { t } })
+
+ assert.equal(wrapper.find('.transaction-view-balance').length, 1)
+ assert.equal(wrapper.find('.transaction-view-balance__button').length, 2)
+ assert.equal(wrapper.find(CurrencyDisplay).length, 2)
+
+ const buttons = wrapper.find('.transaction-view-balance__buttons')
+ assert.equal(propsMethodSpies.showDepositModal.callCount, 0)
+ buttons.childAt(0).simulate('click')
+ assert.equal(propsMethodSpies.showDepositModal.callCount, 1)
+ assert.equal(historySpies.push.callCount, 0)
+ buttons.childAt(1).simulate('click')
+ assert.equal(historySpies.push.callCount, 1)
+ assert.equal(historySpies.push.getCall(0).args[0], SEND_ROUTE)
+ })
+
+ it('should render token balance properly', () => {
+ const token = {
+ address: '0x35865238f0bec9d5ce6abff0fdaebe7b853dfcc5',
+ decimals: '2',
+ symbol: 'ABC',
+ }
+
+ const wrapper = shallow(<TransactionViewBalance
+ showDepositModal={propsMethodSpies.showDepositModal}
+ history={historySpies}
+ network="3"
+ ethBalance={123}
+ fiatBalance={456}
+ currentCurrency="usd"
+ selectedToken={token}
+ />, { context: { t } })
+
+ assert.equal(wrapper.find('.transaction-view-balance').length, 1)
+ assert.equal(wrapper.find('.transaction-view-balance__button').length, 1)
+ assert.equal(wrapper.find(TokenBalance).length, 1)
+ })
+})
diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
new file mode 100644
index 000000000..bdc46f714
--- /dev/null
+++ b/ui/app/components/transaction-view-balance/transaction-view-balance.component.js
@@ -0,0 +1,94 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../button'
+import Identicon from '../identicon'
+import TokenBalance from '../token-balance'
+import CurrencyDisplay from '../currency-display'
+import { SEND_ROUTE } from '../../routes'
+import { ETH } from '../../constants/common'
+
+export default class TransactionViewBalance extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ showDepositModal: PropTypes.func,
+ selectedToken: PropTypes.object,
+ history: PropTypes.object,
+ network: PropTypes.string,
+ balance: PropTypes.string,
+ }
+
+ renderBalance () {
+ const { selectedToken, balance } = this.props
+
+ return selectedToken
+ ? (
+ <TokenBalance
+ token={selectedToken}
+ withSymbol
+ className="transaction-view-balance__token-balance"
+ />
+ ) : (
+ <div className="transaction-view-balance__balance">
+ <CurrencyDisplay
+ className="transaction-view-balance__primary-balance"
+ value={balance}
+ currency={ETH}
+ numberOfDecimals={3}
+ />
+ <CurrencyDisplay
+ className="transaction-view-balance__secondary-balance"
+ value={balance}
+ />
+ </div>
+ )
+ }
+
+ renderButtons () {
+ const { t } = this.context
+ const { selectedToken, showDepositModal, history } = this.props
+
+ return (
+ <div className="transaction-view-balance__buttons">
+ {
+ !selectedToken && (
+ <Button
+ type="primary"
+ className="transaction-view-balance__button"
+ onClick={() => showDepositModal()}
+ >
+ { t('deposit') }
+ </Button>
+ )
+ }
+ <Button
+ type="primary"
+ className="transaction-view-balance__button"
+ onClick={() => history.push(SEND_ROUTE)}
+ >
+ { t('send') }
+ </Button>
+ </div>
+ )
+ }
+
+ render () {
+ const { network, selectedToken } = this.props
+
+ return (
+ <div className="transaction-view-balance">
+ <div className="transaction-view-balance__balance-container">
+ <Identicon
+ diameter={50}
+ address={selectedToken && selectedToken.address}
+ network={network}
+ />
+ { this.renderBalance() }
+ </div>
+ { this.renderButtons() }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-view-balance/transaction-view-balance.container.js b/ui/app/components/transaction-view-balance/transaction-view-balance.container.js
new file mode 100644
index 000000000..1d3432b15
--- /dev/null
+++ b/ui/app/components/transaction-view-balance/transaction-view-balance.container.js
@@ -0,0 +1,30 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import TransactionViewBalance from './transaction-view-balance.component'
+import { getSelectedToken, getSelectedAddress } from '../../selectors'
+import { showModal } from '../../actions'
+
+const mapStateToProps = state => {
+ const selectedAddress = getSelectedAddress(state)
+ const { metamask: { network, accounts } } = state
+ const account = accounts[selectedAddress]
+ const { balance } = account
+
+ return {
+ selectedToken: getSelectedToken(state),
+ network,
+ balance,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(TransactionViewBalance)
diff --git a/ui/app/components/transaction-view/index.js b/ui/app/components/transaction-view/index.js
new file mode 100644
index 000000000..9eb0c3c83
--- /dev/null
+++ b/ui/app/components/transaction-view/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-view.component'
diff --git a/ui/app/components/transaction-view/index.scss b/ui/app/components/transaction-view/index.scss
new file mode 100644
index 000000000..af9771ce0
--- /dev/null
+++ b/ui/app/components/transaction-view/index.scss
@@ -0,0 +1,27 @@
+.transaction-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;
+ }
+ }
+}
diff --git a/ui/app/components/transaction-view/transaction-view.component.js b/ui/app/components/transaction-view/transaction-view.component.js
new file mode 100644
index 000000000..7014ca173
--- /dev/null
+++ b/ui/app/components/transaction-view/transaction-view.component.js
@@ -0,0 +1,27 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Media from 'react-media'
+import MenuBar from '../menu-bar'
+import TransactionViewBalance from '../transaction-view-balance'
+import TransactionList from '../transaction-list'
+
+export default class TransactionView extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ render () {
+ return (
+ <div className="transaction-view">
+ <Media
+ query="(max-width: 575px)"
+ render={() => <MenuBar />}
+ />
+ <div className="transaction-view__balance-wrapper">
+ <TransactionViewBalance />
+ </div>
+ <TransactionList />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
deleted file mode 100644
index 474d62638..000000000
--- a/ui/app/components/tx-list-item.js
+++ /dev/null
@@ -1,356 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const { compose } = require('recompose')
-const { withRouter } = require('react-router-dom')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const inherits = require('util').inherits
-const classnames = require('classnames')
-const abi = require('human-standard-token-abi')
-const abiDecoder = require('abi-decoder')
-abiDecoder.addABI(abi)
-const Identicon = require('./identicon')
-const contractMap = require('eth-contract-metadata')
-const { checksumAddress } = require('../util')
-
-const actions = require('../actions')
-const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
-const { calcTokenAmount } = require('../token-util')
-
-const { getCurrentCurrency } = require('../selectors')
-const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
-
-TxListItem.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(TxListItem)
-
-function mapStateToProps (state) {
- return {
- tokens: state.metamask.tokens,
- currentCurrency: getCurrentCurrency(state),
- contractExchangeRates: state.metamask.contractExchangeRates,
- selectedAddressTxList: state.metamask.selectedAddressTxList,
- networkNonce: state.appState.networkNonce,
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)),
- retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
- }
-}
-
-inherits(TxListItem, Component)
-function TxListItem () {
- Component.call(this)
-
- this.state = {
- total: null,
- fiatTotal: null,
- isTokenTx: null,
- }
-
- this.unmounted = false
-}
-
-TxListItem.prototype.componentDidMount = async function () {
- const { txParams = {} } = this.props
-
- const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
- const { name: txDataName } = decodedData || {}
- const isTokenTx = txDataName === 'transfer'
-
- const { total, fiatTotal } = isTokenTx
- ? await this.getSendTokenTotal()
- : this.getSendEtherTotal()
-
- if (this.unmounted) {
- return
- }
- this.setState({ total, fiatTotal, isTokenTx })
-}
-
-TxListItem.prototype.componentWillUnmount = function () {
- this.unmounted = true
-}
-
-TxListItem.prototype.getAddressText = function () {
- const {
- address,
- txParams = {},
- isMsg,
- } = this.props
-
- const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
- const { name: txDataName, params = [] } = decodedData || {}
- const { value } = params[0] || {}
- const checksummedAddress = checksumAddress(address)
- const checksummedValue = checksumAddress(value)
-
- let addressText
- if (txDataName === 'transfer' || address) {
- const addressToRender = txDataName === 'transfer' ? checksummedValue : checksummedAddress
- addressText = `${addressToRender.slice(0, 10)}...${addressToRender.slice(-4)}`
- } else if (isMsg) {
- addressText = this.context.t('sigRequest')
- } else {
- addressText = this.context.t('contractDeployment')
- }
-
- return addressText
-}
-
-TxListItem.prototype.getSendEtherTotal = function () {
- const {
- transactionAmount,
- conversionRate,
- address,
- currentCurrency,
- } = this.props
-
- if (!address) {
- return {}
- }
-
- const totalInFiat = conversionUtil(transactionAmount, {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- fromCurrency: 'ETH',
- toCurrency: currentCurrency,
- fromDenomination: 'WEI',
- numberOfDecimals: 2,
- conversionRate,
- })
- const totalInETH = conversionUtil(transactionAmount, {
- fromNumericBase: 'hex',
- toNumericBase: 'dec',
- fromCurrency: 'ETH',
- toCurrency: 'ETH',
- fromDenomination: 'WEI',
- conversionRate,
- numberOfDecimals: 6,
- })
-
- return {
- total: `${totalInETH} ETH`,
- fiatTotal: `${totalInFiat} ${currentCurrency.toUpperCase()}`,
- }
-}
-
-TxListItem.prototype.getTokenInfo = async function () {
- const { txParams = {}, tokenInfoGetter, tokens } = this.props
- const toAddress = txParams.to
-
- let decimals
- let symbol
-
- ({ decimals, symbol } = tokens.filter(({ address }) => address === toAddress)[0] || {})
-
- if (!decimals && !symbol) {
- ({ decimals, symbol } = contractMap[toAddress] || {})
- }
-
- if (!decimals && !symbol) {
- ({ decimals, symbol } = await tokenInfoGetter(toAddress))
- }
-
- return { decimals, symbol, address: toAddress }
-}
-
-TxListItem.prototype.getSendTokenTotal = async function () {
- const {
- txParams = {},
- conversionRate,
- contractExchangeRates,
- currentCurrency,
- } = this.props
-
- const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
- const { params = [] } = decodedData || {}
- const { value } = params[1] || {}
- const { decimals, symbol, address } = await this.getTokenInfo()
- const total = calcTokenAmount(value, decimals)
-
- let tokenToFiatRate
- let totalInFiat
-
- if (contractExchangeRates[address]) {
- tokenToFiatRate = multiplyCurrencies(
- contractExchangeRates[address],
- conversionRate
- )
-
- totalInFiat = conversionUtil(total, {
- fromNumericBase: 'dec',
- toNumericBase: 'dec',
- fromCurrency: symbol,
- toCurrency: currentCurrency,
- numberOfDecimals: 2,
- conversionRate: tokenToFiatRate,
- })
- }
-
- const showFiat = Boolean(totalInFiat) && currentCurrency.toUpperCase() !== symbol
-
- return {
- total: `${total} ${symbol}`,
- fiatTotal: showFiat && `${totalInFiat} ${currentCurrency.toUpperCase()}`,
- }
-}
-
-TxListItem.prototype.showRetryButton = function () {
- const {
- transactionSubmittedTime,
- selectedAddressTxList,
- transactionId,
- txParams,
- networkNonce,
- } = this.props
- if (!txParams) {
- return false
- }
- let currentTxSharesEarliestNonce = false
- const currentNonce = txParams.nonce
- const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce)
- const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
- const currentSubmittedTxs = selectedAddressTxList.filter(tx => tx.status === 'submitted')
- const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1]
- const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce &&
- lastSubmittedTxWithCurrentNonce.id === transactionId
- if (currentSubmittedTxs.length > 0) {
- currentTxSharesEarliestNonce = currentNonce === networkNonce
- }
-
- return currentTxSharesEarliestNonce && currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000
-}
-
-TxListItem.prototype.setSelectedToken = function (tokenAddress) {
- this.props.setSelectedToken(tokenAddress)
-}
-
-TxListItem.prototype.resubmit = function () {
- const { transactionId } = this.props
- this.props.retryTransaction(transactionId)
- .then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`))
-}
-
-TxListItem.prototype.render = function () {
- const {
- transactionStatus,
- onClick,
- transactionId,
- dateString,
- address,
- className,
- txParams,
- } = this.props
- const { total, fiatTotal, isTokenTx } = this.state
-
- return h(`div${className || ''}`, {
- key: transactionId,
- onClick: () => onClick && onClick(transactionId),
- }, [
- h(`div.flex-column.tx-list-item-wrapper`, {}, [
-
- h('div.tx-list-date-wrapper', {
- style: {},
- }, [
- h('span.tx-list-date', {}, [
- dateString,
- ]),
- ]),
-
- h('div.flex-row.tx-list-content-wrapper', {
- style: {},
- }, [
-
- h('div.tx-list-identicon-wrapper', {
- style: {},
- }, [
- h(Identicon, {
- address,
- diameter: 28,
- }),
- ]),
-
- h('div.tx-list-account-and-status-wrapper', {}, [
- h('div.tx-list-account-wrapper', {
- style: {},
- }, [
- h('span.tx-list-account', {}, [
- this.getAddressText(address),
- ]),
- ]),
-
- h('div.tx-list-status-wrapper', {
- style: {},
- }, [
- h('span', {
- className: classnames('tx-list-status', {
- 'tx-list-status--rejected': transactionStatus === 'rejected',
- 'tx-list-status--failed': transactionStatus === 'failed',
- 'tx-list-status--dropped': transactionStatus === 'dropped',
- }),
- },
- this.txStatusIndicator(),
- ),
- ]),
- ]),
-
- h('div.flex-column.tx-list-details-wrapper', {
- style: {},
- }, [
-
- h('span.tx-list-value', total),
-
- fiatTotal && h('span.tx-list-fiat-value', fiatTotal),
-
- ]),
- ]),
-
- this.showRetryButton() && h('.tx-list-item-retry-container', {
- onClick: (event) => {
- event.stopPropagation()
- if (isTokenTx) {
- this.setSelectedToken(txParams.to)
- }
- this.resubmit()
- },
- }, [
- h('span', 'Taking too long? Increase the gas price on your transaction'),
- ]),
-
- ]), // holding on icon from design
- ])
-}
-
-TxListItem.prototype.txStatusIndicator = function () {
- const { transactionStatus } = this.props
-
- let name
-
- if (transactionStatus === 'unapproved') {
- name = this.context.t('unapproved')
- } else if (transactionStatus === 'rejected') {
- name = this.context.t('rejected')
- } else if (transactionStatus === 'approved') {
- name = this.context.t('approved')
- } else if (transactionStatus === 'signed') {
- name = this.context.t('signed')
- } else if (transactionStatus === 'submitted') {
- name = this.context.t('submitted')
- } else if (transactionStatus === 'confirmed') {
- name = this.context.t('confirmed')
- } else if (transactionStatus === 'failed') {
- name = this.context.t('failed')
- } else if (transactionStatus === 'dropped') {
- name = this.context.t('dropped')
- }
- return name
-}
diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js
deleted file mode 100644
index d8c4a9d19..000000000
--- a/ui/app/components/tx-list.js
+++ /dev/null
@@ -1,171 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
-const selectors = require('../selectors')
-const TxListItem = require('./tx-list-item')
-const ShiftListItem = require('./shift-list-item')
-const { formatDate } = require('../util')
-const { showConfTxPage, updateNetworkNonce } = require('../actions')
-const classnames = require('classnames')
-const { tokenInfoGetter } = require('../token-util')
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const { CONFIRM_TRANSACTION_ROUTE } = require('../routes')
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(TxList)
-
-TxList.contextTypes = {
- t: PropTypes.func,
-}
-
-function mapStateToProps (state) {
- return {
- txsToRender: selectors.transactionsSelector(state),
- conversionRate: selectors.conversionRateSelector(state),
- selectedAddress: selectors.getSelectedAddress(state),
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- showConfTxPage: ({ id }) => dispatch(showConfTxPage({ id })),
- updateNetworkNonce: (address) => dispatch(updateNetworkNonce(address)),
- }
-}
-
-inherits(TxList, Component)
-function TxList () {
- Component.call(this)
-}
-
-TxList.prototype.componentWillMount = function () {
- this.tokenInfoGetter = tokenInfoGetter()
- this.props.updateNetworkNonce(this.props.selectedAddress)
-}
-
-TxList.prototype.componentDidUpdate = function (prevProps) {
- const oldTxsToRender = prevProps.txsToRender
- const {
- txsToRender: newTxsToRender,
- selectedAddress,
- updateNetworkNonce,
- } = this.props
-
- if (newTxsToRender.length > oldTxsToRender.length) {
- updateNetworkNonce(selectedAddress)
- }
-}
-
-TxList.prototype.render = function () {
- return h('div.flex-column', [
- h('div.flex-row.tx-list-header-wrapper', [
- h('div.flex-row.tx-list-header', [
- h('div', this.context.t('transactions')),
- ]),
- ]),
- h('div.flex-column.tx-list-container', {}, [
- this.renderTransaction(),
- ]),
- ])
-}
-
-TxList.prototype.renderTransaction = function () {
- const { txsToRender, conversionRate } = this.props
-
- return txsToRender.length
- ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate, i))
- : [h(
- 'div.tx-list-item.tx-list-item--empty',
- { key: 'tx-list-none' },
- [ this.context.t('noTransactions') ],
- )]
-}
-
-// TODO: Consider moving TxListItem into a separate component
-TxList.prototype.renderTransactionListItem = function (transaction, conversionRate, index) {
- // console.log({transaction})
- // refer to transaction-list.js:line 58
-
- if (transaction.key === 'shapeshift') {
- return h(ShiftListItem, { ...transaction, key: `shapeshift${index}` })
- }
-
- const props = {
- dateString: formatDate(transaction.time),
- address: transaction.txParams && transaction.txParams.to,
- transactionStatus: transaction.status,
- transactionAmount: transaction.txParams && transaction.txParams.value,
- transactionId: transaction.id,
- transactionHash: transaction.hash,
- transactionNetworkId: transaction.metamaskNetworkId,
- transactionSubmittedTime: transaction.submittedTime,
- }
-
- const {
- address,
- transactionStatus,
- transactionAmount,
- dateString,
- transactionId,
- transactionHash,
- transactionNetworkId,
- transactionSubmittedTime,
- } = props
- const { history } = this.props
-
- const opts = {
- key: transactionId || transactionHash,
- txParams: transaction.txParams,
- isMsg: Boolean(transaction.msgParams),
- transactionStatus,
- transactionId,
- dateString,
- address,
- transactionAmount,
- transactionHash,
- conversionRate,
- tokenInfoGetter: this.tokenInfoGetter,
- transactionSubmittedTime,
- }
-
- const isUnapproved = transactionStatus === 'unapproved'
-
- if (isUnapproved) {
- opts.onClick = () => {
- this.props.showConfTxPage({ id: transactionId })
- history.push(CONFIRM_TRANSACTION_ROUTE)
- }
- opts.transactionStatus = this.context.t('notStarted')
- } else if (transactionHash) {
- opts.onClick = () => this.view(transactionHash, transactionNetworkId)
- }
-
- opts.className = classnames('.tx-list-item', {
- '.tx-list-pending-item-container': isUnapproved,
- '.tx-list-clickable': Boolean(transactionHash) || isUnapproved,
- })
-
- return h(TxListItem, opts)
-}
-
-TxList.prototype.view = function (txHash, network) {
- const url = etherscanLinkFor(txHash, network)
- if (url) {
- navigateTo(url)
- }
-}
-
-function navigateTo (url) {
- global.platform.openWindow({ url })
-}
-
-function etherscanLinkFor (txHash, network) {
- const prefix = prefixForNetwork(network)
- return `https://${prefix}etherscan.io/tx/${txHash}`
-}
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
deleted file mode 100644
index 654090da6..000000000
--- a/ui/app/components/tx-view.js
+++ /dev/null
@@ -1,156 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const connect = require('react-redux').connect
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const { withRouter } = require('react-router-dom')
-const { compose } = require('recompose')
-const actions = require('../actions')
-const selectors = require('../selectors')
-const { SEND_ROUTE } = require('../routes')
-const { checksumAddress: toChecksumAddress } = require('../util')
-
-const BalanceComponent = require('./balance-component')
-const Tooltip = require('./tooltip')
-const TxList = require('./tx-list')
-const SelectedAccount = require('./selected-account')
-
-module.exports = compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(TxView)
-
-TxView.contextTypes = {
- t: PropTypes.func,
-}
-
-function mapStateToProps (state) {
- const sidebarOpen = state.appState.sidebarOpen
- const isMascara = state.appState.isMascara
-
- const identities = state.metamask.identities
- const accounts = state.metamask.accounts
- const network = state.metamask.network
- const selectedTokenAddress = state.metamask.selectedTokenAddress
- const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
- const checksumAddress = toChecksumAddress(selectedAddress)
- const identity = identities[selectedAddress]
-
- return {
- sidebarOpen,
- selectedAddress,
- checksumAddress,
- selectedTokenAddress,
- selectedToken: selectors.getSelectedToken(state),
- identity,
- network,
- isMascara,
- }
-}
-
-function mapDispatchToProps (dispatch) {
- return {
- showSidebar: () => { dispatch(actions.showSidebar()) },
- hideSidebar: () => { dispatch(actions.hideSidebar()) },
- showModal: (payload) => { dispatch(actions.showModal(payload)) },
- showSendPage: () => { dispatch(actions.showSendPage()) },
- showSendTokenPage: () => { dispatch(actions.showSendTokenPage()) },
- }
-}
-
-inherits(TxView, Component)
-function TxView () {
- Component.call(this)
-}
-
-TxView.prototype.renderHeroBalance = function () {
- const { selectedToken } = this.props
-
- return h('div.hero-balance', {}, [
-
- h(BalanceComponent, { token: selectedToken }),
-
- this.renderButtons(),
- ])
-}
-
-TxView.prototype.renderButtons = function () {
- const {selectedToken, showModal, history } = this.props
-
- return !selectedToken
- ? (
- h('div.flex-row.flex-center.hero-balance-buttons', [
- h('button.btn-primary.hero-balance-button', {
- onClick: () => showModal({
- name: 'DEPOSIT_ETHER',
- }),
- }, this.context.t('deposit')),
-
- h('button.btn-primary.hero-balance-button', {
- style: {
- marginLeft: '0.8em',
- },
- onClick: () => history.push(SEND_ROUTE),
- }, this.context.t('send')),
- ])
- )
- : (
- h('div.flex-row.flex-center.hero-balance-buttons', [
- h('button.btn-primary.hero-balance-button', {
- onClick: () => history.push(SEND_ROUTE),
- }, this.context.t('send')),
- ])
- )
-}
-
-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(),
- }),
- ]),
-
- 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(),
-
- h(TxList),
-
- ])
-}
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index 8e092364c..ffa60e3ed 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -26,6 +26,10 @@ WalletView.contextTypes = {
t: PropTypes.func,
}
+WalletView.defaultProps = {
+ responsiveDisplayClassname: '',
+}
+
function mapStateToProps (state) {
return {
@@ -131,8 +135,9 @@ WalletView.prototype.render = function () {
}
}
- return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), {
+ return h('div.wallet-view.flex-column', {
style: {},
+ className: responsiveDisplayClassname,
}, [
// TODO: Separate component: wallet account details