aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/app/account-detail.js17
-rw-r--r--ui/app/accounts/import/index.js91
-rw-r--r--ui/app/accounts/import/json.js98
-rw-r--r--ui/app/accounts/import/private-key.js68
-rw-r--r--ui/app/accounts/import/seed.js30
-rw-r--r--ui/app/accounts/index.js10
-rw-r--r--ui/app/actions.js45
-rw-r--r--ui/app/app.js16
-rw-r--r--ui/app/components/buy-button-subview.js82
-rw-r--r--ui/app/components/coinbase-form.js8
-rw-r--r--ui/app/components/loading.js8
-rw-r--r--ui/app/components/pending-tx-details.js12
-rw-r--r--ui/app/components/tab-bar.js35
-rw-r--r--ui/app/components/transaction-list-item-icon.js39
-rw-r--r--ui/app/components/transaction-list-item.js21
-rw-r--r--ui/app/components/transaction-list.js9
-rw-r--r--ui/app/conf-tx.js27
-rw-r--r--ui/app/css/lib.css8
-rw-r--r--ui/app/info.js2
-rw-r--r--ui/app/reducers/app.js20
-rw-r--r--ui/app/unlock.js2
-rw-r--r--ui/css.js1
-rw-r--r--ui/lib/explorer-link.js2
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, {
diff --git a/ui/css.js b/ui/css.js
index 01f317acd..043363cd7 100644
--- a/ui/css.js
+++ b/ui/css.js
@@ -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: