aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md26
-rw-r--r--README.md4
-rw-r--r--app/_locales/en/messages.json11
-rw-r--r--app/_locales/ja/messages.json43
-rw-r--r--app/manifest.json6
-rw-r--r--app/scripts/controllers/preferences.js81
-rw-r--r--app/scripts/controllers/user-actions.js17
-rw-r--r--app/scripts/lib/createErrorMiddleware.js1
-rw-r--r--app/scripts/lib/diagnostics-reporter.js71
-rw-r--r--app/scripts/lib/setupRaven.js2
-rw-r--r--app/scripts/metamask-controller.js38
-rw-r--r--app/scripts/migrations/026.js2
-rw-r--r--development/states/add-token.json6
-rw-r--r--development/states/confirm-sig-requests.json6
-rw-r--r--development/states/currency-localization.json6
-rw-r--r--development/states/send-edit.json6
-rw-r--r--development/states/send-new-ui.json6
-rw-r--r--development/states/tx-list-items.json6
-rw-r--r--docs/publishing.md28
-rw-r--r--mascara/src/app/first-time/index.css17
-rw-r--r--old-ui/app/components/account-dropdowns.js5
-rw-r--r--package-lock.json50
-rw-r--r--package.json2
-rw-r--r--test/e2e/metamask.spec.js7
-rw-r--r--test/integration/lib/add-token.js10
-rw-r--r--test/integration/lib/confirm-sig-requests.js6
-rw-r--r--test/integration/lib/send-new-ui.js6
-rw-r--r--test/screens/new-ui.js2
-rw-r--r--test/unit/app/controllers/metamask-controller-test.js31
-rw-r--r--ui/app/actions.js36
-rw-r--r--ui/app/app.js8
-rw-r--r--ui/app/components/account-menu/index.js7
-rw-r--r--ui/app/components/button/button.component.js23
-rw-r--r--ui/app/components/button/button.stories.js19
-rw-r--r--ui/app/components/customize-gas-modal/index.js2
-rw-r--r--ui/app/components/index.scss2
-rw-r--r--ui/app/components/loading-screen/loading-screen.component.js6
-rw-r--r--ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js54
-rw-r--r--ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js13
-rw-r--r--ui/app/components/modals/confirm-reset-account/index.js2
-rw-r--r--ui/app/components/modals/deposit-ether-modal.js2
-rw-r--r--ui/app/components/modals/edit-account-name-modal.js2
-rw-r--r--ui/app/components/modals/export-private-key-modal.js6
-rw-r--r--ui/app/components/modals/hide-token-confirmation-modal.js2
-rw-r--r--ui/app/components/modals/index.scss53
-rw-r--r--ui/app/components/modals/modal.js72
-rw-r--r--ui/app/components/modals/notification-modals/confirm-reset-account.js46
-rw-r--r--ui/app/components/modals/notification/index.js2
-rw-r--r--ui/app/components/modals/notification/notification.component.js30
-rw-r--r--ui/app/components/modals/notification/notification.container.js38
-rw-r--r--ui/app/components/modals/shapeshift-deposit-tx-modal.js2
-rw-r--r--ui/app/components/modals/transaction-confirmed/index.js2
-rw-r--r--ui/app/components/modals/transaction-confirmed/index.scss21
-rw-r--r--ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js48
-rw-r--r--ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js20
-rw-r--r--ui/app/components/modals/welcome-beta/index.js2
-rw-r--r--ui/app/components/modals/welcome-beta/welcome-beta.component.js23
-rw-r--r--ui/app/components/pages/add-token/add-token.component.js8
-rw-r--r--ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js2
-rw-r--r--ui/app/components/pages/confirm-add-token/confirm-add-token.component.js2
-rw-r--r--ui/app/components/pages/create-account/import-account/index.js2
-rw-r--r--ui/app/components/pages/create-account/import-account/json.js26
-rw-r--r--ui/app/components/pages/create-account/import-account/private-key.js29
-rw-r--r--ui/app/components/pages/create-account/index.js15
-rw-r--r--ui/app/components/pages/create-account/new-account.js4
-rw-r--r--ui/app/components/pages/keychains/reveal-seed.js6
-rw-r--r--ui/app/components/pages/settings/index.js9
-rw-r--r--ui/app/components/pages/settings/settings.js8
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.component.js22
-rw-r--r--ui/app/components/pending-tx/index.js5
-rw-r--r--ui/app/components/selected-account/index.js2
-rw-r--r--ui/app/components/selected-account/index.scss38
-rw-r--r--ui/app/components/selected-account/selected-account.component.js60
-rw-r--r--ui/app/components/selected-account/selected-account.container.js13
-rw-r--r--ui/app/components/shapeshift-form.js2
-rw-r--r--ui/app/components/signature-request.js4
-rw-r--r--ui/app/components/token-cell.js4
-rw-r--r--ui/app/components/tx-view.js28
-rw-r--r--ui/app/components/wallet-view.js12
-rw-r--r--ui/app/css/itcss/components/account-menu.scss4
-rw-r--r--ui/app/css/itcss/components/buttons.scss49
-rw-r--r--ui/app/css/itcss/components/hero-balance.scss1
-rw-r--r--ui/app/css/itcss/components/loading-overlay.scss4
-rw-r--r--ui/app/css/itcss/components/token-list.scss13
-rw-r--r--ui/app/reducers.js7
-rw-r--r--ui/app/reducers/identities.js15
-rw-r--r--ui/app/send-v2.js6
-rw-r--r--ui/app/welcome-screen.js14
88 files changed, 1031 insertions, 428 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af4c886bc..19287f046 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
## Current Master
+## 4.7.4 Tue Jun 05 2018
+
+- Add diagnostic reporting for users with multiple HD keyrings
+- Throw explicit error when selected account is unset
+
+## 4.7.3 Mon Jun 04 2018
+
+- Hide token now uses new modal
+- Indicate the current selected account on the popup account view
+- Reduce height of notice container in onboarding
+- Fixes issue where old nicknames were kept around causing errors
+
+## 4.7.2 Sun Jun 03 2018
+
+- Fix bug preventing users from logging in. Internally accounts and identities were out of sync.
+- Fix support links to point to new support system (Zendesk)
+- Fix bug in migration #26 ( moving account nicknames to preferences )
+- Clears account nicknames on restore from seedPhrase
+
+## 4.7.1 Fri Jun 01 2018
+
+- Fix bug where errors were not returned to Dapps.
+
+## 4.7.0 Wed May 30 2018
+
+- Fix Brave support
- Adds error messages when passwords don't match in onboarding flow.
- Adds modal notification if a retry in the process of being confirmed is dropped.
- New unlock screen design.
diff --git a/README.md b/README.md
index 970bd758f..70faa8856 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,9 @@ If you're a web dapp developer, we've got two types of guides for you:
## Building locally
- Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later.
- - Install local dependencies with `npm install`.
+ - Install dependencies:
+ - For node versions up to and including 9, install local dependencies with `npm install`.
+ - For node versions 10 and later, install [Yarn](https://yarnpkg.com/lang/en/docs/install/) and use `yarn install`.
- Install gulp globally with `npm install -g gulp-cli`.
- Build the project to the `./dist/` folder with `gulp build`.
- Optionally, to rebuild on file changes, run `gulp dev`.
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 4851508a3..40d362f51 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -523,6 +523,9 @@
"networks": {
"message": "Networks"
},
+ "nevermind": {
+ "message": "Nevermind"
+ },
"newAccount": {
"message": "New Account"
},
@@ -637,9 +640,15 @@
"rejected": {
"message": "Rejected"
},
+ "reset": {
+ "message": "Reset"
+ },
"resetAccount": {
"message": "Reset Account"
},
+ "resetAccountDescription": {
+ "message": "Resetting your account will clear your transaction history."
+ },
"restoreFromSeed": {
"message": "Restore account?"
},
@@ -897,7 +906,7 @@
"message": "Welcome to the New UI (Beta)"
},
"uiWelcomeMessage": {
- "message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues."
+ "message": "You are now using the new Metamask UI."
},
"unapproved": {
"message": "Unapproved"
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index 3a664ec00..75deeaddf 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -62,6 +62,9 @@
"message": " $1以上 $2以下にして下さい。",
"description": "helper for inputting hex as decimal input"
},
+ "blockiesIdenticon": {
+ "message": "Blockies Identicon を使用"
+ },
"borrowDharma": {
"message": "Dharmaで借りる(ベータ版)"
},
@@ -95,6 +98,9 @@
"confirmTransaction": {
"message": "トランザクションの確認"
},
+ "continue": {
+ "message": "続行"
+ },
"continueToCoinbase": {
"message": "Coinbaseを開く"
},
@@ -359,6 +365,9 @@
"likeToAddTokens": {
"message": "トークンを追加しますか?"
},
+ "links": {
+ "message": "リンク"
+ },
"limit": {
"message": "リミット"
},
@@ -371,12 +380,18 @@
"localhost": {
"message": "Localhost 8545"
},
+ "login": {
+ "message": "ログイン"
+ },
"logout": {
"message": "ログアウト"
},
"loose": {
"message": "外部秘密鍵"
},
+ "max": {
+ "message": "最大"
+ },
"mainnet": {
"message": "Ethereumメインネットワーク"
},
@@ -417,7 +432,7 @@
"message": "新規コントラクト"
},
"newPassword": {
- "message": "新規パスワード(最低8文字)"
+ "message": "新規パスワード(最低8文字)"
},
"newRecipient": {
"message": "新規受取人"
@@ -453,6 +468,9 @@
"message": "または",
"description": "choice between creating or importing a new account"
},
+ "password": {
+ "message": "パスワード"
+ },
"passwordMismatch": {
"message": "パスワードが一致しません。",
"description": "in password creation process, the two new password fields did not match"
@@ -474,6 +492,9 @@
"popularTokens": {
"message": "人気のトークン"
},
+ "privacyMsg": {
+ "message": "プライバシーポリシー"
+ },
"privateKey": {
"message": "秘密鍵",
"description": "select this type of file to use to import an account"
@@ -546,6 +567,12 @@
"message": "ファイルとして保存",
"description": "Account export process"
},
+ "search": {
+ "message": "検索"
+ },
+ "searchResults": {
+ "message": "検索結果"
+ },
"selectService": {
"message": "サービスを選択"
},
@@ -575,7 +602,7 @@
},
"info": {
"message": "情報"
- },
+ },
"shapeshiftBuy": {
"message": "Shapeshiftで交換"
},
@@ -609,6 +636,9 @@
"takesTooLong": {
"message": "送信に時間がかかりますか?"
},
+ "terms": {
+ "message": "利用規約"
+ },
"testFaucet": {
"message": "Faucetをテスト"
},
@@ -619,6 +649,9 @@
"message": "ShapeShiftで $1をETHにする",
"description": "system will fill in deposit type in start of message"
},
+ "token": {
+ "message": "トークン"
+ },
"tokenAddress": {
"message": "トークンアドレス"
},
@@ -690,6 +723,12 @@
"warning": {
"message": "警告"
},
+ "welcomeBack": {
+ "message": "おかえりなさい!"
+ },
+ "welcomeBeta": {
+ "message": "MetaMask ベータ版へようこそ!"
+ },
"whatsThis": {
"message": "この機能について"
},
diff --git a/app/manifest.json b/app/manifest.json
index 141026d10..e3a7fd963 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
- "version": "4.6.1",
+ "version": "4.7.4",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
@@ -68,6 +68,8 @@
"matches": [
"https://metamask.io/*"
],
- "ids": ["*"]
+ "ids": [
+ "*"
+ ]
}
} \ No newline at end of file
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index a4ff1207e..8411e3a28 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize
const extend = require('xtend')
+
class PreferencesController {
/**
@@ -28,7 +29,11 @@ class PreferencesController {
featureFlags: {},
currentLocale: opts.initLangCode,
identities: {},
+ lostIdentities: {},
}, opts.initState)
+
+ this.diagnostics = opts.diagnostics
+
this.store = new ObservableStore(initState)
}
// PUBLIC METHODS
@@ -63,6 +68,13 @@ class PreferencesController {
this.store.updateState({ currentLocale: key })
}
+ /**
+ * Updates identities to only include specified addresses. Removes identities
+ * not included in addresses array
+ *
+ * @param {string[]} addresses An array of hex addresses
+ *
+ */
setAddresses (addresses) {
const oldIdentities = this.store.getState().identities
const identities = addresses.reduce((ids, address, index) => {
@@ -74,6 +86,68 @@ class PreferencesController {
}
/**
+ * Adds addresses to the identities object without removing identities
+ *
+ * @param {string[]} addresses An array of hex addresses
+ *
+ */
+ addAddresses (addresses) {
+ const identities = this.store.getState().identities
+ addresses.forEach((address) => {
+ // skip if already exists
+ if (identities[address]) return
+ // add missing identity
+ const identityCount = Object.keys(identities).length
+ identities[address] = { name: `Account ${identityCount + 1}`, address }
+ })
+ this.store.updateState({ identities })
+ }
+
+ /*
+ * Synchronizes identity entries with known accounts.
+ * Removes any unknown identities, and returns the resulting selected address.
+ *
+ * @param {Array<string>} addresses known to the vault.
+ * @returns {Promise<string>} selectedAddress the selected address.
+ */
+ syncAddresses (addresses) {
+ let { identities, lostIdentities } = this.store.getState()
+
+ let newlyLost = {}
+ Object.keys(identities).forEach((identity) => {
+ if (!addresses.includes(identity)) {
+ newlyLost[identity] = identities[identity]
+ delete identities[identity]
+ }
+ })
+
+ // Identities are no longer present.
+ if (Object.keys(newlyLost).length > 0) {
+
+ // Notify our servers:
+ if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost)
+
+ // store lost accounts
+ for (let key in newlyLost) {
+ lostIdentities[key] = newlyLost[key]
+ }
+ }
+
+ this.store.updateState({ identities, lostIdentities })
+ this.addAddresses(addresses)
+
+ // If the selected account is no longer valid,
+ // select an arbitrary other account:
+ let selected = this.getSelectedAddress()
+ if (!addresses.includes(selected)) {
+ selected = addresses[0]
+ this.setSelectedAddress(selected)
+ }
+
+ return selected
+ }
+
+ /**
* Setter for the `selectedAddress` property
*
* @param {string} _address A new hex address for an account
@@ -111,7 +185,7 @@ class PreferencesController {
/**
* Adds a new token to the token array, or updates the token if passed an address that already exists.
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
- * @see AddedToken {@link AddedToken}
+ * @see AddedToken {@link AddedToken}
*
* @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address.
* @param {string} symbol The symbol of the token
@@ -173,6 +247,7 @@ class PreferencesController {
* @return {Promise<string>}
*/
setAccountLabel (account, label) {
+ if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account))
const address = normalizeAddress(account)
const {identities} = this.store.getState()
identities[address] = identities[address] || {}
@@ -197,7 +272,7 @@ class PreferencesController {
}
/**
- * Setter for the `currentAccountTab` property
+ * Setter for the `currentAccountTab` property
*
* @param {string} currentAccountTab Specifies the new tab to be marked as current
* @returns {Promise<void>} Promise resolves with undefined
@@ -215,7 +290,7 @@ class PreferencesController {
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
*
- * @param {string} _url The rpc url to add to the frequentRpcList.
+ * @param {string} _url The rpc url to add to the frequentRpcList.
* @returns {Promise<array>} The updated frequentRpcList.
*
*/
diff --git a/app/scripts/controllers/user-actions.js b/app/scripts/controllers/user-actions.js
new file mode 100644
index 000000000..f777054b8
--- /dev/null
+++ b/app/scripts/controllers/user-actions.js
@@ -0,0 +1,17 @@
+const MessageManager = require('./lib/message-manager')
+const PersonalMessageManager = require('./lib/personal-message-manager')
+const TypedMessageManager = require('./lib/typed-message-manager')
+
+class UserActionController {
+
+ constructor (opts = {}) {
+
+ this.messageManager = new MessageManager()
+ this.personalMessageManager = new PersonalMessageManager()
+ this.typedMessageManager = new TypedMessageManager()
+
+ }
+
+}
+
+module.exports = UserActionController
diff --git a/app/scripts/lib/createErrorMiddleware.js b/app/scripts/lib/createErrorMiddleware.js
index baed99e45..c70beddfd 100644
--- a/app/scripts/lib/createErrorMiddleware.js
+++ b/app/scripts/lib/createErrorMiddleware.js
@@ -59,6 +59,7 @@ function createErrorMiddleware ({ override = true } = {}) {
if (!error) { return done() }
sanitizeRPCError(error)
log.error(`MetaMask - RPC Error: ${error.message}`, error)
+ done()
})
}
}
diff --git a/app/scripts/lib/diagnostics-reporter.js b/app/scripts/lib/diagnostics-reporter.js
new file mode 100644
index 000000000..aa4ca6e26
--- /dev/null
+++ b/app/scripts/lib/diagnostics-reporter.js
@@ -0,0 +1,71 @@
+class DiagnosticsReporter {
+
+ constructor ({ firstTimeInfo, version }) {
+ this.firstTimeInfo = firstTimeInfo
+ this.version = version
+ }
+
+ async reportOrphans(orphans) {
+ try {
+ return await this.submit({
+ accounts: Object.keys(orphans),
+ metadata: {
+ type: 'orphans',
+ },
+ })
+ } catch (err) {
+ console.error('DiagnosticsReporter - "reportOrphans" encountered an error:')
+ console.error(err)
+ }
+ }
+
+ async reportMultipleKeyrings(rawKeyrings) {
+ try {
+ const keyrings = await Promise.all(rawKeyrings.map(async (keyring, index) => {
+ return {
+ index,
+ type: keyring.type,
+ accounts: await keyring.getAccounts(),
+ }
+ }))
+ return await this.submit({
+ accounts: [],
+ metadata: {
+ type: 'keyrings',
+ keyrings,
+ },
+ })
+ } catch (err) {
+ console.error('DiagnosticsReporter - "reportMultipleKeyrings" encountered an error:')
+ console.error(err)
+ }
+ }
+
+ async submit (message) {
+ try {
+ // add metadata
+ message.metadata.version = this.version
+ message.metadata.firstTimeInfo = this.firstTimeInfo
+ return await postData(message)
+ } catch (err) {
+ console.error('DiagnosticsReporter - "submit" encountered an error:')
+ throw err
+ }
+ }
+
+}
+
+function postData(data) {
+ const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts'
+ return fetch(uri, {
+ body: JSON.stringify(data), // must match 'Content-Type' header
+ credentials: 'same-origin', // include, same-origin, *omit
+ headers: {
+ 'content-type': 'application/json',
+ },
+ method: 'POST', // *GET, POST, PUT, DELETE, etc.
+ mode: 'cors', // no-cors, cors, *same-origin
+ })
+}
+
+module.exports = DiagnosticsReporter
diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js
index d164827ab..77aefb00a 100644
--- a/app/scripts/lib/setupRaven.js
+++ b/app/scripts/lib/setupRaven.js
@@ -66,7 +66,7 @@ function simplifyErrorMessages(report) {
function rewriteErrorMessages(report, rewriteFn) {
// rewrite top level message
- report.message = rewriteFn(report.message)
+ if (report.message) report.message = rewriteFn(report.message)
// rewrite each exception message
if (report.exception && report.exception.values) {
report.exception.values.forEach(item => {
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index a570f2567..1bb0af5ee 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -46,6 +46,7 @@ const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const cleanErrorStack = require('./lib/cleanErrorStack')
+const DiagnosticsReporter = require('./lib/diagnostics-reporter')
const log = require('loglevel')
module.exports = class MetamaskController extends EventEmitter {
@@ -64,6 +65,12 @@ module.exports = class MetamaskController extends EventEmitter {
const initState = opts.initState || {}
this.recordFirstTimeInfo(initState)
+ // metamask diagnostics reporter
+ this.diagnostics = opts.diagnostics || new DiagnosticsReporter({
+ firstTimeInfo: initState.firstTimeInfo,
+ version,
+ })
+
// platform-specific api
this.platform = opts.platform
@@ -85,6 +92,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
+ diagnostics: this.diagnostics,
})
// currency controller
@@ -139,6 +147,8 @@ module.exports = class MetamaskController extends EventEmitter {
const address = addresses[0]
this.preferencesController.setSelectedAddress(address)
}
+ // ensure preferences + identities controller know about all addresses
+ this.preferencesController.addAddresses(addresses)
this.accountTracker.syncWithAddresses(addresses)
})
@@ -354,7 +364,7 @@ module.exports = class MetamaskController extends EventEmitter {
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
// vault management
- submitPassword: nodeify(keyringController.submitPassword, keyringController),
+ submitPassword: nodeify(this.submitPassword, this),
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
@@ -456,7 +466,11 @@ module.exports = class MetamaskController extends EventEmitter {
async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire()
try {
+ // clear known identities
+ this.preferencesController.setAddresses([])
+ // create new vault
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
+ // set new identities
const accounts = await this.keyringController.getAccounts()
this.preferencesController.setAddresses(accounts)
this.selectFirstIdentity()
@@ -468,6 +482,28 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /*
+ * Submits the user's password and attempts to unlock the vault.
+ * Also synchronizes the preferencesController, to ensure its schema
+ * is up to date with known accounts once the vault is decrypted.
+ *
+ * @param {string} password - The user's password
+ * @returns {Promise<object>} - The keyringController update.
+ */
+ async submitPassword (password) {
+ await this.keyringController.submitPassword(password)
+ const accounts = await this.keyringController.getAccounts()
+
+ // verify keyrings
+ const nonSimpleKeyrings = this.keyringController.keyrings.filter(keyring => keyring.type !== 'Simple Key Pair')
+ if (nonSimpleKeyrings.length > 1 && this.diagnostics) {
+ await this.diagnostics.reportMultipleKeyrings(nonSimpleKeyrings)
+ }
+
+ await this.preferencesController.syncAddresses(accounts)
+ return this.keyringController.fullUpdate()
+ }
+
/**
* @type Identity
* @property {string} name - The account nickname.
diff --git a/app/scripts/migrations/026.js b/app/scripts/migrations/026.js
index 1b8a91a45..4e907e09c 100644
--- a/app/scripts/migrations/026.js
+++ b/app/scripts/migrations/026.js
@@ -27,7 +27,7 @@ module.exports = {
function transformState (state) {
if (!state.KeyringController || !state.PreferencesController) {
- return
+ return state
}
if (!state.KeyringController.walletNicknames) {
diff --git a/development/states/add-token.json b/development/states/add-token.json
index 9c0f16372..84ad5dd4c 100644
--- a/development/states/add-token.json
+++ b/development/states/add-token.json
@@ -75,9 +75,9 @@
{
"type": "HD Key Tree",
"accounts": [
- "fdea65c8e26263f6d9a1b5de9555d2931a33b825",
- "c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
- "2f8d4a878cfa04a6e60d46362f5644deab66572d"
+ "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
+ "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
+ "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
diff --git a/development/states/confirm-sig-requests.json b/development/states/confirm-sig-requests.json
index b51003d11..3c9caafb0 100644
--- a/development/states/confirm-sig-requests.json
+++ b/development/states/confirm-sig-requests.json
@@ -115,9 +115,9 @@
{
"type": "HD Key Tree",
"accounts": [
- "fdea65c8e26263f6d9a1b5de9555d2931a33b825",
- "c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
- "2f8d4a878cfa04a6e60d46362f5644deab66572d"
+ "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
+ "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
+ "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
diff --git a/development/states/currency-localization.json b/development/states/currency-localization.json
index 302e24c11..8c8b18a91 100644
--- a/development/states/currency-localization.json
+++ b/development/states/currency-localization.json
@@ -76,9 +76,9 @@
{
"type": "HD Key Tree",
"accounts": [
- "fdea65c8e26263f6d9a1b5de9555d2931a33b825",
- "c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
- "2f8d4a878cfa04a6e60d46362f5644deab66572d"
+ "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
+ "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
+ "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
diff --git a/development/states/send-edit.json b/development/states/send-edit.json
index ae3098ecb..0d4c27116 100644
--- a/development/states/send-edit.json
+++ b/development/states/send-edit.json
@@ -94,9 +94,9 @@
{
"type": "HD Key Tree",
"accounts": [
- "fdea65c8e26263f6d9a1b5de9555d2931a33b825",
- "c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
- "2f8d4a878cfa04a6e60d46362f5644deab66572d"
+ "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
+ "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
+ "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
diff --git a/development/states/send-new-ui.json b/development/states/send-new-ui.json
index 1297a9139..787f8023e 100644
--- a/development/states/send-new-ui.json
+++ b/development/states/send-new-ui.json
@@ -76,9 +76,9 @@
{
"type": "HD Key Tree",
"accounts": [
- "fdea65c8e26263f6d9a1b5de9555d2931a33b825",
- "c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
- "2f8d4a878cfa04a6e60d46362f5644deab66572d"
+ "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
+ "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
+ "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json
index d567e3fed..ee787aaee 100644
--- a/development/states/tx-list-items.json
+++ b/development/states/tx-list-items.json
@@ -83,9 +83,9 @@
{
"type": "HD Key Tree",
"accounts": [
- "fdea65c8e26263f6d9a1b5de9555d2931a33b825",
- "c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
- "2f8d4a878cfa04a6e60d46362f5644deab66572d"
+ "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
+ "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
+ "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
diff --git a/docs/publishing.md b/docs/publishing.md
index 3022b7eda..45662900d 100644
--- a/docs/publishing.md
+++ b/docs/publishing.md
@@ -2,18 +2,32 @@
When publishing a new version of MetaMask, we follow this procedure:
+## Preparation
+
+We try to ensure certain criteria are met before deploying:
+
+- Deploy early in the week, to give time for emergency responses to unforeseen bugs.
+- Deploy early in the day, for the same reason.
+- Make sure at least one member of the support team is "on duty" to watch for new user issues coming through the support system.
+- Roll out incrementally when possible, to a small number of users first, and gradually to more users.
+
## Incrementing Version & Changelog
Version can be automatically incremented [using our bump script](./bumping-version.md).
npm run version:bump $BUMP_TYPE` where `$BUMP_TYPE` is one of `major`, `minor`, or `patch`.
-## Publishing
+## Building
-1. `npm run dist` to generate the latest build.
-2. Publish to chrome store.
- - Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
-3. Publish to firefox addon marketplace.
-4. Post on Github releases page.
-5. `npm run announce`, post that announcement in our public places.
+While we develop on the main `develop` branch, our production version is maintained on the `master` branch.
+
+With each pull request, the @MetaMaskBot will comment with a build of that new pull request, so after bumping the version on `develop`, open a pull request against `master`, and once the pull request is reviewed and merged, you can download those builds for publication.
+
+## Publishing
+1. Publish to chrome store.
+2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
+3. Publish to [firefox addon marketplace](http://addons.mozilla.org/en-us/firefox/addon/ether-metamask).
+4. Publish to [Opera store](https://addons.opera.com/en/extensions/details/metamask/).
+5. Post on [Github releases](https://github.com/MetaMask/metamask-extension/releases) page.
+6. Run the `npm run announce` script, and post that announcement in our public places.
diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
index 25e60b84a..09e7d378d 100644
--- a/mascara/src/app/first-time/index.css
+++ b/mascara/src/app/first-time/index.css
@@ -123,10 +123,6 @@
width: calc(100vw - 80px);
}
- .unique-image {
- width: auto;
- }
-
.create-password__title,
.unique-image__title,
.tou__title,
@@ -148,7 +144,7 @@
height: 100%;
flex-direction: column;
align-items: center;
- justify-content: space-evenly;
+ justify-content: flex-start;
margin-top: 12px;
}
@@ -181,7 +177,6 @@
margin: 0 !important;
padding: 16px 20px !important;
height: 30vh !important;
- width: calc(100% - 48px) !important;
}
.backup-phrase__content-wrapper {
@@ -280,6 +275,12 @@
width: 335px;
}
+@media only screen and (max-width: 575px) {
+ .unique-image__body-text {
+ width: initial;
+ }
+}
+
.unique-image__body-text +
.unique-image__body-text,
.backup-phrase__body-text +
@@ -294,7 +295,7 @@
border-radius: 8px;
background-color: #FFFFFF;
margin: 0 142px 0 0;
- height: 334px;
+ height: 200px;
overflow-y: auto;
color: #757575;
font-family: Roboto;
@@ -679,7 +680,7 @@ button.backup-phrase__confirm-seed-option:hover {
}
.first-time-flow__input {
- width: 350px;
+ max-width: 350px;
}
.first-time-flow__button {
diff --git a/old-ui/app/components/account-dropdowns.js b/old-ui/app/components/account-dropdowns.js
index 53468a1a1..262de6601 100644
--- a/old-ui/app/components/account-dropdowns.js
+++ b/old-ui/app/components/account-dropdowns.js
@@ -23,9 +23,10 @@ class AccountDropdowns extends Component {
renderAccounts () {
const { identities, selected, keyrings } = this.props
+ const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
- return Object.keys(identities).map((key, index) => {
- const identity = identities[key]
+ return accountOrder.map((address, index) => {
+ const identity = identities[address]
const isSelected = identity.address === selected
const simpleAddress = identity.address.substring(2).toLowerCase()
diff --git a/package-lock.json b/package-lock.json
index f501b923c..dd046f089 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8169,26 +8169,28 @@
}
},
"eth-keyring-controller": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.3.tgz",
- "integrity": "sha512-5xzUeT2mq+S2GvTRnvhdZjrasZg+TqeSTH5sZRbDSXgXC9TYobFvEcK6g2wP/RkOQ3dt0xo/6/TWgwiXFfNZjw==",
- "requires": {
- "bip39": "^2.4.0",
- "bluebird": "^3.5.0",
- "browser-passworder": "^2.0.3",
- "eth-hd-keyring": "^1.2.2",
- "eth-sig-util": "^1.4.0",
- "eth-simple-keyring": "^1.2.1",
- "ethereumjs-util": "^5.1.2",
- "loglevel": "^1.5.0",
- "obs-store": "^2.4.1",
- "promise-filter": "^1.1.0"
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz",
+ "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==",
+ "dev": true,
+ "requires": {
+ "bip39": "2.4.0",
+ "bluebird": "3.5.1",
+ "browser-passworder": "2.0.3",
+ "eth-hd-keyring": "1.2.2",
+ "eth-sig-util": "1.4.2",
+ "eth-simple-keyring": "1.2.2",
+ "ethereumjs-util": "5.2.0",
+ "loglevel": "1.6.0",
+ "obs-store": "2.4.1",
+ "promise-filter": "1.1.0"
},
"dependencies": {
"babelify": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
+ "dev": true,
"requires": {
"babel-core": "^6.0.14",
"object-assign": "^4.0.0"
@@ -8198,6 +8200,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
+ "dev": true,
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
@@ -8212,6 +8215,7 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz",
"integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==",
+ "dev": true,
"requires": {
"babel-preset-es2015": "^6.22.0",
"babelify": "^7.3.0",
@@ -8287,21 +8291,23 @@
}
},
"eth-simple-keyring": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz",
- "integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.2.tgz",
+ "integrity": "sha512-uQVBYshHUOaXVoat1BpLA/QNMCr4hgdFBgwIB7rRmQ+m3vQQAseUsOM+biPDYzq6end+6LjcccElLpQaIZe6dg==",
+ "dev": true,
"requires": {
- "eth-sig-util": "^1.4.2",
- "ethereumjs-util": "^5.1.1",
- "ethereumjs-wallet": "^0.6.0",
- "events": "^1.1.1",
- "xtend": "^4.0.1"
+ "eth-sig-util": "1.4.2",
+ "ethereumjs-util": "5.2.0",
+ "ethereumjs-wallet": "0.6.0",
+ "events": "1.1.1",
+ "xtend": "4.0.1"
},
"dependencies": {
"ethereumjs-util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
+ "dev": true,
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
diff --git a/package.json b/package.json
index 0b0ac839a..ba764f45b 100644
--- a/package.json
+++ b/package.json
@@ -98,7 +98,6 @@
"eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.6",
"eth-json-rpc-infura": "^3.0.0",
- "eth-keyring-controller": "^3.1.3",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.4.2",
@@ -230,6 +229,7 @@
"eslint-plugin-mocha": "^5.0.0",
"eslint-plugin-react": "^7.4.0",
"eth-json-rpc-middleware": "^1.6.0",
+ "eth-keyring-controller": "^3.1.4",
"file-loader": "^1.1.11",
"fs-promise": "^2.0.3",
"ganache-cli": "^6.1.0",
diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js
index 8ec7de16c..a08a34d96 100644
--- a/test/e2e/metamask.spec.js
+++ b/test/e2e/metamask.spec.js
@@ -121,7 +121,14 @@ describe('Metamask popup page', function () {
await delay(300)
})
+ it('adds a second account', async function () {
+ await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div')).click()
+ await delay(300)
+ await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click()
+ })
+
it('shows account address', async function () {
+ await delay(300)
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText()
})
diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js
index e51c854d2..5a08c90cd 100644
--- a/test/integration/lib/add-token.js
+++ b/test/integration/lib/add-token.js
@@ -43,7 +43,7 @@ async function runAddTokenFlowTest (assert, done) {
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
// Cancel Add Token
- const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.page-container__footer-button')
+ const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button')
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
cancelAddTokenButton.click()
@@ -75,15 +75,15 @@ async function runAddTokenFlowTest (assert, done) {
tokenWrapper[0].click()
// Click Next button
- let nextButton = await queryAsync($, 'button.btn-primary--lg')
+ let nextButton = await queryAsync($, 'button.btn-primary.btn--large')
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
nextButton[0].click()
// Confirm Add token
const confirmAddToken = await queryAsync($, '.confirm-add-token')
assert.ok(confirmAddToken[0], 'confirm add token rendered')
- assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
- $('button.btn-primary--lg')[0].click()
+ assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
+ $('button.btn-primary.btn--large')[0].click()
// Verify added token image
let heroBalance = await queryAsync($, '.hero-balance')
@@ -120,7 +120,7 @@ async function runAddTokenFlowTest (assert, done) {
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
assert.ok(errorMessage[0], 'error rendered')
- $('button.btn-secondary--lg')[0].click()
+ $('button.btn-default.btn--large')[0].click()
// await timeout(100000)
diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js
index 3936ac5fa..d5ed7c77c 100644
--- a/test/integration/lib/confirm-sig-requests.js
+++ b/test/integration/lib/confirm-sig-requests.js
@@ -38,7 +38,7 @@ async function runConfirmSigRequestsTest(assert, done) {
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
- let confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
+ let confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton[0].click()
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@@ -47,7 +47,7 @@ async function runConfirmSigRequestsTest(assert, done) {
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/))
- confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
+ confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton[0].click()
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@@ -57,7 +57,7 @@ async function runConfirmSigRequestsTest(assert, done) {
assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!')
assert.equal(confirmSigRowValue[1].textContent, '1337')
- confirmSigSignButton = await queryAsync($, 'button.btn-primary--lg')
+ confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton[0].click()
const txView = await queryAsync($, '.tx-view')
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
index 3da3f4f95..176907926 100644
--- a/test/integration/lib/send-new-ui.js
+++ b/test/integration/lib/send-new-ui.js
@@ -129,7 +129,7 @@ async function runSendFlowTest(assert, done) {
await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
await customizeGas(assert, 500, 60000, '0.003', '$3.60 USD')
- const sendButton = await queryAsync($, 'button.btn-primary--lg.page-container__footer-button')
+ const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
sendButton[0].click()
await timeout()
@@ -169,13 +169,13 @@ async function runSendFlowTest(assert, done) {
sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0])
- const sendButtonInEdit = await queryAsync($, '.btn-primary--lg.page-container__footer-button')
+ const sendButtonInEdit = await queryAsync($, '.btn-primary.btn--large.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
selectState.val('send new ui')
reactTriggerChange(selectState[0])
- const cancelButtonInEdit = await queryAsync($, '.btn-secondary--lg.page-container__footer-button')
+ const cancelButtonInEdit = await queryAsync($, '.btn-default.btn--large.page-container__footer-button')
cancelButtonInEdit[0].click()
// sendButtonInEdit[0].click()
diff --git a/test/screens/new-ui.js b/test/screens/new-ui.js
index 65a542e49..e3ba7f6ab 100644
--- a/test/screens/new-ui.js
+++ b/test/screens/new-ui.js
@@ -163,7 +163,7 @@ async function captureAllScreens() {
await delay(300)
await captureLanguageScreenShots('metamask account detail export private key screen - password entered')
- await driver.findElement(By.css('.btn-primary--lg.export-private-key__button')).click()
+ await driver.findElement(By.css('.btn-primary.btn--large.export-private-key__button')).click()
await delay(300)
await captureLanguageScreenShots('metamask account detail export private key screen - reveal key')
diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js
index 4bc16e65e..266c3f258 100644
--- a/test/unit/app/controllers/metamask-controller-test.js
+++ b/test/unit/app/controllers/metamask-controller-test.js
@@ -45,7 +45,7 @@ describe('MetaMaskController', function () {
encryptor: {
encrypt: function (password, object) {
this.object = object
- return Promise.resolve()
+ return Promise.resolve('mock-encrypted')
},
decrypt: function () {
return Promise.resolve(this.object)
@@ -62,6 +62,31 @@ describe('MetaMaskController', function () {
sandbox.restore()
})
+ describe('submitPassword', function () {
+ const password = 'password'
+
+ beforeEach(async function () {
+ await metamaskController.createNewVaultAndKeychain(password)
+ })
+
+ it('removes any identities that do not correspond to known accounts.', async function () {
+ const fakeAddress = '0xbad0'
+ metamaskController.preferencesController.addAddresses([fakeAddress])
+ await metamaskController.submitPassword(password)
+
+ const identities = Object.keys(metamaskController.preferencesController.store.getState().identities)
+ const addresses = await metamaskController.keyringController.getAccounts()
+
+ identities.forEach((identity) => {
+ assert.ok(addresses.includes(identity), `addresses should include all IDs: ${identity}`)
+ })
+
+ addresses.forEach((address) => {
+ assert.ok(identities.includes(address), `identities should include all Addresses: ${address}`)
+ })
+ })
+ })
+
describe('#getGasPrice', function () {
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
@@ -479,7 +504,7 @@ describe('MetaMaskController', function () {
it('errors when signing a message', async function () {
await metamaskController.signPersonalMessage(personalMessages[0].msgParams)
assert.equal(metamaskPersonalMsgs[msgId].status, 'signed')
- assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b')
+ assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b')
})
})
@@ -513,7 +538,7 @@ describe('MetaMaskController', function () {
})
it('sets up controller dnode api for trusted communication', function (done) {
- streamTest = createThoughStream((chunk, enc, cb) => {
+ streamTest = createThoughStream((chunk, enc, cb) => {
assert.equal(chunk.name, 'controller')
cb()
done()
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 649f740e9..07d7bd5d1 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -503,17 +503,23 @@ function requestRevealSeedWords (password) {
}
function resetAccount () {
- return (dispatch) => {
- background.resetAccount((err, account) => {
- dispatch(actions.hideLoadingIndication())
- if (err) {
- dispatch(actions.displayWarning(err.message))
- }
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
- log.info('Transaction history reset for ' + account)
- dispatch(actions.showAccountsPage())
- })
- }
+ return new Promise((resolve, reject) => {
+ background.resetAccount((err, account) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ log.info('Transaction history reset for ' + account)
+ dispatch(actions.showAccountsPage())
+ resolve(account)
+ })
+ })
+ }
}
function addNewKeyring (type, opts) {
@@ -544,10 +550,12 @@ function importNewAccount (strategy, args) {
}
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateMetamaskState(newState))
- dispatch({
- type: actions.SHOW_ACCOUNT_DETAIL,
- value: newState.selectedAddress,
- })
+ if (newState.selectedAddress) {
+ dispatch({
+ type: actions.SHOW_ACCOUNT_DETAIL,
+ value: newState.selectedAddress,
+ })
+ }
return newState
}
}
diff --git a/ui/app/app.js b/ui/app/app.js
index aa2b24422..7005adb7f 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -99,7 +99,7 @@ class App extends Component {
} = this.props
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
- this.getConnectingLabel() : null
+ this.getConnectingLabel(loadingMessage) : null
log.debug('Main ui render function')
return (
@@ -137,7 +137,6 @@ class App extends Component {
(isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage,
- fullScreen: true,
}),
// content
@@ -211,7 +210,10 @@ class App extends Component {
}
}
- getConnectingLabel = function () {
+ getConnectingLabel = function (loadingMessage) {
+ if (loadingMessage) {
+ return loadingMessage
+ }
const { provider } = this.props
const providerName = provider.type
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index 7638995ea..f34631ca8 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -135,11 +135,12 @@ AccountMenu.prototype.renderAccounts = function () {
showAccountDetail,
} = this.props
- return Object.keys(identities).map((key, index) => {
- const identity = identities[key]
+ const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
+ return accountOrder.map((address) => {
+ const identity = identities[address]
const isSelected = identity.address === selectedAddress
- const balanceValue = accounts[key] ? accounts[key].balance : ''
+ const balanceValue = accounts[address] ? accounts[address].balance : ''
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase()
diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js
index fe3bf363c..e8e798445 100644
--- a/ui/app/components/button/button.component.js
+++ b/ui/app/components/button/button.component.js
@@ -2,20 +2,15 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-const SECONDARY = 'secondary'
+const CLASSNAME_DEFAULT = 'btn-default'
const CLASSNAME_PRIMARY = 'btn-primary'
-const CLASSNAME_PRIMARY_LARGE = 'btn-primary--lg'
const CLASSNAME_SECONDARY = 'btn-secondary'
-const CLASSNAME_SECONDARY_LARGE = 'btn-secondary--lg'
+const CLASSNAME_LARGE = 'btn--large'
-const getClassName = (type, large = false) => {
- let output = type === SECONDARY ? CLASSNAME_SECONDARY : CLASSNAME_PRIMARY
-
- if (large) {
- output += ` ${type === SECONDARY ? CLASSNAME_SECONDARY_LARGE : CLASSNAME_PRIMARY_LARGE}`
- }
-
- return output
+const typeHash = {
+ default: CLASSNAME_DEFAULT,
+ primary: CLASSNAME_PRIMARY,
+ secondary: CLASSNAME_SECONDARY,
}
class Button extends Component {
@@ -24,7 +19,11 @@ class Button extends Component {
return (
<button
- className={classnames(getClassName(type, large), className)}
+ className={classnames(
+ typeHash[type],
+ large && CLASSNAME_LARGE,
+ className
+ )}
{ ...buttonProps }
>
{ this.props.children }
diff --git a/ui/app/components/button/button.stories.js b/ui/app/components/button/button.stories.js
index d1e14e869..dec084a25 100644
--- a/ui/app/components/button/button.stories.js
+++ b/ui/app/components/button/button.stories.js
@@ -13,13 +13,21 @@ storiesOf('Button', module)
{text('text', 'Click me')}
</Button>
)
- .add('secondary', () => (
+ .add('secondary', () =>
<Button
onClick={action('clicked')}
type="secondary"
>
{text('text', 'Click me')}
</Button>
+ )
+ .add('default', () => (
+ <Button
+ onClick={action('clicked')}
+ type="default"
+ >
+ {text('text', 'Click me')}
+ </Button>
))
.add('large primary', () => (
<Button
@@ -39,3 +47,12 @@ storiesOf('Button', module)
{text('text', 'Click me')}
</Button>
))
+ .add('large default', () => (
+ <Button
+ onClick={action('clicked')}
+ type="default"
+ large
+ >
+ {text('text', 'Click me')}
+ </Button>
+ ))
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 1ff8eea87..e3529041b 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -308,7 +308,7 @@ CustomizeGasModal.prototype.render = function () {
}, [this.context.t('revert')]),
h('div.send-v2__customize-gas__buttons', [
- h('button.btn-secondary.send-v2__customize-gas__cancel', {
+ h('button.btn-default.send-v2__customize-gas__cancel', {
onClick: this.props.hideModal,
style: {
marginRight: '10px',
diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss
index e69acff63..351640f6e 100644
--- a/ui/app/components/index.scss
+++ b/ui/app/components/index.scss
@@ -1,5 +1,7 @@
@import './export-text-container/index';
+@import './selected-account/index';
+
@import './info-box/index';
@import './pages/index';
diff --git a/ui/app/components/loading-screen/loading-screen.component.js b/ui/app/components/loading-screen/loading-screen.component.js
index bce2a4aac..6b843cfee 100644
--- a/ui/app/components/loading-screen/loading-screen.component.js
+++ b/ui/app/components/loading-screen/loading-screen.component.js
@@ -1,7 +1,6 @@
const { Component } = require('react')
const h = require('react-hyperscript')
const PropTypes = require('prop-types')
-const classnames = require('classnames')
const Spinner = require('../spinner')
class LoadingScreen extends Component {
@@ -12,9 +11,7 @@ class LoadingScreen extends Component {
render () {
return (
- h('.loading-overlay', {
- className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }),
- }, [
+ h('.loading-overlay', [
h('.loading-overlay__container', [
h(Spinner, {
color: '#F7C06C',
@@ -29,7 +26,6 @@ class LoadingScreen extends Component {
LoadingScreen.propTypes = {
loadingMessage: PropTypes.string,
- fullScreen: PropTypes.bool,
}
module.exports = LoadingScreen
diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
new file mode 100644
index 000000000..14a4da62a
--- /dev/null
+++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.component.js
@@ -0,0 +1,54 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../../button'
+
+class ConfirmResetAccount extends Component {
+ static propTypes = {
+ hideModal: PropTypes.func.isRequired,
+ resetAccount: PropTypes.func.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ handleReset () {
+ this.props.resetAccount()
+ .then(() => this.props.hideModal())
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <div className="modal-container">
+ <div className="modal-container__content">
+ <div className="modal-container__title">
+ { `${t('resetAccount')}?` }
+ </div>
+ <div className="modal-container__description">
+ { t('resetAccountDescription') }
+ </div>
+ </div>
+ <div className="modal-container__footer">
+ <Button
+ type="default"
+ className="modal-container__footer-button"
+ onClick={() => this.props.hideModal()}
+ >
+ { t('nevermind') }
+ </Button>
+ <Button
+ type="secondary"
+ className="modal-container__footer-button"
+ onClick={() => this.handleReset()}
+ >
+ { t('reset') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+}
+
+export default ConfirmResetAccount
diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
new file mode 100644
index 000000000..9630a5593
--- /dev/null
+++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import ConfirmResetAccount from './confirm-reset-account.component'
+
+const { hideModal, resetAccount } = require('../../../actions')
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => dispatch(hideModal()),
+ resetAccount: () => dispatch(resetAccount()),
+ }
+}
+
+export default connect(null, mapDispatchToProps)(ConfirmResetAccount)
diff --git a/ui/app/components/modals/confirm-reset-account/index.js b/ui/app/components/modals/confirm-reset-account/index.js
new file mode 100644
index 000000000..c812ffc55
--- /dev/null
+++ b/ui/app/components/modals/confirm-reset-account/index.js
@@ -0,0 +1,2 @@
+import ConfirmResetAccount from './confirm-reset-account.container'
+module.exports = ConfirmResetAccount
diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js
index ad5f9b695..2daa7fa1d 100644
--- a/ui/app/components/modals/deposit-ether-modal.js
+++ b/ui/app/components/modals/deposit-ether-modal.js
@@ -109,7 +109,7 @@ DepositEtherModal.prototype.renderRow = function ({
]),
!hideButton && h('div.deposit-ether-modal__buy-row__button', [
- h('button.btn-primary--lg.deposit-ether-modal__deposit-button', {
+ h('button.btn-primary.btn--large.deposit-ether-modal__deposit-button', {
onClick: onButtonClick,
}, [buttonLabel]),
]),
diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js
index 5681a3cad..edced8725 100644
--- a/ui/app/components/modals/edit-account-name-modal.js
+++ b/ui/app/components/modals/edit-account-name-modal.js
@@ -9,7 +9,7 @@ const { getSelectedAccount } = require('../../selectors')
function mapStateToProps (state) {
return {
selectedAccount: getSelectedAccount(state),
- identity: state.appState.modal.modalState.identity,
+ identity: state.appState.modal.modalState.props.identity,
}
}
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js
index 447e43b7a..80ece425f 100644
--- a/ui/app/components/modals/export-private-key-modal.js
+++ b/ui/app/components/modals/export-private-key-modal.js
@@ -87,14 +87,14 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab
ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
return h('div.export-private-key-buttons', {}, [
!privateKey && this.renderButton(
- 'btn-secondary--lg export-private-key__button export-private-key__button--cancel',
+ 'btn-default btn--large export-private-key__button export-private-key__button--cancel',
() => hideModal(),
'Cancel'
),
(privateKey
- ? this.renderButton('btn-primary--lg export-private-key__button', () => hideModal(), this.context.t('done'))
- : this.renderButton('btn-primary--lg export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), this.context.t('confirm'))
+ ? this.renderButton('btn-primary btn--large export-private-key__button', () => hideModal(), this.context.t('done'))
+ : this.renderButton('btn-primary btn--large export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), this.context.t('confirm'))
),
])
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js
index 72e9c84eb..1518fa9a0 100644
--- a/ui/app/components/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/modals/hide-token-confirmation-modal.js
@@ -9,7 +9,7 @@ const Identicon = require('../identicon')
function mapStateToProps (state) {
return {
network: state.metamask.network,
- token: state.appState.modal.modalState.token,
+ token: state.appState.modal.modalState.props.token,
}
}
diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss
index ec6207f7e..ad6fe16d3 100644
--- a/ui/app/components/modals/index.scss
+++ b/ui/app/components/modals/index.scss
@@ -1 +1,52 @@
-@import './transaction-confirmed/index';
+.modal-container {
+ width: 100%;
+ height: 100%;
+ background-color: #fff;
+ display: flex;
+ flex-flow: column;
+ border-radius: 8px;
+
+ &__title {
+ font-size: 1.5rem;
+ font-weight: 500;
+ padding: 16px 0;
+ text-align: center;
+ }
+
+ &__description {
+ text-align: center;
+ font-size: .875rem;
+ }
+
+ &__content {
+ overflow-y: auto;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 32px;
+
+ @media screen and (max-width: 575px) {
+ justify-content: center;
+ padding: 28px 20px;
+ }
+ }
+
+ &__footer {
+ display: flex;
+ flex-flow: row;
+ justify-content: center;
+ border-top: 1px solid #d2d8dd;
+ padding: 16px;
+ flex: 0 0 auto;
+
+ &-button {
+ min-width: 0;
+ margin-right: 16px;
+
+ &:last-of-type {
+ margin-right: 0;
+ }
+ }
+ }
+}
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 841189277..85e85597a 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -19,8 +19,30 @@ const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js')
const HideTokenConfirmationModal = require('./hide-token-confirmation-modal')
const CustomizeGasModal = require('../customize-gas-modal')
const NotifcationModal = require('./notification-modal')
-const ConfirmResetAccount = require('./notification-modals/confirm-reset-account')
+const ConfirmResetAccount = require('./confirm-reset-account')
const TransactionConfirmed = require('./transaction-confirmed')
+const WelcomeBeta = require('./welcome-beta')
+const Notification = require('./notification')
+
+const modalContainerBaseStyle = {
+ transform: 'translate3d(-50%, 0, 0px)',
+ border: '1px solid #CCCFD1',
+ borderRadius: '8px',
+ backgroundColor: '#FFFFFF',
+ boxShadow: '0 2px 22px 0 rgba(0,0,0,0.2)',
+}
+
+const modalContainerLaptopStyle = {
+ ...modalContainerBaseStyle,
+ width: '344px',
+ top: '15%',
+}
+
+const modalContainerMobileStyle = {
+ ...modalContainerBaseStyle,
+ width: '309px',
+ top: '12.5%',
+}
const accountModalStyle = {
mobileModalStyle: {
@@ -174,18 +196,18 @@ const MODALS = {
BETA_UI_NOTIFICATION_MODAL: {
contents: [
- h(NotifcationModal, {
- header: 'uiWelcome',
- message: 'uiWelcomeMessage',
- }),
+ h(Notification, [
+ h(WelcomeBeta),
+ ]),
],
mobileModalStyle: {
- width: '95%',
- top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
+ ...modalContainerMobileStyle,
},
laptopModalStyle: {
- width: '449px',
- top: 'calc(33% + 45px)',
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
},
},
@@ -209,12 +231,13 @@ const MODALS = {
CONFIRM_RESET_ACCOUNT: {
contents: h(ConfirmResetAccount),
mobileModalStyle: {
- width: '95%',
- top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
+ ...modalContainerMobileStyle,
},
laptopModalStyle: {
- width: '473px',
- top: 'calc(33% + 45px)',
+ ...modalContainerLaptopStyle,
+ },
+ contentStyle: {
+ borderRadius: '8px',
},
},
@@ -269,31 +292,18 @@ const MODALS = {
TRANSACTION_CONFIRMED: {
disableBackdropClick: true,
contents: [
- h(TransactionConfirmed, {}, []),
+ h(Notification, [
+ h(TransactionConfirmed),
+ ]),
],
mobileModalStyle: {
- width: '100%',
- height: '100%',
- transform: 'none',
- left: '0',
- right: '0',
- margin: '0 auto',
- boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
- top: '0',
- display: 'flex',
+ ...modalContainerMobileStyle,
},
laptopModalStyle: {
- width: '344px',
- transform: 'translate3d(-50%, 0, 0px)',
- top: '15%',
- border: '1px solid #CCCFD1',
- borderRadius: '8px',
- backgroundColor: '#FFFFFF',
- boxShadow: '0 2px 22px 0 rgba(0,0,0,0.2)',
+ ...modalContainerLaptopStyle,
},
contentStyle: {
borderRadius: '8px',
- height: '100%',
},
},
diff --git a/ui/app/components/modals/notification-modals/confirm-reset-account.js b/ui/app/components/modals/notification-modals/confirm-reset-account.js
deleted file mode 100644
index 89fa9bef1..000000000
--- a/ui/app/components/modals/notification-modals/confirm-reset-account.js
+++ /dev/null
@@ -1,46 +0,0 @@
-const { Component } = require('react')
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('../../../actions')
-const NotifcationModal = require('../notification-modal')
-
-class ConfirmResetAccount extends Component {
- render () {
- const { resetAccount } = this.props
-
- return h(NotifcationModal, {
- header: 'Are you sure you want to reset account?',
- message: h('div', [
-
- h('span', `Resetting is for developer use only. This button wipes the current account's transaction history,
- which is used to calculate the current account nonce. `),
-
- h('a.notification-modal__link', {
- href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account',
- target: '_blank',
- onClick (event) { global.platform.openWindow({ url: event.target.href }) },
- }, 'Read more.'),
-
- ]),
- showCancelButton: true,
- showConfirmButton: true,
- onConfirm: resetAccount,
-
- })
- }
-}
-
-ConfirmResetAccount.propTypes = {
- resetAccount: PropTypes.func,
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- resetAccount: () => {
- dispatch(actions.resetAccount())
- },
- }
-}
-
-module.exports = connect(null, mapDispatchToProps)(ConfirmResetAccount)
diff --git a/ui/app/components/modals/notification/index.js b/ui/app/components/modals/notification/index.js
new file mode 100644
index 000000000..d60a3129b
--- /dev/null
+++ b/ui/app/components/modals/notification/index.js
@@ -0,0 +1,2 @@
+import Notification from './notification.container'
+module.exports = Notification
diff --git a/ui/app/components/modals/notification/notification.component.js b/ui/app/components/modals/notification/notification.component.js
new file mode 100644
index 000000000..1af2f3ca8
--- /dev/null
+++ b/ui/app/components/modals/notification/notification.component.js
@@ -0,0 +1,30 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import Button from '../../button'
+
+const Notification = (props, context) => {
+ return (
+ <div className="modal-container">
+ { props.children }
+ <div className="modal-container__footer">
+ <Button
+ type="primary"
+ onClick={() => props.onHide()}
+ >
+ { context.t('ok') }
+ </Button>
+ </div>
+ </div>
+ )
+}
+
+Notification.propTypes = {
+ onHide: PropTypes.func.isRequired,
+ children: PropTypes.element,
+}
+
+Notification.contextTypes = {
+ t: PropTypes.func,
+}
+
+export default Notification
diff --git a/ui/app/components/modals/notification/notification.container.js b/ui/app/components/modals/notification/notification.container.js
new file mode 100644
index 000000000..5b98714da
--- /dev/null
+++ b/ui/app/components/modals/notification/notification.container.js
@@ -0,0 +1,38 @@
+import { connect } from 'react-redux'
+import Notification from './notification.component'
+
+const { hideModal } = require('../../../actions')
+
+const mapStateToProps = state => {
+ const { appState: { modal: { modalState: { props } } } } = state
+ const { onHide } = props
+ return {
+ onHide,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ hideModal: () => dispatch(hideModal()),
+ }
+}
+
+const mergeProps = (stateProps, dispatchProps, ownProps) => {
+ const { onHide, ...otherStateProps } = stateProps
+ const { hideModal, ...otherDispatchProps } = dispatchProps
+
+ return {
+ ...otherStateProps,
+ ...otherDispatchProps,
+ ...ownProps,
+ onHide: () => {
+ hideModal()
+
+ if (onHide && typeof onHide === 'function') {
+ onHide()
+ }
+ },
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notification)
diff --git a/ui/app/components/modals/shapeshift-deposit-tx-modal.js b/ui/app/components/modals/shapeshift-deposit-tx-modal.js
index 24af5a0de..242c7b89d 100644
--- a/ui/app/components/modals/shapeshift-deposit-tx-modal.js
+++ b/ui/app/components/modals/shapeshift-deposit-tx-modal.js
@@ -8,7 +8,7 @@ const AccountModalContainer = require('./account-modal-container')
function mapStateToProps (state) {
return {
- Qr: state.appState.modal.modalState.Qr,
+ Qr: state.appState.modal.modalState.props.Qr,
}
}
diff --git a/ui/app/components/modals/transaction-confirmed/index.js b/ui/app/components/modals/transaction-confirmed/index.js
index c8db91388..cee8da7f8 100644
--- a/ui/app/components/modals/transaction-confirmed/index.js
+++ b/ui/app/components/modals/transaction-confirmed/index.js
@@ -1,2 +1,2 @@
-import TransactionConfirmed from './transaction-confirmed.container'
+import TransactionConfirmed from './transaction-confirmed.component'
module.exports = TransactionConfirmed
diff --git a/ui/app/components/modals/transaction-confirmed/index.scss b/ui/app/components/modals/transaction-confirmed/index.scss
deleted file mode 100644
index f8cd1f212..000000000
--- a/ui/app/components/modals/transaction-confirmed/index.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.transaction-confirmed {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 32px;
-
- &__title {
- font-size: 2rem;
- padding: 16px 0;
- }
-
- &__description {
- text-align: center;
- font-size: .875rem;
- line-height: 1.5rem;
- }
-
- @media screen and (max-width: 575px) {
- justify-content: center;
- }
-}
diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
index 8d3b288ae..c1c8a2976 100644
--- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
+++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js
@@ -1,42 +1,20 @@
-import React, { Component } from 'react'
+import React from 'react'
import PropTypes from 'prop-types'
-import Button from '../../button'
-class TransactionConfirmed extends Component {
- render () {
- const { t } = this.context
+const TransactionConfirmed = (props, context) => {
+ const { t } = context
- return (
- <div className="page-container page-container--full-width page-container--full-height">
- <div className="page-container__content transaction-confirmed">
- <img src="images/check-icon.svg" />
- <div className="transaction-confirmed__title">
- { `${t('confirmed')}!` }
- </div>
- <div className="transaction-confirmed__description">
- { t('initialTransactionConfirmed') }
- </div>
- </div>
- <div className="page-container__footer">
- <Button
- type="primary"
- className="page-container__footer-button"
- onClick={() => {
- this.props.hideModal()
- this.props.onHide()
- }}
- >
- { t('ok') }
- </Button>
- </div>
+ return (
+ <div className="modal-container__content">
+ <img src="images/check-icon.svg" />
+ <div className="modal-container__title">
+ { `${t('confirmed')}!` }
</div>
- )
- }
-}
-
-TransactionConfirmed.propTypes = {
- hideModal: PropTypes.func.isRequired,
- onHide: PropTypes.func.isRequired,
+ <div className="modal-container__description">
+ { t('initialTransactionConfirmed') }
+ </div>
+ </div>
+ )
}
TransactionConfirmed.contextTypes = {
diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js
deleted file mode 100644
index 63872f7f2..000000000
--- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { connect } from 'react-redux'
-import TransactionConfirmed from './transaction-confirmed.component'
-
-const { hideModal } = require('../../../actions')
-
-const mapStateToProps = state => {
- const { appState: { modal: { modalState: { props } } } } = state
- const { onHide } = props
- return {
- onHide,
- }
-}
-
-const mapDispatchToProps = dispatch => {
- return {
- hideModal: () => dispatch(hideModal()),
- }
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(TransactionConfirmed)
diff --git a/ui/app/components/modals/welcome-beta/index.js b/ui/app/components/modals/welcome-beta/index.js
new file mode 100644
index 000000000..515c9cdaf
--- /dev/null
+++ b/ui/app/components/modals/welcome-beta/index.js
@@ -0,0 +1,2 @@
+import WelcomeBeta from './welcome-beta.component'
+module.exports = WelcomeBeta
diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.component.js b/ui/app/components/modals/welcome-beta/welcome-beta.component.js
new file mode 100644
index 000000000..61571723a
--- /dev/null
+++ b/ui/app/components/modals/welcome-beta/welcome-beta.component.js
@@ -0,0 +1,23 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+const TransactionConfirmed = (props, context) => {
+ const { t } = context
+
+ return (
+ <div className="modal-container__content">
+ <div className="modal-container__title">
+ { `${t('uiWelcome')}` }
+ </div>
+ <div className="modal-container__description">
+ { t('uiWelcomeMessage') }
+ </div>
+ </div>
+ )
+}
+
+TransactionConfirmed.contextTypes = {
+ t: PropTypes.func,
+}
+
+export default TransactionConfirmed
diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js
index 0677b4317..bcb93d401 100644
--- a/ui/app/components/pages/add-token/add-token.component.js
+++ b/ui/app/components/pages/add-token/add-token.component.js
@@ -231,7 +231,7 @@ class AddToken extends Component {
<div className="add-token__custom-token-form">
<TextField
id="custom-address"
- label="Token Address"
+ label={this.context.t('tokenAddress')}
type="text"
value={customAddress}
onChange={e => this.handleCustomAddressChange(e.target.value)}
@@ -241,7 +241,7 @@ class AddToken extends Component {
/>
<TextField
id="custom-symbol"
- label="Token Symbol"
+ label={this.context.t('tokenSymbol')}
type="text"
value={customSymbol}
onChange={e => this.handleCustomSymbolChange(e.target.value)}
@@ -252,7 +252,7 @@ class AddToken extends Component {
/>
<TextField
id="custom-decimals"
- label="Decimals of Precision"
+ label={this.context.t('decimal')}
type="number"
value={customDecimals}
onChange={e => this.handleCustomDecimalsChange(e.target.value)}
@@ -323,7 +323,7 @@ class AddToken extends Component {
</div>
<div className="page-container__footer">
<Button
- type="secondary"
+ type="default"
large
className="page-container__footer-button"
onClick={() => {
diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js b/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js
index abd599b26..1611f817b 100644
--- a/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js
+++ b/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js
@@ -15,7 +15,7 @@ export default class TokenListPlaceholder extends Component {
</div>
<a
className="token-list-placeholder__link"
- href="http://metamask.helpscoutdocs.com/article/16-managing-erc20-tokens"
+ href="https://consensys.zendesk.com/hc/en-us/articles/360004135092"
target="_blank"
rel="noopener noreferrer"
>
diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
index 9db9efc37..65d654b92 100644
--- a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
+++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
@@ -87,7 +87,7 @@ export default class ConfirmAddToken extends Component {
</div>
<div className="page-container__footer">
<Button
- type="secondary"
+ type="default"
large
className="page-container__footer-button"
onClick={() => history.push(ADD_TOKEN_ROUTE)}
diff --git a/ui/app/components/pages/create-account/import-account/index.js b/ui/app/components/pages/create-account/import-account/index.js
index 52d3dcde9..e2e973af9 100644
--- a/ui/app/components/pages/create-account/import-account/index.js
+++ b/ui/app/components/pages/create-account/import-account/index.js
@@ -46,7 +46,7 @@ AccountImportSubview.prototype.render = function () {
},
onClick: () => {
global.platform.openWindow({
- url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts',
+ url: 'https://consensys.zendesk.com/hc/en-us/articles/360004180111-What-are-imported-accounts-New-UI',
})
},
}, this.context.t('here')),
diff --git a/ui/app/components/pages/create-account/import-account/json.js b/ui/app/components/pages/create-account/import-account/json.js
index 0a3314b2a..1dc2ba534 100644
--- a/ui/app/components/pages/create-account/import-account/json.js
+++ b/ui/app/components/pages/create-account/import-account/json.js
@@ -51,7 +51,7 @@ class JsonImportSubview extends Component {
h('div.new-account-create-form__buttons', {}, [
- h('button.btn-secondary.new-account-create-form__button', {
+ h('button.btn-default.new-account-create-form__button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
@@ -82,18 +82,19 @@ class JsonImportSubview extends Component {
}
createNewKeychain () {
+ const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress } = this.props
const state = this.state
if (!state) {
const message = this.context.t('validFileImport')
- return this.props.displayWarning(message)
+ return displayWarning(message)
}
const { fileContents } = state
if (!fileContents) {
const message = this.context.t('needImportFile')
- return this.props.displayWarning(message)
+ return displayWarning(message)
}
const passwordInput = document.getElementById('json-password-box')
@@ -101,12 +102,19 @@ class JsonImportSubview extends Component {
if (!password) {
const message = this.context.t('needImportPassword')
- return this.props.displayWarning(message)
+ return displayWarning(message)
}
- this.props.importNewJsonAccount([ fileContents, password ])
- // JS runtime requires caught rejections but failures are handled by Redux
- .catch()
+ importNewJsonAccount([ fileContents, password ])
+ .then(({ selectedAddress }) => {
+ if (selectedAddress) {
+ history.push(DEFAULT_ROUTE)
+ } else {
+ displayWarning('Error importing account.')
+ setSelectedAddress(firstAddress)
+ }
+ })
+ .catch(err => displayWarning(err))
}
}
@@ -114,14 +122,17 @@ JsonImportSubview.propTypes = {
error: PropTypes.string,
goHome: PropTypes.func,
displayWarning: PropTypes.func,
+ firstAddress: PropTypes.string,
importNewJsonAccount: PropTypes.func,
history: PropTypes.object,
+ setSelectedAddress: PropTypes.func,
t: PropTypes.func,
}
const mapStateToProps = state => {
return {
error: state.appState.warning,
+ firstAddress: Object.keys(state.metamask.accounts)[0],
}
}
@@ -130,6 +141,7 @@ const mapDispatchToProps = dispatch => {
goHome: () => dispatch(actions.goHome()),
displayWarning: warning => dispatch(actions.displayWarning(warning)),
importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)),
+ setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
}
}
diff --git a/ui/app/components/pages/create-account/import-account/private-key.js b/ui/app/components/pages/create-account/import-account/private-key.js
index df7ac910a..5df3777da 100644
--- a/ui/app/components/pages/create-account/import-account/private-key.js
+++ b/ui/app/components/pages/create-account/import-account/private-key.js
@@ -21,6 +21,7 @@ module.exports = compose(
function mapStateToProps (state) {
return {
error: state.appState.warning,
+ firstAddress: Object.keys(state.metamask.accounts)[0],
}
}
@@ -29,7 +30,8 @@ function mapDispatchToProps (dispatch) {
importNewAccount: (strategy, [ privateKey ]) => {
return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
},
- displayWarning: () => dispatch(actions.displayWarning(null)),
+ displayWarning: (message) => dispatch(actions.displayWarning(message || null)),
+ setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
}
}
@@ -40,7 +42,7 @@ function PrivateKeyImportView () {
}
PrivateKeyImportView.prototype.render = function () {
- const { error } = this.props
+ const { error, displayWarning } = this.props
return (
h('div.new-account-import-form__private-key', [
@@ -59,13 +61,16 @@ PrivateKeyImportView.prototype.render = function () {
h('div.new-account-import-form__buttons', {}, [
- h('button.btn-secondary--lg.new-account-create-form__button', {
- onClick: () => this.props.history.push(DEFAULT_ROUTE),
+ h('button.btn-default.btn--large.new-account-create-form__button', {
+ onClick: () => {
+ displayWarning(null)
+ this.props.history.push(DEFAULT_ROUTE)
+ },
}, [
this.context.t('cancel'),
]),
- h('button.btn-primary--lg.new-account-create-form__button', {
+ h('button.btn-primary.btn--large.new-account-create-form__button', {
onClick: () => this.createNewKeychain(),
}, [
this.context.t('import'),
@@ -88,10 +93,16 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
- const { importNewAccount, history } = this.props
+ const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props
importNewAccount('Private Key', [ privateKey ])
- // JS runtime requires caught rejections but failures are handled by Redux
- .catch()
- .then(() => history.push(DEFAULT_ROUTE))
+ .then(({ selectedAddress }) => {
+ if (selectedAddress) {
+ history.push(DEFAULT_ROUTE)
+ } else {
+ displayWarning('Error importing account.')
+ setSelectedAddress(firstAddress)
+ }
+ })
+ .catch(err => displayWarning(err))
}
diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js
index 475261253..6e3b93742 100644
--- a/ui/app/components/pages/create-account/index.js
+++ b/ui/app/components/pages/create-account/index.js
@@ -22,7 +22,9 @@ class CreateAccountPage extends Component {
}),
}),
onClick: () => history.push(NEW_ACCOUNT_ROUTE),
- }, 'Create'),
+ }, [
+ this.context.t('create'),
+ ]),
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
@@ -31,14 +33,16 @@ class CreateAccountPage extends Component {
}),
}),
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
- }, 'Import'),
+ }, [
+ this.context.t('import'),
+ ]),
])
}
render () {
return h('div.new-account', {}, [
h('div.new-account__header', [
- h('div.new-account__title', 'New Account'),
+ h('div.new-account__title', this.context.t('newAccount') ),
this.renderTabs(),
]),
h('div.new-account__form', [
@@ -62,6 +66,11 @@ class CreateAccountPage extends Component {
CreateAccountPage.propTypes = {
location: PropTypes.object,
history: PropTypes.object,
+ t: PropTypes.func,
+}
+
+CreateAccountPage.contextTypes = {
+ t: PropTypes.func,
}
const mapStateToProps = state => ({
diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js
index 03a5ee72d..9c94990e0 100644
--- a/ui/app/components/pages/create-account/new-account.js
+++ b/ui/app/components/pages/create-account/new-account.js
@@ -38,13 +38,13 @@ class NewAccountCreateForm extends Component {
h('div.new-account-create-form__buttons', {}, [
- h('button.btn-secondary--lg.new-account-create-form__button', {
+ h('button.btn-default.btn--large.new-account-create-form__button', {
onClick: () => history.push(DEFAULT_ROUTE),
}, [
this.context.t('cancel'),
]),
- h('button.btn-primary--lg.new-account-create-form__button', {
+ h('button.btn-primary.btn--large.new-account-create-form__button', {
onClick: () => {
createAccount(newAccountName || defaultAccountName)
.then(() => history.push(DEFAULT_ROUTE))
diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js
index 685c81074..7d7d3f462 100644
--- a/ui/app/components/pages/keychains/reveal-seed.js
+++ b/ui/app/components/pages/keychains/reveal-seed.js
@@ -106,10 +106,10 @@ class RevealSeedPage extends Component {
renderPasswordPromptFooter () {
return (
h('.page-container__footer', [
- h('button.btn-secondary--lg.page-container__footer-button', {
+ h('button.btn-default.btn--large.page-container__footer-button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
- h('button.btn-primary--lg.page-container__footer-button', {
+ h('button.btn-primary.btn--large.page-container__footer-button', {
onClick: event => this.handleSubmit(event),
disabled: this.state.password === '',
}, this.context.t('next')),
@@ -120,7 +120,7 @@ class RevealSeedPage extends Component {
renderRevealSeedFooter () {
return (
h('.page-container__footer', [
- h('button.btn-secondary--lg.page-container__footer-button', {
+ h('button.btn-default.btn--large.page-container__footer-button', {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('close')),
])
diff --git a/ui/app/components/pages/settings/index.js b/ui/app/components/pages/settings/index.js
index 384ae4b41..aee17e0e8 100644
--- a/ui/app/components/pages/settings/index.js
+++ b/ui/app/components/pages/settings/index.js
@@ -14,8 +14,8 @@ class Config extends Component {
return h('div.settings__tabs', [
h(TabBar, {
tabs: [
- { content: 'Settings', key: SETTINGS_ROUTE },
- { content: 'Info', key: INFO_ROUTE },
+ { content: this.context.t('settings'), key: SETTINGS_ROUTE },
+ { content: this.context.t('info'), key: INFO_ROUTE },
],
isActive: key => matchPath(location.pathname, { path: key, exact: true }),
onSelect: key => history.push(key),
@@ -54,6 +54,11 @@ class Config extends Component {
Config.propTypes = {
location: PropTypes.object,
history: PropTypes.object,
+ t: PropTypes.func,
+}
+
+Config.contextTypes = {
+ t: PropTypes.func,
}
module.exports = Config
diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js
index f58ac7ddf..ff42a13de 100644
--- a/ui/app/components/pages/settings/settings.js
+++ b/ui/app/components/pages/settings/settings.js
@@ -217,7 +217,7 @@ class Settings extends Component {
]),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
- h('button.btn-primary--lg.settings__button', {
+ h('button.btn-primary.btn--large.settings__button', {
onClick (event) {
window.logStateString((err, result) => {
if (err) {
@@ -242,7 +242,7 @@ class Settings extends Component {
h('div.settings__content-item', this.context.t('revealSeedWords')),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
- h('button.btn-primary--lg.settings__button--red', {
+ h('button.btn-primary.btn--large.settings__button--red', {
onClick: event => {
event.preventDefault()
history.push(REVEAL_SEED_ROUTE)
@@ -262,7 +262,7 @@ class Settings extends Component {
h('div.settings__content-item', this.context.t('useOldUI')),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
- h('button.btn-primary--lg.settings__button--orange', {
+ h('button.btn-primary.btn--large.settings__button--orange', {
onClick (event) {
event.preventDefault()
setFeatureFlagToBeta()
@@ -281,7 +281,7 @@ class Settings extends Component {
h('div.settings__content-item', this.context.t('resetAccount')),
h('div.settings__content-item', [
h('div.settings__content-item-col', [
- h('button.btn-primary--lg.settings__button--orange', {
+ h('button.btn-primary.btn--large.settings__button--orange', {
onClick (event) {
event.preventDefault()
showResetAccountConfirmationModal()
diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js
index b6384b32d..a1d3f9181 100644
--- a/ui/app/components/pages/unlock-page/unlock-page.component.js
+++ b/ui/app/components/pages/unlock-page/unlock-page.component.js
@@ -34,14 +34,7 @@ class UnlockPage extends Component {
}
}
- tryUnlockMetamask (password) {
- const { tryUnlockMetamask, history } = this.props
- tryUnlockMetamask(password)
- .catch(({ message }) => this.setState({ error: message }))
- .then(() => history.push(DEFAULT_ROUTE))
- }
-
- handleSubmit (event) {
+ async handleSubmit (event) {
event.preventDefault()
event.stopPropagation()
@@ -54,9 +47,14 @@ class UnlockPage extends Component {
this.setState({ error: null })
- tryUnlockMetamask(password)
- .catch(({ message }) => this.setState({ error: message }))
- .then(() => history.push(DEFAULT_ROUTE))
+ try {
+ await tryUnlockMetamask(password)
+ } catch ({ message }) {
+ this.setState({ error: message })
+ return
+ }
+
+ history.push(DEFAULT_ROUTE)
}
handleInputChange ({ target }) {
@@ -122,7 +120,7 @@ class UnlockPage extends Component {
>
<TextField
id="password"
- label="Password"
+ label={this.context.t('password')}
type="password"
value={this.state.password}
onChange={event => this.handleInputChange(event)}
diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js
index 893538bcf..3f8cd8823 100644
--- a/ui/app/components/pending-tx/index.js
+++ b/ui/app/components/pending-tx/index.js
@@ -130,7 +130,6 @@ PendingTx.prototype.render = function () {
if (isFetching) {
return h(Loading, {
- fullScreen: true,
loadingMessage: this.context.t('generatingTransaction'),
})
}
@@ -157,9 +156,7 @@ PendingTx.prototype.render = function () {
sendTransaction,
})
default:
- return h(Loading, {
- fullScreen: true,
- })
+ return h(Loading)
}
}
diff --git a/ui/app/components/selected-account/index.js b/ui/app/components/selected-account/index.js
new file mode 100644
index 000000000..eb342181f
--- /dev/null
+++ b/ui/app/components/selected-account/index.js
@@ -0,0 +1,2 @@
+import SelectedAccount from './selected-account.container'
+module.exports = SelectedAccount
diff --git a/ui/app/components/selected-account/index.scss b/ui/app/components/selected-account/index.scss
new file mode 100644
index 000000000..5339a228b
--- /dev/null
+++ b/ui/app/components/selected-account/index.scss
@@ -0,0 +1,38 @@
+.selected-account {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+
+ &__name {
+ max-width: 200px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ &__address {
+ font-size: .75rem;
+ color: $silver-chalice;
+ }
+
+ &__clickable {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 5px 15px;
+ border-radius: 10px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #e8e6e8;
+ }
+
+ &:active {
+ background-color: #d9d7da;
+ }
+ }
+}
diff --git a/ui/app/components/selected-account/selected-account.component.js b/ui/app/components/selected-account/selected-account.component.js
new file mode 100644
index 000000000..3386a4196
--- /dev/null
+++ b/ui/app/components/selected-account/selected-account.component.js
@@ -0,0 +1,60 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import copyToClipboard from 'copy-to-clipboard'
+
+const Tooltip = require('../tooltip-v2.js')
+
+const addressStripper = (address = '') => {
+ if (address.length < 4) {
+ return address
+ }
+
+ return `${address.slice(0, 4)}...${address.slice(-4)}`
+}
+
+class SelectedAccount extends Component {
+ state = {
+ copied: false,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ selectedAddress: PropTypes.string,
+ selectedIdentity: PropTypes.object,
+ }
+
+ render () {
+ const { t } = this.context
+ const { selectedAddress, selectedIdentity } = this.props
+
+ return (
+ <div className="selected-account">
+ <Tooltip
+ position="bottom"
+ title={this.state.copied ? t('copiedExclamation') : t('copyToClipboard')}
+ >
+ <div
+ className="selected-account__clickable"
+ onClick={() => {
+ this.setState({ copied: true })
+ setTimeout(() => this.setState({ copied: false }), 3000)
+ copyToClipboard(selectedAddress)
+ }}
+ >
+ <div className="selected-account__name">
+ { selectedIdentity.name }
+ </div>
+ <div className="selected-account__address">
+ { addressStripper(selectedAddress) }
+ </div>
+ </div>
+ </Tooltip>
+ </div>
+ )
+ }
+}
+
+export default SelectedAccount
diff --git a/ui/app/components/selected-account/selected-account.container.js b/ui/app/components/selected-account/selected-account.container.js
new file mode 100644
index 000000000..f9e061d15
--- /dev/null
+++ b/ui/app/components/selected-account/selected-account.container.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import SelectedAccount from './selected-account.component'
+
+const selectors = require('../../selectors')
+
+const mapStateToProps = state => {
+ return {
+ selectedAddress: selectors.getSelectedAddress(state),
+ selectedIdentity: selectors.getSelectedIdentity(state),
+ }
+}
+
+export default connect(mapStateToProps)(SelectedAccount)
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 22ab64426..93d2023b5 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -242,7 +242,7 @@ ShapeshiftForm.prototype.render = function () {
]),
- !depositAddress && h('button.btn-primary--lg.shapeshift-form__shapeshift-buy-btn', {
+ !depositAddress && h('button.btn-primary.btn--large.shapeshift-form__shapeshift-buy-btn', {
className: btnClass,
disabled: !token,
onClick: () => this.onBuyWithShapeShift(),
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
index 474fcf439..ab780dcf4 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/signature-request.js
@@ -235,12 +235,12 @@ SignatureRequest.prototype.renderFooter = function () {
}
return h('div.request-signature__footer', [
- h('button.btn-secondary--lg.request-signature__footer__cancel-button', {
+ h('button.btn-default.btn--large.request-signature__footer__cancel-button', {
onClick: event => {
cancel(event).then(() => this.props.history.push(DEFAULT_ROUTE))
},
}, this.context.t('cancel')),
- h('button.btn-primary--lg', {
+ h('button.btn-primary.btn--large', {
onClick: event => {
sign(event).then(() => this.props.history.push(DEFAULT_ROUTE))
},
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index c84117d84..4100d76a5 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -101,8 +101,8 @@ TokenCell.prototype.render = function () {
h('div.token-list-item__balance-ellipsis', null, [
h('div.token-list-item__balance-wrapper', null, [
- h('h3.token-list-item__token-balance', `${string || 0} ${symbol}`),
-
+ h('div.token-list-item__token-balance', `${string || 0}`),
+ h('div.token-list-item__token-symbol', symbol),
showFiat && h('div.token-list-item__fiat-amount', {
style: {},
}, formattedFiat),
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index 263f992c0..014497fcd 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -12,7 +12,7 @@ const { checksumAddress: toChecksumAddress } = require('../util')
const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list')
-const Identicon = require('./identicon')
+const SelectedAccount = require('./selected-account')
module.exports = compose(
withRouter,
@@ -103,7 +103,7 @@ TxView.prototype.renderButtons = function () {
}
TxView.prototype.render = function () {
- const { selectedAddress, identity, network, isMascara } = this.props
+ const { isMascara } = this.props
return h('div.tx-view.flex-column', {
style: {},
@@ -111,10 +111,12 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', {
style: {
- justifyContent: 'space-between',
+ justifyContent: 'center',
alignItems: 'center',
flex: '0 0 auto',
- margin: '10px',
+ marginBottom: '16px',
+ padding: '5px',
+ borderBottom: '1px solid #e5e5e5',
},
}, [
@@ -127,23 +129,7 @@ TxView.prototype.render = function () {
onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(),
}),
- h('.identicon-wrapper.select-none', {
- style: {
- marginLeft: '0.9em',
- },
- }, [
- h(Identicon, {
- diameter: 24,
- address: selectedAddress,
- network,
- }),
- ]),
-
- h('span.account-name', {
- style: {},
- }, [
- identity.name,
- ]),
+ h(SelectedAccount),
!isMascara && h('div.open-in-browser', {
onClick: () => global.platform.openExtensionInBrowser(),
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index 3b29dacac..da142fad8 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -36,7 +36,6 @@ function mapStateToProps (state) {
tokens: state.metamask.tokens,
keyrings: state.metamask.keyrings,
selectedAddress: selectors.getSelectedAddress(state),
- selectedIdentity: selectors.getSelectedIdentity(state),
selectedAccount: selectors.getSelectedAccount(state),
selectedTokenAddress: state.metamask.selectedTokenAddress,
}
@@ -99,21 +98,24 @@ WalletView.prototype.render = function () {
const {
responsiveDisplayClassname,
selectedAddress,
- selectedIdentity,
keyrings,
showAccountDetailModal,
sidebarOpen,
hideSidebar,
history,
+ identities,
} = this.props
// temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount)
const checksummedAddress = checksumAddress(selectedAddress)
+ if (!selectedAddress) {
+ throw new Error('selectedAddress should not be ' + String(selectedAddress))
+ }
+
const keyring = keyrings.find((kr) => {
- return kr.accounts.includes(selectedAddress) ||
- kr.accounts.includes(selectedIdentity.address)
+ return kr.accounts.includes(selectedAddress)
})
const type = keyring.type
@@ -145,7 +147,7 @@ WalletView.prototype.render = function () {
h('span.account-name', {
style: {},
}, [
- selectedIdentity.name,
+ identities[selectedAddress].name,
]),
h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')),
diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss
index 657760ab5..96fba890c 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/css/itcss/components/account-menu.scss
@@ -116,6 +116,10 @@
&__name {
color: $white;
font-size: 18px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 200px;
}
&__balance {
diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss
index 4cbed6093..f93daec04 100644
--- a/ui/app/css/itcss/components/buttons.scss
+++ b/ui/app/css/itcss/components/buttons.scss
@@ -2,10 +2,10 @@
Buttons
*/
+.btn-default,
.btn-primary,
-.btn-primary--lg,
-.btn-secondary,
-.btn-secondary--lg {
+.btn-secondary {
+ height: 44px;
background: $white;
display: flex;
justify-content: center;
@@ -20,10 +20,16 @@
width: 100%;
text-transform: uppercase;
outline: none;
+
+ &--disabled,
+ &[disabled] {
+ cursor: auto;
+ opacity: .5;
+ pointer-events: none;
+ }
}
-.btn-primary,
-.btn-primary--lg {
+.btn-primary {
color: $curious-blue;
border: 2px solid $spindle;
@@ -35,17 +41,23 @@
&:hover {
border-color: $curious-blue;
}
+}
- &--disabled,
- &[disabled] {
- cursor: auto;
- opacity: .5;
- pointer-events: none;
+.btn-secondary {
+ color: $monzo;
+ border: 2px solid lighten($monzo, 40%);
+
+ &:active {
+ background: lighten($monzo, 55%);
+ border-color: $monzo;
+ }
+
+ &:hover {
+ border-color: $monzo;
}
}
-.btn-secondary,
-.btn-secondary--lg {
+.btn-default {
color: $scorpion;
border: 2px solid $dusty-gray;
@@ -57,20 +69,9 @@
&:hover {
border-color: $scorpion;
}
-
- &--disabled,
- &[disabled] {
- cursor: auto;
- opacity: .5;
- pointer-events: none;
- }
-}
-
-.btn-primary, .btn-secondary {
- height: 44px;
}
-.btn-primary--lg, .btn-secondary--lg {
+.btn--large {
height: 54px;
}
diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss
index 69cde8a0f..09d66aedd 100644
--- a/ui/app/css/itcss/components/hero-balance.scss
+++ b/ui/app/css/itcss/components/hero-balance.scss
@@ -6,6 +6,7 @@
justify-content: flex-start;
align-items: center;
flex: 0 0 auto;
+ padding-top: 16px;
}
@media screen and (min-width: $break-large) {
diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss
index c18b7fa59..b07d6af17 100644
--- a/ui/app/css/itcss/components/loading-overlay.scss
+++ b/ui/app/css/itcss/components/loading-overlay.scss
@@ -11,8 +11,8 @@
background: rgba(255, 255, 255, .8);
@media screen and (max-width: 575px) {
- margin-top: 56px;
- height: calc(100% - 56px);
+ margin-top: 66px;
+ height: calc(100% - 66px);
}
@media screen and (min-width: 576px) {
diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss
index e8de317e3..72fda372f 100644
--- a/ui/app/css/itcss/components/token-list.scss
+++ b/ui/app/css/itcss/components/token-list.scss
@@ -14,10 +14,17 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
min-width: 0;
&__token-balance {
- font-size: 1.5rem;
+ margin-right: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ &__token-balance, &__token-symbol {
+ font-size: 1.5rem;
+ flex: 0 0 auto;
@media #{$wallet-balance-breakpoint-range} {
font-size: 95%;
@@ -66,7 +73,9 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
}
&__balance-wrapper {
- flex: 1 1 auto;
+ flex: 1;
+ flex-flow: row wrap;
+ display: flex;
min-width: 0;
}
}
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index f155b2bf3..e3a3077d9 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -4,7 +4,6 @@ const copyToClipboard = require('copy-to-clipboard')
//
// Sub-Reducers take in the complete state and return their sub-state
//
-const reduceIdentities = require('./reducers/identities')
const reduceMetamask = require('./reducers/metamask')
const reduceApp = require('./reducers/app')
const reduceLocale = require('./reducers/locale')
@@ -22,12 +21,6 @@ function rootReducer (state, action) {
}
//
- // Identities
- //
-
- state.identities = reduceIdentities(state, action)
-
- //
// MetaMask
//
diff --git a/ui/app/reducers/identities.js b/ui/app/reducers/identities.js
deleted file mode 100644
index 341a404e7..000000000
--- a/ui/app/reducers/identities.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const extend = require('xtend')
-
-module.exports = reduceIdentities
-
-function reduceIdentities (state, action) {
- // clone + defaults
- var idState = extend({
-
- }, state.identities)
-
- switch (action.type) {
- default:
- return idState
- }
-}
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 6736b5571..612f256df 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -224,7 +224,7 @@ SendTransactionScreen.prototype.renderFromRow = function () {
return h('div.send-v2__form-row', [
- h('div.send-v2__form-label', 'From:'),
+ h('div.send-v2__form-label', this.context.t('from')),
h('div.send-v2__form-field', [
h(FromDropdown, {
@@ -396,7 +396,7 @@ SendTransactionScreen.prototype.renderAmountRow = function () {
return h('div.send-v2__form-row', [
h('div.send-v2__form-label', [
- 'Amount:',
+ this.context.t('amount'),
this.renderErrorMessage('amount'),
!errors.amount && gasTotal && h('div.send-v2__amount-max', {
onClick: (event) => {
@@ -499,7 +499,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
return h('div.page-container__footer', [
h(Button, {
- type: 'secondary',
+ type: 'default',
large: true,
className: 'page-container__footer-button',
onClick: () => {
diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js
index 2fa244d9f..63512cd50 100644
--- a/ui/app/welcome-screen.js
+++ b/ui/app/welcome-screen.js
@@ -14,6 +14,11 @@ class WelcomeScreen extends Component {
closeWelcomeScreen: PropTypes.func.isRequired,
welcomeScreenSeen: PropTypes.bool,
history: PropTypes.object,
+ t: PropTypes.func,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
}
constructor (props) {
@@ -45,16 +50,15 @@ class WelcomeScreen extends Component {
height: '225',
}),
- h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'),
+ h('div.welcome-screen__info__header', this.context.t('welcomeBeta')),
- h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'),
+ h('div.welcome-screen__info__copy', this.context.t('metamaskDescription')),
- h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens,
- and serves as your bridge to decentralized applications.`),
+ h('div.welcome-screen__info__copy', this.context.t('holdEther')),
h('button.welcome-screen__button', {
onClick: this.initiateAccountCreation,
- }, 'Continue'),
+ }, this.context.t('continue')),
]),