aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/pages/keychains
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/pages/keychains')
-rw-r--r--ui/app/pages/keychains/index.scss197
-rw-r--r--ui/app/pages/keychains/restore-vault.js197
-rw-r--r--ui/app/pages/keychains/reveal-seed.js177
3 files changed, 571 insertions, 0 deletions
diff --git a/ui/app/pages/keychains/index.scss b/ui/app/pages/keychains/index.scss
new file mode 100644
index 000000000..868185419
--- /dev/null
+++ b/ui/app/pages/keychains/index.scss
@@ -0,0 +1,197 @@
+.first-view-main-wrapper {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ justify-content: center;
+ padding: 0 10px;
+}
+
+.first-view-main {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+}
+
+@media screen and (min-width: 1281px) {
+ .first-view-main {
+ width: 62vw;
+ }
+}
+
+.import-account {
+ display: flex;
+ flex-flow: column nowrap;
+ margin: 60px 0 30px 0;
+ position: relative;
+ max-width: initial;
+}
+
+@media only screen and (max-width: 575px) {
+ .import-account{
+ margin: 24px;
+ display: flex;
+ flex-flow: column nowrap;
+ width: calc(100vw - 80px);
+ }
+
+ .import-account__title {
+ width: initial !important;
+ }
+
+ .first-view-main {
+ height: 100%;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ margin-top: 12px;
+ }
+
+ .first-view-phone-invisible {
+ display: none;
+ }
+
+ .first-time-flow__input {
+ width: 100%;
+ }
+
+ .import-account__secret-phrase {
+ width: initial !important;
+ height: initial !important;
+ min-height: 190px;
+ }
+}
+
+.import-account__title {
+ color: #1B344D;
+ font-size: 40px;
+ line-height: 51px;
+ margin-bottom: 10px;
+}
+
+.import-account__back-button {
+ margin-bottom: 18px;
+ color: #22232c;
+ font-size: 16px;
+ line-height: 21px;
+ position: absolute;
+ top: -25px;
+}
+
+.import-account__secret-phrase {
+ height: 190px;
+ width: 495px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: #FFFFFF;
+ padding: 17px;
+ font-size: 16px;
+}
+
+.import-account__secret-phrase::placeholder {
+ color: #9B9B9B;
+ font-weight: 200;
+}
+
+.import-account__faq-link {
+ font-size: 18px;
+ line-height: 23px;
+ font-family: Roboto;
+}
+
+.import-account__selector-label {
+ color: #1B344D;
+ font-size: 16px;
+}
+
+.import-account__dropdown {
+ width: 325px;
+ border: 1px solid #CDCDCD;
+ border-radius: 4px;
+ background-color: #FFFFFF;
+ margin-top: 14px;
+ color: #5B5D67;
+ font-family: Roboto;
+ font-size: 18px;
+ line-height: 23px;
+ padding: 14px 21px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ cursor: pointer;
+}
+
+.import-account__description-text {
+ color: #757575;
+ font-size: 18px;
+ line-height: 23px;
+ margin-top: 21px;
+ font-family: Roboto;
+}
+
+.import-account__input-wrapper {
+ display: flex;
+ flex-flow: column nowrap;
+ margin-top: 30px;
+}
+
+.import-account__input-error-message {
+ margin-top: 10px;
+ width: 422px;
+ color: #FF001F;
+ font-size: 16px;
+ line-height: 21px;
+}
+
+.import-account__input-label {
+ margin-bottom: 9px;
+ color: #1B344D;
+ font-size: 18px;
+ line-height: 23px;
+}
+
+.import-account__input-label__disabled {
+ opacity: 0.5;
+}
+
+.import-account__input {
+ width: 350px;
+}
+
+@media only screen and (max-width: 575px) {
+ .import-account__input {
+ width: 100%;
+ }
+}
+
+.import-account__file-input {
+ display: none;
+}
+
+.import-account__file-input-label {
+ height: 53px;
+ width: 148px;
+ border: 1px solid #1B344D;
+ border-radius: 4px;
+ color: #1B344D;
+ font-family: Roboto;
+ font-size: 18px;
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.import-account__file-picker-wrapper {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
+
+.import-account__file-name {
+ color: #000000;
+ font-family: Roboto;
+ font-size: 18px;
+ line-height: 23px;
+ margin-left: 22px;
+}
diff --git a/ui/app/pages/keychains/restore-vault.js b/ui/app/pages/keychains/restore-vault.js
new file mode 100644
index 000000000..574949258
--- /dev/null
+++ b/ui/app/pages/keychains/restore-vault.js
@@ -0,0 +1,197 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import {connect} from 'react-redux'
+import {
+ createNewVaultAndRestore,
+ unMarkPasswordForgotten,
+} from '../../store/actions'
+import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
+import TextField from '../../components/ui/text-field'
+import Button from '../../components/ui/button'
+
+class RestoreVaultPage extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ warning: PropTypes.string,
+ createNewVaultAndRestore: PropTypes.func.isRequired,
+ leaveImportSeedScreenState: PropTypes.func,
+ history: PropTypes.object,
+ isLoading: PropTypes.bool,
+ };
+
+ state = {
+ seedPhrase: '',
+ password: '',
+ confirmPassword: '',
+ seedPhraseError: null,
+ passwordError: null,
+ confirmPasswordError: null,
+ }
+
+ parseSeedPhrase = (seedPhrase) => {
+ return seedPhrase
+ .match(/\w+/g)
+ .join(' ')
+ }
+
+ handleSeedPhraseChange (seedPhrase) {
+ let seedPhraseError = null
+
+ if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
+ seedPhraseError = this.context.t('seedPhraseReq')
+ }
+
+ this.setState({ seedPhrase, seedPhraseError })
+ }
+
+ handlePasswordChange (password) {
+ const { confirmPassword } = this.state
+ let confirmPasswordError = null
+ let passwordError = null
+
+ if (password && password.length < 8) {
+ passwordError = this.context.t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
+ }
+
+ this.setState({ password, passwordError, confirmPasswordError })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { password } = this.state
+ let confirmPasswordError = null
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = this.context.t('passwordsDontMatch')
+ }
+
+ this.setState({ confirmPassword, confirmPasswordError })
+ }
+
+ onClick = () => {
+ const { password, seedPhrase } = this.state
+ const {
+ createNewVaultAndRestore,
+ leaveImportSeedScreenState,
+ history,
+ } = this.props
+
+ leaveImportSeedScreenState()
+ createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
+ .then(() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Retention',
+ action: 'userEntersSeedPhrase',
+ name: 'onboardingRestoredVault',
+ },
+ })
+ history.push(DEFAULT_ROUTE)
+ })
+ }
+
+ hasError () {
+ const { passwordError, confirmPasswordError, seedPhraseError } = this.state
+ return passwordError || confirmPasswordError || seedPhraseError
+ }
+
+ render () {
+ const {
+ seedPhrase,
+ password,
+ confirmPassword,
+ seedPhraseError,
+ passwordError,
+ confirmPasswordError,
+ } = this.state
+ const { t } = this.context
+ const { isLoading } = this.props
+ const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
+
+ return (
+ <div className="first-view-main-wrapper">
+ <div className="first-view-main">
+ <div className="import-account">
+ <a
+ className="import-account__back-button"
+ onClick={e => {
+ e.preventDefault()
+ this.props.history.goBack()
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ <div className="import-account__title">
+ { this.context.t('restoreAccountWithSeed') }
+ </div>
+ <div className="import-account__selector-label">
+ { this.context.t('secretPhrase') }
+ </div>
+ <div className="import-account__input-wrapper">
+ <label className="import-account__input-label">Wallet Seed</label>
+ <textarea
+ className="import-account__secret-phrase"
+ onChange={e => this.handleSeedPhraseChange(e.target.value)}
+ value={this.state.seedPhrase}
+ placeholder={this.context.t('separateEachWord')}
+ />
+ </div>
+ <span className="error">
+ { seedPhraseError }
+ </span>
+ <TextField
+ id="password"
+ label={t('newPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoComplete="new-password"
+ margin="normal"
+ largeLabel
+ />
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={this.state.confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ largeLabel
+ />
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={() => !disabled && this.onClick()}
+ disabled={disabled}
+ >
+ {this.context.t('restore')}
+ </Button>
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ ({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
+ dispatch => ({
+ leaveImportSeedScreenState: () => {
+ dispatch(unMarkPasswordForgotten())
+ },
+ createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
+ })
+)(RestoreVaultPage)
diff --git a/ui/app/pages/keychains/reveal-seed.js b/ui/app/pages/keychains/reveal-seed.js
new file mode 100644
index 000000000..edc9db5a0
--- /dev/null
+++ b/ui/app/pages/keychains/reveal-seed.js
@@ -0,0 +1,177 @@
+const { Component } = require('react')
+const { connect } = require('react-redux')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const classnames = require('classnames')
+
+const { requestRevealSeedWords } = require('../../store/actions')
+const { DEFAULT_ROUTE } = require('../../helpers/constants/routes')
+const ExportTextContainer = require('../../components/ui/export-text-container')
+
+import Button from '../../components/ui/button'
+
+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) {
+ passwordBox.focus()
+ }
+ }
+
+ 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 }))
+ }
+
+ renderWarning () {
+ return (
+ h('.page-container__warning-container', [
+ h('img.page-container__warning-icon', {
+ src: 'images/warning.svg',
+ }),
+ h('.page-container__warning-message', [
+ h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]),
+ h('div', [this.context.t('revealSeedWordsWarning')]),
+ ]),
+ ])
+ )
+ }
+
+ renderContent () {
+ return this.state.screen === PASSWORD_PROMPT_SCREEN
+ ? this.renderPasswordPromptContent()
+ : this.renderRevealSeedContent()
+ }
+
+ renderPasswordPromptContent () {
+ const { t } = this.context
+
+ return (
+ h('form', {
+ onSubmit: event => this.handleSubmit(event),
+ }, [
+ h('label.input-label', {
+ htmlFor: 'password-box',
+ }, t('enterPasswordContinue')),
+ h('.input-group', [
+ h('input.form-control', {
+ type: 'password',
+ placeholder: t('password'),
+ id: 'password-box',
+ 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),
+ ])
+ )
+ }
+
+ renderRevealSeedContent () {
+ const { t } = this.context
+
+ return (
+ h('div', [
+ h('label.reveal-seed__label', t('yourPrivateSeedPhrase')),
+ h(ExportTextContainer, {
+ text: this.state.seedWords,
+ filename: t('metamaskSeedWords'),
+ }),
+ ])
+ )
+ }
+
+ renderFooter () {
+ return this.state.screen === PASSWORD_PROMPT_SCREEN
+ ? this.renderPasswordPromptFooter()
+ : this.renderRevealSeedFooter()
+ }
+
+ renderPasswordPromptFooter () {
+ return (
+ h('.page-container__footer', [
+ h('header', [
+ h(Button, {
+ type: 'default',
+ large: true,
+ className: 'page-container__footer-button',
+ onClick: () => this.props.history.push(DEFAULT_ROUTE),
+ }, this.context.t('cancel')),
+ h(Button, {
+ type: 'primary',
+ large: true,
+ className: '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, {
+ type: 'default',
+ large: true,
+ className: 'page-container__footer-button',
+ onClick: () => this.props.history.push(DEFAULT_ROUTE),
+ }, this.context.t('close')),
+ ])
+ )
+ }
+
+ render () {
+ 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 = {
+ requestRevealSeedWords: PropTypes.func,
+ history: PropTypes.object,
+}
+
+RevealSeedPage.contextTypes = {
+ t: PropTypes.func,
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)),
+ }
+}
+
+module.exports = connect(null, mapDispatchToProps)(RevealSeedPage)