aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components/pages/first-time-flow/seed-phrase
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components/pages/first-time-flow/seed-phrase')
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js161
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js12
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js41
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss44
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/index.scss36
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js1
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss53
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js139
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js59
-rw-r--r--ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js12
12 files changed, 560 insertions, 0 deletions
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
new file mode 100644
index 000000000..bc0f73a27
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
@@ -0,0 +1,161 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import shuffle from 'lodash.shuffle'
+import Identicon from '../../../../identicon'
+import Button from '../../../../button'
+import Breadcrumbs from '../../../../breadcrumbs'
+import { DEFAULT_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../../routes'
+import { exportAsFile } from '../../../../../../app/util'
+import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state'
+
+export default class ConfirmSeedPhrase extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static defaultProps = {
+ seedPhrase: '',
+ }
+
+ static propTypes = {
+ address: PropTypes.string,
+ completeOnboarding: PropTypes.func,
+ history: PropTypes.object,
+ onSubmit: PropTypes.func,
+ openBuyEtherModal: PropTypes.func,
+ seedPhrase: PropTypes.string,
+ }
+
+ state = {
+ selectedSeedWords: [],
+ shuffledSeedWords: [],
+ // Hash of shuffledSeedWords index {Number} to selectedSeedWords index {Number}
+ selectedSeedWordsHash: {},
+ }
+
+ componentDidMount () {
+ const { seedPhrase = '' } = this.props
+ const shuffledSeedWords = shuffle(seedPhrase.split(' ')) || []
+ this.setState({ shuffledSeedWords })
+ }
+
+ handleExport = () => {
+ exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
+ }
+
+ handleSubmit = async () => {
+ const { completeOnboarding, history, openBuyEtherModal } = this.props
+
+ if (!this.isValid()) {
+ return
+ }
+
+ try {
+ await completeOnboarding()
+ history.push(DEFAULT_ROUTE)
+ openBuyEtherModal()
+ } catch (error) {
+ console.error(error.message)
+ }
+ }
+
+ handleSelectSeedWord = (word, shuffledIndex) => {
+ this.setState(selectSeedWord(word, shuffledIndex))
+ }
+
+ handleDeselectSeedWord = shuffledIndex => {
+ this.setState(deselectSeedWord(shuffledIndex))
+ }
+
+ isValid () {
+ const { seedPhrase } = this.props
+ const { selectedSeedWords } = this.state
+ return seedPhrase === selectedSeedWords.join(' ')
+ }
+
+ render () {
+ const { t } = this.context
+ const { address, history } = this.props
+ const { selectedSeedWords, shuffledSeedWords, selectedSeedWordsHash } = this.state
+
+ return (
+ <div>
+ <div className="confirm-seed-phrase__back-button">
+ <a
+ onClick={e => {
+ e.preventDefault()
+ history.push(INITIALIZE_SEED_PHRASE_ROUTE)
+ }}
+ href="#"
+ >
+ {`< Back`}
+ </a>
+ </div>
+ <Identicon
+ className="first-time-flow__unique-image"
+ address={address}
+ diameter={70}
+ />
+ <div className="first-time-flow__header">
+ { t('confirmSecretBackupPhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('selectEachPhrase') }
+ </div>
+ <div className="confirm-seed-phrase__selected-seed-words">
+ {
+ selectedSeedWords.map((word, index) => (
+ <div
+ key={index}
+ className="confirm-seed-phrase__seed-word"
+ >
+ { word }
+ </div>
+ ))
+ }
+ </div>
+ <div className="confirm-seed-phrase__shuffled-seed-words">
+ {
+ shuffledSeedWords.map((word, index) => {
+ const isSelected = index in selectedSeedWordsHash
+
+ return (
+ <div
+ key={index}
+ className={classnames(
+ 'confirm-seed-phrase__seed-word',
+ 'confirm-seed-phrase__seed-word--shuffled',
+ { 'confirm-seed-phrase__seed-word--selected': isSelected }
+ )}
+ onClick={() => {
+ if (!isSelected) {
+ this.handleSelectSeedWord(word, index)
+ } else {
+ this.handleDeselectSeedWord(index)
+ }
+ }}
+ >
+ { word }
+ </div>
+ )
+ })
+ }
+ </div>
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={this.handleSubmit}
+ disabled={!this.isValid()}
+ >
+ { t('confirm') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={3}
+ currentIndex={2}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js
new file mode 100644
index 000000000..5fa2bec1e
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import ConfirmSeedPhrase from './confirm-seed-phrase.component'
+import { setCompletedOnboarding, showModal } from '../../../../../actions'
+
+const mapDispatchToProps = dispatch => {
+ return {
+ completeOnboarding: () => dispatch(setCompletedOnboarding()),
+ openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
+ }
+}
+
+export default connect(null, mapDispatchToProps)(ConfirmSeedPhrase)
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
new file mode 100644
index 000000000..f2476fc5c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
@@ -0,0 +1,41 @@
+export function selectSeedWord (word, shuffledIndex) {
+ return function update (state) {
+ const { selectedSeedWords, selectedSeedWordsHash } = state
+ const nextSelectedIndex = selectedSeedWords.length
+
+ return {
+ selectedSeedWords: [ ...selectedSeedWords, word ],
+ selectedSeedWordsHash: { ...selectedSeedWordsHash, [shuffledIndex]: nextSelectedIndex },
+ }
+ }
+}
+
+export function deselectSeedWord (shuffledIndex) {
+ return function update (state) {
+ const {
+ selectedSeedWords: prevSelectedSeedWords,
+ selectedSeedWordsHash: prevSelectedSeedWordsHash,
+ } = state
+
+ const selectedSeedWords = [...prevSelectedSeedWords]
+ const indexToRemove = prevSelectedSeedWordsHash[shuffledIndex]
+ selectedSeedWords.splice(indexToRemove, 1)
+ const selectedSeedWordsHash = Object.keys(prevSelectedSeedWordsHash).reduce((acc, index) => {
+ const output = { ...acc }
+ const selectedSeedWordIndex = prevSelectedSeedWordsHash[index]
+
+ if (selectedSeedWordIndex < indexToRemove) {
+ output[index] = selectedSeedWordIndex
+ } else if (selectedSeedWordIndex > indexToRemove) {
+ output[index] = selectedSeedWordIndex - 1
+ }
+
+ return output
+ }, {})
+
+ return {
+ selectedSeedWords,
+ selectedSeedWordsHash,
+ }
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js
new file mode 100644
index 000000000..beb53b383
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-seed-phrase.container'
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
new file mode 100644
index 000000000..e0444571f
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
@@ -0,0 +1,44 @@
+.confirm-seed-phrase {
+ &__back-button {
+ margin-bottom: 12px;
+ }
+
+ &__selected-seed-words {
+ min-height: 190px;
+ max-width: 496px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ margin: 24px 0 36px;
+ padding: 12px;
+ }
+
+ &__shuffled-seed-words {
+ max-width: 496px;
+ }
+
+ &__seed-word {
+ display: inline-block;
+ color: #5B5D67;
+ background-color: #E7E7E7;
+ padding: 8px 18px;
+ min-width: 64px;
+ margin: 4px;
+ text-align: center;
+
+ &--selected {
+ background-color: #85D1CC;
+ color: $white;
+ }
+
+ &--shuffled {
+ cursor: pointer;
+ margin: 6px;
+ }
+
+ @media screen and (max-width: 575px) {
+ font-size: .875rem;
+ padding: 6px 18px;
+ }
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/index.js b/ui/app/components/pages/first-time-flow/seed-phrase/index.js
new file mode 100644
index 000000000..7355bfb2c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './seed-phrase.container'
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/index.scss b/ui/app/components/pages/first-time-flow/seed-phrase/index.scss
new file mode 100644
index 000000000..88b28950c
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/index.scss
@@ -0,0 +1,36 @@
+@import './confirm-seed-phrase/index';
+
+@import './reveal-seed-phrase/index';
+
+.seed-phrase {
+
+ &__sections {
+ display: flex;
+
+ @media screen and (min-width: $break-large) {
+ flex-direction: row;
+ }
+
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ }
+ }
+
+ &__main {
+ flex: 3;
+ min-width: 0;
+ }
+
+ &__side {
+ flex: 2;
+ min-width: 0;
+
+ @media screen and (min-width: $break-large) {
+ margin-left: 48px;
+ }
+
+ @media screen and (max-width: $break-small) {
+ margin-top: 24px;
+ }
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js
new file mode 100644
index 000000000..4a1b191b5
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.js
@@ -0,0 +1 @@
+export { default } from './reveal-seed-phrase.component'
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss
new file mode 100644
index 000000000..568359d31
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss
@@ -0,0 +1,53 @@
+.reveal-seed-phrase {
+ &__secret {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ padding: 18px;
+ margin-top: 36px;
+ max-width: 350px;
+ }
+
+ &__secret-words {
+ width: 310px;
+ font-size: 1.25rem;
+ text-align: center;
+
+ &--hidden {
+ filter: blur(5px);
+ }
+ }
+
+ &__secret-blocker {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(0,0,0,0.6);
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 0 18px;
+ cursor: pointer;
+ }
+
+ &__reveal-button {
+ color: $white;
+ font-size: .75rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ margin-top: 8px;
+ text-align: center;
+ }
+
+ &__export-text {
+ color: $curious-blue;
+ cursor: pointer;
+ font-weight: 500;
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
new file mode 100644
index 000000000..bb822d1d5
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
@@ -0,0 +1,139 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import Identicon from '../../../../identicon'
+import LockIcon from '../../../../lock-icon'
+import Button from '../../../../button'
+import Breadcrumbs from '../../../../breadcrumbs'
+import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE } from '../../../../../routes'
+import { exportAsFile } from '../../../../../../app/util'
+
+export default class RevealSeedPhrase extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ address: PropTypes.string,
+ history: PropTypes.object,
+ seedPhrase: PropTypes.string,
+ }
+
+ state = {
+ isShowingSeedPhrase: false,
+ }
+
+ handleExport = () => {
+ exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
+ }
+
+ handleNext = event => {
+ event.preventDefault()
+ const { isShowingSeedPhrase } = this.state
+ const { history } = this.props
+
+ if (!isShowingSeedPhrase) {
+ return
+ }
+
+ history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE)
+ }
+
+ renderSecretWordsContainer () {
+ const { t } = this.context
+ const { seedPhrase } = this.props
+ const { isShowingSeedPhrase } = this.state
+
+ return (
+ <div className="reveal-seed-phrase__secret">
+ <div className={classnames(
+ 'reveal-seed-phrase__secret-words',
+ { 'reveal-seed-phrase__secret-words--hidden': !isShowingSeedPhrase }
+ )}>
+ { seedPhrase }
+ </div>
+ {
+ !isShowingSeedPhrase && (
+ <div
+ className="reveal-seed-phrase__secret-blocker"
+ onClick={() => this.setState({ isShowingSeedPhrase: true })}
+ >
+ <LockIcon
+ width="28px"
+ height="35px"
+ fill="#FFFFFF"
+ />
+ <div className="reveal-seed-phrase__reveal-button">
+ { t('clickToRevealSeed') }
+ </div>
+ </div>
+ )
+ }
+ </div>
+ )
+ }
+
+ render () {
+ const { t } = this.context
+ const { address } = this.props
+ const { isShowingSeedPhrase } = this.state
+
+ return (
+ <div>
+ <Identicon
+ className="first-time-flow__unique-image"
+ address={address}
+ diameter={70}
+ />
+ <div className="seed-phrase__sections">
+ <div className="seed-phrase__main">
+ <div className="first-time-flow__header">
+ { t('secretBackupPhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('secretBackupPhraseDescription') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('secretBackupPhraseWarning') }
+ </div>
+ { this.renderSecretWordsContainer() }
+ </div>
+ <div className="seed-phrase__side">
+ <div className="first-time-flow__text-block">
+ { `${t('tips')}:` }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('storePhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('writePhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ { t('memorizePhrase') }
+ </div>
+ <div className="first-time-flow__text-block">
+ <a
+ className="reveal-seed-phrase__export-text"
+ onClick={this.handleExport}>
+ { t('downloadSecretBackup') }
+ </a>
+ </div>
+ </div>
+ </div>
+ <Button
+ type="first-time"
+ className="first-time-flow__button"
+ onClick={this.handleNext}
+ disabled={!isShowingSeedPhrase}
+ >
+ { t('next') }
+ </Button>
+ <Breadcrumbs
+ className="first-time-flow__breadcrumbs"
+ total={3}
+ currentIndex={2}
+ />
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js
new file mode 100644
index 000000000..5f5b8a0b2
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.component.js
@@ -0,0 +1,59 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { Switch, Route } from 'react-router-dom'
+import RevealSeedPhrase from './reveal-seed-phrase'
+import ConfirmSeedPhrase from './confirm-seed-phrase'
+import {
+ INITIALIZE_SEED_PHRASE_ROUTE,
+ INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE,
+ DEFAULT_ROUTE,
+} from '../../../../routes'
+
+export default class SeedPhrase extends PureComponent {
+ static propTypes = {
+ address: PropTypes.string,
+ history: PropTypes.object,
+ seedPhrase: PropTypes.string,
+ }
+
+ componentDidMount () {
+ const { seedPhrase, history } = this.props
+
+ if (!seedPhrase) {
+ history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ render () {
+ const { address, seedPhrase } = this.props
+
+ return (
+ <div className="first-time-flow__wrapper">
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ConfirmSeedPhrase
+ { ...props }
+ address={address}
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <RevealSeedPhrase
+ { ...props }
+ address={address}
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ </Switch>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js
new file mode 100644
index 000000000..4df024ffc
--- /dev/null
+++ b/ui/app/components/pages/first-time-flow/seed-phrase/seed-phrase.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import SeedPhrase from './seed-phrase.component'
+
+const mapStateToProps = state => {
+ const { metamask: { selectedAddress } } = state
+
+ return {
+ address: selectedAddress,
+ }
+}
+
+export default connect(mapStateToProps)(SeedPhrase)