aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@gmail.com>2018-07-31 13:03:20 +0800
committerAlexander Tseung <alextsg@gmail.com>2018-08-24 07:44:44 +0800
commit5ee40675b9f986a9ff2e5d15a271d7de2145d0e9 (patch)
tree80f4b3e0a88a5621724a05efeb320596e0bcedad
parentd733bd34fbd356bca640b3a50582208c0284be40 (diff)
downloadtangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar
tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.gz
tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.bz2
tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.lz
tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.xz
tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.tar.zst
tangerine-wallet-browser-5ee40675b9f986a9ff2e5d15a271d7de2145d0e9.zip
Refactor transactions list views. Add redesign components
-rw-r--r--app/_locales/en/messages.json11
-rw-r--r--ui/app/components/index.scss24
-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/home.component.js21
-rw-r--r--ui/app/components/pages/home/home.container.js10
-rw-r--r--ui/app/components/token-balance.js120
-rw-r--r--ui/app/components/token-view-balance/index.js1
-rw-r--r--ui/app/components/token-view-balance/index.scss66
-rw-r--r--ui/app/components/token-view-balance/token-view-balance.component.js92
-rw-r--r--ui/app/components/token-view-balance/token-view-balance.container.js42
-rw-r--r--ui/app/components/token-view/index.js1
-rw-r--r--ui/app/components/token-view/index.scss27
-rw-r--r--ui/app/components/token-view/token-view.component.js28
-rw-r--r--ui/app/components/transaction-action/index.js1
-rw-r--r--ui/app/components/transaction-action/transaction-action.component.js52
-rw-r--r--ui/app/components/transaction-action/transaction-action.container.js4
-rw-r--r--ui/app/components/transaction-list-item/index.js1
-rw-r--r--ui/app/components/transaction-list-item/index.scss71
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.component.js82
-rw-r--r--ui/app/components/transaction-list-item/transaction-list-item.container.js28
-rw-r--r--ui/app/components/transaction-list/index.js1
-rw-r--r--ui/app/components/transaction-list/index.scss40
-rw-r--r--ui/app/components/transaction-list/transaction-list.component.js90
-rw-r--r--ui/app/components/transaction-list/transaction-list.container.js20
-rw-r--r--ui/app/components/transaction-status/index.scss6
-rw-r--r--ui/app/components/tx-view.js51
-rw-r--r--ui/app/constants/transactions.js18
-rw-r--r--ui/app/css/itcss/components/hero-balance.scss130
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss7
-rw-r--r--ui/app/ducks/confirm-transaction.duck.js8
-rw-r--r--ui/app/helpers/confirm-transaction/util.js17
-rw-r--r--ui/app/helpers/confirm-transaction/util.test.js6
-rw-r--r--ui/app/helpers/conversions.util.js37
-rw-r--r--ui/app/helpers/transactions.util.js57
-rw-r--r--ui/app/higher-order-components/with-method-data/with-method-data.component.js22
-rw-r--r--ui/app/i18n-provider.js4
-rw-r--r--ui/app/selectors.js62
-rw-r--r--ui/app/selectors/transactions.js50
40 files changed, 936 insertions, 381 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 2656432d2..9c55dc11d 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -451,6 +451,9 @@
"hideTokenPrompt": {
"message": "Hide Token?"
},
+ "history": {
+ "message": "History"
+ },
"howToDeposit": {
"message": "How would you like to deposit Ether?"
},
@@ -651,7 +654,7 @@
"message": "No transaction history."
},
"noTransactions": {
- "message": "No Transactions"
+ "message": "You have no transactions"
},
"notFound": {
"message": "Not Found"
@@ -702,6 +705,9 @@
"pasteSeed": {
"message": "Paste your seed phrase here!"
},
+ "pending": {
+ "message": "Pending"
+ },
"personalAddressDetected": {
"message": "Personal address detected. Input the token contract address."
},
@@ -894,6 +900,9 @@
"sendETH": {
"message": "Send ETH"
},
+ "sendEther": {
+ "message": "Send Ether"
+ },
"sendTokens": {
"message": "Send Tokens"
},
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index 35d38e2a3..261d917f6 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 './token-view/index';
+
+@import './token-view-balance/index';
+
+@import './transaction-list/index';
+
+@import './transaction-list-item/index';
+
+@import './transaction-status/index';
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/home.component.js b/ui/app/components/pages/home/home.component.js
index 20ba44484..dae9790de 100644
--- a/ui/app/components/pages/home/home.component.js
+++ b/ui/app/components/pages/home/home.component.js
@@ -4,6 +4,7 @@ import Media from 'react-media'
import { Redirect } from 'react-router-dom'
import WalletView from '../../wallet-view'
import TxView from '../../tx-view'
+import TokenView from '../../token-view'
import {
INITIALIZE_BACKUP_PHRASE_ROUTE,
RESTORE_VAULT_ROUTE,
@@ -14,28 +15,17 @@ import {
export default class Home extends PureComponent {
static propTypes = {
history: PropTypes.object,
- unapprovedTxs: PropTypes.object,
- unapprovedMsgCount: PropTypes.number,
- unapprovedPersonalMsgCount: PropTypes.number,
- unapprovedTypedMessagesCount: PropTypes.number,
noActiveNotices: PropTypes.bool,
lostAccounts: PropTypes.array,
forgottenPassword: PropTypes.bool,
seedWords: PropTypes.string,
+ unconfirmedTransactionsCount: PropTypes.number,
}
componentDidMount () {
- const {
- history,
- unapprovedTxs = {},
- unapprovedMsgCount = 0,
- unapprovedPersonalMsgCount = 0,
- unapprovedTypedMessagesCount = 0,
- } = this.props
+ const { history, unconfirmedTransactionsCount = 0 } = this.props
- // unapprovedTxs and unapproved messages
- if (Object.keys(unapprovedTxs).length ||
- unapprovedTypedMessagesCount + unapprovedMsgCount + unapprovedPersonalMsgCount > 0) {
+ if (unconfirmedTransactionsCount > 0) {
history.push(CONFIRM_TRANSACTION_ROUTE)
}
}
@@ -69,7 +59,8 @@ export default class Home extends PureComponent {
query="(min-width: 576px)"
render={() => <WalletView />}
/>
- <TxView />
+ <TokenView />
+ {/* <TxView /> */}
</div>
</div>
)
diff --git a/ui/app/components/pages/home/home.container.js b/ui/app/components/pages/home/home.container.js
index 96a45a69b..b0e34f832 100644
--- a/ui/app/components/pages/home/home.container.js
+++ b/ui/app/components/pages/home/home.container.js
@@ -2,14 +2,11 @@ import Home from './home.component'
import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
+import { unconfirmedTransactionsCountSelector } from '../../../selectors/confirm-transaction'
const mapStateToProps = state => {
const { metamask, appState } = state
const {
- unapprovedTxs = {},
- unapprovedMsgCount = 0,
- unapprovedPersonalMsgCount = 0,
- unapprovedTypedMessagesCount = 0,
noActiveNotices,
lostAccounts,
seedWords,
@@ -17,14 +14,11 @@ const mapStateToProps = state => {
const { forgottenPassword } = appState
return {
- unapprovedTxs,
- unapprovedMsgCount,
- unapprovedPersonalMsgCount,
- unapprovedTypedMessagesCount,
noActiveNotices,
lostAccounts,
forgottenPassword,
seedWords,
+ unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
}
}
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-view-balance/index.js b/ui/app/components/token-view-balance/index.js
new file mode 100644
index 000000000..e0509096a
--- /dev/null
+++ b/ui/app/components/token-view-balance/index.js
@@ -0,0 +1 @@
+export { default } from './token-view-balance.container'
diff --git a/ui/app/components/token-view-balance/index.scss b/ui/app/components/token-view-balance/index.scss
new file mode 100644
index 000000000..6a89e125b
--- /dev/null
+++ b/ui/app/components/token-view-balance/index.scss
@@ -0,0 +1,66 @@
+.token-view-balance {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex: 1;
+ height: 54px;
+
+ &__balance {
+ margin-left: 12px;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: $break-small) {
+ align-items: center;
+ margin: 16px 0;
+ }
+ }
+
+ &__primary-balance {
+ font-size: 1.5rem;
+
+ @media screen and (max-width: $break-small) {
+ margin-bottom: 12px;
+ font-size: 1.75rem;
+ }
+ }
+
+ &__secondary-balance {
+ font-size: 1.15rem;
+ color: #a0a0a0;
+ }
+
+ &__balance-container {
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ }
+ }
+
+ &__buttons {
+ display: flex;
+ flex-direction: row;
+
+ @media screen and (max-width: $break-small) {
+ margin-bottom: 16px;
+ }
+ }
+
+ &__button {
+ min-width: initial;
+ width: 100px;
+
+ &:not(:last-child) {
+ margin-right: 12px;
+ }
+ }
+
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ height: initial
+ }
+}
diff --git a/ui/app/components/token-view-balance/token-view-balance.component.js b/ui/app/components/token-view-balance/token-view-balance.component.js
new file mode 100644
index 000000000..6b8140a22
--- /dev/null
+++ b/ui/app/components/token-view-balance/token-view-balance.component.js
@@ -0,0 +1,92 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../button'
+import Identicon from '../identicon'
+import TokenBalance from '../token-balance'
+import { SEND_ROUTE } from '../../routes'
+import { formatCurrency } from '../../helpers/confirm-transaction/util'
+
+export default class TokenViewBalance extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ showDepositModal: PropTypes.func,
+ selectedToken: PropTypes.object,
+ history: PropTypes.object,
+ network: PropTypes.string,
+ ethBalance: PropTypes.string,
+ fiatBalance: PropTypes.string,
+ currentCurrency: PropTypes.string,
+ }
+
+ renderBalance () {
+ const { selectedToken, ethBalance, fiatBalance, currentCurrency } = this.props
+ const formattedFiatBalance = formatCurrency(fiatBalance, currentCurrency)
+
+ return selectedToken
+ ? (
+ <TokenBalance
+ token={selectedToken}
+ withSymbol
+ className="token-view-balance__primary-balance"
+ />
+ ) : (
+ <div className="token-view-balance__balance">
+ <div className="token-view-balance__primary-balance">
+ { `${ethBalance} ETH` }
+ </div>
+ <div className="token-view-balance__secondary-balance">
+ { formattedFiatBalance }
+ </div>
+ </div>
+ )
+ }
+
+ renderButtons () {
+ const { t } = this.context
+ const { selectedToken, showDepositModal, history } = this.props
+
+ return (
+ <div className="token-view-balance__buttons">
+ {
+ !selectedToken && (
+ <Button
+ type="primary"
+ className="token-view-balance__button"
+ onClick={() => showDepositModal()}
+ >
+ { t('deposit') }
+ </Button>
+ )
+ }
+ <Button
+ type="primary"
+ className="token-view-balance__button"
+ onClick={() => history.push(SEND_ROUTE)}
+ >
+ { t('send') }
+ </Button>
+ </div>
+ )
+ }
+
+ render () {
+ const { network, selectedToken } = this.props
+
+ return (
+ <div className="token-view-balance">
+ <div className="token-view-balance__balance-container">
+ <Identicon
+ diameter={50}
+ address={selectedToken && selectedToken.address}
+ network={network}
+ />
+ { this.renderBalance() }
+ </div>
+ { this.renderButtons() }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/token-view-balance/token-view-balance.container.js b/ui/app/components/token-view-balance/token-view-balance.container.js
new file mode 100644
index 000000000..692e6e32f
--- /dev/null
+++ b/ui/app/components/token-view-balance/token-view-balance.container.js
@@ -0,0 +1,42 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import TokenViewBalance from './token-view-balance.component'
+import { getSelectedToken, getSelectedAddress } from '../../selectors'
+import { showModal } from '../../actions'
+import { getValueFromWeiHex } from '../../helpers/confirm-transaction/util'
+
+const mapStateToProps = state => {
+ const selectedAddress = getSelectedAddress(state)
+ const { metamask } = state
+ const { network, accounts, currentCurrency, conversionRate } = metamask
+ const account = accounts[selectedAddress]
+ const { balance: value } = account
+
+ const ethBalance = getValueFromWeiHex({
+ value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 3,
+ })
+
+ const fiatBalance = getValueFromWeiHex({
+ value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
+ })
+
+ return {
+ selectedToken: getSelectedToken(state),
+ network,
+ ethBalance,
+ fiatBalance,
+ currentCurrency,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ showDepositModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(TokenViewBalance)
diff --git a/ui/app/components/token-view/index.js b/ui/app/components/token-view/index.js
new file mode 100644
index 000000000..f49cb034f
--- /dev/null
+++ b/ui/app/components/token-view/index.js
@@ -0,0 +1 @@
+export { default } from './token-view.component'
diff --git a/ui/app/components/token-view/index.scss b/ui/app/components/token-view/index.scss
new file mode 100644
index 000000000..438147ad9
--- /dev/null
+++ b/ui/app/components/token-view/index.scss
@@ -0,0 +1,27 @@
+.token-view {
+ flex: 1 1 66.5%;
+ background: $white;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+
+ &__balance-wrapper {
+ @media screen and (max-width: $break-small) {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ flex: 0 0 auto;
+ padding-top: 16px;
+ }
+
+ @media screen and (min-width: $break-large) {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ margin: 2.3em 2.37em .8em;
+ flex: 0 0 auto;
+ }
+ }
+}
diff --git a/ui/app/components/token-view/token-view.component.js b/ui/app/components/token-view/token-view.component.js
new file mode 100644
index 000000000..3e1a4a0c3
--- /dev/null
+++ b/ui/app/components/token-view/token-view.component.js
@@ -0,0 +1,28 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Media from 'react-media'
+import MenuBar from '../menu-bar'
+import TokenViewBalance from '../token-view-balance'
+// import TransactionList from '../tx-list'
+import TransactionList from '../transaction-list'
+
+export default class TokenView extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ render () {
+ return (
+ <div className="token-view">
+ <Media
+ query="(max-width: 575px)"
+ render={() => <MenuBar />}
+ />
+ <div className="token-view__balance-wrapper">
+ <TokenViewBalance />
+ </div>
+ <TransactionList />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-action/index.js b/ui/app/components/transaction-action/index.js
new file mode 100644
index 000000000..5882443b6
--- /dev/null
+++ b/ui/app/components/transaction-action/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-action.container'
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..b608615d0
--- /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()
+ }
+
+ getTransactionAction () {
+ const { transactionAction } = this.state
+ const { transaction, methodData } = this.props
+ const { data, isFetching } = methodData
+
+ if (isFetching || transactionAction) {
+ return
+ }
+
+ const actionKey = getTransactionActionKey(transaction, data)
+ const action = actionKey && this.context.tOrDefault(actionKey)
+ this.setState({ transactionAction: action })
+ }
+
+ render () {
+ const { className } = this.props
+ const { transactionAction } = this.state
+
+ return (
+ <div className={className}>
+ { transactionAction || '--' }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/transaction-action/transaction-action.container.js b/ui/app/components/transaction-action/transaction-action.container.js
new file mode 100644
index 000000000..56efbdc26
--- /dev/null
+++ b/ui/app/components/transaction-action/transaction-action.container.js
@@ -0,0 +1,4 @@
+import withMethodData from '../../higher-order-components/with-method-data'
+import TransactionAction from './transaction-action.component'
+
+export default withMethodData(TransactionAction)
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..8a3973f92
--- /dev/null
+++ b/ui/app/components/transaction-list-item/index.scss
@@ -0,0 +1,71 @@
+.transaction-list-item {
+ box-sizing: border-box;
+ height: 74px;
+ padding: 0 21px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ border-bottom: 1px solid $geyser;
+ cursor: pointer;
+
+ @media screen and (max-width: $break-small) {
+ padding: 0 12px;
+ }
+
+ &__identicon-wrapper {
+ padding-top: 2px;
+ }
+
+ &__action-block {
+ padding: 0 8px 0 12px;
+ width: 180px;
+
+ @media screen and (max-width: $break-small) {
+ padding: 0 8px;
+ width: 160px;
+ }
+ }
+
+ &__action {
+ text-transform: capitalize;
+ padding-bottom: 2px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ @media screen and (max-width: $break-small) {
+ padding-bottom: 0;
+ font-size: .875rem;
+ }
+ }
+
+ &__nonce {
+ font-size: .75rem;
+ color: #5e6064;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__transaction-amounts {
+ flex: 1;
+ }
+
+ &__primary-transaction-amount {
+ text-align: end;
+
+ @media screen and (max-width: $break-small) {
+ font-size: .75rem;
+ }
+ }
+
+ &__secondary-transaction-amount {
+ text-align: end;
+ font-size: .75rem;
+ color: #5e6064;
+ }
+
+ &:hover {
+ background: rgba($alto, .2);
+ }
+}
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..e334cd938
--- /dev/null
+++ b/ui/app/components/transaction-list-item/transaction-list-item.component.js
@@ -0,0 +1,82 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Media from 'react-media'
+import Identicon from '../identicon'
+import TransactionStatus from '../transaction-status'
+import TransactionAction from '../transaction-action'
+import { formatDate } from '../../util'
+import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
+import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
+import { UNAPPROVED_STATUS } from '../../constants/transactions'
+import { hexToDecimal } from '../../helpers/conversions.util'
+
+export default class TransactionListItem extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ methodData: PropTypes.object,
+ transaction: PropTypes.object,
+ ethTransactionAmount: PropTypes.string,
+ fiatDisplayValue: PropTypes.string,
+ }
+
+ handleClick = () => {
+ const { transaction, history } = this.props
+ const { id, status, hash, metamaskNetworkId } = transaction
+
+ if (status === UNAPPROVED_STATUS) {
+ history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)
+ } else if (hash) {
+ const prefix = prefixForNetwork(metamaskNetworkId)
+ const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
+ global.platform.openWindow({ url: etherscanUrl })
+ }
+ }
+
+ render () {
+ const {
+ transaction,
+ ethTransactionAmount,
+ fiatDisplayValue,
+ } = this.props
+ const { txParams = {} } = transaction
+ const nonce = hexToDecimal(txParams.nonce)
+
+ return (
+ <div
+ className="transaction-list-item"
+ onClick={this.handleClick}
+ >
+ <div className="transaction-list-item__identicon-wrapper">
+ <Media query="(max-width: 575px)">
+ {
+ matches => (
+ <Identicon
+ address={txParams.to}
+ diameter={matches ? 26 : 34}
+ />
+ )
+ }
+ </Media>
+ </div>
+ <div className="transaction-list-item__action-block">
+ <TransactionAction
+ transaction={transaction}
+ className="transaction-list-item__action"
+ />
+ <div className="transaction-list-item__nonce">
+ { `#${nonce} - ${formatDate(transaction.time)}` }
+ </div>
+ </div>
+ <TransactionStatus status={transaction.status} />
+ <div className="transaction-list-item__transaction-amounts">
+ <div className="transaction-list-item__primary-transaction-amount">
+ { `-${fiatDisplayValue}` }
+ </div>
+ <div className="transaction-list-item__secondary-transaction-amount">
+ { `-${ethTransactionAmount} ETH` }
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
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..bc47f20aa
--- /dev/null
+++ b/ui/app/components/transaction-list-item/transaction-list-item.container.js
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import TransactionListItem from './transaction-list-item.component'
+import { getEthFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
+import { formatCurrency } from '../../helpers/confirm-transaction/util'
+
+const mapStateToProps = (state, ownProps) => {
+ const { metamask } = state
+ const { currentCurrency, conversionRate } = metamask
+ const { transaction: { txParams: { value } = {} } = {} } = ownProps
+ const ethTransactionAmount = getEthFromWeiHex({ value, conversionRate })
+ const fiatTransactionAmount = getValueFromWeiHex({
+ value, conversionRate, toCurrency: currentCurrency, numberOfDecimals: 2,
+ })
+ const fiatFormattedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
+ const fiatDisplayValue = `${fiatFormattedAmount} ${currentCurrency.toUpperCase()}`
+
+ return {
+ ethTransactionAmount,
+ fiatDisplayValue,
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps),
+)(TransactionListItem)
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..f6f209831
--- /dev/null
+++ b/ui/app/components/transaction-list/index.scss
@@ -0,0 +1,40 @@
+.transaction-list {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow-y: hidden;
+
+ &__header {
+ flex: 0 0 auto;
+ font-size: .875rem;
+ color: $dusty-gray;
+ border-bottom: 1px solid $geyser;
+ padding: 16px 0 8px 20px;
+
+ @media screen and (max-width: $break-small) {
+ padding: 8px 0 8px 16px;
+ }
+ }
+
+ &__transactions {
+ flex: 1;
+ overflow-y: auto;
+ }
+
+ &__pending-transactions {
+ margin-bottom: 16px;
+ }
+
+ &__empty {
+ flex: 1;
+ display: grid;
+ grid-template-rows: 35% 1fr;
+ }
+
+ &__empty-text {
+ grid-row-start: 2;
+ display: flex;
+ justify-content: center;
+ color: $silver;
+ }
+}
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..63d171127
--- /dev/null
+++ b/ui/app/components/transaction-list/transaction-list.component.js
@@ -0,0 +1,90 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import TransactionListItem from '../transaction-list-item'
+
+export default class TransactionList extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static defaultProps = {
+ pendingTransactions: [],
+ completedTransactions: [],
+ }
+
+ static propTypes = {
+ pendingTransactions: PropTypes.array,
+ completedTransactions: PropTypes.array,
+ }
+
+ renderTransactions () {
+ const { t } = this.context
+ const { pendingTransactions, completedTransactions } = this.props
+
+ return (
+ <div className="transaction-list__transactions">
+ {
+ pendingTransactions.length > 0 && (
+ <div className="transaction-list__pending-transactions">
+ <div className="transaction-list__header">
+ { `${t('pending')} (${pendingTransactions.length})` }
+ </div>
+ {
+ pendingTransactions.map(transaction => {
+ return (
+ <TransactionListItem
+ transaction={transaction}
+ key={transaction.id}
+ />
+ )
+ })
+ }
+ </div>
+ )
+ }
+ <div className="transaction-list__completed-transactions">
+ <div className="transaction-list__header">
+ { t('history') }
+ </div>
+ {
+ completedTransactions.length > 0
+ ? (
+ completedTransactions.map(transaction => {
+ return (
+ <TransactionListItem
+ transaction={transaction}
+ key={transaction.id}
+ />
+ )
+ })
+ )
+ : this.renderEmpty()
+ }
+ </div>
+ </div>
+ )
+ }
+
+ renderEmpty () {
+ return (
+ <div className="transaction-list__empty">
+ <div className="transaction-list__empty-text">
+ { this.context.t('noTransactions') }
+ </div>
+ </div>
+ )
+ }
+
+ render () {
+ return (
+ <div className="transaction-list">
+ {
+ this.renderTransactions()
+ // pendingTransactions.length + completedTransactions.length > 0
+ // ? this.renderTransactions()
+ // : this.renderEmpty()
+ }
+ </div>
+ )
+ }
+}
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..b1c2c04c9
--- /dev/null
+++ b/ui/app/components/transaction-list/transaction-list.container.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import { compose } from 'recompose'
+import TransactionList from './transaction-list.component'
+import {
+ pendingTransactionsSelector,
+ completedTransactionsSelector,
+} from '../../selectors/transactions'
+
+const mapStateToProps = state => {
+ return {
+ pendingTransactions: pendingTransactionsSelector(state),
+ completedTransactions: completedTransactionsSelector(state),
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps)
+)(TransactionList)
diff --git a/ui/app/components/transaction-status/index.scss b/ui/app/components/transaction-status/index.scss
index dd9bf5877..03a378b4e 100644
--- a/ui/app/components/transaction-status/index.scss
+++ b/ui/app/components/transaction-status/index.scss
@@ -10,6 +10,12 @@
justify-content: center;
align-items: center;
+ @media screen and (max-width: $break-small) {
+ height: 24px;
+ width: 74px;
+ font-size: .5rem;
+ }
+
&--confirmed {
background-color: #eafad7;
color: #609a1c;
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index 654090da6..aa540249f 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -15,6 +15,9 @@ const Tooltip = require('./tooltip')
const TxList = require('./tx-list')
const SelectedAccount = require('./selected-account')
+import Media from 'react-media'
+import MenuBar from './menu-bar'
+
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
@@ -104,49 +107,11 @@ TxView.prototype.renderButtons = function () {
}
TxView.prototype.render = function () {
- const { hideSidebar, isMascara, showSidebar, sidebarOpen } = this.props
- const { t } = this.context
-
- return h('div.tx-view.flex-column', {
- style: {},
- }, [
-
- h('div.flex-row.phone-visible', {
- style: {
- justifyContent: 'center',
- alignItems: 'center',
- flex: '0 0 auto',
- marginBottom: '16px',
- padding: '5px',
- borderBottom: '1px solid #e5e5e5',
- },
- }, [
-
- h(Tooltip, {
- title: t('menu'),
- position: 'bottom',
- }, [
- h('div.fa.fa-bars', {
- style: {
- fontSize: '1.3em',
- cursor: 'pointer',
- padding: '10px',
- },
- onClick: () => sidebarOpen ? hideSidebar() : showSidebar(),
- }),
- ]),
-
- 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' })]),
- ]),
- ]),
+ return h('div.tx-view.flex-column', [
+ h(Media, {
+ query: '(max-width: 575px)',
+ render: () => h(MenuBar),
+ }),
this.renderHeroBalance(),
diff --git a/ui/app/constants/transactions.js b/ui/app/constants/transactions.js
new file mode 100644
index 000000000..8b843ba2b
--- /dev/null
+++ b/ui/app/constants/transactions.js
@@ -0,0 +1,18 @@
+export const UNAPPROVED_STATUS = 'unapproved'
+export const REJECTED_STATUS = 'rejected'
+export const APPROVED_STATUS = 'approved'
+export const SIGNED_STATUS = 'signed'
+export const SUBMITTED_STATUS = 'submitted'
+export const CONFIRMED_STATUS = 'confirmed'
+export const FAILED_STATUS = 'failed'
+export const DROPPED_STATUS = 'dropped'
+
+export const TOKEN_METHOD_TRANSFER = 'transfer'
+export const TOKEN_METHOD_APPROVE = 'approve'
+export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'
+
+export const SEND_ETHER_ACTION_KEY = 'sendEther'
+export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment'
+export const APPROVE_ACTION_KEY = 'approve'
+export const SEND_TOKEN_ACTION_KEY = 'sendToken'
+export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss
deleted file mode 100644
index eba93ecb4..000000000
--- a/ui/app/css/itcss/components/hero-balance.scss
+++ /dev/null
@@ -1,130 +0,0 @@
-.hero-balance {
-
- @media screen and (max-width: $break-small) {
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- align-items: center;
- flex: 0 0 auto;
- padding-top: 16px;
- }
-
- @media screen and (min-width: $break-large) {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- align-items: center;
- margin: 2.3em 2.37em .8em;
- flex: 0 0 auto;
- }
-
- .balance-container {
- display: flex;
- margin: 0;
- justify-content: flex-start;
- align-items: center;
-
- @media screen and (max-width: $break-small) {
- flex-direction: column;
- flex: 0 0 auto;
- max-width: 100%;
- }
-
- @media screen and (min-width: $break-large) {
- flex-direction: row;
- flex-grow: 3;
- min-width: 0;
- }
- }
-
- .balance-display {
- .token-amount {
- color: $black;
- max-width: 100%;
-
- .token-balance {
- display: flex;
- }
- }
-
- @media screen and (max-width: $break-small) {
- max-width: 100%;
- text-align: center;
-
- .token-amount {
- font-size: 1.75rem;
- margin-top: 1rem;
-
- .token-balance {
- flex-direction: column;
- }
- }
-
- .fiat-amount {
- font-size: 115%;
- margin-top: 8.5%;
- color: #a0a0a0;
- }
- }
-
- @media screen and (min-width: $break-large) {
- margin: 0 .8em;
- justify-content: flex-start;
- align-items: flex-start;
- min-width: 0;
-
- .token-amount {
- font-size: 1.5rem;
- }
-
- .fiat-amount {
- margin-top: .25%;
- font-size: 105%;
- }
- }
-
- @media #{$sub-mid-size-breakpoint-range} {
- margin-left: .4em;
- margin-right: .4em;
- justify-content: flex-start;
- align-items: flex-start;
-
- .token-amount {
- font-size: 1rem;
- }
-
- .fiat-amount {
- margin-top: .25%;
- font-size: 1rem;
- }
- }
- }
-
- .hero-balance-buttons {
-
- @media screen and (max-width: $break-small) {
- width: 100%;
- // height: 100px; // needed a round number to set the heights of the buttons inside
- flex: 0 0 auto;
- padding: 16px 0;
- }
-
- @media screen and (min-width: $break-large) {
- flex-grow: 2;
- justify-content: flex-end;
- }
- }
-}
-
-.hero-balance-button {
- min-width: initial;
- width: 6rem;
-
- @media #{$sub-mid-size-breakpoint-range} {
- padding: .4rem;
- width: 4rem;
- display: flex;
- flex: 1;
- justify-content: center;
- }
-}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index 821a6b612..9e2008b54 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -19,8 +19,6 @@
@import './loading-overlay.scss';
// Balances
-@import './hero-balance.scss';
-
@import './wallet-balance.scss';
// Tx List and Sections
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index bbfd85c90..7ad5cd076 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -49,13 +49,6 @@ $wallet-view-bg: $alabaster;
}
}
-.open-in-browser {
- cursor: pointer;
- display: flex;
- justify-content: center;
- padding: 10px;
-}
-
// wallet view and sidebar
.wallet-view {
diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js
index f17933ddd..eb56d5695 100644
--- a/ui/app/ducks/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction.duck.js
@@ -6,8 +6,7 @@ import {
import {
getTokenData,
- getMethodData,
- getTransactionAmount,
+ getValueFromWeiHex,
getTransactionFee,
getHexGasTotal,
addFiat,
@@ -17,6 +16,7 @@ import {
isSmartContractAddress,
} from '../helpers/confirm-transaction/util'
+import { getMethodData } from '../helpers/transactions.util'
import { getSymbolAndDecimals } from '../token-util'
import { conversionUtil } from '../conversion-util'
@@ -301,10 +301,10 @@ export function updateTxDataAndCalculate (txData) {
const { txParams: { value, gas: gasLimit = '0x0', gasPrice = '0x0' } = {} } = txData
- const fiatTransactionAmount = getTransactionAmount({
+ const fiatTransactionAmount = getValueFromWeiHex({
value, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
- const ethTransactionAmount = getTransactionAmount({
+ const ethTransactionAmount = getValueFromWeiHex({
value, toCurrency: 'ETH', conversionRate, numberOfDecimals: 6,
})
diff --git a/ui/app/helpers/confirm-transaction/util.js b/ui/app/helpers/confirm-transaction/util.js
index 3d0cb57e7..04978b48f 100644
--- a/ui/app/helpers/confirm-transaction/util.js
+++ b/ui/app/helpers/confirm-transaction/util.js
@@ -7,9 +7,6 @@ import BigNumber from 'bignumber.js'
abiDecoder.addABI(abi)
-import MethodRegistry from 'eth-method-registry'
-const registry = new MethodRegistry({ provider: global.ethereumProvider })
-
import {
conversionUtil,
addCurrencies,
@@ -23,18 +20,6 @@ export function getTokenData (data = {}) {
return abiDecoder.decodeMethod(data)
}
-export async function getMethodData (data = {}) {
- const prefixedData = ethUtil.addHexPrefix(data)
- const fourBytePrefix = prefixedData.slice(0, 10)
- const sig = await registry.lookup(fourBytePrefix)
- const parsedResult = registry.parse(sig)
-
- return {
- name: parsedResult.name,
- params: parsedResult.args,
- }
-}
-
export function increaseLastGasPrice (lastGasPrice) {
return ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, {
multiplicandBase: 16,
@@ -76,7 +61,7 @@ export function addFiat (...args) {
})
}
-export function getTransactionAmount ({
+export function getValueFromWeiHex ({
value,
toCurrency,
conversionRate,
diff --git a/ui/app/helpers/confirm-transaction/util.test.js b/ui/app/helpers/confirm-transaction/util.test.js
index a9c8fae34..4c1a3e16b 100644
--- a/ui/app/helpers/confirm-transaction/util.test.js
+++ b/ui/app/helpers/confirm-transaction/util.test.js
@@ -92,9 +92,9 @@ describe('Confirm Transaction utils', () => {
})
})
- describe('getTransactionAmount', () => {
+ describe('getValueFromWeiHex', () => {
it('should get the transaction amount in ETH', () => {
- const ethTransactionAmount = utils.getTransactionAmount({
+ const ethTransactionAmount = utils.getValueFromWeiHex({
value: '0xde0b6b3a7640000', toCurrency: 'ETH', conversionRate: 468.58, numberOfDecimals: 6,
})
@@ -102,7 +102,7 @@ describe('Confirm Transaction utils', () => {
})
it('should get the transaction amount in fiat', () => {
- const fiatTransactionAmount = utils.getTransactionAmount({
+ const fiatTransactionAmount = utils.getValueFromWeiHex({
value: '0xde0b6b3a7640000', toCurrency: 'usd', conversionRate: 468.58, numberOfDecimals: 2,
})
diff --git a/ui/app/helpers/conversions.util.js b/ui/app/helpers/conversions.util.js
new file mode 100644
index 000000000..1dec216fa
--- /dev/null
+++ b/ui/app/helpers/conversions.util.js
@@ -0,0 +1,37 @@
+import { conversionUtil } from '../conversion-util'
+
+export function hexToDecimal (hexValue) {
+ return conversionUtil(hexValue, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ })
+}
+
+export function getEthFromWeiHex ({
+ value,
+ conversionRate,
+}) {
+ return getValueFromWeiHex({
+ value,
+ conversionRate,
+ toCurrency: 'ETH',
+ numberOfDecimals: 6,
+ })
+}
+
+export function getValueFromWeiHex ({
+ value,
+ toCurrency,
+ conversionRate,
+ numberOfDecimals,
+}) {
+ return conversionUtil(value, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency,
+ numberOfDecimals,
+ fromDenomination: 'WEI',
+ conversionRate,
+ })
+}
diff --git a/ui/app/helpers/transactions.util.js b/ui/app/helpers/transactions.util.js
new file mode 100644
index 000000000..04cef150f
--- /dev/null
+++ b/ui/app/helpers/transactions.util.js
@@ -0,0 +1,57 @@
+import ethUtil from 'ethereumjs-util'
+import MethodRegistry from 'eth-method-registry'
+const registry = new MethodRegistry({ provider: global.ethereumProvider })
+
+import {
+ TOKEN_METHOD_TRANSFER,
+ TOKEN_METHOD_APPROVE,
+ TOKEN_METHOD_TRANSFER_FROM,
+ SEND_ETHER_ACTION_KEY,
+ DEPLOY_CONTRACT_ACTION_KEY,
+ APPROVE_ACTION_KEY,
+ SEND_TOKEN_ACTION_KEY,
+ TRANSFER_FROM_ACTION_KEY,
+} from '../constants/transactions'
+
+export function isConfirmDeployContract (txData = {}) {
+ const { txParams = {} } = txData
+ return !txParams.to
+}
+
+export function getTransactionActionKey (transaction, methodData) {
+ const { txParams: { data } = {} } = transaction
+
+ if (isConfirmDeployContract(transaction)) {
+ return DEPLOY_CONTRACT_ACTION_KEY
+ }
+
+ if (data) {
+ const { name } = methodData
+ const methodName = name && name.toLowerCase()
+
+ switch (methodName) {
+ case TOKEN_METHOD_TRANSFER:
+ return SEND_TOKEN_ACTION_KEY
+ case TOKEN_METHOD_APPROVE:
+ return APPROVE_ACTION_KEY
+ case TOKEN_METHOD_TRANSFER_FROM:
+ return TRANSFER_FROM_ACTION_KEY
+ default:
+ return name
+ }
+ } else {
+ return SEND_ETHER_ACTION_KEY
+ }
+}
+
+export async function getMethodData (data = {}) {
+ const prefixedData = ethUtil.addHexPrefix(data)
+ const fourBytePrefix = prefixedData.slice(0, 10)
+ const sig = await registry.lookup(fourBytePrefix)
+ const parsedResult = registry.parse(sig)
+
+ return {
+ name: parsedResult.name,
+ params: parsedResult.args,
+ }
+}
diff --git a/ui/app/higher-order-components/with-method-data/with-method-data.component.js b/ui/app/higher-order-components/with-method-data/with-method-data.component.js
index aa38afd8a..c05d33c20 100644
--- a/ui/app/higher-order-components/with-method-data/with-method-data.component.js
+++ b/ui/app/higher-order-components/with-method-data/with-method-data.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { getMethodData } from '../../helpers/confirm-transaction/util'
+import { getMethodData } from '../../helpers/transactions.util'
export default function withMethodData (WrappedComponent) {
return class MethodDataWrappedComponent extends PureComponent {
@@ -13,7 +13,11 @@ export default function withMethodData (WrappedComponent) {
}
state = {
- methodData: {},
+ methodData: {
+ data: {},
+ },
+ isFetching: false,
+ error: null,
}
componentDidMount () {
@@ -25,18 +29,24 @@ export default function withMethodData (WrappedComponent) {
const { txParams: { data = '' } = {} } = transaction
if (data) {
- const methodData = await getMethodData(data)
- this.setState({ methodData })
+ this.setState({ isFetching: true })
+
+ try {
+ const methodData = await getMethodData(data)
+ this.setState({ methodData, isFetching: false })
+ } catch (error) {
+ this.setState({ isFetching: false, error })
+ }
}
}
render () {
- const { methodData } = this.state
+ const { methodData, isFetching, error } = this.state
return (
<WrappedComponent
{ ...this.props }
- methodData={methodData}
+ methodData={{ data: methodData, isFetching, error }}
/>
)
}
diff --git a/ui/app/i18n-provider.js b/ui/app/i18n-provider.js
index d46911f7c..936b185f7 100644
--- a/ui/app/i18n-provider.js
+++ b/ui/app/i18n-provider.js
@@ -13,6 +13,9 @@ class I18nProvider extends Component {
t (key, ...args) {
return t(current, key, ...args) || t(en, key, ...args) || `[${key}]`
},
+ tOrDefault (key, ...args) {
+ return t(current, key, ...args) || t(en, key, ...args) || key
+ },
}
}
@@ -28,6 +31,7 @@ I18nProvider.propTypes = {
I18nProvider.childContextTypes = {
t: PropTypes.func,
+ tOrDefault: PropTypes.func,
}
const mapStateToProps = state => {
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index d86462275..1b0100297 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -1,5 +1,9 @@
-const valuesFor = require('./util').valuesFor
const abi = require('human-standard-token-abi')
+import { createSelector } from 'reselect'
+
+import {
+ transactionsSelector,
+} from './selectors/transactions'
const {
multiplyCurrencies,
@@ -101,21 +105,49 @@ function getCurrentAccountWithSendEtherInfo (state) {
return accounts.find(({ address }) => address === currentAddress)
}
-function transactionsSelector (state) {
- const { network, selectedTokenAddress } = state.metamask
- const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
- const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined
- const transactions = state.metamask.selectedAddressTxList || []
- const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
+// // function shapeShiftTxListSelector (state) {
+// // return state.metamask.shapeShiftTxList || []
+// // }
+
+// const transactionsSelector = createSelector(
+// selectedTokenAddressSelector,
+// unapprovedMsgsSelector,
+// shapeShiftTxListSelector,
+// selectedAddressTxListSelector,
+// (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => {
+// const unapprovedMsgsList = valuesFor(unapprovedMsgs)
+// const txsToRender = transactions.concat(unapprovedMsgsList, shapeShiftTxList)
+
+// return selectedTokenAddress
+// ? txsToRender
+// .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
+// .sort((a, b) => b.time - a.time)
+// : txsToRender
+// .sort((a, b) => b.time - a.time)
+// }
+// )
+
+// // function transactionsSelector (state) {
+// // const { selectedTokenAddress } = state.metamask
+// // const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
+// // const shapeShiftTxList = shapeShiftTxListSelector(state)
+// // const transactions = state.metamask.selectedAddressTxList || []
+// // const txsToRender = transactions.concat(unapprovedMsgs, shapeShiftTxList)
+
+// // return selectedTokenAddress
+// // ? txsToRender
+// // .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
+// // .sort((a, b) => b.time - a.time)
+// // : txsToRender
+// // .sort((a, b) => b.time - a.time)
+// // }
+
+export const pendingTransactionsSelector = createSelector(
+ transactionsSelector,
+ transactions => {
- // console.log({txsToRender, selectedTokenAddress})
- return selectedTokenAddress
- ? txsToRender
- .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
- .sort((a, b) => b.time - a.time)
- : txsToRender
- .sort((a, b) => b.time - a.time)
-}
+ }
+)
function getGasIsLoading (state) {
return state.appState.gasIsLoading
diff --git a/ui/app/selectors/transactions.js b/ui/app/selectors/transactions.js
new file mode 100644
index 000000000..a265b8e70
--- /dev/null
+++ b/ui/app/selectors/transactions.js
@@ -0,0 +1,50 @@
+import { createSelector } from 'reselect'
+import { valuesFor } from '../util'
+import {
+ UNAPPROVED_STATUS,
+ APPROVED_STATUS,
+ SUBMITTED_STATUS,
+} from '../constants/transactions'
+
+export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList
+export const selectedTokenAddressSelector = state => state.metamask.selectedTokenAddress
+export const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
+export const selectedAddressTxListSelector = state => state.metamask.selectedAddressTxList
+
+const pendingStatusHash = {
+ [UNAPPROVED_STATUS]: true,
+ [APPROVED_STATUS]: true,
+ [SUBMITTED_STATUS]: true,
+}
+
+export const transactionsSelector = createSelector(
+ selectedTokenAddressSelector,
+ unapprovedMsgsSelector,
+ shapeShiftTxListSelector,
+ selectedAddressTxListSelector,
+ (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => {
+ const unapprovedMsgsList = valuesFor(unapprovedMsgs)
+ const txsToRender = transactions.concat(unapprovedMsgsList, shapeShiftTxList)
+
+ return selectedTokenAddress
+ ? txsToRender
+ .filter(({ txParams }) => txParams && txParams.to === selectedTokenAddress)
+ .sort((a, b) => b.time - a.time)
+ : txsToRender
+ .sort((a, b) => b.time - a.time)
+ }
+)
+
+export const pendingTransactionsSelector = createSelector(
+ transactionsSelector,
+ (transactions = []) => (
+ transactions.filter(transaction => transaction.status in pendingStatusHash)
+ )
+)
+
+export const completedTransactionsSelector = createSelector(
+ transactionsSelector,
+ (transactions = []) => (
+ transactions.filter(transaction => !(transaction.status in pendingStatusHash))
+ )
+)