First time flow updates (#6192)
* Action select step of onboarding flow added. * Update navigation on create and import password screens. * Adds terms of service checkbox to create and import account screens. * Add security warning to jazzicon intro step * Update and streamline unique image to confirm seed steps of first time flow. * UI touch ups to welcome screen. * UI touch up on select action page * Fix first time import flow. * Add end of flow screen to first time flow * Replace unique image screen with updated fishing warning screen. * Update e2e tests for onboarding flow changes. * Add required translations to onboarding flow. * Update design of select action screen to emphasize create new wallet option. * Clean up onboarding flow code. * Remove notice related code from first-time-flow directory. * Use updater function argument in new-account.componentfeature/default_network_editable
parent
a2320c76fe
commit
cb2698d20e
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 221 B |
@ -0,0 +1,70 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Button from '../../../button' |
||||||
|
import { DEFAULT_ROUTE } from '../../../../routes' |
||||||
|
|
||||||
|
export default class EndOfFlowScreen extends PureComponent { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object, |
||||||
|
completeOnboarding: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { t } = this.context |
||||||
|
const { history, completeOnboarding } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="end-of-flow"> |
||||||
|
<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> |
||||||
|
<div className="end-of-flow__emoji">🎉</div> |
||||||
|
<div className="first-time-flow__header"> |
||||||
|
{ t('congratulations') } |
||||||
|
</div> |
||||||
|
<div className="first-time-flow__text-block end-of-flow__text-1"> |
||||||
|
{ t('endOfFlowMessage1') } |
||||||
|
</div> |
||||||
|
<div className="first-time-flow__text-block end-of-flow__text-2"> |
||||||
|
{ t('endOfFlowMessage2') } |
||||||
|
</div> |
||||||
|
<div className="first-time-flow__text-block end-of-flow__text-3"> |
||||||
|
{ '• ' + t('endOfFlowMessage3') } |
||||||
|
</div> |
||||||
|
<div className="first-time-flow__text-block end-of-flow__text-4"> |
||||||
|
{ '• ' + t('endOfFlowMessage4') } |
||||||
|
</div> |
||||||
|
<div className="first-time-flow__text-block end-of-flow__text-3"> |
||||||
|
{ t('endOfFlowMessage5') } |
||||||
|
</div> |
||||||
|
<div className="first-time-flow__text-block end-of-flow__text-3"> |
||||||
|
{ '*' + t('endOfFlowMessage6') } |
||||||
|
</div> |
||||||
|
<Button |
||||||
|
type="confirm" |
||||||
|
className="first-time-flow__button" |
||||||
|
onClick={async () => { |
||||||
|
await completeOnboarding() |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
}} |
||||||
|
> |
||||||
|
{ 'All Done' } |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import EndOfFlow from './end-of-flow.component' |
||||||
|
import { setCompletedOnboarding } from '../../../../actions' |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
completeOnboarding: () => dispatch(setCompletedOnboarding()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(EndOfFlow) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './end-of-flow.container' |
@ -0,0 +1,47 @@ |
|||||||
|
.end-of-flow { |
||||||
|
color: black; |
||||||
|
font-family: Roboto; |
||||||
|
font-style: normal; |
||||||
|
|
||||||
|
.app-header__logo-container { |
||||||
|
width: 742px; |
||||||
|
margin-top: 3%; |
||||||
|
|
||||||
|
@media screen and (max-width: $break-small) { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__text-1, &__text-3 { |
||||||
|
font-weight: normal; |
||||||
|
font-size: 16px; |
||||||
|
margin-top: 18px; |
||||||
|
} |
||||||
|
|
||||||
|
&__text-2 { |
||||||
|
font-weight: bold; |
||||||
|
font-size: 16px; |
||||||
|
margin-top: 26px; |
||||||
|
} |
||||||
|
|
||||||
|
&__text-3 { |
||||||
|
margin-top: 26px; |
||||||
|
} |
||||||
|
|
||||||
|
&__text-3 { |
||||||
|
margin-top: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
width: 207px; |
||||||
|
} |
||||||
|
|
||||||
|
&__start-over-button { |
||||||
|
width: 744px; |
||||||
|
} |
||||||
|
|
||||||
|
&__emoji { |
||||||
|
font-size: 80px; |
||||||
|
margin-top: 70px; |
||||||
|
} |
||||||
|
} |
@ -1 +0,0 @@ |
|||||||
export { default } from './notices.container' |
|
@ -1,124 +0,0 @@ |
|||||||
import React, { PureComponent } from 'react' |
|
||||||
import PropTypes from 'prop-types' |
|
||||||
import Markdown from 'react-markdown' |
|
||||||
import debounce from 'lodash.debounce' |
|
||||||
import Button from '../../../button' |
|
||||||
import Identicon from '../../../identicon' |
|
||||||
import Breadcrumbs from '../../../breadcrumbs' |
|
||||||
import { DEFAULT_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../routes' |
|
||||||
|
|
||||||
export default class Notices extends PureComponent { |
|
||||||
static contextTypes = { |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
static propTypes = { |
|
||||||
address: PropTypes.string.isRequired, |
|
||||||
completeOnboarding: PropTypes.func, |
|
||||||
history: PropTypes.object, |
|
||||||
isImportedKeyring: PropTypes.bool, |
|
||||||
markNoticeRead: PropTypes.func, |
|
||||||
nextUnreadNotice: PropTypes.shape({ |
|
||||||
title: PropTypes.string, |
|
||||||
date: PropTypes.string, |
|
||||||
body: PropTypes.string, |
|
||||||
}), |
|
||||||
noActiveNotices: PropTypes.bool, |
|
||||||
} |
|
||||||
|
|
||||||
static defaultProps = { |
|
||||||
nextUnreadNotice: {}, |
|
||||||
} |
|
||||||
|
|
||||||
state = { |
|
||||||
atBottom: false, |
|
||||||
} |
|
||||||
|
|
||||||
componentDidMount () { |
|
||||||
const { noActiveNotices, history } = this.props |
|
||||||
|
|
||||||
if (noActiveNotices) { |
|
||||||
history.push(INITIALIZE_SEED_PHRASE_ROUTE) |
|
||||||
} |
|
||||||
|
|
||||||
this.onScroll() |
|
||||||
} |
|
||||||
|
|
||||||
acceptTerms = async () => { |
|
||||||
const { |
|
||||||
completeOnboarding, |
|
||||||
history, |
|
||||||
isImportedKeyring, |
|
||||||
markNoticeRead, |
|
||||||
nextUnreadNotice, |
|
||||||
} = this.props |
|
||||||
|
|
||||||
const hasActiveNotices = await markNoticeRead(nextUnreadNotice) |
|
||||||
|
|
||||||
if (!hasActiveNotices) { |
|
||||||
if (isImportedKeyring) { |
|
||||||
await completeOnboarding() |
|
||||||
history.push(DEFAULT_ROUTE) |
|
||||||
} else { |
|
||||||
history.push(INITIALIZE_SEED_PHRASE_ROUTE) |
|
||||||
} |
|
||||||
} else { |
|
||||||
this.setState({ atBottom: false }, () => this.onScroll()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
onScroll = debounce(() => { |
|
||||||
if (this.state.atBottom) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
const target = document.querySelector('.first-time-flow__markdown') |
|
||||||
|
|
||||||
if (target) { |
|
||||||
const { scrollTop, offsetHeight, scrollHeight } = target |
|
||||||
const atBottom = scrollTop + offsetHeight >= scrollHeight |
|
||||||
|
|
||||||
this.setState({ atBottom }) |
|
||||||
} |
|
||||||
}, 25) |
|
||||||
|
|
||||||
render () { |
|
||||||
const { t } = this.context |
|
||||||
const { isImportedKeyring, address, nextUnreadNotice: { title, body } } = this.props |
|
||||||
const { atBottom } = this.state |
|
||||||
|
|
||||||
return ( |
|
||||||
<div |
|
||||||
className="first-time-flow__wrapper" |
|
||||||
onScroll={this.onScroll} |
|
||||||
> |
|
||||||
<Identicon |
|
||||||
className="first-time-flow__unique-image" |
|
||||||
address={address} |
|
||||||
diameter={70} |
|
||||||
/> |
|
||||||
<div className="first-time-flow__header"> |
|
||||||
{ title } |
|
||||||
</div> |
|
||||||
<Markdown |
|
||||||
className="first-time-flow__markdown" |
|
||||||
source={body} |
|
||||||
skipHtml |
|
||||||
/> |
|
||||||
<Button |
|
||||||
type="first-time" |
|
||||||
className="first-time-flow__button" |
|
||||||
onClick={atBottom && this.acceptTerms} |
|
||||||
disabled={!atBottom} |
|
||||||
> |
|
||||||
{ t('accept') } |
|
||||||
</Button> |
|
||||||
<Breadcrumbs |
|
||||||
className="first-time-flow__breadcrumbs" |
|
||||||
total={isImportedKeyring ? 2 : 3} |
|
||||||
currentIndex={1} |
|
||||||
/> |
|
||||||
</div> |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -1,27 +0,0 @@ |
|||||||
import { connect } from 'react-redux' |
|
||||||
import { withRouter } from 'react-router-dom' |
|
||||||
import { compose } from 'recompose' |
|
||||||
import { markNoticeRead, setCompletedOnboarding } from '../../../../actions' |
|
||||||
import Notices from './notices.component' |
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask }) => { |
|
||||||
const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask |
|
||||||
|
|
||||||
return { |
|
||||||
address: selectedAddress, |
|
||||||
nextUnreadNotice, |
|
||||||
noActiveNotices, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => { |
|
||||||
return { |
|
||||||
markNoticeRead: notice => dispatch(markNoticeRead(notice)), |
|
||||||
completeOnboarding: () => dispatch(setCompletedOnboarding()), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default compose( |
|
||||||
withRouter, |
|
||||||
connect(mapStateToProps, mapDispatchToProps) |
|
||||||
)(Notices) |
|
@ -1,12 +0,0 @@ |
|||||||
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) |
|
@ -1 +1 @@ |
|||||||
export { default } from './confirm-seed-phrase.container' |
export { default } from './confirm-seed-phrase.component' |
||||||
|
@ -1 +1 @@ |
|||||||
export { default } from './seed-phrase.container' |
export { default } from './seed-phrase.component' |
||||||
|
@ -1,12 +0,0 @@ |
|||||||
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) |
|
@ -0,0 +1 @@ |
|||||||
|
export { default } from './select-action.component' |
@ -0,0 +1,87 @@ |
|||||||
|
.select-action { |
||||||
|
.app-header__logo-container { |
||||||
|
width: 742px; |
||||||
|
margin-top: 3%; |
||||||
|
} |
||||||
|
|
||||||
|
&__body { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__body-header { |
||||||
|
font-family: Roboto; |
||||||
|
font-style: normal; |
||||||
|
font-weight: normal; |
||||||
|
line-height: 39px; |
||||||
|
font-size: 28px; |
||||||
|
text-align: center; |
||||||
|
margin-top: 65px; |
||||||
|
color: black; |
||||||
|
} |
||||||
|
|
||||||
|
&__select-buttons { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
margin-top: 40px; |
||||||
|
} |
||||||
|
|
||||||
|
&__select-button { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-evenly; |
||||||
|
width: 269px; |
||||||
|
height: 278px; |
||||||
|
|
||||||
|
border: 1px solid #D8D8D8; |
||||||
|
box-sizing: border-box; |
||||||
|
border-radius: 10px; |
||||||
|
margin-left: 22px; |
||||||
|
|
||||||
|
.first-time-flow__button { |
||||||
|
max-width: 221px; |
||||||
|
height: 44px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__button-symbol { |
||||||
|
color: #C4C4C4; |
||||||
|
margin-top: 41px; |
||||||
|
} |
||||||
|
|
||||||
|
&__button-content { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
height: 144px; |
||||||
|
} |
||||||
|
|
||||||
|
&__button-text-big { |
||||||
|
font-family: Roboto; |
||||||
|
font-style: normal; |
||||||
|
font-weight: normal; |
||||||
|
line-height: 28px; |
||||||
|
font-size: 20px; |
||||||
|
color: #000000; |
||||||
|
margin-top: 12px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__button-text-small { |
||||||
|
font-family: Roboto; |
||||||
|
font-style: normal; |
||||||
|
font-weight: normal; |
||||||
|
line-height: 20px; |
||||||
|
font-size: 14px; |
||||||
|
color: #7A7A7B; |
||||||
|
margin-top: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
font-weight: 500; |
||||||
|
width: 221px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,104 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Button from '../../../button' |
||||||
|
import { |
||||||
|
INITIALIZE_CREATE_PASSWORD_ROUTE, |
||||||
|
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, |
||||||
|
INITIALIZE_UNIQUE_IMAGE_ROUTE, |
||||||
|
} from '../../../../routes' |
||||||
|
|
||||||
|
export default class SelectAction extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object, |
||||||
|
isInitialized: PropTypes.bool, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount () { |
||||||
|
const { history, isInitialized } = this.props |
||||||
|
|
||||||
|
if (isInitialized) { |
||||||
|
history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handleCreate = () => { |
||||||
|
this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) |
||||||
|
} |
||||||
|
|
||||||
|
handleImport = () => { |
||||||
|
this.props.history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { t } = this.context |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="select-action"> |
||||||
|
<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> |
||||||
|
|
||||||
|
<div className="select-action__wrapper"> |
||||||
|
|
||||||
|
|
||||||
|
<div className="select-action__body"> |
||||||
|
<div className="select-action__body-header"> |
||||||
|
{ t('newToMetaMask') } |
||||||
|
</div> |
||||||
|
<div className="select-action__select-buttons"> |
||||||
|
<div className="select-action__select-button"> |
||||||
|
<div className="select-action__button-content"> |
||||||
|
<div className="select-action__button-symbol"> |
||||||
|
<img src="/images/download-alt.svg" /> |
||||||
|
</div> |
||||||
|
<div className="select-action__button-text-big"> |
||||||
|
{ t('noAlreadyHaveSeed') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<Button |
||||||
|
type="primary" |
||||||
|
className="first-time-flow__button" |
||||||
|
onClick={this.handleImport} |
||||||
|
> |
||||||
|
{ t('importWallet') } |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
<div className="select-action__select-button"> |
||||||
|
<div className="select-action__button-content"> |
||||||
|
<div className="select-action__button-symbol"> |
||||||
|
<img src="/images/thin-plus.svg" /> |
||||||
|
</div> |
||||||
|
<div className="select-action__button-text-big"> |
||||||
|
{ t('letsGoSetUp') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<Button |
||||||
|
type="confirm" |
||||||
|
className="first-time-flow__button" |
||||||
|
onClick={this.handleCreate} |
||||||
|
> |
||||||
|
{ t('createAWallet') } |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -1,43 +1,42 @@ |
|||||||
.welcome-page { |
.welcome-page { |
||||||
display: flex; |
display: flex; |
||||||
flex-direction: column; |
flex-direction: column; |
||||||
justify-content: center; |
justify-content: flex-start; |
||||||
align-items: center; |
align-items: center; |
||||||
width: 400px; |
max-width: 442px; |
||||||
padding: 0 18px; |
padding: 0 18px; |
||||||
|
color: black; |
||||||
|
|
||||||
&__wrapper { |
&__wrapper { |
||||||
display: flex; |
display: flex; |
||||||
flex-direction: row; |
flex-direction: row; |
||||||
justify-content: center; |
justify-content: center; |
||||||
align-items: center; |
align-items: flex-start; |
||||||
height: 100%; |
height: 100%; |
||||||
|
margin-top: 110px; |
||||||
} |
} |
||||||
|
|
||||||
&__header { |
&__header { |
||||||
font-size: 1.5rem; |
font-size: 28px; |
||||||
margin-bottom: 14px; |
margin-bottom: 22px; |
||||||
|
margin-top: 50px; |
||||||
} |
} |
||||||
|
|
||||||
&__description { |
&__description { |
||||||
text-align: center; |
text-align: center; |
||||||
|
|
||||||
|
div { |
||||||
|
font-size: 16px; |
||||||
|
} |
||||||
|
|
||||||
@media screen and (max-width: 575px) { |
@media screen and (max-width: 575px) { |
||||||
font-size: .9rem; |
font-size: .9rem; |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
&__button { |
.first-time-flow__button { |
||||||
height: 54px; |
width: 184px; |
||||||
width: 198px; |
|
||||||
font-family: Roboto; |
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14); |
|
||||||
color: $white; |
|
||||||
font-size: 1.25rem; |
|
||||||
font-weight: 500; |
font-weight: 500; |
||||||
text-transform: uppercase; |
margin-top: 44px; |
||||||
margin: 35px 0 14px; |
|
||||||
transition: 200ms ease-in-out; |
|
||||||
background-color: rgba(247, 134, 28, .9); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue