aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components')
-rw-r--r--ui/app/components/app-header/app-header.component.js106
-rw-r--r--ui/app/components/app-header/app-header.container.js38
-rw-r--r--ui/app/components/app-header/index.js2
-rw-r--r--ui/app/components/button/button.component.js43
-rw-r--r--ui/app/components/button/button.stories.js41
-rw-r--r--ui/app/components/button/index.js2
-rw-r--r--ui/app/components/buy-button-subview.js2
-rw-r--r--ui/app/components/export-text-container/export-text-container.scss2
-rw-r--r--ui/app/components/loading-screen/index.js2
-rw-r--r--ui/app/components/loading-screen/loading-screen.component.js (renamed from ui/app/components/loading.js)13
-rw-r--r--ui/app/components/pages/unlock-page/index.js2
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.component.js181
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.container.js33
-rw-r--r--ui/app/components/pages/unlock-page/unlock-page.scss51
-rw-r--r--ui/app/components/pending-tx/index.js2
-rw-r--r--ui/app/components/spinner/index.js2
-rw-r--r--ui/app/components/spinner/spinner.component.js78
-rw-r--r--ui/app/components/text-field/index.js2
-rw-r--r--ui/app/components/text-field/text-field.component.js59
-rw-r--r--ui/app/components/text-field/text-field.stories.js24
-rw-r--r--ui/app/components/wallet-view.js6
21 files changed, 681 insertions, 10 deletions
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/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/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/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/pending-tx/index.js b/ui/app/components/pending-tx/index.js
index fb409cb92..893538bcf 100644
--- a/ui/app/components/pending-tx/index.js
+++ b/ui/app/components/pending-tx/index.js
@@ -12,7 +12,7 @@ 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',
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..6fd3b82b4
--- /dev/null
+++ b/ui/app/components/text-field/text-field.component.js
@@ -0,0 +1,59 @@
+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',
+ },
+ '&$cssError': {
+ color: '#aeaeae',
+ },
+ fontWeight: '400',
+ color: '#aeaeae',
+ },
+ cssFocused: {},
+ cssUnderline: {
+ '&:after': {
+ backgroundColor: '#f7861c',
+ },
+ },
+ cssError: {},
+}
+
+const TextField = props => {
+ const { error, classes, ...textFieldProps } = props
+
+ return (
+ <MaterialTextField
+ error={Boolean(error)}
+ helperText={error}
+ InputLabelProps={{
+ FormLabelClasses: {
+ root: classes.cssLabel,
+ focused: classes.cssFocused,
+ error: classes.cssError,
+ },
+ }}
+ 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')),
])
}