diff options
Merge branch 'develop' into clearNotices
Diffstat (limited to 'ui/app/pages/unlock-page')
-rw-r--r-- | ui/app/pages/unlock-page/index.js | 2 | ||||
-rw-r--r-- | ui/app/pages/unlock-page/index.scss | 51 | ||||
-rw-r--r-- | ui/app/pages/unlock-page/unlock-page.component.js | 191 | ||||
-rw-r--r-- | ui/app/pages/unlock-page/unlock-page.container.js | 64 |
4 files changed, 308 insertions, 0 deletions
diff --git a/ui/app/pages/unlock-page/index.js b/ui/app/pages/unlock-page/index.js new file mode 100644 index 000000000..be80cde4f --- /dev/null +++ b/ui/app/pages/unlock-page/index.js @@ -0,0 +1,2 @@ +import UnlockPage from './unlock-page.container' +module.exports = UnlockPage diff --git a/ui/app/pages/unlock-page/index.scss b/ui/app/pages/unlock-page/index.scss new file mode 100644 index 000000000..3d44bd037 --- /dev/null +++ b/ui/app/pages/unlock-page/index.scss @@ -0,0 +1,51 @@ +.unlock-page { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + width: 357px; + padding: 30px; + font-weight: 400; + color: $silver-chalice; + + &__container { + background: $white; + display: flex; + align-self: stretch; + justify-content: center; + flex: 1 0 auto; + } + + &__mascot-container { + margin-top: 24px; + } + + &__title { + margin-top: 5px; + font-size: 2rem; + font-weight: 800; + color: $tundora; + } + + &__form { + width: 100%; + margin: 56px 0 8px; + } + + &__links { + margin-top: 25px; + width: 100%; + } + + &__link { + cursor: pointer; + + &--import { + color: $ecstasy; + } + + &--use-classic { + margin-top: 10px; + } + } +} diff --git a/ui/app/pages/unlock-page/unlock-page.component.js b/ui/app/pages/unlock-page/unlock-page.component.js new file mode 100644 index 000000000..3aeb2a59b --- /dev/null +++ b/ui/app/pages/unlock-page/unlock-page.component.js @@ -0,0 +1,191 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from '@material-ui/core/Button' +import TextField from '../../components/ui/text-field' +import getCaretCoordinates from 'textarea-caret' +import { EventEmitter } from 'events' +import Mascot from '../../components/ui/mascot' +import { DEFAULT_ROUTE } from '../../helpers/constants/routes' + +export default class UnlockPage extends Component { + static contextTypes = { + metricsEvent: PropTypes.func, + t: PropTypes.func, + } + + static propTypes = { + history: PropTypes.object, + isUnlocked: PropTypes.bool, + onImport: PropTypes.func, + onRestore: PropTypes.func, + onSubmit: PropTypes.func, + forceUpdateMetamaskState: PropTypes.func, + showOptInModal: PropTypes.func, + } + + constructor (props) { + super(props) + + this.state = { + password: '', + error: null, + } + + this.submitting = false + this.animationEventEmitter = new EventEmitter() + } + + componentWillMount () { + const { isUnlocked, history } = this.props + + if (isUnlocked) { + history.push(DEFAULT_ROUTE) + } + } + + handleSubmit = async event => { + event.preventDefault() + event.stopPropagation() + + const { password } = this.state + const { onSubmit, forceUpdateMetamaskState, showOptInModal } = this.props + + if (password === '' || this.submitting) { + return + } + + this.setState({ error: null }) + this.submitting = true + + try { + await onSubmit(password) + const newState = await forceUpdateMetamaskState() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Unlock', + name: 'Success', + }, + isNewVisit: true, + }) + + if (newState.participateInMetaMetrics === null || newState.participateInMetaMetrics === undefined) { + showOptInModal() + } + } catch ({ message }) { + if (message === 'Incorrect password') { + const newState = await forceUpdateMetamaskState() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Unlock', + name: 'Incorrect Passowrd', + }, + customVariables: { + numberOfTokens: newState.tokens.length, + numberOfAccounts: Object.keys(newState.accounts).length, + }, + }) + } + + this.setState({ error: message }) + this.submitting = false + } + } + + 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={this.handleSubmit} + disableRipple + > + { this.context.t('login') } + </Button> + ) + } + + render () { + const { password, error } = this.state + const { t } = this.context + const { onImport, onRestore } = this.props + + 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"> + { t('welcomeBack') } + </h1> + <div>{ t('unlockMessage') }</div> + <form + className="unlock-page__form" + onSubmit={this.handleSubmit} + > + <TextField + id="password" + label={t('password')} + type="password" + value={password} + onChange={event => this.handleInputChange(event)} + error={error} + autoFocus + autoComplete="current-password" + material + fullWidth + /> + </form> + { this.renderSubmitButton() } + <div className="unlock-page__links"> + <div + className="unlock-page__link" + onClick={() => onRestore()} + > + { t('restoreFromSeed') } + </div> + <div + className="unlock-page__link unlock-page__link--import" + onClick={() => onImport()} + > + { t('importUsingSeed') } + </div> + </div> + </div> + </div> + ) + } +} diff --git a/ui/app/pages/unlock-page/unlock-page.container.js b/ui/app/pages/unlock-page/unlock-page.container.js new file mode 100644 index 000000000..b89392ab5 --- /dev/null +++ b/ui/app/pages/unlock-page/unlock-page.container.js @@ -0,0 +1,64 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import { getEnvironmentType } from '../../../../app/scripts/lib/util' +import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' +import { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } from '../../helpers/constants/routes' +import { + tryUnlockMetamask, + forgotPassword, + markPasswordForgotten, + forceUpdateMetamaskState, + showModal, +} from '../../store/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()), + forceUpdateMetamaskState: () => forceUpdateMetamaskState(dispatch), + showOptInModal: () => dispatch(showModal({ name: 'METAMETRICS_OPT_IN_MODAL' })), + } +} + +const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { markPasswordForgotten, tryUnlockMetamask, ...restDispatchProps } = dispatchProps + const { history, onSubmit: ownPropsSubmit, ...restOwnProps } = ownProps + + const onImport = () => { + markPasswordForgotten() + history.push(RESTORE_VAULT_ROUTE) + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser(RESTORE_VAULT_ROUTE) + } + } + + const onSubmit = async password => { + await tryUnlockMetamask(password) + history.push(DEFAULT_ROUTE) + } + + return { + ...stateProps, + ...restDispatchProps, + ...restOwnProps, + onImport, + onRestore: onImport, + onSubmit: ownPropsSubmit || onSubmit, + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps, mergeProps) +)(UnlockPage) |