aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.babelrc2
-rw-r--r--.eslintrc12
-rw-r--r--CHANGELOG.md12
-rw-r--r--app/images/caret-right.svg76
-rw-r--r--app/images/check-white.svg14
-rw-r--r--app/images/info-logo.pngbin0 -> 32567 bytes
-rw-r--r--app/notification.html4
-rw-r--r--app/scripts/controllers/computed-balances.js8
-rw-r--r--app/scripts/controllers/network.js6
-rw-r--r--app/scripts/inpage.js10
-rw-r--r--app/scripts/lib/createLoggerMiddleware.js4
-rw-r--r--app/scripts/lib/createOriginMiddleware.js4
-rw-r--r--app/scripts/lib/createProviderMiddleware.js5
-rw-r--r--app/scripts/lib/events-proxy.js4
-rw-r--r--app/scripts/lib/nodeify.js4
-rw-r--r--app/scripts/lib/notification-manager.js2
-rw-r--r--app/scripts/lib/pending-balance-calculator.js2
-rw-r--r--app/scripts/lib/pending-tx-tracker.js12
-rw-r--r--app/scripts/lib/port-stream.js2
-rw-r--r--app/scripts/lib/tx-gas-utils.js2
-rw-r--r--app/scripts/lib/tx-state-history-helper.js10
-rw-r--r--app/scripts/lib/tx-state-manager.js2
-rw-r--r--gulpfile.js15
-rw-r--r--mascara/server/index.js15
-rw-r--r--mascara/server/util.js2
-rw-r--r--mascara/src/app/buy-ether-widget/index.js197
-rw-r--r--mascara/src/app/first-time/backup-phrase-screen.js254
-rw-r--r--mascara/src/app/first-time/breadcrumbs.js25
-rw-r--r--mascara/src/app/first-time/buy-ether-screen.js199
-rw-r--r--mascara/src/app/first-time/create-password-screen.js109
-rw-r--r--mascara/src/app/first-time/import-account-screen.js203
-rw-r--r--mascara/src/app/first-time/import-seed-phrase-screen.js109
-rw-r--r--mascara/src/app/first-time/index.css750
-rw-r--r--mascara/src/app/first-time/index.js142
-rw-r--r--mascara/src/app/first-time/loading-screen.js11
-rw-r--r--mascara/src/app/first-time/notice-screen.js92
-rw-r--r--mascara/src/app/first-time/spinner.js70
-rw-r--r--mascara/src/app/first-time/unique-image-screen.js39
-rw-r--r--mascara/src/app/shapeshift-form/index.js217
-rw-r--r--package.json14
-rw-r--r--test/integration/lib/first-time.js7
-rw-r--r--test/integration/lib/mascara-first-time.js167
-rw-r--r--test/unit/pending-balance-test.js2
-rw-r--r--test/unit/pending-tx-test.js3
-rw-r--r--ui/app/accounts/import/json.js2
-rw-r--r--ui/app/actions.js187
-rw-r--r--ui/app/add-token.js11
-rw-r--r--ui/app/app.js115
-rw-r--r--ui/app/components/account-dropdowns.js7
-rw-r--r--ui/app/components/account-menu/index.js20
-rw-r--r--ui/app/components/bn-as-decimal-input.js16
-rw-r--r--ui/app/components/buy-button-subview.js2
-rw-r--r--ui/app/components/customize-gas-modal/index.js162
-rw-r--r--ui/app/components/dropdowns/account-dropdown-mini.js78
-rw-r--r--ui/app/components/dropdowns/components/dropdown.js4
-rw-r--r--ui/app/components/dropdowns/simple-dropdown.js91
-rw-r--r--ui/app/components/editable-label.js114
-rw-r--r--ui/app/components/ens-input.js2
-rw-r--r--ui/app/components/identicon.js2
-rw-r--r--ui/app/components/menu-droppo.js2
-rw-r--r--ui/app/components/modals/account-details-modal.js21
-rw-r--r--ui/app/components/modals/buy-options-modal.js4
-rw-r--r--ui/app/components/modals/export-private-key-modal.js5
-rw-r--r--ui/app/components/modals/modal.js7
-rw-r--r--ui/app/components/modals/new-account-modal.js16
-rw-r--r--ui/app/components/network.js7
-rw-r--r--ui/app/components/notice.js2
-rw-r--r--ui/app/components/pending-personal-msg.js47
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js4
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js4
-rw-r--r--ui/app/components/qr-code.js10
-rw-r--r--ui/app/components/send/account-list-item.js8
-rw-r--r--ui/app/components/send/currency-display.js42
-rw-r--r--ui/app/components/send/send-constants.js20
-rw-r--r--ui/app/components/send/send-utils.js39
-rw-r--r--ui/app/components/send/send-v2-container.js9
-rw-r--r--ui/app/components/send/to-autocomplete.js118
-rw-r--r--ui/app/components/shapeshift-form.js4
-rw-r--r--ui/app/components/signature-request.js255
-rw-r--r--ui/app/components/tab-bar.js63
-rw-r--r--ui/app/components/tx-view.js9
-rw-r--r--ui/app/components/typed-message-renderer.js4
-rw-r--r--ui/app/components/wallet-view.js112
-rw-r--r--ui/app/conf-tx.js33
-rw-r--r--ui/app/config.js215
-rw-r--r--ui/app/css/itcss/components/account-dropdown-mini.scss48
-rw-r--r--ui/app/css/itcss/components/account-dropdown.scss7
-rw-r--r--ui/app/css/itcss/components/account-menu.scss14
-rw-r--r--ui/app/css/itcss/components/add-token.scss87
-rw-r--r--ui/app/css/itcss/components/confirm.scss29
-rw-r--r--ui/app/css/itcss/components/currency-display.scss10
-rw-r--r--ui/app/css/itcss/components/editable-label.scss34
-rw-r--r--ui/app/css/itcss/components/header.scss7
-rw-r--r--ui/app/css/itcss/components/index.scss11
-rw-r--r--ui/app/css/itcss/components/modal.scss7
-rw-r--r--ui/app/css/itcss/components/network.scss15
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss103
-rw-r--r--ui/app/css/itcss/components/request-signature.scss222
-rw-r--r--ui/app/css/itcss/components/sections.scss4
-rw-r--r--ui/app/css/itcss/components/send.scss89
-rw-r--r--ui/app/css/itcss/components/settings.scss201
-rw-r--r--ui/app/css/itcss/components/simple-dropdown.scss65
-rw-r--r--ui/app/css/itcss/components/tab-bar.scss23
-rw-r--r--ui/app/css/itcss/components/token-list.scss6
-rw-r--r--ui/app/css/itcss/components/wallet-balance.scss3
-rw-r--r--ui/app/css/itcss/tools/utilities.scss1
-rw-r--r--ui/app/keychains/hd/create-vault-complete.js9
-rw-r--r--ui/app/main-container.js4
-rw-r--r--ui/app/reducers.js4
-rw-r--r--ui/app/reducers/app.js10
-rw-r--r--ui/app/reducers/metamask.js42
-rw-r--r--ui/app/selectors.js23
-rw-r--r--ui/app/send-v2.js94
-rw-r--r--ui/app/settings.js378
-rw-r--r--ui/css.js1
-rw-r--r--ui/lib/tx-helper.js2
-rw-r--r--yarn.lock340
117 files changed, 5652 insertions, 953 deletions
diff --git a/.babelrc b/.babelrc
index bca3364fc..307583ffd 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,4 +1,4 @@
{
- "presets": ["es2015", "stage-0"],
+ "presets": ["es2015", "stage-0", "react"],
"plugins": ["transform-runtime", "transform-async-to-generator"]
}
diff --git a/.eslintrc b/.eslintrc
index 2eb0dc8b5..20a2a7a00 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,4 +1,5 @@
{
+ "parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2017,
@@ -10,10 +11,14 @@
"arrowFunctions": true,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
- "templateStrings": true
+ "templateStrings": true,
+ "classes": true,
+ "jsx": true
},
},
+ "extends": ["plugin:react/recommended"],
+
"env": {
"es6": true,
"node": true,
@@ -23,7 +28,8 @@
"plugins": [
"mocha",
- "chai"
+ "chai",
+ "react"
],
"globals": {
@@ -51,7 +57,7 @@
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [1, "^(err|error)$" ],
"indent": "off",
- "jsx-quotes": [2, "prefer-single"],
+ "jsx-quotes": [2, "prefer-double"],
"key-spacing": 1,
"keyword-spacing": [2, { "before": true, "after": true }],
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b24cc161a..671bb7a6f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
## Current Master
+- Add support for alternative ENS TLDs (Ethereum Name Service Top-Level Domains).
+- Lower minimum gas price to 0.1 GWEI.
+- Remove web3 injection message from production (thanks to @ChainsawBaby)
+
+## 3.11.2 2017-10-21
+
+- Fix bug where reject button would sometimes not work.
+- Fixed bug where sometimes MetaMask's connection to a page would be unreliable.
+
+## 3.11.1 2017-10-20
+
+- Fix bug where log filters were not populated correctly
- Fix bug where web3 API was sometimes injected after the page loaded.
- Fix bug where first account was sometimes not selected correctly after creating or restoring a vault.
- Fix bug where imported accounts could not use new eth_signTypedData method.
diff --git a/app/images/caret-right.svg b/app/images/caret-right.svg
new file mode 100644
index 000000000..8981ac254
--- /dev/null
+++ b/app/images/caret-right.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#231F20;}
+ .st1{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+
+ .st2{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:25,61;}
+ .st3{display:none;}
+ .st4{display:inline;}
+ .st5{fill:#EC008C;}
+ .st6{display:inline;fill:#FFF200;}
+</style>
+<g id="Layer_4">
+</g>
+<g id="Layer_1">
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st0" d="M380.4,756.7c-4.5,0-9-1.7-12.4-5.1c-6.8-6.8-6.8-17.9,0-24.7L594.9,500L368,273.2
+ c-6.8-6.8-6.8-17.9,0-24.7c6.8-6.8,17.9-6.8,24.7,0L632,487.6c6.8,6.8,6.8,17.9,0,24.7L392.8,751.6
+ C389.3,755,384.9,756.7,380.4,756.7z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</g>
+<g id="Layer_2" class="st3">
+</g>
+</svg>
diff --git a/app/images/check-white.svg b/app/images/check-white.svg
new file mode 100644
index 000000000..0f15667da
--- /dev/null
+++ b/app/images/check-white.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="13px" viewBox="0 0 16 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
+ <title>check-white</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="account-dropdown-top-bar-IXD" transform="translate(-17.000000, -80.000000)" fill-rule="nonzero" fill="#FFFFFF">
+ <g id="Group-11" transform="translate(18.000000, 74.000000)">
+ <polygon id="check-white" points="4.2 15.5712828 0.714212839 12.0143571 -0.714212839 13.4142143 4.2 18.4287172 14.7142128 7.69992858 13.2857872 6.30007142"></polygon>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/images/info-logo.png b/app/images/info-logo.png
new file mode 100644
index 000000000..f654ed5b1
--- /dev/null
+++ b/app/images/info-logo.png
Binary files differ
diff --git a/app/notification.html b/app/notification.html
index be38f4aa3..f10cbbf41 100644
--- a/app/notification.html
+++ b/app/notification.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html>
+<html style="height:600px;">
<head>
<meta charset="utf-8">
<title>MetaMask Notification</title>
@@ -9,7 +9,7 @@
}
</style>
</head>
- <body style="width:350px; height:500px;">
+ <body class="notification" style="height:600px;">
<div id="app-content"></div>
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
</body>
diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js
index 9855f715e..907b087cf 100644
--- a/app/scripts/controllers/computed-balances.js
+++ b/app/scripts/controllers/computed-balances.js
@@ -32,13 +32,13 @@ class ComputedbalancesController {
this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
}
- syncAllAccountsFromStore(store) {
+ syncAllAccountsFromStore (store) {
const upstream = Object.keys(store.accounts)
const balances = Object.keys(this.balances)
.map(address => this.balances[address])
// Follow new addresses
- for (let address in balances) {
+ for (const address in balances) {
this.trackAddressIfNotAlready(address)
}
@@ -58,14 +58,14 @@ class ComputedbalancesController {
}
trackAddress (address) {
- let updater = new BalanceController({
+ const updater = new BalanceController({
address,
accountTracker: this.accountTracker,
txController: this.txController,
blockTracker: this.blockTracker,
})
updater.store.subscribe((accountBalance) => {
- let newState = this.store.getState()
+ const newState = this.store.getState()
newState.computedBalances[address] = accountBalance
this.store.updateState(newState)
})
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js
index 0f9db4d53..23afdc12b 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network.js
@@ -51,6 +51,10 @@ module.exports = class NetworkController extends EventEmitter {
}
lookupNetwork () {
+ // Prevent firing when provider is not defined.
+ if (!this.ethQuery || !this.ethQuery.sendAsync) {
+ return
+ }
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
if (err) return this.setNetworkState('loading')
log.info('web3.getNetwork returned ' + network)
@@ -101,7 +105,7 @@ module.exports = class NetworkController extends EventEmitter {
this.emit('networkDidChange')
}
- _configureStandardProvider(_providerParams) {
+ _configureStandardProvider (_providerParams) {
const providerParams = extend(this._baseProviderParams, _providerParams)
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index 9e98c044b..b6889b00f 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -1,6 +1,7 @@
/*global Web3*/
cleanContextForImports()
require('web3/dist/web3.min.js')
+const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
// const PingStream = require('ping-pong-stream/ping')
// const endOfStream = require('end-of-stream')
@@ -8,6 +9,10 @@ const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
restoreContextAfterImports()
+const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
+window.log = log
+log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
+
//
// setup plugin communication
@@ -28,9 +33,9 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
var web3 = new Web3(inpageProvider)
web3.setProvider = function () {
- console.log('MetaMask - overrode web3.setProvider')
+ log.debug('MetaMask - overrode web3.setProvider')
}
-console.log('MetaMask - injected web3')
+log.debug('MetaMask - injected web3')
// export global web3, with usage-detection
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
@@ -65,4 +70,3 @@ function restoreContextAfterImports () {
console.warn('MetaMask - global.define could not be overwritten.')
}
}
-
diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js
index b92a965de..2707cbd9e 100644
--- a/app/scripts/lib/createLoggerMiddleware.js
+++ b/app/scripts/lib/createLoggerMiddleware.js
@@ -1,7 +1,7 @@
// log rpc activity
module.exports = createLoggerMiddleware
-function createLoggerMiddleware({ origin }) {
+function createLoggerMiddleware ({ origin }) {
return function loggerMiddleware (req, res, next, end) {
next((cb) => {
if (res.error) {
@@ -12,4 +12,4 @@ function createLoggerMiddleware({ origin }) {
cb()
})
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js
index e1e097cc4..f8bdb2dc2 100644
--- a/app/scripts/lib/createOriginMiddleware.js
+++ b/app/scripts/lib/createOriginMiddleware.js
@@ -1,9 +1,9 @@
// append dapp origin domain to request
module.exports = createOriginMiddleware
-function createOriginMiddleware({ origin }) {
+function createOriginMiddleware ({ origin }) {
return function originMiddleware (req, res, next, end) {
req.origin = origin
next()
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js
index 6dd192411..4e667bac2 100644
--- a/app/scripts/lib/createProviderMiddleware.js
+++ b/app/scripts/lib/createProviderMiddleware.js
@@ -1,8 +1,7 @@
-
module.exports = createProviderMiddleware
// forward requests to provider
-function createProviderMiddleware({ provider }) {
+function createProviderMiddleware ({ provider }) {
return (req, res, next, end) => {
provider.sendAsync(req, (err, _res) => {
if (err) return end(err)
@@ -10,4 +9,4 @@ function createProviderMiddleware({ provider }) {
end()
})
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js
index d1199a278..c0a490b05 100644
--- a/app/scripts/lib/events-proxy.js
+++ b/app/scripts/lib/events-proxy.js
@@ -1,4 +1,4 @@
-module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
+module.exports = function createEventEmitterProxy (eventEmitter, listeners) {
let target = eventEmitter
const eventHandlers = listeners || {}
const proxy = new Proxy({}, {
@@ -28,4 +28,4 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
}
if (listeners) proxy.setTarget(eventEmitter)
return proxy
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js
index d24e92206..9b595d93c 100644
--- a/app/scripts/lib/nodeify.js
+++ b/app/scripts/lib/nodeify.js
@@ -1,8 +1,8 @@
const promiseToCallback = require('promise-to-callback')
-const noop = function(){}
+const noop = function () {}
module.exports = function nodeify (fn, context) {
- return function(){
+ return function () {
const args = [].slice.call(arguments)
const lastArg = args[args.length - 1]
const lastArgIsCallback = typeof lastArg === 'function'
diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js
index 7846ef7f0..adaf60c65 100644
--- a/app/scripts/lib/notification-manager.js
+++ b/app/scripts/lib/notification-manager.js
@@ -1,5 +1,5 @@
const extension = require('extensionizer')
-const height = 520
+const height = 620
const width = 360
diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js
index cea642f1a..6ae526463 100644
--- a/app/scripts/lib/pending-balance-calculator.js
+++ b/app/scripts/lib/pending-balance-calculator.js
@@ -13,7 +13,7 @@ class PendingBalanceCalculator {
this.getNetworkBalance = getBalance
}
- async getBalance() {
+ async getBalance () {
const results = await Promise.all([
this.getNetworkBalance(),
this.getPendingTransactions(),
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
index df504c126..0d7c6a92c 100644
--- a/app/scripts/lib/pending-tx-tracker.js
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -81,14 +81,14 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
const errorMessage = err.message.toLowerCase()
const isKnownTx = (
// geth
- errorMessage.includes('replacement transaction underpriced')
- || errorMessage.includes('known transaction')
+ errorMessage.includes('replacement transaction underpriced') ||
+ errorMessage.includes('known transaction') ||
// parity
- || errorMessage.includes('gas price too low to replace')
- || errorMessage.includes('transaction with the same hash was already imported')
+ errorMessage.includes('gas price too low to replace') ||
+ errorMessage.includes('transaction with the same hash was already imported') ||
// other
- || errorMessage.includes('gateway timeout')
- || errorMessage.includes('nonce too low')
+ errorMessage.includes('gateway timeout') ||
+ errorMessage.includes('nonce too low')
)
// ignore resubmit warnings, return early
if (isKnownTx) return
diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js
index 648d88087..a9716fb00 100644
--- a/app/scripts/lib/port-stream.js
+++ b/app/scripts/lib/port-stream.js
@@ -1,6 +1,6 @@
const Duplex = require('readable-stream').Duplex
const inherits = require('util').inherits
-const noop = function(){}
+const noop = function () {}
module.exports = PortDuplexStream
diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js
index 41f67e230..7e72ea71d 100644
--- a/app/scripts/lib/tx-gas-utils.js
+++ b/app/scripts/lib/tx-gas-utils.js
@@ -81,4 +81,4 @@ module.exports = class txProvideUtil {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/tx-state-history-helper.js b/app/scripts/lib/tx-state-history-helper.js
index db6e3bc9f..94c7b6792 100644
--- a/app/scripts/lib/tx-state-history-helper.js
+++ b/app/scripts/lib/tx-state-history-helper.js
@@ -9,7 +9,7 @@ module.exports = {
}
-function migrateFromSnapshotsToDiffs(longHistory) {
+function migrateFromSnapshotsToDiffs (longHistory) {
return (
longHistory
// convert non-initial history entries into diffs
@@ -20,22 +20,22 @@ function migrateFromSnapshotsToDiffs(longHistory) {
)
}
-function generateHistoryEntry(previousState, newState, note) {
+function generateHistoryEntry (previousState, newState, note) {
const entry = jsonDiffer.compare(previousState, newState)
// Add a note to the first op, since it breaks if we append it to the entry
if (note && entry[0]) entry[0].note = note
return entry
}
-function replayHistory(_shortHistory) {
+function replayHistory (_shortHistory) {
const shortHistory = clone(_shortHistory)
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
}
-function snapshotFromTxMeta(txMeta) {
+function snapshotFromTxMeta (txMeta) {
// create txMeta snapshot for history
const snapshot = clone(txMeta)
// dont include previous history in this snapshot
delete snapshot.history
return snapshot
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js
index 2250403f6..0fd6bed4b 100644
--- a/app/scripts/lib/tx-state-manager.js
+++ b/app/scripts/lib/tx-state-manager.js
@@ -91,7 +91,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
updateTx (txMeta, note) {
if (txMeta.txParams) {
Object.keys(txMeta.txParams).forEach((key) => {
- let value = txMeta.txParams[key]
+ const value = txMeta.txParams[key]
if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`)
if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed')
})
diff --git a/gulpfile.js b/gulpfile.js
index 96aada130..620b9b663 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -165,7 +165,20 @@ gulp.task('lint', function () {
// To have the process exit with an error code (1) on
// lint error, return the stream and pipe to failAfterError last.
.pipe(eslint.failAfterError())
-})
+});
+
+gulp.task('lint:fix', function () {
+ return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js'])
+ .pipe(eslint(Object.assign(fs.readFileSync(path.join(__dirname, '.eslintrc')), {fix: true})))
+ .pipe(eslint.format())
+ .pipe(eslint.failAfterError())
+});
+
+/*
+gulp.task('default', ['lint'], function () {
+ // This will only run if the lint task is successful...
+});
+*/
// build js
diff --git a/mascara/server/index.js b/mascara/server/index.js
index 12b527e5d..83f84f6d1 100644
--- a/mascara/server/index.js
+++ b/mascara/server/index.js
@@ -1,3 +1,4 @@
+const path = require('path')
const express = require('express')
const createBundle = require('./util').createBundle
const serveBundle = require('./util').serveBundle
@@ -8,22 +9,22 @@ module.exports = createMetamascaraServer
function createMetamascaraServer () {
// start bundlers
- const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js')
- const proxyBundle = createBundle(__dirname + '/../src/proxy.js')
- const uiBundle = createBundle(__dirname + '/../src/ui.js')
- const backgroundBuild = createBundle(__dirname + '/../src/background.js')
+ const metamascaraBundle = createBundle(path.join(__dirname, '/../src/mascara.js'))
+ const proxyBundle = createBundle(path.join(__dirname, '/../src/proxy.js'))
+ const uiBundle = createBundle(path.join(__dirname, '/../src/ui.js'))
+ const backgroundBuild = createBundle(path.join(__dirname, '/../src/background.js'))
// serve bundles
const server = express()
// ui window
serveBundle(server, '/ui.js', uiBundle)
- server.use(express.static(__dirname + '/../ui/'))
- server.use(express.static(__dirname + '/../../dist/chrome'))
+ server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))
+ server.use(express.static(path.join(__dirname, '/../../dist/chrome')))
// metamascara
serveBundle(server, '/metamascara.js', metamascaraBundle)
// proxy
serveBundle(server, '/proxy/proxy.js', proxyBundle)
- server.use('/proxy/', express.static(__dirname + '/../proxy'))
+ server.use('/proxy/', express.static(path.join(__dirname, '/../proxy')))
// background
serveBundle(server, '/background.js', backgroundBuild)
diff --git a/mascara/server/util.js b/mascara/server/util.js
index 6ab41b729..af2daddb9 100644
--- a/mascara/server/util.js
+++ b/mascara/server/util.js
@@ -23,7 +23,7 @@ function createBundle (entryPoint) {
cache: {},
packageCache: {},
plugin: [watchify],
- })
+ }).transform('babelify')
bundler.on('update', bundle)
bundle()
diff --git a/mascara/src/app/buy-ether-widget/index.js b/mascara/src/app/buy-ether-widget/index.js
new file mode 100644
index 000000000..9fe659daa
--- /dev/null
+++ b/mascara/src/app/buy-ether-widget/index.js
@@ -0,0 +1,197 @@
+import React, {Component, PropTypes} from 'react'
+import classnames from 'classnames'
+import {connect} from 'react-redux'
+import {qrcode} from 'qrcode-npm'
+import copyToClipboard from 'copy-to-clipboard'
+import ShapeShiftForm from '../shapeshift-form'
+import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
+
+const OPTION_VALUES = {
+ COINBASE: 'coinbase',
+ SHAPESHIFT: 'shapeshift',
+ QR_CODE: 'qr_code',
+}
+
+const OPTIONS = [
+ {
+ name: 'Direct Deposit',
+ value: OPTION_VALUES.QR_CODE,
+ },
+ {
+ name: 'Buy with Dollars',
+ value: OPTION_VALUES.COINBASE,
+ },
+ {
+ name: 'Buy with Cryptos',
+ value: OPTION_VALUES.SHAPESHIFT,
+ },
+]
+
+class BuyEtherWidget extends Component {
+
+ static propTypes = {
+ address: PropTypes.string,
+ skipText: PropTypes.string,
+ className: PropTypes.string,
+ onSkip: PropTypes.func,
+ goToCoinbase: PropTypes.func,
+ showAccountDetail: PropTypes.func,
+ };
+
+ state = {
+ selectedOption: OPTION_VALUES.QR_CODE,
+ };
+
+
+ copyToClipboard = () => {
+ const { address } = this.props
+
+ this.setState({ justCopied: true }, () => copyToClipboard(address))
+
+ setTimeout(() => this.setState({ justCopied: false }), 1000)
+ }
+
+ renderSkip () {
+ const {showAccountDetail, address, skipText, onSkip} = this.props
+
+ return (
+ <div
+ className="buy-ether__do-it-later"
+ onClick={() => {
+ if (onSkip) return onSkip()
+ showAccountDetail(address)
+ }}
+ >
+ {skipText || 'Do it later'}
+ </div>
+ )
+ }
+
+ renderCoinbaseLogo () {
+ return (
+ <svg width="140px" height="49px" viewBox="0 0 579 126" version="1.1">
+ <g id="Page-1" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd">
+ <g id="Imported-Layers" fill="#0081C9">
+ <path d="M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873" id="Fill-1" />
+ <path d="M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z" id="Fill-2" />
+ <path d="M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z" id="Fill-3" />
+ <path d="M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137" id="Fill-4" />
+ <path d="M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z" id="Fill-5" />
+ <path d="M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z" id="Fill-6" />
+ <path d="M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873" id="Fill-7" />
+ <path d="M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z" id="Fill-8" />
+ </g>
+ </g>
+ </svg>
+ )
+ }
+
+ renderCoinbaseForm () {
+ const {goToCoinbase, address} = this.props
+
+ return (
+ <div className="buy-ether__action-content-wrapper">
+ <div>{this.renderCoinbaseLogo()}</div>
+ <div className="buy-ether__body-text">Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
+ <a className="first-time-flow__link buy-ether__faq-link">What is Ethereum?</a>
+ <div className="buy-ether__buttons">
+ <button
+ className="first-time-flow__button"
+ onClick={() => goToCoinbase(address)}
+ >
+ Buy
+ </button>
+ </div>
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { address } = this.props
+ const { justCopied } = this.state
+ const qrImage = qrcode(4, 'M')
+ qrImage.addData(address)
+ qrImage.make()
+
+ switch (this.state.selectedOption) {
+ case OPTION_VALUES.COINBASE:
+ return this.renderCoinbaseForm()
+ case OPTION_VALUES.SHAPESHIFT:
+ return (
+ <div className="buy-ether__action-content-wrapper">
+ <div className="shapeshift-logo" />
+ <div className="buy-ether__body-text">
+ Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
+ </div>
+ <ShapeShiftForm btnClass="first-time-flow__button" />
+ </div>
+ )
+ case OPTION_VALUES.QR_CODE:
+ return (
+ <div className="buy-ether__action-content-wrapper">
+ <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
+ <div className="buy-ether__body-text">Deposit Ether directly into your account.</div>
+ <div className="buy-ether__small-body-text">(This is the account address that MetaMask created for you to recieve funds.)</div>
+ <div className="buy-ether__buttons">
+ <button
+ className="first-time-flow__button"
+ onClick={this.copyToClipboard}
+ disabled={justCopied}
+ >
+ { justCopied ? 'Copied' : 'Copy' }
+ </button>
+ </div>
+ </div>
+ )
+ default:
+ return null
+ }
+ }
+
+ render () {
+ const { className = '' } = this.props
+ const { selectedOption } = this.state
+
+ return (
+ <div className={`${className} buy-ether__content-wrapper`}>
+ <div className="buy-ether__content-headline-wrapper">
+ <div className="buy-ether__content-headline">Deposit Options</div>
+ {this.renderSkip()}
+ </div>
+ <div className="buy-ether__content">
+ <div className="buy-ether__side-panel">
+ {OPTIONS.map(({ name, value }) => (
+ <div
+ key={value}
+ className={classnames('buy-ether__side-panel-item', {
+ 'buy-ether__side-panel-item--selected': value === selectedOption,
+ })}
+ onClick={() => this.setState({ selectedOption: value })}
+ >
+ <div className="buy-ether__side-panel-item-name">{name}</div>
+ {value === selectedOption && (
+ <svg viewBox="0 0 574 1024" id="si-ant-right" width="15px" height="15px">
+ <path d="M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z" />
+ </svg>
+ )}
+ </div>
+ ))}
+ </div>
+ <div className="buy-ether__action-content">
+ {this.renderContent()}
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ metamask: { selectedAddress } }) => ({
+ address: selectedAddress,
+ }),
+ dispatch => ({
+ goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
+ showAccountDetail: address => dispatch(showAccountDetail(address)),
+ })
+)(BuyEtherWidget)
diff --git a/mascara/src/app/first-time/backup-phrase-screen.js b/mascara/src/app/first-time/backup-phrase-screen.js
new file mode 100644
index 000000000..c68dacea2
--- /dev/null
+++ b/mascara/src/app/first-time/backup-phrase-screen.js
@@ -0,0 +1,254 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux';
+import classnames from 'classnames'
+import shuffle from 'lodash.shuffle'
+import {compose, onlyUpdateForPropTypes} from 'recompose'
+import Identicon from '../../../../ui/app/components/identicon'
+import {confirmSeedWords} from '../../../../ui/app/actions'
+import Breadcrumbs from './breadcrumbs'
+import LoadingScreen from './loading-screen'
+
+const LockIcon = props => (
+ <svg
+ version="1.1"
+ id="Capa_1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ x="0px"
+ y="0px"
+ width="401.998px"
+ height="401.998px"
+ viewBox="0 0 401.998 401.998"
+ style={{enableBackground: 'new 0 0 401.998 401.998'}}
+ xmlSpace="preserve"
+ {...props}
+ >
+ <g>
+ <path
+ d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
+ C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
+ h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
+ c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
+ C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
+ c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
+ z"
+ />
+ </g>
+ </svg>
+);
+
+class BackupPhraseScreen extends Component {
+ static propTypes = {
+ isLoading: PropTypes.bool.isRequired,
+ address: PropTypes.string.isRequired,
+ seedWords: PropTypes.string.isRequired,
+ next: PropTypes.func.isRequired,
+ confirmSeedWords: PropTypes.func.isRequired,
+ };
+
+ static defaultProps = {
+ seedWords: ''
+ };
+
+ static PAGE = {
+ SECRET: 'secret',
+ CONFIRM: 'confirm'
+ };
+
+ constructor(props) {
+ const {seedWords} = props
+ super(props)
+ this.state = {
+ isShowingSecret: false,
+ page: BackupPhraseScreen.PAGE.SECRET,
+ selectedSeeds: [],
+ shuffledSeeds: seedWords && shuffle(seedWords.split(' ')),
+ }
+ }
+
+ renderSecretWordsContainer () {
+ const { isShowingSecret } = this.state
+
+ return (
+ <div className="backup-phrase__secret">
+ <div className={classnames('backup-phrase__secret-words', {
+ 'backup-phrase__secret-words--hidden': !isShowingSecret
+ })}>
+ {this.props.seedWords}
+ </div>
+ {!isShowingSecret && (
+ <div className="backup-phrase__secret-blocker">
+ <LockIcon width="28px" height="35px" fill="#FFFFFF" />
+ <button
+ className="backup-phrase__reveal-button"
+ onClick={() => this.setState({ isShowingSecret: true })}
+ >
+ Click here to reveal secret words
+ </button>
+ </div>
+ )}
+ </div>
+ );
+ }
+
+ renderSecretScreen() {
+ const { isShowingSecret } = this.state
+
+ return (
+ <div className="backup-phrase__content-wrapper">
+ <div>
+ <div className="backup-phrase__title">Secret Backup Phrase</div>
+ <div className="backup-phrase__body-text">
+ Your secret backup phrase makes it easy to back up and restore your account.
+ </div>
+ <div className="backup-phrase__body-text">
+ WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever.
+ </div>
+ {this.renderSecretWordsContainer()}
+ <button
+ className="first-time-flow__button"
+ onClick={() => isShowingSecret && this.setState({
+ isShowingSecret: false,
+ page: BackupPhraseScreen.PAGE.CONFIRM
+ })}
+ disabled={!isShowingSecret}
+ >
+ Next
+ </button>
+ <Breadcrumbs total={3} currentIndex={1} />
+ </div>
+ <div className="backup-phrase__tips">
+ <div className="backup-phrase__tips-text">Tips:</div>
+ <div className="backup-phrase__tips-text">
+ Store this phrase in a password manager like 1password.
+ </div>
+ <div className="backup-phrase__tips-text">
+ Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
+ </div>
+ <div className="backup-phrase__tips-text">
+ Memorize this phrase.
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderConfirmationScreen() {
+ const { seedWords, confirmSeedWords, next } = this.props;
+ const { selectedSeeds, shuffledSeeds } = this.state;
+ const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
+
+ return (
+ <div className="backup-phrase__content-wrapper">
+ <div>
+ <div className="backup-phrase__title">Confirm your Secret Backup Phrase</div>
+ <div className="backup-phrase__body-text">
+ Please select each phrase in order to make sure it is correct.
+ </div>
+ <div className="backup-phrase__confirm-secret">
+ {selectedSeeds.map(([_, word], i) => (
+ <button
+ key={i}
+ className="backup-phrase__confirm-seed-option"
+ >
+ {word}
+ </button>
+ ))}
+ </div>
+ <div className="backup-phrase__confirm-seed-options">
+ {shuffledSeeds.map((word, i) => {
+ const isSelected = selectedSeeds
+ .filter(([index, seed]) => seed === word && index === i)
+ .length
+
+ return (
+ <button
+ key={i}
+ className={classnames('backup-phrase__confirm-seed-option', {
+ 'backup-phrase__confirm-seed-option--selected': isSelected
+ })}
+ onClick={() => {
+ if (!isSelected) {
+ this.setState({
+ selectedSeeds: [...selectedSeeds, [i, word]]
+ })
+ } else {
+ this.setState({
+ selectedSeeds: selectedSeeds
+ .filter(([index, seed]) => !(seed === word && index === i))
+ })
+ }
+ }}
+ >
+ {word}
+ </button>
+ )
+ })}
+ </div>
+ <button
+ className="first-time-flow__button"
+ onClick={() => isValid && confirmSeedWords().then(next)}
+ disabled={!isValid}
+ >
+ Confirm
+ </button>
+ </div>
+ </div>
+ )
+ }
+
+ renderBack () {
+ return this.state.page === BackupPhraseScreen.PAGE.CONFIRM
+ ? (
+ <a
+ className="backup-phrase__back-button"
+ onClick={e => {
+ e.preventDefault()
+ this.setState({
+ page: BackupPhraseScreen.PAGE.SECRET
+ })
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ )
+ : null
+ }
+
+ renderContent () {
+ switch (this.state.page) {
+ case BackupPhraseScreen.PAGE.CONFIRM:
+ return this.renderConfirmationScreen()
+ case BackupPhraseScreen.PAGE.SECRET:
+ default:
+ return this.renderSecretScreen()
+ }
+ }
+
+ render () {
+ return this.props.isLoading
+ ? <LoadingScreen loadingMessage="Creating your new account" />
+ : (
+ <div className="backup-phrase">
+ {this.renderBack()}
+ <Identicon address={this.props.address} diameter={70} />
+ {this.renderContent()}
+ </div>
+ )
+ }
+}
+
+export default compose(
+ onlyUpdateForPropTypes,
+ connect(
+ ({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
+ seedWords,
+ isLoading,
+ address: selectedAddress,
+ }),
+ dispatch => ({
+ confirmSeedWords: () => dispatch(confirmSeedWords()),
+ })
+ )
+)(BackupPhraseScreen)
diff --git a/mascara/src/app/first-time/breadcrumbs.js b/mascara/src/app/first-time/breadcrumbs.js
new file mode 100644
index 000000000..f8460d200
--- /dev/null
+++ b/mascara/src/app/first-time/breadcrumbs.js
@@ -0,0 +1,25 @@
+import React, {Component, PropTypes} from 'react'
+
+export default class Breadcrumbs extends Component {
+
+ static propTypes = {
+ total: PropTypes.number,
+ currentIndex: PropTypes.number
+ };
+
+ render() {
+ const {total, currentIndex} = this.props
+ return (
+ <div className="breadcrumbs">
+ {Array(total).fill().map((_, i) => (
+ <div
+ key={i}
+ className="breadcrumb"
+ style={{backgroundColor: i === currentIndex ? '#D8D8D8' : '#FFFFFF'}}
+ />
+ ))}
+ </div>
+ );
+ }
+
+}
diff --git a/mascara/src/app/first-time/buy-ether-screen.js b/mascara/src/app/first-time/buy-ether-screen.js
new file mode 100644
index 000000000..45b2df1c8
--- /dev/null
+++ b/mascara/src/app/first-time/buy-ether-screen.js
@@ -0,0 +1,199 @@
+import React, {Component, PropTypes} from 'react'
+import classnames from 'classnames'
+import {connect} from 'react-redux'
+import {qrcode} from 'qrcode-npm'
+import copyToClipboard from 'copy-to-clipboard'
+import ShapeShiftForm from '../shapeshift-form'
+import Identicon from '../../../../ui/app/components/identicon'
+import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
+
+class BuyEtherScreen extends Component {
+ static OPTION_VALUES = {
+ COINBASE: 'coinbase',
+ SHAPESHIFT: 'shapeshift',
+ QR_CODE: 'qr_code',
+ };
+
+ static OPTIONS = [
+ {
+ name: 'Direct Deposit',
+ value: BuyEtherScreen.OPTION_VALUES.QR_CODE,
+ },
+ {
+ name: 'Buy with Dollars',
+ value: BuyEtherScreen.OPTION_VALUES.COINBASE,
+ },
+ {
+ name: 'Buy with Cryptos',
+ value: BuyEtherScreen.OPTION_VALUES.SHAPESHIFT,
+ },
+ ];
+
+ static propTypes = {
+ address: PropTypes.string,
+ goToCoinbase: PropTypes.func.isRequired,
+ showAccountDetail: PropTypes.func.isRequired,
+ }
+
+ state = {
+ selectedOption: BuyEtherScreen.OPTION_VALUES.QR_CODE,
+ justCopied: false,
+ }
+
+ copyToClipboard = () => {
+ const { address } = this.props
+
+ this.setState({ justCopied: true }, () => copyToClipboard(address))
+
+ setTimeout(() => this.setState({ justCopied: false }), 1000)
+ }
+
+ renderSkip () {
+ const {showAccountDetail, address} = this.props
+
+ return (
+ <div
+ className='buy-ether__do-it-later'
+ onClick={() => showAccountDetail(address)}
+ >
+ Do it later
+ </div>
+ )
+ }
+
+ renderCoinbaseLogo () {
+ return (
+ <svg width='140px' height='49px' viewBox='0 0 579 126' version='1.1'>
+ <g id='Page-1' stroke='none' strokeWidth={1} fill='none' fillRule='evenodd'>
+ <g id='Imported-Layers' fill='#0081C9'>
+ <path d='M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873' id='Fill-1' />
+ <path d='M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z' id='Fill-2' />
+ <path d='M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z' id='Fill-3' />
+ <path d='M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137' id='Fill-4' />
+ <path d='M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z' id='Fill-5' />
+ <path d='M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z' id='Fill-6' />
+ <path d='M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873' id='Fill-7' />
+ <path d='M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z' id='Fill-8' />
+ </g>
+ </g>
+ </svg>
+ )
+ }
+
+ renderCoinbaseForm () {
+ const {goToCoinbase, address} = this.props
+
+ return (
+ <div className='buy-ether__action-content-wrapper'>
+ <div>{this.renderCoinbaseLogo()}</div>
+ <div className='buy-ether__body-text'>Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
+ <a className='first-time-flow__link buy-ether__faq-link'>What is Ethereum?</a>
+ <div className='buy-ether__buttons'>
+ <button
+ className='first-time-flow__button'
+ onClick={() => goToCoinbase(address)}
+ >
+ Buy
+ </button>
+ </div>
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { OPTION_VALUES } = BuyEtherScreen
+ const { address } = this.props
+ const { justCopied } = this.state
+ const qrImage = qrcode(4, 'M')
+ qrImage.addData(address)
+ qrImage.make()
+
+ switch (this.state.selectedOption) {
+ case OPTION_VALUES.COINBASE:
+ return this.renderCoinbaseForm()
+ case OPTION_VALUES.SHAPESHIFT:
+ return (
+ <div className='buy-ether__action-content-wrapper'>
+ <div className='shapeshift-logo' />
+ <div className='buy-ether__body-text'>
+ Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
+ </div>
+ <ShapeShiftForm btnClass='first-time-flow__button' />
+ </div>
+ )
+ case OPTION_VALUES.QR_CODE:
+ return (
+ <div className='buy-ether__action-content-wrapper'>
+ <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
+ <div className='buy-ether__body-text'>Deposit Ether directly into your account.</div>
+ <div className='buy-ether__small-body-text'>(This is the account address that MetaMask created for you to recieve funds.)</div>
+ <div className='buy-ether__buttons'>
+ <button
+ className='first-time-flow__button'
+ onClick={this.copyToClipboard}
+ disabled={justCopied}
+ >
+ { justCopied ? 'Copied' : 'Copy' }
+ </button>
+ </div>
+ </div>
+ )
+ default:
+ return null
+ }
+ }
+
+ render () {
+ const { OPTIONS } = BuyEtherScreen
+ const { selectedOption } = this.state
+
+ return (
+ <div className='buy-ether'>
+ <Identicon address={this.props.address} diameter={70} />
+ <div className='buy-ether__title'>Deposit Ether</div>
+ <div className='buy-ether__body-text'>
+ MetaMask works best if you have Ether in your account to pay for transaction gas fees and more. To get Ether, choose from one of these methods.
+ </div>
+ <div className='buy-ether__content-wrapper'>
+ <div className='buy-ether__content-headline-wrapper'>
+ <div className='buy-ether__content-headline'>Deposit Options</div>
+ {this.renderSkip()}
+ </div>
+ <div className='buy-ether__content'>
+ <div className='buy-ether__side-panel'>
+ {OPTIONS.map(({ name, value }) => (
+ <div
+ key={value}
+ className={classnames('buy-ether__side-panel-item', {
+ 'buy-ether__side-panel-item--selected': value === selectedOption,
+ })}
+ onClick={() => this.setState({ selectedOption: value })}
+ >
+ <div className='buy-ether__side-panel-item-name'>{name}</div>
+ {value === selectedOption && (
+ <svg viewBox='0 0 574 1024' id='si-ant-right' width='15px' height='15px'>
+ <path d='M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z' />
+ </svg>
+ )}
+ </div>
+ ))}
+ </div>
+ <div className='buy-ether__action-content'>
+ {this.renderContent()}
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ metamask: { selectedAddress } }) => ({
+ address: selectedAddress,
+ }),
+ dispatch => ({
+ goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
+ showAccountDetail: address => dispatch(showAccountDetail(address)),
+ })
+)(BuyEtherScreen)
diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js
new file mode 100644
index 000000000..2f4b81e7c
--- /dev/null
+++ b/mascara/src/app/first-time/create-password-screen.js
@@ -0,0 +1,109 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux';
+import {createNewVaultAndKeychain} from '../../../../ui/app/actions'
+import LoadingScreen from './loading-screen'
+import Breadcrumbs from './breadcrumbs'
+
+class CreatePasswordScreen extends Component {
+ static propTypes = {
+ isLoading: PropTypes.bool.isRequired,
+ createAccount: PropTypes.func.isRequired,
+ goToImportWithSeedPhrase: PropTypes.func.isRequired,
+ goToImportAccount: PropTypes.func.isRequired,
+ next: PropTypes.func.isRequired
+ }
+
+ state = {
+ password: '',
+ confirmPassword: ''
+ }
+
+ isValid() {
+ const {password, confirmPassword} = this.state;
+
+ if (!password || !confirmPassword) {
+ return false;
+ }
+
+ if (password.length < 8) {
+ return false;
+ }
+
+ return password === confirmPassword;
+ }
+
+ createAccount = () => {
+ if (!this.isValid()) {
+ return;
+ }
+
+ const {password} = this.state;
+ const {createAccount, next} = this.props;
+
+ createAccount(password)
+ .then(next);
+ }
+
+ render() {
+ const { isLoading, goToImportAccount, goToImportWithSeedPhrase } = this.props
+
+ return isLoading
+ ? <LoadingScreen loadingMessage="Creating your new account" />
+ : (
+ <div className="create-password">
+ <div className="create-password__title">
+ Create Password
+ </div>
+ <input
+ className="first-time-flow__input"
+ type="password"
+ placeholder="New Password (min 8 characters)"
+ onChange={e => this.setState({password: e.target.value})}
+ />
+ <input
+ className="first-time-flow__input create-password__confirm-input"
+ type="password"
+ placeholder="Confirm Password"
+ onChange={e => this.setState({confirmPassword: e.target.value})}
+ />
+ <button
+ className="first-time-flow__button"
+ disabled={!this.isValid()}
+ onClick={this.createAccount}
+ >
+ Create
+ </button>
+ <a
+ href=""
+ className="first-time-flow__link create-password__import-link"
+ onClick={e => {
+ e.preventDefault()
+ goToImportWithSeedPhrase()
+ }}
+ >
+ Import with seed phrase
+ </a>
+ { /* }
+ <a
+ href=""
+ className="first-time-flow__link create-password__import-link"
+ onClick={e => {
+ e.preventDefault()
+ goToImportAccount()
+ }}
+ >
+ Import an account
+ </a>
+ { */ }
+ <Breadcrumbs total={3} currentIndex={0} />
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ appState: { isLoading } }) => ({ isLoading }),
+ dispatch => ({
+ createAccount: password => dispatch(createNewVaultAndKeychain(password)),
+ })
+)(CreatePasswordScreen)
diff --git a/mascara/src/app/first-time/import-account-screen.js b/mascara/src/app/first-time/import-account-screen.js
new file mode 100644
index 000000000..bf8e209e4
--- /dev/null
+++ b/mascara/src/app/first-time/import-account-screen.js
@@ -0,0 +1,203 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux'
+import classnames from 'classnames'
+import LoadingScreen from './loading-screen'
+import {importNewAccount, hideWarning} from '../../../../ui/app/actions'
+
+const Input = ({ label, placeholder, onChange, errorMessage, type = 'text' }) => (
+ <div className="import-account__input-wrapper">
+ <div className="import-account__input-label">{label}</div>
+ <input
+ type={type}
+ placeholder={placeholder}
+ className={classnames('first-time-flow__input import-account__input', {
+ 'first-time-flow__input--error': errorMessage,
+ })}
+ onChange={onChange}
+ />
+ <div className="import-account__input-error-message">{errorMessage}</div>
+ </div>
+)
+
+Input.prototype.propTypes = {
+ label: PropTypes.string.isRequired,
+ placeholder: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+ errorMessage: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+
+class ImportAccountScreen extends Component {
+ static OPTIONS = {
+ PRIVATE_KEY: 'private_key',
+ JSON_FILE: 'json_file',
+ };
+
+ static propTypes = {
+ warning: PropTypes.string,
+ back: PropTypes.func.isRequired,
+ next: PropTypes.func.isRequired,
+ importNewAccount: PropTypes.func.isRequired,
+ hideWarning: PropTypes.func.isRequired,
+ isLoading: PropTypes.bool.isRequired,
+ };
+
+ state = {
+ selectedOption: ImportAccountScreen.OPTIONS.PRIVATE_KEY,
+ privateKey: '',
+ jsonFile: {},
+ }
+
+ isValid () {
+ const { OPTIONS } = ImportAccountScreen
+ const { privateKey, jsonFile, password } = this.state
+
+ switch (this.state.selectedOption) {
+ case OPTIONS.JSON_FILE:
+ return Boolean(jsonFile && password)
+ case OPTIONS.PRIVATE_KEY:
+ default:
+ return Boolean(privateKey)
+ }
+ }
+
+ onClick = () => {
+ const { OPTIONS } = ImportAccountScreen
+ const { importNewAccount, next } = this.props
+ const { privateKey, jsonFile, password } = this.state
+
+ switch (this.state.selectedOption) {
+ case OPTIONS.JSON_FILE:
+ return importNewAccount('JSON File', [ jsonFile, password ])
+ .then(next)
+ case OPTIONS.PRIVATE_KEY:
+ default:
+ return importNewAccount('Private Key', [ privateKey ])
+ .then(next)
+ }
+ }
+
+ renderPrivateKey () {
+ return Input({
+ label: 'Add Private Key String',
+ placeholder: 'Enter private key',
+ onChange: e => this.setState({ privateKey: e.target.value }),
+ errorMessage: this.props.warning && 'Something went wrong. Please make sure your private key is correct.',
+ })
+ }
+
+ renderJsonFile () {
+ const { jsonFile: { name } } = this.state
+ const { warning } = this.props
+
+ return (
+ <div className="">
+ <div className="import-account__input-wrapper">
+ <div className="import-account__input-label">Upload File</div>
+ <div className="import-account__file-picker-wrapper">
+ <input
+ type="file"
+ id="file"
+ className="import-account__file-input"
+ onChange={e => this.setState({ jsonFile: e.target.files[0] })}
+ />
+ <label
+ htmlFor="file"
+ className={classnames('import-account__file-input-label', {
+ 'import-account__file-input-label--error': warning,
+ })}
+ >
+ Choose File
+ </label>
+ <div className="import-account__file-name">{name}</div>
+ </div>
+ <div className="import-account__input-error-message">
+ {warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'}
+ </div>
+ </div>
+ {Input({
+ label: 'Enter Password',
+ placeholder: 'Enter Password',
+ type: 'password',
+ onChange: e => this.setState({ password: e.target.value }),
+ errorMessage: warning && 'Please make sure your password is correct.',
+ })}
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { OPTIONS } = ImportAccountScreen
+
+ switch (this.state.selectedOption) {
+ case OPTIONS.JSON_FILE:
+ return this.renderJsonFile()
+ case OPTIONS.PRIVATE_KEY:
+ default:
+ return this.renderPrivateKey()
+ }
+ }
+
+ render () {
+ const { OPTIONS } = ImportAccountScreen
+ const { selectedOption } = this.state
+
+ return this.props.isLoading
+ ? <LoadingScreen loadingMessage="Creating your new account" />
+ : (
+ <div className="import-account">
+ <a
+ className="import-account__back-button"
+ onClick={e => {
+ e.preventDefault()
+ this.props.back()
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ <div className="import-account__title">
+ Import an Account
+ </div>
+ <div className="import-account__selector-label">
+ How would you like to import your account?
+ </div>
+ <select
+ className="import-account__dropdown"
+ value={selectedOption}
+ onChange={e => {
+ this.setState({ selectedOption: e.target.value })
+ this.props.hideWarning()
+ }}
+ >
+ <option value={OPTIONS.PRIVATE_KEY}>Private Key</option>
+ <option value={OPTIONS.JSON_FILE}>JSON File</option>
+ </select>
+ {this.renderContent()}
+ <button
+ className="first-time-flow__button"
+ disabled={!this.isValid()}
+ onClick={this.onClick}
+ >
+ Import
+ </button>
+ <a
+ href="https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file"
+ className="first-time-flow__link import-account__faq-link"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ File import not working?
+ </a>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ appState: { isLoading, warning } }) => ({ isLoading, warning }),
+ dispatch => ({
+ importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args)),
+ hideWarning: () => dispatch(hideWarning()),
+ })
+)(ImportAccountScreen)
diff --git a/mascara/src/app/first-time/import-seed-phrase-screen.js b/mascara/src/app/first-time/import-seed-phrase-screen.js
new file mode 100644
index 000000000..d9a9dc835
--- /dev/null
+++ b/mascara/src/app/first-time/import-seed-phrase-screen.js
@@ -0,0 +1,109 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux'
+import LoadingScreen from './loading-screen'
+import {createNewVaultAndRestore, hideWarning, displayWarning} from '../../../../ui/app/actions'
+
+class ImportSeedPhraseScreen extends Component {
+ static propTypes = {
+ warning: PropTypes.string,
+ back: PropTypes.func.isRequired,
+ next: PropTypes.func.isRequired,
+ createNewVaultAndRestore: PropTypes.func.isRequired,
+ hideWarning: PropTypes.func.isRequired,
+ isLoading: PropTypes.bool.isRequired,
+ displayWarning: PropTypes.func,
+ };
+
+ state = {
+ seedPhrase: '',
+ password: '',
+ confirmPassword: '',
+ }
+
+ onClick = () => {
+ const { password, seedPhrase, confirmPassword } = this.state
+ const { createNewVaultAndRestore, next, displayWarning } = this.props
+
+ if (seedPhrase.split(' ').length !== 12) {
+ this.warning = 'Seed Phrases are 12 words long'
+ displayWarning(this.warning)
+ return
+ }
+
+ if (password.length < 8) {
+ this.warning = 'Passwords require a mimimum length of 8'
+ displayWarning(this.warning)
+ return
+ }
+
+ if (password !== confirmPassword) {
+ this.warning = 'Confirmed password does not match'
+ displayWarning(this.warning)
+ return
+ }
+ this.warning = null
+ createNewVaultAndRestore(password, seedPhrase)
+ .then(next)
+ }
+
+ render () {
+ return this.props.isLoading
+ ? <LoadingScreen loadingMessage="Creating your new account" />
+ : (
+ <div className="import-account">
+ <a
+ className="import-account__back-button"
+ onClick={e => {
+ e.preventDefault()
+ this.props.back()
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ <div className="import-account__title">
+ Import an Account with Seed Phrase
+ </div>
+ <div className="import-account__selector-label">
+ Enter your secret twelve word phrase here to restore your vault.
+ </div>
+ <textarea
+ className="import-account__secret-phrase"
+ onChange={e => this.setState({seedPhrase: e.target.value})}
+ />
+ <span
+ className="error"
+ >
+ {this.props.warning}
+ </span>
+ <input
+ className="first-time-flow__input"
+ type="password"
+ placeholder="New Password (min 8 characters)"
+ onChange={e => this.setState({password: e.target.value})}
+ />
+ <input
+ className="first-time-flow__input create-password__confirm-input"
+ type="password"
+ placeholder="Confirm Password"
+ onChange={e => this.setState({confirmPassword: e.target.value})}
+ />
+ <button
+ className="first-time-flow__button"
+ onClick={this.onClick}
+ >
+ Import
+ </button>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ appState: { isLoading, warning } }) => ({ isLoading, warning }),
+ dispatch => ({
+ createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
+ displayWarning: (warning) => dispatch(displayWarning(warning)),
+ hideWarning: () => dispatch(hideWarning()),
+ })
+)(ImportSeedPhraseScreen)
diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
new file mode 100644
index 000000000..28aa3060a
--- /dev/null
+++ b/mascara/src/app/first-time/index.css
@@ -0,0 +1,750 @@
+.first-time-flow {
+ height: 100vh;
+ width: 100vw;
+ background-color: #FFF;
+ overflow: auto;
+}
+
+.create-password,
+.unique-image,
+.tou,
+.backup-phrase,
+.import-account,
+.buy-ether {
+ display: flex;
+ flex-flow: column nowrap;
+ margin: 67px 0 0 146px;
+ max-width: 35rem;
+}
+
+@media only screen and (max-width: 575px) {
+ .create-password,
+ .unique-image,
+ .tou,
+ .backup-phrase,
+ .import-account,
+ .buy-ether {
+ margin: 24px;
+ display: flex;
+ flex-flow: column nowrap;
+ width: calc(100vw - 80px);
+ }
+
+ .create-password__title,
+ .unique-image__title,
+ .tou__title,
+ .backup-phrase__title,
+ .import-account__title,
+ .buy-ether__title,
+ .tou__title,
+ .backup-phrase__title {
+ width: initial !important;
+ }
+
+ .first-time-flow__input {
+ width: initial !important;
+ font-size: 14px !important;
+ line-height: 18px !important;
+ padding: 12px !important;
+ }
+
+ .tou__body {
+ margin: 0 !important;
+ padding: 16px 20px !important;
+ height: 30vh !important;
+ width: calc(100% - 48px) !important;
+ }
+
+ .backup-phrase__content-wrapper {
+ flex-flow: column nowrap;
+ }
+
+ .backup-phrase__body-text {
+ width: initial !important;
+ }
+
+ .backup-phrase__secret {
+ width: initial !important;
+ padding: 12px !important;
+ }
+
+ .backup-phrase__secret-words {
+ font-size: 16px;
+ line-height: 22px;
+ }
+
+ .backup-phrase__tips {
+ margin: 40px 0 !important;
+ width: initial !important;
+ }
+
+ .backup-phrase__confirm-secret,
+ .import-account__secret-phrase {
+ width: initial !important;
+ height: initial !important;
+ min-height: 190px;
+ }
+
+ .backup-phrase__confirm-seed-options {
+ width: initial !important;
+ }
+}
+
+.tou {
+ max-width: 46rem;
+}
+
+.backup-phrase {
+ max-width: 100%;
+}
+
+.create-password__title,
+.unique-image__title,
+.tou__title,
+.backup-phrase__title,
+.import-account__title,
+.buy-ether__title {
+ width: 280px;
+ color: #1B344D;
+ font-size: 40px;
+ font-weight: 500;
+ line-height: 51px;
+ margin-bottom: 24px;
+}
+
+.tou__title,
+.backup-phrase__title {
+ width: 480px;
+}
+
+.create-password__confirm-input {
+ margin-top: 15px;
+}
+
+.create-password__import-link {
+ margin-bottom: 54px;
+}
+
+.unique-image__title,
+.tou__title,
+.backup-phrase__title,
+.buy-ether__title {
+ margin-top: 24px;
+}
+
+.unique-image__body-text,
+.backup-phrase__body-text,
+.buy-ether__body-text {
+ color: #1B344D;
+ font-size: 16px;
+ line-height: 23px;
+ font-family: Montserrat UltraLight;
+}
+
+.buy-ether__small-body-text {
+ font-family: Montserrat UltraLight;
+ height: 14px;
+ color: #757575;
+ font-size: 12px;
+ line-height: 14px;
+}
+
+.unique-image__body-text {
+ width: 335px;
+}
+
+.unique-image__body-text +
+.unique-image__body-text,
+.backup-phrase__body-text +
+.backup-phrase__body-text,
+.backup-phrase__tips-text +
+.backup-phrase__tips-text {
+ margin-top: 24px;
+}
+
+.tou__body {
+ border: 1px solid #979797;
+ border-radius: 8px;
+ background-color: #FFFFFF;
+ margin: 0 142px 0 0;
+ height: 334px;
+ overflow-y: auto;
+ color: #757575;
+ font-family: Montserrat UltraLight;
+ font-size: 12px;
+ line-height: 15px;
+ text-align: justify;
+ padding: 22px 30px;
+}
+
+.backup-phrase__content-wrapper {
+ display: flex;
+ flex: row nowrap;
+}
+
+.backup-phrase__body-text {
+ width: 450px;
+}
+
+.backup-phrase__tips {
+ margin: 40px 85px;
+ width: 285px;
+}
+
+.backup-phrase__tips-text {
+ color: #5B5D67;
+ font-size: 16px;
+ line-height: 23px;
+ font-family: Montserrat UltraLight;
+}
+
+.backup-phrase__secret {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ width: 349px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ padding: 20px 0;
+ margin-top: 36px;
+}
+
+.backup-phrase__secret-words {
+ width: 310px;
+ color: #5B5D67;
+ font-family: Montserrat Light;
+ font-size: 20px;
+ line-height: 26px;
+ text-align: center;
+}
+
+.backup-phrase__secret-words--hidden {
+ filter: blur(5px);
+}
+
+.backup-phrase__secret-blocker {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(0,0,0,0.6);
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ padding: 13px 0 18px;
+}
+
+.backup-phrase__reveal-button {
+ border: 1px solid #979797;
+ border-radius: 4px;
+ background: none;
+ box-shadow: none;
+ color: #FFFFFF;
+ font-family: Montserrat Regular;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 15px;
+ text-align: center;
+ text-transform: uppercase;
+ margin-top: 10px;
+}
+
+.backup-phrase__back-button,
+.backup-phrase__back-button:hover,
+.import-account__back-button,
+.import-account__back-button:hover {
+ margin-bottom: 18px;
+ color: #22232C;
+ font-family: Montserrat Regular;
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 21px;
+}
+
+button.backup-phrase__reveal-button:hover {
+ transform: scale(1);
+}
+
+.backup-phrase__confirm-secret,
+.import-account__secret-phrase {
+ height: 190px;
+ width: 495px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ margin: 25px 0 36px;
+ padding: 17px;
+}
+
+.import-account__secret-phrase {
+ font-size: 16px;
+}
+
+.backup-phrase__confirm-seed-options {
+ display: flex;
+ flex-flow: row wrap;
+ width: 465px;
+ position: relative;
+ left: -7px;
+}
+
+.backup-phrase__confirm-seed-option {
+ color: #5B5D67;
+ font-family: Montserrat Light;
+ font-size: 16px;
+ line-height: 21px;
+ background-color: #E7E7E7;
+ padding: 8px 19px;
+ box-shadow: none;
+ min-width: 65px;
+ margin: 7px;
+}
+
+.backup-phrase__confirm-seed-option--selected {
+ background-color: #85D1CC;
+ color: #FFFFFF;
+}
+
+button.backup-phrase__confirm-seed-option:hover {
+ transform: scale(1);
+}
+
+.import-account__faq-link {
+ font-size: 18px;
+ line-height: 23px;
+ font-family: Montserrat Light;
+}
+
+.import-account__selector-label {
+ color: #1B344D;
+ font-family: Montserrat Light;
+ font-size: 18px;
+ line-height: 23px;
+}
+
+.import-account__dropdown {
+ width: 325px;
+ border: 1px solid #CDCDCD;
+ border-radius: 4px;
+ background-color: #FFFFFF;
+ margin-top: 14px;
+ color: #5B5D67;
+ font-family: Montserrat Light;
+ font-size: 18px;
+ line-height: 23px;
+ padding: 14px 21px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ cursor: pointer;
+}
+
+.import-account__description-text {
+ color: #757575;
+ font-size: 18px;
+ line-height: 23px;
+ margin-top: 21px;
+ font-family: Montserrat UltraLight;
+}
+
+.import-account__input-wrapper {
+ display: flex;
+ flex-flow: column nowrap;
+ margin-top: 30px;
+}
+
+.first-time-flow__input--error {
+ border: 1px solid #FF001F !important;
+}
+
+.import-account__input-error-message {
+ margin-top: 10px;
+ width: 422px;
+ color: #FF001F;
+ font-family: Montserrat Light;
+ font-size: 16px;
+ line-height: 21px;
+}
+
+.import-account__input-label {
+ margin-bottom: 9px;
+ color: #1B344D;
+ font-family: Montserrat Light;
+ font-size: 18px;
+ line-height: 23px;
+ text-transform: uppercase;
+}
+
+.import-account__input {
+ width: 325px !important;
+}
+
+.import-account__file-input {
+ display: none;
+}
+
+.import-account__file-input-label {
+ height: 53px;
+ width: 148px;
+ border: 1px solid #1B344D;
+ border-radius: 4px;
+ color: #1B344D;
+ font-family: Montserrat Light;
+ font-size: 18px;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.import-account__file-picker-wrapper {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
+
+.import-account__file-name {
+ color: #000000;
+ font-family: Montserrat Light;
+ font-size: 18px;
+ line-height: 23px;
+ margin-left: 22px;
+}
+
+.buy-ether__content-wrapper {
+ display: flex;
+ flex-flow: column nowrap;
+ margin-top: 31px;
+}
+
+.buy-ether__content-headline-wrapper {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.buy-ether__content-headline {
+ color: #1B344D;
+ font-family: Montserrat Light;
+ font-size: 18px;
+ line-height: 23px;
+}
+
+.buy-ether__do-it-later {
+ color: #1B344D;
+ font-size: 16px;
+ line-height: 23px;
+ cursor: pointer;
+}
+
+.buy-ether__content {
+ margin-top: 12px;
+ display: flex;
+ flex-flow: row nowrap;
+}
+
+.buy-ether__side-panel {
+ display: flex;
+ flex-flow: column nowrap;
+}
+
+.buy-ether__side-panel-item {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 20px 0;
+ color: #9B9B9B;
+ font-family: Montserrat Light;
+ font-size: 14px;
+ line-height: 18px;
+ cursor: pointer;
+ min-width: 140px;
+}
+
+
+.buy-ether__side-panel-item {
+ border-bottom: 1px solid #CDCDCD;
+}
+
+.buy-ether__side-panel-item--selected {
+ position: relative;
+ color: #1B344D;
+}
+
+.buy-ether__side-panel-item-name {
+ flex: 1 0 auto;
+ padding-right: 13px;
+}
+
+.buy-ether__action-content {
+ margin-left: 34px;
+}
+
+.buy-ether__buttons {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
+
+.buy-ether__button-separator-text {
+ font-size: 20px;
+ line-height: 26px;
+ font-family: Montserrat Light;
+ margin: 35px 0 14px 30px;
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: center;
+}
+
+.buy-ether__faq-link {
+ margin-top: 26px;
+ color: #1B344D !important;
+ font-size: 14px !important;
+ line-height: 18px !important;
+ font-family: Montserrat UltraLight !important;
+}
+
+.buy-ether__action-content-wrapper {
+ width: 360px;
+ display: flex;
+ flex-flow: column nowrap;
+}
+
+.first-time-flow__input {
+ width: 350px;
+ font-size: 18px;
+ line-height: 24px;
+ padding: 15px 28px;
+ border: 1px solid #CDCDCD;
+ background-color: #FFFFFF;
+}
+
+.first-time-flow__input::placeholder {
+ color: #9B9B9B;
+ font-weight: 200;
+}
+
+.first-time-flow__button {
+ height: 54px;
+ width: 198px;
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14);
+ color: #FFFFFF;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 26px;
+ text-align: center;
+ text-transform: uppercase;
+ margin: 35px 0 14px;
+ transition: 200ms ease-in-out;
+}
+
+button.first-time-flow__button[disabled] {
+ background-color: rgba(247, 134, 28, 0.9);
+ opacity: .6;
+}
+
+button.first-time-flow__button:hover {
+ transform: scale(1);
+ background-color: rgba(247, 134, 28, 0.9);
+}
+
+.first-time-flow__button--tertiary {
+ height: 54px;
+ width: 198px;
+ box-shadow: none;
+ color: #1B344D;
+ font-size: 20px;
+ line-height: 26px;
+ font-family: Montserrat Light;
+ text-align: center;
+ margin: 35px 0 14px;
+ background-color: transparent;
+}
+
+button.first-time-flow__button--tertiary:hover {
+ transform: scale(1);
+}
+
+.first-time-flow__link {
+ color: #1B344D;
+ font-size: 18px;
+ line-height: 23px;
+}
+
+.breadcrumbs {
+ display: flex;
+ flex-flow: row nowrap;
+}
+
+.breadcrumb {
+ height: 10px;
+ width: 10px;
+ border: 1px solid #979797;
+ border-radius: 50%;
+}
+
+.breadcrumb + .breadcrumb {
+ margin-left: 10px;
+}
+
+.loading-screen {
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ margin-top: 143px;
+}
+
+.loading-screen .spinner {
+ margin-bottom: 25px;
+ width: 100px;
+ height: 100px;
+}
+
+.loading-screen__message {
+ color: #1B344D;
+ font-size: 20px;
+ line-height: 26px;
+ text-align: center;
+ font-family: Montserrat UltraLight;
+}
+
+.icon {
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+}
+
+.shapeshift-logo {
+ background: url('data:image/png;base64,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');
+ width: 161px;
+ height: 84px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: 50%;
+}
+
+.shapeshift-form {
+ width: 360px;
+ border-radius: 8px;
+ background-color: rgba(0, 0, 0, .05);
+ padding: 17px 15px;
+}
+
+.shapeshift-form__selectors {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding-bottom: 17px;
+}
+
+.shapeshift-form__caret {
+ width: 40px;
+ height: 40px;
+ flex: 0 0 auto;
+ width: 120px;
+ margin-top: 24px;
+}
+
+.shapeshift-form__selector {
+ flex: 1 0 auto;
+}
+
+.shapeshift-form__selector-label,
+.shapeshift-form__deposit-instruction {
+ color: #757575;
+ color: rgba(0, 0, 0, 0.45);
+ font-family: Montserrat Light;
+ font-weight: 300;
+ line-height: 19px;
+ padding-bottom: 6px;
+}
+
+.shapeshift-form__selector-input {
+ color: #5B5D67;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 21px;
+ border: 1px solid #D8D8D8;
+ background-color: #FFFFFF;
+ text-align: center;
+ width: 100%;
+ height: 45px;
+ line-height: 44px;
+ font-family: Montserrat Light;
+}
+
+.shapeshift-form__address-input-label {
+ color: #757575;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 18px;
+ padding-bottom: 6px;
+ font-family: Montserrat Light;
+}
+
+.shapeshift-form__address-input {
+ border: 1px solid #D8D8D8;
+ background-color: #FFFFFF;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 21px;
+ padding: 15px;
+ width: 100%;
+}
+
+.shapeshift-form__address-input-wrapper--error .shapeshift-form__address-input {
+ border-color: #FF001F;
+}
+
+.shapeshift-form__address-input-error-message {
+ color: #FF001F;
+ font-family: Montserrat Light;
+ font-size: 12px;
+ height: 24px;
+ line-height: 18px;
+}
+
+.shapeshift-form__metadata {
+ display: flex;
+ flex-flow: row wrap;
+ color: #9B9B9B;
+ font-family: Montserrat Light;
+ font-size: 10px;
+ line-height: 16px;
+}
+
+.shapeshift-form__metadata-wrapper {
+ flex: 1 0 50%;
+ display: flex;
+ flex-flow: row nowrap;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.shapeshift-form__metadata-wrapper:nth-child(odd) {
+ padding-right: 14px;
+}
+
+.shapeshift-form__metadata-label {
+ flex: 1 0 60%;
+}
+
+.shapeshift-form__metadata-value {
+ flex: 0 0 40%;
+ overflow: hidden;
+ color: #000;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.shapeshift-form__qr-code {
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: center;
+}
diff --git a/mascara/src/app/first-time/index.js b/mascara/src/app/first-time/index.js
new file mode 100644
index 000000000..66b591bd8
--- /dev/null
+++ b/mascara/src/app/first-time/index.js
@@ -0,0 +1,142 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux'
+import CreatePasswordScreen from './create-password-screen'
+import UniqueImageScreen from './unique-image-screen'
+import NoticeScreen from './notice-screen'
+import BackupPhraseScreen from './backup-phrase-screen'
+import ImportAccountScreen from './import-account-screen'
+import ImportSeedPhraseScreen from './import-seed-phrase-screen'
+import {onboardingBuyEthView} from '../../../../ui/app/actions'
+
+class FirstTimeFlow extends Component {
+
+ static propTypes = {
+ isInitialized: PropTypes.bool,
+ seedWords: PropTypes.string,
+ address: PropTypes.string,
+ noActiveNotices: PropTypes.bool,
+ goToBuyEtherView: PropTypes.func.isRequired,
+ };
+
+ static defaultProps = {
+ isInitialized: false,
+ seedWords: '',
+ noActiveNotices: false,
+ };
+
+ static SCREEN_TYPE = {
+ CREATE_PASSWORD: 'create_password',
+ IMPORT_ACCOUNT: 'import_account',
+ IMPORT_SEED_PHRASE: 'import_seed_phrase',
+ UNIQUE_IMAGE: 'unique_image',
+ NOTICE: 'notice',
+ BACK_UP_PHRASE: 'back_up_phrase',
+ CONFIRM_BACK_UP_PHRASE: 'confirm_back_up_phrase',
+ };
+
+ constructor (props) {
+ super(props)
+ this.state = {
+ screenType: this.getScreenType(),
+ }
+ }
+
+ setScreenType (screenType) {
+ this.setState({ screenType })
+ }
+
+ getScreenType () {
+ const {
+ isInitialized,
+ seedWords,
+ noActiveNotices,
+ } = this.props
+ const {SCREEN_TYPE} = FirstTimeFlow
+
+ // return SCREEN_TYPE.NOTICE
+
+ if (!isInitialized) {
+ return SCREEN_TYPE.CREATE_PASSWORD
+ }
+
+ if (!noActiveNotices) {
+ return SCREEN_TYPE.NOTICE
+ }
+
+ if (seedWords) {
+ return SCREEN_TYPE.BACK_UP_PHRASE
+ }
+ };
+
+ renderScreen () {
+ const {SCREEN_TYPE} = FirstTimeFlow
+ const {goToBuyEtherView, address} = this.props
+
+ switch (this.state.screenType) {
+ case SCREEN_TYPE.CREATE_PASSWORD:
+ return (
+ <CreatePasswordScreen
+ next={() => this.setScreenType(SCREEN_TYPE.UNIQUE_IMAGE)}
+ goToImportAccount={() => this.setScreenType(SCREEN_TYPE.IMPORT_ACCOUNT)}
+ goToImportWithSeedPhrase={() => this.setScreenType(SCREEN_TYPE.IMPORT_SEED_PHRASE)}
+ />
+ )
+ case SCREEN_TYPE.IMPORT_ACCOUNT:
+ return (
+ <ImportAccountScreen
+ back={() => this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)}
+ next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
+ />
+ )
+ case SCREEN_TYPE.IMPORT_SEED_PHRASE:
+ return (
+ <ImportSeedPhraseScreen
+ back={() => this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)}
+ next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
+ />
+ )
+ case SCREEN_TYPE.UNIQUE_IMAGE:
+ return (
+ <UniqueImageScreen
+ next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
+ />
+ )
+ case SCREEN_TYPE.NOTICE:
+ return (
+ <NoticeScreen
+ next={() => this.setScreenType(SCREEN_TYPE.BACK_UP_PHRASE)}
+ />
+ )
+ case SCREEN_TYPE.BACK_UP_PHRASE:
+ return (
+ <BackupPhraseScreen
+ next={() => goToBuyEtherView(address)}
+ />
+ )
+ default:
+ return <noscript />
+ }
+ }
+
+ render () {
+ return (
+ <div className="first-time-flow">
+ {this.renderScreen()}
+ </div>
+ )
+ }
+
+}
+
+export default connect(
+ ({ metamask: { isInitialized, seedWords, noActiveNotices, selectedAddress } }) => ({
+ isInitialized,
+ seedWords,
+ noActiveNotices,
+ address: selectedAddress,
+ }),
+ dispatch => ({
+ goToBuyEtherView: address => dispatch(onboardingBuyEthView(address)),
+ })
+)(FirstTimeFlow)
+
diff --git a/mascara/src/app/first-time/loading-screen.js b/mascara/src/app/first-time/loading-screen.js
new file mode 100644
index 000000000..732b7f2d7
--- /dev/null
+++ b/mascara/src/app/first-time/loading-screen.js
@@ -0,0 +1,11 @@
+import React, {Component, PropTypes} from 'react'
+import Spinner from './spinner'
+
+export default function LoadingScreen({ className = '', loadingMessage }) {
+ return (
+ <div className={`${className} loading-screen`}>
+ <Spinner color="#1B344D" />
+ <div className="loading-screen__message">{loadingMessage}</div>
+ </div>
+ );
+}
diff --git a/mascara/src/app/first-time/notice-screen.js b/mascara/src/app/first-time/notice-screen.js
new file mode 100644
index 000000000..713f3f242
--- /dev/null
+++ b/mascara/src/app/first-time/notice-screen.js
@@ -0,0 +1,92 @@
+import React, {Component, PropTypes} from 'react'
+import Markdown from 'react-markdown'
+import {connect} from 'react-redux'
+import debounce from 'lodash.debounce'
+import {markNoticeRead} from '../../../../ui/app/actions'
+import Identicon from '../../../../ui/app/components/identicon'
+import Breadcrumbs from './breadcrumbs'
+
+class NoticeScreen extends Component {
+ static propTypes = {
+ address: PropTypes.string.isRequired,
+ lastUnreadNotice: PropTypes.shape({
+ title: PropTypes.string,
+ date: PropTypes.string,
+ body: PropTypes.string
+ }),
+ next: PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ lastUnreadNotice: {}
+ };
+
+ state = {
+ atBottom: false,
+ }
+
+ componentDidMount() {
+ this.onScroll()
+ }
+
+ acceptTerms = () => {
+ const { markNoticeRead, lastUnreadNotice, next } = this.props;
+ const defer = markNoticeRead(lastUnreadNotice)
+ .then(() => this.setState({ atBottom: false }))
+
+ if ((/terms/gi).test(lastUnreadNotice.title)) {
+ defer.then(next)
+ }
+ }
+
+ onScroll = debounce(() => {
+ if (this.state.atBottom) return
+
+ const target = document.querySelector('.tou__body')
+ const {scrollTop, offsetHeight, scrollHeight} = target;
+ const atBottom = scrollTop + offsetHeight >= scrollHeight;
+
+ this.setState({atBottom: atBottom})
+ }, 25)
+
+ render() {
+ const {
+ address,
+ lastUnreadNotice: { title, body }
+ } = this.props;
+ const { atBottom } = this.state
+
+ return (
+ <div
+ className="tou"
+ onScroll={this.onScroll}
+ >
+ <Identicon address={address} diameter={70} />
+ <div className="tou__title">{title}</div>
+ <Markdown
+ className="tou__body"
+ source={body}
+ skipHtml
+ />
+ <button
+ className="first-time-flow__button"
+ onClick={atBottom && this.acceptTerms}
+ disabled={!atBottom}
+ >
+ Accept
+ </button>
+ <Breadcrumbs total={3} currentIndex={2} />
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ metamask: { selectedAddress, lastUnreadNotice } }) => ({
+ lastUnreadNotice,
+ address: selectedAddress
+ }),
+ dispatch => ({
+ markNoticeRead: notice => dispatch(markNoticeRead(notice))
+ })
+)(NoticeScreen)
diff --git a/mascara/src/app/first-time/spinner.js b/mascara/src/app/first-time/spinner.js
new file mode 100644
index 000000000..78dca9a88
--- /dev/null
+++ b/mascara/src/app/first-time/spinner.js
@@ -0,0 +1,70 @@
+import React from 'react';
+
+export default function Spinner({ className = '', color = "#000000" }) {
+ return (
+ <div className={`spinner ${className}`}>
+ <svg className="lds-spinner" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style={{background: 'none'}}>
+ <g transform="rotate(0 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(30 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(60 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.75s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(90 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(120 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(150 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(180 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(210 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(240 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.25s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(270 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(300 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ <g transform="rotate(330 50 50)">
+ <rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
+ <animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" />
+ </rect>
+ </g>
+ </svg>
+ </div>
+ );
+}
diff --git a/mascara/src/app/first-time/unique-image-screen.js b/mascara/src/app/first-time/unique-image-screen.js
new file mode 100644
index 000000000..ef6bd28ab
--- /dev/null
+++ b/mascara/src/app/first-time/unique-image-screen.js
@@ -0,0 +1,39 @@
+import React, {Component, PropTypes} from 'react'
+import {connect} from 'react-redux'
+import Identicon from '../../../../ui/app/components/identicon'
+import Breadcrumbs from './breadcrumbs'
+
+class UniqueImageScreen extends Component {
+ static propTypes = {
+ address: PropTypes.string,
+ next: PropTypes.func.isRequired,
+ }
+
+ render () {
+ return (
+ <div className="unique-image">
+ <Identicon address={this.props.address} diameter={70} />
+ <div className="unique-image__title">Your unique account image</div>
+ <div className="unique-image__body-text">
+ This image was programmatically generated for you by your new account number.
+ </div>
+ <div className="unique-image__body-text">
+ You’ll see this image everytime you need to confirm a transaction.
+ </div>
+ <button
+ className="first-time-flow__button"
+ onClick={this.props.next}
+ >
+ Next
+ </button>
+ <Breadcrumbs total={3} currentIndex={1} />
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ metamask: { selectedAddress } }) => ({
+ address: selectedAddress,
+ })
+)(UniqueImageScreen)
diff --git a/mascara/src/app/shapeshift-form/index.js b/mascara/src/app/shapeshift-form/index.js
new file mode 100644
index 000000000..15c7e95e1
--- /dev/null
+++ b/mascara/src/app/shapeshift-form/index.js
@@ -0,0 +1,217 @@
+import React, {Component, PropTypes} from 'react'
+import classnames from 'classnames'
+import {qrcode} from 'qrcode-npm'
+import {connect} from 'react-redux'
+import {shapeShiftSubview, pairUpdate, buyWithShapeShift} from '../../../../ui/app/actions'
+import {isValidAddress} from '../../../../ui/app/util'
+
+export class ShapeShiftForm extends Component {
+ static propTypes = {
+ selectedAddress: PropTypes.string.isRequired,
+ btnClass: PropTypes.string.isRequired,
+ tokenExchangeRates: PropTypes.object.isRequired,
+ coinOptions: PropTypes.object.isRequired,
+ shapeShiftSubview: PropTypes.func.isRequired,
+ pairUpdate: PropTypes.func.isRequired,
+ buyWithShapeShift: PropTypes.func.isRequired,
+ };
+
+ state = {
+ depositCoin: 'btc',
+ refundAddress: '',
+ showQrCode: false,
+ depositAddress: '',
+ errorMessage: '',
+ isLoading: false,
+ };
+
+ componentWillMount () {
+ this.props.shapeShiftSubview()
+ }
+
+ onCoinChange = e => {
+ const coin = e.target.value
+ this.setState({
+ depositCoin: coin,
+ errorMessage: '',
+ })
+ this.props.pairUpdate(coin)
+ }
+
+ onBuyWithShapeShift = () => {
+ this.setState({
+ isLoading: true,
+ showQrCode: true,
+ })
+
+ const {
+ buyWithShapeShift,
+ selectedAddress: withdrawal,
+ } = this.props
+ const {
+ refundAddress: returnAddress,
+ depositCoin,
+ } = this.state
+ const pair = `${depositCoin}_eth`
+ const data = {
+ withdrawal,
+ pair,
+ returnAddress,
+ // Public api key
+ 'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
+ }
+
+ if (isValidAddress(withdrawal)) {
+ buyWithShapeShift(data)
+ .then(d => this.setState({
+ showQrCode: true,
+ depositAddress: d.deposit,
+ isLoading: false,
+ }))
+ .catch(() => this.setState({
+ showQrCode: false,
+ errorMessage: 'Invalid Request',
+ isLoading: false,
+ }))
+ }
+ }
+
+ renderMetadata (label, value) {
+ return (
+ <div className='shapeshift-form__metadata-wrapper'>
+ <div className='shapeshift-form__metadata-label'>
+ {label}:
+ </div>
+ <div className='shapeshift-form__metadata-value'>
+ {value}
+ </div>
+ </div>
+ )
+ }
+
+ renderMarketInfo () {
+ const { depositCoin } = this.state
+ const coinPair = `${depositCoin}_eth`
+ const { tokenExchangeRates } = this.props
+ const {
+ limit,
+ rate,
+ minimum,
+ } = tokenExchangeRates[coinPair] || {}
+
+ return (
+ <div className='shapeshift-form__metadata'>
+ {this.renderMetadata('Status', limit ? 'Available' : 'Unavailable')}
+ {this.renderMetadata('Limit', limit)}
+ {this.renderMetadata('Exchange Rate', rate)}
+ {this.renderMetadata('Minimum', minimum)}
+ </div>
+ )
+ }
+
+ renderQrCode () {
+ const { depositAddress, isLoading } = this.state
+ const qrImage = qrcode(4, 'M')
+ qrImage.addData(depositAddress)
+ qrImage.make()
+
+ return (
+ <div className='shapeshift-form'>
+ <div className='shapeshift-form__deposit-instruction'>
+ Deposit your BTC to the address bellow:
+ </div>
+ <div className='shapeshift-form__qr-code'>
+ {isLoading
+ ? <img src='images/loading.svg' style={{ width: '60px' }} />
+ : <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
+ }
+ </div>
+ {this.renderMarketInfo()}
+ </div>
+ )
+ }
+
+ render () {
+ const { coinOptions, btnClass } = this.props
+ const { depositCoin, errorMessage, showQrCode } = this.state
+ const coinPair = `${depositCoin}_eth`
+ const { tokenExchangeRates } = this.props
+ const token = tokenExchangeRates[coinPair]
+
+ return showQrCode ? this.renderQrCode() : (
+ <div>
+ <div className='shapeshift-form'>
+ <div className='shapeshift-form__selectors'>
+ <div className='shapeshift-form__selector'>
+ <div className='shapeshift-form__selector-label'>
+ Deposit
+ </div>
+ <select
+ className='shapeshift-form__selector-input'
+ value={this.state.depositCoin}
+ onChange={this.onCoinChange}
+ >
+ {Object.entries(coinOptions).map(([coin]) => (
+ <option key={coin} value={coin.toLowerCase()}>
+ {coin}
+ </option>
+ ))}
+ </select>
+ </div>
+ <div
+ className='icon shapeshift-form__caret'
+ style={{ backgroundImage: 'url(images/caret-right.svg)'}}
+ />
+ <div className='shapeshift-form__selector'>
+ <div className='shapeshift-form__selector-label'>
+ Receive
+ </div>
+ <div className='shapeshift-form__selector-input'>
+ ETH
+ </div>
+ </div>
+ </div>
+ <div
+ className={classnames('shapeshift-form__address-input-wrapper', {
+ 'shapeshift-form__address-input-wrapper--error': errorMessage,
+ })}
+ >
+ <div className='shapeshift-form__address-input-label'>
+ Your Refund Address
+ </div>
+ <input
+ type='text'
+ className='shapeshift-form__address-input'
+ onChange={e => this.setState({
+ refundAddress: e.target.value,
+ errorMessage: '',
+ })}
+ />
+ <div className='shapeshift-form__address-input-error-message'>
+ {errorMessage}
+ </div>
+ </div>
+ {this.renderMarketInfo()}
+ </div>
+ <button
+ className={btnClass}
+ disabled={!token}
+ onClick={this.onBuyWithShapeShift}
+ >
+ Buy
+ </button>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ metamask: { coinOptions, tokenExchangeRates, selectedAddress } }) => ({
+ coinOptions, tokenExchangeRates, selectedAddress,
+ }),
+ dispatch => ({
+ shapeShiftSubview: () => dispatch(shapeShiftSubview()),
+ pairUpdate: coin => dispatch(pairUpdate(coin)),
+ buyWithShapeShift: data => dispatch(buyWithShapeShift(data)),
+ })
+)(ShapeShiftForm)
diff --git a/package.json b/package.json
index ed5b5fea1..28f35914f 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"dist": "npm run dist:clear && npm install && gulp dist",
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"test": "npm run lint && npm run test:coverage && npm run test:integration",
- "test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
+ "test:unit": "METAMASK_ENV=test mocha --compilers js:babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:single": "METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "npm run test:flat && npm run test:mascara",
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
@@ -29,6 +29,7 @@
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
"lint": "gulp lint",
+ "lint:fix": "gulp lint:fix",
"disc": "gulp disc --debug",
"announce": "node development/announcer.js",
"generateNotice": "node notices/notice-generator.js",
@@ -73,11 +74,12 @@
"dnode": "^1.2.2",
"end-of-stream": "^1.1.0",
"ensnare": "^1.0.0",
+ "eslint-plugin-react": "^7.4.0",
"eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^2.2.0",
"eth-hd-keyring": "^1.2.1",
- "eth-json-rpc-filters": "^1.2.2",
"eth-contract-metadata": "^1.1.5",
+ "eth-json-rpc-filters": "^1.2.4",
"eth-keyring-controller": "^2.1.2",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
@@ -111,8 +113,11 @@
"iframe-stream": "^3.0.0",
"inject-css": "^0.1.1",
"jazzicon": "^1.2.0",
- "json-rpc-engine": "^3.2.0",
+ "json-rpc-engine": "3.2.0",
"json-rpc-middleware-stream": "^1.0.1",
+ "lodash.debounce": "^4.0.8",
+ "lodash.memoize": "^4.1.2",
+ "lodash.shuffle": "^4.2.0",
"loglevel": "^1.4.1",
"metamascara": "^1.3.1",
"metamask-logo": "^2.1.2",
@@ -144,7 +149,9 @@
"react-tooltip-component": "^0.3.0",
"react-transition-group": "^2.2.0",
"reactify": "^1.1.1",
+ "react-trigger-change": "^1.0.2",
"readable-stream": "^2.3.3",
+ "recompose": "^0.25.0",
"redux": "^3.0.5",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
@@ -168,6 +175,7 @@
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
+ "babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.7.2",
"babelify": "^7.2.0",
diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js
index cedb14f6e..61b38897e 100644
--- a/test/integration/lib/first-time.js
+++ b/test/integration/lib/first-time.js
@@ -1,4 +1,5 @@
const PASSWORD = 'password123'
+const runMascaraFirstTimeTest = require('./mascara-first-time')
QUnit.module('first time usage')
@@ -11,9 +12,9 @@ QUnit.test('render init screen', (assert) => {
})
async function runFirstTimeUsageTest(assert, done) {
- let waitTime = 0
- if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 4000
- await timeout(waitTime)
+ if (window.METAMASK_PLATFORM_TYPE === 'mascara') {
+ return runMascaraFirstTimeTest(assert, done)
+ }
const app = $('#app-content')
diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js
new file mode 100644
index 000000000..3398a5511
--- /dev/null
+++ b/test/integration/lib/mascara-first-time.js
@@ -0,0 +1,167 @@
+const PASSWORD = 'password123'
+const reactTriggerChange = require('react-trigger-change')
+
+async function runFirstTimeUsageTest (assert, done) {
+ await timeout(4000)
+
+ const app = $('#app-content')
+
+ // recurse notices
+ while (true) {
+ const button = app.find('button')
+ if (button.html() === 'Accept') {
+ // still notices to accept
+ const termsPage = app.find('.markdown')[0]
+ termsPage.scrollTop = termsPage.scrollHeight
+ await timeout()
+ console.log('Clearing notice')
+ button.click()
+ await timeout()
+ } else {
+ // exit loop
+ console.log('No more notices...')
+ break
+ }
+ }
+
+ await timeout()
+
+ // Scroll through terms
+ const title = app.find('.create-password__title').text()
+ assert.equal(title, 'Create Password', 'create password screen')
+
+ // enter password
+ const pwBox = app.find('.first-time-flow__input')[0]
+ const confBox = app.find('.first-time-flow__input')[1]
+ pwBox.value = PASSWORD
+ confBox.value = PASSWORD
+ reactTriggerChange(pwBox)
+ reactTriggerChange(confBox)
+
+
+ await timeout()
+
+ // Create Password
+ const createButton = app.find('button.first-time-flow__button')[0]
+ createButton.click()
+
+ await timeout(3000)
+
+ const created = app.find('.unique-image__title')[0]
+ assert.equal(created.textContent, 'Your unique account image', 'unique image screen')
+
+ // Agree button
+ const button = app.find('button')[0]
+ assert.ok(button, 'button present')
+ button.click()
+
+ await timeout(1000)
+
+ // Privacy Screen
+ const detail = app.find('.tou__title')[0]
+ assert.equal(detail.textContent, 'Privacy Notice', 'privacy notice screen')
+ app.find('button').click()
+
+ await timeout(1000)
+
+
+ // terms of service screen
+ const tou = app.find('.tou__title')[0]
+ assert.equal(tou.textContent, 'Terms of Use', 'terms of use screen')
+ app.find('.tou__body').scrollTop(100000)
+ await timeout(1000)
+
+ app.find('.first-time-flow__button').click()
+ await timeout(1000)
+
+ // secret backup phrase
+ const seedTitle = app.find('.backup-phrase__title')[0]
+ assert.equal(seedTitle.textContent, 'Secret Backup Phrase', 'seed phrase screen')
+ app.find('.backup-phrase__reveal-button').click()
+
+ await timeout(1000)
+ const seedPhrase = app.find('.backup-phrase__secret-words').text().split(' ')
+ app.find('.first-time-flow__button').click()
+
+ const selectPhrase = text => {
+ const option = $('.backup-phrase__confirm-seed-option')
+ .filter((i, d) => d.textContent === text)[0]
+
+ $(option).click()
+ }
+
+ await timeout(1000)
+
+ seedPhrase.forEach(sp => selectPhrase(sp))
+ app.find('.first-time-flow__button').click()
+ await timeout(1000)
+
+ // Deposit Ether Screen
+ const buyEthTitle = app.find('.buy-ether__title')[0]
+ assert.equal(buyEthTitle.textContent, 'Deposit Ether', 'deposit ether screen')
+ app.find('.buy-ether__do-it-later').click()
+ await timeout(1000)
+
+ const sandwich = app.find('.sandwich-expando')[0]
+ sandwich.click()
+
+ await timeout()
+
+ const menu = app.find('.menu-droppo')[0]
+ const children = menu.children
+ const lock = children[children.length - 2]
+ assert.ok(lock, 'Lock menu item found')
+ lock.click()
+
+ await timeout(1000)
+
+ const pwBox2 = app.find('#password-box')[0]
+ pwBox2.value = PASSWORD
+
+ const createButton2 = app.find('button.primary')[0]
+ createButton2.click()
+
+ await timeout(1000)
+
+ const detail2 = app.find('.account-detail-section')[0]
+ assert.ok(detail2, 'Account detail section loaded again.')
+
+ await timeout()
+
+ // open account settings dropdown
+ const qrButton = app.find('.fa.fa-ellipsis-h')[0]
+ qrButton.click()
+
+ await timeout(1000)
+
+ // qr code item
+ const qrButton2 = app.find('.dropdown-menu-item')[1]
+ qrButton2.click()
+
+ await timeout(1000)
+
+ const qrHeader = app.find('.qr-header')[0]
+ const qrContainer = app.find('#qr-container')[0]
+ assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
+ assert.ok(qrContainer, 'QR Container found')
+
+ await timeout()
+
+ const networkMenu = app.find('.network-indicator')[0]
+ networkMenu.click()
+
+ await timeout()
+
+ const networkMenu2 = app.find('.network-indicator')[0]
+ const children2 = networkMenu2.children
+ children2.length[3]
+ assert.ok(children2, 'All network options present')
+}
+
+module.exports = runFirstTimeUsageTest
+
+function timeout (time) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, time || 1500)
+ })
+}
diff --git a/test/unit/pending-balance-test.js b/test/unit/pending-balance-test.js
index 5048d487b..dc4c1c3e4 100644
--- a/test/unit/pending-balance-test.js
+++ b/test/unit/pending-balance-test.js
@@ -9,7 +9,7 @@ const etherBn = new BN(String(1e18))
const ether = '0x' + etherBn.toString(16)
describe('PendingBalanceCalculator', function () {
- let balanceCalculator
+ let balanceCalculator, pendingTxs
describe('#calculateMaxCost(tx)', function () {
it('returns a BN for a given tx value', function () {
diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js
index 32421a44f..4b5170dfe 100644
--- a/test/unit/pending-tx-test.js
+++ b/test/unit/pending-tx-test.js
@@ -13,7 +13,8 @@ const otherNetworkId = 36
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
describe('PendingTransactionTracker', function () {
- let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, provider
+ let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub,
+ provider, txMeta3, txList, knownErrors
this.timeout(10000)
beforeEach(function () {
txMeta = {
diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js
index 158a3c923..486ed8886 100644
--- a/ui/app/accounts/import/json.js
+++ b/ui/app/accounts/import/json.js
@@ -5,7 +5,7 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const FileInput = require('react-simple-file-input').default
-const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file'
+const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
module.exports = connect(mapStateToProps)(JsonImportSubview)
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 4c83f95b4..2e9b34c58 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -1,5 +1,6 @@
const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
+const ethUtil = require('ethereumjs-util')
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
@@ -143,6 +144,7 @@ var actions = {
UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT',
UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO',
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
+ CLEAR_SEND: 'CLEAR_SEND',
updateGasLimit,
updateGasPrice,
updateGasTotal,
@@ -151,6 +153,7 @@ var actions = {
updateSendAmount,
updateSendMemo,
updateSendErrors,
+ clearSend,
setSelectedAddress,
// app messages
confirmSeedWords: confirmSeedWords,
@@ -181,9 +184,12 @@ var actions = {
showLoadingIndication: showLoadingIndication,
hideLoadingIndication: hideLoadingIndication,
// buy Eth with coinbase
+ onboardingBuyEthView,
+ ONBOARDING_BUY_ETH_VIEW: 'ONBOARDING_BUY_ETH_VIEW',
BUY_ETH: 'BUY_ETH',
buyEth: buyEth,
buyEthView: buyEthView,
+ buyWithShapeShift,
BUY_ETH_VIEW: 'BUY_ETH_VIEW',
COINBASE_SUBVIEW: 'COINBASE_SUBVIEW',
coinBaseSubview: coinBaseSubview,
@@ -268,14 +274,18 @@ function confirmSeedWords () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
- background.clearSeedWordCache((err, account) => {
- dispatch(actions.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
+ return new Promise((resolve, reject) => {
+ background.clearSeedWordCache((err, account) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ reject(err)
+ }
- log.info('Seed word cache cleared. ' + account)
- dispatch(actions.showAccountDetail(account))
+ log.info('Seed word cache cleared. ' + account)
+ dispatch(actions.showAccountsPage())
+ resolve(account)
+ })
})
}
}
@@ -284,10 +294,20 @@ function createNewVaultAndRestore (password, seed) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.createNewVaultAndRestore`)
- background.createNewVaultAndRestore(password, seed, (err) => {
- dispatch(actions.hideLoadingIndication())
- if (err) return dispatch(actions.displayWarning(err.message))
- dispatch(actions.showAccountsPage())
+
+ return new Promise((resolve, reject) => {
+ background.createNewVaultAndRestore(password, seed, (err) => {
+
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.showAccountsPage())
+ resolve()
+ })
})
}
}
@@ -296,19 +316,26 @@ function createNewVaultAndKeychain (password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.createNewVaultAndKeychain`)
- background.createNewVaultAndKeychain(password, (err) => {
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
- log.debug(`background.placeSeedWords`)
- background.placeSeedWords((err) => {
+
+ return new Promise((resolve, reject) => {
+ background.createNewVaultAndKeychain(password, (err) => {
if (err) {
- return dispatch(actions.displayWarning(err.message))
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
}
- dispatch(actions.hideLoadingIndication())
- forceUpdateMetamaskState(dispatch)
+ log.debug(`background.placeSeedWords`)
+ background.placeSeedWords((err) => {
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+ dispatch(actions.hideLoadingIndication())
+ forceUpdateMetamaskState(dispatch)
+ resolve()
+ })
})
})
+
}
}
@@ -352,18 +379,25 @@ function importNewAccount (strategy, args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
log.debug(`background.importAccountWithStrategy`)
- background.importAccountWithStrategy(strategy, args, (err) => {
- if (err) return dispatch(actions.displayWarning(err.message))
- log.debug(`background.getState`)
- background.getState((err, newState) => {
- dispatch(actions.hideLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.importAccountWithStrategy(strategy, args, (err) => {
if (err) {
- return dispatch(actions.displayWarning(err.message))
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
}
- dispatch(actions.updateMetamaskState(newState))
- dispatch({
- type: actions.SHOW_ACCOUNT_DETAIL,
- value: newState.selectedAddress,
+ log.debug(`background.getState`)
+ background.getState((err, newState) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch({
+ type: actions.SHOW_ACCOUNT_DETAIL,
+ value: newState.selectedAddress,
+ })
+ resolve(newState)
})
})
})
@@ -577,13 +611,18 @@ function updateSendMemo (memo) {
}
function updateSendErrors (error) {
- console.log(`updateSendErrors error`, error);
return {
type: actions.UPDATE_SEND_ERRORS,
value: error,
}
}
+function clearSend () {
+ return {
+ type: actions.CLEAR_SEND
+ }
+}
+
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
@@ -603,7 +642,7 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
return dispatch => {
dispatch(actions.showLoadingIndication())
const token = global.eth.contract(abi).at(tokenAddress)
- token.transfer(toAddress, amount, txData)
+ token.transfer(toAddress, ethUtil.addHexPrefix(amount), txData)
.catch(err => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.displayWarning(err.message))
@@ -941,21 +980,23 @@ function goBackToInitView () {
function markNoticeRead (notice) {
return (dispatch) => {
- dispatch(this.showLoadingIndication())
+ dispatch(actions.showLoadingIndication())
log.debug(`background.markNoticeRead`)
- background.markNoticeRead(notice, (err, notice) => {
- dispatch(this.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err))
- }
- if (notice) {
- return dispatch(actions.showNotice(notice))
- } else {
- dispatch(this.clearNotices())
- return {
- type: actions.SHOW_ACCOUNTS_PAGE,
+ return new Promise((resolve, reject) => {
+ background.markNoticeRead(notice, (err, notice) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err))
+ return reject(err)
}
- }
+ if (notice) {
+ dispatch(actions.showNotice(notice))
+ resolve()
+ } else {
+ dispatch(actions.clearNotices())
+ resolve()
+ }
+ })
})
}
}
@@ -993,7 +1034,7 @@ function setProviderType (type) {
dispatch(actions.updateProviderType(type))
dispatch(actions.setSelectedToken())
})
-
+
}
}
@@ -1172,14 +1213,22 @@ function saveAccountLabel (account, label) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.saveAccountLabel`)
- background.saveAccountLabel(account, label, (err) => {
- dispatch(actions.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
- dispatch({
- type: actions.SAVE_ACCOUNT_LABEL,
- value: { account, label },
+
+ return new Promise((resolve, reject) => {
+ background.saveAccountLabel(account, label, (err) => {
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ reject(err)
+ }
+
+ dispatch({
+ type: actions.SAVE_ACCOUNT_LABEL,
+ value: { account, label },
+ })
+
+ resolve(account)
})
})
}
@@ -1207,6 +1256,13 @@ function buyEth (opts) {
}
}
+function onboardingBuyEthView (address) {
+ return {
+ type: actions.ONBOARDING_BUY_ETH_VIEW,
+ value: address,
+ }
+}
+
function buyEthView (address) {
return {
type: actions.BUY_ETH_VIEW,
@@ -1272,6 +1328,18 @@ function coinShiftRquest (data, marketData) {
}
}
+function buyWithShapeShift (data) {
+ return dispatch => new Promise((resolve, reject) => {
+ shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
+ if (response.error) {
+ return reject(response.error)
+ }
+ background.createShapeShiftTx(response.deposit, response.depositType)
+ return resolve(response)
+ })
+ })
+}
+
function showQrView (data, message) {
return {
type: actions.SHOW_QR_VIEW,
@@ -1308,9 +1376,14 @@ function shapeShiftRequest (query, options, cb) {
options.method ? method = options.method : method = 'GET'
var requestListner = function (request) {
- queryResponse = JSON.parse(this.responseText)
- cb ? cb(queryResponse) : null
- return queryResponse
+ try {
+ queryResponse = JSON.parse(this.responseText)
+ cb ? cb(queryResponse) : null
+ return queryResponse
+ } catch (e) {
+ cb ? cb({error: e}) : null
+ return e
+ }
}
var shapShiftReq = new XMLHttpRequest()
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index e313babf3..518701a1d 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -255,9 +255,9 @@ AddTokenScreen.prototype.renderTokenList = function () {
h('div.add-token__token-symbol', symbol),
h('div.add-token__token-name', name),
]),
- tokenAlreadyAdded && (
- h('div.add-token__token-message', 'Already added')
- ),
+ // tokenAlreadyAdded && (
+ // h('div.add-token__token-message', 'Already added')
+ // ),
])
)
})
@@ -350,7 +350,10 @@ AddTokenScreen.prototype.render = function () {
h('div.add-token__footers', [
h('div.add-token__add-custom', {
onClick: () => this.setState({ isCollapsed: !isCollapsed }),
- }, 'Add custom token'),
+ }, [
+ 'Add custom token',
+ h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`),
+ ]),
this.renderCustomForm(),
]),
]),
diff --git a/ui/app/app.js b/ui/app/app.js
index ae38fad7f..7264c79c7 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -4,6 +4,9 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { checkFeatureToggle } = require('../lib/feature-toggle-utils')
const actions = require('./actions')
+// mascara
+const MascaraFirstTime = require('../../mascara/src/app/first-time').default
+const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
// init
const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain')
@@ -21,7 +24,7 @@ const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
const WalletView = require('./components/wallet-view')
// other views
-const ConfigScreen = require('./config')
+const Settings = require('./settings')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const InfoScreen = require('./info')
@@ -50,6 +53,9 @@ function mapStateToProps (state) {
accounts,
address,
keyrings,
+ isInitialized,
+ noActiveNotices,
+ seedWords,
} = state.metamask
const selected = address || Object.keys(accounts)[0]
@@ -66,6 +72,8 @@ function mapStateToProps (state) {
currentView: state.appState.currentView,
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
+ isMascara: state.metamask.isMascara,
+ isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
seedWords: state.metamask.seedWords,
unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
@@ -140,6 +148,8 @@ App.prototype.render = function () {
(isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage,
}),
+
+ // this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// content
this.renderPrimary(),
@@ -203,9 +213,35 @@ App.prototype.renderSidebar = function () {
}
App.prototype.renderAppBar = function () {
+ const {
+ isUnlocked,
+ network,
+ provider,
+ networkDropdownOpen,
+ showNetworkDropdown,
+ hideNetworkDropdown,
+ currentView,
+ } = this.props
+
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
+
+ const props = this.props
+ const state = this.state || {}
+ const isNetworkMenuOpen = state.isNetworkMenuOpen || false
+ const {isMascara, isOnboarding} = props
+
+ // Do not render header if user is in mascara onboarding
+ if (isMascara && isOnboarding) {
+ return null
+ }
+
+ // Do not render header if user is in mascara buy ether
+ if (isMascara && props.currentView.name === 'buyEth') {
+ return null
+ }
+
return (
h('.full-width', {
@@ -217,12 +253,14 @@ App.prototype.renderAppBar = function () {
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
- style: {},
+ onClick: () => {
+ props.dispatch(actions.backToAccountDetail(props.activeAddress))
+ },
}, [
// mini logo
- h('img', {
- height: 24,
- width: 24,
+ h('img.metafox-icon', {
+ height: 29,
+ width: 29,
src: '/images/icon-128.png',
}),
@@ -243,22 +281,21 @@ App.prototype.renderAppBar = function () {
}, [
// Network Indicator
h(NetworkIndicator, {
- network: this.props.network,
- provider: this.props.provider,
+ network,
+ provider,
+ disabled: currentView.name === 'confTx',
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
- if (this.props.networkDropdownOpen === false) {
- this.props.showNetworkDropdown()
- } else {
- this.props.hideNetworkDropdown()
- }
+ return networkDropdownOpen === false
+ ? showNetworkDropdown()
+ : hideNetworkDropdown()
},
}),
]),
- h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
+ isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
h(Identicon, {
address: this.props.selectedAddress,
diameter: 32,
@@ -273,6 +310,17 @@ App.prototype.renderAppBar = function () {
}
+App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
+ const { isMascara } = this.props
+
+ return isMascara
+ ? null
+ : h(Loading, {
+ isLoading: isLoading || isLoadingNetwork,
+ loadingMessage: loadMessage,
+ })
+}
+
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
@@ -295,6 +343,11 @@ App.prototype.renderBackButton = function (style, justArrow = false) {
App.prototype.renderPrimary = function () {
log.debug('rendering primary')
var props = this.props
+ const {isMascara, isOnboarding} = props
+
+ if (isMascara && isOnboarding) {
+ return h(MascaraFirstTime)
+ }
// notices
if (!props.noActiveNotices) {
@@ -383,7 +436,7 @@ App.prototype.renderPrimary = function () {
case 'config':
log.debug('rendering config screen')
- return h(ConfigScreen, {key: 'config'})
+ return h(Settings, {key: 'config'})
case 'import-menu':
log.debug('rendering import screen')
@@ -395,12 +448,44 @@ App.prototype.renderPrimary = function () {
case 'info':
log.debug('rendering info screen')
- return h(InfoScreen, {key: 'info'})
+ return h(Settings, {key: 'info', tab: 'info'})
case 'buyEth':
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
+ case 'onboardingBuyEth':
+ log.debug('rendering onboarding buy ether screen')
+ return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
+
+ case 'qr':
+ log.debug('rendering show qr screen')
+ return h('div', {
+ style: {
+ position: 'absolute',
+ height: '100%',
+ top: '0px',
+ left: '0px',
+ },
+ }, [
+ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
+ onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
+ style: {
+ marginLeft: '10px',
+ marginTop: '50px',
+ },
+ }),
+ h('div', {
+ style: {
+ position: 'absolute',
+ left: '44px',
+ width: '285px',
+ },
+ }, [
+ h(QrView, {key: 'qr'}),
+ ]),
+ ])
+
default:
log.debug('rendering default, account detail screen')
return h(MainContainer, {key: 'account-detail'})
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
index 1b46e532a..0c34a5154 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/account-dropdowns.js
@@ -161,8 +161,6 @@ class AccountDropdowns extends Component {
)
}
-
-
renderAccountOptions () {
const { actions } = this.props
const { optionsMenuActive } = this.state
@@ -297,6 +295,11 @@ AccountDropdowns.propTypes = {
identities: PropTypes.objectOf(PropTypes.object),
selected: PropTypes.string,
keyrings: PropTypes.array,
+ actions: PropTypes.objectOf(PropTypes.func),
+ network: PropTypes.string,
+ style: PropTypes.object,
+ enableAccountOptions: PropTypes.bool,
+ enableAccountsSelector: PropTypes.bool,
}
const mapDispatchToProps = (dispatch) => {
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index 85bd21076..38c7bcb2d 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -46,6 +46,10 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.showImportPage())
dispatch(actions.toggleAccountMenu())
},
+ showInfoPage: () => {
+ dispatch(actions.showInfoPage())
+ dispatch(actions.toggleAccountMenu())
+ },
}
}
@@ -57,16 +61,18 @@ AccountMenu.prototype.render = function () {
showImportPage,
lockMetamask,
showConfigPage,
+ showInfoPage,
} = this.props
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
h(CloseArea, { onClick: toggleAccountMenu }),
h(Item, {
className: 'account-menu__header',
- onClick: lockMetamask,
}, [
'My Accounts',
- h('button.account-menu__logout-button', 'Log out'),
+ h('button.account-menu__logout-button', {
+ onClick: lockMetamask,
+ }, 'Log out'),
]),
h(Divider),
h('div.account-menu__accounts', this.renderAccounts()),
@@ -83,6 +89,7 @@ AccountMenu.prototype.render = function () {
}),
h(Divider),
h(Item, {
+ onClick: showInfoPage,
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: 'Info & Help',
}),
@@ -98,15 +105,14 @@ AccountMenu.prototype.renderAccounts = function () {
const {
identities,
accounts,
- selected,
+ selectedAddress,
keyrings,
showAccountDetail,
} = this.props
- console.log({ accounts })
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
- const isSelected = identity.address === selected
+ const isSelected = identity.address === selectedAddress
const balanceValue = accounts[key] ? accounts[key].balance : ''
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
@@ -122,7 +128,7 @@ AccountMenu.prototype.renderAccounts = function () {
{ onClick: () => showAccountDetail(identity.address) },
[
h('div.account-menu__check-mark', [
- isSelected ? h('i.fa.fa-check') : null,
+ isSelected ? h('div.account-menu__check-mark-icon') : null,
]),
h(
@@ -148,6 +154,6 @@ AccountMenu.prototype.indicateIfLoose = function (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
+ return isLoose ? h('.keyring-label', 'IMPORTED') : null
} catch (e) { return }
}
diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js
index d84834d06..22e37602e 100644
--- a/ui/app/components/bn-as-decimal-input.js
+++ b/ui/app/components/bn-as-decimal-input.js
@@ -31,6 +31,8 @@ BnAsDecimalInput.prototype.render = function () {
const suffix = props.suffix
const style = props.style
const valueString = value.toString(10)
+ const newMin = min && this.downsize(min.toString(10), scale)
+ const newMax = max && this.downsize(max.toString(10), scale)
const newValue = this.downsize(valueString, scale)
return (
@@ -47,8 +49,8 @@ BnAsDecimalInput.prototype.render = function () {
type: 'number',
step: 'any',
required: true,
- min,
- max,
+ min: newMin,
+ max: newMax,
style: extend({
display: 'block',
textAlign: 'right',
@@ -128,15 +130,17 @@ BnAsDecimalInput.prototype.updateValidity = function (event) {
}
BnAsDecimalInput.prototype.constructWarning = function () {
- const { name, min, max } = this.props
+ const { name, min, max, scale, suffix } = this.props
+ const newMin = min && this.downsize(min.toString(10), scale)
+ const newMax = max && this.downsize(max.toString(10), scale)
let message = name ? name + ' ' : ''
if (min && max) {
- message += `must be greater than or equal to ${min} and less than or equal to ${max}.`
+ message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.`
} else if (min) {
- message += `must be greater than or equal to ${min}.`
+ message += `must be greater than or equal to ${newMin} ${suffix}.`
} else if (max) {
- message += `must be less than or equal to ${max}.`
+ message += `must be less than or equal to ${newMax} ${suffix}.`
} else {
message += 'Invalid input.'
}
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index a36f41df5..d5958787b 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -76,7 +76,7 @@ BuyButtonSubview.prototype.headerSubview = function () {
paddingTop: '4px',
paddingBottom: '4px',
},
- }, 'Buy Eth'),
+ }, 'Deposit Eth'),
]),
// loading indication
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 744891c47..722ed2b23 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -6,23 +6,46 @@ const actions = require('../../actions')
const GasModalCard = require('./gas-modal-card')
const {
- MIN_GAS_PRICE,
- MIN_GAS_LIMIT,
+ MIN_GAS_PRICE_DEC,
+ MIN_GAS_LIMIT_DEC,
+ MIN_GAS_PRICE_GWEI,
} = require('../send/send-constants')
-const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
+const {
+ isBalanceSufficient,
+} = require('../send/send-utils')
+
+const {
+ conversionUtil,
+ multiplyCurrencies,
+ conversionGreaterThan,
+} = require('../../conversion-util')
const {
getGasPrice,
getGasLimit,
conversionRateSelector,
+ getSendAmount,
+ getSelectedToken,
+ getSendFrom,
+ getCurrentAccountWithSendEtherInfo,
+ getSelectedTokenToFiatRate,
} = require('../../selectors')
function mapStateToProps (state) {
+ const selectedToken = getSelectedToken(state)
+ const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
+ const conversionRate = conversionRateSelector(state)
+
return {
gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state),
- conversionRate: conversionRateSelector(state),
+ conversionRate,
+ amount: getSendAmount(state),
+ balance: currentAccount.balance,
+ primaryCurrency: selectedToken && selectedToken.symbol,
+ selectedToken,
+ amountConversionRate: selectedToken ? getSelectedTokenToFiatRate(state) : conversionRate,
}
}
@@ -35,19 +58,34 @@ function mapDispatchToProps (dispatch) {
}
}
+function getOriginalState(props) {
+ const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC
+ const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC
+
+ const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+
+ return {
+ gasPrice,
+ gasLimit,
+ gasTotal,
+ error: null,
+ }
+}
+
inherits(CustomizeGasModal, Component)
function CustomizeGasModal (props) {
Component.call(this)
- this.state = {
- gasPrice: props.gasPrice || MIN_GAS_PRICE,
- gasLimit: props.gasLimit || MIN_GAS_LIMIT,
- }
+ this.state = getOriginalState(props)
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
-CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) {
+CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
const {
updateGasPrice,
updateGasLimit,
@@ -55,41 +93,105 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) {
updateGasTotal
} = this.props
- const newGasTotal = multiplyCurrencies(gasLimit, gasPrice, {
- toNumericBase: 'hex',
- multiplicandBase: 16,
- multiplierBase: 16,
- })
-
updateGasPrice(gasPrice)
updateGasLimit(gasLimit)
- updateGasTotal(newGasTotal)
+ updateGasTotal(gasTotal)
hideModal()
}
+CustomizeGasModal.prototype.revert = function () {
+ this.setState(getOriginalState(this.props))
+}
+
+CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
+ const {
+ amount,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
+ } = this.props
+
+ let error = null
+
+ const balanceIsSufficient = isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
+ })
+
+ if (!balanceIsSufficient) {
+ error = 'Insufficient balance for current gas total'
+ }
+
+ const gasLimitTooLow = gasLimit && conversionGreaterThan(
+ {
+ value: MIN_GAS_LIMIT_DEC,
+ fromNumericBase: 'dec',
+ conversionRate,
+ },
+ {
+ value: gasLimit,
+ fromNumericBase: 'hex',
+ },
+ )
+
+ if (gasLimitTooLow) {
+ error = 'Gas limit must be at least 21000'
+ }
+
+ this.setState({ error })
+ return error
+}
+
CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) {
- const convertedGasLimit = conversionUtil(newGasLimit, {
+ const { gasPrice } = this.state
+
+ const gasLimit = conversionUtil(newGasLimit, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
})
- this.setState({ gasLimit: convertedGasLimit })
+ const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+
+ this.validate({ gasTotal, gasLimit })
+
+ this.setState({ gasTotal, gasLimit })
}
CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
- const convertedGasPrice = conversionUtil(newGasPrice, {
+ const { gasLimit } = this.state
+
+ const gasPrice = conversionUtil(newGasPrice, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
fromDenomination: 'GWEI',
toDenomination: 'WEI',
})
- this.setState({ gasPrice: convertedGasPrice })
+ const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+
+ this.validate({ gasTotal })
+
+ this.setState({ gasTotal, gasPrice })
}
CustomizeGasModal.prototype.render = function () {
const { hideModal, conversionRate } = this.props
- const { gasPrice, gasLimit } = this.state
+ const { gasPrice, gasLimit, gasTotal, error } = this.state
const convertedGasPrice = conversionUtil(gasPrice, {
fromNumericBase: 'hex',
@@ -104,7 +206,7 @@ CustomizeGasModal.prototype.render = function () {
})
return h('div.send-v2__customize-gas', {}, [
- h('div', {
+ h('div.send-v2__customize-gas__content', {
}, [
h('div.send-v2__customize-gas__header', {}, [
@@ -120,17 +222,17 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, {
value: convertedGasPrice,
- min: MIN_GAS_PRICE,
+ min: MIN_GAS_PRICE_GWEI,
// max: 1000,
step: 1,
onChange: value => this.convertAndSetGasPrice(value),
- title: 'Gas Price',
+ title: 'Gas Price (GWEI)',
copy: 'We calculate the suggested gas prices based on network success rates.',
}),
h(GasModalCard, {
value: convertedGasLimit,
- min: MIN_GAS_LIMIT,
+ min: 1,
// max: 100000,
step: 1,
onChange: value => this.convertAndSetGasLimit(value),
@@ -141,9 +243,13 @@ CustomizeGasModal.prototype.render = function () {
]),
h('div.send-v2__customize-gas__footer', {}, [
+
+ error && h('div.send-v2__customize-gas__error-message', [
+ error,
+ ]),
h('div.send-v2__customize-gas__revert', {
- onClick: () => console.log('Revert'),
+ onClick: () => this.revert(),
}, ['Revert']),
h('div.send-v2__customize-gas__buttons', [
@@ -151,8 +257,8 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal,
}, ['CANCEL']),
- h('div.send-v2__customize-gas__save', {
- onClick: () => this.save(gasPrice, gasLimit),
+ h(`div.send-v2__customize-gas__save${error ? '__error' : ''}`, {
+ onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal),
}, ['SAVE']),
])
diff --git a/ui/app/components/dropdowns/account-dropdown-mini.js b/ui/app/components/dropdowns/account-dropdown-mini.js
new file mode 100644
index 000000000..96057d2b4
--- /dev/null
+++ b/ui/app/components/dropdowns/account-dropdown-mini.js
@@ -0,0 +1,78 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const Identicon = require('../identicon')
+const AccountListItem = require('../send/account-list-item')
+
+module.exports = AccountDropdownMini
+
+inherits(AccountDropdownMini, Component)
+function AccountDropdownMini () {
+ Component.call(this)
+}
+
+AccountDropdownMini.prototype.getListItemIcon = function (currentAccount, selectedAccount) {
+ const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
+
+ return currentAccount.address === selectedAccount.address
+ ? listItemIcon
+ : null
+}
+
+AccountDropdownMini.prototype.renderDropdown = function () {
+ const {
+ accounts,
+ selectedAccount,
+ closeDropdown,
+ onSelect,
+ } = this.props
+
+ return h('div', {}, [
+
+ h('div.account-dropdown-mini__close-area', {
+ onClick: closeDropdown,
+ }),
+
+ h('div.account-dropdown-mini__list', {}, [
+
+ ...accounts.map(account => h(AccountListItem, {
+ account,
+ displayBalance: false,
+ displayAddress: false,
+ handleClick: () => {
+ onSelect(account)
+ closeDropdown()
+ },
+ icon: this.getListItemIcon(account, selectedAccount),
+ }))
+
+ ]),
+
+ ])
+}
+
+AccountDropdownMini.prototype.render = function () {
+ const {
+ accounts,
+ selectedAccount,
+ openDropdown,
+ closeDropdown,
+ dropdownOpen,
+ } = this.props
+
+ return h('div.account-dropdown-mini', {}, [
+
+ h(AccountListItem, {
+ account: selectedAccount,
+ handleClick: openDropdown,
+ displayBalance: false,
+ displayAddress: false,
+ icon: h(`i.fa.fa-caret-down.fa-lg`, { style: { color: '#dedede' } })
+ }),
+
+ dropdownOpen && this.renderDropdown(),
+
+ ])
+
+}
+
diff --git a/ui/app/components/dropdowns/components/dropdown.js b/ui/app/components/dropdowns/components/dropdown.js
index 991c89cb8..ca68e55f7 100644
--- a/ui/app/components/dropdowns/components/dropdown.js
+++ b/ui/app/components/dropdowns/components/dropdown.js
@@ -65,6 +65,9 @@ Dropdown.propTypes = {
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
style: PropTypes.object.isRequired,
+ onClickOutside: PropTypes.func,
+ innerStyle: PropTypes.object,
+ useCssTransition: PropTypes.bool,
}
class DropdownMenuItem extends Component {
@@ -100,6 +103,7 @@ DropdownMenuItem.propTypes = {
closeMenu: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
+ style: PropTypes.object,
}
module.exports = {
diff --git a/ui/app/components/dropdowns/simple-dropdown.js b/ui/app/components/dropdowns/simple-dropdown.js
new file mode 100644
index 000000000..8cea78518
--- /dev/null
+++ b/ui/app/components/dropdowns/simple-dropdown.js
@@ -0,0 +1,91 @@
+const { Component, PropTypes } = require('react')
+const h = require('react-hyperscript')
+const classnames = require('classnames')
+const R = require('ramda')
+
+class SimpleDropdown extends Component {
+ constructor (props) {
+ super(props)
+ this.state = {
+ isOpen: false,
+ }
+ }
+
+ getDisplayValue () {
+ const { selectedOption, options } = this.props
+ const matchesOption = option => option.value === selectedOption
+ const matchingOption = R.find(matchesOption)(options)
+ return matchingOption
+ ? matchingOption.displayValue || matchingOption.value
+ : selectedOption
+ }
+
+ handleClose () {
+ this.setState({ isOpen: false })
+ }
+
+ toggleOpen () {
+ const { isOpen } = this.state
+ this.setState({ isOpen: !isOpen })
+ }
+
+ renderOptions () {
+ const { options, onSelect, selectedOption } = this.props
+
+ return h('div', [
+ h('div.simple-dropdown__close-area', {
+ onClick: event => {
+ event.stopPropagation()
+ this.handleClose()
+ },
+ }),
+ h('div.simple-dropdown__options', [
+ ...options.map(option => {
+ return h(
+ 'div.simple-dropdown__option',
+ {
+ className: classnames({
+ 'simple-dropdown__option--selected': option.value === selectedOption,
+ }),
+ key: option.value,
+ onClick: () => {
+ if (option.value !== selectedOption) {
+ onSelect(option.value)
+ }
+
+ this.handleClose()
+ },
+ },
+ option.displayValue || option.value,
+ )
+ }),
+ ]),
+ ])
+ }
+
+ render () {
+ const { placeholder } = this.props
+ const { isOpen } = this.state
+
+ return h(
+ 'div.simple-dropdown',
+ {
+ onClick: () => this.toggleOpen(),
+ },
+ [
+ h('div.simple-dropdown__selected', this.getDisplayValue() || placeholder || 'Select'),
+ h('i.fa.fa-caret-down.fa-lg.simple-dropdown__caret'),
+ isOpen && this.renderOptions(),
+ ]
+ )
+ }
+}
+
+SimpleDropdown.propTypes = {
+ options: PropTypes.array.isRequired,
+ placeholder: PropTypes.string,
+ onSelect: PropTypes.func,
+ selectedOption: PropTypes.string,
+}
+
+module.exports = SimpleDropdown
diff --git a/ui/app/components/editable-label.js b/ui/app/components/editable-label.js
index 167be7eaf..eb41ec50c 100644
--- a/ui/app/components/editable-label.js
+++ b/ui/app/components/editable-label.js
@@ -1,56 +1,88 @@
-const Component = require('react').Component
+const { Component } = require('react')
+const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const findDOMNode = require('react-dom').findDOMNode
+const classnames = require('classnames')
-module.exports = EditableLabel
+class EditableLabel extends Component {
+ constructor (props) {
+ super(props)
-inherits(EditableLabel, Component)
-function EditableLabel () {
- Component.call(this)
-}
+ this.state = {
+ isEditing: false,
+ value: props.defaultValue || '',
+ }
+ }
+
+ handleSubmit () {
+ const { value } = this.state
+
+ if (value === '') {
+ return
+ }
+
+ Promise.resolve(this.props.onSubmit(value))
+ .then(() => this.setState({ isEditing: false }))
+ }
+
+ saveIfEnter (event) {
+ if (event.key === 'Enter') {
+ this.handleSubmit()
+ }
+ }
-EditableLabel.prototype.render = function () {
- const props = this.props
- const state = this.state
+ renderEditing () {
+ const { value } = this.state
- if (state && state.isEditingLabel) {
- return h('div.editable-label', [
- h('input.sizing-input', {
- defaultValue: props.textValue,
- maxLength: '20',
+ return ([
+ h('input.large-input.editable-label__input', {
+ type: 'text',
+ required: true,
+ value: this.state.value,
onKeyPress: (event) => {
- this.saveIfEnter(event)
+ if (event.key === 'Enter') {
+ this.handleSubmit()
+ }
},
+ onChange: event => this.setState({ value: event.target.value }),
+ className: classnames({ 'editable-label__input--error': value === '' }),
}),
- h('button.editable-button', {
- onClick: () => this.saveText(),
- }, 'Save'),
+ h('div.editable-label__icon-wrapper', [
+ h('i.fa.fa-check.editable-label__icon', {
+ onClick: () => this.handleSubmit(),
+ }),
+ ]),
])
- } else {
- return h('div.name-label', {
- onClick: (event) => {
- const nameAttribute = event.target.getAttribute('name')
- // checks for class to handle smaller CTA above the account name
- const classAttribute = event.target.getAttribute('class')
- if (nameAttribute === 'edit' || classAttribute === 'edit-text') {
- this.setState({ isEditingLabel: true })
- }
- },
- }, this.props.children)
}
-}
-EditableLabel.prototype.saveIfEnter = function (event) {
- if (event.key === 'Enter') {
- this.saveText()
+ renderReadonly () {
+ return ([
+ h('div.editable-label__value', this.state.value),
+ h('div.editable-label__icon-wrapper', [
+ h('i.fa.fa-pencil.editable-label__icon', {
+ onClick: () => this.setState({ isEditing: true }),
+ }),
+ ]),
+ ])
+ }
+
+ render () {
+ const { isEditing } = this.state
+ const { className } = this.props
+
+ return (
+ h('div.editable-label', { className: classnames(className) },
+ isEditing
+ ? this.renderEditing()
+ : this.renderReadonly()
+ )
+ )
}
}
-EditableLabel.prototype.saveText = function () {
- var container = findDOMNode(this)
- var text = container.querySelector('.editable-label input').value
- var truncatedText = text.substring(0, 20)
- this.props.saveText(truncatedText)
- this.setState({ isEditingLabel: false, textLabel: truncatedText })
+EditableLabel.propTypes = {
+ onSubmit: PropTypes.func.isRequired,
+ defaultValue: PropTypes.string,
+ className: PropTypes.string,
}
+
+module.exports = EditableLabel
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index fb8c8e579..6553053f7 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -6,7 +6,7 @@ const debounce = require('debounce')
const copyToClipboard = require('copy-to-clipboard')
const ENS = require('ethjs-ens')
const networkMap = require('ethjs-ens/lib/network-map.json')
-const ensRE = /.+\.eth$/
+const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
index 259fa4d73..d30b7cd56 100644
--- a/ui/app/components/identicon.js
+++ b/ui/app/components/identicon.js
@@ -55,6 +55,7 @@ IdenticonComponent.prototype.componentDidMount = function () {
if (!address) return
+ // eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
var diameter = props.diameter || this.defaultDiameter
@@ -70,6 +71,7 @@ IdenticonComponent.prototype.componentDidUpdate = function () {
if (!address) return
+ // eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
var children = container.children
diff --git a/ui/app/components/menu-droppo.js b/ui/app/components/menu-droppo.js
index 95b75f54c..c80bee2be 100644
--- a/ui/app/components/menu-droppo.js
+++ b/ui/app/components/menu-droppo.js
@@ -97,6 +97,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () {
if (this && document.body) {
this.globalClickHandler = this.globalClickOccurred.bind(this)
document.body.addEventListener('click', this.globalClickHandler)
+ // eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
this.container = container
}
@@ -110,6 +111,7 @@ MenuDroppoComponent.prototype.componentWillUnmount = function () {
MenuDroppoComponent.prototype.globalClickOccurred = function (event) {
const target = event.target
+ // eslint-disable-next-line react/no-find-dom-node
const container = findDOMNode(this)
if (target !== container &&
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js
index 37a62e1c0..e3c936702 100644
--- a/ui/app/components/modals/account-details-modal.js
+++ b/ui/app/components/modals/account-details-modal.js
@@ -7,6 +7,7 @@ const AccountModalContainer = require('./account-modal-container')
const { getSelectedIdentity, getSelectedAddress } = require('../../selectors')
const genAccountLink = require('../../../lib/account-link.js')
const QrView = require('../qr-code')
+const EditableLabel = require('../editable-label')
function mapStateToProps (state) {
return {
@@ -23,6 +24,7 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
hideModal: () => dispatch(actions.hideModal()),
+ saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
}
}
@@ -41,14 +43,19 @@ AccountDetailsModal.prototype.render = function () {
selectedIdentity,
network,
showExportPrivateKeyModal,
- hideModal,
+ saveAccountLabel,
} = this.props
const { name, address } = selectedIdentity
return h(AccountModalContainer, {}, [
+ h(EditableLabel, {
+ className: 'account-modal__name',
+ defaultValue: name,
+ onSubmit: label => saveAccountLabel(address, label),
+ }),
+
h(QrView, {
Qr: {
- message: name,
data: address,
},
}),
@@ -57,14 +64,12 @@ AccountDetailsModal.prototype.render = function () {
h('button.btn-clear', {
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
- }, [ 'View account on Etherscan' ]),
+ }, 'View account on Etherscan'),
// Holding on redesign for Export Private Key functionality
h('button.btn-clear', {
- onClick: () => {
- showExportPrivateKeyModal()
- },
- }, [ 'Export private key' ]),
-
+ onClick: () => showExportPrivateKeyModal(),
+ }, 'Export private key'),
+
])
}
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
index f1a5aa9fd..33615c483 100644
--- a/ui/app/components/modals/buy-options-modal.js
+++ b/ui/app/components/modals/buy-options-modal.js
@@ -42,7 +42,7 @@ BuyOptions.prototype.render = function () {
h('div.buy-modal-content-title', {
style: {},
}, 'Transfers'),
- h('div', {}, 'How would you like to buy Ether?'),
+ h('div', {}, 'How would you like to deposit Ether?'),
]),
h('div.buy-modal-content-options.flex-column.flex-center', {}, [
@@ -54,7 +54,7 @@ BuyOptions.prototype.render = function () {
},
}, [
h('div.buy-modal-content-option-title', {}, 'Coinbase'),
- h('div.buy-modal-content-option-subtitle', {}, 'Buy with Fiat'),
+ h('div.buy-modal-content-option-subtitle', {}, 'Deposit with Fiat'),
]),
// h('div.buy-modal-content-option', {}, [
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js
index 302596eda..2d8470634 100644
--- a/ui/app/components/modals/export-private-key-modal.js
+++ b/ui/app/components/modals/export-private-key-modal.js
@@ -66,7 +66,6 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
})
: h('input.private-key-password-input', {
type: 'password',
- placeholder: 'Type password',
onChange: event => this.setState({ password: event.target.value }),
})
}
@@ -84,7 +83,7 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password,
(privateKey
? this.renderButton('btn-clear', () => hideModal(), 'Done')
- : this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Download')
+ : this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Show')
),
])
@@ -118,7 +117,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h('div.account-modal-divider'),
- h('span.modal-body-title', 'Download Private Keys'),
+ h('span.modal-body-title', 'Show Private Keys'),
h('div.private-key-password', {}, [
this.renderPasswordLabel(privateKey),
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 88deb2bb0..e15dd6c1b 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -162,10 +162,9 @@ const MODALS = {
h(CustomizeGasModal, {}, []),
],
mobileModalStyle: {
- width: '355px',
- height: '598px',
- // top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
- top: '5%',
+ width: '100vw',
+ height: '100vh',
+ top: '0',
transform: 'none',
left: '0',
right: '0',
diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/modals/new-account-modal.js
index 25beb6745..b78de1d8d 100644
--- a/ui/app/components/modals/new-account-modal.js
+++ b/ui/app/components/modals/new-account-modal.js
@@ -28,6 +28,7 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.hideModal())
})
},
+ showImportPage: () => dispatch(actions.showImportPage()),
}
}
@@ -36,7 +37,7 @@ function NewAccountModal () {
Component.call(this)
this.state = {
- newAccountName: ''
+ newAccountName: '',
}
}
@@ -63,7 +64,7 @@ NewAccountModal.prototype.render = function () {
h('div.new-account-input-wrapper', {}, [
h('input.new-account-input', {
placeholder: 'E.g. My new account',
- onChange: (event) => this.setState({ newAccountName: event.target.value })
+ onChange: event => this.setState({ newAccountName: event.target.value }),
}, []),
]),
@@ -71,13 +72,16 @@ NewAccountModal.prototype.render = function () {
'or',
]),
- h('div.new-account-modal-content.after-input', {}, [
- 'Import an account',
- ]),
+ h('div.new-account-modal-content.after-input.pointer', {
+ onClick: () => {
+ this.props.hideModal()
+ this.props.showImportPage()
+ },
+ }, 'Import an account'),
h('div.new-account-modal-content.button', {}, [
h('button.btn-clear', {
- onClick: () => this.props.createAccount(newAccountName)
+ onClick: () => this.props.createAccount(newAccountName),
}, [
'SAVE',
]),
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index b24505750..229d02e36 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -64,13 +64,18 @@ Network.prototype.render = function () {
return (
h('div.network-component.pointer', {
className: classnames('network-component pointer', {
+ 'network-component--disabled': this.props.disabled,
'ethereum-network': providerName === 'mainnet',
'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
'kovan-test-network': providerName === 'kovan',
'rinkeby-test-network': providerName === 'rinkeby',
}),
title: hoverText,
- onClick: (event) => this.props.onClick(event),
+ onClick: (event) => {
+ if (!this.props.disabled) {
+ this.props.onClick(event)
+ }
+ },
}, [
(function () {
switch (iconName) {
diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js
index abfff1f5c..941ac33e6 100644
--- a/ui/app/components/notice.js
+++ b/ui/app/components/notice.js
@@ -117,6 +117,7 @@ Notice.prototype.render = function () {
}
Notice.prototype.componentDidMount = function () {
+ // eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.setupListener(node)
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
@@ -125,6 +126,7 @@ Notice.prototype.componentDidMount = function () {
}
Notice.prototype.componentWillUnmount = function () {
+ // eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.teardownListener(node)
}
diff --git a/ui/app/components/pending-personal-msg.js b/ui/app/components/pending-personal-msg.js
deleted file mode 100644
index 4542adb28..000000000
--- a/ui/app/components/pending-personal-msg.js
+++ /dev/null
@@ -1,47 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const PendingTxDetails = require('./pending-personal-msg-details')
-
-module.exports = PendingMsg
-
-inherits(PendingMsg, Component)
-function PendingMsg () {
- Component.call(this)
-}
-
-PendingMsg.prototype.render = function () {
- var state = this.props
- var msgData = state.txData
-
- return (
-
- h('div', {
- key: msgData.id,
- }, [
-
- // header
- h('h3', {
- style: {
- fontWeight: 'bold',
- textAlign: 'center',
- },
- }, 'Sign Message'),
-
- // message details
- h(PendingTxDetails, state),
-
- // sign + cancel
- h('.flex-row.flex-space-around', [
- h('button', {
- onClick: state.cancelPersonalMessage,
- }, 'Cancel'),
- h('button', {
- onClick: state.signPersonalMessage,
- }, 'Sign'),
- ]),
- ])
-
- )
-}
-
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 7162c7122..2f178f179 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -50,7 +50,7 @@ ConfirmSendEther.prototype.getAmount = function () {
const { conversionRate, currentCurrency } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
- console.log(`conversionRate, currentCurrency`, conversionRate, currentCurrency);
+
const FIAT = conversionUtil(txParams.value, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
@@ -194,7 +194,7 @@ ConfirmSendEther.prototype.render = function () {
this.inputs = []
return (
- h('div.confirm-screen-container', {
+ h('div.confirm-screen-container.confirm-send-ether', {
style: { minWidth: '355px' },
}, [
// Main Send token Card
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index a4c3d16e3..abb7a0770 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -224,7 +224,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
]),
h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${fiatAmount + fiatGas} ${currentCurrency}`),
+ h('div.confirm-screen-row-info', `${addCurrencies(fiatAmount, fiatGas)} ${currentCurrency}`),
h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`),
]),
])
@@ -263,7 +263,7 @@ ConfirmSendToken.prototype.render = function () {
this.inputs = []
return (
- h('div.confirm-screen-container', {
+ h('div.confirm-screen-container.confirm-send-token', {
style: { minWidth: '355px' },
}, [
// Main Send token Card
diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js
index cc723df14..83885539c 100644
--- a/ui/app/components/qr-code.js
+++ b/ui/app/components/qr-code.js
@@ -29,11 +29,11 @@ QrCodeView.prototype.render = function () {
const qrImage = qrCode(4, 'M')
qrImage.addData(address)
qrImage.make()
- return h('.div.flex-column.flex-center', {
- style: {
- },
- }, [
- Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message),
+
+ return h('.div.flex-column.flex-center', [
+ Array.isArray(Qr.message)
+ ? h('.message-container', this.renderMultiMessage())
+ : Qr.message && h('.qr-header', Qr.message),
this.props.warning ? this.props.warning && h('span.error.flex-center', {
style: {
diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js
index ba7eec940..cc514cbd4 100644
--- a/ui/app/components/send/account-list-item.js
+++ b/ui/app/components/send/account-list-item.js
@@ -27,6 +27,8 @@ AccountListItem.prototype.render = function () {
icon = null,
conversionRate,
currentCurrency,
+ displayBalance = true,
+ displayAddress = false,
} = this.props
const { name, address, balance } = account || {}
@@ -46,13 +48,15 @@ AccountListItem.prototype.render = function () {
},
),
- h('div.account-list-item__account-name', {}, name),
+ h('div.account-list-item__account-name', {}, name || address),
icon && h('div.account-list-item__icon', [icon]),
]),
- h(CurrencyDisplay, {
+ displayAddress && name && h('div.account-list-item__account-address', address),
+
+ displayBalance && h(CurrencyDisplay, {
primaryCurrency: 'ETH',
convertedCurrency: currentCurrency,
value: balance,
diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js
index 7180b94d3..5dba6a8dd 100644
--- a/ui/app/components/send/currency-display.js
+++ b/ui/app/components/send/currency-display.js
@@ -2,7 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Identicon = require('../identicon')
-const { conversionUtil } = require('../../conversion-util')
+const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
module.exports = CurrencyDisplay
@@ -20,14 +20,6 @@ function isValidInput (text) {
return re.test(text)
}
-function resetCaretIfPastEnd (value, event) {
- const caretPosition = event.target.selectionStart
-
- if (caretPosition > value.length) {
- event.target.setSelectionRange(value.length, value.length)
- }
-}
-
function toHexWei (value) {
return conversionUtil(value, {
fromNumericBase: 'dec',
@@ -40,7 +32,9 @@ CurrencyDisplay.prototype.getAmount = function (value) {
const { selectedToken } = this.props
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
- const sendAmount = '0x' + Number(value * multiplier).toString(16)
+
+ const sendAmount = multiplyCurrencies(value, multiplier, {toNumericBase: 'hex'})
+
return selectedToken
? sendAmount
: toHexWei(value)
@@ -80,6 +74,8 @@ CurrencyDisplay.prototype.render = function () {
conversionRate,
})
+ const inputSizeMultiplier = readOnly ? 1 : 1.2;
+
return h('div', {
className,
style: {
@@ -93,35 +89,33 @@ CurrencyDisplay.prototype.render = function () {
h('input', {
className: primaryBalanceClassName,
- value: `${value || initValueToRender} ${primaryCurrency}`,
- placeholder: `${0} ${primaryCurrency}`,
+ value: `${value || initValueToRender}`,
+ placeholder: '0',
+ size: (value || initValueToRender).length * inputSizeMultiplier,
readOnly,
onChange: (event) => {
- let newValue = event.target.value.split(' ')[0]
+ let newValue = event.target.value
if (newValue === '') {
- this.setState({ value: '0' })
+ newValue = '0'
}
else if (newValue.match(/^0[1-9]$/)) {
- this.setState({ value: newValue.match(/[1-9]/)[0] })
+ newValue = newValue.match(/[1-9]/)[0]
}
- else if (newValue && !isValidInput(newValue)) {
+
+ if (newValue && !isValidInput(newValue)) {
event.preventDefault()
}
else {
+ validate(this.getAmount(newValue))
this.setState({ value: newValue })
}
},
- onBlur: event => !readOnly && handleChange(this.getAmount(event.target.value.split(' ')[0])),
- onKeyUp: event => {
- if (!readOnly) {
- validate(toHexWei(value || initValueToRender))
- resetCaretIfPastEnd(value || initValueToRender, event)
- }
- },
- onClick: event => !readOnly && resetCaretIfPastEnd(value || initValueToRender, event),
+ onBlur: event => !readOnly && handleChange(this.getAmount(event.target.value)),
}),
+ h('span.currency-display__currency-symbol', primaryCurrency),
+
]),
]),
diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js
index a819a8c28..8b56607cc 100644
--- a/ui/app/components/send/send-constants.js
+++ b/ui/app/components/send/send-constants.js
@@ -3,12 +3,19 @@ const { multiplyCurrencies } = require('../../conversion-util')
const MIN_GAS_PRICE_GWEI = '1'
const GWEI_FACTOR = '1e9'
-const MIN_GAS_PRICE = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
+const MIN_GAS_PRICE_HEX = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
multiplicandBase: 16,
multiplierBase: 16,
+ toNumericBase: 'hex',
+})
+const MIN_GAS_PRICE_DEC = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ toNumericBase: 'dec',
})
-const MIN_GAS_LIMIT = (21000).toString(16)
-const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT, MIN_GAS_PRICE, {
+const MIN_GAS_LIMIT_HEX = (21000).toString(16)
+const MIN_GAS_LIMIT_DEC = 21000
+const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
@@ -16,8 +23,9 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT, MIN_GAS_PRICE, {
module.exports = {
MIN_GAS_PRICE_GWEI,
- GWEI_FACTOR,
- MIN_GAS_PRICE,
- MIN_GAS_LIMIT,
+ MIN_GAS_PRICE_HEX,
+ MIN_GAS_PRICE_DEC,
+ MIN_GAS_LIMIT_HEX,
+ MIN_GAS_LIMIT_DEC,
MIN_GAS_TOTAL,
}
diff --git a/ui/app/components/send/send-utils.js b/ui/app/components/send/send-utils.js
new file mode 100644
index 000000000..bf096d610
--- /dev/null
+++ b/ui/app/components/send/send-utils.js
@@ -0,0 +1,39 @@
+const { addCurrencies, conversionGreaterThan } = require('../../conversion-util')
+
+function isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
+}) {
+ const totalAmount = addCurrencies(amount, gasTotal, {
+ aBase: 16,
+ bBase: 16,
+ toNumericBase: 'hex',
+ })
+
+ const balanceIsSufficient = conversionGreaterThan(
+ {
+ value: balance,
+ fromNumericBase: 'hex',
+ fromCurrency: primaryCurrency,
+ conversionRate,
+ },
+ {
+ value: totalAmount,
+ fromNumericBase: 'hex',
+ conversionRate: amountConversionRate,
+ fromCurrency: selectedToken || primaryCurrency,
+ conversionRate: amountConversionRate,
+ },
+ )
+
+ return balanceIsSufficient
+}
+
+module.exports = {
+ isBalanceSufficient,
+} \ No newline at end of file
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index c14865e9f..fb2634de2 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -17,6 +17,7 @@ const {
getAddressBook,
getSendFrom,
getCurrentCurrency,
+ getSelectedTokenToFiatRate,
} = require('../../selectors')
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
@@ -26,7 +27,6 @@ function mapStateToProps (state) {
const selectedAddress = getSelectedAddress(state)
const selectedToken = getSelectedToken(state)
const tokenExchangeRates = state.metamask.tokenExchangeRates
- const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
const conversionRate = conversionRateSelector(state)
let data;
@@ -40,11 +40,7 @@ function mapStateToProps (state) {
primaryCurrency = selectedToken.symbol
- tokenToFiatRate = multiplyCurrencies(
- conversionRate,
- selectedTokenExchangeRate,
- { toNumericBase: 'dec' }
- )
+ tokenToFiatRate = getSelectedTokenToFiatRate(state)
}
return {
@@ -80,5 +76,6 @@ function mapDispatchToProps (dispatch) {
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
goHome: () => dispatch(actions.goHome()),
+ clearSend: () => dispatch(actions.clearSend())
}
}
diff --git a/ui/app/components/send/to-autocomplete.js b/ui/app/components/send/to-autocomplete.js
index 686a7a23e..ab490155b 100644
--- a/ui/app/components/send/to-autocomplete.js
+++ b/ui/app/components/send/to-autocomplete.js
@@ -2,54 +2,118 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Identicon = require('../identicon')
+const AccountListItem = require('./account-list-item')
module.exports = ToAutoComplete
inherits(ToAutoComplete, Component)
function ToAutoComplete () {
Component.call(this)
+
+ this.state = { accountsToRender: [] }
+}
+
+ToAutoComplete.prototype.getListItemIcon = function (listItemAddress, toAddress) {
+ const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
+
+ return toAddress && listItemAddress === toAddress
+ ? listItemIcon
+ : null
+}
+
+ToAutoComplete.prototype.renderDropdown = function () {
+ const {
+ accounts,
+ closeDropdown,
+ onChange,
+ to,
+ } = this.props
+ const { accountsToRender } = this.state
+
+ return accountsToRender.length && h('div', {}, [
+
+ h('div.send-v2__from-dropdown__close-area', {
+ onClick: closeDropdown,
+ }),
+
+ h('div.send-v2__from-dropdown__list', {}, [
+
+ ...accountsToRender.map(account => h(AccountListItem, {
+ account,
+ handleClick: () => {
+ onChange(account.address)
+ closeDropdown()
+ },
+ icon: this.getListItemIcon(account.address, to),
+ displayBalance: false,
+ displayAddress: true,
+ }))
+
+ ]),
+
+ ])
+}
+
+ToAutoComplete.prototype.handleInputEvent = function (event = {}, cb) {
+ const {
+ to,
+ accounts,
+ closeDropdown,
+ openDropdown,
+ } = this.props
+
+ const matchingAccounts = accounts.filter(({ address }) => address.match(to || ''))
+ const matches = matchingAccounts.length
+
+ if (!matches || matchingAccounts[0].address === to) {
+ this.setState({ accountsToRender: [] })
+ event.target && event.target.select()
+ closeDropdown()
+ }
+ else {
+ this.setState({ accountsToRender: matchingAccounts })
+ openDropdown()
+ }
+ cb && cb(event.target.value)
+}
+
+ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
+ if (this.props.to !== nextProps.to) {
+ this.handleInputEvent()
+ }
}
ToAutoComplete.prototype.render = function () {
- const { to, accounts, onChange, inError } = this.props
+ const {
+ to,
+ accounts,
+ openDropdown,
+ closeDropdown,
+ dropdownOpen,
+ onChange,
+ inError,
+ } = this.props
- return h('div.send-v2__to-autocomplete', [
+ return h('div.to-autocomplete', {}, [
h('input.send-v2__to-autocomplete__input', {
- name: 'address',
- list: 'addresses',
placeholder: 'Recipient Address',
className: inError ? `send-v2__error-border` : '',
value: to,
- onChange,
- onFocus: event => {
- to && event.target.select()
- },
+ onChange: event => onChange(event.target.value),
+ onFocus: event => this.handleInputEvent(event),
style: {
borderColor: inError ? 'red' : null,
}
}),
- h('datalist#addresses', [
- // Corresponds to the addresses owned.
- ...Object.entries(accounts).map(([key, { address, name }]) => {
- return h('option', {
- value: address,
- label: name,
- key: address,
- })
- }),
- // Corresponds to previously sent-to addresses.
- // ...addressBook.map(({ address, name }) => {
- // return h('option', {
- // value: address,
- // label: name,
- // key: address,
- // })
- // }),
- ]),
+ !to && h(`i.fa.fa-caret-down.fa-lg.send-v2__to-autocomplete__down-caret`, {
+ style: { color: '#dedede' },
+ onClick: () => this.handleInputEvent(),
+ }),
+
+ dropdownOpen && this.renderDropdown(),
])
-
}
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 96a86d3b1..c5993e3d3 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -130,8 +130,8 @@ ShapeshiftForm.prototype.renderMain = function () {
alignItems: 'flex-start',
},
}, [
- this.props.warning
- ? this.props.warning &&
+ this.props.warning ?
+ this.props.warning &&
h('span.error.flex-center', {
style: {
textAlign: 'center',
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
new file mode 100644
index 000000000..a0ecbe8ec
--- /dev/null
+++ b/ui/app/components/signature-request.js
@@ -0,0 +1,255 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const Identicon = require('./identicon')
+const connect = require('react-redux').connect
+const ethUtil = require('ethereumjs-util')
+const PendingTxDetails = require('./pending-personal-msg-details')
+const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
+const BinaryRenderer = require('./binary-renderer')
+
+const actions = require('../actions')
+const { conversionUtil } = require('../conversion-util')
+
+const {
+ getSelectedAccount,
+ getCurrentAccountWithSendEtherInfo,
+ getSelectedAddress,
+ accountsWithSendEtherInfoSelector,
+ conversionRateSelector,
+} = require('../selectors.js')
+
+function mapStateToProps (state) {
+ return {
+ balance: getSelectedAccount(state).balance,
+ selectedAccount: getCurrentAccountWithSendEtherInfo(state),
+ selectedAddress: getSelectedAddress(state),
+ requester: null,
+ requesterAddress: null,
+ accounts: accountsWithSendEtherInfoSelector(state),
+ conversionRate: conversionRateSelector(state)
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ goHome: () => dispatch(actions.goHome())
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest)
+
+inherits(SignatureRequest, Component)
+function SignatureRequest (props) {
+ Component.call(this)
+
+ this.state = {
+ selectedAccount: props.selectedAccount,
+ accountDropdownOpen: false,
+ }
+}
+
+SignatureRequest.prototype.renderHeader = function () {
+ return h('div.request-signature__header', [
+
+ h('div.request-signature__header-background'),
+
+ h('div.request-signature__header__text', 'Signature Request'),
+
+ h('div.request-signature__header__tip-container', [
+ h('div.request-signature__header__tip'),
+ ]),
+
+ ])
+}
+
+SignatureRequest.prototype.renderAccountDropdown = function () {
+ const {
+ selectedAccount,
+ accountDropdownOpen,
+ } = this.state
+
+ const {
+ accounts,
+ } = this.props
+
+ return h('div.request-signature__account', [
+
+ h('div.request-signature__account-text', ['Account:']),
+
+ h(AccountDropdownMini, {
+ selectedAccount,
+ accounts,
+ onSelect: selectedAccount => this.setState({ selectedAccount }),
+ dropdownOpen: accountDropdownOpen,
+ openDropdown: () => this.setState({ accountDropdownOpen: true }),
+ closeDropdown: () => this.setState({ accountDropdownOpen: false }),
+ })
+
+ ])
+}
+
+SignatureRequest.prototype.renderBalance = function () {
+ const { balance, conversionRate } = this.props
+
+ const balanceInEther = conversionUtil(balance, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromDenomination: 'WEI',
+ numberOfDecimals: 6,
+ conversionRate,
+ })
+
+ return h('div.request-signature__balance', [
+
+ h('div.request-signature__balance-text', ['Balance:']),
+
+ h('div.request-signature__balance-value', `${balanceInEther} ETH`),
+
+ ])
+}
+
+SignatureRequest.prototype.renderAccountInfo = function () {
+ return h('div.request-signature__account-info', [
+
+ this.renderAccountDropdown(),
+
+ this.renderRequestIcon(),
+
+ this.renderBalance(),
+
+ ])
+}
+
+SignatureRequest.prototype.renderRequestIcon = function () {
+ const { requesterAddress } = this.props
+
+ return h('div.request-signature__request-icon', [
+ h(Identicon, {
+ diameter: 40,
+ address: requesterAddress,
+ })
+ ])
+}
+
+SignatureRequest.prototype.renderRequestInfo = function () {
+ const { requester } = this.props
+
+ return h('div.request-signature__request-info', [
+
+ h('div.request-signature__headline', [
+ `Your signature is being requested`,
+ ])
+
+ ])
+}
+
+SignatureRequest.prototype.msgHexToText = function (hex) {
+ try {
+ const stripped = ethUtil.stripHexPrefix(hex)
+ const buff = Buffer.from(stripped, 'hex')
+ return buff.toString('utf8')
+ } catch (e) {
+ return hex
+ }
+}
+
+SignatureRequest.prototype.renderBody = function () {
+ let rows
+ let notice = 'You are signing:'
+
+ const { txData } = this.props
+ const { type, msgParams: { data } } = txData
+
+ if (type === 'personal_sign') {
+ rows = [{ name: 'Message', value: this.msgHexToText(data) }]
+ }
+ else if (type === 'eth_signTypedData') {
+ rows = data
+ }
+ else if (type === 'eth_sign') {
+ rows = [{ name: 'Message', value: data }]
+ notice = `Signing this message can have
+ dangerous side effects. Only sign messages from
+ sites you fully trust with your entire account.
+ This dangerous method will be removed in a future version. `
+ }
+
+ return h('div.request-signature__body', {}, [
+
+ this.renderAccountInfo(),
+
+ this.renderRequestInfo(),
+
+ h('div.request-signature__notice', [notice]),
+
+ h('div.request-signature__rows', [
+
+ ...rows.map(({ name, value }) => {
+ return h('div.request-signature__row', [
+ h('div.request-signature__row-title', [`${name}:`]),
+ h('div.request-signature__row-value', value),
+ ])
+ }),
+
+ ]),
+
+ ])
+}
+
+SignatureRequest.prototype.renderFooter = function () {
+ const {
+ goHome,
+ signPersonalMessage,
+ signTypedMessage,
+ cancelPersonalMessage,
+ cancelTypedMessage,
+ signMessage,
+ cancelMessage,
+ } = this.props
+
+ const { txData } = this.props
+ const { type } = txData
+
+ let cancel
+ let sign
+ if (type === 'personal_sign') {
+ cancel = cancelPersonalMessage
+ sign = signPersonalMessage
+ }
+ else if (type === 'eth_signTypedData') {
+ cancel = cancelTypedMessage
+ sign = signTypedMessage
+ }
+ else if (type === 'eth_sign') {
+ cancel = cancelMessage
+ sign = signMessage
+ }
+
+ return h('div.request-signature__footer', [
+ h('button.request-signature__footer__cancel-button', {
+ onClick: cancel,
+ }, 'CANCEL'),
+ h('button.request-signature__footer__sign-button', {
+ onClick: sign,
+ }, 'SIGN'),
+ ])
+}
+
+SignatureRequest.prototype.render = function () {
+ return (
+
+ h('div.request-signature__container', [
+
+ this.renderHeader(),
+
+ this.renderBody(),
+
+ this.renderFooter(),
+
+ ])
+
+ )
+
+}
+
diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js
index bef444a48..fe4076ed0 100644
--- a/ui/app/components/tab-bar.js
+++ b/ui/app/components/tab-bar.js
@@ -1,37 +1,40 @@
-const Component = require('react').Component
+const { Component } = require('react')
const h = require('react-hyperscript')
-const inherits = require('util').inherits
+const classnames = require('classnames')
-module.exports = TabBar
+class TabBar extends Component {
+ constructor (props) {
+ super(props)
+ const { defaultTab, tabs } = props
-inherits(TabBar, Component)
-function TabBar () {
- Component.call(this)
-}
+ this.state = {
+ subview: defaultTab || tabs[0].key,
+ }
+ }
-TabBar.prototype.render = function () {
- const props = this.props
- const state = this.state || {}
- const { tabs = [], defaultTab, tabSelected } = props
- const { subview = defaultTab } = state
+ render () {
+ const { tabs = [], tabSelected } = this.props
+ const { subview } = this.state
- return (
- h('.flex-row.space-around.text-transform-uppercase', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- paddingTop: '4px',
- minHeight: '30px',
- },
- }, tabs.map((tab) => {
- const { key, content } = tab
- return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
- onClick: () => {
- this.setState({ subview: key })
- tabSelected(key)
- },
- }, content)
- }))
- )
+ return (
+ h('.tab-bar', {}, [
+ tabs.map((tab) => {
+ const { key, content } = tab
+ return h('div', {
+ className: classnames('tab-bar__tab pointer', {
+ 'tab-bar__tab--active': subview === key,
+ }),
+ onClick: () => {
+ this.setState({ subview: key })
+ tabSelected(key)
+ },
+ key,
+ }, content)
+ }),
+ h('div.tab-bar__tab.tab-bar__grow-tab'),
+ ])
+ )
+ }
}
+module.exports = TabBar
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index 59f55d485..ebef22680 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -73,7 +73,7 @@ TxView.prototype.renderButtons = function () {
onClick: () => showModal({
name: 'BUY',
}),
- }, 'BUY'),
+ }, 'DEPOSIT'),
h('button.btn-clear', {
style: {
@@ -109,14 +109,15 @@ TxView.prototype.render = function () {
margin: '1em 0.9em',
alignItems: 'center',
},
- onClick: () => {
- this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar()
- },
}, [
h('div.fa.fa-bars', {
style: {
fontSize: '1.3em',
+ cursor: 'pointer',
+ },
+ onClick: () => {
+ this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar()
},
}, []),
diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js
index a042b57be..d170d63b7 100644
--- a/ui/app/components/typed-message-renderer.js
+++ b/ui/app/components/typed-message-renderer.js
@@ -32,11 +32,11 @@ TypedMessageRenderer.prototype.render = function () {
)
}
-function renderTypedData(values) {
+function renderTypedData (values) {
return values.map(function (value) {
return h('div', {}, [
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'),
h('div', {}, value.value),
])
})
-} \ No newline at end of file
+}
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index a870a24e3..3cb7a8b76 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -3,7 +3,8 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Identicon = require('./identicon')
-const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
+// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
+const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions')
const BalanceComponent = require('./balance-component')
const TokenList = require('./token-list')
@@ -19,6 +20,7 @@ function mapStateToProps (state) {
identities: state.metamask.identities,
accounts: state.metamask.accounts,
tokens: state.metamask.tokens,
+ keyrings: state.metamask.keyrings,
selectedAddress: selectors.getSelectedAddress(state),
selectedIdentity: selectors.getSelectedIdentity(state),
selectedAccount: selectors.getSelectedAccount(state),
@@ -28,15 +30,22 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- showSendPage: () => { dispatch(actions.showSendPage()) },
- hideSidebar: () => { dispatch(actions.hideSidebar()) },
+ showSendPage: () => dispatch(actions.showSendPage()),
+ hideSidebar: () => dispatch(actions.hideSidebar()),
unsetSelectedToken: () => dispatch(actions.setSelectedToken()),
+ showAccountDetailModal: () => {
+ dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
+ },
+ showAddTokenPage: () => dispatch(actions.showAddTokenPage()),
}
}
inherits(WalletView, Component)
function WalletView () {
Component.call(this)
+ this.state = {
+ hasCopied: false,
+ }
}
WalletView.prototype.renderWalletBalance = function () {
@@ -47,7 +56,7 @@ WalletView.prototype.renderWalletBalance = function () {
hideSidebar,
sidebarOpen,
} = this.props
- console.log({ selectedAccount })
+
const selectedClass = selectedTokenAddress
? ''
: 'wallet-balance-wrapper--active'
@@ -73,13 +82,25 @@ WalletView.prototype.renderWalletBalance = function () {
WalletView.prototype.render = function () {
const {
- network, responsiveDisplayClassname, identities,
- selectedAddress, accounts,
+ responsiveDisplayClassname,
+ selectedAddress,
selectedIdentity,
+ keyrings,
+ showAccountDetailModal,
+ hideSidebar,
+ showAddTokenPage,
} = this.props
// temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount)
+ const keyring = keyrings.find((kr) => {
+ return kr.accounts.includes(selectedAddress) ||
+ kr.accounts.includes(selectedIdentity.address)
+ })
+
+ const type = keyring.type
+ const isLoose = type !== 'HD Key Tree'
+
return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), {
style: {},
}, [
@@ -88,57 +109,16 @@ WalletView.prototype.render = function () {
h('div.flex-column.wallet-view-account-details', {
style: {},
}, [
+ h('div.wallet-view__sidebar-close', {
+ onClick: hideSidebar,
+ }),
- h('div.flex-row.account-options-menu', {
- style: {
- position: 'relative',
- },
- }, [
-
- h(AccountDropdowns, {
- selected: selectedAddress,
- network,
- identities,
- useCssTransition: true,
- enableAccountOptions: true,
- dropdownWrapperStyle: {
- padding: '1px 15px',
- marginLeft: '-25px',
- position: 'absolute',
- width: '122%', // TODO, refactor all of this component out into media queries
- },
- menuItemStyles: {
- padding: '0px 0px',
- margin: '22px 0px',
- },
- }, []),
-
- ]),
+ h('div.wallet-view__keyring-label', isLoose ? 'IMPORTED' : ''),
- h('div.flex-column.flex-center', {
+ h('div.flex-column.flex-center.wallet-view__name-container', {
+ style: { margin: '0 auto' },
+ onClick: showAccountDetailModal,
}, [
- h('div', {
- style: {
- position: 'relative',
- },
- }, [
- h(AccountDropdowns, {
- accounts,
- style: {
- position: 'absolute',
- left: 'calc(50% + 28px + 5.5px)',
- top: '14px',
- },
- innerStyle: {
- padding: '10px 16px',
- },
- useCssTransition: true,
- selected: selectedAddress,
- network,
- identities,
- }, []),
- ]),
-
h(Identicon, {
diameter: 54,
address: selectedAddress,
@@ -150,21 +130,33 @@ WalletView.prototype.render = function () {
selectedIdentity.name,
]),
+ h('button.wallet-view__details-button', 'DETAILS'),
]),
]),
- // 'Wallet' - Title
- // Not visible on mobile
- h('div.flex-column.wallet-view-title-wrapper', {}, [
- h('span.wallet-view-title', {}, [
- 'Wallet',
- ]),
+
+ h('div.wallet-view__address', {
+ onClick: () => {
+ copyToClipboard(selectedAddress)
+ this.setState({ hasCopied: true })
+ setTimeout(() => this.setState({ hasCopied: false }), 3000)
+ },
+ }, [
+ this.state.hasCopied && 'Copied to Clipboard',
+ !this.state.hasCopied && `${selectedAddress.slice(0, 4)}...${selectedAddress.slice(-4)}`,
+ h('i.fa.fa-clipboard', { style: { marginLeft: '8px' } }),
]),
this.renderWalletBalance(),
h(TokenList),
+ h('button.wallet-view__add-token-button', {
+ onClick: () => {
+ showAddTokenPage()
+ hideSidebar()
+ },
+ }, 'Add Token'),
])
}
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index dfa6f88c4..97e0646e8 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -6,9 +6,10 @@ const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
const PendingTx = require('./components/pending-tx')
-const PendingMsg = require('./components/pending-msg')
-const PendingPersonalMsg = require('./components/pending-personal-msg')
-const PendingTypedMsg = require('./components/pending-typed-msg')
+const SignatureRequest = require('./components/signature-request')
+// const PendingMsg = require('./components/pending-msg')
+// const PendingPersonalMsg = require('./components/pending-personal-msg')
+// const PendingTypedMsg = require('./components/pending-typed-msg')
const Loading = require('./components/loading')
// const contentDivider = h('div', {
@@ -102,8 +103,10 @@ ConfirmTxScreen.prototype.render = function () {
cancelTransaction: this.cancelTransaction.bind(this, txData),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
+ signTypedMessage: this.signTypedMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
+ cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
})
}
@@ -118,17 +121,19 @@ function currentTxView (opts) {
return h(PendingTx, opts)
} else if (msgParams) {
log.debug('msgParams detected, rendering pending msg')
-
- if (type === 'eth_sign') {
- log.debug('rendering eth_sign message')
- return h(PendingMsg, opts)
- } else if (type === 'personal_sign') {
- log.debug('rendering personal_sign message')
- return h(PendingPersonalMsg, opts)
- } else if (type === 'eth_signTypedData') {
- log.debug('rendering eth_signTypedData message')
- return h(PendingTypedMsg, opts)
- }
+
+ return h(SignatureRequest, opts)
+
+ // if (type === 'eth_sign') {
+ // log.debug('rendering eth_sign message')
+ // return h(PendingMsg, opts)
+ // } else if (type === 'personal_sign') {
+ // log.debug('rendering personal_sign message')
+ // return h(PendingPersonalMsg, opts)
+ // } else if (type === 'eth_signTypedData') {
+ // log.debug('rendering eth_signTypedData message')
+ // return h(PendingTypedMsg, opts)
+ // }
}
return h(Loading)
}
diff --git a/ui/app/config.js b/ui/app/config.js
deleted file mode 100644
index 8b4044882..000000000
--- a/ui/app/config.js
+++ /dev/null
@@ -1,215 +0,0 @@
-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 infuraCurrencies = require('./infura-conversion.json').objects.sort((a, b) => {
- return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
- })
-const validUrl = require('valid-url')
-const exportAsFile = require('./util').exportAsFile
-
-
-module.exports = connect(mapStateToProps)(ConfigScreen)
-
-function mapStateToProps (state) {
- return {
- metamask: state.metamask,
- warning: state.appState.warning,
- }
-}
-
-inherits(ConfigScreen, Component)
-function ConfigScreen () {
- Component.call(this)
-}
-
-ConfigScreen.prototype.render = function () {
- var state = this.props
- var metamaskState = state.metamask
- var warning = state.warning
-
- return (
- h('.flex-column.flex-grow', { style: { marginTop: '32px' } }, [
-
- // subtitle and nav
- h('.section-title.flex-row.flex-center', [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- onClick: (event) => {
- state.dispatch(actions.goHome())
- },
- }),
- h('h2.page-subtitle', 'Settings'),
- ]),
-
- h('.error', {
- style: {
- display: warning ? 'block' : 'none',
- padding: '0 20px',
- textAlign: 'center',
- },
- }, warning),
-
- // conf view
- h('.flex-column.flex-justify-center.flex-grow.select-none', [
- h('.flex-space-around', {
- style: {
- padding: '20px',
- },
- }, [
-
- currentProviderDisplay(metamaskState),
-
- h('div', { style: {display: 'flex'} }, [
- h('input#new_rpc', {
- placeholder: 'New RPC URL',
- style: {
- width: 'inherit',
- flex: '1 0 auto',
- height: '30px',
- margin: '8px',
- },
- onKeyPress (event) {
- if (event.key === 'Enter') {
- var element = event.target
- var newRpc = element.value
- rpcValidation(newRpc, state)
- }
- },
- }),
- h('button', {
- style: {
- alignSelf: 'center',
- },
- onClick (event) {
- event.preventDefault()
- var element = document.querySelector('input#new_rpc')
- var newRpc = element.value
- rpcValidation(newRpc, state)
- },
- }, 'Save'),
- ]),
-
- h('hr.horizontal-line'),
-
- currentConversionInformation(metamaskState, state),
-
- h('hr.horizontal-line'),
-
- h('div', {
- style: {
- marginTop: '20px',
- },
- }, [
- h('p', {
- style: {
- fontFamily: 'Montserrat Light',
- fontSize: '13px',
- },
- }, `State logs contain your public account addresses and sent transactions.`),
- h('br'),
- h('button', {
- style: {
- alignSelf: 'center',
- },
- onClick (event) {
- exportAsFile('MetaMask State Logs', window.logState())
- },
- }, 'Download State Logs'),
- ]),
-
- h('hr.horizontal-line'),
-
- h('div', {
- style: {
- marginTop: '20px',
- },
- }, [
- h('button', {
- style: {
- alignSelf: 'center',
- },
- onClick (event) {
- event.preventDefault()
- state.dispatch(actions.revealSeedConfirmation())
- },
- }, 'Reveal Seed Words'),
- ]),
-
- ]),
- ]),
- ])
- )
-}
-
-function rpcValidation (newRpc, state) {
- if (validUrl.isWebUri(newRpc)) {
- state.dispatch(actions.setRpcTarget(newRpc))
- } else {
- var appendedRpc = `http://${newRpc}`
- if (validUrl.isWebUri(appendedRpc)) {
- state.dispatch(actions.displayWarning('URIs require the appropriate HTTP/HTTPS prefix.'))
- } else {
- state.dispatch(actions.displayWarning('Invalid RPC URI'))
- }
- }
-}
-
-function currentConversionInformation (metamaskState, state) {
- var currentCurrency = metamaskState.currentCurrency
- var conversionDate = metamaskState.conversionDate
- return h('div', [
- h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, 'Current Conversion'),
- h('span', {style: { fontWeight: 'bold', paddingRight: '10px', fontSize: '13px'}}, `Updated ${Date(conversionDate)}`),
- h('select#currentCurrency', {
- onChange (event) {
- event.preventDefault()
- var element = document.getElementById('currentCurrency')
- var newCurrency = element.value
- state.dispatch(actions.setCurrentCurrency(newCurrency))
- },
- defaultValue: currentCurrency,
- }, infuraCurrencies.map((currency) => {
- console.log(`currency`, currency);
- return h('option', {key: currency.quote.code, value: currency.quote.code}, `${currency.quote.code.toUpperCase()} - ${currency.quote.name}`)
- })
- ),
- ])
-}
-
-function currentProviderDisplay (metamaskState) {
- var provider = metamaskState.provider
- var title, value
-
- switch (provider.type) {
-
- case 'mainnet':
- title = 'Current Network'
- value = 'Main Ethereum Network'
- break
-
- case 'ropsten':
- title = 'Current Network'
- value = 'Ropsten Test Network'
- break
-
- case 'kovan':
- title = 'Current Network'
- value = 'Kovan Test Network'
- break
-
- case 'rinkeby':
- title = 'Current Network'
- value = 'Rinkeby Test Network'
- break
-
- default:
- title = 'Current RPC'
- value = metamaskState.provider.rpcTarget
- }
-
- return h('div', [
- h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, title),
- h('span', value),
- ])
-}
diff --git a/ui/app/css/itcss/components/account-dropdown-mini.scss b/ui/app/css/itcss/components/account-dropdown-mini.scss
new file mode 100644
index 000000000..996993db7
--- /dev/null
+++ b/ui/app/css/itcss/components/account-dropdown-mini.scss
@@ -0,0 +1,48 @@
+.account-dropdown-mini {
+ height: 22px;
+ background-color: $white;
+ font-family: Roboto;
+ line-height: 16px;
+ font-size: 12px;
+ width: 124px;
+
+ &__close-area {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+ }
+
+ &__list {
+ z-index: 1050;
+ position: absolute;
+ height: 180px;
+ width: 96pxpx;
+ border: 1px solid $geyser;
+ border-radius: 4px;
+ background-color: $white;
+ box-shadow: 0 3px 6px 0 rgba(0 ,0 ,0 ,.11);
+ overflow-y: scroll;
+ }
+
+ .account-list-item {
+ margin-top: 6px;
+ }
+
+ .account-list-item__account-name {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ width: 80px;
+ }
+
+ .account-list-item__top-row {
+ margin: 0;
+ }
+
+ .account-list-item__icon {
+ position: initial;
+ }
+} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss
index 4fc7c705a..c298c4019 100644
--- a/ui/app/css/itcss/components/account-dropdown.scss
+++ b/ui/app/css/itcss/components/account-dropdown.scss
@@ -62,4 +62,11 @@
&__account-secondary-balance {
color: $dusty-gray;
}
+
+ &__account-address {
+ margin-left: 35px;
+ width: 80%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
}
diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss
index 090710f7b..e40e5a8c0 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/css/itcss/components/account-menu.scss
@@ -21,6 +21,7 @@
}
&__icon {
+ margin-left: 20px;
cursor: pointer;
}
@@ -65,6 +66,8 @@
.keyring-label {
margin-top: 5px;
+ background-color: $black;
+ color: $dusty-gray;
}
}
@@ -88,9 +91,20 @@
&__check-mark {
width: 14px;
+ margin-right: 12px;
flex: 0 0 auto;
}
+ &__check-mark-icon {
+ background-image: url("images/check-white.svg");
+ height: 18px;
+ width: 18px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ margin: 3px 0;
+ }
+
.identicon {
margin: 0 12px 0 0;
flex: 0 0 auto;
diff --git a/ui/app/css/itcss/components/add-token.scss b/ui/app/css/itcss/components/add-token.scss
index aa8221c9a..5f6d0fcff 100644
--- a/ui/app/css/itcss/components/add-token.scss
+++ b/ui/app/css/itcss/components/add-token.scss
@@ -7,19 +7,6 @@
z-index: 12;
font-family: 'DIN Next Light';
- @media screen and (max-width: $break-small) {
- top: 0;
- width: 100%;
-
- &__wrapper {
- box-shadow: none !important;
- }
-
- &__footers {
- border-bottom: 1px solid $gallery;
- }
- }
-
&__wrapper {
background-color: $white;
box-shadow: 0 2px 4px 0 rgba($black, .08);
@@ -109,7 +96,18 @@
cursor: pointer;
&:hover {
- background-color: $gallery;
+ background-color: rgba(0, 0, 0, .05);
+ }
+
+ &:active {
+ background-color: rgba(0, 0, 0, .1);
+ }
+
+ .fa {
+ position: absolute;
+ right: 24px;
+ font-size: 24px;
+ line-height: 24px;
}
}
@@ -180,7 +178,7 @@
transition: 200ms ease-in-out;
display: flex;
flex-flow: row nowrap;
- flex: 0 0 45%;
+ flex: 0 0 42.5%;
align-items: center;
padding: 12px;
margin: 2.5%;
@@ -204,6 +202,10 @@
}
}
+ &__token-data {
+ align-self: flex-start;
+ }
+
&__token-name {
font-size: 14px;
line-height: 19px;
@@ -263,6 +265,11 @@
&__confirmation-title {
padding: 30px 120px 12px;
+
+ @media screen and (max-width: $break-small) {
+ padding: 20px 0;
+ width: 100%;
+ }
}
&__confirmation-content {
@@ -272,7 +279,7 @@
&__confirmation-token-list-item {
display: flex;
flex-flow: row nowrap;
- padding: 0 120px;
+ margin: 0 auto;
align-items: center;
}
@@ -283,4 +290,52 @@
&__confirmation-token-icon {
margin-right: 18px;
}
+
+ @media screen and (max-width: $break-small) {
+ top: 0;
+ width: 100%;
+ overflow: hidden;
+ height: 100%;
+
+ &__wrapper {
+ box-shadow: none !important;
+ flex: 1 1 auto;
+ width: 100%;
+ overflow-y: auto;
+ }
+
+ &__footers {
+ border-bottom: 1px solid $gallery;
+ }
+
+ &__token-icon {
+ width: 50px;
+ height: 50px;
+ }
+
+ &__token-symbol {
+ font-size: 18px;
+ line-height: 24px;
+ }
+
+ &__token-name {
+ font-size: 12px;
+ line-height: 16px;
+ }
+
+ &__buttons {
+ flex-flow: row nowrap;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ padding: 12px 0;
+ margin: 0;
+ border-top: 1px solid $gallery;
+
+ button {
+ flex: 1 0 auto;
+ margin: 0 12px;
+ }
+ }
+ }
}
diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss
index d4f0fe5ac..4a8232e39 100644
--- a/ui/app/css/itcss/components/confirm.scss
+++ b/ui/app/css/itcss/components/confirm.scss
@@ -16,6 +16,15 @@
}
}
+.notification {
+ .confirm-screen-wrapper {
+
+ @media screen and (max-width: $break-small) {
+ height: calc(100vh - 85px);
+ }
+ }
+}
+
.confirm-screen-wrapper {
height: 100%;
width: 380px;
@@ -37,7 +46,7 @@
overflow-y: auto;
top: 0;
box-shadow: none;
- height: calc(100vh - 58px - 100px);
+ height: calc(100vh - 58px - 85px);
border-top-left-radius: 0;
border-top-right-radius: 0;
}
@@ -66,7 +75,7 @@
flex: 0 0 auto;
@media screen and (max-width: $break-small) {
- font-size: 22px;
+ font-size: 20px;
}
}
@@ -76,8 +85,10 @@
background: $athens-grey;
position: absolute;
transform: rotate(45deg);
- left: 178px;
top: 71px;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
}
.confirm-screen-title {
@@ -133,6 +144,14 @@
height: 16px;
}
+.confirm-send-ether,
+.confirm-send-token {
+ i.fa-arrow-right {
+ align-self: start;
+ margin: 24px 14px 0 !important;
+ }
+}
+
.confirm-screen-identicons {
margin-top: 24px;
flex: 0 0 auto;
@@ -271,6 +290,7 @@ section .confirm-screen-account-number,
box-shadow: none;
flex: 1 0 auto;
font-weight: 300;
+ margin: 0 8px;
}
.btn-light.confirm-screen-cancel-button {
@@ -288,6 +308,7 @@ section .confirm-screen-account-number,
cursor: pointer;
flex: 1 0 auto;
font-weight: 300;
+ margin: 0 8px;
}
#pending-tx-form {
@@ -296,7 +317,7 @@ section .confirm-screen-account-number,
display: flex;
flex-flow: row nowrap;
background-color: $white;
- padding: 19px 18px;
+ padding: 12px 18px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
width: 100%;
diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss
index eb1776c58..9459629b6 100644
--- a/ui/app/css/itcss/components/currency-display.scss
+++ b/ui/app/css/itcss/components/currency-display.scss
@@ -22,6 +22,7 @@
line-height: 22px;
border: none;
outline: 0 !important;
+ max-width: 100%;
}
&__primary-currency {
@@ -43,4 +44,13 @@
font-size: 12px;
line-height: 12px;
}
+
+ &__input-wrapper {
+ position: relative;
+ display: flex;
+ }
+
+ &__currency-symbol {
+ margin-top: 1px;
+ }
} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/editable-label.scss b/ui/app/css/itcss/components/editable-label.scss
new file mode 100644
index 000000000..13570610c
--- /dev/null
+++ b/ui/app/css/itcss/components/editable-label.scss
@@ -0,0 +1,34 @@
+.editable-label {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+
+ &__value {
+ max-width: 250px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ &__input {
+ width: 250px;
+ font-size: 14px;
+ text-align: center;
+
+ &--error {
+ border: 1px solid $monzo;
+ }
+ }
+
+ &__icon-wrapper {
+ position: absolute;
+ margin-left: 10px;
+ left: 100%;
+ }
+
+ &__icon {
+ cursor: pointer;
+ color: $dusty-gray;
+ }
+}
diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss
index ef84dc3f4..a6332f819 100644
--- a/ui/app/css/itcss/components/header.scss
+++ b/ui/app/css/itcss/components/header.scss
@@ -27,6 +27,10 @@
bottom: -32px;
}
}
+
+ .metafox-icon {
+ cursor: pointer;
+ }
}
.app-header-contents {
@@ -58,6 +62,7 @@
text-transform: uppercase;
font-weight: 400;
color: #22232c; // $shark
+ line-height: 29px;
@media screen and (max-width: 575px) {
display: none;
@@ -75,13 +80,13 @@ h2.page-subtitle {
display: flex;
flex-direction: row;
align-items: center;
- margin-right: 20px;
}
.left-menu-wrapper {
display: flex;
flex-direction: row;
align-items: center;
+ cursor: pointer;
}
.header__right-actions {
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index fda002785..4ba02be67 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -38,3 +38,14 @@
@import './gas-slider.scss';
+@import './settings.scss';
+
+@import './tab-bar.scss';
+
+@import './simple-dropdown.scss';
+
+@import './request-signature.scss';
+
+@import './account-dropdown-mini.scss';
+
+@import './editable-label.scss';
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index 1ffea58a9..b69bd5c7e 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.scss
@@ -57,6 +57,7 @@
border-radius: 6px;
border: 1px solid $black;
padding: 0% 7%;
+ justify-content: center;
div.buy-modal-content-option-title {
font-size: 20px;
@@ -293,6 +294,11 @@
font-size: 18px;
}
+.account-modal__name {
+ margin-top: 9px;
+ font-size: 20px;
+}
+
.private-key-password {
display: flex;
flex-direction: column;
@@ -372,6 +378,7 @@
resize: none;
padding: 9px 13px 8px;
text-transform: uppercase;
+ font-weight: 300;
}
diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss
index bb8c4eea8..98dbdffb2 100644
--- a/ui/app/css/itcss/components/network.scss
+++ b/ui/app/css/itcss/components/network.scss
@@ -1,3 +1,12 @@
+.network-component--disabled {
+ // border-color: transparent !important;
+ cursor: default;
+
+ .fa-caret-down {
+ opacity: 0;
+ }
+}
+
.network-component.pointer {
border: 1px solid $shark;
border-radius: 82px;
@@ -40,7 +49,7 @@
.dropdown-menu-item {
.menu-icon-circle,
.menu-icon-circle--active {
- margin: 0 16px;
+ margin: 0 14px;
}
}
@@ -116,8 +125,8 @@
.menu-icon-circle div,
.menu-icon-circle--active div {
- height: 17px;
- width: 17px;
+ height: 12px;
+ width: 12px;
border-radius: 17px;
}
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index 1ee8283ef..244de2ba0 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -43,8 +43,11 @@ $wallet-view-bg: $wild-sand;
.wallet-view {
display: flex;
flex-direction: column;
- flex: 33.5 0 33.5%;
+ flex: 33.5 1 33.5%;
+ width: 0;
background: $wallet-view-bg;
+ z-index: 200;
+ position: relative;
@media screen and (min-width: 576px) {
overflow-y: scroll;
@@ -52,7 +55,78 @@ $wallet-view-bg: $wild-sand;
}
.wallet-view-account-details {
- flex: 0 0 150px;
+ flex: 0 0 auto;
+ }
+
+ &__name-container {
+ flex: 0 0 auto;
+ cursor: pointer;
+ width: 100%;
+ }
+
+ &__keyring-label {
+ height: 40px;
+ color: $dusty-gray;
+ font-family: Roboto;
+ font-size: 10px;
+ line-height: 40px;
+ text-align: right;
+ padding: 0 20px;
+ }
+
+ &__details-button {
+ color: $curious-blue;
+ font-size: 10px;
+ line-height: 13px;
+ text-align: center;
+ border: 1px solid $curious-blue;
+ border-radius: 10.5px;
+ background-color: transparent;
+ margin: 0 auto;
+ padding: 4px 12px;
+ flex: 0 0 auto;
+ }
+
+ &__address {
+ border-radius: 3px;
+ background-color: $alto;
+ color: $scorpion;
+ font-size: 14px;
+ line-height: 12px;
+ padding: 4px 12px;
+ margin: 24px auto;
+ font-weight: 300;
+ cursor: pointer;
+ flex: 0 0 auto;
+ }
+
+ &__sidebar-close {
+
+ @media screen and (max-width: 575px) {
+ &::after {
+ content: '\00D7';
+ font-size: 40px;
+ color: $tundora;
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ cursor: pointer;
+ }
+ }
+ }
+
+ &__add-token-button {
+ flex: 0 0 auto;
+ color: $dusty-gray;
+ font-size: 14px;
+ line-height: 19px;
+ text-align: center;
+ margin: 36px auto;
+ border: 1px solid $dusty-gray;
+ border-radius: 2px;
+ font-weight: 300;
+ background: none;
+ padding: 9px 30px;
}
}
@@ -81,7 +155,7 @@ $wallet-view-bg: $wild-sand;
background: rgb(250, 250, 250);
z-index: $sidebar-z-index;
position: fixed;
- top: 57px;
+ top: 56px;
left: 0;
right: 0;
bottom: 0;
@@ -91,7 +165,7 @@ $wallet-view-bg: $wild-sand;
overflow-y: auto;
box-shadow: rgba(0, 0, 0, .15) 2px 2px 4px;
width: 85%;
- height: calc(100% - 57px);
+ height: calc(100% - 56px);
}
.sidebar-overlay {
@@ -173,15 +247,18 @@ $wallet-view-bg: $wild-sand;
// wallet view
.account-name {
-
- @media screen and (max-width: 575px) {
- font-size: 102%;
- margin-left: 3%;
- }
-
- @media screen and (max-width: 575px) {
- text-align: center;
- }
+ font-size: 24px;
+ font-weight: 200;
+ line-height: 20px;
+ color: $scorpion;
+ margin-top: 8px;
+ margin-bottom: 24px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ width: 100%;
+ padding: 0 8px;
+ text-align: center;
}
// account options dropdown
diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss
new file mode 100644
index 000000000..ee54235d0
--- /dev/null
+++ b/ui/app/css/itcss/components/request-signature.scss
@@ -0,0 +1,222 @@
+.request-signature {
+ &__container {
+ width: 380px;
+ border-radius: 8px;
+ background-color: $white;
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
+ display: flex;
+ flex-flow: column nowrap;
+ z-index: 25;
+ align-items: center;
+ font-family: Roboto;
+ position: relative;
+ height: 100%;
+
+ @media screen and (max-width: $break-small) {
+ width: 100%;
+ top: 0;
+ box-shadow: none;
+ }
+
+ @media screen and (min-width: $break-large) {
+ max-height: 620px;
+ }
+ }
+
+ &__header {
+ height: 64px;
+ width: 100%;
+ position: relative;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ flex: 0 0 auto;
+ }
+
+ &__header-background {
+ position: absolute;
+ background-color: $athens-grey;
+ z-index: 2;
+ width: 100%;
+ height: 100%;
+ }
+
+ &__header__text {
+ height: 29px;
+ width: 179px;
+ color: #5B5D67;
+ font-family: Roboto;
+ font-size: 22px;
+ font-weight: 300;
+ line-height: 29px;
+ z-index: 3;
+ }
+
+ &__header__tip-container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ }
+
+ &__header__tip {
+ height: 25px;
+ width: 25px;
+ background: $athens-grey;
+ transform: rotate(45deg);
+ position: absolute;
+ bottom: -8px;
+ z-index: 1;
+ }
+
+ &__account-info {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 18px;
+ margin-bottom: 20px;
+ }
+
+ &__account {
+ color: $dusty-gray;
+ margin-left: 17px;
+ }
+
+ &__account-text {
+ font-size: 14px;
+ }
+
+ &__balance {
+ color: $dusty-gray;
+ margin-right: 17px;
+ width: 124px;
+ }
+
+ &__balance-text {
+ text-align: right;
+ font-size: 14px;
+ }
+
+ &__balance-value {
+ text-align: right;
+ margin-top: 2.5px;
+ }
+
+ &__request-icon {
+ margin-top: 25px;
+ }
+
+ &__body {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ flex: 1 1 auto;
+ height: 0;
+ }
+
+ &__request-info {
+ display: flex;
+ justify-content: center;
+ }
+
+ &__headline {
+ height: 48px;
+ width: 240px;
+ color: $tundora;
+ font-family: Roboto;
+ font-size: 18px;
+ font-weight: 300;
+ line-height: 24px;
+ text-align: center;
+ margin-top: 20px;
+ }
+
+ &__notice {
+ color: #9B9B9B;
+ font-family: "Avenir Next";
+ font-size: 14px;
+ line-height: 19px;
+ text-align: center;
+ margin-top: 41px;
+ margin-bottom: 11px;
+ width: 100%;
+ }
+
+ &__rows {
+ height: 100%;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ border-top: 1px solid $geyser;
+ display: flex;
+ flex-flow: column;
+ }
+
+ &__row {
+ display: flex;
+ flex-flow: column;
+ }
+
+ &__row-title {
+ width: 80px;
+ color: $dusty-gray;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 22px;
+ margin-top: 12px;
+ margin-left: 18px;
+ width: 100%;
+ }
+
+ &__row-value {
+ color: $scorpion;
+ font-family: Roboto;
+ font-size: 14px;
+ line-height: 19px;
+ width: 100%;
+ overflow-wrap: break-word;
+ border-bottom: 1px solid #d2d8dd;
+ padding: 6px 18px 15px;
+ }
+
+ &__footer {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-evenly;
+ font-size: 22px;
+ position: relative;
+ flex: 0 0 auto;
+ border-top: 1px solid $geyser;
+
+ &__cancel-button,
+ &__sign-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 1 0 auto;
+ font-family: Roboto;
+ font-size: 16px;
+ font-weight: 300;
+ height: 55px;
+ line-height: 32px;
+ cursor: pointer;
+ border-radius: 2px;
+ box-shadow: none;
+ max-width: 162px;
+ margin: 12px;
+ }
+
+ &__cancel-button {
+ background: none;
+ border: 1px solid $dusty-gray;
+ margin-right: 6px;
+ }
+
+ &__sign-button {
+ background-color: $caribbean-green;
+ border-width: 0;
+ color: $white;
+ margin-left: 6px;
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss
index bc89fdccc..388aea175 100644
--- a/ui/app/css/itcss/components/sections.scss
+++ b/ui/app/css/itcss/components/sections.scss
@@ -39,7 +39,9 @@ textarea.twelve-word-phrase {
/* unlock */
.error {
- color: #e20202;
+ // color: #e20202;
+ color: #f7861c;
+ margin-bottom: 9px;
}
.warning {
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 9a076551e..282eef030 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -274,6 +274,7 @@
color: #9b9b9b;
font-size: .8em;
padding: 1px 4px;
+ cursor: pointer;
}
.token-gas {
@@ -400,7 +401,7 @@
.send-v2 {
&__container {
- height: 701px;
+ // height: 701px;
width: 380px;
border-radius: 8px;
background-color: $white;
@@ -416,6 +417,7 @@
width: 100%;
top: 0;
box-shadow: none;
+ flex: 1 1 auto;
}
}
@@ -473,6 +475,7 @@
@media screen and (max-width: $break-small) {
height: 59px;
+ width: 100vw;
}
}
@@ -483,10 +486,13 @@
position: absolute;
transform: rotate(45deg);
left: 178px;
- top: 65px;
+ top: 75px;
@media screen and (max-width: $break-small) {
top: 46px;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
}
}
@@ -520,18 +526,20 @@
}
&__form {
- margin-top: 13px;
+ margin: 13px 0;
width: 100%;
@media screen and (max-width: $break-small) {
- margin-top: 0px;
+ padding: 13px 0;
+ margin: 0;
height: 0;
overflow-y: auto;
flex: 1 1 auto;
}
}
- &__form-header, &__form-header-copy {
+ &__form-header,
+ &__form-header-copy {
width: 100%;
display: flex;
flex-flow: column;
@@ -594,6 +602,16 @@
}
}
+ &__to-autocomplete {
+ position: relative;
+
+ &__down-caret {
+ position: absolute;
+ top: 18px;
+ right: 12px;
+ }
+ }
+
&__to-autocomplete, &__memo-text-area {
&__input {
height: 54px;
@@ -652,31 +670,32 @@
}
&__next-btn,
- &__cancel-btn {
+ &__cancel-btn,
+ &__next-btn__disabled {
width: 163px;
text-align: center;
height: 55px;
- width: 163px;
border-radius: 2px;
background-color: $white;
font-family: Roboto;
font-size: 16px;
font-weight: 300;
line-height: 21px;
- text-align: center;
border: 1px solid;
+ margin: 0 4px;
}
+ &__next-btn,
&__next-btn__disabled {
- opacity: .5;
- cursor: auto;
- }
-
- &__next-btn {
color: $curious-blue;
border-color: $curious-blue;
}
+ &__next-btn__disabled {
+ opacity: .5;
+ cursor: auto;
+ }
+
&__cancel-btn {
color: $dusty-gray;
border-color: $dusty-gray;
@@ -692,8 +711,8 @@
flex-flow: column;
@media screen and (max-width: $break-small) {
- width: 355px;
- height: 598px;
+ width: 100vw;
+ height: 100vh;
}
&__header {
@@ -703,6 +722,10 @@
align-items: center;
justify-content: space-between;
font-size: 22px;
+
+ @media screen and (max-width: $break-small) {
+ flex: 0 0 auto;
+ }
}
&__title {
@@ -718,14 +741,19 @@
margin-right: 19.25px;
}
+ &__content {
+ display: flex;
+ flex-flow: column nowrap;
+ height: 100%;
+ }
+
&__body {
- height: 248px;
display: flex;
+ margin-bottom: 24px;
@media screen and (max-width: $break-small) {
- width: 355px;
- height: 470px;
flex-flow: column;
+ flex: 1 1 auto;
}
}
@@ -736,6 +764,11 @@
align-items: center;
justify-content: space-between;
font-size: 22px;
+ position: relative;
+
+ @media screen and (max-width: $break-small) {
+ flex: 0 0 auto;
+ }
}
&__buttons {
@@ -745,7 +778,7 @@
margin-right: 21.25px;
}
- &__revert, &__cancel, &__save {
+ &__revert, &__cancel, &__save, &__save__error {
display: flex;
justify-content: center;
align-items: center;
@@ -758,7 +791,7 @@
margin-left: 21.25px;
}
- &__cancel, &__save {
+ &__cancel, &__save, &__save__error {
height: 34.64px;
width: 85.74px;
border: 1px solid $dusty-gray;
@@ -767,6 +800,21 @@
font-size: 12px;
color: $dusty-gray;
}
+
+ &__save__error {
+ opacity: 0.5;
+ cursor: auto;
+ }
+
+ &__error-message {
+ display: block;
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ font-size: 12px;
+ line-height: 12px;
+ color: $red;
+ }
}
&__gas-modal-card {
@@ -778,7 +826,6 @@
&__title {
height: 26px;
- width: 84px;
color: $tundora;
font-family: Roboto;
font-size: 20px;
diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss
new file mode 100644
index 000000000..2f29d8017
--- /dev/null
+++ b/ui/app/css/itcss/components/settings.scss
@@ -0,0 +1,201 @@
+.settings {
+ position: relative;
+ background: $white;
+ display: flex;
+ flex-flow: column nowrap;
+ height: auto;
+ overflow: auto;
+}
+
+.settings__header {
+ padding: 25px;
+}
+
+.settings__close-button::after {
+ content: '\00D7';
+ font-size: 40px;
+ color: $dusty-gray;
+ position: absolute;
+ top: 25px;
+ right: 30px;
+ cursor: pointer;
+}
+
+.settings__error {
+ padding-bottom: 20px;
+ text-align: center;
+ color: $crimson;
+}
+
+.settings__content {
+ padding: 0 25px;
+}
+
+.settings__content-row {
+ display: flex;
+ flex-direction: row;
+ padding: 10px 0 20px;
+
+ @media screen and (max-width: 575px) {
+ flex-direction: column;
+ padding: 10px 0;
+ }
+}
+
+.settings__content-item {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ padding: 0 5px;
+ height: 71px;
+
+ @media screen and (max-width: 575px) {
+ height: initial;
+ padding: 5px 0;
+ }
+
+ &--without-height {
+ height: initial;
+ }
+}
+
+.settings__content-item-col {
+ max-width: 300px;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: 575px) {
+ max-width: 100%;
+ width: 100%;
+ }
+}
+
+.settings__content-description {
+ font-size: 14px;
+ color: $dusty-gray;
+ padding-top: 5px;
+}
+
+.settings__input {
+ padding-left: 10px;
+ font-size: 14px;
+ height: 40px;
+ border: 1px solid $alto;
+}
+
+.settings__input::-webkit-input-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__input::-moz-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__input:-ms-input-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__input:-moz-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__provider-wrapper {
+ font-size: 16px;
+ border: 1px solid $alto;
+ border-radius: 2px;
+ padding: 15px;
+ background-color: $white;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.settings__provider-icon {
+ height: 10px;
+ width: 10px;
+ margin-right: 10px;
+ border-radius: 10px;
+}
+
+.settings__rpc-save-button {
+ align-self: flex-end;
+ padding: 5px;
+ text-transform: uppercase;
+ color: $dusty-gray;
+ cursor: pointer;
+}
+
+.settings__clear-button {
+ font-size: 16px;
+ border: 1px solid $curious-blue;
+ color: $curious-blue;
+ border-radius: 2px;
+ padding: 18px;
+ background-color: $white;
+ text-transform: uppercase;
+}
+
+.settings__clear-button--red {
+ border: 1px solid $monzo;
+ color: $monzo;
+}
+
+.settings__info-logo-wrapper {
+ height: 80px;
+ margin-bottom: 20px;
+}
+
+.settings__info-logo {
+ max-height: 100%;
+ max-width: 100%;
+}
+
+.settings__info-item {
+ padding: 10px 0;
+}
+
+.settings__info-link-header {
+ padding-bottom: 15px;
+
+ @media screen and (max-width: 575px) {
+ padding-bottom: 5px;
+ }
+}
+
+.settings__info-link-item {
+ padding: 15px 0;
+
+ @media screen and (max-width: 575px) {
+ padding: 5px 0;
+ }
+}
+
+.settings__info-version-number {
+ padding-top: 5px;
+ font-size: 13px;
+ color: $dusty-gray;
+}
+
+.settings__info-about {
+ color: $dusty-gray;
+ margin-bottom: 15px;
+}
+
+.settings__info-link {
+ color: $curious-blue;
+}
+
+.settings__info-separator {
+ margin: 15px 0;
+ width: 80px;
+ border-color: $alto;
+ border: none;
+ height: 1px;
+ background-color: $alto;
+ color: $alto;
+}
diff --git a/ui/app/css/itcss/components/simple-dropdown.scss b/ui/app/css/itcss/components/simple-dropdown.scss
new file mode 100644
index 000000000..a21095a3e
--- /dev/null
+++ b/ui/app/css/itcss/components/simple-dropdown.scss
@@ -0,0 +1,65 @@
+.simple-dropdown {
+ height: 56px;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ border: 1px solid $alto;
+ border-radius: 4px;
+ background-color: $white;
+ font-size: 16px;
+ color: #4d4d4d;
+ cursor: pointer;
+ position: relative;
+}
+
+.simple-dropdown__caret {
+ color: $silver;
+ padding: 0 10px;
+}
+
+.simple-dropdown__selected {
+ flex-grow: 1;
+ padding: 0 15px;
+}
+
+.simple-dropdown__options {
+ z-index: 1050;
+ position: absolute;
+ height: 220px;
+ width: 100%;
+ border: 1px solid #d2d8dd;
+ border-radius: 4px;
+ background-color: #fff;
+ -webkit-box-shadow: 0 3px 6px 0 rgba(0, 0, 0, .11);
+ box-shadow: 0 3px 6px 0 rgba(0, 0, 0, .11);
+ margin-top: 10px;
+ overflow-y: scroll;
+ left: 0;
+ top: 100%;
+}
+
+.simple-dropdown__option {
+ padding: 10px;
+
+ &:hover {
+ background-color: $gallery;
+ }
+}
+
+.simple-dropdown__option--selected {
+ background-color: $alto;
+
+ &:hover {
+ background-color: $alto;
+ cursor: default;
+ }
+}
+
+.simple-dropdown__close-area {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+}
diff --git a/ui/app/css/itcss/components/tab-bar.scss b/ui/app/css/itcss/components/tab-bar.scss
new file mode 100644
index 000000000..4f3077974
--- /dev/null
+++ b/ui/app/css/itcss/components/tab-bar.scss
@@ -0,0 +1,23 @@
+.tab-bar {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: flex-end;
+}
+
+.tab-bar__tab {
+ min-width: 0;
+ flex: 0 0 auto;
+ padding: 15px 25px;
+ border-bottom: 1px solid $alto;
+ box-sizing: border-box;
+ font-size: 18px;
+}
+
+.tab-bar__tab--active {
+ border-color: $black;
+}
+
+.tab-bar__grow-tab {
+ flex-grow: 1;
+}
diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss
index bbc64c324..d4add71b1 100644
--- a/ui/app/css/itcss/components/token-list.scss
+++ b/ui/app/css/itcss/components/token-list.scss
@@ -67,19 +67,21 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
position: fixed;
margin-top: 20px;
margin-left: 105px;
+ z-index: 2000;
&__close-area {
position: fixed;
top: 0;
left: 0;
- z-index: 1000;
+ z-index: 2100;
width: 100%;
height: 100%;
+ cursor: default;
}
&__container {
padding: 16px 34px 32px;
- z-index: 1050;
+ z-index: 2200;
position: relative;
}
diff --git a/ui/app/css/itcss/components/wallet-balance.scss b/ui/app/css/itcss/components/wallet-balance.scss
index cd44f89bb..64b291b89 100644
--- a/ui/app/css/itcss/components/wallet-balance.scss
+++ b/ui/app/css/itcss/components/wallet-balance.scss
@@ -1,4 +1,4 @@
-$wallet-balance-bg: $gallery;
+$wallet-balance-bg: #e7e7e7;
$wallet-balance-breakpoint: 890px;
$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
@@ -20,6 +20,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
align-items: center;
flex: 0 0 auto;
cursor: pointer;
+ border-top: 1px solid $wallet-balance-bg;
.balance-container {
display: flex;
diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss
index ca9fd0d9c..ee867640d 100644
--- a/ui/app/css/itcss/tools/utilities.scss
+++ b/ui/app/css/itcss/tools/utilities.scss
@@ -237,7 +237,6 @@ hr.horizontal-line {
color: #fff;
border-radius: 10px;
padding: 4px;
- width: 41px;
text-align: center;
height: 15px;
}
diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js
index 745990351..5ab5d4c33 100644
--- a/ui/app/keychains/hd/create-vault-complete.js
+++ b/ui/app/keychains/hd/create-vault-complete.js
@@ -62,7 +62,8 @@ CreateVaultCompleteScreen.prototype.render = function () {
}),
h('button.primary', {
- onClick: () => this.confirmSeedWords(),
+ onClick: () => this.confirmSeedWords()
+ .then(account => this.showAccountDetail(account)),
style: {
margin: '24px',
fontSize: '0.9em',
@@ -82,5 +83,9 @@ CreateVaultCompleteScreen.prototype.render = function () {
}
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
- this.props.dispatch(actions.confirmSeedWords())
+ return this.props.dispatch(actions.confirmSeedWords())
+}
+
+CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) {
+ return this.props.dispatch(actions.showAccountDetail(account))
}
diff --git a/ui/app/main-container.js b/ui/app/main-container.js
index eaaff8517..6e2342c2b 100644
--- a/ui/app/main-container.js
+++ b/ui/app/main-container.js
@@ -3,7 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountAndTransactionDetails = require('./account-and-transaction-details')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
-const ConfigScreen = require('./config')
+const Settings = require('./settings')
const UnlockScreen = require('./unlock')
module.exports = MainContainer
@@ -38,7 +38,7 @@ MainContainer.prototype.render = function () {
case 'config':
log.debug('rendering config screen from unlock screen.')
contents = {
- component: ConfigScreen,
+ component: Settings,
key: 'config',
}
break
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index 1cded7ca7..e1a890535 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -42,7 +42,7 @@ function rootReducer (state, action) {
}
window.logState = function () {
- let state = window.METAMASK_CACHED_LOG_STATE
+ const state = window.METAMASK_CACHED_LOG_STATE
let version
try {
version = global.platform.getVersion()
@@ -50,7 +50,7 @@ window.logState = function () {
version = 'unable to load version.'
}
state.version = version
- let stateString = JSON.stringify(state, removeSeedWords, 2)
+ const stateString = JSON.stringify(state, removeSeedWords, 2)
return stateString
}
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index f10bf9fb7..6fb7f8cca 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -557,6 +557,16 @@ function reduceApp (state, action) {
},
})
+ case actions.ONBOARDING_BUY_ETH_VIEW:
+ return extend(appState, {
+ transForward: true,
+ currentView: {
+ name: 'onboardingBuyEth',
+ context: appState.currentView.name,
+ },
+ identity: state.metamask.identities[action.value],
+ })
+
case actions.COINBASE_SUBVIEW:
return extend(appState, {
buyView: {
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index fb2b2e674..7408f827a 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -1,5 +1,6 @@
const extend = require('xtend')
const actions = require('../actions')
+const MetamascaraPlatform = require('../../../app/scripts/platforms/window')
module.exports = reduceMetamask
@@ -11,6 +12,7 @@ function reduceMetamask (state, action) {
isInitialized: false,
isUnlocked: false,
isAccountMenuOpen: false,
+ isMascara: window.platform instanceof MetamascaraPlatform,
rpcTarget: 'https://rawtestrpc.metamask.io/',
identities: {},
unapprovedTxs: {},
@@ -31,6 +33,7 @@ function reduceMetamask (state, action) {
memo: '',
errors: {},
},
+ coinOptions: {},
}, state.metamask)
switch (action.type) {
@@ -150,7 +153,7 @@ function reduceMetamask (state, action) {
})
case actions.UPDATE_TOKEN_EXCHANGE_RATE:
- const { payload: { pair, marketinfo } } = action
+ const { payload: { pair, marketinfo } } = action
return extend(metamaskState, {
tokenExchangeRates: {
...metamaskState.tokenExchangeRates,
@@ -226,10 +229,6 @@ function reduceMetamask (state, action) {
})
case actions.UPDATE_SEND_ERRORS:
- console.log(123, {
- ...metamaskState.send.errors,
- ...action.value,
- })
return extend(metamaskState, {
send: {
...metamaskState.send,
@@ -240,6 +239,39 @@ function reduceMetamask (state, action) {
},
})
+ case actions.CLEAR_SEND:
+ return extend(metamaskState, {
+ send: {
+ gasLimit: null,
+ gasPrice: null,
+ gasTotal: null,
+ from: '',
+ to: '',
+ amount: '0x0',
+ memo: '',
+ errors: {},
+ },
+ })
+
+ case actions.PAIR_UPDATE:
+ const { value: { marketinfo: pairMarketInfo } } = action
+ return extend(metamaskState, {
+ tokenExchangeRates: {
+ ...metamaskState.tokenExchangeRates,
+ [pairMarketInfo.pair]: pairMarketInfo,
+ },
+ })
+
+ case actions.SHAPESHIFT_SUBVIEW:
+ const { value: { marketinfo: ssMarketInfo, coinOptions } } = action
+ return extend(metamaskState, {
+ tokenExchangeRates: {
+ ...metamaskState.tokenExchangeRates,
+ [marketinfo.pair]: ssMarketInfo,
+ },
+ coinOptions,
+ })
+
default:
return metamaskState
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index 4c3d21d33..3a15cef4c 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -1,5 +1,9 @@
const valuesFor = require('./util').valuesFor
+const {
+ multiplyCurrencies,
+} = require('./conversion-util')
+
const selectors = {
getSelectedAddress,
getSelectedIdentity,
@@ -16,6 +20,8 @@ const selectors = {
getAddressBook,
getSendFrom,
getCurrentCurrency,
+ getSendAmount,
+ getSelectedTokenToFiatRate,
}
module.exports = selectors
@@ -123,6 +129,23 @@ function getSendFrom (state) {
return state.metamask.send.from
}
+function getSendAmount (state) {
+ return state.metamask.send.amount
+}
+
function getCurrentCurrency (state) {
return state.metamask.currentCurrency
}
+
+function getSelectedTokenToFiatRate (state) {
+ const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
+ const conversionRate = conversionRateSelector(state)
+
+ const tokenToFiatRate = multiplyCurrencies(
+ conversionRate,
+ selectedTokenExchangeRate,
+ { toNumericBase: 'dec' }
+ )
+
+ return tokenToFiatRate
+}
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 5e64daceb..e772477ae 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -2,6 +2,7 @@ const { inherits } = require('util')
const PersistentForm = require('../lib/persistent-form')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
+const classnames = require('classnames')
const Identicon = require('./components/identicon')
const FromDropdown = require('./components/send/from-dropdown')
@@ -19,6 +20,9 @@ const {
conversionGreaterThan,
addCurrencies,
} = require('./conversion-util')
+const {
+ isBalanceSufficient,
+} = require('./components/send/send-utils.js')
const { isValidAddress } = require('./util')
module.exports = SendTransactionScreen
@@ -28,7 +32,8 @@ function SendTransactionScreen () {
PersistentForm.call(this)
this.state = {
- dropdownOpen: false,
+ fromDropdownOpen: false,
+ toDropdownOpen: false,
errors: {
to: null,
amount: null,
@@ -153,7 +158,7 @@ SendTransactionScreen.prototype.renderFromRow = function () {
updateSendFrom,
} = this.props
- const { dropdownOpen } = this.state
+ const { fromDropdownOpen } = this.state
return h('div.send-v2__form-row', [
@@ -161,12 +166,12 @@ SendTransactionScreen.prototype.renderFromRow = function () {
h('div.send-v2__form-field', [
h(FromDropdown, {
- dropdownOpen,
+ dropdownOpen: fromDropdownOpen,
accounts: fromAccounts,
selectedAccount: from,
onSelect: updateSendFrom,
- openDropdown: () => this.setState({ dropdownOpen: true }),
- closeDropdown: () => this.setState({ dropdownOpen: false }),
+ openDropdown: () => this.setState({ fromDropdownOpen: true }),
+ closeDropdown: () => this.setState({ fromDropdownOpen: false }),
conversionRate,
}),
]),
@@ -174,15 +179,20 @@ SendTransactionScreen.prototype.renderFromRow = function () {
])
}
-SendTransactionScreen.prototype.handleToChange = function (event) {
- const { updateSendTo, updateSendErrors } = this.props
- const to = event.target.value
+SendTransactionScreen.prototype.handleToChange = function (to) {
+ const {
+ updateSendTo,
+ updateSendErrors,
+ from: {address: from},
+ } = this.props
let toError = null
if (!to) {
toError = 'Required'
} else if (!isValidAddress(to)) {
- toError = 'Recipient address is invalid.'
+ toError = 'Recipient address is invalid'
+ } else if (to === from) {
+ toError = 'From and To address cannot be the same'
}
updateSendTo(to)
@@ -190,8 +200,9 @@ SendTransactionScreen.prototype.handleToChange = function (event) {
}
SendTransactionScreen.prototype.renderToRow = function () {
- const { toAccounts, errors } = this.props
- const { to } = this.state
+ const { toAccounts, errors, to } = this.props
+
+ const { toDropdownOpen } = this.state
return h('div.send-v2__form-row', [
@@ -206,7 +217,10 @@ SendTransactionScreen.prototype.renderToRow = function () {
h('div.send-v2__form-field', [
h(ToAutoComplete, {
to,
- accounts: toAccounts,
+ accounts: Object.entries(toAccounts).map(([key, account]) => account),
+ dropdownOpen: toDropdownOpen,
+ openDropdown: () => this.setState({ toDropdownOpen: true }),
+ closeDropdown: () => this.setState({ toDropdownOpen: false }),
onChange: this.handleToChange,
inError: Boolean(errors.to),
}),
@@ -237,28 +251,16 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
let amountError = null
- const totalAmount = addCurrencies(amount, gasTotal, {
- aBase: 16,
- bBase: 16,
- toNumericBase: 'hex',
+ const sufficientBalance = isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
})
- const sufficientBalance = conversionGreaterThan(
- {
- value: balance,
- fromNumericBase: 'hex',
- fromCurrency: primaryCurrency,
- conversionRate,
- },
- {
- value: totalAmount,
- fromNumericBase: 'hex',
- conversionRate: amountConversionRate,
- fromCurrency: selectedToken || primaryCurrency,
- conversionRate: amountConversionRate,
- },
- )
-
const amountLessThanZero = conversionGreaterThan(
{ value: 0, fromNumericBase: 'dec' },
{ value: amount, fromNumericBase: 'hex' },
@@ -376,19 +378,29 @@ SendTransactionScreen.prototype.renderForm = function () {
this.renderGasRow(),
- this.renderMemoRow(),
+ // this.renderMemoRow(),
])
}
SendTransactionScreen.prototype.renderFooter = function () {
- const { goHome } = this.props
+ const {
+ goHome,
+ clearSend,
+ errors: { amount: amountError, to: toError },
+ } = this.props
+
+ const noErrors = amountError === null && toError === null
+ const errorClass = noErrors ? '' : '__disabled'
return h('div.send-v2__footer', [
h('button.send-v2__cancel-btn', {
- onClick: goHome,
+ onClick: () => {
+ clearSend()
+ goHome()
+ },
}, 'Cancel'),
- h('button.send-v2__next-btn', {
+ h(`button.send-v2__next-btn${errorClass}`, {
onClick: event => this.onSubmit(event),
}, 'Next'),
])
@@ -429,8 +441,16 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
signTx,
selectedToken,
toAccounts,
+ clearSend,
+ errors: { amount: amountError, to: toError },
} = this.props
+ const noErrors = amountError === null && toError === null
+
+ if (!noErrors) {
+ return
+ }
+
this.addToAddressBookIfNew(to)
const txParams = {
@@ -445,6 +465,8 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
txParams.to = to
}
+ clearSend()
+
selectedToken
? signTokenTx(selectedToken.address, to, amount, txParams)
: signTx(txParams)
diff --git a/ui/app/settings.js b/ui/app/settings.js
index 454cc95e0..786a70e7e 100644
--- a/ui/app/settings.js
+++ b/ui/app/settings.js
@@ -1,59 +1,363 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
+const { Component } = require('react')
+const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const connect = require('react-redux').connect
+const { connect } = require('react-redux')
const actions = require('./actions')
+const infuraCurrencies = require('./infura-conversion.json')
+const validUrl = require('valid-url')
+const { exportAsFile } = require('./util')
+const TabBar = require('./components/tab-bar')
+const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
-module.exports = connect(mapStateToProps)(AppSettingsPage)
+const getInfuraCurrencyOptions = () => {
+ const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
+ return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
+ })
-function mapStateToProps (state) {
- return {}
+ return sortedCurrencies.map(({ quote: { code, name } }) => {
+ return {
+ displayValue: `${code.toUpperCase()} - ${name}`,
+ key: code,
+ value: code,
+ }
+ })
}
-inherits(AppSettingsPage, Component)
-function AppSettingsPage () {
- Component.call(this)
-}
+class Settings extends Component {
+ constructor (props) {
+ super(props)
-AppSettingsPage.prototype.render = function () {
- return (
+ const { tab } = props
+ const activeTab = tab === 'info' ? 'info' : 'settings'
- h('.account-detail-section.flex-column.flex-grow', [
+ this.state = {
+ activeTab,
+ newRpc: '',
+ }
+ }
- // subtitle and nav
- h('.flex-row.flex-center', [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- onClick: this.navigateToAccounts.bind(this),
- }),
- h('h2.page-subtitle', 'Settings'),
- ]),
+ renderTabs () {
+ const { activeTab } = this.state
- h('label', {
- htmlFor: 'settings-rpc-endpoint',
- }, 'RPC Endpoint:'),
- h('input', {
- type: 'url',
- id: 'settings-rpc-endpoint',
- onKeyPress: this.onKeyPress.bind(this),
+ return h('div.settings__tabs', [
+ h(TabBar, {
+ tabs: [
+ { content: 'Settings', key: 'settings' },
+ { content: 'Info', key: 'info' },
+ ],
+ defaultTab: activeTab,
+ tabSelected: key => this.setState({ activeTab: key }),
}),
+ ])
+ }
+
+ renderCurrentConversion () {
+ const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props
+ return h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('span', 'Current Conversion'),
+ h('span.settings__content-description', `Updated ${Date(conversionDate)}`),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h(SimpleDropdown, {
+ placeholder: 'Select Currency',
+ options: getInfuraCurrencyOptions(),
+ selectedOption: currentCurrency,
+ onSelect: newCurrency => setCurrentCurrency(newCurrency),
+ }),
+ ]),
+ ]),
])
+ }
+
+ renderCurrentProvider () {
+ const { metamask: { provider = {} } } = this.props
+ let title, value, color
+
+ switch (provider.type) {
+
+ case 'mainnet':
+ title = 'Current Network'
+ value = 'Main Ethereum Network'
+ color = '#038789'
+ break
+
+ case 'ropsten':
+ title = 'Current Network'
+ value = 'Ropsten Test Network'
+ color = '#e91550'
+ break
+
+ case 'kovan':
+ title = 'Current Network'
+ value = 'Kovan Test Network'
+ color = '#690496'
+ break
+
+ case 'rinkeby':
+ title = 'Current Network'
+ value = 'Rinkeby Test Network'
+ color = '#ebb33f'
+ break
+
+ default:
+ title = 'Current RPC'
+ value = provider.rpcTarget
+ }
+
+ return h('div.settings__content-row', [
+ h('div.settings__content-item', title),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('div.settings__provider-wrapper', [
+ h('div.settings__provider-icon', { style: { background: color } }),
+ h('div', value),
+ ]),
+ ]),
+ ]),
+ ])
+ }
+
+ renderNewRpcUrl () {
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('span', 'New RPC URL'),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('input.settings__input', {
+ placeholder: 'New RPC URL',
+ onChange: event => this.setState({ newRpc: event.target.value }),
+ onKeyPress: event => {
+ if (event.key === 'Enter') {
+ this.validateRpc(this.state.newRpc)
+ }
+ },
+ }),
+ h('div.settings__rpc-save-button', {
+ onClick: event => {
+ event.preventDefault()
+ this.validateRpc(this.state.newRpc)
+ },
+ }, 'Save'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ validateRpc (newRpc) {
+ const { setRpcTarget, displayWarning } = this.props
- )
+ if (validUrl.isWebUri(newRpc)) {
+ setRpcTarget(newRpc)
+ } else {
+ const appendedRpc = `http://${newRpc}`
+
+ if (validUrl.isWebUri(appendedRpc)) {
+ displayWarning('URIs require the appropriate HTTP/HTTPS prefix.')
+ } else {
+ displayWarning('Invalid RPC URI')
+ }
+ }
+ }
+
+ renderStateLogs () {
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('div', 'State Logs'),
+ h(
+ 'div.settings__content-description',
+ 'State logs contain your public account addresses and sent transactions.'
+ ),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('button.settings__clear-button', {
+ onClick (event) {
+ exportAsFile('MetaMask State Logs', window.logState())
+ },
+ }, 'Download State Logs'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ renderSeedWords () {
+ const { revealSeedConfirmation } = this.props
+
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', 'Reveal Seed Words'),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('button.settings__clear-button.settings__clear-button--red', {
+ onClick (event) {
+ event.preventDefault()
+ revealSeedConfirmation()
+ },
+ }, 'Reveal Seed Words'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ renderSettingsContent () {
+ const { warning } = this.props
+
+ return (
+ h('div.settings__content', [
+ warning && h('div.settings__error', warning),
+ this.renderCurrentConversion(),
+ // this.renderCurrentProvider(),
+ this.renderNewRpcUrl(),
+ this.renderStateLogs(),
+ this.renderSeedWords(),
+ ])
+ )
+ }
+
+ renderLogo () {
+ return (
+ h('div.settings__info-logo-wrapper', [
+ h('img.settings__info-logo', { src: 'images/info-logo.png' }),
+ ])
+ )
+ }
+
+ renderInfoLinks () {
+ return (
+ h('div.settings__content-item.settings__content-item--without-height', [
+ h('div.settings__info-link-header', 'Links'),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/privacy.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Privacy Policy'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/terms.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Terms of Use'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/attributions.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Attributions'),
+ ]),
+ ]),
+ h('hr.settings__info-separator'),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://support.metamask.io',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Visit our Support Center'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Visit our web site'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ target: '_blank',
+ href: 'mailto:help@metamask.io?subject=Feedback',
+ }, [
+ h('span.settings__info-link', 'Email us!'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ renderInfoContent () {
+ return (
+ h('div.settings__content', [
+ h('div.settings__content-row', [
+ h('div.settings__content-item.settings__content-item--without-height', [
+ this.renderLogo(),
+ h('div.settings__info-item', [
+ h('div.settings__info-version-header', 'MetaMask Version'),
+ h('div.settings__info-version-number', '4.0.0'),
+ ]),
+ h('div.settings__info-item', [
+ h(
+ 'div.settings__info-about',
+ 'MetaMask is designed and built in California.'
+ ),
+ ]),
+ ]),
+ this.renderInfoLinks(),
+ ]),
+ ])
+ )
+ }
+
+ render () {
+ const { goHome } = this.props
+ const { activeTab } = this.state
+
+ return (
+ h('.main-container.settings', {}, [
+ h('.settings__header', [
+ h('div.settings__close-button', {
+ onClick: goHome,
+ }),
+ this.renderTabs(),
+ ]),
+
+ activeTab === 'settings'
+ ? this.renderSettingsContent()
+ : this.renderInfoContent(),
+ ])
+ )
+ }
}
-AppSettingsPage.prototype.componentDidMount = function () {
- document.querySelector('input').focus()
+Settings.propTypes = {
+ tab: PropTypes.string,
+ metamask: PropTypes.object,
+ setCurrentCurrency: PropTypes.func,
+ setRpcTarget: PropTypes.func,
+ displayWarning: PropTypes.func,
+ revealSeedConfirmation: PropTypes.func,
+ warning: PropTypes.string,
+ goHome: PropTypes.func,
}
-AppSettingsPage.prototype.onKeyPress = function (event) {
- // get submit event
- if (event.key === 'Enter') {
- // this.submitPassword(event)
+const mapStateToProps = state => {
+ return {
+ metamask: state.metamask,
+ warning: state.appState.warning,
}
}
-AppSettingsPage.prototype.navigateToAccounts = function (event) {
- event.stopPropagation()
- this.props.dispatch(actions.showAccountsPage())
+const mapDispatchToProps = dispatch => {
+ return {
+ goHome: () => dispatch(actions.goHome()),
+ setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
+ setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
+ displayWarning: warning => dispatch(actions.displayWarning(warning)),
+ revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
+ }
}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings)
diff --git a/ui/css.js b/ui/css.js
index d8f954434..0d0f60806 100644
--- a/ui/css.js
+++ b/ui/css.js
@@ -5,6 +5,7 @@ module.exports = bundleCss
var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/output/index.css'), 'utf8'),
+ 'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.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'),
}
diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js
index 341567e2f..de3f00d2d 100644
--- a/ui/lib/tx-helper.js
+++ b/ui/lib/tx-helper.js
@@ -24,4 +24,4 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMes
})
return allValues
-} \ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index ef81fe3ce..8f63c1fff 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -63,7 +63,13 @@ abi-decoder@^1.0.8:
web3 "^0.18.4"
webpack "^2.2.1"
-abstract-leveldown@2.6.3, abstract-leveldown@~2.6.0:
+abstract-leveldown@2.7.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.7.0.tgz#985052daf3d7d0ac0029dca8eb793f4cdd2a6834"
+ dependencies:
+ xtend "~4.0.0"
+
+abstract-leveldown@~2.6.0:
version "2.6.3"
resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz#1c5e8c6a5ef965ae8c35dfb3a8770c476b82c4b8"
dependencies:
@@ -157,9 +163,9 @@ amdefine@>=0.0.4:
version "1.0.1"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
-ansi-escapes@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b"
+ansi-escapes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
ansi-regex@^0.2.0, ansi-regex@^0.2.1:
version "0.2.1"
@@ -283,6 +289,13 @@ array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+array-includes@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.7.0"
+
array-initial@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.0.1.tgz#86122222a29c1ed42347f6334111afa40f8b20ec"
@@ -391,13 +404,7 @@ async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
-async-eventemitter@^0.2.2:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.3.tgz#f79f480dfda6645a97bd6142c017150d63b4e70e"
- dependencies:
- async "^2.4.0"
-
-async-eventemitter@ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c:
+async-eventemitter@^0.2.2, async-eventemitter@ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c:
version "0.2.3"
resolved "https://codeload.github.com/ahultgren/async-eventemitter/tar.gz/fa06e39e56786ba541c180061dbf2c0a5bbf951c"
dependencies:
@@ -559,6 +566,14 @@ babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
babel-runtime "^6.22.0"
babel-types "^6.24.1"
+babel-helper-builder-react-jsx@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ esutils "^2.0.2"
+
babel-helper-call-delegate@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
@@ -741,10 +756,18 @@ babel-plugin-syntax-export-extensions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
+babel-plugin-syntax-flow@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
+
babel-plugin-syntax-function-bind@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46"
+babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
+
babel-plugin-syntax-object-rest-spread@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
@@ -986,6 +1009,13 @@ babel-plugin-transform-export-extensions@^6.22.0:
babel-plugin-syntax-export-extensions "^6.8.0"
babel-runtime "^6.22.0"
+babel-plugin-transform-flow-strip-types@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
+ dependencies:
+ babel-plugin-syntax-flow "^6.18.0"
+ babel-runtime "^6.22.0"
+
babel-plugin-transform-function-bind@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97"
@@ -1000,6 +1030,34 @@ babel-plugin-transform-object-rest-spread@^6.22.0:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.26.0"
+babel-plugin-transform-react-display-name@^6.23.0:
+ version "6.25.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-react-jsx-self@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e"
+ dependencies:
+ babel-plugin-syntax-jsx "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-react-jsx-source@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6"
+ dependencies:
+ babel-plugin-syntax-jsx "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-react-jsx@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3"
+ dependencies:
+ babel-helper-builder-react-jsx "^6.24.1"
+ babel-plugin-syntax-jsx "^6.8.0"
+ babel-runtime "^6.22.0"
+
babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
@@ -1091,6 +1149,23 @@ babel-preset-es2015@^6.22.0, babel-preset-es2015@^6.24.0, babel-preset-es2015@^6
babel-plugin-transform-es2015-unicode-regex "^6.24.1"
babel-plugin-transform-regenerator "^6.24.1"
+babel-preset-flow@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d"
+ dependencies:
+ babel-plugin-transform-flow-strip-types "^6.22.0"
+
+babel-preset-react@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380"
+ dependencies:
+ babel-plugin-syntax-jsx "^6.3.13"
+ babel-plugin-transform-react-display-name "^6.23.0"
+ babel-plugin-transform-react-jsx "^6.24.1"
+ babel-plugin-transform-react-jsx-self "^6.22.0"
+ babel-plugin-transform-react-jsx-source "^6.22.0"
+ babel-preset-flow "^6.23.0"
+
babel-preset-stage-0@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a"
@@ -1799,7 +1874,11 @@ caniuse-db@^1.0.30000187, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000727"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000727.tgz#4e22593089b0f35c1b2adcfc28234493a21a4b2e"
-caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726:
+caniuse-lite@^1.0.30000718:
+ version "1.0.30000733"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000733.tgz#ebfc48254117cc0c66197a4536cb4397a6cfbccd"
+
+caniuse-lite@^1.0.30000726:
version "1.0.30000727"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000727.tgz#20c895768398ded5f98a4beab4a76c285def41d2"
@@ -1865,6 +1944,10 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
+change-emitter@^0.1.2:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
+
charm@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/charm/-/charm-1.0.2.tgz#8add367153a6d9a581331052c4090991da995e35"
@@ -1953,8 +2036,8 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
client-sw-ready-event@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/client-sw-ready-event/-/client-sw-ready-event-3.3.0.tgz#988d1045562b0c228e33d9247a6dd3ed7b276fe3"
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/client-sw-ready-event/-/client-sw-ready-event-3.4.0.tgz#ff486461769055e7748570f1aef102b8675b66d0"
cliui@^2.1.0:
version "2.1.0"
@@ -3088,7 +3171,7 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.5.0, es-abstract@^1.6.1:
+es-abstract@^1.5.0, es-abstract@^1.6.1, es-abstract@^1.7.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.2.tgz#25103263dc4decbda60e0c737ca32313518027ee"
dependencies:
@@ -3226,6 +3309,15 @@ eslint-plugin-mocha@^4.9.0:
dependencies:
ramda "^0.24.1"
+eslint-plugin-react@^7.4.0:
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz#300a95861b9729c087d362dd64abcc351a74364a"
+ dependencies:
+ doctrine "^2.0.0"
+ has "^1.0.1"
+ jsx-ast-utils "^2.0.0"
+ prop-types "^15.5.10"
+
eslint-scope@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
@@ -3234,18 +3326,18 @@ eslint-scope@^3.7.1:
estraverse "^4.1.1"
eslint@^4.0.0, eslint@^4.2.0:
- version "4.6.1"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.6.1.tgz#ddc7fc7fd70bf93205b0b3449bb16a1e9e7d4950"
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.7.1.tgz#849804136953ebe366782f9f8611e2cbd1b54681"
dependencies:
ajv "^5.2.0"
babel-code-frame "^6.22.0"
chalk "^2.1.0"
concat-stream "^1.6.0"
cross-spawn "^5.1.0"
- debug "^2.6.8"
+ debug "^3.0.1"
doctrine "^2.0.0"
eslint-scope "^3.7.1"
- espree "^3.5.0"
+ espree "^3.5.1"
esquery "^1.0.0"
estraverse "^4.2.0"
esutils "^2.0.2"
@@ -3266,7 +3358,7 @@ eslint@^4.0.0, eslint@^4.2.0:
natural-compare "^1.4.0"
optionator "^0.8.2"
path-is-inside "^1.0.2"
- pluralize "^4.0.0"
+ pluralize "^7.0.0"
progress "^2.0.0"
require-uncached "^1.0.3"
semver "^5.3.0"
@@ -3275,9 +3367,9 @@ eslint@^4.0.0, eslint@^4.2.0:
table "^4.0.1"
text-table "~0.2.0"
-espree@^3.5.0:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.0.tgz#98358625bdd055861ea27e2867ea729faf463d8d"
+espree@^3.5.1:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e"
dependencies:
acorn "^5.1.1"
acorn-jsx "^3.0.0"
@@ -3347,11 +3439,7 @@ esutils@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.0.0.tgz#8151d358e20c8acc7fb745e7472c0025fe496570"
-etag@~1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
-
-etag@~1.8.1:
+etag@~1.8.0, etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
@@ -3426,9 +3514,9 @@ eth-hd-keyring@^1.2.1:
ethereumjs-wallet "^0.6.0"
events "^1.1.1"
-eth-json-rpc-filters@^1.2.2:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-1.2.3.tgz#6ad6db134d1fc84c4d0b60f9faf19b70d300ae1e"
+eth-json-rpc-filters@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-1.2.4.tgz#716109b1cf4d0ec01f30f848d65c60c34400e975"
dependencies:
await-semaphore "^0.1.1"
eth-json-rpc-middleware "^1.0.0"
@@ -3450,9 +3538,9 @@ eth-json-rpc-middleware@^1.0.0, eth-json-rpc-middleware@^1.2.7:
promise-to-callback "^1.0.0"
tape "^4.6.3"
-eth-keyring-controller@^2.1.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-2.1.1.tgz#08129c8300f0ac6de9110e0b8d51292b5c6327e3"
+eth-keyring-controller@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-2.1.2.tgz#1af179d8fd7ff470eb91e113a0fd3a440bd66bcc"
dependencies:
bip39 "^2.4.0"
bluebird "^3.5.0"
@@ -3886,8 +3974,8 @@ express@^4.10.7:
vary "~1.1.1"
express@^4.15.5:
- version "4.16.0"
- resolved "https://registry.yarnpkg.com/express/-/express-4.16.0.tgz#b519638e4eb58e7178c81b498ef22f798cb2e255"
+ version "4.16.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0"
dependencies:
accepts "~1.3.4"
array-flatten "1.1.1"
@@ -3912,8 +4000,8 @@ express@^4.15.5:
qs "6.5.1"
range-parser "~1.2.0"
safe-buffer "5.1.1"
- send "0.16.0"
- serve-static "1.13.0"
+ send "0.16.1"
+ serve-static "1.13.1"
setprototypeof "1.1.0"
statuses "~1.3.1"
type-is "~1.6.15"
@@ -4008,9 +4096,9 @@ faye-websocket@~0.7.2:
dependencies:
websocket-driver ">=0.3.6"
-fbjs@^0.8.16:
- version "0.8.16"
- resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
+fbjs@^0.8.1, fbjs@^0.8.9:
+ version "0.8.15"
+ resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.15.tgz#4f0695fdfcc16c37c0b07facec8cb4c4091685b9"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
@@ -4020,9 +4108,9 @@ fbjs@^0.8.16:
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
-fbjs@^0.8.9:
- version "0.8.15"
- resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.15.tgz#4f0695fdfcc16c37c0b07facec8cb4c4091685b9"
+fbjs@^0.8.16:
+ version "0.8.16"
+ resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
@@ -4073,7 +4161,7 @@ fill-range@^2.1.0:
repeat-element "^1.1.2"
repeat-string "^1.5.2"
-finalhandler@1.0.4, finalhandler@~1.0.4:
+finalhandler@1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7"
dependencies:
@@ -4097,6 +4185,18 @@ finalhandler@1.1.0:
statuses "~1.3.1"
unpipe "~1.0.0"
+finalhandler@~1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.5.tgz#a701303d257a1bc82fea547a33e5ae89531723df"
+ dependencies:
+ debug "2.6.8"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.2"
+ statuses "~1.3.1"
+ unpipe "~1.0.0"
+
find-cache-dir@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
@@ -4256,11 +4356,7 @@ formatio@1.2.0, formatio@^1.2.0:
dependencies:
samsam "1.x"
-forwarded@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.1.tgz#8a4e30c640b05395399a3549c730257728048961"
-
-forwarded@~0.1.2:
+forwarded@~0.1.0, forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -4937,6 +5033,10 @@ hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+hoist-non-react-statics@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
+
hoist-non-react-statics@^2.2.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
@@ -5022,8 +5122,8 @@ http-errors@~1.3.1:
statuses "1"
http-parser-js@>=0.4.0:
- version "0.4.5"
- resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.5.tgz#a3ecf39a667481a38ca60882ab57a2db578b9970"
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.6.tgz#195273f58704c452d671076be201329dd341dc55"
http-proxy@^1.13.0, http-proxy@^1.13.1:
version "1.16.2"
@@ -5158,10 +5258,10 @@ inline-source-map@~0.6.0:
source-map "~0.5.3"
inquirer@^3.0.6:
- version "3.2.3"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.3.tgz#1c7b1731cf77b934ec47d22c9ac5aa8fe7fbe095"
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
dependencies:
- ansi-escapes "^2.0.0"
+ ansi-escapes "^3.0.0"
chalk "^2.0.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
@@ -5190,8 +5290,8 @@ insert-module-globals@^7.0.0:
xtend "^4.0.0"
interpret@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.2:
version "2.2.2"
@@ -5646,22 +5746,22 @@ json-loader@^0.5.4:
version "0.5.7"
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
-json-rpc-engine@^3.0.1, json-rpc-engine@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.1.0.tgz#09285363372857569d75f61df6591b1b0afb0758"
+json-rpc-engine@3.2.0, json-rpc-engine@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.2.0.tgz#d34dff106c8339c337a894da801f73b1f77b1bc8"
dependencies:
async "^2.0.1"
babel-preset-env "^1.3.2"
babelify "^7.3.0"
+ json-rpc-error "^2.0.0"
-json-rpc-engine@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.2.0.tgz#d34dff106c8339c337a894da801f73b1f77b1bc8"
+json-rpc-engine@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.1.0.tgz#09285363372857569d75f61df6591b1b0afb0758"
dependencies:
async "^2.0.1"
babel-preset-env "^1.3.2"
babelify "^7.3.0"
- json-rpc-error "^2.0.0"
json-rpc-engine@^3.4.0:
version "3.4.0"
@@ -5679,16 +5779,7 @@ json-rpc-error@^2.0.0:
dependencies:
inherits "^2.0.1"
-json-rpc-middleware-stream@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-1.0.0.tgz#84d0faefe123ce9cfc7a9753e6e5236b9a104522"
- dependencies:
- eth-block-tracker "^2.1.2"
- ethjs-query "^0.2.9"
- json-rpc-engine "^3.0.1"
- readable-stream "^2.3.3"
-
-json-rpc-middleware-stream@^1.0.1:
+json-rpc-middleware-stream@^1.0.0, json-rpc-middleware-stream@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-1.0.1.tgz#c9b8a005c80af32e6df8bb88e6bdd1300484a4ed"
dependencies:
@@ -5778,6 +5869,12 @@ jstransform@^10.1.0:
esprima-fb "13001.1001.0-dev-harmony-fb"
source-map "0.1.31"
+jsx-ast-utils@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f"
+ dependencies:
+ array-includes "^3.0.3"
+
just-extend@^1.1.22:
version "1.1.22"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.22.tgz#3330af756cab6a542700c64b2e4e4aa062d52fff"
@@ -6146,7 +6243,7 @@ lodash.debounce@^3.1.1:
dependencies:
lodash._getnative "^3.0.0"
-lodash.debounce@^4.0.6:
+lodash.debounce@^4.0.6, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@@ -6231,6 +6328,10 @@ lodash.mapvalues@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+
lodash.memoize@~3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
@@ -6259,6 +6360,10 @@ lodash.restparam@^3.0.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
+lodash.shuffle@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b"
+
lodash.some@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
@@ -6446,10 +6551,10 @@ mem@^1.1.0:
mimic-fn "^1.0.0"
memdown@^1.0.0:
- version "1.2.7"
- resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.2.7.tgz#64f2d6b511724c016f3cbd5a40b84349da247b0f"
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.3.1.tgz#071499332e3a74b88c3d9551b0750d61fdf0c5b7"
dependencies:
- abstract-leveldown "2.6.3"
+ abstract-leveldown "2.7.0"
functional-red-black-tree "^1.0.1"
immediate "^3.2.3"
inherits "~2.0.1"
@@ -6800,8 +6905,8 @@ mute-stream@0.0.7, mute-stream@~0.0.4:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
mz@^2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/mz/-/mz-2.6.0.tgz#c8b8521d958df0a4f2768025db69c719ee4ef1ce"
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
@@ -7315,8 +7420,8 @@ p-locate@^2.0.0:
p-limit "^1.1.0"
p-map@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
pako@~0.2.0:
version "0.2.9"
@@ -7565,9 +7670,9 @@ plur@^2.0.0, plur@^2.1.0, plur@^2.1.2:
dependencies:
irregular-plurals "^1.0.0"
-pluralize@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762"
+pluralize@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
pn@^1.0.0:
version "1.0.0"
@@ -8025,9 +8130,9 @@ react-hyperscript@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-hyperscript/-/react-hyperscript-3.0.0.tgz#3c16010b33175de6bc01fd1ebad0a16a9a6dc9ab"
-react-input-autosize@^1.1.5:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.2.0.tgz#87241071159f742123897691da6796ec33b57d05"
+react-input-autosize@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.0.1.tgz#e92190497b4026c2780ad0f2fd703c835ba03e33"
dependencies:
create-react-class "^15.5.2"
prop-types "^15.5.8"
@@ -8053,12 +8158,12 @@ react-redux@^5.0.5:
prop-types "^15.5.10"
react-select@^1.0.0-rc.2:
- version "1.0.0-rc.7"
- resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.0.0-rc.7.tgz#fab65abb7a6d51ccdde51d6ff6b594018fc208bc"
+ version "1.0.0-rc.10"
+ resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.0.0-rc.10.tgz#f137346250f9255c979fbfa21860899928772350"
dependencies:
classnames "^2.2.4"
prop-types "^15.5.8"
- react-input-autosize "^1.1.5"
+ react-input-autosize "^2.0.1"
react-simple-file-input@^2.0.0:
version "2.0.0"
@@ -8112,6 +8217,10 @@ react-transition-group@^2.2.0:
prop-types "^15.5.8"
warning "^3.0.0"
+react-trigger-change@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/react-trigger-change/-/react-trigger-change-1.0.2.tgz#af573398ecef2475362b84f8c08c07fea23914c3"
+
"react@>= 0.12.0 < 16.0.0", react@^15.0.2:
version "15.6.1"
resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
@@ -8248,6 +8357,15 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
+recompose@^0.25.0:
+ version "0.25.0"
+ resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.25.0.tgz#e2ec723692ff0fdab3d62bd22c1da69af1985acd"
+ dependencies:
+ change-emitter "^0.1.2"
+ fbjs "^0.8.1"
+ hoist-non-react-statics "^1.0.0"
+ symbol-observable "^1.0.4"
+
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@@ -8275,8 +8393,8 @@ redux@^3.0.5:
symbol-observable "^1.0.3"
regenerate@^1.2.1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
regenerator-runtime@^0.10.5:
version "0.10.5"
@@ -8355,8 +8473,8 @@ replaceall@^0.1.6:
resolved "https://registry.yarnpkg.com/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e"
replacestream@^4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/replacestream/-/replacestream-4.0.2.tgz#0c4140707e4f0323f50de044851708cf58bc37bd"
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/replacestream/-/replacestream-4.0.3.tgz#3ee5798092be364b1cdb1484308492cb3dff2f36"
dependencies:
escape-string-regexp "^1.0.3"
object-assign "^4.0.1"
@@ -8540,14 +8658,12 @@ samsam@1.x, samsam@^1.1.3:
version "1.2.1"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67"
-sandwich-expando@^1.0.5:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/sandwich-expando/-/sandwich-expando-1.1.1.tgz#83806fcca2375af8b6c30e6f52ed4f989debb165"
+sandwich-expando@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/sandwich-expando/-/sandwich-expando-1.1.3.tgz#6ba78d034c32f8bf5ab5934c214f8384614a88a5"
dependencies:
babel-preset-es2015 "^6.6.0"
babelify "^7.3.0"
- brfs "^1.4.3"
- raphael "^2.2.0"
react "^15.0.2"
react-dom "^15.0.2"
react-hyperscript "^2.4.0"
@@ -8653,9 +8769,9 @@ send@0.15.4:
range-parser "~1.2.0"
statuses "~1.3.1"
-send@0.16.0:
- version "0.16.0"
- resolved "https://registry.yarnpkg.com/send/-/send-0.16.0.tgz#16338dbb9a2ede4ad57b48420ec3b82d8e80a57b"
+send@0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
dependencies:
debug "2.6.9"
depd "~1.1.1"
@@ -8680,14 +8796,14 @@ serve-static@1.12.4:
parseurl "~1.3.1"
send "0.15.4"
-serve-static@1.13.0:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.0.tgz#810c91db800e94ba287eae6b4e06caab9fdc16f1"
+serve-static@1.13.1:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
dependencies:
encodeurl "~1.0.1"
escape-html "~1.0.3"
parseurl "~1.3.2"
- send "0.16.0"
+ send "0.16.1"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
@@ -9448,7 +9564,7 @@ sw-stream@^2.0.0:
readable-stream "^2.2.2"
through2 "^2.0.3"
-symbol-observable@^1.0.3:
+symbol-observable@^1.0.3, symbol-observable@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
@@ -9849,8 +9965,8 @@ ua-parser-js@^0.7.9:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.14.tgz#110d53fa4c3f326c121292bbeac904d2e03387ca"
uglify-es@^3.0.15:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.1.0.tgz#b4ca636bde6467e82a061ea7f2c124899d82bd2c"
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.1.1.tgz#27615a1203cd0b351d8b5bda743ac92ed482b826"
dependencies:
commander "~2.11.0"
source-map "~0.5.1"
@@ -10207,8 +10323,8 @@ web3@^0.18.4:
xmlhttprequest "*"
web3@^0.20.1:
- version "0.20.1"
- resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.1.tgz#fb262e9ad71552167a6af012fdd420de017032f0"
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.2.tgz#c54dac5fc0e377399c04c1a6ecbb12e4513278d6"
dependencies:
bignumber.js "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
crypto-js "^3.1.4"