diff options
Merge branch 'develop' into i3725-refactor-send-component-
Diffstat (limited to 'ui')
63 files changed, 1233 insertions, 1019 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js index 7b838940a..8b9928d5b 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,4 +1,5 @@ const abi = require('human-standard-token-abi') +const pify = require('pify') const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const { getTokenAddressFromTokenObject } = require('./util') const { @@ -88,7 +89,7 @@ var actions = { REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION', revealSeedConfirmation: revealSeedConfirmation, requestRevealSeed: requestRevealSeed, - + requestRevealSeedWords, // unlock screen UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS', UNLOCK_FAILED: 'UNLOCK_FAILED', @@ -325,6 +326,7 @@ function tryUnlockMetamask (password) { background.verifySeedPhrase(err => { if (err) { dispatch(actions.displayWarning(err.message)) + return reject(err) } resolve() @@ -338,6 +340,7 @@ function tryUnlockMetamask (password) { .catch(err => { dispatch(actions.unlockFailed(err.message)) dispatch(actions.hideLoadingIndication()) + return Promise.reject(err) }) } } @@ -354,11 +357,13 @@ function transitionBackward () { } } -function clearSeedWordCache () { - log.debug(`background.clearSeedWordCache`) +function confirmSeedWords () { return dispatch => { + dispatch(actions.showLoadingIndication()) + log.debug(`background.clearSeedWordCache`) return new Promise((resolve, reject) => { background.clearSeedWordCache((err, account) => { + dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) @@ -372,22 +377,6 @@ function clearSeedWordCache () { } } -function confirmSeedWords () { - return async dispatch => { - dispatch(actions.showLoadingIndication()) - const account = await dispatch(clearSeedWordCache()) - return dispatch(setIsRevealingSeedWords(false)) - .then(() => { - dispatch(actions.hideLoadingIndication()) - return account - }) - .catch(() => { - dispatch(actions.hideLoadingIndication()) - return account - }) - } -} - function createNewVaultAndRestore (password, seed) { return (dispatch) => { dispatch(actions.showLoadingIndication()) @@ -450,6 +439,30 @@ function revealSeedConfirmation () { } } +function verifyPassword (password) { + return new Promise((resolve, reject) => { + background.submitPassword(password, error => { + if (error) { + return reject(error) + } + + resolve(true) + }) + }) +} + +function verifySeedPhrase () { + return new Promise((resolve, reject) => { + background.verifySeedPhrase((error, seedWords) => { + if (error) { + return reject(error) + } + + resolve(seedWords) + }) + }) +} + function requestRevealSeed (password) { return dispatch => { dispatch(actions.showLoadingIndication()) @@ -469,13 +482,29 @@ function requestRevealSeed (password) { } dispatch(actions.showNewVaultSeed(result)) + dispatch(actions.hideLoadingIndication()) resolve() }) }) }) - .then(() => dispatch(setIsRevealingSeedWords(true))) - .then(() => dispatch(actions.hideLoadingIndication())) - .catch(() => dispatch(actions.hideLoadingIndication())) + } +} + +function requestRevealSeedWords (password) { + return async dispatch => { + dispatch(actions.showLoadingIndication()) + log.debug(`background.submitPassword`) + + try { + await verifyPassword(password) + const seedWords = await verifySeedPhrase() + dispatch(actions.hideLoadingIndication()) + return seedWords + } catch (error) { + dispatch(actions.hideLoadingIndication()) + dispatch(actions.displayWarning(error.message)) + throw new Error(error.message) + } } } @@ -506,31 +535,26 @@ function addNewKeyring (type, opts) { } function importNewAccount (strategy, args) { - return (dispatch) => { - dispatch(actions.showLoadingIndication('This may take a while, be patient.')) - log.debug(`background.importAccountWithStrategy`) - return new Promise((resolve, reject) => { - background.importAccountWithStrategy(strategy, args, (err) => { - if (err) { - dispatch(actions.displayWarning(err.message)) - return reject(err) - } - log.debug(`background.getState`) - background.getState((err, newState) => { - dispatch(actions.hideLoadingIndication()) - if (err) { - dispatch(actions.displayWarning(err.message)) - return reject(err) - } - dispatch(actions.updateMetamaskState(newState)) - dispatch({ - type: actions.SHOW_ACCOUNT_DETAIL, - value: newState.selectedAddress, - }) - resolve(newState) - }) - }) + return async (dispatch) => { + let newState + dispatch(actions.showLoadingIndication('This may take a while, please be patient.')) + try { + log.debug(`background.importAccountWithStrategy`) + await pify(background.importAccountWithStrategy).call(background, strategy, args) + log.debug(`background.getState`) + newState = await pify(background.getState).call(background) + } catch (err) { + dispatch(actions.hideLoadingIndication()) + dispatch(actions.displayWarning(err.message)) + throw err + } + dispatch(actions.hideLoadingIndication()) + dispatch(actions.updateMetamaskState(newState)) + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAddress, }) + return newState } } @@ -1975,11 +1999,3 @@ function updateNetworkEndpointType (networkEndpointType) { value: networkEndpointType, } } - -function setIsRevealingSeedWords (reveal) { - return dispatch => { - log.debug(`background.setIsRevealingSeedWords`) - background.setIsRevealingSeedWords(reveal) - return forceUpdateMetamaskState(dispatch) - } -} diff --git a/ui/app/app.js b/ui/app/app.js index 6511979b2..0c0c6ec44 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -1,7 +1,7 @@ const { Component } = require('react') const PropTypes = require('prop-types') const connect = require('react-redux').connect -const { Route, Switch, withRouter } = require('react-router-dom') +const { Route, Switch, withRouter, matchPath } = require('react-router-dom') const { compose } = require('recompose') const h = require('react-hyperscript') const actions = require('./actions') @@ -22,16 +22,14 @@ const Home = require('./components/pages/home') const Authenticated = require('./components/pages/authenticated') const Initialized = require('./components/pages/initialized') const Settings = require('./components/pages/settings') -const UnlockPage = require('./components/pages/unlock') +const UnlockPage = require('./components/pages/unlock-page') const RestoreVaultPage = require('./components/pages/keychains/restore-vault') -const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') +const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed') const AddTokenPage = require('./components/pages/add-token') const CreateAccountPage = require('./components/pages/create-account') const NoticeScreen = require('./components/pages/notice') -const Loading = require('./components/loading') -const NetworkIndicator = require('./components/network') -const Identicon = require('./components/identicon') +const Loading = require('./components/loading-screen') const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const NetworkDropdown = require('./components/dropdowns/network-dropdown') const AccountMenu = require('./components/account-menu') @@ -39,6 +37,8 @@ const AccountMenu = require('./components/account-menu') // Global Modals const Modal = require('./components/modals/index').Modal +const AppHeader = require('./components/app-header') + // Routes const { DEFAULT_ROUTE, @@ -56,20 +56,11 @@ const { class App extends Component { componentWillMount () { - const { - currentCurrency, - setCurrentCurrencyToUSD, - isRevealingSeedWords, - clearSeedWords, - } = this.props + const { currentCurrency, setCurrentCurrencyToUSD } = this.props if (!currentCurrency) { setCurrentCurrencyToUSD() } - - if (isRevealingSeedWords) { - clearSeedWords() - } } renderRoutes () { @@ -78,11 +69,11 @@ class App extends Component { return ( h(Switch, [ h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }), - h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }), h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }), - h(Initialized, { path: SETTINGS_ROUTE, component: Settings }), h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }), - h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), + h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }), + h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }), + h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }), h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }), h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }), h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }), @@ -92,6 +83,15 @@ class App extends Component { ) } + renderAppHeader () { + const { location } = this.props + const isInitializing = matchPath(location.pathname, { + path: INITIALIZE_ROUTE, exact: false, + }) + + return isInitializing ? null : h(AppHeader) + } + render () { const { isLoading, @@ -128,8 +128,7 @@ class App extends Component { // global modal h(Modal, {}, []), - // app bar - this.renderAppBar(), + this.renderAppHeader(), // sidebar this.renderSidebar(), @@ -144,6 +143,7 @@ class App extends Component { (isLoading || isLoadingNetwork) && h(Loading, { loadingMessage: loadMessage, + fullScreen: true, }), // content @@ -205,110 +205,6 @@ class App extends Component { ]) } - renderAppBar () { - const { - isUnlocked, - network, - provider, - networkDropdownOpen, - showNetworkDropdown, - hideNetworkDropdown, - isInitialized, - welcomeScreenSeen, - isPopup, - betaUI, - } = this.props - - if (window.METAMASK_UI_TYPE === 'notification') { - return null - } - - const props = this.props - const {isMascara, isOnboarding} = props - - // Do not render header if user is in mascara onboarding - if (isMascara && isOnboarding) { - return null - } - - // Do not render header if user is in mascara buy ether - if (isMascara && props.currentView.name === 'buyEth') { - return null - } - - return ( - - h('.full-width', { - style: {}, - }, [ - - (isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', { - className: classnames({ - 'app-header--initialized': !isOnboarding, - }), - }, [ - h('div.app-header-contents', {}, [ - h('div.left-menu-wrapper', { - onClick: () => props.history.push(DEFAULT_ROUTE), - }, [ - // mini logo - h('img.metafox-icon', { - height: 42, - width: 42, - src: '/images/metamask-fox.svg', - }), - - // metamask name - h('.flex-row', [ - h('h1', this.context.t('appName')), - h('div.beta-label', this.context.t('beta')), - ]), - - ]), - - betaUI && isInitialized && h('div.header__right-actions', [ - h('div.network-component-wrapper', { - style: {}, - }, [ - // Network Indicator - h(NetworkIndicator, { - network, - provider, - disabled: this.props.location.pathname === CONFIRM_TRANSACTION_ROUTE, - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - return networkDropdownOpen === false - ? showNetworkDropdown() - : hideNetworkDropdown() - }, - }), - - ]), - - isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [ - h(Identicon, { - address: this.props.selectedAddress, - diameter: 32, - }), - ]), - ]), - ]), - ]), - - !isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [ - h('h2', { - className: classnames({ - 'alpha-warning': welcomeScreenSeen, - 'alpha-warning-welcome-screen': !welcomeScreenSeen, - }), - }, 'Please be aware that this version is still under development'), - ]), - - ]) - ) - } - toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box @@ -402,8 +298,6 @@ App.propTypes = { isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, t: PropTypes.func, - isRevealingSeedWords: PropTypes.bool, - clearSeedWords: PropTypes.func, } function mapStateToProps (state) { @@ -484,7 +378,6 @@ function mapDispatchToProps (dispatch, ownProps) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), - clearSeedWords: () => dispatch(actions.confirmSeedWords()), } } 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 && ( + <div + className="account-menu__icon" + onClick={toggleAccountMenu} + > + <Identicon + address={selectedAddress} + diameter={32} + /> + </div> + ) + } + + render () { + const { + network, + provider, + history, + location, + isUnlocked, + } = this.props + + if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { + return null + } + + return ( + <div + className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}> + <div className="app-header__contents"> + <div + className="app-header__logo-container" + onClick={() => history.push(DEFAULT_ROUTE)} + > + <img + className="app-header__metafox" + src="/images/metamask-fox.svg" + height={42} + width={42} + /> + <div className="flex-row"> + <h1>{ this.context.t('appName') }</h1> + <div className="app-header__beta-label"> + { this.context.t('beta') } + </div> + </div> + </div> + <div className="app-header__account-menu-container"> + <div className="network-component-wrapper"> + <NetworkIndicator + network={network} + provider={provider} + onClick={event => this.handleNetworkIndicatorClick(event)} + disabled={location.pathname === CONFIRM_TRANSACTION_ROUTE} + /> + </div> + { this.renderAccountMenu() } + </div> + </div> + </div> + ) + } +} + +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/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', () => + <Button + onClick={action('clicked')} + type="primary" + > + {text('text', 'Click me')} + </Button> + ) + .add('secondary', () => ( + <Button + onClick={action('clicked')} + type="secondary" + > + {text('text', 'Click me')} + </Button> + )) + .add('large primary', () => ( + <Button + onClick={action('clicked')} + type="primary" + large + > + {text('text', 'Click me')} + </Button> + )) + .add('large secondary', () => ( + <Button + onClick={action('clicked')} + type="secondary" + large + > + {text('text', 'Click me')} + </Button> + )) 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 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/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 845e4ddc2..df933e363 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -284,8 +284,7 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasPrice, min: forceGasMin || MIN_GAS_PRICE_GWEI, - // max: 1000, - step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), + step: 1, onChange: value => this.convertAndSetGasPrice(value), title: this.context.t('gasPrice'), copy: this.context.t('gasPriceCalculation'), @@ -294,7 +293,6 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasLimit, min: 1, - // max: 100000, step: 1, onChange: value => this.convertAndSetGasLimit(value), title: this.context.t('gasLimit'), diff --git a/ui/app/components/export-text-container/export-text-container.component.js b/ui/app/components/export-text-container/export-text-container.component.js new file mode 100644 index 000000000..c2546fa9b --- /dev/null +++ b/ui/app/components/export-text-container/export-text-container.component.js @@ -0,0 +1,45 @@ +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const copyToClipboard = require('copy-to-clipboard') +const { exportAsFile } = require('../../util') + +class ExportTextContainer extends Component { + render () { + const { text = '', filename = '' } = this.props + const { t } = this.context + + return ( + h('.export-text-container', [ + h('.export-text-container__text-container', [ + h('.export-text-container__text', text), + ]), + h('.export-text-container__buttons-container', [ + h('.export-text-container__button.export-text-container__button--copy', { + onClick: () => copyToClipboard(text), + }, [ + h('img', { src: 'images/copy-to-clipboard.svg' }), + h('.export-text-container__button-text', t('copyToClipboard')), + ]), + h('.export-text-container__button', { + onClick: () => exportAsFile(filename, text), + }, [ + h('img', { src: 'images/download.svg' }), + h('.export-text-container__button-text', t('saveAsCsvFile')), + ]), + ]), + ]) + ) + } +} + +ExportTextContainer.propTypes = { + text: PropTypes.string, + filename: PropTypes.string, +} + +ExportTextContainer.contextTypes = { + t: PropTypes.func, +} + +module.exports = ExportTextContainer diff --git a/ui/app/components/export-text-container/export-text-container.scss b/ui/app/components/export-text-container/export-text-container.scss new file mode 100644 index 000000000..975d62f70 --- /dev/null +++ b/ui/app/components/export-text-container/export-text-container.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/export-text-container/index.js b/ui/app/components/export-text-container/index.js new file mode 100644 index 000000000..b2864a717 --- /dev/null +++ b/ui/app/components/export-text-container/index.js @@ -0,0 +1,2 @@ +const ExportTextContainer = require('./export-text-container.component') +module.exports = ExportTextContainer 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.js b/ui/app/components/loading-screen/loading-screen.component.js index b9afc550f..bce2a4aac 100644 --- a/ui/app/components/loading.js +++ b/ui/app/components/loading-screen/loading-screen.component.js @@ -2,8 +2,9 @@ const { Component } = require('react') const h = require('react-hyperscript') const PropTypes = require('prop-types') const classnames = require('classnames') +const Spinner = require('../spinner') -class LoadingIndicator extends Component { +class LoadingScreen extends Component { renderMessage () { const { loadingMessage } = this.props return loadingMessage && h('span', loadingMessage) @@ -14,9 +15,9 @@ class LoadingIndicator extends Component { h('.loading-overlay', { className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), }, [ - h('.flex-center.flex-column', [ - h('img', { - src: 'images/loading.svg', + h('.loading-overlay__container', [ + h(Spinner, { + color: '#F7C06C', }), this.renderMessage(), @@ -26,9 +27,9 @@ class LoadingIndicator extends Component { } } -LoadingIndicator.propTypes = { +LoadingScreen.propTypes = { loadingMessage: PropTypes.string, fullScreen: PropTypes.bool, } -module.exports = LoadingIndicator +module.exports = LoadingScreen diff --git a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js index fafe1c19e..84d8296b0 100644 --- a/ui/app/components/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/page-container/page-container-footer/page-container-footer.component.js @@ -1,5 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import Button from './components/button' export default class PageContainerFooter extends Component { @@ -27,20 +28,24 @@ export default class PageContainerFooter extends Component { return ( <div className="page-container__footer"> - <button - className="btn-secondary--lg page-container__footer-button" + <Button + type="secondary" + large={true} + className="page-container__footer-button" onClick={() => onCancel()} > { this.context.t('cancel') || cancelText } - </button> + </Button> - <button - className="btn-primary--lg page-container__footer-button" + <Button + type="primary" + large={true} + className="page-container__footer-button" disabled={disabled} onClick={e => onSubmit(e)} > { this.context.t('next') || submitText } - </button> + </Button> </div> ) diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 1e7fc3a41..6240499be 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -192,7 +192,7 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) if (symbol && decimals) { this.setState({ customSymbol: symbol, - customDecimals: decimals.toString(), + customDecimals: decimals, autoFilled: true, }) } 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)) } diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js index 90b8e1d37..9110f8202 100644 --- a/ui/app/components/pages/home.js +++ b/ui/app/components/pages/home.js @@ -21,7 +21,7 @@ const QrView = require('../../components/qr-code') // Routes const { - REVEAL_SEED_ROUTE, + INITIALIZE_BACKUP_PHRASE_ROUTE, RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, NOTICE_ROUTE, @@ -69,7 +69,7 @@ class Home extends Component { log.debug('rendering seed words') return h(Redirect, { to: { - pathname: REVEAL_SEED_ROUTE, + pathname: INITIALIZE_BACKUP_PHRASE_ROUTE, }, }) } diff --git a/ui/app/components/pages/keychains/reveal-seed.js b/ui/app/components/pages/keychains/reveal-seed.js index 247f3c8e2..685c81074 100644 --- a/ui/app/components/pages/keychains/reveal-seed.js +++ b/ui/app/components/pages/keychains/reveal-seed.js @@ -2,11 +2,27 @@ const { Component } = require('react') const { connect } = require('react-redux') const PropTypes = require('prop-types') const h = require('react-hyperscript') -const { exportAsFile } = require('../../../util') -const { requestRevealSeed, confirmSeedWords } = require('../../../actions') +const classnames = require('classnames') + +const { requestRevealSeedWords } = require('../../../actions') const { DEFAULT_ROUTE } = require('../../../routes') +const ExportTextContainer = require('../../export-text-container') + +const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' +const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' class RevealSeedPage extends Component { + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + } + } + componentDidMount () { const passwordBox = document.getElementById('password-box') if (passwordBox) { @@ -14,182 +30,135 @@ class RevealSeedPage extends Component { } } - checkConfirmation (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.revealSeedWords() - } + handleSubmit (event) { + event.preventDefault() + this.setState({ seedWords: null, error: null }) + this.props.requestRevealSeedWords(this.state.password) + .then(seedWords => this.setState({ seedWords, screen: REVEAL_SEED_SCREEN })) + .catch(error => this.setState({ error: error.message })) } - revealSeedWords () { - const password = document.getElementById('password-box').value - this.props.requestRevealSeed(password) - } - - renderSeed () { - const { seedWords, confirmSeedWords, history } = this.props - + renderWarning () { return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginTop: 36, - marginBottom: 8, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - 'Vault Created', - ]), - - h('div', { - style: { - fontSize: '1em', - marginTop: '10px', - textAlign: 'center', - }, - }, [ - h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'), - ]), - - h('textarea.twelve-word-phrase', { - readOnly: true, - value: seedWords, + h('.page-container__warning-container', [ + h('img.page-container__warning-icon', { + src: 'images/warning.svg', }), - - h('button.primary', { - onClick: () => confirmSeedWords().then(() => history.push(DEFAULT_ROUTE)), - style: { - margin: '24px', - fontSize: '0.9em', - marginBottom: '10px', - }, - }, 'I\'ve copied it somewhere safe'), - - h('button.primary', { - onClick: () => exportAsFile(`MetaMask Seed Words`, seedWords), - style: { - margin: '10px', - fontSize: '0.9em', - }, - }, 'Save Seed Words As File'), + h('.page-container__warning-message', [ + h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]), + h('div', [this.context.t('revealSeedWordsWarning')]), + ]), ]) ) } - renderConfirmation () { - const { history, warning, inProgress } = this.props + renderContent () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptContent() + : this.renderRevealSeedContent() + } + + renderPasswordPromptContent () { + const { t } = this.context return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', { - style: { maxWidth: '420px' }, + h('form', { + onSubmit: event => this.handleSubmit(event), }, [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: 24, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - 'Reveal Seed Words', - ]), - - h('.div', { - style: { - display: 'flex', - flexDirection: 'column', - padding: '20px', - justifyContent: 'center', - }, - }, [ - - h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'), - - // confirmation - h('input.large-input.letter-spacey', { + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { type: 'password', + placeholder: t('password'), id: 'password-box', - placeholder: 'Enter your password to confirm', - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: '12px', - }, + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) + ) + } - h('.flex-row.flex-start', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - // cancel - h('button.primary', { - onClick: () => history.push(DEFAULT_ROUTE), - }, 'CANCEL'), - - // submit - h('button.primary', { - style: { marginLeft: '10px' }, - onClick: this.revealSeedWords.bind(this), - }, 'OK'), + renderRevealSeedContent () { + const { t } = this.context - ]), + return ( + h('div', [ + h('label.reveal-seed__label', t('yourPrivateSeedPhrase')), + h(ExportTextContainer, { + text: this.state.seedWords, + filename: t('metamaskSeedWords'), + }), + ]) + ) + } - warning && ( - h('span.error', { - style: { - margin: '20px', - }, - }, warning.split('-')) - ), - - inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') - ), - ]), + renderFooter () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptFooter() + : this.renderRevealSeedFooter() + } + + renderPasswordPromptFooter () { + return ( + h('.page-container__footer', [ + h('button.btn-secondary--lg.page-container__footer-button', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + h('button.btn-primary--lg.page-container__footer-button', { + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]) + ) + } + + renderRevealSeedFooter () { + return ( + h('.page-container__footer', [ + h('button.btn-secondary--lg.page-container__footer-button', { + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), ]) ) } render () { - return this.props.seedWords - ? this.renderSeed() - : this.renderConfirmation() + return ( + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('revealSeedWordsTitle')), + h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')), + ]), + h('.page-container__content', [ + this.renderWarning(), + h('.reveal-seed__content', [ + this.renderContent(), + ]), + ]), + this.renderFooter(), + ]) + ) } } RevealSeedPage.propTypes = { - requestRevealSeed: PropTypes.func, - confirmSeedWords: PropTypes.func, - seedWords: PropTypes.string, - inProgress: PropTypes.bool, + requestRevealSeedWords: PropTypes.func, history: PropTypes.object, - warning: PropTypes.string, } -const mapStateToProps = state => { - const { appState: { warning }, metamask: { seedWords } } = state - - return { - warning, - seedWords, - } +RevealSeedPage.contextTypes = { + t: PropTypes.func, } const mapDispatchToProps = dispatch => { return { - requestRevealSeed: password => dispatch(requestRevealSeed(password)), - confirmSeedWords: () => dispatch(confirmSeedWords()), + requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), } } -module.exports = connect(mapStateToProps, mapDispatchToProps)(RevealSeedPage) +module.exports = connect(null, mapDispatchToProps)(RevealSeedPage) 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..d5e2a3224 --- /dev/null +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -0,0 +1,181 @@ +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 ( + <Button + type="submit" + style={style} + disabled={!this.state.password} + fullWidth + variant="raised" + size="large" + onClick={event => this.handleSubmit(event)} + disableRipple + > + { this.context.t('login') } + </Button> + ) + } + + render () { + const { error } = this.state + + return ( + <div className="unlock-page__container"> + <div className="unlock-page"> + <div className="unlock-page__mascot-container"> + <Mascot + animationEventEmitter={this.animationEventEmitter} + width="120" + height="120" + /> + </div> + <h1 className="unlock-page__title"> + { this.context.t('welcomeBack') } + </h1> + <div>{ this.context.t('unlockMessage') }</div> + <form + className="unlock-page__form" + onSubmit={event => this.handleSubmit(event)} + > + <TextField + id="password" + label="Password" + type="password" + value={this.state.password} + onChange={event => this.handleInputChange(event)} + error={error} + autoFocus + autoComplete="current-password" + fullWidth + /> + </form> + { this.renderSubmitButton() } + <div className="unlock-page__links"> + <div + className="unlock-page__link" + onClick={() => { + 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') } + </div> + <div + className="unlock-page__link unlock-page__link--import" + onClick={() => { + 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') } + </div> + </div> + </div> + </div> + ) + } +} + +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..3d44bd037 --- /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: 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.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/pending-tx/index.js b/ui/app/components/pending-tx/index.js index 6ee83ba7e..893538bcf 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -8,11 +8,11 @@ const abiDecoder = require('abi-decoder') abiDecoder.addABI(abi) const inherits = require('util').inherits const actions = require('../../actions') -const util = require('../../util') +const { getSymbolAndDecimals } = require('../../token-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', @@ -26,6 +26,7 @@ function mapStateToProps (state) { const { conversionRate, identities, + tokens: existingTokens, } = state.metamask const accounts = state.metamask.accounts const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] @@ -33,6 +34,7 @@ function mapStateToProps (state) { conversionRate, identities, selectedAddress, + existingTokens, } } @@ -66,6 +68,7 @@ PendingTx.prototype.componentDidUpdate = function (prevProps, prevState) { } PendingTx.prototype.setTokenData = async function () { + const { existingTokens } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -89,30 +92,15 @@ PendingTx.prototype.setTokenData = async function () { } if (isTokenTransaction) { - const token = util.getContractAtAddress(txParams.to) - const results = await Promise.all([ - token.symbol(), - token.decimals(), - ]) - const [ symbol, decimals ] = results - - if (symbol[0] && decimals[0]) { - this.setState({ - transactionType: TX_TYPES.SEND_TOKEN, - tokenAddress: txParams.to, - tokenSymbol: symbol[0], - tokenDecimals: decimals[0], - isFetching: false, - }) - } else { - this.setState({ - transactionType: TX_TYPES.SEND_TOKEN, - tokenAddress: txParams.to, - tokenSymbol: null, - tokenDecimals: null, - isFetching: false, - }) - } + const { symbol, decimals } = await getSymbolAndDecimals(txParams.to, existingTokens) + + this.setState({ + transactionType: TX_TYPES.SEND_TOKEN, + tokenAddress: txParams.to, + tokenSymbol: symbol, + tokenDecimals: decimals, + isFetching: false, + }) } else { this.setState({ transactionType: TX_TYPES.SEND_ETHER, diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js index a7bd5d7ea..90fb2b66c 100644 --- a/ui/app/components/send/currency-display.js +++ b/ui/app/components/send/currency-display.js @@ -89,7 +89,6 @@ CurrencyDisplay.prototype.render = function () { } = this.props const valueToRender = this.getValueToRender() - const convertedValueToRender = this.getConvertedValueToRender(valueToRender) return h('div', { @@ -97,22 +96,24 @@ CurrencyDisplay.prototype.render = function () { style: { borderColor: inError ? 'red' : null, }, - onClick: () => this.currencyInput.focus(), + onClick: () => this.currencyInput && this.currencyInput.focus(), }, [ h('div.currency-display__primary-row', [ h('div.currency-display__input-wrapper', [ - h(CurrencyInput, { + h(readOnly ? 'input' : CurrencyInput, { className: primaryBalanceClassName, value: `${valueToRender}`, placeholder: '0', readOnly, - onInputChange: newValue => { - handleChange(this.getAmount(newValue)) - }, - inputRef: input => { this.currencyInput = input }, + ...(!readOnly ? { + onInputChange: newValue => { + handleChange(this.getAmount(newValue)) + }, + inputRef: input => { this.currencyInput = input }, + } : {}), }), h('span.currency-display__currency-symbol', primaryCurrency), diff --git a/ui/app/components/send_/send.constants.js b/ui/app/components/send_/send.constants.js index d047ed704..b59fcaaf0 100644 --- a/ui/app/components/send_/send.constants.js +++ b/ui/app/components/send_/send.constants.js @@ -1,8 +1,8 @@ const ethUtil = require('ethereumjs-util') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') -const MIN_GAS_PRICE_HEX = (100000000).toString(16) -const MIN_GAS_PRICE_DEC = '100000000' +const MIN_GAS_PRICE_DEC = '0' +const MIN_GAS_PRICE_HEX = (parseInt(MIN_GAS_PRICE_DEC)).toString(16) const MIN_GAS_LIMIT_DEC = '21000' const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16) diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index fd4a80a4a..22ab64426 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -55,6 +55,10 @@ function ShapeshiftForm () { } } +ShapeshiftForm.prototype.getCoinPair = function () { + return `${this.state.depositCoin.toUpperCase()}_ETH` +} + ShapeshiftForm.prototype.componentWillMount = function () { this.props.shapeShiftSubview() } @@ -120,14 +124,12 @@ ShapeshiftForm.prototype.renderMetadata = function (label, value) { } ShapeshiftForm.prototype.renderMarketInfo = function () { - const { depositCoin } = this.state - const coinPair = `${depositCoin}_eth` const { tokenExchangeRates } = this.props const { limit, rate, minimum, - } = tokenExchangeRates[coinPair] || {} + } = tokenExchangeRates[this.getCoinPair()] || {} return h('div.shapeshift-form__metadata', {}, [ @@ -172,10 +174,9 @@ ShapeshiftForm.prototype.renderQrCode = function () { ShapeshiftForm.prototype.render = function () { const { coinOptions, btnClass, warning } = this.props - const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state - const coinPair = `${depositCoin}_eth` + const { errorMessage, showQrCode, depositAddress } = this.state const { tokenExchangeRates } = this.props - const token = tokenExchangeRates[coinPair] + const token = tokenExchangeRates[this.getCoinPair()] return h('div.shapeshift-form-wrapper', [ showQrCode 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 ( + <div className={`spinner ${className}`}> + <svg className="lds-spinner" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style={{background: 'none'}}> + <g transform="rotate(0 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(30 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(60 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.75s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(90 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(120 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(150 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(180 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(210 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(240 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.25s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(270 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(300 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" /> + </rect> + </g> + <g transform="rotate(330 50 50)"> + <rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> + <animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" /> + </rect> + </g> + </svg> + </div> + ) +} + +Spinner.propTypes = { + className: PropTypes.string, + color: PropTypes.string, +} + +module.exports = Spinner 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 ( + <MaterialTextField + error={Boolean(error)} + helperText={error} + InputLabelProps={{ + FormLabelClasses: { + root: classes.cssLabel, + focused: classes.cssFocused, + }, + }} + InputProps={{ + classes: { + underline: classes.cssUnderline, + }, + }} + {...textFieldProps} + /> + ) +} + +TextField.defaultProps = { + error: null, +} + +TextField.propTypes = { + error: PropTypes.string, + classes: PropTypes.object, +} + +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 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', () => + <TextField + label="Text" + type="text" + /> + ) + .add('password', () => + <TextField + label="Password" + type="password" + /> + ) + .add('error', () => + <TextField + type="text" + label="Name" + error="Invalid value" + /> + ) 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')), ]) } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index b71538e31..fb38aaa76 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -13,7 +13,7 @@ const SignatureRequest = require('./components/signature-request') // const PendingMsg = require('./components/pending-msg') // const PendingPersonalMsg = require('./components/pending-personal-msg') // const PendingTypedMsg = require('./components/pending-typed-msg') -const Loading = require('./components/loading') +const Loading = require('./components/loading-screen') const { DEFAULT_ROUTE } = require('./routes') module.exports = compose( diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss index 725da9d39..b29afdc8c 100644 --- a/ui/app/css/itcss/components/account-dropdown.scss +++ b/ui/app/css/itcss/components/account-dropdown.scss @@ -33,7 +33,7 @@ margin-top: 4px; position: relative; } - + &__account-name { font-size: 16px; margin-left: 8px; @@ -50,7 +50,6 @@ font-family: Roboto; line-height: 16px; font-size: 12px; - font-weight: 300; } &__account-primary-balance { diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index c4037d862..824b2ddb6 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -40,7 +40,6 @@ font-size: 12px; line-height: 23px; padding: 0 24px; - font-weight: 300; } &__item-icon { @@ -113,7 +112,6 @@ &__name { color: $white; font-size: 18px; - font-weight: 300; } &__balance { @@ -124,7 +122,6 @@ &__action { font-size: 16px; line-height: 18px; - font-weight: 300; cursor: pointer; } } diff --git a/ui/app/css/itcss/components/add-token.scss b/ui/app/css/itcss/components/add-token.scss index 01579c27c..a3ea0d85b 100644 --- a/ui/app/css/itcss/components/add-token.scss +++ b/ui/app/css/itcss/components/add-token.scss @@ -377,7 +377,6 @@ &__amount { color: $scorpion; font-size: 43px; - font-weight: 300; line-height: 43px; margin-right: 8px; } diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss index 04e1ed96e..86daf60d8 100644 --- a/ui/app/css/itcss/components/buttons.scss +++ b/ui/app/css/itcss/components/buttons.scss @@ -18,6 +18,7 @@ padding: 0 20px; min-width: 140px; text-transform: uppercase; + outline: none; } .btn-primary, diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index 47762e8de..44cfcf870 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -175,7 +175,6 @@ margin-top: 12px; text-align: center; font-size: 40px; - font-weight: 300; line-height: 53px; flex: 0 0 auto; } @@ -235,7 +234,6 @@ section .confirm-screen-account-number, padding-left: 35px; font-size: 16px; line-height: 22px; - font-weight: 300; &:not(:last-of-type) { border-bottom: 1px solid $alto; @@ -336,7 +334,6 @@ section .confirm-screen-account-number, border-width: 0; box-shadow: none; flex: 1 0 auto; - font-weight: 300; margin: 0 5px; } @@ -353,6 +350,5 @@ section .confirm-screen-account-number, box-shadow: none; cursor: pointer; flex: 1 0 auto; - font-weight: 300; margin: 0 5px; } diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss index e043c1966..36d843c79 100644 --- a/ui/app/css/itcss/components/currency-display.scss +++ b/ui/app/css/itcss/components/currency-display.scss @@ -7,7 +7,6 @@ color: $scorpion; font-family: Roboto; font-size: 16px; - font-weight: 300; padding: 8px 10px; position: relative; diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss index eeed9ee06..cef61d0e2 100644 --- a/ui/app/css/itcss/components/header.scss +++ b/ui/app/css/itcss/components/header.scss @@ -1,15 +1,15 @@ .app-header { align-items: center; - visibility: visible; background: $gallery; position: relative; z-index: $header-z-index; display: flex; flex-flow: column nowrap; + width: 100%; + flex: 0 0 auto; @media screen and (max-width: 575px) { padding: 12px; - width: 100%; box-shadow: 0 0 0 1px rgba(0, 0, 0, .08); z-index: $mobile-header-z-index; } @@ -17,48 +17,75 @@ @media screen and (min-width: 576px) { height: 75px; justify-content: center; + + &--back-drop { + &::after { + content: ''; + position: absolute; + width: 100%; + height: 32px; + background: $gallery; + bottom: -32px; + } + } } - .metafox-icon { + &__metafox { cursor: pointer; } -} - -.app-header--initialized { - @media screen and (min-width: 576px) { - &::after { - content: ''; - position: absolute; - width: 100%; - height: 32px; - background: $gallery; - bottom: -32px; + &__beta-label { + font-family: Roboto; + text-transform: uppercase; + font-weight: 500; + font-size: .8rem; + color: $buttercup; + margin-left: 5px; + line-height: initial; + + @media screen and (max-width: 575px) { + display: none; } } -} -.app-header-contents { - display: flex; - justify-content: space-between; - flex-flow: row nowrap; - width: 100%; - height: 6.9vh; + &__contents { + display: flex; + justify-content: space-between; + flex-flow: row nowrap; + width: 100%; - @media screen and (max-width: 575px) { - height: 100%; - } + @media screen and (max-width: 575px) { + height: 100%; + } - @media screen and (min-width: 576px) { - width: 85vw; + @media screen and (min-width: 576px) { + width: 85vw; + } + + @media screen and (min-width: 769px) { + width: 80vw; + } + + @media screen and (min-width: 1281px) { + width: 62vw; + } } - @media screen and (min-width: 769px) { - width: 80vw; + &__logo-container { + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; } - @media screen and (min-width: 1281px) { - width: 62vw; + &__account-menu-container { + display: flex; + flex-flow: row nowrap; + align-items: center; + + .identicon { + cursor: pointer; + } } } @@ -76,20 +103,6 @@ } } -.beta-label { - font-family: Roboto; - text-transform: uppercase; - font-weight: 500; - font-size: .8rem; - color: $buttercup; - margin-left: 5px; - line-height: initial; - - @media screen and (max-width: 575px) { - display: none; - } -} - h2.page-subtitle { text-transform: uppercase; color: #aeaeae; @@ -102,20 +115,3 @@ h2.page-subtitle { flex-direction: row; align-items: center; } - -.left-menu-wrapper { - display: flex; - flex-direction: row; - align-items: center; - cursor: pointer; -} - -.header__right-actions { - display: flex; - flex-flow: row nowrap; - align-items: center; - - .identicon { - cursor: pointer; - } -} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 959eb9d15..1c544e162 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -61,3 +61,5 @@ @import './welcome-screen.scss'; @import './sender-to-recipient.scss'; + +@import '../../../components/export-text-container/export-text-container.scss'; diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss index a92fffec5..c18b7fa59 100644 --- a/ui/app/css/itcss/components/loading-overlay.scss +++ b/ui/app/css/itcss/components/loading-overlay.scss @@ -26,4 +26,25 @@ width: 100vw; margin-top: 0; } + + &__container { + position: absolute; + top: 33%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + &__message { + margin-top: 32px; + font-weight: 400; + font-size: 20px; + color: $manatee; + } +} + +.spinner { + height: 58px; + width: 58px; } diff --git a/ui/app/css/itcss/components/menu.scss b/ui/app/css/itcss/components/menu.scss index eb92a1b70..6409ad545 100644 --- a/ui/app/css/itcss/components/menu.scss +++ b/ui/app/css/itcss/components/menu.scss @@ -11,7 +11,6 @@ flex-flow: row nowrap; align-items: center; position: relative; - font-weight: 300; z-index: 201; @media screen and (max-width: 575px) { diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 9ae3ea7fa..f972c0f7a 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -368,7 +368,6 @@ resize: none; padding: 9px 13px 8px; text-transform: uppercase; - font-weight: 300; } @@ -796,7 +795,6 @@ .simple-dropdown { color: #5B5D67; font-size: 16px; - font-weight: 300; line-height: 21px; border: 1px solid #D8D8D8; background-color: #FFFFFF; diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 777a82318..2903e07b4 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -117,7 +117,6 @@ $wallet-view-bg: $alabaster; font-size: 14px; line-height: 12px; padding: 4px 12px; - font-weight: 300; cursor: pointer; flex: 0 0 auto; @@ -264,7 +263,6 @@ $wallet-view-bg: $alabaster; // wallet view .account-name { font-size: 24px; - font-weight: 300; color: $black; margin-top: 8px; margin-bottom: .9rem; diff --git a/ui/app/css/itcss/components/pages/index.scss b/ui/app/css/itcss/components/pages/index.scss index 82446fd7a..195185fff 100644 --- a/ui/app/css/itcss/components/pages/index.scss +++ b/ui/app/css/itcss/components/pages/index.scss @@ -1 +1,3 @@ -@import './unlock.scss'; +@import './reveal-seed.scss'; + +@import '../../../../components/pages/unlock-page/unlock-page.scss'; diff --git a/ui/app/css/itcss/components/pages/reveal-seed.scss b/ui/app/css/itcss/components/pages/reveal-seed.scss new file mode 100644 index 000000000..b8f13af4a --- /dev/null +++ b/ui/app/css/itcss/components/pages/reveal-seed.scss @@ -0,0 +1,17 @@ +.reveal-seed { + &__content { + padding: 20px; + } + + &__label { + padding-bottom: 10px; + font-weight: 400; + display: inline-block; + } + + &__error { + color: $crimson; + font-size: 14px; + padding-top: 5px; + } +} diff --git a/ui/app/css/itcss/components/pages/unlock.scss b/ui/app/css/itcss/components/pages/unlock.scss deleted file mode 100644 index 5d438377b..000000000 --- a/ui/app/css/itcss/components/pages/unlock.scss +++ /dev/null @@ -1,9 +0,0 @@ -.unlock-page { - box-shadow: none; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: rgb(247, 247, 247); - width: 100%; -} diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss index 083481b8f..8bba6c98e 100644 --- a/ui/app/css/itcss/components/request-signature.scss +++ b/ui/app/css/itcss/components/request-signature.scss @@ -48,7 +48,6 @@ color: #5B5D67; font-family: Roboto; font-size: 22px; - font-weight: 300; line-height: 29px; z-index: 3; } @@ -125,7 +124,6 @@ color: $tundora; font-family: Roboto; font-size: 18px; - font-weight: 300; line-height: 24px; text-align: center; margin-top: 20px; diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss index ace46bd8a..feec71c89 100644 --- a/ui/app/css/itcss/components/sections.scss +++ b/ui/app/css/itcss/components/sections.scss @@ -95,19 +95,6 @@ textarea.twelve-word-phrase { margin: -2px 8px 0px -8px; } -.unlock-screen #metamask-mascot-container { - margin-top: 24px; -} - -.unlock-screen h1 { - margin-top: -28px; - margin-bottom: 42px; -} - -.unlock-screen input[type=password] { - width: 260px; -} - .sizing-input { font-size: 14px; height: 30px; @@ -118,34 +105,6 @@ textarea.twelve-word-phrase { display: flex; } -/* Webkit */ - -.unlock-screen input::-webkit-input-placeholder { - text-align: center; - font-size: 1.2em; -} - -/* Firefox 18- */ - -.unlock-screen input:-moz-placeholder { - text-align: center; - font-size: 1.2em; -} - -/* Firefox 19+ */ - -.unlock-screen input::-moz-placeholder { - text-align: center; - font-size: 1.2em; -} - -/* IE */ - -.unlock-screen input:-ms-input-placeholder { - text-align: center; - font-size: 1.2em; -} - /* accounts */ .accounts-section { diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index 362feeec8..c168242cf 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -507,7 +507,6 @@ &__copy { color: $gray; font-size: 14px; - font-weight: 300; line-height: 19px; text-align: center; margin-top: 10px; @@ -641,7 +640,6 @@ font-family: Roboto; font-size: 16px; line-height: 21px; - font-weight: 300; } } @@ -832,7 +830,6 @@ color: $tundora; font-family: Roboto; font-size: 20px; - font-weight: 300; line-height: 26px; margin-top: 17px; } diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss index dcc9b98d5..0dd61ac5e 100644 --- a/ui/app/css/itcss/components/settings.scss +++ b/ui/app/css/itcss/components/settings.scss @@ -3,8 +3,6 @@ background: $white; display: flex; flex-flow: column nowrap; - height: auto; - overflow: auto; } .settings__header { @@ -29,6 +27,8 @@ .settings__content { padding: 0 25px; + height: auto; + overflow: auto; } .settings__content-row { diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss index 92321394b..9b2982096 100644 --- a/ui/app/css/itcss/generic/index.scss +++ b/ui/app/css/itcss/generic/index.scss @@ -12,7 +12,7 @@ html, body { font-family: Roboto, Arial; color: #4d4d4d; - font-weight: 300; + font-weight: 400; background: #f7f7f7; width: 100%; height: 100%; @@ -205,8 +205,27 @@ input.large-input { } &__content { - height: 100%; overflow-y: auto; + flex: 1; + } + + &__warning-container { + background: $linen; + padding: 20px; + display: flex; + align-items: start; + } + + &__warning-message { + padding-left: 15px; + } + + &__warning-title { + font-weight: 500; + } + + &__warning-icon { + padding-top: 5px; } } @@ -235,5 +254,52 @@ input.large-input { overflow-y: auto; background-color: $white; border-radius: 0; + flex: 1; + } +} + +@media screen and (min-width: 576px) { + .page-container { + height: 600px; + flex: 0 0 auto; + } +} + +.input-label { + padding-bottom: 10px; + font-weight: 400; + display: inline-block; +} + +input.form-control { + padding-left: 10px; + font-size: 14px; + height: 40px; + border: 1px solid $alto; + border-radius: 3px; + width: 100%; + + &::-webkit-input-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &::-moz-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &:-ms-input-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &:-moz-placeholder { + font-weight: 100; + color: $dusty-gray; + } + + &--error { + border: 1px solid $monzo; } } diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index 51548306f..814d7a382 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -54,6 +54,7 @@ $saffron: #f6c343; $dodger-blue: #3099f2; $zumthor: #edf7ff; $ecstasy: #f7861c; +$linen: #fdf4f4; /* Z-Indicies diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js deleted file mode 100644 index eb588415f..000000000 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ /dev/null @@ -1,138 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const actions = require('../../../actions') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const { - DEFAULT_ROUTE, - INITIALIZE_BACKUP_PHRASE_ROUTE, -} = require('../../../routes') - -RevealSeedConfirmation.contextTypes = { - t: PropTypes.func, -} - -module.exports = compose( - withRouter, - connect(mapStateToProps) -)(RevealSeedConfirmation) - - -inherits(RevealSeedConfirmation, Component) -function RevealSeedConfirmation () { - Component.call(this) -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -RevealSeedConfirmation.prototype.render = function () { - const props = this.props - - return ( - - h('.initialize-screen.flex-column.flex-center.flex-grow', { - style: { maxWidth: '420px' }, - }, [ - - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: 24, - width: '100%', - fontSize: '20px', - padding: 6, - }, - }, [ - 'Reveal Seed Words', - ]), - - h('.div', { - style: { - display: 'flex', - flexDirection: 'column', - padding: '20px', - justifyContent: 'center', - }, - }, [ - - h('h4', this.context.t('revealSeedWordsWarning')), - - // confirmation - h('input.large-input.letter-spacey', { - type: 'password', - id: 'password-box', - placeholder: this.context.t('enterPasswordConfirm'), - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: '12px', - }, - }), - - h('.flex-row.flex-start', { - style: { - marginTop: 30, - width: '50%', - }, - }, [ - // cancel - h('button.primary', { - onClick: this.goHome.bind(this), - }, 'CANCEL'), - - // submit - h('button.primary', { - style: { marginLeft: '10px' }, - onClick: this.revealSeedWords.bind(this), - }, 'OK'), - - ]), - - (props.warning) && ( - h('span.error', { - style: { - margin: '20px', - }, - }, props.warning.split('-')) - ), - - props.inProgress && ( - h('span.in-progress-notification', this.context.t('generatingSeed')) - ), - ]), - ]) - ) -} - -RevealSeedConfirmation.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() -} - -RevealSeedConfirmation.prototype.goHome = function () { - this.props.dispatch(actions.showConfigPage(false)) - this.props.dispatch(actions.confirmSeedWords()) - .then(() => this.props.history.push(DEFAULT_ROUTE)) -} - -// create vault - -RevealSeedConfirmation.prototype.checkConfirmation = function (event) { - if (event.key === 'Enter') { - event.preventDefault() - this.revealSeedWords() - } -} - -RevealSeedConfirmation.prototype.revealSeedWords = function () { - var password = document.getElementById('password-box').value - this.props.dispatch(actions.requestRevealSeed(password)) - .then(() => this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)) -} diff --git a/ui/app/main-container.js b/ui/app/main-container.js index 68d1d45e7..b49a52363 100644 --- a/ui/app/main-container.js +++ b/ui/app/main-container.js @@ -3,7 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const AccountAndTransactionDetails = require('./account-and-transaction-details') const Settings = require('./components/pages/settings') -const UnlockScreen = require('./components/pages/unlock') +const UnlockScreen = require('./components/pages/unlock-page') const log = require('loglevel') module.exports = MainContainer diff --git a/ui/app/token-util.js b/ui/app/token-util.js index f84051ef5..920442bfc 100644 --- a/ui/app/token-util.js +++ b/ui/app/token-util.js @@ -1,14 +1,6 @@ -const abi = require('human-standard-token-abi') -const Eth = require('ethjs-query') -const EthContract = require('ethjs-contract') - -const tokenInfoGetter = function () { - if (typeof global.ethereumProvider === 'undefined') return - - const eth = new Eth(global.ethereumProvider) - const contract = new EthContract(eth) - const TokenContract = contract(abi) +const util = require('./util') +function tokenInfoGetter () { const tokens = {} return async (address) => { @@ -16,18 +8,35 @@ const tokenInfoGetter = function () { return tokens[address] } - const contract = TokenContract.at(address) + tokens[address] = await getSymbolAndDecimals(address) - const result = await Promise.all([ - contract.symbol(), - contract.decimals(), - ]) + return tokens[address] + } +} - const [ symbol = [], decimals = [] ] = result +async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { + const existingToken = existingTokens.find(({ address }) => tokenAddress === address) + if (existingToken) { + return existingToken + } + + let result = [] + try { + const token = util.getContractAtAddress(tokenAddress) + + result = await Promise.all([ + token.symbol(), + token.decimals(), + ]) + } catch (err) { + console.log(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, err) + } - tokens[address] = { symbol: symbol[0], decimals: decimals[0] } + const [ symbol = [], decimals = [] ] = result - return tokens[address] + return { + symbol: symbol[0] || null, + decimals: decimals[0] && decimals[0].toString() || null, } } @@ -42,4 +51,5 @@ function calcTokenAmount (value, decimals) { module.exports = { tokenInfoGetter, calcTokenAmount, + getSymbolAndDecimals, } diff --git a/ui/app/unlock.js b/ui/app/unlock.js deleted file mode 100644 index 099e5f9c6..000000000 --- a/ui/app/unlock.js +++ /dev/null @@ -1,141 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('./actions') -const getCaretCoordinates = require('textarea-caret') -const EventEmitter = require('events').EventEmitter -const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums') -const { getEnvironmentType } = require('../../app/scripts/lib/util') -const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums') - -const Mascot = require('./components/mascot') - -UnlockScreen.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps)(UnlockScreen) - - -inherits(UnlockScreen, Component) -function UnlockScreen () { - Component.call(this) - this.animationEventEmitter = new EventEmitter() -} - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -UnlockScreen.prototype.render = function () { - const state = this.props - const warning = state.warning - return ( - h('.unlock-screen', [ - - h(Mascot, { - animationEventEmitter: this.animationEventEmitter, - }), - - h('h1', { - style: { - fontSize: '1.4em', - textTransform: 'uppercase', - color: '#7F8082', - }, - }, this.context.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: warning ? 'block' : 'none', - padding: '0 20px', - textAlign: 'center', - }, - }, warning), - - h('button.primary.cursor-pointer', { - onClick: this.onSubmit.bind(this), - style: { - margin: 10, - }, - }, this.context.t('login')), - - h('p.pointer', { - onClick: () => { - this.props.dispatch(actions.markPasswordForgotten()) - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser() - } - }, - style: { - fontSize: '0.8em', - color: 'rgb(247, 134, 28)', - textDecoration: 'underline', - }, - }, this.context.t('restoreFromSeed')), - - h('p.pointer', { - onClick: () => { - this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) - .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) - }, - style: { - fontSize: '0.8em', - color: '#aeaeae', - textDecoration: 'underline', - marginTop: '32px', - }, - }, this.context.t('classicInterface')), - ]) - ) -} - -UnlockScreen.prototype.componentDidMount = function () { - document.getElementById('password-box').focus() -} - -UnlockScreen.prototype.onSubmit = function (event) { - const input = document.getElementById('password-box') - const password = input.value - this.props.dispatch(actions.tryUnlockMetamask(password)) -} - -UnlockScreen.prototype.onKeyPress = function (event) { - if (event.key === 'Enter') { - this.submitPassword(event) - } -} - -UnlockScreen.prototype.submitPassword = function (event) { - var element = event.target - var password = element.value - // reset input - element.value = '' - this.props.dispatch(actions.tryUnlockMetamask(password)) -} - -UnlockScreen.prototype.inputChanged = function (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, - }) -} diff --git a/ui/i18n-helper.js b/ui/i18n-helper.js index 3eee55ae9..79aa93116 100644 --- a/ui/i18n-helper.js +++ b/ui/i18n-helper.js @@ -11,8 +11,9 @@ const getMessage = (locale, key, substitutions) => { const { current, en } = locale const entry = current[key] || en[key] if (!entry) { - log.error(`Translator - Unable to find value for "${key}"`) // throw new Error(`Translator - Unable to find value for "${key}"`) + log.error(`Translator - Unable to find value for "${key}"`) + return `[${key}]` } let phrase = entry.message // perform substitutions |