From 5ec631cad34a31a1268c990f0b952453ce97090b Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Mon, 30 Apr 2018 18:07:25 -0400 Subject: Handle Promise rejections when importing accounts (#4142) * Silently catch import failures since errors exist in Redux state * Add comment about no-op catch statement --- ui/app/components/pages/create-account/import-account/json.js | 2 ++ ui/app/components/pages/create-account/import-account/private-key.js | 2 ++ 2 files changed, 4 insertions(+) (limited to 'ui/app/components') 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 946907a47..0a3314b2a 100644 --- a/ui/app/components/pages/create-account/import-account/json.js +++ b/ui/app/components/pages/create-account/import-account/json.js @@ -105,6 +105,8 @@ class JsonImportSubview extends Component { } this.props.importNewJsonAccount([ fileContents, password ]) + // JS runtime requires caught rejections but failures are handled by Redux + .catch() } } 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 c77612ea4..df7ac910a 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 @@ -91,5 +91,7 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { const { importNewAccount, history } = this.props importNewAccount('Private Key', [ privateKey ]) + // JS runtime requires caught rejections but failures are handled by Redux + .catch() .then(() => history.push(DEFAULT_ROUTE)) } -- cgit v1.2.3 From 6f316ca450967c161e7f231127a76494bace40a8 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 30 Apr 2018 16:36:17 -0700 Subject: network - remove setNetworkEndpoints --- ui/app/components/pages/settings/settings.js | 1 - ui/app/components/pages/unlock.js | 4 ---- 2 files changed, 5 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index bdefe56f8..e5725c0ca 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -349,7 +349,6 @@ const mapDispatchToProps = dispatch => { updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)), setFeatureFlagToBeta: () => { return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) - .then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) }, showResetAccountConfirmationModal: () => { return dispatch(actions.showModal({ name: 'CONFIRM_RESET_ACCOUNT' })) diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js index 30144b978..bbdb17306 100644 --- a/ui/app/components/pages/unlock.js +++ b/ui/app/components/pages/unlock.js @@ -8,7 +8,6 @@ const { tryUnlockMetamask, forgotPassword, markPasswordForgotten, - setNetworkEndpoints, setFeatureFlag, } = require('../../actions') const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums') @@ -146,7 +145,6 @@ class UnlockScreen extends Component { h('p.pointer', { onClick: () => { this.props.useOldInterface() - .then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)) }, style: { fontSize: '0.8em', @@ -168,7 +166,6 @@ UnlockScreen.propTypes = { isUnlocked: PropTypes.bool, t: PropTypes.func, useOldInterface: PropTypes.func, - setNetworkEndpoints: PropTypes.func, } const mapStateToProps = state => { @@ -184,7 +181,6 @@ const mapDispatchToProps = dispatch => { tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), markPasswordForgotten: () => dispatch(markPasswordForgotten()), useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')), - setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), } } -- cgit v1.2.3 From ffda954add95fc17049cd0f5386952dabfc4168b Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 30 Apr 2018 18:28:34 -0700 Subject: Fix styling of the app spinner --- ui/app/components/buy-button-subview.js | 2 +- ui/app/components/loading-screen/index.js | 2 + .../loading-screen/loading-screen.component.js | 35 ++++++++++ ui/app/components/loading.js | 34 ---------- ui/app/components/pending-tx/index.js | 2 +- ui/app/components/spinner/index.js | 2 + ui/app/components/spinner/spinner.component.js | 78 ++++++++++++++++++++++ 7 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 ui/app/components/loading-screen/index.js create mode 100644 ui/app/components/loading-screen/loading-screen.component.js delete mode 100644 ui/app/components/loading.js create mode 100644 ui/app/components/spinner/index.js create mode 100644 ui/app/components/spinner/spinner.component.js (limited to 'ui/app/components') diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index fda7c3e17..c6957d2aa 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -6,7 +6,7 @@ const connect = require('react-redux').connect const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') -const Loading = require('./loading') +const Loading = require('./loading-screen') const AccountPanel = require('./account-panel') const RadioList = require('./custom-radio-list') const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util') diff --git a/ui/app/components/loading-screen/index.js b/ui/app/components/loading-screen/index.js new file mode 100644 index 000000000..191d953f7 --- /dev/null +++ b/ui/app/components/loading-screen/index.js @@ -0,0 +1,2 @@ +const LoadingScreen = require('./loading-screen.component') +module.exports = LoadingScreen diff --git a/ui/app/components/loading-screen/loading-screen.component.js b/ui/app/components/loading-screen/loading-screen.component.js new file mode 100644 index 000000000..bce2a4aac --- /dev/null +++ b/ui/app/components/loading-screen/loading-screen.component.js @@ -0,0 +1,35 @@ +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 { + renderMessage () { + const { loadingMessage } = this.props + return loadingMessage && h('span', loadingMessage) + } + + render () { + return ( + h('.loading-overlay', { + className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), + }, [ + h('.loading-overlay__container', [ + h(Spinner, { + color: '#F7C06C', + }), + + this.renderMessage(), + ]), + ]) + ) + } +} + +LoadingScreen.propTypes = { + loadingMessage: PropTypes.string, + fullScreen: PropTypes.bool, +} + +module.exports = LoadingScreen diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js deleted file mode 100644 index b9afc550f..000000000 --- a/ui/app/components/loading.js +++ /dev/null @@ -1,34 +0,0 @@ -const { Component } = require('react') -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -const classnames = require('classnames') - -class LoadingIndicator extends Component { - renderMessage () { - const { loadingMessage } = this.props - return loadingMessage && h('span', loadingMessage) - } - - render () { - return ( - h('.loading-overlay', { - className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), - }, [ - h('.flex-center.flex-column', [ - h('img', { - src: 'images/loading.svg', - }), - - this.renderMessage(), - ]), - ]) - ) - } -} - -LoadingIndicator.propTypes = { - loadingMessage: PropTypes.string, - fullScreen: PropTypes.bool, -} - -module.exports = LoadingIndicator diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js index 6ee83ba7e..ace52748c 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -12,7 +12,7 @@ const util = require('../../util') const ConfirmSendEther = require('./confirm-send-ether') const ConfirmSendToken = require('./confirm-send-token') const ConfirmDeployContract = require('./confirm-deploy-contract') -const Loading = require('../loading') +const Loading = require('../loading-screen') const TX_TYPES = { DEPLOY_CONTRACT: 'deploy_contract', diff --git a/ui/app/components/spinner/index.js b/ui/app/components/spinner/index.js new file mode 100644 index 000000000..9589efcf0 --- /dev/null +++ b/ui/app/components/spinner/index.js @@ -0,0 +1,2 @@ +const Spinner = require('./spinner.component') +module.exports = Spinner diff --git a/ui/app/components/spinner/spinner.component.js b/ui/app/components/spinner/spinner.component.js new file mode 100644 index 000000000..b9a2eb52a --- /dev/null +++ b/ui/app/components/spinner/spinner.component.js @@ -0,0 +1,78 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const Spinner = ({ className = '', color = '#000000' }) => { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} + +Spinner.propTypes = { + className: PropTypes.string, + color: PropTypes.string, +} + +module.exports = Spinner -- cgit v1.2.3 From a1d13d45cf7451162b071e5507f1e31b12574e6e Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 2 May 2018 18:23:55 -0700 Subject: lint - cleanup some unused variables --- ui/app/components/pages/settings/settings.js | 1 - ui/app/components/pages/unlock.js | 1 - 2 files changed, 2 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index e5725c0ca..f58ac7ddf 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -12,7 +12,6 @@ const SimpleDropdown = require('../../dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { REVEAL_SEED_ROUTE } = require('../../../routes') const locales = require('../../../../../app/_locales/index.json') -const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/controllers/network/enums') const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js index bbdb17306..ac541dad7 100644 --- a/ui/app/components/pages/unlock.js +++ b/ui/app/components/pages/unlock.js @@ -15,7 +15,6 @@ const { getEnvironmentType } = require('../../../../app/scripts/lib/util') const getCaretCoordinates = require('textarea-caret') const EventEmitter = require('events').EventEmitter const Mascot = require('../mascot') -const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums') const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') class UnlockScreen extends Component { -- cgit v1.2.3 From d5759cf4a8041d1a7cfd55a7d55d8b7ecb29caca Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 3 May 2018 10:51:15 -0700 Subject: Add storybook integration --- ui/app/components/button/button.component.js | 43 ++++++++++++++++++++++++++++ ui/app/components/button/button.stories.js | 41 ++++++++++++++++++++++++++ ui/app/components/button/index.js | 2 ++ 3 files changed, 86 insertions(+) create mode 100644 ui/app/components/button/button.component.js create mode 100644 ui/app/components/button/button.stories.js create mode 100644 ui/app/components/button/index.js (limited to 'ui/app/components') diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js new file mode 100644 index 000000000..7769e4875 --- /dev/null +++ b/ui/app/components/button/button.component.js @@ -0,0 +1,43 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const classnames = require('classnames') + +const SECONDARY = 'secondary' +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 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 +} + +class Button extends Component { + render () { + const { type, large, className, ...buttonProps } = this.props + + return ( + h('button', { + className: classnames(getClassName(type, large), className), + ...buttonProps, + }, this.props.children) + ) + } +} + +Button.propTypes = { + type: PropTypes.string, + large: PropTypes.bool, + className: PropTypes.string, + children: PropTypes.string, +} + +module.exports = Button + diff --git a/ui/app/components/button/button.stories.js b/ui/app/components/button/button.stories.js new file mode 100644 index 000000000..d1e14e869 --- /dev/null +++ b/ui/app/components/button/button.stories.js @@ -0,0 +1,41 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { action } from '@storybook/addon-actions' +import Button from './' +import { text } from '@storybook/addon-knobs/react' + +storiesOf('Button', module) + .add('primary', () => + + ) + .add('secondary', () => ( + + )) + .add('large primary', () => ( + + )) + .add('large secondary', () => ( + + )) diff --git a/ui/app/components/button/index.js b/ui/app/components/button/index.js new file mode 100644 index 000000000..3dc7d1eea --- /dev/null +++ b/ui/app/components/button/index.js @@ -0,0 +1,2 @@ +const Button = require('./button.component') +module.exports = Button -- cgit v1.2.3 From 2381c0e0f461304265279155176fa655e2eb97b4 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 10 May 2018 16:51:26 -0700 Subject: Add new unlock screen design --- .../components/app-header/app-header.component.js | 106 +++++++++++ .../components/app-header/app-header.container.js | 38 ++++ ui/app/components/app-header/index.js | 2 + .../export-text-container.scss | 2 +- ui/app/components/pages/unlock-page/index.js | 2 + .../pages/unlock-page/unlock-page.component.js | 182 +++++++++++++++++++ .../pages/unlock-page/unlock-page.container.js | 33 ++++ .../components/pages/unlock-page/unlock-page.scss | 51 ++++++ ui/app/components/pages/unlock.js | 194 --------------------- ui/app/components/text-field/index.js | 2 + .../components/text-field/text-field.component.js | 54 ++++++ ui/app/components/wallet-view.js | 6 +- 12 files changed, 476 insertions(+), 196 deletions(-) create mode 100644 ui/app/components/app-header/app-header.component.js create mode 100644 ui/app/components/app-header/app-header.container.js create mode 100644 ui/app/components/app-header/index.js create mode 100644 ui/app/components/pages/unlock-page/index.js create mode 100644 ui/app/components/pages/unlock-page/unlock-page.component.js create mode 100644 ui/app/components/pages/unlock-page/unlock-page.container.js create mode 100644 ui/app/components/pages/unlock-page/unlock-page.scss delete mode 100644 ui/app/components/pages/unlock.js create mode 100644 ui/app/components/text-field/index.js create mode 100644 ui/app/components/text-field/text-field.component.js (limited to 'ui/app/components') diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app-header/app-header.component.js new file mode 100644 index 000000000..cf36e0d79 --- /dev/null +++ b/ui/app/components/app-header/app-header.component.js @@ -0,0 +1,106 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../../app/scripts/lib/enums') +const { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes') +const Identicon = require('../identicon') +const NetworkIndicator = require('../network') + +class AppHeader extends Component { + static propTypes = { + history: PropTypes.object, + location: PropTypes.object, + network: PropTypes.string, + provider: PropTypes.object, + networkDropdownOpen: PropTypes.bool, + showNetworkDropdown: PropTypes.func, + hideNetworkDropdown: PropTypes.func, + toggleAccountMenu: PropTypes.func, + selectedAddress: PropTypes.string, + isUnlocked: PropTypes.bool, + } + + static contextTypes = { + t: PropTypes.func, + } + + handleNetworkIndicatorClick (event) { + event.preventDefault() + event.stopPropagation() + + const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props + + return networkDropdownOpen === false + ? showNetworkDropdown() + : hideNetworkDropdown() + } + + renderAccountMenu () { + const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props + + return isUnlocked && ( +
+ +
+ ) + } + + render () { + const { + network, + provider, + history, + location, + isUnlocked, + } = this.props + + if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { + return null + } + + return ( +
+
+
history.push(DEFAULT_ROUTE)} + > + +
+

{ this.context.t('appName') }

+
+ { this.context.t('beta') } +
+
+
+
+
+ this.handleNetworkIndicatorClick(event)} + disabled={location.pathname === CONFIRM_TRANSACTION_ROUTE} + /> +
+ { this.renderAccountMenu() } +
+
+
+ ) + } +} + +export default AppHeader diff --git a/ui/app/components/app-header/app-header.container.js b/ui/app/components/app-header/app-header.container.js new file mode 100644 index 000000000..30d3f8cc4 --- /dev/null +++ b/ui/app/components/app-header/app-header.container.js @@ -0,0 +1,38 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' + +import AppHeader from './app-header.component' +const actions = require('../../actions') + +const mapStateToProps = state => { + const { appState, metamask } = state + const { networkDropdownOpen } = appState + const { + network, + provider, + selectedAddress, + isUnlocked, + } = metamask + + return { + networkDropdownOpen, + network, + provider, + selectedAddress, + isUnlocked, + } +} + +const mapDispatchToProps = dispatch => { + return { + showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), + hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), + toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(AppHeader) diff --git a/ui/app/components/app-header/index.js b/ui/app/components/app-header/index.js new file mode 100644 index 000000000..daa31f621 --- /dev/null +++ b/ui/app/components/app-header/index.js @@ -0,0 +1,2 @@ +import AppHeader from './app-header.container' +module.exports = AppHeader diff --git a/ui/app/components/export-text-container/export-text-container.scss b/ui/app/components/export-text-container/export-text-container.scss index a42de8233..975d62f70 100644 --- a/ui/app/components/export-text-container/export-text-container.scss +++ b/ui/app/components/export-text-container/export-text-container.scss @@ -37,7 +37,7 @@ display: flex; justify-content: center; align-items: center; - font-size: 14px; + font-size: 12px; cursor: pointer; color: $curious-blue; diff --git a/ui/app/components/pages/unlock-page/index.js b/ui/app/components/pages/unlock-page/index.js new file mode 100644 index 000000000..be80cde4f --- /dev/null +++ b/ui/app/components/pages/unlock-page/index.js @@ -0,0 +1,2 @@ +import UnlockPage from './unlock-page.container' +module.exports = UnlockPage diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js new file mode 100644 index 000000000..e655b2cc3 --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -0,0 +1,182 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from 'material-ui/Button' +import TextField from '../../text-field' + +const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums') +const { getEnvironmentType } = require('../../../../../app/scripts/lib/util') +const getCaretCoordinates = require('textarea-caret') +const EventEmitter = require('events').EventEmitter +const Mascot = require('../../mascot') +const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes') + +class UnlockPage extends Component { + static contextTypes = { + t: PropTypes.func, + } + + constructor (props) { + super(props) + + this.state = { + password: '', + error: null, + } + + this.animationEventEmitter = new EventEmitter() + } + + componentWillMount () { + const { isUnlocked, history } = this.props + + if (isUnlocked) { + history.push(DEFAULT_ROUTE) + } + } + + tryUnlockMetamask (password) { + const { tryUnlockMetamask, history } = this.props + tryUnlockMetamask(password) + .then(() => history.push(DEFAULT_ROUTE)) + .catch(({ message }) => this.setState({ error: message })) + } + + handleSubmit (event) { + event.preventDefault() + event.stopPropagation() + + const { password } = this.state + const { tryUnlockMetamask, history } = this.props + + if (password === '') { + return + } + + this.setState({ error: null }) + + tryUnlockMetamask(password) + .then(() => history.push(DEFAULT_ROUTE)) + .catch(({ message }) => this.setState({ error: message })) + } + + handleInputChange ({ target }) { + this.setState({ password: target.value, error: null }) + + // tell mascot to look at page action + const element = target + const boundingRect = element.getBoundingClientRect() + const coordinates = getCaretCoordinates(element, element.selectionEnd) + this.animationEventEmitter.emit('point', { + x: boundingRect.left + coordinates.left - element.scrollLeft, + y: boundingRect.top + coordinates.top - element.scrollTop, + }) + } + + renderSubmitButton () { + const style = { + backgroundColor: '#f7861c', + color: 'white', + marginTop: '20px', + height: '60px', + fontWeight: '400', + boxShadow: 'none', + borderRadius: '4px', + } + + return ( + + ) + } + + render () { + const { error } = this.state + + return ( +
+
+
+ +
+

+ { this.context.t('welcomeBack') } +

+
{ this.context.t('unlockMessage') }
+
this.handleSubmit(event)} + > + this.handleInputChange(event)} + error={error} + autoFocus + margin="normal" + autoComplete="current-password" + fullWidth + /> + + { this.renderSubmitButton() } +
+
{ + this.props.markPasswordForgotten() + this.props.history.push(RESTORE_VAULT_ROUTE) + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser() + } + }} + > + { this.context.t('restoreFromSeed') } +
+
{ + this.props.markPasswordForgotten() + this.props.history.push(RESTORE_VAULT_ROUTE) + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser() + } + }} + > + { this.context.t('importUsingSeed') } +
+
+
+
+ ) + } +} + +UnlockPage.propTypes = { + forgotPassword: PropTypes.func, + tryUnlockMetamask: PropTypes.func, + markPasswordForgotten: PropTypes.func, + history: PropTypes.object, + isUnlocked: PropTypes.bool, + t: PropTypes.func, + useOldInterface: PropTypes.func, + setNetworkEndpoints: PropTypes.func, +} + +export default UnlockPage diff --git a/ui/app/components/pages/unlock-page/unlock-page.container.js b/ui/app/components/pages/unlock-page/unlock-page.container.js new file mode 100644 index 000000000..9788a18ea --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.container.js @@ -0,0 +1,33 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' + +const { + tryUnlockMetamask, + forgotPassword, + markPasswordForgotten, + setNetworkEndpoints, +} = require('../../../actions') + +import UnlockPage from './unlock-page.component' + +const mapStateToProps = state => { + const { metamask: { isUnlocked } } = state + return { + isUnlocked, + } +} + +const mapDispatchToProps = dispatch => { + return { + forgotPassword: () => dispatch(forgotPassword()), + tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), + markPasswordForgotten: () => dispatch(markPasswordForgotten()), + setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(UnlockPage) diff --git a/ui/app/components/pages/unlock-page/unlock-page.scss b/ui/app/components/pages/unlock-page/unlock-page.scss new file mode 100644 index 000000000..47b6537e7 --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.scss @@ -0,0 +1,51 @@ +.unlock-page { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 357px; + padding: 30px; + font-weight: 400; + color: $silver-chalice; + + &__container { + background: $white; + display: flex; + align-self: stretch; + justify-content: center; + flex: 1 0 auto; + } + + &__mascot-container { + margin-top: 24px; + } + + &__title { + margin-top: 5px; + font-size: 2rem; + font-weight: 800; + color: $tundora; + } + + &__form { + width: 100%; + margin-top: 40px; + } + + &__links { + margin-top: 25px; + width: 100%; + } + + &__link { + cursor: pointer; + + &--import { + color: $ecstasy; + } + + &--use-classic { + margin-top: 10px; + } + } +} diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js deleted file mode 100644 index 30144b978..000000000 --- a/ui/app/components/pages/unlock.js +++ /dev/null @@ -1,194 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const connect = require('../../metamask-connect') -const h = require('react-hyperscript') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { - tryUnlockMetamask, - forgotPassword, - markPasswordForgotten, - setNetworkEndpoints, - setFeatureFlag, -} = require('../../actions') -const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums') -const { getEnvironmentType } = require('../../../../app/scripts/lib/util') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter -const Mascot = require('../mascot') -const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums') -const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') - -class UnlockScreen extends Component { - constructor (props) { - super(props) - - this.state = { - error: null, - } - - this.animationEventEmitter = new EventEmitter() - } - - componentWillMount () { - const { isUnlocked, history } = this.props - - if (isUnlocked) { - history.push(DEFAULT_ROUTE) - } - } - - componentDidMount () { - const passwordBox = document.getElementById('password-box') - - if (passwordBox) { - passwordBox.focus() - } - } - - tryUnlockMetamask (password) { - const { tryUnlockMetamask, history } = this.props - tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) - .catch(({ message }) => this.setState({ error: message })) - } - - onSubmit (event) { - const input = document.getElementById('password-box') - const password = input.value - this.tryUnlockMetamask(password) - } - - onKeyPress (event) { - if (event.key === 'Enter') { - this.submitPassword(event) - } - } - - submitPassword (event) { - var element = event.target - var password = element.value - // reset input - element.value = '' - this.tryUnlockMetamask(password) - } - - inputChanged (event) { - // tell mascot to look at page action - var element = event.target - var boundingRect = element.getBoundingClientRect() - var coordinates = getCaretCoordinates(element, element.selectionEnd) - this.animationEventEmitter.emit('point', { - x: boundingRect.left + coordinates.left - element.scrollLeft, - y: boundingRect.top + coordinates.top - element.scrollTop, - }) - } - - render () { - const { error } = this.state - return ( - h('.unlock-screen', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, this.props.t('appName')), - - h('input.large-input', { - type: 'password', - id: 'password-box', - placeholder: 'enter password', - style: { - background: 'white', - }, - onKeyPress: this.onKeyPress.bind(this), - onInput: this.inputChanged.bind(this), - }), - - h('.error', { - style: { - display: error ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, error), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, this.props.t('login')), - - h('p.pointer', { - onClick: () => { - this.props.markPasswordForgotten() - this.props.history.push(RESTORE_VAULT_ROUTE) - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser() - } - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, this.props.t('restoreFromSeed')), - - h('p.pointer', { - onClick: () => { - this.props.useOldInterface() - .then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)) - }, - style: { - fontSize: '0.8em', - color: '#aeaeae', - textDecoration: 'underline', - marginTop: '32px', - }, - }, this.props.t('classicInterface')), - ]) - ) - } -} - -UnlockScreen.propTypes = { - forgotPassword: PropTypes.func, - tryUnlockMetamask: PropTypes.func, - markPasswordForgotten: PropTypes.func, - history: PropTypes.object, - isUnlocked: PropTypes.bool, - t: PropTypes.func, - useOldInterface: PropTypes.func, - setNetworkEndpoints: PropTypes.func, -} - -const mapStateToProps = state => { - const { metamask: { isUnlocked } } = state - return { - isUnlocked, - } -} - -const mapDispatchToProps = dispatch => { - return { - forgotPassword: () => dispatch(forgotPassword()), - tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), - markPasswordForgotten: () => dispatch(markPasswordForgotten()), - useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')), - setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), - } -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(UnlockScreen) diff --git a/ui/app/components/text-field/index.js b/ui/app/components/text-field/index.js new file mode 100644 index 000000000..171caf7a4 --- /dev/null +++ b/ui/app/components/text-field/index.js @@ -0,0 +1,2 @@ +import TextField from './text-field.component' +module.exports = TextField diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js new file mode 100644 index 000000000..4a02f76d8 --- /dev/null +++ b/ui/app/components/text-field/text-field.component.js @@ -0,0 +1,54 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { withStyles } from 'material-ui/styles' +import { default as MaterialTextField } from 'material-ui/TextField' + +const styles = { + cssLabel: { + '&$cssFocused': { + color: '#aeaeae', + }, + fontWeight: '400', + color: '#aeaeae', + }, + cssFocused: {}, + cssUnderline: { + '&:after': { + backgroundColor: '#f7861c', + }, + }, +} + +const TextField = props => { + const { error, classes, ...textFieldProps } = props + + return ( + + ) +} + +TextField.defaultProps = { + error: null, +} + +TextField.propTypes = { + error: PropTypes.string, + classes: PropTypes.object, +} + +export default withStyles(styles)(TextField) diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 9e430f87b..3b29dacac 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -102,6 +102,7 @@ WalletView.prototype.render = function () { selectedIdentity, keyrings, showAccountDetailModal, + sidebarOpen, hideSidebar, history, } = this.props @@ -182,7 +183,10 @@ WalletView.prototype.render = function () { h(TokenList), h('button.btn-primary.wallet-view__add-token-button', { - onClick: () => history.push(ADD_TOKEN_ROUTE), + onClick: () => { + history.push(ADD_TOKEN_ROUTE) + sidebarOpen && hideSidebar() + }, }, this.context.t('addToken')), ]) } -- cgit v1.2.3 From 0301b33a48fea3c345e2e49fa53693856770a254 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 10 May 2018 19:23:44 -0700 Subject: Add TextField component to storybook --- .../pages/unlock-page/unlock-page.component.js | 1 - .../components/pages/unlock-page/unlock-page.scss | 2 +- ui/app/components/text-field/text-field.stories.js | 24 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 ui/app/components/text-field/text-field.stories.js (limited to 'ui/app/components') 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 e655b2cc3..d5e2a3224 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -128,7 +128,6 @@ class UnlockPage extends Component { onChange={event => this.handleInputChange(event)} error={error} autoFocus - margin="normal" autoComplete="current-password" fullWidth /> diff --git a/ui/app/components/pages/unlock-page/unlock-page.scss b/ui/app/components/pages/unlock-page/unlock-page.scss index 47b6537e7..3d44bd037 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.scss +++ b/ui/app/components/pages/unlock-page/unlock-page.scss @@ -29,7 +29,7 @@ &__form { width: 100%; - margin-top: 40px; + margin: 56px 0 8px; } &__links { diff --git a/ui/app/components/text-field/text-field.stories.js b/ui/app/components/text-field/text-field.stories.js new file mode 100644 index 000000000..ee3e5faaf --- /dev/null +++ b/ui/app/components/text-field/text-field.stories.js @@ -0,0 +1,24 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import TextField from './' + +storiesOf('TextField', module) + .add('text', () => + + ) + .add('password', () => + + ) + .add('error', () => + + ) -- cgit v1.2.3 From 0bcfbc15446b01b3b87233715cd3ead42d2730e4 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 12 May 2018 20:53:40 -0700 Subject: Add error message when passwords don't match in first time flow. Change input field styling in first time flow --- ui/app/components/text-field/text-field.component.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'ui/app/components') diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js index 4a02f76d8..6fd3b82b4 100644 --- a/ui/app/components/text-field/text-field.component.js +++ b/ui/app/components/text-field/text-field.component.js @@ -8,6 +8,9 @@ const styles = { '&$cssFocused': { color: '#aeaeae', }, + '&$cssError': { + color: '#aeaeae', + }, fontWeight: '400', color: '#aeaeae', }, @@ -17,6 +20,7 @@ const styles = { backgroundColor: '#f7861c', }, }, + cssError: {}, } const TextField = props => { @@ -30,6 +34,7 @@ const TextField = props => { FormLabelClasses: { root: classes.cssLabel, focused: classes.cssFocused, + error: classes.cssError, }, }} InputProps={{ -- cgit v1.2.3 From 5561937773b4e59e3df9df385693680e17e6b8c0 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 14 May 2018 14:34:10 -0700 Subject: Fix account and network dropdowns in confirm screen --- .../components/app-header/app-header.component.js | 44 +++++++++++++++++++--- ui/app/components/pending-tx/confirm-send-ether.js | 14 ++++++- 2 files changed, 52 insertions(+), 6 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/app-header/app-header.component.js b/ui/app/components/app-header/app-header.component.js index cf36e0d79..62b04562a 100644 --- a/ui/app/components/app-header/app-header.component.js +++ b/ui/app/components/app-header/app-header.component.js @@ -1,9 +1,13 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' +import { matchPath } from 'react-router-dom' -const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../../app/scripts/lib/enums') -const { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes') +const { + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_POPUP, +} = require('../../../../app/scripts/lib/enums') +const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes') const Identicon = require('../identicon') const NetworkIndicator = require('../network') @@ -36,13 +40,23 @@ class AppHeader extends Component { : hideNetworkDropdown() } + isConfirming () { + const { location } = this.props + + return Boolean(matchPath(location.pathname, { + path: CONFIRM_TRANSACTION_ROUTE, exact: false, + })) + } + renderAccountMenu () { const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props return isUnlocked && (
this.isConfirming() || toggleAccountMenu()} > Date: Wed, 16 May 2018 12:29:57 -0700 Subject: ui - remove files accidently added by bad merge --- ui/app/components/pages/unlock.js | 189 -------------------------------------- 1 file changed, 189 deletions(-) delete mode 100644 ui/app/components/pages/unlock.js (limited to 'ui/app/components') diff --git a/ui/app/components/pages/unlock.js b/ui/app/components/pages/unlock.js deleted file mode 100644 index ac541dad7..000000000 --- a/ui/app/components/pages/unlock.js +++ /dev/null @@ -1,189 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const connect = require('../../metamask-connect') -const h = require('react-hyperscript') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { - tryUnlockMetamask, - forgotPassword, - markPasswordForgotten, - setFeatureFlag, -} = require('../../actions') -const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums') -const { getEnvironmentType } = require('../../../../app/scripts/lib/util') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter -const Mascot = require('../mascot') -const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes') - -class UnlockScreen extends Component { - constructor (props) { - super(props) - - this.state = { - error: null, - } - - this.animationEventEmitter = new EventEmitter() - } - - componentWillMount () { - const { isUnlocked, history } = this.props - - if (isUnlocked) { - history.push(DEFAULT_ROUTE) - } - } - - componentDidMount () { - const passwordBox = document.getElementById('password-box') - - if (passwordBox) { - passwordBox.focus() - } - } - - tryUnlockMetamask (password) { - const { tryUnlockMetamask, history } = this.props - tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) - .catch(({ message }) => this.setState({ error: message })) - } - - onSubmit (event) { - const input = document.getElementById('password-box') - const password = input.value - this.tryUnlockMetamask(password) - } - - onKeyPress (event) { - if (event.key === 'Enter') { - this.submitPassword(event) - } - } - - submitPassword (event) { - var element = event.target - var password = element.value - // reset input - element.value = '' - this.tryUnlockMetamask(password) - } - - inputChanged (event) { - // tell mascot to look at page action - var element = event.target - var boundingRect = element.getBoundingClientRect() - var coordinates = getCaretCoordinates(element, element.selectionEnd) - this.animationEventEmitter.emit('point', { - x: boundingRect.left + coordinates.left - element.scrollLeft, - y: boundingRect.top + coordinates.top - element.scrollTop, - }) - } - - render () { - const { error } = this.state - return ( - h('.unlock-screen', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, this.props.t('appName')), - - h('input.large-input', { - type: 'password', - id: 'password-box', - placeholder: 'enter password', - style: { - background: 'white', - }, - onKeyPress: this.onKeyPress.bind(this), - onInput: this.inputChanged.bind(this), - }), - - h('.error', { - style: { - display: error ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, error), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, this.props.t('login')), - - h('p.pointer', { - onClick: () => { - this.props.markPasswordForgotten() - this.props.history.push(RESTORE_VAULT_ROUTE) - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser() - } - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, this.props.t('restoreFromSeed')), - - h('p.pointer', { - onClick: () => { - this.props.useOldInterface() - }, - style: { - fontSize: '0.8em', - color: '#aeaeae', - textDecoration: 'underline', - marginTop: '32px', - }, - }, this.props.t('classicInterface')), - ]) - ) - } -} - -UnlockScreen.propTypes = { - forgotPassword: PropTypes.func, - tryUnlockMetamask: PropTypes.func, - markPasswordForgotten: PropTypes.func, - history: PropTypes.object, - isUnlocked: PropTypes.bool, - t: PropTypes.func, - useOldInterface: PropTypes.func, -} - -const mapStateToProps = state => { - const { metamask: { isUnlocked } } = state - return { - isUnlocked, - } -} - -const mapDispatchToProps = dispatch => { - return { - forgotPassword: () => dispatch(forgotPassword()), - tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), - markPasswordForgotten: () => dispatch(markPasswordForgotten()), - useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')), - } -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(UnlockScreen) -- cgit v1.2.3 From ff91ef96ef1f71c22dd66ea4140b1b211c033641 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 12:35:43 -0700 Subject: ui - unlock - remove setNetworkEndpoints calls from new unlock screen --- ui/app/components/pages/unlock-page/unlock-page.component.js | 1 - ui/app/components/pages/unlock-page/unlock-page.container.js | 2 -- 2 files changed, 3 deletions(-) (limited to 'ui/app/components') 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 d5e2a3224..0976d9506 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -175,7 +175,6 @@ UnlockPage.propTypes = { isUnlocked: PropTypes.bool, t: PropTypes.func, useOldInterface: PropTypes.func, - setNetworkEndpoints: PropTypes.func, } export default UnlockPage diff --git a/ui/app/components/pages/unlock-page/unlock-page.container.js b/ui/app/components/pages/unlock-page/unlock-page.container.js index 9788a18ea..18fed9b2e 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.container.js +++ b/ui/app/components/pages/unlock-page/unlock-page.container.js @@ -6,7 +6,6 @@ const { tryUnlockMetamask, forgotPassword, markPasswordForgotten, - setNetworkEndpoints, } = require('../../../actions') import UnlockPage from './unlock-page.component' @@ -23,7 +22,6 @@ const mapDispatchToProps = dispatch => { forgotPassword: () => dispatch(forgotPassword()), tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)), markPasswordForgotten: () => dispatch(markPasswordForgotten()), - setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)), } } -- cgit v1.2.3 From 924cc1fcf7de1896ac09bbe7a400d5ff5df0b50d Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 19 Apr 2018 01:03:51 -0230 Subject: Move setAccountLabel into PreferencesController --- ui/app/components/modals/account-details-modal.js | 6 +++--- ui/app/components/modals/edit-account-name-modal.js | 8 ++++---- ui/app/components/modals/new-account-modal.js | 2 +- ui/app/components/pages/create-account/index.js | 2 +- ui/app/components/pages/create-account/new-account.js | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js index d9885daf5..5607cf051 100644 --- a/ui/app/components/modals/account-details-modal.js +++ b/ui/app/components/modals/account-details-modal.js @@ -25,7 +25,7 @@ function mapDispatchToProps (dispatch) { dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' })) }, hideModal: () => dispatch(actions.hideModal()), - saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)), + setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)), } } @@ -49,7 +49,7 @@ AccountDetailsModal.prototype.render = function () { selectedIdentity, network, showExportPrivateKeyModal, - saveAccountLabel, + setAccountLabel, } = this.props const { name, address } = selectedIdentity @@ -57,7 +57,7 @@ AccountDetailsModal.prototype.render = function () { h(EditableLabel, { className: 'account-modal__name', defaultValue: name, - onSubmit: label => saveAccountLabel(address, label), + onSubmit: label => setAccountLabel(address, label), }), h(QrView, { diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js index c79645dbf..5681a3cad 100644 --- a/ui/app/components/modals/edit-account-name-modal.js +++ b/ui/app/components/modals/edit-account-name-modal.js @@ -18,8 +18,8 @@ function mapDispatchToProps (dispatch) { hideModal: () => { dispatch(actions.hideModal()) }, - saveAccountLabel: (account, label) => { - dispatch(actions.saveAccountLabel(account, label)) + setAccountLabel: (account, label) => { + dispatch(actions.setAccountLabel(account, label)) }, } } @@ -41,7 +41,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(EditAccountNameMod EditAccountNameModal.prototype.render = function () { - const { hideModal, saveAccountLabel, identity } = this.props + const { hideModal, setAccountLabel, identity } = this.props return h('div', {}, [ h('div.flex-column.edit-account-name-modal-content', { @@ -69,7 +69,7 @@ EditAccountNameModal.prototype.render = function () { h('button.btn-clear.edit-account-name-modal-save-button.allcaps', { onClick: () => { if (this.state.inputText.length !== 0) { - saveAccountLabel(identity.address, this.state.inputText) + setAccountLabel(identity.address, this.state.inputText) hideModal() } }, diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/modals/new-account-modal.js index 0635b3f72..a66a3ed4a 100644 --- a/ui/app/components/modals/new-account-modal.js +++ b/ui/app/components/modals/new-account-modal.js @@ -95,7 +95,7 @@ const mapDispatchToProps = dispatch => { dispatch(actions.addNewAccount()) .then((newAccountAddress) => { if (newAccountName) { - dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName)) + dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) } dispatch(actions.hideModal()) }) diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js index 0962477d8..475261253 100644 --- a/ui/app/components/pages/create-account/index.js +++ b/ui/app/components/pages/create-account/index.js @@ -75,7 +75,7 @@ const mapDispatchToProps = dispatch => ({ dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' })) }, hideModal: () => dispatch(actions.hideModal()), - saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)), + setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)), }) module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage) diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js index 40fa584be..03a5ee72d 100644 --- a/ui/app/components/pages/create-account/new-account.js +++ b/ui/app/components/pages/create-account/new-account.js @@ -87,7 +87,7 @@ const mapDispatchToProps = dispatch => { return dispatch(actions.addNewAccount()) .then(newAccountAddress => { if (newAccountName) { - dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName)) + dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) } }) }, -- cgit v1.2.3 From 4f6b53c1aa383c05fa3c356adb332fcc6dbf45e9 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 19 May 2018 23:04:19 -0700 Subject: Update designs for Add Token screen --- ui/app/components/button/button.component.js | 19 +- ui/app/components/button/index.js | 2 +- .../export-text-container.scss | 52 --- ui/app/components/export-text-container/index.scss | 52 +++ ui/app/components/index.scss | 5 + ui/app/components/info-box/index.js | 2 + ui/app/components/info-box/index.scss | 24 ++ ui/app/components/info-box/info-box.component.js | 49 +++ ui/app/components/pages/add-token.js | 431 --------------------- .../pages/add-token/add-token.component.js | 356 +++++++++++++++++ .../pages/add-token/add-token.container.js | 22 ++ ui/app/components/pages/add-token/index.js | 2 + ui/app/components/pages/add-token/index.scss | 25 ++ .../components/pages/add-token/token-list/index.js | 2 + .../pages/add-token/token-list/index.scss | 65 ++++ .../token-list/token-list-placeholder/index.js | 2 + .../token-list/token-list-placeholder/index.scss | 19 + .../token-list-placeholder.component.js | 27 ++ .../add-token/token-list/token-list.component.js | 60 +++ .../add-token/token-list/token-list.container.js | 11 + .../pages/add-token/token-search/index.js | 2 + .../token-search/token-search.component.js | 85 ++++ ui/app/components/pages/add-token/util.js | 13 + .../confirm-add-token.component.js | 115 ++++++ .../confirm-add-token.container.js | 20 + ui/app/components/pages/confirm-add-token/index.js | 2 + .../components/pages/confirm-add-token/index.scss | 69 ++++ .../pages/confirm-add-token/token-balance/index.js | 2 + .../token-balance/token-balance.component.js | 16 + .../token-balance/token-balance.container.js | 16 + ui/app/components/pages/index.scss | 5 + ui/app/components/pages/unlock-page/index.scss | 51 +++ .../pages/unlock-page/unlock-page.component.js | 3 +- .../components/pages/unlock-page/unlock-page.scss | 51 --- ui/app/components/signature-request.js | 2 +- .../components/text-field/text-field.component.js | 117 ++++-- 36 files changed, 1212 insertions(+), 584 deletions(-) delete mode 100644 ui/app/components/export-text-container/export-text-container.scss create mode 100644 ui/app/components/export-text-container/index.scss create mode 100644 ui/app/components/index.scss create mode 100644 ui/app/components/info-box/index.js create mode 100644 ui/app/components/info-box/index.scss create mode 100644 ui/app/components/info-box/info-box.component.js delete mode 100644 ui/app/components/pages/add-token.js create mode 100644 ui/app/components/pages/add-token/add-token.component.js create mode 100644 ui/app/components/pages/add-token/add-token.container.js create mode 100644 ui/app/components/pages/add-token/index.js create mode 100644 ui/app/components/pages/add-token/index.scss create mode 100644 ui/app/components/pages/add-token/token-list/index.js create mode 100644 ui/app/components/pages/add-token/token-list/index.scss create mode 100644 ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js create mode 100644 ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss create mode 100644 ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js create mode 100644 ui/app/components/pages/add-token/token-list/token-list.component.js create mode 100644 ui/app/components/pages/add-token/token-list/token-list.container.js create mode 100644 ui/app/components/pages/add-token/token-search/index.js create mode 100644 ui/app/components/pages/add-token/token-search/token-search.component.js create mode 100644 ui/app/components/pages/add-token/util.js create mode 100644 ui/app/components/pages/confirm-add-token/confirm-add-token.component.js create mode 100644 ui/app/components/pages/confirm-add-token/confirm-add-token.container.js create mode 100644 ui/app/components/pages/confirm-add-token/index.js create mode 100644 ui/app/components/pages/confirm-add-token/index.scss create mode 100644 ui/app/components/pages/confirm-add-token/token-balance/index.js create mode 100644 ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js create mode 100644 ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js create mode 100644 ui/app/components/pages/index.scss create mode 100644 ui/app/components/pages/unlock-page/index.scss delete mode 100644 ui/app/components/pages/unlock-page/unlock-page.scss (limited to 'ui/app/components') diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js index 7769e4875..fe3bf363c 100644 --- a/ui/app/components/button/button.component.js +++ b/ui/app/components/button/button.component.js @@ -1,7 +1,6 @@ -const { Component } = require('react') -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -const classnames = require('classnames') +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' const SECONDARY = 'secondary' const CLASSNAME_PRIMARY = 'btn-primary' @@ -24,10 +23,12 @@ class Button extends Component { const { type, large, className, ...buttonProps } = this.props return ( - h('button', { - className: classnames(getClassName(type, large), className), - ...buttonProps, - }, this.props.children) + ) } } @@ -39,5 +40,5 @@ Button.propTypes = { children: PropTypes.string, } -module.exports = Button +export default Button diff --git a/ui/app/components/button/index.js b/ui/app/components/button/index.js index 3dc7d1eea..33ae95ae2 100644 --- a/ui/app/components/button/index.js +++ b/ui/app/components/button/index.js @@ -1,2 +1,2 @@ -const Button = require('./button.component') +import Button from './button.component' module.exports = Button diff --git a/ui/app/components/export-text-container/export-text-container.scss b/ui/app/components/export-text-container/export-text-container.scss deleted file mode 100644 index 975d62f70..000000000 --- a/ui/app/components/export-text-container/export-text-container.scss +++ /dev/null @@ -1,52 +0,0 @@ -.export-text-container { - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - border: 1px solid $alto; - border-radius: 4px; - font-weight: 400; - - &__text-container { - width: 100%; - display: flex; - justify-content: center; - padding: 20px; - border-radius: 4px; - background: $alabaster; - } - - &__text { - resize: none; - border: none; - background: $alabaster; - font-size: 20px; - text-align: center; - } - - &__buttons-container { - display: flex; - flex-direction: row; - border-top: 1px solid $alto; - width: 100%; - } - - &__button { - padding: 10px; - flex: 1; - display: flex; - justify-content: center; - align-items: center; - font-size: 12px; - cursor: pointer; - color: $curious-blue; - - &--copy { - border-right: 1px solid $alto; - } - } - - &__button-text { - padding-left: 10px; - } -} diff --git a/ui/app/components/export-text-container/index.scss b/ui/app/components/export-text-container/index.scss new file mode 100644 index 000000000..975d62f70 --- /dev/null +++ b/ui/app/components/export-text-container/index.scss @@ -0,0 +1,52 @@ +.export-text-container { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + border: 1px solid $alto; + border-radius: 4px; + font-weight: 400; + + &__text-container { + width: 100%; + display: flex; + justify-content: center; + padding: 20px; + border-radius: 4px; + background: $alabaster; + } + + &__text { + resize: none; + border: none; + background: $alabaster; + font-size: 20px; + text-align: center; + } + + &__buttons-container { + display: flex; + flex-direction: row; + border-top: 1px solid $alto; + width: 100%; + } + + &__button { + padding: 10px; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + cursor: pointer; + color: $curious-blue; + + &--copy { + border-right: 1px solid $alto; + } + } + + &__button-text { + padding-left: 10px; + } +} diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss new file mode 100644 index 000000000..f3fe823f8 --- /dev/null +++ b/ui/app/components/index.scss @@ -0,0 +1,5 @@ +@import './export-text-container/index'; + +@import './info-box/index'; + +@import './pages/index'; diff --git a/ui/app/components/info-box/index.js b/ui/app/components/info-box/index.js new file mode 100644 index 000000000..6110422ed --- /dev/null +++ b/ui/app/components/info-box/index.js @@ -0,0 +1,2 @@ +import InfoBox from './info-box.component' +module.exports = InfoBox diff --git a/ui/app/components/info-box/index.scss b/ui/app/components/info-box/index.scss new file mode 100644 index 000000000..8b5626d79 --- /dev/null +++ b/ui/app/components/info-box/index.scss @@ -0,0 +1,24 @@ +.info-box { + border-radius: 4px; + background-color: $alabaster; + position: relative; + padding: 16px; + display: flex; + flex-flow: column; + color: $mid-gray; + + &__close::after { + content: '\00D7'; + font-size: 29px; + font-weight: 200; + color: $dusty-gray; + position: absolute; + right: 12px; + top: 0; + cursor: pointer; + } + + &__description { + font-size: .75rem; + } +} diff --git a/ui/app/components/info-box/info-box.component.js b/ui/app/components/info-box/info-box.component.js new file mode 100644 index 000000000..8688b8e8f --- /dev/null +++ b/ui/app/components/info-box/info-box.component.js @@ -0,0 +1,49 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class InfoBox extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + onClose: PropTypes.func, + title: PropTypes.string, + description: PropTypes.string, + } + + constructor (props) { + super(props) + + this.state = { + isShowing: true, + } + } + + handleClose () { + const { onClose } = this.props + + if (onClose) { + onClose() + } else { + this.setState({ isShowing: false }) + } + } + + render () { + const { title, description } = this.props + + return !this.state.isShowing + ? null + : ( +
+
this.handleClose()} + /> +
{ title }
+
{ description }
+
+ ) + } +} diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js deleted file mode 100644 index 8d52571d0..000000000 --- a/ui/app/components/pages/add-token.js +++ /dev/null @@ -1,431 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const classnames = require('classnames') -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const R = require('ramda') -const Fuse = require('fuse.js') -const contractMap = require('eth-contract-metadata') -const TokenBalance = require('../../components/token-balance') -const Identicon = require('../../components/identicon') -const contractList = Object.entries(contractMap) - .map(([ _, tokenData]) => tokenData) - .filter(tokenData => Boolean(tokenData.erc20)) -const fuse = new Fuse(contractList, { - shouldSort: true, - threshold: 0.45, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: [ - { name: 'name', weight: 0.5 }, - { name: 'symbol', weight: 0.5 }, - ], -}) -const actions = require('../../actions') -const ethUtil = require('ethereumjs-util') -const { tokenInfoGetter } = require('../../token-util') -const { DEFAULT_ROUTE } = require('../../routes') - -const emptyAddr = '0x0000000000000000000000000000000000000000' - -AddTokenScreen.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(AddTokenScreen) - - -function mapStateToProps (state) { - const { identities, tokens } = state.metamask - return { - identities, - tokens, - } -} - -function mapDispatchToProps (dispatch) { - return { - addTokens: tokens => dispatch(actions.addTokens(tokens)), - } -} - -inherits(AddTokenScreen, Component) -function AddTokenScreen () { - this.state = { - isShowingConfirmation: false, - isShowingInfoBox: true, - customAddress: '', - customSymbol: '', - customDecimals: '', - searchQuery: '', - selectedTokens: {}, - errors: {}, - autoFilled: false, - displayedTab: 'SEARCH', - } - this.tokenAddressDidChange = this.tokenAddressDidChange.bind(this) - this.tokenSymbolDidChange = this.tokenSymbolDidChange.bind(this) - this.tokenDecimalsDidChange = this.tokenDecimalsDidChange.bind(this) - this.onNext = this.onNext.bind(this) - Component.call(this) -} - -AddTokenScreen.prototype.componentWillMount = function () { - this.tokenInfoGetter = tokenInfoGetter() -} - -AddTokenScreen.prototype.toggleToken = function (address, token) { - const { selectedTokens = {}, errors } = this.state - const selectedTokensCopy = { ...selectedTokens } - - if (address in selectedTokensCopy) { - delete selectedTokensCopy[address] - } else { - selectedTokensCopy[address] = token - } - - this.setState({ - selectedTokens: selectedTokensCopy, - errors: { - ...errors, - tokenSelector: null, - }, - }) -} - -AddTokenScreen.prototype.onNext = function () { - const { isValid, errors } = this.validate() - - return !isValid - ? this.setState({ errors }) - : this.setState({ isShowingConfirmation: true }) -} - -AddTokenScreen.prototype.tokenAddressDidChange = function (e) { - const customAddress = e.target.value.trim() - this.setState({ customAddress }) - if (ethUtil.isValidAddress(customAddress) && customAddress !== emptyAddr) { - this.attemptToAutoFillTokenParams(customAddress) - } else { - this.setState({ - customSymbol: '', - customDecimals: 0, - }) - } -} - -AddTokenScreen.prototype.tokenSymbolDidChange = function (e) { - const customSymbol = e.target.value.trim() - this.setState({ customSymbol }) -} - -AddTokenScreen.prototype.tokenDecimalsDidChange = function (e) { - const customDecimals = e.target.value.trim() - this.setState({ customDecimals }) -} - -AddTokenScreen.prototype.checkExistingAddresses = function (address) { - if (!address) return false - const tokensList = this.props.tokens - const matchesAddress = existingToken => { - return existingToken.address.toLowerCase() === address.toLowerCase() - } - - return R.any(matchesAddress)(tokensList) -} - -AddTokenScreen.prototype.validate = function () { - const errors = {} - const identitiesList = Object.keys(this.props.identities) - const { customAddress, customSymbol, customDecimals, selectedTokens } = this.state - const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase() - - if (customAddress) { - const validAddress = ethUtil.isValidAddress(customAddress) - if (!validAddress) { - errors.customAddress = this.context.t('invalidAddress') - } - - const validDecimals = customDecimals !== null - && customDecimals !== '' - && customDecimals >= 0 - && customDecimals < 36 - if (!validDecimals) { - errors.customDecimals = this.context.t('decimalsMustZerotoTen') - } - - const symbolLen = customSymbol.trim().length - const validSymbol = symbolLen > 0 && symbolLen < 10 - if (!validSymbol) { - errors.customSymbol = this.context.t('symbolBetweenZeroTen') - } - - const ownAddress = identitiesList.includes(standardAddress) - if (ownAddress) { - errors.customAddress = this.context.t('personalAddressDetected') - } - - const tokenAlreadyAdded = this.checkExistingAddresses(customAddress) - if (tokenAlreadyAdded) { - errors.customAddress = this.context.t('tokenAlreadyAdded') - } - } else if ( - Object.entries(selectedTokens) - .reduce((isEmpty, [ symbol, isSelected ]) => ( - isEmpty && !isSelected - ), true) - ) { - errors.tokenSelector = this.context.t('mustSelectOne') - } - - return { - isValid: !Object.keys(errors).length, - errors, - } -} - -AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { - const { symbol, decimals } = await this.tokenInfoGetter(address) - if (symbol && decimals) { - this.setState({ - customSymbol: symbol, - customDecimals: decimals, - autoFilled: true, - }) - } -} - -AddTokenScreen.prototype.renderCustomForm = function () { - const { autoFilled, customAddress, customSymbol, customDecimals, errors } = this.state - - return ( - h('div.add-token__add-custom-form', [ - h('div', { - className: classnames('add-token__add-custom-field', { - 'add-token__add-custom-field--error': errors.customAddress, - }), - }, [ - h('div.add-token__add-custom-label', this.context.t('tokenAddress')), - h('input.add-token__add-custom-input', { - type: 'text', - onChange: this.tokenAddressDidChange, - value: customAddress, - }), - h('div.add-token__add-custom-error-message', errors.customAddress), - ]), - h('div', { - className: classnames('add-token__add-custom-field', { - 'add-token__add-custom-field--error': errors.customSymbol, - }), - }, [ - h('div.add-token__add-custom-label', this.context.t('tokenSymbol')), - h('input.add-token__add-custom-input', { - type: 'text', - onChange: this.tokenSymbolDidChange, - value: customSymbol, - disabled: autoFilled, - }), - h('div.add-token__add-custom-error-message', errors.customSymbol), - ]), - h('div', { - className: classnames('add-token__add-custom-field', { - 'add-token__add-custom-field--error': errors.customDecimals, - }), - }, [ - h('div.add-token__add-custom-label', this.context.t('decimal')), - h('input.add-token__add-custom-input', { - type: 'number', - onChange: this.tokenDecimalsDidChange, - value: customDecimals, - disabled: autoFilled, - }), - h('div.add-token__add-custom-error-message', errors.customDecimals), - ]), - ]) - ) -} - -AddTokenScreen.prototype.renderTokenList = function () { - const { searchQuery = '', selectedTokens } = this.state - const fuseSearchResult = fuse.search(searchQuery) - const addressSearchResult = contractList.filter(token => { - return token.address.toLowerCase() === searchQuery.toLowerCase() - }) - const results = [...addressSearchResult, ...fuseSearchResult] - - return h('div', [ - results.length > 0 && h('div.add-token__token-icons-title', this.context.t('popularTokens')), - h('div.add-token__token-icons-container', Array(6).fill(undefined) - .map((_, i) => { - const { logo, symbol, name, address } = results[i] || {} - const tokenAlreadyAdded = this.checkExistingAddresses(address) - return Boolean(logo || symbol || name) && ( - h('div.add-token__token-wrapper', { - className: classnames({ - 'add-token__token-wrapper--selected': selectedTokens[address], - 'add-token__token-wrapper--disabled': tokenAlreadyAdded, - }), - onClick: () => !tokenAlreadyAdded && this.toggleToken(address, results[i]), - }, [ - h('div.add-token__token-icon', { - style: { - backgroundImage: logo && `url(images/contract/${logo})`, - }, - }), - h('div.add-token__token-data', [ - h('div.add-token__token-symbol', symbol), - h('div.add-token__token-name', name), - ]), - // tokenAlreadyAdded && ( - // h('div.add-token__token-message', 'Already added') - // ), - ]) - ) - })), - ]) -} - -AddTokenScreen.prototype.renderConfirmation = function () { - const { - customAddress: address, - customSymbol: symbol, - customDecimals: decimals, - selectedTokens, - } = this.state - - const { addTokens, history } = this.props - - const customToken = { - address, - symbol, - decimals, - } - - const tokens = address && symbol && decimals - ? { ...selectedTokens, [address]: customToken } - : selectedTokens - - return ( - h('div.add-token', [ - h('div.add-token__wrapper', [ - h('div.add-token__content-container.add-token__confirmation-content', [ - h('div.add-token__description.add-token__confirmation-description', this.context.t('balances')), - h('div.add-token__confirmation-token-list', - Object.entries(tokens) - .map(([ address, token ]) => ( - h('span.add-token__confirmation-token-list-item', [ - h(Identicon, { - className: 'add-token__confirmation-token-icon', - diameter: 75, - address, - }), - h(TokenBalance, { token }), - ]) - )) - ), - ]), - ]), - h('div.add-token__buttons', [ - h('button.btn-secondary--lg.add-token__cancel-button', { - onClick: () => this.setState({ isShowingConfirmation: false }), - }, this.context.t('back')), - h('button.btn-primary--lg', { - onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)), - }, this.context.t('addTokens')), - ]), - ]) - ) -} - -AddTokenScreen.prototype.displayTab = function (selectedTab) { - this.setState({ displayedTab: selectedTab }) -} - -AddTokenScreen.prototype.renderTabs = function () { - const { isShowingInfoBox, displayedTab, errors } = this.state - - return displayedTab === 'CUSTOM_TOKEN' - ? this.renderCustomForm() - : h('div', [ - h('div.add-token__wrapper', [ - h('div.add-token__content-container', [ - isShowingInfoBox && h('div.add-token__info-box', [ - h('div.add-token__info-box__close', { - onClick: () => this.setState({ isShowingInfoBox: false }), - }), - h('div.add-token__info-box__title', this.context.t('whatsThis')), - h('div.add-token__info-box__copy', this.context.t('keepTrackTokens')), - h('a.add-token__info-box__copy--blue', { - href: 'http://metamask.helpscoutdocs.com/article/16-managing-erc20-tokens', - target: '_blank', - }, this.context.t('learnMore')), - ]), - h('div.add-token__input-container', [ - h('input.add-token__input', { - type: 'text', - placeholder: this.context.t('searchTokens'), - onChange: e => this.setState({ searchQuery: e.target.value }), - }), - h('div.add-token__search-input-error-message', errors.tokenSelector), - ]), - this.renderTokenList(), - ]), - ]), - ]) -} - -AddTokenScreen.prototype.render = function () { - const { - isShowingConfirmation, - displayedTab, - } = this.state - const { history } = this.props - - return h('div.add-token', [ - h('div.add-token__header', [ - h('div.add-token__header__cancel', { - onClick: () => history.push(DEFAULT_ROUTE), - }, [ - h('i.fa.fa-angle-left.fa-lg'), - h('span', this.context.t('cancel')), - ]), - h('div.add-token__header__title', this.context.t('addTokens')), - isShowingConfirmation && h('div.add-token__header__subtitle', this.context.t('likeToAddTokens')), - !isShowingConfirmation && h('div.add-token__header__tabs', [ - - h('div.add-token__header__tabs__tab', { - className: classnames('add-token__header__tabs__tab', { - 'add-token__header__tabs__selected': displayedTab === 'SEARCH', - 'add-token__header__tabs__unselected': displayedTab !== 'SEARCH', - }), - onClick: () => this.displayTab('SEARCH'), - }, this.context.t('search')), - - h('div.add-token__header__tabs__tab', { - className: classnames('add-token__header__tabs__tab', { - 'add-token__header__tabs__selected': displayedTab === 'CUSTOM_TOKEN', - 'add-token__header__tabs__unselected': displayedTab !== 'CUSTOM_TOKEN', - }), - onClick: () => this.displayTab('CUSTOM_TOKEN'), - }, this.context.t('customToken')), - - ]), - ]), - - isShowingConfirmation - ? this.renderConfirmation() - : this.renderTabs(), - - !isShowingConfirmation && h('div.add-token__buttons', [ - h('button.btn-secondary--lg.add-token__cancel-button', { - onClick: () => history.push(DEFAULT_ROUTE), - }, this.context.t('cancel')), - h('button.btn-primary--lg.add-token__confirm-button', { - onClick: this.onNext, - }, this.context.t('next')), - ]), - ]) -} diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js new file mode 100644 index 000000000..885c7b2ac --- /dev/null +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -0,0 +1,356 @@ +import React, { Component } from 'react' +import classnames from 'classnames' +import PropTypes from 'prop-types' +import ethUtil from 'ethereumjs-util' +import { checkExistingAddresses } from './util' +import { tokenInfoGetter } from '../../../token-util' +import { DEFAULT_ROUTE, CONFIRM_ADD_TOKEN_ROUTE } from '../../../routes' +import Button from '../../button' +import TextField from '../../text-field' +import TokenList from './token-list' +import TokenSearch from './token-search' + +const emptyAddr = '0x0000000000000000000000000000000000000000' +const SEARCH_TAB = 'SEARCH' +const CUSTOM_TOKEN_TAB = 'CUSTOM_TOKEN' + +class AddToken extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + history: PropTypes.object, + setPendingTokens: PropTypes.func, + pendingTokens: PropTypes.object, + clearPendingTokens: PropTypes.func, + tokens: PropTypes.array, + identities: PropTypes.object, + } + + constructor (props) { + super(props) + + this.state = { + customAddress: '', + customSymbol: '', + customDecimals: 0, + searchResults: [], + selectedTokens: {}, + tokenSelectorError: null, + customAddressError: null, + customSymbolError: null, + customDecimalsError: null, + autoFilled: false, + displayedTab: SEARCH_TAB, + } + } + + componentDidMount () { + this.tokenInfoGetter = tokenInfoGetter() + const { pendingTokens = {} } = this.props + const pendingTokenKeys = Object.keys(pendingTokens) + + if (pendingTokenKeys.length > 0) { + let selectedTokens = {} + let customToken = {} + + pendingTokenKeys.forEach(tokenAddress => { + const token = pendingTokens[tokenAddress] + const { isCustom } = token + + if (isCustom) { + customToken = { ...token } + } else { + selectedTokens = { ...selectedTokens, [tokenAddress]: { ...token } } + } + }) + + const { + address: customAddress = '', + symbol: customSymbol = '', + decimals: customDecimals = 0, + } = customToken + + const displayedTab = Object.keys(selectedTokens).length > 0 ? SEARCH_TAB : CUSTOM_TOKEN_TAB + this.setState({ selectedTokens, customAddress, customSymbol, customDecimals, displayedTab }) + } + } + + handleToggleToken (token) { + const { address } = token + const { selectedTokens = {} } = this.state + const selectedTokensCopy = { ...selectedTokens } + + if (address in selectedTokensCopy) { + delete selectedTokensCopy[address] + } else { + selectedTokensCopy[address] = token + } + + this.setState({ + selectedTokens: selectedTokensCopy, + tokenSelectorError: null, + }) + } + + hasError () { + const { + tokenSelectorError, + customAddressError, + customSymbolError, + customDecimalsError, + } = this.state + + return tokenSelectorError || customAddressError || customSymbolError || customDecimalsError + } + + hasSelected () { + const { customAddress = '', selectedTokens = {} } = this.state + return customAddress || Object.keys(selectedTokens).length > 0 + } + + handleNext () { + if (this.hasError()) { + return + } + + if (!this.hasSelected()) { + this.setState({ tokenSelectorError: this.context.t('mustSelectOne') }) + return + } + + const { setPendingTokens, history } = this.props + const { + customAddress: address, + customSymbol: symbol, + customDecimals: decimals, + selectedTokens, + } = this.state + + const customToken = { + address, + symbol, + decimals, + } + + setPendingTokens({ customToken, selectedTokens }) + history.push(CONFIRM_ADD_TOKEN_ROUTE) + } + + async attemptToAutoFillTokenParams (address) { + const { symbol, decimals } = await this.tokenInfoGetter(address) + + if (symbol && decimals) { + this.setState({ + customSymbol: symbol, + customDecimals: decimals, + customSymbolError: null, + customDecimalsError: null, + autoFilled: true, + }) + } + } + + handleCustomAddressChange (value) { + const customAddress = value.trim() + this.setState({ + customAddress, + customAddressError: null, + tokenSelectorError: null, + autoFilled: false, + }) + + const isValidAddress = ethUtil.isValidAddress(customAddress) + const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase() + + switch (true) { + case !isValidAddress: + this.setState({ + customAddressError: this.context.t('invalidAddress'), + customSymbol: '', + customDecimals: 0, + customSymbolError: null, + customDecimalsError: null, + }) + + break + case Boolean(this.props.identities[standardAddress]): + this.setState({ + customAddressError: this.context.t('personalAddressDetected'), + }) + + break + case checkExistingAddresses(customAddress, this.props.tokens): + this.setState({ + customAddressError: this.context.t('tokenAlreadyAdded'), + }) + + break + default: + if (customAddress !== emptyAddr) { + this.attemptToAutoFillTokenParams(customAddress) + } + } + } + + handleCustomSymbolChange (value) { + const customSymbol = value.trim() + const symbolLength = customSymbol.length + let customSymbolError = null + + if (symbolLength <= 0 || symbolLength >= 10) { + customSymbolError = this.context.t('symbolBetweenZeroTen') + } + + this.setState({ customSymbol, customSymbolError }) + } + + handleCustomDecimalsChange (value) { + const customDecimals = value.trim() + const validDecimals = customDecimals !== null && + customDecimals !== '' && + customDecimals >= 0 && + customDecimals < 36 + let customDecimalsError = null + + if (!validDecimals) { + customDecimalsError = this.context.t('decimalsMustZerotoTen') + } + + this.setState({ customDecimals, customDecimalsError }) + } + + renderCustomTokenForm () { + const { + customAddress, + customSymbol, + customDecimals, + customAddressError, + customSymbolError, + customDecimalsError, + autoFilled, + } = this.state + + return ( +
+ this.handleCustomAddressChange(e.target.value)} + error={customAddressError} + fullWidth + margin="normal" + /> + this.handleCustomSymbolChange(e.target.value)} + error={customSymbolError} + fullWidth + margin="normal" + disabled={autoFilled} + /> + this.handleCustomDecimalsChange(e.target.value)} + error={customDecimalsError} + fullWidth + margin="normal" + disabled={autoFilled} + /> +
+ ) + } + + renderSearchToken () { + const { tokenSelectorError, selectedTokens, searchResults } = this.state + + return ( +
+ this.setState({ searchResults: results })} + error={tokenSelectorError} + /> +
+ this.handleToggleToken(token)} + /> +
+
+ ) + } + + render () { + const { displayedTab } = this.state + const { history, clearPendingTokens } = this.props + + return ( +
+
+
+ { this.context.t('addTokens') } +
+
+
this.setState({ displayedTab: SEARCH_TAB })} + > + { this.context.t('search') } +
+
this.setState({ displayedTab: CUSTOM_TOKEN_TAB })} + > + { this.context.t('customToken') } +
+
+
+
+ { + displayedTab === CUSTOM_TOKEN_TAB + ? this.renderCustomTokenForm() + : this.renderSearchToken() + } +
+
+ + +
+
+ ) + } +} + +export default AddToken diff --git a/ui/app/components/pages/add-token/add-token.container.js b/ui/app/components/pages/add-token/add-token.container.js new file mode 100644 index 000000000..87671b156 --- /dev/null +++ b/ui/app/components/pages/add-token/add-token.container.js @@ -0,0 +1,22 @@ +import { connect } from 'react-redux' +import AddToken from './add-token.component' + +const { setPendingTokens, clearPendingTokens } = require('../../../actions') + +const mapStateToProps = ({ metamask }) => { + const { identities, tokens, pendingTokens } = metamask + return { + identities, + tokens, + pendingTokens, + } +} + +const mapDispatchToProps = dispatch => { + return { + setPendingTokens: tokens => dispatch(setPendingTokens(tokens)), + clearPendingTokens: () => dispatch(clearPendingTokens()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddToken) diff --git a/ui/app/components/pages/add-token/index.js b/ui/app/components/pages/add-token/index.js new file mode 100644 index 000000000..3666cae82 --- /dev/null +++ b/ui/app/components/pages/add-token/index.js @@ -0,0 +1,2 @@ +import AddToken from './add-token.container' +module.exports = AddToken diff --git a/ui/app/components/pages/add-token/index.scss b/ui/app/components/pages/add-token/index.scss new file mode 100644 index 000000000..39e86b97b --- /dev/null +++ b/ui/app/components/pages/add-token/index.scss @@ -0,0 +1,25 @@ +@import './token-list/index'; + +.add-token { + &__custom-token-form { + padding: 8px 16px 16px; + + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + + input[type="number"]:hover::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + } + + &__search-token { + padding: 16px; + } + + &__token-list { + margin-top: 16px; + } +} diff --git a/ui/app/components/pages/add-token/token-list/index.js b/ui/app/components/pages/add-token/token-list/index.js new file mode 100644 index 000000000..21dd5ac72 --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/index.js @@ -0,0 +1,2 @@ +import TokenList from './token-list.container' +module.exports = TokenList diff --git a/ui/app/components/pages/add-token/token-list/index.scss b/ui/app/components/pages/add-token/token-list/index.scss new file mode 100644 index 000000000..e32739d59 --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/index.scss @@ -0,0 +1,65 @@ +@import './token-list-placeholder/index'; + +.token-list { + &__title { + font-size: .75rem; + } + + &__tokens-container { + display: flex; + flex-direction: column; + } + + &__token { + transition: 200ms ease-in-out; + display: flex; + flex-flow: row nowrap; + align-items: center; + padding: 8px; + margin-top: 8px; + box-sizing: border-box; + border-radius: 10px; + cursor: pointer; + border: 2px solid transparent; + position: relative; + + &:hover { + border: 2px solid rgba($malibu-blue, .5); + } + + &--selected { + border: 2px solid $malibu-blue !important; + } + + &--disabled { + opacity: .4; + pointer-events: none; + } + } + + &__token-icon { + width: 48px; + height: 48px; + background-repeat: no-repeat; + background-size: contain; + background-position: center; + border-radius: 50%; + background-color: $white; + box-shadow: 0 2px 4px 0 rgba($black, .24); + margin-right: 12px; + flex: 0 0 auto; + } + + &__token-data { + display: flex; + flex-direction: row; + align-items: center; + min-width: 0; + } + + &__token-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js new file mode 100644 index 000000000..b82f45e93 --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.js @@ -0,0 +1,2 @@ +import TokenListPlaceholder from './token-list-placeholder.component' +module.exports = TokenListPlaceholder diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss new file mode 100644 index 000000000..9d0f4be32 --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss @@ -0,0 +1,19 @@ +.token-list-placeholder { + display: flex; + align-items: center; + padding-top: 36px; + flex-direction: column; + line-height: 22px; + opacity: .5; + + &__text { + color: $silver-chalice; + width: 50%; + text-align: center; + margin-top: 8px; + } + + &__link { + color: $curious-blue; + } +} 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 new file mode 100644 index 000000000..abd599b26 --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/token-list-placeholder/token-list-placeholder.component.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class TokenListPlaceholder extends Component { + static contextTypes = { + t: PropTypes.func, + } + + render () { + return ( +
+ +
+ { this.context.t('addAcquiredTokens') } +
+ + { this.context.t('learnMore') } + +
+ ) + } +} diff --git a/ui/app/components/pages/add-token/token-list/token-list.component.js b/ui/app/components/pages/add-token/token-list/token-list.component.js new file mode 100644 index 000000000..724a68d6e --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/token-list.component.js @@ -0,0 +1,60 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import { checkExistingAddresses } from '../util' +import TokenListPlaceholder from './token-list-placeholder' + +export default class InfoBox extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + tokens: PropTypes.array, + results: PropTypes.array, + selectedTokens: PropTypes.object, + onToggleToken: PropTypes.func, + } + + render () { + const { results = [], selectedTokens = {}, onToggleToken, tokens = [] } = this.props + + return results.length === 0 + ? + : ( +
+
+ { this.context.t('searchResults') } +
+
+ { + Array(6).fill(undefined) + .map((_, i) => { + const { logo, symbol, name, address } = results[i] || {} + const tokenAlreadyAdded = checkExistingAddresses(address, tokens) + + return Boolean(logo || symbol || name) && ( +
!tokenAlreadyAdded && onToggleToken(results[i])} + key={i} + > +
+
+
+ { `${name} (${symbol})` } +
+
+ ) + }) + } +
+
+ ) + } +} diff --git a/ui/app/components/pages/add-token/token-list/token-list.container.js b/ui/app/components/pages/add-token/token-list/token-list.container.js new file mode 100644 index 000000000..cd7b07a37 --- /dev/null +++ b/ui/app/components/pages/add-token/token-list/token-list.container.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux' +import TokenList from './token-list.component' + +const mapStateToProps = ({ metamask }) => { + const { tokens } = metamask + return { + tokens, + } +} + +export default connect(mapStateToProps)(TokenList) diff --git a/ui/app/components/pages/add-token/token-search/index.js b/ui/app/components/pages/add-token/token-search/index.js new file mode 100644 index 000000000..acaa6b084 --- /dev/null +++ b/ui/app/components/pages/add-token/token-search/index.js @@ -0,0 +1,2 @@ +import TokenSearch from './token-search.component' +module.exports = TokenSearch diff --git a/ui/app/components/pages/add-token/token-search/token-search.component.js b/ui/app/components/pages/add-token/token-search/token-search.component.js new file mode 100644 index 000000000..036b2db1e --- /dev/null +++ b/ui/app/components/pages/add-token/token-search/token-search.component.js @@ -0,0 +1,85 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import contractMap from 'eth-contract-metadata' +import Fuse from 'fuse.js' +import InputAdornment from '@material-ui/core/InputAdornment' +import TextField from '../../../text-field' + +const contractList = Object.entries(contractMap) + .map(([ _, tokenData]) => tokenData) + .filter(tokenData => Boolean(tokenData.erc20)) + +const fuse = new Fuse(contractList, { + shouldSort: true, + threshold: 0.45, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: [ + { name: 'name', weight: 0.5 }, + { name: 'symbol', weight: 0.5 }, + ], +}) + +export default class TokenSearch extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static defaultProps = { + error: null, + } + + static propTypes = { + onSearch: PropTypes.func, + error: PropTypes.string, + } + + constructor (props) { + super(props) + + this.state = { + searchQuery: '', + } + } + + handleSearch (searchQuery) { + this.setState({ searchQuery }) + const fuseSearchResult = fuse.search(searchQuery) + const addressSearchResult = contractList.filter(token => { + return token.address.toLowerCase() === searchQuery.toLowerCase() + }) + const results = [...addressSearchResult, ...fuseSearchResult] + this.props.onSearch({ searchQuery, results }) + } + + renderAdornment () { + return ( + + + + ) + } + + render () { + const { error } = this.props + const { searchQuery } = this.state + + return ( + this.handleSearch(e.target.value)} + error={error} + fullWidth + startAdornment={this.renderAdornment()} + /> + ) + } +} diff --git a/ui/app/components/pages/add-token/util.js b/ui/app/components/pages/add-token/util.js new file mode 100644 index 000000000..579c56cc0 --- /dev/null +++ b/ui/app/components/pages/add-token/util.js @@ -0,0 +1,13 @@ +import R from 'ramda' + +export function checkExistingAddresses (address, tokenList = []) { + if (!address) { + return false + } + + const matchesAddress = existingToken => { + return existingToken.address.toLowerCase() === address.toLowerCase() + } + + return R.any(matchesAddress)(tokenList) +} 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 new file mode 100644 index 000000000..9db9efc37 --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { DEFAULT_ROUTE, ADD_TOKEN_ROUTE } from '../../../routes' +import Button from '../../button' +import Identicon from '../../../components/identicon' +import TokenBalance from './token-balance' + +export default class ConfirmAddToken extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + history: PropTypes.object, + clearPendingTokens: PropTypes.func, + addTokens: PropTypes.func, + pendingTokens: PropTypes.object, + } + + componentDidMount () { + const { pendingTokens = {}, history } = this.props + + if (Object.keys(pendingTokens).length === 0) { + history.push(DEFAULT_ROUTE) + } + } + + getTokenName (name, symbol) { + return typeof name === 'undefined' + ? symbol + : `${name} (${symbol})` + } + + render () { + const { history, addTokens, clearPendingTokens, pendingTokens } = this.props + + return ( +
+
+
+ { this.context.t('addTokens') } +
+
+ { this.context.t('likeToAddTokens') } +
+
+
+
+
+
+ { this.context.t('token') } +
+
+ { this.context.t('balance') } +
+
+
+ { + Object.entries(pendingTokens) + .map(([ address, token ]) => { + const { name, symbol } = token + + return ( +
+
+ +
+ { this.getTokenName(name, symbol) } +
+
+
+ +
+
+ ) + }) + } +
+
+
+
+ + +
+
+ ) + } +} diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js new file mode 100644 index 000000000..0190024d9 --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import ConfirmAddToken from './confirm-add-token.component' + +const { addTokens, clearPendingTokens } = require('../../../actions') + +const mapStateToProps = ({ metamask }) => { + const { pendingTokens } = metamask + return { + pendingTokens, + } +} + +const mapDispatchToProps = dispatch => { + return { + addTokens: tokens => dispatch(addTokens(tokens)), + clearPendingTokens: () => dispatch(clearPendingTokens()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(ConfirmAddToken) diff --git a/ui/app/components/pages/confirm-add-token/index.js b/ui/app/components/pages/confirm-add-token/index.js new file mode 100644 index 000000000..b7decabec --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/index.js @@ -0,0 +1,2 @@ +import ConfirmAddToken from './confirm-add-token.container' +module.exports = ConfirmAddToken diff --git a/ui/app/components/pages/confirm-add-token/index.scss b/ui/app/components/pages/confirm-add-token/index.scss new file mode 100644 index 000000000..66146cf78 --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/index.scss @@ -0,0 +1,69 @@ +.confirm-add-token { + padding: 16px; + + &__header { + font-size: .75rem; + display: flex; + } + + &__token { + flex: 1; + min-width: 0; + } + + &__balance { + flex: 0 0 30%; + min-width: 0; + } + + &__token-list { + display: flex; + flex-flow: column nowrap; + + .token-balance { + display: flex; + flex-flow: row nowrap; + align-items: flex-start; + + &__amount { + color: $scorpion; + font-size: 43px; + line-height: 43px; + margin-right: 8px; + } + + &__symbol { + color: $scorpion; + font-size: 16px; + font-weight: 400; + line-height: 24px; + } + } + } + + &__token-list-item { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin-top: 8px; + box-sizing: border-box; + } + + &__data { + display: flex; + align-items: center; + padding: 8px; + } + + &__name { + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__token-icon { + margin-right: 12px; + flex: 0 0 auto; + } +} diff --git a/ui/app/components/pages/confirm-add-token/token-balance/index.js b/ui/app/components/pages/confirm-add-token/token-balance/index.js new file mode 100644 index 000000000..6fb5c8223 --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/token-balance/index.js @@ -0,0 +1,2 @@ +import TokenBalance from './token-balance.container' +module.exports = TokenBalance diff --git a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js new file mode 100644 index 000000000..976788d4c --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.component.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +export default class TokenBalance extends Component { + static propTypes = { + string: PropTypes.string, + symbol: PropTypes.string, + error: PropTypes.string, + } + + render () { + return ( +
{ this.props.string }
+ ) + } +} diff --git a/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js new file mode 100644 index 000000000..bc1289ce1 --- /dev/null +++ b/ui/app/components/pages/confirm-add-token/token-balance/token-balance.container.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import withTokenTracker from '../../../../helpers/with-token-tracker' +import TokenBalance from './token-balance.component' +import selectors from '../../../../selectors' + +const mapStateToProps = state => { + return { + userAddress: selectors.getSelectedAddress(state), + } +} + +export default compose( + connect(mapStateToProps), + withTokenTracker +)(TokenBalance) diff --git a/ui/app/components/pages/index.scss b/ui/app/components/pages/index.scss new file mode 100644 index 000000000..b15c59863 --- /dev/null +++ b/ui/app/components/pages/index.scss @@ -0,0 +1,5 @@ +@import './unlock-page/index'; + +@import './add-token/index'; + +@import './confirm-add-token/index'; diff --git a/ui/app/components/pages/unlock-page/index.scss b/ui/app/components/pages/unlock-page/index.scss new file mode 100644 index 000000000..3d44bd037 --- /dev/null +++ b/ui/app/components/pages/unlock-page/index.scss @@ -0,0 +1,51 @@ +.unlock-page { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 357px; + padding: 30px; + font-weight: 400; + color: $silver-chalice; + + &__container { + background: $white; + display: flex; + align-self: stretch; + justify-content: center; + flex: 1 0 auto; + } + + &__mascot-container { + margin-top: 24px; + } + + &__title { + margin-top: 5px; + font-size: 2rem; + font-weight: 800; + color: $tundora; + } + + &__form { + width: 100%; + margin: 56px 0 8px; + } + + &__links { + margin-top: 25px; + width: 100%; + } + + &__link { + cursor: pointer; + + &--import { + color: $ecstasy; + } + + &--use-classic { + margin-top: 10px; + } + } +} 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 0976d9506..a2f009d8f 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Button from 'material-ui/Button' +import Button from '@material-ui/core/Button' import TextField from '../../text-field' const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums') @@ -129,6 +129,7 @@ class UnlockPage extends Component { error={error} autoFocus autoComplete="current-password" + material fullWidth /> diff --git a/ui/app/components/pages/unlock-page/unlock-page.scss b/ui/app/components/pages/unlock-page/unlock-page.scss deleted file mode 100644 index 3d44bd037..000000000 --- a/ui/app/components/pages/unlock-page/unlock-page.scss +++ /dev/null @@ -1,51 +0,0 @@ -.unlock-page { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - width: 357px; - padding: 30px; - font-weight: 400; - color: $silver-chalice; - - &__container { - background: $white; - display: flex; - align-self: stretch; - justify-content: center; - flex: 1 0 auto; - } - - &__mascot-container { - margin-top: 24px; - } - - &__title { - margin-top: 5px; - font-size: 2rem; - font-weight: 800; - color: $tundora; - } - - &__form { - width: 100%; - margin: 56px 0 8px; - } - - &__links { - margin-top: 25px; - width: 100%; - } - - &__link { - cursor: pointer; - - &--import { - color: $ecstasy; - } - - &--use-classic { - margin-top: 10px; - } - } -} diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js index b958a2d2d..474fcf439 100644 --- a/ui/app/components/signature-request.js +++ b/ui/app/components/signature-request.js @@ -115,7 +115,7 @@ SignatureRequest.prototype.renderBalance = function () { return h('div.request-signature__balance', [ - h('div.request-signature__balance-text', [this.context.t('balance')]), + h('div.request-signature__balance-text', `${this.context.t('balance')}:`), h('div.request-signature__balance-value', `${balanceInEther} ETH`), diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js index 6fd3b82b4..4107068b5 100644 --- a/ui/app/components/text-field/text-field.component.js +++ b/ui/app/components/text-field/text-field.component.js @@ -1,59 +1,100 @@ -import React from 'react' +import React, { Component } from 'react' import PropTypes from 'prop-types' -import { withStyles } from 'material-ui/styles' -import { default as MaterialTextField } from 'material-ui/TextField' +import { withStyles } from '@material-ui/core/styles' +import { default as MaterialTextField } from '@material-ui/core/TextField' const styles = { - cssLabel: { - '&$cssFocused': { + materialLabel: { + '&$materialFocused': { color: '#aeaeae', }, - '&$cssError': { + '&$materialError': { color: '#aeaeae', }, fontWeight: '400', color: '#aeaeae', }, - cssFocused: {}, - cssUnderline: { + materialFocused: {}, + materialUnderline: { '&:after': { - backgroundColor: '#f7861c', + borderBottom: '2px solid #f7861c', }, }, - cssError: {}, + materialError: {}, + // Non-material styles + formLabel: { + '&$formLabelFocused': { + color: '#5b5b5b', + }, + '&$materialError': { + color: '#5b5b5b', + }, + }, + formLabelFocused: {}, + inputRoot: { + 'label + &': { + marginTop: '8px', + }, + border: '1px solid #d2d8dd', + height: '48px', + borderRadius: '4px', + padding: '0 16px', + display: 'flex', + alignItems: 'center', + '&:focus': { + border: '1px solid #2f9ae0', + }, + }, + inputLabel: { + fontSize: '.75rem', + transform: 'none', + transition: 'none', + position: 'initial', + color: '#5b5b5b', + }, } -const TextField = props => { - const { error, classes, ...textFieldProps } = props +class TextField extends Component { + static defaultProps = { + error: null, + } - return ( - - ) -} + static propTypes = { + error: PropTypes.string, + classes: PropTypes.object, + material: PropTypes.bool, + startAdornment: PropTypes.element, + } -TextField.defaultProps = { - error: null, -} + render () { + const { error, classes, material, startAdornment, ...textFieldProps } = this.props -TextField.propTypes = { - error: PropTypes.string, - classes: PropTypes.object, + return ( + + ) + } } export default withStyles(styles)(TextField) -- cgit v1.2.3 From c4e75a7075a2a7d4726475a37d11acb4b1eec5fa Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sun, 20 May 2018 14:08:45 -0700 Subject: Fix tests --- .../components/pages/add-token/add-token.component.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'ui/app/components') 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 885c7b2ac..0677b4317 100644 --- a/ui/app/components/pages/add-token/add-token.component.js +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -139,17 +139,12 @@ class AddToken extends Component { } async attemptToAutoFillTokenParams (address) { - const { symbol, decimals } = await this.tokenInfoGetter(address) - - if (symbol && decimals) { - this.setState({ - customSymbol: symbol, - customDecimals: decimals, - customSymbolError: null, - customDecimalsError: null, - autoFilled: true, - }) - } + const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address) + + const autoFilled = Boolean(symbol && decimals) + this.setState({ autoFilled }) + this.handleCustomSymbolChange(symbol || '') + this.handleCustomDecimalsChange(decimals) } handleCustomAddressChange (value) { -- cgit v1.2.3 From 8245bf010e22dda3e00c044429e3f5b880691fcb Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 21 May 2018 15:00:13 -0700 Subject: Update input field border on focus --- ui/app/components/text-field/text-field.component.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'ui/app/components') diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js index 4107068b5..b695a449a 100644 --- a/ui/app/components/text-field/text-field.component.js +++ b/ui/app/components/text-field/text-field.component.js @@ -31,6 +31,7 @@ const styles = { }, }, formLabelFocused: {}, + inputFocused: {}, inputRoot: { 'label + &': { marginTop: '8px', @@ -41,7 +42,7 @@ const styles = { padding: '0 16px', display: 'flex', alignItems: 'center', - '&:focus': { + '&$inputFocused': { border: '1px solid #2f9ae0', }, }, @@ -89,6 +90,7 @@ class TextField extends Component { root: material ? '' : classes.inputRoot, input: material ? '' : classes.input, underline: material ? classes.materialUnderline : '', + focused: material ? '' : classes.inputFocused, }, }} {...textFieldProps} -- cgit v1.2.3 From a7b7c8f0349ac8db16f264268745fd4b14e89422 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 23:34:40 -0700 Subject: newui - unlock - dont catch errors unrelated to tryUnlockMetamask --- ui/app/components/pages/unlock-page/unlock-page.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ui/app/components') 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 a2f009d8f..b6384b32d 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -37,8 +37,8 @@ class UnlockPage extends Component { tryUnlockMetamask (password) { const { tryUnlockMetamask, history } = this.props tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) .catch(({ message }) => this.setState({ error: message })) + .then(() => history.push(DEFAULT_ROUTE)) } handleSubmit (event) { @@ -55,8 +55,8 @@ class UnlockPage extends Component { this.setState({ error: null }) tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) .catch(({ message }) => this.setState({ error: message })) + .then(() => history.push(DEFAULT_ROUTE)) } handleInputChange ({ target }) { -- cgit v1.2.3 From d1f5d8ccc663bdc379864e155f12f580af127e8c Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 29 May 2018 09:35:18 -0500 Subject: Fix text field labels of first time flow. Add text fields to storybook (#4389) --- .../components/text-field/text-field.component.js | 95 ++++++++++++---------- ui/app/components/text-field/text-field.stories.js | 29 +++++++ 2 files changed, 80 insertions(+), 44 deletions(-) (limited to 'ui/app/components') diff --git a/ui/app/components/text-field/text-field.component.js b/ui/app/components/text-field/text-field.component.js index b695a449a..2c72d8124 100644 --- a/ui/app/components/text-field/text-field.component.js +++ b/ui/app/components/text-field/text-field.component.js @@ -1,8 +1,15 @@ -import React, { Component } from 'react' +import React from 'react' import PropTypes from 'prop-types' import { withStyles } from '@material-ui/core/styles' import { default as MaterialTextField } from '@material-ui/core/TextField' +const inputLabelBase = { + transform: 'none', + transition: 'none', + position: 'initial', + color: '#5b5b5b', +} + const styles = { materialLabel: { '&$materialFocused': { @@ -46,57 +53,57 @@ const styles = { border: '1px solid #2f9ae0', }, }, + largeInputLabel: { + ...inputLabelBase, + fontSize: '1rem', + }, inputLabel: { + ...inputLabelBase, fontSize: '.75rem', - transform: 'none', - transition: 'none', - position: 'initial', - color: '#5b5b5b', }, } -class TextField extends Component { - static defaultProps = { - error: null, - } +const TextField = props => { + const { error, classes, material, startAdornment, largeLabel, ...textFieldProps } = props - static propTypes = { - error: PropTypes.string, - classes: PropTypes.object, - material: PropTypes.bool, - startAdornment: PropTypes.element, - } + return ( + + ) +} - render () { - const { error, classes, material, startAdornment, ...textFieldProps } = this.props +TextField.defaultProps = { + error: null, +} - return ( - - ) - } +TextField.propTypes = { + error: PropTypes.string, + classes: PropTypes.object, + material: PropTypes.bool, + startAdornment: PropTypes.element, + largeLabel: PropTypes.bool, } export default withStyles(styles)(TextField) diff --git a/ui/app/components/text-field/text-field.stories.js b/ui/app/components/text-field/text-field.stories.js index ee3e5faaf..c00873b8a 100644 --- a/ui/app/components/text-field/text-field.stories.js +++ b/ui/app/components/text-field/text-field.stories.js @@ -22,3 +22,32 @@ storiesOf('TextField', module) error="Invalid value" /> ) + .add('Mascara text', () => + + ) + .add('Material text', () => + + ) + .add('Material password', () => + + ) + .add('Material error', () => + + ) -- cgit v1.2.3 From e3c9629130901c2268350a58eb18f2ab9f6ab72e Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 29 May 2018 10:03:08 -0700 Subject: Fix add-token token list placeholder width (#4365) --- .../pages/add-token/token-list/token-list-placeholder/index.scss | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ui/app/components') diff --git a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss index 9d0f4be32..cc495dfb0 100644 --- a/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss +++ b/ui/app/components/pages/add-token/token-list/token-list-placeholder/index.scss @@ -11,6 +11,10 @@ width: 50%; text-align: center; margin-top: 8px; + + @media screen and (max-width: 575px) { + width: 60%; + } } &__link { -- cgit v1.2.3 From 41e38fe5530bce1fd7d19060774179a215087fac Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 29 May 2018 10:23:06 -0700 Subject: Add notification for dropped retry transactions (#4363) --- ui/app/components/index.scss | 2 + ui/app/components/modals/index.scss | 1 + ui/app/components/modals/modal.js | 35 ++++++++++++- .../modals/transaction-confirmed/index.js | 2 + .../modals/transaction-confirmed/index.scss | 21 ++++++++ .../transaction-confirmed.component.js | 46 +++++++++++++++++ .../transaction-confirmed.container.js | 20 ++++++++ ui/app/components/pending-tx/confirm-send-ether.js | 60 +++++++++++++--------- ui/app/components/pending-tx/confirm-send-token.js | 57 +++++++++++++++----- ui/app/components/tx-list-item.js | 10 +++- 10 files changed, 215 insertions(+), 39 deletions(-) create mode 100644 ui/app/components/modals/index.scss create mode 100644 ui/app/components/modals/transaction-confirmed/index.js create mode 100644 ui/app/components/modals/transaction-confirmed/index.scss create mode 100644 ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js create mode 100644 ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js (limited to 'ui/app/components') diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index f3fe823f8..e69acff63 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -3,3 +3,5 @@ @import './info-box/index'; @import './pages/index'; + +@import './modals/index'; diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss new file mode 100644 index 000000000..ec6207f7e --- /dev/null +++ b/ui/app/components/modals/index.scss @@ -0,0 +1 @@ +@import './transaction-confirmed/index'; diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 43dcd20ae..841189277 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -20,6 +20,7 @@ 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 TransactionConfirmed = require('./transaction-confirmed') const accountModalStyle = { mobileModalStyle: { @@ -265,6 +266,37 @@ const MODALS = { }, }, + TRANSACTION_CONFIRMED: { + disableBackdropClick: true, + contents: [ + 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', + }, + 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)', + }, + contentStyle: { + borderRadius: '8px', + height: '100%', + }, + }, + DEFAULT: { contents: [], mobileModalStyle: {}, @@ -306,7 +338,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal) Modal.prototype.render = function () { const modal = MODALS[this.props.modalState.name || 'DEFAULT'] - const children = modal.contents + const { contents: children, disableBackdropClick = false } = modal const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] const contentStyle = modal.contentStyle || {} @@ -326,6 +358,7 @@ Modal.prototype.render = function () { modalStyle, contentStyle, backdropStyle: BACKDROPSTYLE, + closeOnClick: !disableBackdropClick, }, children, ) diff --git a/ui/app/components/modals/transaction-confirmed/index.js b/ui/app/components/modals/transaction-confirmed/index.js new file mode 100644 index 000000000..c8db91388 --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/index.js @@ -0,0 +1,2 @@ +import TransactionConfirmed from './transaction-confirmed.container' +module.exports = TransactionConfirmed diff --git a/ui/app/components/modals/transaction-confirmed/index.scss b/ui/app/components/modals/transaction-confirmed/index.scss new file mode 100644 index 000000000..f8cd1f212 --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/index.scss @@ -0,0 +1,21 @@ +.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 new file mode 100644 index 000000000..8d3b288ae --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js @@ -0,0 +1,46 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from '../../button' + +class TransactionConfirmed extends Component { + render () { + const { t } = this.context + + return ( +
+
+ +
+ { `${t('confirmed')}!` } +
+
+ { t('initialTransactionConfirmed') } +
+
+
+ +
+
+ ) + } +} + +TransactionConfirmed.propTypes = { + hideModal: PropTypes.func.isRequired, + onHide: PropTypes.func.isRequired, +} + +TransactionConfirmed.contextTypes = { + t: PropTypes.func, +} + +export default TransactionConfirmed diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js new file mode 100644 index 000000000..63872f7f2 --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js @@ -0,0 +1,20 @@ +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/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index c07c96ccc..5ad35c269 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -291,18 +291,48 @@ ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, curren : value } -ConfirmSendEther.prototype.editTransaction = function (txMeta) { +ConfirmSendEther.prototype.editTransaction = function () { const { editTransaction, history } = this.props + const txMeta = this.gatherTxMeta() editTransaction(txMeta) history.push(SEND_ROUTE) } -ConfirmSendEther.prototype.renderNetworkDisplay = function () { +ConfirmSendEther.prototype.renderHeaderRow = function (isTxReprice) { const windowType = window.METAMASK_UI_TYPE + const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && + windowType !== ENVIRONMENT_TYPE_POPUP - return (windowType === ENVIRONMENT_TYPE_NOTIFICATION || windowType === ENVIRONMENT_TYPE_POPUP) - ? h(NetworkDisplay) - : null + if (isTxReprice && isFullScreen) { + return null + } + + return ( + h('.page-container__header-row', [ + h('span.page-container__back-button', { + onClick: () => this.editTransaction(), + style: { + visibility: isTxReprice ? 'hidden' : 'initial', + }, + }, 'Edit'), + !isFullScreen && h(NetworkDisplay), + ]) + ) +} + +ConfirmSendEther.prototype.renderHeader = function (isTxReprice) { + const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm') + const subtitle = isTxReprice + ? this.context.t('speedUpSubtitle') + : this.context.t('pleaseReviewTransaction') + + return ( + h('.page-container__header', [ + this.renderHeaderRow(isTxReprice), + h('.page-container__title', title), + h('.page-container__subtitle', subtitle), + ]) + ) } ConfirmSendEther.prototype.render = function () { @@ -320,6 +350,7 @@ ConfirmSendEther.prototype.render = function () { }, } = this.props const txMeta = this.gatherTxMeta() + const isTxReprice = Boolean(txMeta.lastGasPrice) const txParams = txMeta.txParams || {} const { @@ -338,11 +369,6 @@ ConfirmSendEther.prototype.render = function () { totalInETH, } = this.getData() - const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm' - const subtitle = txMeta.lastGasPrice - ? 'Increase your gas fee to attempt to overwrite and speed up your transaction' - : 'Please review your transaction.' - const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency) const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency) @@ -362,19 +388,7 @@ ConfirmSendEther.prototype.render = function () { return ( // Main Send token Card h('.page-container', [ - h('.page-container__header', [ - h('.page-container__header-row', [ - h('span.page-container__back-button', { - onClick: () => this.editTransaction(txMeta), - style: { - visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden', - }, - }, 'Edit'), - this.renderNetworkDisplay(), - ]), - h('.page-container__title', title), - h('.page-container__subtitle', subtitle), - ]), + this.renderHeader(isTxReprice), h('.page-container__content', [ h(SenderToRecipient, { senderName: fromName, diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 656093b3d..ddaa13d22 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -12,6 +12,7 @@ const actions = require('../../actions') const clone = require('clone') const Identicon = require('../identicon') const GasFeeDisplay = require('../send/gas-fee-display-v2.js') +const NetworkDisplay = require('../network-display') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const { @@ -39,6 +40,11 @@ const { } = require('../../selectors') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') +const { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, +} = require('../../../../app/scripts/lib/enums') + ConfirmSendToken.contextTypes = { t: PropTypes.func, } @@ -430,6 +436,43 @@ ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, curren : value } +ConfirmSendToken.prototype.renderHeaderRow = function (isTxReprice) { + const windowType = window.METAMASK_UI_TYPE + const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && + windowType !== ENVIRONMENT_TYPE_POPUP + + if (isTxReprice && isFullScreen) { + return null + } + + return ( + h('.page-container__header-row', [ + h('span.page-container__back-button', { + onClick: () => this.editTransaction(), + style: { + visibility: isTxReprice ? 'hidden' : 'initial', + }, + }, 'Edit'), + !isFullScreen && h(NetworkDisplay), + ]) + ) +} + +ConfirmSendToken.prototype.renderHeader = function (isTxReprice) { + const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm') + const subtitle = isTxReprice + ? this.context.t('speedUpSubtitle') + : this.context.t('pleaseReviewTransaction') + + return ( + h('.page-container__header', [ + this.renderHeaderRow(isTxReprice), + h('.page-container__title', title), + h('.page-container__subtitle', subtitle), + ]) + ) +} + ConfirmSendToken.prototype.render = function () { const txMeta = this.gatherTxMeta() const { @@ -443,25 +486,13 @@ ConfirmSendToken.prototype.render = function () { }, } = this.getData() - this.inputs = [] - const isTxReprice = Boolean(txMeta.lastGasPrice) - const title = isTxReprice ? this.context.t('reprice_title') : this.context.t('confirm') - const subtitle = isTxReprice - ? this.context.t('reprice_subtitle') - : this.context.t('pleaseReviewTransaction') return ( h('div.confirm-screen-container.confirm-send-token', [ // Main Send token Card h('div.page-container', [ - h('div.page-container__header', [ - !txMeta.lastGasPrice && h('button.confirm-screen-back-button', { - onClick: () => this.editTransaction(txMeta), - }, this.context.t('edit')), - h('div.page-container__title', title), - h('div.page-container__subtitle', subtitle), - ]), + this.renderHeader(isTxReprice), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ h('div.confirm-screen-account-wrapper', [ diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index bd4ea80a6..ef441ff73 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -1,5 +1,7 @@ const Component = require('react').Component const PropTypes = require('prop-types') +const { compose } = require('recompose') +const { withRouter } = require('react-router-dom') const h = require('react-hyperscript') const connect = require('react-redux').connect const inherits = require('util').inherits @@ -16,13 +18,16 @@ const { conversionUtil, multiplyCurrencies } = require('../conversion-util') const { calcTokenAmount } = require('../token-util') const { getCurrentCurrency } = require('../selectors') +const { CONFIRM_TRANSACTION_ROUTE } = require('../routes') TxListItem.contextTypes = { t: PropTypes.func, } -module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem) - +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TxListItem) function mapStateToProps (state) { return { @@ -216,6 +221,7 @@ TxListItem.prototype.setSelectedToken = function (tokenAddress) { TxListItem.prototype.resubmit = function () { const { transactionId } = this.props this.props.retryTransaction(transactionId) + .then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)) } TxListItem.prototype.render = function () { -- cgit v1.2.3 From d40971e7f30f86629f661a53e4c7b51f34397d87 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 30 May 2018 09:23:31 -0700 Subject: Fix error handling on incorrect password (#4401) --- .../pages/unlock-page/unlock-page.component.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'ui/app/components') 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..8bc3897da 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 }) { -- cgit v1.2.3 From 389346913bf076fde6190a61afc4e3e4cd210afc Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 30 May 2018 10:38:53 -0700 Subject: Prevent loading screen from overlaying the app bar (#4417) --- ui/app/components/loading-screen/loading-screen.component.js | 6 +----- ui/app/components/pending-tx/index.js | 5 +---- 2 files changed, 2 insertions(+), 9 deletions(-) (limited to 'ui/app/components') 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/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) } } -- cgit v1.2.3