aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/pages/first-time-flow/create-password
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/pages/first-time-flow/create-password')
-rw-r--r--ui/app/pages/first-time-flow/create-password/create-password.component.js71
-rw-r--r--ui/app/pages/first-time-flow/create-password/create-password.container.js12
-rw-r--r--ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js256
-rw-r--r--ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js1
-rw-r--r--ui/app/pages/first-time-flow/create-password/index.js1
-rw-r--r--ui/app/pages/first-time-flow/create-password/new-account/index.js1
-rw-r--r--ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js225
-rw-r--r--ui/app/pages/first-time-flow/create-password/unique-image/index.js1
-rw-r--r--ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js55
-rw-r--r--ui/app/pages/first-time-flow/create-password/unique-image/unique-image.container.js12
10 files changed, 635 insertions, 0 deletions
diff --git a/ui/app/pages/first-time-flow/create-password/create-password.component.js b/ui/app/pages/first-time-flow/create-password/create-password.component.js
new file mode 100644
index 000000000..5e67a2244
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/create-password.component.js
@@ -0,0 +1,71 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { Switch, Route } from 'react-router-dom'
+import NewAccount from './new-account'
+import ImportWithSeedPhrase from './import-with-seed-phrase'
+import {
+ INITIALIZE_CREATE_PASSWORD_ROUTE,
+ INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
+ INITIALIZE_SEED_PHRASE_ROUTE,
+} from '../../../helpers/constants/routes'
+
+export default class CreatePassword extends PureComponent {
+ static propTypes = {
+ history: PropTypes.object,
+ isInitialized: PropTypes.bool,
+ onCreateNewAccount: PropTypes.func,
+ onCreateNewAccountFromSeed: PropTypes.func,
+ }
+
+ componentDidMount () {
+ const { isInitialized, history } = this.props
+
+ if (isInitialized) {
+ history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ }
+ }
+
+ render () {
+ const { onCreateNewAccount, onCreateNewAccountFromSeed } = this.props
+
+ return (
+ <div className="first-time-flow__wrapper">
+ <div className="app-header__logo-container">
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--horizontal"
+ src="/images/logo/metamask-logo-horizontal.svg"
+ height={30}
+ />
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--icon"
+ src="/images/logo/metamask-fox.svg"
+ height={42}
+ width={42}
+ />
+ </div>
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ImportWithSeedPhrase
+ { ...props }
+ onSubmit={onCreateNewAccountFromSeed}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_CREATE_PASSWORD_ROUTE}
+ render={props => (
+ <NewAccount
+ { ...props }
+ onSubmit={onCreateNewAccount}
+ />
+ )}
+ />
+ </Switch>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/pages/first-time-flow/create-password/create-password.container.js b/ui/app/pages/first-time-flow/create-password/create-password.container.js
new file mode 100644
index 000000000..89106f016
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/create-password.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import CreatePassword from './create-password.component'
+
+const mapStateToProps = state => {
+ const { metamask: { isInitialized } } = state
+
+ return {
+ isInitialized,
+ }
+}
+
+export default connect(mapStateToProps)(CreatePassword)
diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
new file mode 100644
index 000000000..433dad6e2
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
@@ -0,0 +1,256 @@
+import {validateMnemonic} from 'bip39'
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import TextField from '../../../../components/ui/text-field'
+import Button from '../../../../components/ui/button'
+import {
+ INITIALIZE_SELECT_ACTION_ROUTE,
+ INITIALIZE_END_OF_FLOW_ROUTE,
+} from '../../../../helpers/constants/routes'
+
+export default class ImportWithSeedPhrase extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ history: PropTypes.object,
+ onSubmit: PropTypes.func.isRequired,
+ }
+
+ state = {
+ seedPhrase: '',
+ password: '',
+ confirmPassword: '',
+ seedPhraseError: '',
+ passwordError: '',
+ confirmPasswordError: '',
+ termsChecked: false,
+ }
+
+ parseSeedPhrase = (seedPhrase) => {
+ return seedPhrase
+ .trim()
+ .match(/\w+/g)
+ .join(' ')
+ }
+
+ handleSeedPhraseChange (seedPhrase) {
+ let seedPhraseError = ''
+
+ if (seedPhrase) {
+ const parsedSeedPhrase = this.parseSeedPhrase(seedPhrase)
+ if (parsedSeedPhrase.split(' ').length !== 12) {
+ seedPhraseError = this.context.t('seedPhraseReq')
+ } else if (!validateMnemonic(parsedSeedPhrase)) {
+ seedPhraseError = this.context.t('invalidSeedPhrase')
+ }
+ }
+
+ this.setState({ seedPhrase, seedPhraseError })
+ }
+
+ handlePasswordChange (password) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { confirmPassword } = state
+ let confirmPasswordError = ''
+ let passwordError = ''
+
+ if (password && password.length < 8) {
+ passwordError = t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ password,
+ passwordError,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { password } = state
+ let confirmPasswordError = ''
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ confirmPassword,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleImport = async event => {
+ event.preventDefault()
+
+ if (!this.isValid()) {
+ return
+ }
+
+ const { password, seedPhrase } = this.state
+ const { history, onSubmit } = this.props
+
+ try {
+ await onSubmit(password, this.parseSeedPhrase(seedPhrase))
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Import Seed Phrase',
+ name: 'Import Complete',
+ },
+ })
+ history.push(INITIALIZE_END_OF_FLOW_ROUTE)
+ } catch (error) {
+ this.setState({ seedPhraseError: error.message })
+ }
+ }
+
+ isValid () {
+ const {
+ seedPhrase,
+ password,
+ confirmPassword,
+ passwordError,
+ confirmPasswordError,
+ seedPhraseError,
+ } = this.state
+
+ if (!password || !confirmPassword || !seedPhrase || password !== confirmPassword) {
+ return false
+ }
+
+ if (password.length < 8) {
+ return false
+ }
+
+ return !passwordError && !confirmPasswordError && !seedPhraseError
+ }
+
+ toggleTermsCheck = () => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Import Seed Phrase',
+ name: 'Check ToS',
+ },
+ })
+
+ this.setState((prevState) => ({
+ termsChecked: !prevState.termsChecked,
+ }))
+ }
+
+ render () {
+ const { t } = this.context
+ const { seedPhraseError, passwordError, confirmPasswordError, termsChecked } = this.state
+
+ return (
+ <form
+ className="first-time-flow__form"
+ onSubmit={this.handleImport}
+ >
+ <div className="first-time-flow__create-back">
+ <a
+ onClick={e => {
+ e.preventDefault()
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Import Seed Phrase',
+ name: 'Go Back from Onboarding Import',
+ },
+ })
+ this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE)
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ </div>
+ <div className="first-time-flow__header">
+ { t('importAccountSeedPhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('secretPhrase') }
+ </div>
+ <div className="first-time-flow__textarea-wrapper">
+ <label>{ t('walletSeed') }</label>
+ <textarea
+ className="first-time-flow__textarea"
+ onChange={e => this.handleSeedPhraseChange(e.target.value)}
+ value={this.state.seedPhrase}
+ placeholder={t('seedPhrasePlaceholder')}
+ />
+ </div>
+ {
+ seedPhraseError && (
+ <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
+ />
+ <div className="first-time-flow__checkbox-container" onClick={this.toggleTermsCheck}>
+ <div className="first-time-flow__checkbox">
+ {termsChecked ? <i className="fa fa-check fa-2x" /> : null}
+ </div>
+ <span className="first-time-flow__checkbox-label">
+ I have read and agree to the <a
+ href="https://metamask.io/terms.html"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <span className="first-time-flow__link-text">
+ { 'Terms of Use' }
+ </span>
+ </a>
+ </span>
+ </div>
+ <Button
+ type="confirm"
+ className="first-time-flow__button"
+ disabled={!this.isValid() || !termsChecked}
+ onClick={this.handleImport}
+ >
+ { t('import') }
+ </Button>
+ </form>
+ )
+ }
+}
diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js
new file mode 100644
index 000000000..e5ff1fde5
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './import-with-seed-phrase.component'
diff --git a/ui/app/pages/first-time-flow/create-password/index.js b/ui/app/pages/first-time-flow/create-password/index.js
new file mode 100644
index 000000000..42e7436f9
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/index.js
@@ -0,0 +1 @@
+export { default } from './create-password.container'
diff --git a/ui/app/pages/first-time-flow/create-password/new-account/index.js b/ui/app/pages/first-time-flow/create-password/new-account/index.js
new file mode 100644
index 000000000..97db39cc3
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/new-account/index.js
@@ -0,0 +1 @@
+export { default } from './new-account.component'
diff --git a/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
new file mode 100644
index 000000000..c040cff88
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
@@ -0,0 +1,225 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../../../../components/ui/button'
+import {
+ INITIALIZE_SEED_PHRASE_ROUTE,
+ INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
+ INITIALIZE_SELECT_ACTION_ROUTE,
+} from '../../../../helpers/constants/routes'
+import TextField from '../../../../components/ui/text-field'
+
+export default class NewAccount extends PureComponent {
+ static contextTypes = {
+ metricsEvent: PropTypes.func,
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ onSubmit: PropTypes.func.isRequired,
+ history: PropTypes.object.isRequired,
+ }
+
+ state = {
+ password: '',
+ confirmPassword: '',
+ passwordError: '',
+ confirmPasswordError: '',
+ termsChecked: false,
+ }
+
+ isValid () {
+ const {
+ password,
+ confirmPassword,
+ passwordError,
+ confirmPasswordError,
+ } = this.state
+
+ if (!password || !confirmPassword || password !== confirmPassword) {
+ return false
+ }
+
+ if (password.length < 8) {
+ return false
+ }
+
+ return !passwordError && !confirmPasswordError
+ }
+
+ handlePasswordChange (password) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { confirmPassword } = state
+ let passwordError = ''
+ let confirmPasswordError = ''
+
+ if (password && password.length < 8) {
+ passwordError = t('passwordNotLongEnough')
+ }
+
+ if (confirmPassword && password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ password,
+ passwordError,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleConfirmPasswordChange (confirmPassword) {
+ const { t } = this.context
+
+ this.setState(state => {
+ const { password } = state
+ let confirmPasswordError = ''
+
+ if (password !== confirmPassword) {
+ confirmPasswordError = t('passwordsDontMatch')
+ }
+
+ return {
+ confirmPassword,
+ confirmPasswordError,
+ }
+ })
+ }
+
+ handleCreate = async event => {
+ event.preventDefault()
+
+ if (!this.isValid()) {
+ return
+ }
+
+ const { password } = this.state
+ const { onSubmit, history } = this.props
+
+ try {
+ await onSubmit(password)
+
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Create Password',
+ name: 'Submit Password',
+ },
+ })
+
+ history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ } catch (error) {
+ this.setState({ passwordError: error.message })
+ }
+ }
+
+ handleImportWithSeedPhrase = event => {
+ const { history } = this.props
+
+ event.preventDefault()
+ history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
+ }
+
+ toggleTermsCheck = () => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Create Password',
+ name: 'Check ToS',
+ },
+ })
+
+ this.setState((prevState) => ({
+ termsChecked: !prevState.termsChecked,
+ }))
+ }
+
+ render () {
+ const { t } = this.context
+ const { password, confirmPassword, passwordError, confirmPasswordError, termsChecked } = this.state
+
+ return (
+ <div>
+ <div className="first-time-flow__create-back">
+ <a
+ onClick={e => {
+ e.preventDefault()
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Create Password',
+ name: 'Go Back from Onboarding Create',
+ },
+ })
+ this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE)
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ </div>
+ <div className="first-time-flow__header">
+ { t('createPassword') }
+ </div>
+ <form
+ className="first-time-flow__form"
+ onSubmit={this.handleCreate}
+ >
+ <TextField
+ id="create-password"
+ label={t('newPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={password}
+ onChange={event => this.handlePasswordChange(event.target.value)}
+ error={passwordError}
+ autoFocus
+ autoComplete="new-password"
+ margin="normal"
+ fullWidth
+ largeLabel
+ />
+ <TextField
+ id="confirm-password"
+ label={t('confirmPassword')}
+ type="password"
+ className="first-time-flow__input"
+ value={confirmPassword}
+ onChange={event => this.handleConfirmPasswordChange(event.target.value)}
+ error={confirmPasswordError}
+ autoComplete="confirm-password"
+ margin="normal"
+ fullWidth
+ largeLabel
+ />
+ <div className="first-time-flow__checkbox-container" onClick={this.toggleTermsCheck}>
+ <div className="first-time-flow__checkbox">
+ {termsChecked ? <i className="fa fa-check fa-2x" /> : null}
+ </div>
+ <span className="first-time-flow__checkbox-label">
+ I have read and agree to the <a
+ href="https://metamask.io/terms.html"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <span className="first-time-flow__link-text">
+ { 'Terms of Use' }
+ </span>
+ </a>
+ </span>
+ </div>
+ <Button
+ type="confirm"
+ className="first-time-flow__button"
+ disabled={!this.isValid() || !termsChecked}
+ onClick={this.handleCreate}
+ >
+ { t('create') }
+ </Button>
+ </form>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/pages/first-time-flow/create-password/unique-image/index.js b/ui/app/pages/first-time-flow/create-password/unique-image/index.js
new file mode 100644
index 000000000..0e97bf755
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/unique-image/index.js
@@ -0,0 +1 @@
+export { default } from './unique-image.container'
diff --git a/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
new file mode 100644
index 000000000..3434d117a
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
@@ -0,0 +1,55 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import Button from '../../../../components/ui/button'
+import { INITIALIZE_END_OF_FLOW_ROUTE } from '../../../../helpers/constants/routes'
+
+export default class UniqueImageScreen extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ static propTypes = {
+ history: PropTypes.object,
+ }
+
+ render () {
+ const { t } = this.context
+ const { history } = this.props
+
+ return (
+ <div>
+ <img
+ src="/images/sleuth.svg"
+ height={42}
+ width={42}
+ />
+ <div className="first-time-flow__header">
+ { t('protectYourKeys') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('protectYourKeysMessage1') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('protectYourKeysMessage2') }
+ </div>
+ <Button
+ type="confirm"
+ className="first-time-flow__button"
+ onClick={() => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Agree to Phishing Warning',
+ name: 'Agree to Phishing Warning',
+ },
+ })
+ history.push(INITIALIZE_END_OF_FLOW_ROUTE)
+ }}
+ >
+ { t('next') }
+ </Button>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.container.js b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.container.js
new file mode 100644
index 000000000..34874aaec
--- /dev/null
+++ b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import UniqueImage from './unique-image.component'
+
+const mapStateToProps = ({ metamask }) => {
+ const { selectedAddress } = metamask
+
+ return {
+ address: selectedAddress,
+ }
+}
+
+export default connect(mapStateToProps)(UniqueImage)