aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorChi Kei Chan <chikeichan@gmail.com>2017-09-19 02:28:10 +0800
committerChi Kei Chan <chikeichan@gmail.com>2017-09-19 02:28:10 +0800
commit6c5865d564167c1097d6010e47d1e82a75087fd1 (patch)
tree9ced5adba4b6e5b77724f19a086a10981ba7e375 /ui/app
parent54bbf8d8590014b92e7857f30bdc2d8f3779431a (diff)
parent693655e2da7cacf5a5326b50bddc37bcece9422e (diff)
downloadtangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.tar
tangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.tar.gz
tangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.tar.bz2
tangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.tar.lz
tangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.tar.xz
tangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.tar.zst
tangerine-wallet-browser-6c5865d564167c1097d6010e47d1e82a75087fd1.zip
Merge branch 'master' into nm
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/actions.js13
-rw-r--r--ui/app/add-token.js31
-rw-r--r--ui/app/app.js2
-rw-r--r--ui/app/components/account-export.js28
-rw-r--r--ui/app/components/dropdowns/components/account-dropdowns.js37
-rw-r--r--ui/app/components/network.js11
-rw-r--r--ui/app/components/pending-msg-details.js2
-rw-r--r--ui/app/components/pending-msg.js18
-rw-r--r--ui/app/components/pending-tx.js90
-rw-r--r--ui/app/components/token-list.js23
-rw-r--r--ui/app/components/transaction-list-item.js11
-rw-r--r--ui/app/conf-tx.js7
-rw-r--r--ui/app/config.js7
-rw-r--r--ui/app/css/itcss/tools/utilities.scss2
-rw-r--r--ui/app/info.js2
-rw-r--r--ui/app/keychains/hd/create-vault-complete.js10
-rw-r--r--ui/app/reducers.js5
-rw-r--r--ui/app/unlock.js2
-rw-r--r--ui/app/util.js16
19 files changed, 273 insertions, 44 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 47da70277..678c68a6a 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -126,6 +126,7 @@ var actions = {
txError: txError,
nextTx: nextTx,
previousTx: previousTx,
+ cancelAllTx: cancelAllTx,
viewPendingTx: viewPendingTx,
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
// app messages
@@ -420,6 +421,7 @@ function signPersonalMsg (msgData) {
function signTx (txData) {
return (dispatch) => {
+ dispatch(actions.showLoadingIndication())
global.ethQuery.sendTransaction(txData, (err, data) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
@@ -464,6 +466,7 @@ function updateAndApproveTx (txData) {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.txError(err))
+ dispatch(actions.goHome())
return log.error(err.message)
}
dispatch(actions.completedTx(txData.id))
@@ -506,6 +509,16 @@ function cancelTx (txData) {
}
}
+function cancelAllTx (txsData) {
+ return (dispatch) => {
+ txsData.forEach((txData, i) => {
+ background.cancelTransaction(txData.id, () => {
+ dispatch(actions.completedTx(txData.id))
+ i === txsData.length - 1 ? dispatch(actions.goHome()) : null
+ })
+ })
+ }
+}
//
// initialize screen
//
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index 5c6dea4a0..4374ee586 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -3,6 +3,8 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
+const Tooltip = require('./components/tooltip.js')
+
const ethUtil = require('ethereumjs-util')
const abi = require('human-standard-token-abi')
@@ -15,6 +17,7 @@ module.exports = connect(mapStateToProps)(AddTokenScreen)
function mapStateToProps (state) {
return {
+ identities: state.metamask.identities,
}
}
@@ -64,15 +67,25 @@ AddTokenScreen.prototype.render = function () {
}, [
h('div', [
- h('span', {
- style: { fontWeight: 'bold', paddingRight: '10px'},
- }, 'Token Address'),
+ h(Tooltip, {
+ position: 'top',
+ title: 'The contract of the actual token contract. Click for more info.',
+ }, [
+ h('a', {
+ style: { fontWeight: 'bold', paddingRight: '10px'},
+ href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
+ target: '_blank',
+ }, [
+ h('span', 'Token Contract Address '),
+ h('i.fa.fa-question-circle'),
+ ]),
+ ]),
]),
h('section.flex-row.flex-center', [
h('input#token-address', {
name: 'address',
- placeholder: 'Token Address',
+ placeholder: 'Token Contract Address',
onChange: this.tokenAddressDidChange.bind(this),
style: {
width: 'inherit',
@@ -171,7 +184,9 @@ AddTokenScreen.prototype.tokenAddressDidChange = function (event) {
AddTokenScreen.prototype.validateInputs = function () {
let msg = ''
const state = this.state
+ const identitiesList = Object.keys(this.props.identities)
const { address, symbol, decimals } = state
+ const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
const validAddress = ethUtil.isValidAddress(address)
if (!validAddress) {
@@ -189,7 +204,12 @@ AddTokenScreen.prototype.validateInputs = function () {
msg += 'Symbol must be between 0 and 10 characters.'
}
- const isValid = validAddress && validDecimals
+ const ownAddress = identitiesList.includes(standardAddress)
+ if (ownAddress) {
+ msg = 'Personal address detected. Input the token contract address.'
+ }
+
+ const isValid = validAddress && validDecimals && !ownAddress
if (!isValid) {
this.setState({
@@ -215,4 +235,3 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address)
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() })
}
}
-
diff --git a/ui/app/app.js b/ui/app/app.js
index 1ca59e406..14e6a26e2 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -46,6 +46,7 @@ function mapStateToProps (state) {
identities,
accounts,
address,
+ keyrings,
} = state.metamask
const selected = address || Object.keys(accounts)[0]
@@ -75,6 +76,7 @@ function mapStateToProps (state) {
// state needed to get account dropdown temporarily rendering from app bar
identities,
selected,
+ keyrings,
}
}
diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js
index 330f73805..32b103c86 100644
--- a/ui/app/components/account-export.js
+++ b/ui/app/components/account-export.js
@@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const exportAsFile = require('../util').exportAsFile
const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions')
const ethUtil = require('ethereumjs-util')
@@ -20,20 +21,21 @@ function mapStateToProps (state) {
}
ExportAccountView.prototype.render = function () {
- var state = this.props
- var accountDetail = state.accountDetail
+ const state = this.props
+ const accountDetail = state.accountDetail
+ const nickname = state.identities[state.address].name
if (!accountDetail) return h('div')
- var accountExport = accountDetail.accountExport
+ const accountExport = accountDetail.accountExport
- var notExporting = accountExport === 'none'
- var exportRequested = accountExport === 'requested'
- var accountExported = accountExport === 'completed'
+ const notExporting = accountExport === 'none'
+ const exportRequested = accountExport === 'requested'
+ const accountExported = accountExport === 'completed'
if (notExporting) return h('div')
if (exportRequested) {
- var warning = `Export private keys at your own risk.`
+ const warning = `Export private keys at your own risk.`
return (
h('div', {
style: {
@@ -89,6 +91,8 @@ ExportAccountView.prototype.render = function () {
}
if (accountExported) {
+ const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
+
return h('div.privateKey', {
style: {
margin: '0 20px',
@@ -105,10 +109,16 @@ ExportAccountView.prototype.render = function () {
onClick: function (event) {
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
},
- }, ethUtil.stripHexPrefix(accountDetail.privateKey)),
+ }, plainKey),
h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, 'Done'),
+ h('button', {
+ style: {
+ marginLeft: '10px',
+ },
+ onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
+ }, 'Save as File'),
])
}
}
@@ -117,6 +127,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) {
if (event.key !== 'Enter') return
event.preventDefault()
- var input = document.getElementById('exportAccount').value
+ const input = document.getElementById('exportAccount').value
this.props.dispatch(actions.exportAccount(input, this.props.address))
}
diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js
index bb112dcca..fe80af8b3 100644
--- a/ui/app/components/dropdowns/components/account-dropdowns.js
+++ b/ui/app/components/dropdowns/components/account-dropdowns.js
@@ -25,7 +25,7 @@ class AccountDropdowns extends Component {
}
renderAccounts () {
- const { identities, accounts, selected, menuItemStyles, actions } = this.props
+ const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
@@ -33,6 +33,12 @@ class AccountDropdowns extends Component {
const balanceValue = accounts[key].balance
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
+ const simpleAddress = identity.address.substring(2).toLowerCase()
+
+ const keyring = keyrings.find((kr) => {
+ return kr.accounts.includes(simpleAddress) ||
+ kr.accounts.includes(identity.address)
+ })
return h(
DropdownMenuItem,
@@ -88,6 +94,7 @@ class AccountDropdowns extends Component {
marginLeft: '10px',
},
}, [
+ this.indicateIfLoose(keyring),
h('span.account-dropdown-name', {
style: {
fontSize: '18px',
@@ -97,6 +104,7 @@ class AccountDropdowns extends Component {
textOverflow: 'ellipsis',
},
}, identity.name || ''),
+ h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
h('span.account-dropdown-balance', {
style: {
@@ -125,11 +133,35 @@ class AccountDropdowns extends Component {
]),
]),
+// =======
+// },
+// ),
+// this.indicateIfLoose(keyring),
+// h('span', {
+// style: {
+// marginLeft: '20px',
+// fontSize: '24px',
+// maxWidth: '145px',
+// whiteSpace: 'nowrap',
+// overflow: 'hidden',
+// textOverflow: 'ellipsis',
+// },
+// }, identity.name || ''),
+// h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
+// >>>>>>> master:ui/app/components/account-dropdowns.js
]
)
})
}
+ indicateIfLoose (keyring) {
+ try { // Sometimes keyrings aren't loaded yet:
+ const type = keyring.type
+ const isLoose = type !== 'HD Key Tree'
+ return isLoose ? h('.keyring-label', 'LOOSE') : null
+ } catch (e) { return }
+ }
+
renderAccountSelector () {
const { actions, useCssTransition, innerStyle } = this.props
const { accountSelectorActive, menuItemStyles } = this.state
@@ -389,7 +421,8 @@ AccountDropdowns.defaultProps = {
AccountDropdowns.propTypes = {
identities: PropTypes.objectOf(PropTypes.object),
- selected: PropTypes.string, // TODO: refactor to be more explicit: selectedAddress
+ selected: PropTypes.string,
+ keyrings: PropTypes.array,
}
const mapDispatchToProps = (dispatch) => {
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index 9133c78e3..8424a479a 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -23,7 +23,7 @@ Network.prototype.render = function () {
let iconName, hoverText
if (networkNumber === 'loading') {
- return h('span', {
+ return h('span.pointer', {
style: {
display: 'flex',
alignItems: 'center',
@@ -38,7 +38,7 @@ Network.prototype.render = function () {
},
src: 'images/loading.svg',
}),
- h('i.fa.fa-sort-desc'),
+ h('i.fa.fa-caret-down'),
])
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
@@ -77,7 +77,8 @@ Network.prototype.render = function () {
style: {
color: '#039396',
}},
- 'Ethereum Main Net'),
+ 'Main Network'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
case 'ropsten-test-network':
return h('.network-indicator', [
@@ -90,6 +91,7 @@ Network.prototype.render = function () {
color: '#ff6666',
}},
'Ropsten Test Net'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
case 'kovan-test-network':
return h('.network-indicator', [
@@ -102,6 +104,7 @@ Network.prototype.render = function () {
color: '#690496',
}},
'Kovan Test Net'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
case 'rinkeby-test-network':
return h('.network-indicator', [
@@ -114,6 +117,7 @@ Network.prototype.render = function () {
color: '#e7a218',
}},
'Rinkeby Test Net'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
default:
return h('.network-indicator', [
@@ -129,6 +133,7 @@ Network.prototype.render = function () {
color: '#AEAEAE',
}},
'Private Network'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
}
})(),
diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js
index 16308d121..718a22de0 100644
--- a/ui/app/components/pending-msg-details.js
+++ b/ui/app/components/pending-msg-details.js
@@ -38,7 +38,7 @@ PendingMsgDetails.prototype.render = function () {
// message data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
- h('.flex-row.flex-space-between', [
+ h('.flex-column.flex-space-between', [
h('label.font-small', 'MESSAGE'),
h('span.font-small', msgParams.data),
]),
diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js
index b2cac164a..834719c53 100644
--- a/ui/app/components/pending-msg.js
+++ b/ui/app/components/pending-msg.js
@@ -18,6 +18,9 @@ PendingMsg.prototype.render = function () {
h('div', {
key: msgData.id,
+ style: {
+ maxWidth: '350px',
+ },
}, [
// header
@@ -32,10 +35,21 @@ PendingMsg.prototype.render = function () {
style: {
margin: '10px',
},
- }, `Signing this message can have
+ }, [
+ `Signing this message can have
dangerous side effects. Only sign messages from
sites you fully trust with your entire account.
- This will be fixed in a future version.`),
+ This dangerous method will be removed in a future version. `,
+ h('a', {
+ href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527',
+ style: { color: 'rgb(247, 134, 28)' },
+ onClick: (event) => {
+ event.preventDefault()
+ const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527'
+ global.platform.openWindow({ url })
+ },
+ }, 'Read more here.'),
+ ]),
// message details
h(PendingTxDetails, state),
diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js
index c1b079a25..a679107c9 100644
--- a/ui/app/components/pending-tx.js
+++ b/ui/app/components/pending-tx.js
@@ -240,6 +240,15 @@ PendingTx.prototype.render = function () {
totalInETH,
} = this.getData()
+ // This is from the latest master
+ // It handles some of the errors that we are not currently handling
+ // Leaving as comments fo reference
+
+ // const balanceBn = hexToBn(balance)
+ // const insufficientBalance = balanceBn.lt(maxCost)
+ // const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
+ // const showRejectAll = props.unconfTxListLength > 1
+
this.inputs = []
return (
@@ -332,9 +341,88 @@ PendingTx.prototype.render = function () {
h('div.confirm-screen-row-info', `$${totalInUSD} USD`),
h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
]),
- ]),
+ ]),
]),
+// These are latest errors handling from master
+// Leaving as comments as reference when we start implementing error handling
+// h('style', `
+// .conf-buttons button {
+// margin-left: 10px;
+// text-transform: uppercase;
+// }
+// `),
+
+// txMeta.simulationFails ?
+// h('.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Transaction Error. Exception thrown in contract code.')
+// : null,
+
+// !isValidAddress ?
+// h('.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
+// : null,
+
+// insufficientBalance ?
+// h('span.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Insufficient balance for transaction')
+// : null,
+
+// // send + cancel
+// h('.flex-row.flex-space-around.conf-buttons', {
+// style: {
+// display: 'flex',
+// justifyContent: 'flex-end',
+// margin: '14px 25px',
+// },
+// }, [
+// h('button', {
+// onClick: (event) => {
+// this.resetGasFields()
+// event.preventDefault()
+// },
+// }, 'Reset'),
+
+// // Accept Button or Buy Button
+// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
+// h('input.confirm.btn-green', {
+// type: 'submit',
+// value: 'SUBMIT',
+// style: { marginLeft: '10px' },
+// disabled: buyDisabled,
+// }),
+
+// h('button.cancel.btn-red', {
+// onClick: props.cancelTransaction,
+// }, 'Reject'),
+// ]),
+// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
+// style: {
+// display: 'flex',
+// justifyContent: 'flex-end',
+// margin: '14px 25px',
+// },
+// }, [
+// h('button.cancel.btn-red', {
+// onClick: props.cancelAllTransactions,
+// }, 'Reject All'),
+// ]) : null,
+// ]),
+// ])
+// )
+// }
]),
h('form#pending-tx-form.flex-column.flex-center', {
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 2d1dd0ea7..0efa89c63 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -48,10 +48,28 @@ TokenList.prototype.render = function () {
if (error) {
log.error(error)
- return this.message('There was a problem loading your token balances.')
+ return h('.hotFix', {
+ style: {
+ padding: '80px',
+ },
+ }, [
+ 'We had trouble loading your token balances. You can view them ',
+ h('span.hotFix', {
+ style: {
+ color: 'rgba(247, 134, 28, 1)',
+ cursor: 'pointer',
+ },
+ onClick: () => {
+ global.platform.openWindow({
+ url: `https://ethplorer.io/address/${userAddress}`,
+ })
+ },
+ }, 'here'),
+ ])
}
return h('div', tokens.map((tokenData) => h(TokenCell, tokenData)))
+
}
TokenList.prototype.message = function (body) {
@@ -84,7 +102,7 @@ TokenList.prototype.createFreshTokenTracker = function () {
this.tracker = new TokenTracker({
userAddress,
provider: global.ethereumProvider,
- tokens: uniqueMergeTokens(defaultTokens, this.props.tokens),
+ tokens: this.props.tokens,
pollingInterval: 8000,
})
@@ -149,4 +167,3 @@ function uniqueMergeTokens (tokensA, tokensB = []) {
})
return result
}
-
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index eca2a7100..880a288af 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -60,16 +60,7 @@ TransactionListItem.prototype.render = function () {
}, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [
- h('.pop-hover', {
- onClick: (event) => {
- event.stopPropagation()
- if (!isTx || isPending) return
- var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}`
- global.platform.openWindow({ url })
- },
- }, [
- h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
- ]),
+ h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
]),
h(Tooltip, {
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 7cc319509..7062eee6b 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -76,6 +76,7 @@ ConfirmTxScreen.prototype.render = function () {
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
})
+
}
function currentTxView (opts) {
@@ -116,6 +117,12 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
this.props.dispatch(actions.cancelTx(txData))
}
+ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) {
+ this.stopPropagation(event)
+ event.preventDefault()
+ this.props.dispatch(actions.cancelAllTx(unconfTxList))
+}
+
ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
log.info('conf-tx.js: signing message')
var params = msgData.msgParams
diff --git a/ui/app/config.js b/ui/app/config.js
index 62785c49b..d64088ccb 100644
--- a/ui/app/config.js
+++ b/ui/app/config.js
@@ -5,7 +5,8 @@ const connect = require('react-redux').connect
const actions = require('./actions')
const currencies = require('./conversion.json').rows
const validUrl = require('valid-url')
-const copyToClipboard = require('copy-to-clipboard')
+const exportAsFile = require('./util').exportAsFile
+
module.exports = connect(mapStateToProps)(ConfigScreen)
@@ -110,9 +111,9 @@ ConfigScreen.prototype.render = function () {
alignSelf: 'center',
},
onClick (event) {
- copyToClipboard(window.logState())
+ exportAsFile('MetaMask State Logs', window.logState())
},
- }, 'Copy State Logs'),
+ }, 'Download State Logs'),
]),
h('hr.horizontal-line'),
diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss
index b9c99219b..9f1caa732 100644
--- a/ui/app/css/itcss/tools/utilities.scss
+++ b/ui/app/css/itcss/tools/utilities.scss
@@ -238,7 +238,7 @@ hr.horizontal-line {
border-radius: 10px;
height: 20px;
min-width: 20px;
- position: relative;
+ position: absolute;
display: flex;
align-items: center;
justify-content: center;
diff --git a/ui/app/info.js b/ui/app/info.js
index 899841c83..4c7d4cb4c 100644
--- a/ui/app/info.js
+++ b/ui/app/info.js
@@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () {
[
h('div.fa.fa-support', [
h('a.info', {
- href: 'http://metamask.consensyssupport.happyfox.com',
+ href: 'https://support.metamask.io',
target: '_blank',
}, 'Visit our Support Center'),
]),
diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js
index c32751fff..745990351 100644
--- a/ui/app/keychains/hd/create-vault-complete.js
+++ b/ui/app/keychains/hd/create-vault-complete.js
@@ -3,6 +3,7 @@ const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../actions')
+const exportAsFile = require('../../util').exportAsFile
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
@@ -65,8 +66,17 @@ CreateVaultCompleteScreen.prototype.render = function () {
style: {
margin: '24px',
fontSize: '0.9em',
+ marginBottom: '10px',
},
}, 'I\'ve copied it somewhere safe'),
+
+ h('button.primary', {
+ onClick: () => exportAsFile(`MetaMask Seed Words`, seed),
+ style: {
+ margin: '10px',
+ fontSize: '0.9em',
+ },
+ }, 'Save Seed Words As File'),
])
)
}
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index 36045772f..6a2f44534 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -42,7 +42,10 @@ function rootReducer (state, action) {
}
window.logState = function () {
- var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2)
+ let state = window.METAMASK_CACHED_LOG_STATE
+ const version = global.platform.getVersion()
+ state.version = version
+ let stateString = JSON.stringify(state, removeSeedWords, 2)
return stateString
}
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index 1918e2e6a..ec97b03bf 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -80,7 +80,7 @@ UnlockScreen.prototype.render = function () {
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
- }, 'I forgot my password.'),
+ }, 'Restore from seed phrase'),
]),
])
)
diff --git a/ui/app/util.js b/ui/app/util.js
index 6596ebafb..be26e15a5 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -53,6 +53,7 @@ module.exports = {
getTxFeeBn,
shortenBalance,
getContractAtAddress,
+ exportAsFile: exportAsFile,
}
function valuesFor (obj) {
@@ -250,3 +251,18 @@ function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimi
function getContractAtAddress (tokenAddress) {
return global.eth.contract(abi).at(tokenAddress)
}
+
+function exportAsFile (filename, data) {
+ // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
+ const blob = new Blob([data], {type: 'text/csv'})
+ if (window.navigator.msSaveOrOpenBlob) {
+ window.navigator.msSaveBlob(blob, filename)
+ } else {
+ const elem = window.document.createElement('a')
+ elem.href = window.URL.createObjectURL(blob)
+ elem.download = filename
+ document.body.appendChild(elem)
+ elem.click()
+ document.body.removeChild(elem)
+ }
+}