diff options
29 files changed, 400 insertions, 72 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index af4c886bc..f4c2abd4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ ## Current Master +- 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. @@ -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/manifest.json b/app/manifest.json index 141026d10..c1f26d2ea 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.2", "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..2fe009f9a 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,6 +1,9 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize const extend = require('xtend') +const notifier = require('../lib/bug-notifier') +const log = require('loglevel') +const { version } = require('../../manifest.json') class PreferencesController { @@ -28,7 +31,12 @@ class PreferencesController { featureFlags: {}, currentLocale: opts.initLangCode, identities: {}, + lostIdentities: {}, }, opts.initState) + + this.getFirstTimeInfo = opts.getFirstTimeInfo || null + this.notifier = opts.notifier || notifier + this.store = new ObservableStore(initState) } // PUBLIC METHODS @@ -63,6 +71,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 +89,76 @@ 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: + const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts' + const firstTimeInfo = this.getFirstTimeInfo ? this.getFirstTimeInfo() : {} + this.notifier.notify(uri, { + accounts: Object.keys(newlyLost), + metadata: { + version, + firstTimeInfo, + }, + }) + .catch(log.error) + + 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 +196,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 @@ -197,7 +282,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 +300,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/bug-notifier.js b/app/scripts/lib/bug-notifier.js new file mode 100644 index 000000000..4d305b894 --- /dev/null +++ b/app/scripts/lib/bug-notifier.js @@ -0,0 +1,22 @@ +class BugNotifier { + notify (uri, message) { + return postData(uri, message) + } +} + +function postData(uri, data) { + 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 + }) +} + +const notifier = new BugNotifier() + +module.exports = notifier + 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/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 9db7e186a..80eaafd92 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -85,6 +85,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, + getFirstTimeInfo: () => initState.firstTimeInfo, }) // currency controller @@ -139,6 +140,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 +357,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), @@ -458,7 +461,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() @@ -470,6 +477,22 @@ 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() + + 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/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/package-lock.json b/package-lock.json index 943213eb2..62ada9459 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8174,16 +8174,16 @@ "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==", "dev": true, "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.2", - "ethereumjs-util": "^5.1.2", - "loglevel": "^1.5.0", - "obs-store": "^2.4.1", - "promise-filter": "^1.1.0" + "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": { @@ -8296,11 +8296,11 @@ "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": { diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 4bc16e65e..7ec98766a 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,36 @@ 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]) + metamaskController.preferencesController.notifier = { + notify: async () => { + return true + }, + } + 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 +509,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 +543,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/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/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/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/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/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/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/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/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/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/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/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; } } |