diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/app/account-detail.js | 17 | ||||
-rw-r--r-- | ui/app/accounts/import/index.js | 91 | ||||
-rw-r--r-- | ui/app/accounts/import/json.js | 98 | ||||
-rw-r--r-- | ui/app/accounts/import/private-key.js | 68 | ||||
-rw-r--r-- | ui/app/accounts/import/seed.js | 30 | ||||
-rw-r--r-- | ui/app/accounts/index.js | 10 | ||||
-rw-r--r-- | ui/app/actions.js | 45 | ||||
-rw-r--r-- | ui/app/app.js | 16 | ||||
-rw-r--r-- | ui/app/components/buy-button-subview.js | 82 | ||||
-rw-r--r-- | ui/app/components/coinbase-form.js | 8 | ||||
-rw-r--r-- | ui/app/components/loading.js | 8 | ||||
-rw-r--r-- | ui/app/components/pending-tx-details.js | 12 | ||||
-rw-r--r-- | ui/app/components/tab-bar.js | 35 | ||||
-rw-r--r-- | ui/app/components/transaction-list-item-icon.js | 39 | ||||
-rw-r--r-- | ui/app/components/transaction-list-item.js | 21 | ||||
-rw-r--r-- | ui/app/components/transaction-list.js | 9 | ||||
-rw-r--r-- | ui/app/conf-tx.js | 27 | ||||
-rw-r--r-- | ui/app/css/lib.css | 8 | ||||
-rw-r--r-- | ui/app/info.js | 2 | ||||
-rw-r--r-- | ui/app/reducers/app.js | 20 | ||||
-rw-r--r-- | ui/app/unlock.js | 2 | ||||
-rw-r--r-- | ui/css.js | 1 | ||||
-rw-r--r-- | ui/lib/explorer-link.js | 2 |
23 files changed, 544 insertions, 107 deletions
diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index c41ba61fd..7a0c599ba 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -26,11 +26,10 @@ function mapStateToProps (state) { accounts: state.metamask.accounts, address: state.metamask.selectedAccount, accountDetail: state.appState.accountDetail, - transactions: state.metamask.transactions, network: state.metamask.network, - unconfTxs: valuesFor(state.metamask.unconfTxs), unconfMsgs: valuesFor(state.metamask.unconfMsgs), shapeShiftTxList: state.metamask.shapeShiftTxList, + transactions: state.metamask.selectedAccountTxList || [], } } @@ -248,20 +247,10 @@ AccountDetailScreen.prototype.subview = function () { } AccountDetailScreen.prototype.transactionList = function () { - const { transactions, unconfTxs, unconfMsgs, address, network, shapeShiftTxList } = this.props - - var txsToRender = transactions.concat(unconfTxs) - // only transactions that are from the current address - .filter(tx => tx.txParams.from === address) - // only transactions that are on the current network - .filter(tx => tx.txParams.metamaskNetworkId === network) - // sort by recency - .sort((a, b) => b.time - a.time) - + const {transactions, unconfMsgs, address, network, shapeShiftTxList } = this.props return h(TransactionList, { - txsToRender, + transactions: transactions.sort((a, b) => b.time - a.time), network, - unconfTxs, unconfMsgs, address, shapeShiftTxList, diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js new file mode 100644 index 000000000..96350852a --- /dev/null +++ b/ui/app/accounts/import/index.js @@ -0,0 +1,91 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +import Select from 'react-select' + +// Subviews +const JsonImportView = require('./json.js') +const PrivateKeyImportView = require('./private-key.js') + +const menuItems = [ + 'Private Key', + 'JSON File', +] + +module.exports = connect(mapStateToProps)(AccountImportSubview) + +function mapStateToProps (state) { + return { + menuItems, + } +} + +inherits(AccountImportSubview, Component) +function AccountImportSubview () { + Component.call(this) +} + +AccountImportSubview.prototype.render = function () { + const props = this.props + const state = this.state || {} + const { menuItems } = props + const { type } = state + + return ( + h('div', { + style: { + }, + }, [ + h('div', { + style: { + padding: '10px', + color: 'rgb(174, 174, 174)', + }, + }, [ + + h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'), + + h('style', ` + .has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label { + color: rgb(174,174,174); + } + `), + + h(Select, { + name: 'import-type-select', + clearable: false, + value: type || menuItems[0], + options: menuItems.map((type) => { + return { + value: type, + label: type, + } + }), + onChange: (opt) => { + this.setState({ type: opt.value }) + }, + }), + ]), + + this.renderImportView(), + ]) + ) +} + +AccountImportSubview.prototype.renderImportView = function() { + const props = this.props + const state = this.state || {} + const { type } = state + const { menuItems } = props + const current = type || menuItems[0] + + switch (current) { + case 'Private Key': + return h(PrivateKeyImportView) + case 'JSON File': + return h(JsonImportView) + default: + return h(JsonImportView) + } +} diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js new file mode 100644 index 000000000..1c2b331d4 --- /dev/null +++ b/ui/app/accounts/import/json.js @@ -0,0 +1,98 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('../../actions') +const FileInput = require('react-simple-file-input').default + +module.exports = connect(mapStateToProps)(JsonImportSubview) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +inherits(JsonImportSubview, Component) +function JsonImportSubview () { + Component.call(this) +} + +JsonImportSubview.prototype.render = function () { + const { error } = this.props + + return ( + h('div', { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '5px 15px 0px 15px', + }, + }, [ + + h('p', 'Used by a variety of different clients'), + + h(FileInput, { + readAs: 'text', + onLoad: this.onLoad.bind(this), + style: { + margin: '20px 0px 12px 20px', + fontSize: '15px', + }, + }), + + h('input.large-input.letter-spacey', { + type: 'password', + placeholder: 'Enter password', + id: 'json-password-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + style: { + width: 260, + marginTop: 12, + }, + }), + + h('button.primary', { + onClick: this.createNewKeychain.bind(this), + style: { + margin: 12, + }, + }, 'Import'), + + error ? h('span.warning', error) : null, + ]) + ) +} + +JsonImportSubview.prototype.onLoad = function (event, file) { + this.setState({file: file, fileContents: event.target.result}) +} + +JsonImportSubview.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +JsonImportSubview.prototype.createNewKeychain = function () { + const state = this.state + const { fileContents } = state + + if (!fileContents) { + const message = 'You must select a file to import.' + return this.props.dispatch(actions.displayWarning(message)) + } + + const passwordInput = document.getElementById('json-password-box') + const password = passwordInput.value + + if (!password) { + const message = 'You must enter a password for the selected file.' + return this.props.dispatch(actions.displayWarning(message)) + } + + this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) +} + diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js new file mode 100644 index 000000000..b139a0374 --- /dev/null +++ b/ui/app/accounts/import/private-key.js @@ -0,0 +1,68 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('../../actions') + +module.exports = connect(mapStateToProps)(PrivateKeyImportView) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +inherits(PrivateKeyImportView, Component) +function PrivateKeyImportView () { + Component.call(this) +} + +PrivateKeyImportView.prototype.render = function () { + const { error } = this.props + + return ( + h('div', { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '5px 15px 0px 15px', + }, + }, [ + h('span', 'Paste your private key string here'), + + h('input.large-input.letter-spacey', { + type: 'password', + id: 'private-key-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + style: { + width: 260, + marginTop: 12, + }, + }), + + h('button.primary', { + onClick: this.createNewKeychain.bind(this), + style: { + margin: 12, + }, + }, 'Import'), + + error ? h('span.warning', error) : null, + ]) + ) +} + +PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +PrivateKeyImportView.prototype.createNewKeychain = function () { + const input = document.getElementById('private-key-box') + const privateKey = input.value + this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) +} + diff --git a/ui/app/accounts/import/seed.js b/ui/app/accounts/import/seed.js new file mode 100644 index 000000000..b4a7c0afa --- /dev/null +++ b/ui/app/accounts/import/seed.js @@ -0,0 +1,30 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect + +module.exports = connect(mapStateToProps)(SeedImportSubview) + +function mapStateToProps (state) { + return {} +} + +inherits(SeedImportSubview, Component) +function SeedImportSubview () { + Component.call(this) +} + +SeedImportSubview.prototype.render = function () { + return ( + h('div', { + style: { + }, + }, [ + `Paste your seed phrase here!`, + h('textarea'), + h('br'), + h('button', 'Submit'), + ]) + ) +} + diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index edb15eafe..e6f376735 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () { const simpleAddress = identity.address.substring(2).toLowerCase() const keyring = keyrings.find((kr) => { - return kr.accounts.includes(simpleAddress) + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) }) return h(AccountListItem, { @@ -154,6 +155,13 @@ AccountsScreen.prototype.addNewAccount = function () { this.props.dispatch(actions.addNewAccount(0)) } +/* An optional view proposed in this design: + * https://consensys.quip.com/zZVrAysM5znY +AccountsScreen.prototype.addNewAccount = function () { + this.props.dispatch(actions.navigateToNewAccountScreen()) +} +*/ + AccountsScreen.prototype.goHome = function () { this.props.dispatch(actions.goHome()) } diff --git a/ui/app/actions.js b/ui/app/actions.js index 5a3968f82..bf3617310 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -32,16 +32,21 @@ var actions = { SHOW_INIT_MENU: 'SHOW_INIT_MENU', SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED', SHOW_INFO_PAGE: 'SHOW_INFO_PAGE', + SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE', unlockMetamask: unlockMetamask, unlockFailed: unlockFailed, showCreateVault: showCreateVault, showRestoreVault: showRestoreVault, showInitializeMenu: showInitializeMenu, + showImportPage, createNewVaultAndKeychain: createNewVaultAndKeychain, createNewVaultAndRestore: createNewVaultAndRestore, createNewVaultInProgress: createNewVaultInProgress, addNewKeyring, + importNewAccount, addNewAccount, + NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN', + navigateToNewAccountScreen, showNewVaultSeed: showNewVaultSeed, showInfoPage: showInfoPage, // seed recovery actions @@ -249,7 +254,36 @@ function requestRevealSeed (password) { } function addNewKeyring (type, opts) { - return callBackgroundThenUpdate(background.addNewKeyring, type, opts) + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + background.addNewKeyring(type, opts, (err, newState) => { + dispatch(actions.hideLoadingIndication()) + if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.showAccountsPage()) + }) + } +} + +function importNewAccount (strategy, args) { + return (dispatch) => { + dispatch(actions.showLoadingIndication('This may take a while, be patient.')) + background.importAccountWithStrategy(strategy, args, (err, newState) => { + dispatch(actions.hideLoadingIndication()) + if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.updateMetamaskState(newState)) + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAccount, + }) + }) + } +} + +function navigateToNewAccountScreen() { + return { + type: this.NEW_ACCOUNT_SCREEN, + } } function addNewAccount (ringNumber = 0) { @@ -376,6 +410,12 @@ function showInitializeMenu () { } } +function showImportPage () { + return { + type: actions.SHOW_IMPORT_PAGE, + } +} + function agreeToDisclaimer () { return (dispatch) => { dispatch(this.showLoadingIndication()) @@ -590,9 +630,10 @@ function useEtherscanProvider () { } } -function showLoadingIndication () { +function showLoadingIndication (message) { return { type: actions.SHOW_LOADING, + value: message, } } diff --git a/ui/app/app.js b/ui/app/app.js index 9efe95874..d8dedd397 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -20,6 +20,7 @@ const NoticeScreen = require('./components/notice') const generateLostAccountsNotice = require('../lib/lost-accounts-notice') // other views const ConfigScreen = require('./config') +const Import = require('./accounts/import') const InfoScreen = require('./info') const LoadingIndicator = require('./components/loading') const SandwichExpando = require('sandwich-expando') @@ -42,6 +43,7 @@ function mapStateToProps (state) { return { // state from plugin isLoading: state.appState.isLoading, + loadingMessage: state.appState.loadingMessage, isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed, noActiveNotices: state.metamask.noActiveNotices, isInitialized: state.metamask.isInitialized, @@ -63,7 +65,7 @@ function mapStateToProps (state) { App.prototype.render = function () { var props = this.props - const { isLoading, transForward } = props + const { isLoading, loadingMessage, transForward } = props return ( @@ -75,7 +77,7 @@ App.prototype.render = function () { }, }, [ - h(LoadingIndicator, { isLoading }), + h(LoadingIndicator, { isLoading, loadingMessage }), // app bar this.renderAppBar(), @@ -305,6 +307,13 @@ App.prototype.renderDropdown = function () { }), h(DropMenuItem, { + label: 'Import Account', + closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), + action: () => this.props.dispatch(actions.showImportPage()), + icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'), + }), + + h(DropMenuItem, { label: 'Lock', closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), action: () => this.props.dispatch(actions.lockMetamask()), @@ -411,6 +420,9 @@ App.prototype.renderPrimary = function () { case 'config': return h(ConfigScreen, {key: 'config'}) + case 'import-menu': + return h(Import, {key: 'import-menu'}) + case 'reveal-seed-conf': return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 35eda647e..afda5bf59 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -7,6 +7,7 @@ const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') const extension = require('../../../app/scripts/lib/extension') const Loading = require('./loading') +const TabBar = require('./tab-bar') module.exports = connect(mapStateToProps)(BuyButtonSubview) @@ -29,7 +30,6 @@ function BuyButtonSubview () { BuyButtonSubview.prototype.render = function () { const props = this.props - const currentForm = props.buyView.formView const isLoading = props.isSubLoading return ( @@ -53,43 +53,53 @@ BuyButtonSubview.prototype.render = function () { h(Loading, { isLoading }), - h('h3.flex-row.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - paddingTop: '4px', - justifyContent: 'space-around', + h(TabBar, { + tabs: [ + { + content: [ + 'Coinbase', + h('a', { + onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), + }, [ + h('i.fa.fa-question-circle', { + style: { + margin: '0px 5px', + }, + }), + ]), + ], + key: 'coinbase', + }, + { + content: [ + 'Shapeshift', + h('a', { + href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', + onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), + }, [ + h('i.fa.fa-question-circle', { + style: { + margin: '0px 5px', + }, + }), + ]), + ], + key: 'shapeshift', + }, + ], + defaultTab: 'coinbase', + tabSelected: (key) => { + switch (key) { + case 'coinbase': + props.dispatch(actions.coinBaseSubview()) + break + case 'shapeshift': + props.dispatch(actions.shapeShiftSubview(props.provider.type)) + break + } }, - }, [ - h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', { - onClick: () => props.dispatch(actions.coinBaseSubview()), - }, 'Coinbase'), - h('a', { - onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), - }, [ - h('i.fa.fa-question-circle', { - style: { - position: 'relative', - right: '33px', - }, - }), - ]), - h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', { - onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)), - }, 'Shapeshift'), + }), - h('a', { - href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', - onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), - }, [ - h('i.fa.fa-question-circle', { - style: { - position: 'relative', - right: '28px', - }, - }), - ]), - ]), this.formVersionSubview(), ]) ) diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index 693eb2ea8..430a3eead 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -72,7 +72,7 @@ CoinbaseForm.prototype.render = function () { lineHeight: '13px', }, }, - `there is a USD$ 5 a day max and a USD$ 50 + `there is a USD$ 15 a day max and a USD$ 50 dollar limit per the life time of an account without a coinbase account. A fee of 3.75% will be aplied to debit/credit cards.`), @@ -136,14 +136,14 @@ CoinbaseForm.prototype.renderLoading = function () { function isValidAmountforCoinBase (amount) { amount = parseFloat(amount) if (amount) { - if (amount <= 5 && amount > 0) { + if (amount <= 15 && amount > 0) { return { valid: true, } - } else if (amount > 5) { + } else if (amount > 15) { return { valid: false, - message: 'The amount can not be greater then $5', + message: 'The amount can not be greater then $15', } } else { return { diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js index ae735894f..88dc535df 100644 --- a/ui/app/components/loading.js +++ b/ui/app/components/loading.js @@ -12,7 +12,7 @@ function LoadingIndicator () { } LoadingIndicator.prototype.render = function () { - var isLoading = this.props.isLoading + const { isLoading, loadingMessage } = this.props return ( h(ReactCSSTransitionGroup, { @@ -37,8 +37,14 @@ LoadingIndicator.prototype.render = function () { h('img', { src: 'images/loading.svg', }), + + showMessageIfAny(loadingMessage), ]) : null, ]) ) } +function showMessageIfAny (loadingMessage) { + if (!loadingMessage) return null + return h('span', loadingMessage) +} diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 89472b221..286931f6f 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -7,8 +7,6 @@ const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN module.exports = PendingTxDetails @@ -29,15 +27,9 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' - var gasMultiplier = txData.gasMultiplier - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) + var txFee = txData.txFee || '' + var maxCost = txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons return ( diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js new file mode 100644 index 000000000..65078e0a4 --- /dev/null +++ b/ui/app/components/tab-bar.js @@ -0,0 +1,35 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = TabBar + +inherits(TabBar, Component) +function TabBar () { + Component.call(this) +} + +TabBar.prototype.render = function () { + const props = this.props + const state = this.state || {} + const { tabs = [], defaultTab, tabSelected } = props + const { subview = defaultTab } = state + + return ( + h('.flex-row.space-around.text-transform-uppercase', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + paddingTop: '4px', + }, + }, tabs.map((tab) => { + const { key, content } = tab + return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', { + onClick: () => { + this.setState({ subview: key }) + tabSelected(key) + }, + }, content) + })) + ) +} diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 8b118b1d4..353401099 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -13,13 +13,40 @@ function TransactionIcon () { TransactionIcon.prototype.render = function () { const { transaction, txParams, isMsg } = this.props + switch (transaction.status) { + case 'unapproved': + return h('.unapproved-tx', { + style: { + width: '24px', + height: '24px', + background: '#4dffff', + border: 'solid', + borderColor: '#AEAEAE', + borderWidth: '0.5px', + borderRadius: '13px', + }, + }) - if (transaction.status === 'rejected') { - return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { - style: { - width: '24px', - }, - }) + case 'rejected': + return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { + style: { + width: '24px', + }, + }) + + case 'failed': + return h('i.fa.fa-exclamation-triangle.fa-lg.error', { + style: { + width: '24px', + }, + }) + + case 'submitted': + return h('i.fa.fa-ellipsis-h', { + style: { + fontSize: '27px', + }, + }) } if (isMsg) { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index bb685abda..95e850264 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -8,6 +8,7 @@ const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) const extension = require('../../../app/scripts/lib/extension') +const Tooltip = require('./tooltip') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -27,7 +28,7 @@ TransactionListItem.prototype.render = function () { let isLinkable = false const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 2 + isLinkable = numericNet === 1 || numericNet === 3 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) @@ -41,7 +42,6 @@ TransactionListItem.prototype.render = function () { } const isClickable = ('hash' in transaction && isLinkable) || isPending - return ( h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { onClick: (event) => { @@ -59,11 +59,7 @@ TransactionListItem.prototype.render = function () { }, [ h('.identicon-wrapper.flex-column.flex-center.select-none', [ - transaction.status === 'unapproved' ? h('i.fa.fa-ellipsis-h', { - style: { - fontSize: '27px', - }, - }) : h('.pop-hover', { + h('.pop-hover', { onClick: (event) => { event.stopPropagation() if (!isTx || isPending) return @@ -139,7 +135,14 @@ function failIfFailed (transaction) { if (transaction.status === 'rejected') { return h('span.error', ' (Rejected)') } - if (transaction.status === 'failed') { - return h('span.error', ' (Failed)') + if (transaction.err) { + + return h(Tooltip, { + title: transaction.err.message, + position: 'bottom', + }, [ + h('span.error', ' (Failed)'), + ]) } + } diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 7e1bedb05..b055ca9d5 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -13,12 +13,13 @@ function TransactionList () { } TransactionList.prototype.render = function () { - const { txsToRender, network, unconfMsgs } = this.props + const { transactions, network, unconfMsgs } = this.props + var shapeShiftTxList if (network === '1') { shapeShiftTxList = this.props.shapeShiftTxList } - const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList) + const txsToRender = !shapeShiftTxList ? transactions.concat(unconfMsgs) : transactions.concat(unconfMsgs, shapeShiftTxList) .sort((a, b) => b.time - a.time) return ( @@ -55,8 +56,8 @@ TransactionList.prototype.render = function () { }, }, [ - transactions.length - ? transactions.map((transaction, i) => { + txsToRender.length + ? txsToRender.map((transaction, i) => { let key switch (transaction.key) { case 'shapeshift': diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 5a645022a..a6e03c3ed 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -41,11 +41,13 @@ ConfirmTxScreen.prototype.render = function () { var provider = state.provider var unconfTxs = state.unconfTxs var unconfMsgs = state.unconfMsgs + var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) - var index = state.index !== undefined ? state.index : 0 - var txData = unconfTxList[index] || unconfTxList[0] || {} - var txParams = txData.txParams || {} + var index = state.index !== undefined && unconfTxList[index] ? state.index : 0 + var txData = unconfTxList[index] || {} + var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' + if (unconfTxList.length === 0) return null return ( @@ -115,27 +117,24 @@ ConfirmTxScreen.prototype.render = function () { } function currentTxView (opts) { - if ('txParams' in opts.txData) { + const { txData } = opts + const { txParams, msgParams } = txData + + if (txParams) { // This is a pending transaction return h(PendingTx, opts) - } else if ('msgParams' in opts.txData) { + } else if (msgParams) { // This is a pending message to sign return h(PendingMsg, opts) } } ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { + if (!txData.txParams) return false var state = this.props - - var txParams = txData.txParams || {} - var address = txParams.from || state.selectedAccount + var address = txData.txParams.from || state.selectedAccount var account = state.accounts[address] var balance = account ? account.balance : '0x0' - - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) + var maxCost = new BN(txData.maxCost) var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16) return maxCost.gt(balanceBn) diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index abbf8667e..a8df1d115 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -23,6 +23,14 @@ flex-direction: column; } +.space-between { + justify-content: space-between; +} + +.space-around { + justify-content: space-around; +} + .flex-column-bottom { display: flex; flex-direction: column-reverse; diff --git a/ui/app/info.js b/ui/app/info.js index cc753b2ea..e79580be4 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -110,7 +110,7 @@ InfoScreen.prototype.render = function () { onClick (event) { this.navigateTo(event.target.href) }, }, [ h('img.icon-size', { - src: manifest.icons[128], + src: manifest.icons['128'], style: { filter: 'grayscale(100%)', /* IE6-9 */ WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */ diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 65a3dba49..6a2c93f78 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -99,6 +99,14 @@ function reduceApp (state, action) { transForward: action.value, }) + case actions.SHOW_IMPORT_PAGE: + return extend(appState, { + currentView: { + name: 'import-menu', + }, + transForward: true, + }) + case actions.SHOW_INFO_PAGE: return extend(appState, { currentView: { @@ -128,6 +136,15 @@ function reduceApp (state, action) { isLoading: false, }) + case actions.NEW_ACCOUNT_SCREEN: + return extend(appState, { + currentView: { + name: 'new-account', + context: appState.currentView.context, + }, + transForward: true, + }) + case actions.SHOW_SEND_PAGE: return extend(appState, { currentView: { @@ -369,6 +386,7 @@ function reduceApp (state, action) { case actions.SHOW_LOADING: return extend(appState, { isLoading: true, + loadingMessage: action.value, }) case actions.HIDE_LOADING: @@ -446,7 +464,7 @@ function reduceApp (state, action) { }, buyView: { subview: 'buyForm', - amount: '5.00', + amount: '15.00', buyAddress: action.value, formView: { coinbase: true, diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 19f5eaec2..1aee3c5d0 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -26,7 +26,7 @@ UnlockScreen.prototype.render = function () { const state = this.props const warning = state.warning return ( - h('.flex-column.hey-im-here', [ + h('.flex-column', [ h('.unlock-screen.flex-column.flex-center.flex-grow', [ h(Mascot, { @@ -10,6 +10,7 @@ var cssFiles = { 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), + 'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'), } function bundleCss () { diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index 2993d1cf1..dc6be2984 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -5,7 +5,7 @@ module.exports = function (hash, network) { case 1: // main net prefix = '' break - case 2: // morden test net + case 3: // morden test net prefix = 'testnet.' break default: |