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 { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
justify-content: flex-start; |
||||
align-items: center; |
||||
width: 400px; |
||||
max-width: 442px; |
||||
padding: 0 18px; |
||||
color: black; |
||||
|
||||
&__wrapper { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: center; |
||||
align-items: center; |
||||
align-items: flex-start; |
||||
height: 100%; |
||||
margin-top: 110px; |
||||
} |
||||
|
||||
&__header { |
||||
font-size: 1.5rem; |
||||
margin-bottom: 14px; |
||||
font-size: 28px; |
||||
margin-bottom: 22px; |
||||
margin-top: 50px; |
||||
} |
||||
|
||||
&__description { |
||||
text-align: center; |
||||
|
||||
div { |
||||
font-size: 16px; |
||||
} |
||||
|
||||
@media screen and (max-width: 575px) { |
||||
font-size: .9rem; |
||||
} |
||||
} |
||||
|
||||
&__button { |
||||
height: 54px; |
||||
width: 198px; |
||||
font-family: Roboto; |
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14); |
||||
color: $white; |
||||
font-size: 1.25rem; |
||||
.first-time-flow__button { |
||||
width: 184px; |
||||
font-weight: 500; |
||||
text-transform: uppercase; |
||||
margin: 35px 0 14px; |
||||
transition: 200ms ease-in-out; |
||||
background-color: rgba(247, 134, 28, .9); |
||||
margin-top: 44px; |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue