diff options
Diffstat (limited to 'ui/app/components/pages/first-time-flow')
25 files changed, 609 insertions, 59 deletions
diff --git a/ui/app/components/pages/first-time-flow/create-password/create-password.component.js b/ui/app/components/pages/first-time-flow/create-password/create-password.component.js index 7cca82ca6..3faaa3764 100644 --- a/ui/app/components/pages/first-time-flow/create-password/create-password.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/create-password.component.js @@ -3,18 +3,16 @@ 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 UniqueImage from './unique-image' import { INITIALIZE_CREATE_PASSWORD_ROUTE, INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, - INITIALIZE_UNIQUE_IMAGE_ROUTE, + INITIALIZE_END_OF_FLOW_ROUTE, } from '../../../../routes' export default class CreatePassword extends PureComponent { static propTypes = { history: PropTypes.object, isInitialized: PropTypes.bool, - isImportedKeyring: PropTypes.bool, onCreateNewAccount: PropTypes.func, onCreateNewAccountFromSeed: PropTypes.func, } @@ -23,12 +21,12 @@ export default class CreatePassword extends PureComponent { const { isInitialized, history } = this.props if (isInitialized) { - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + history.push(INITIALIZE_END_OF_FLOW_ROUTE) } } render () { - const { onCreateNewAccount, onCreateNewAccountFromSeed, isImportedKeyring } = this.props + const { onCreateNewAccount, onCreateNewAccountFromSeed } = this.props return ( <div className="first-time-flow__wrapper"> @@ -46,15 +44,6 @@ export default class CreatePassword extends PureComponent { /> </div> <Switch> - <Route exact - path={INITIALIZE_UNIQUE_IMAGE_ROUTE} - render={props => ( - <UniqueImage - { ...props } - isImportedKeyring={isImportedKeyring} - /> - )} - /> <Route exact path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE} diff --git a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index 2e99147bb..6b3c03bb3 100644 --- a/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -5,12 +5,13 @@ import TextField from '../../../../text-field' import Button from '../../../../button' import { INITIALIZE_SELECT_ACTION_ROUTE, - INITIALIZE_UNIQUE_IMAGE_ROUTE, + INITIALIZE_END_OF_FLOW_ROUTE, } from '../../../../../routes' export default class ImportWithSeedPhrase extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { @@ -104,7 +105,14 @@ export default class ImportWithSeedPhrase extends PureComponent { try { await onSubmit(password, seedPhrase) - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + 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 }) } @@ -132,6 +140,14 @@ export default class ImportWithSeedPhrase extends PureComponent { } toggleTermsCheck = () => { + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import Seed Phrase', + name: 'Check ToS', + }, + }) + this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) @@ -150,6 +166,13 @@ export default class ImportWithSeedPhrase extends PureComponent { <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="#" @@ -208,7 +231,15 @@ export default class ImportWithSeedPhrase extends PureComponent { {termsChecked ? <i className="fa fa-check fa-2x" /> : null} </div> <span className="first-time-flow__checkbox-label"> - { t('agreeTermsOfService') } + 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 diff --git a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js index b82cba0c5..11d10e2d9 100644 --- a/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/new-account/new-account.component.js @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Button from '../../../../button' import { - INITIALIZE_UNIQUE_IMAGE_ROUTE, + INITIALIZE_SEED_PHRASE_ROUTE, INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, INITIALIZE_SELECT_ACTION_ROUTE, } from '../../../../../routes' @@ -10,6 +10,7 @@ import TextField from '../../../../text-field' export default class NewAccount extends PureComponent { static contextTypes = { + metricsEvent: PropTypes.func, t: PropTypes.func, } @@ -99,7 +100,16 @@ export default class NewAccount extends PureComponent { try { await onSubmit(password) - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + + 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 }) } @@ -113,6 +123,14 @@ export default class NewAccount extends PureComponent { } toggleTermsCheck = () => { + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Create Password', + name: 'Check ToS', + }, + }) + this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) @@ -128,6 +146,13 @@ export default class NewAccount extends PureComponent { <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="#" @@ -174,7 +199,15 @@ export default class NewAccount extends PureComponent { {termsChecked ? <i className="fa fa-check fa-2x" /> : null} </div> <span className="first-time-flow__checkbox-label"> - I agree to the Terms Of Service + 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 diff --git a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js index fa76074f5..cbc85c0e4 100644 --- a/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js +++ b/ui/app/components/pages/first-time-flow/create-password/unique-image/unique-image.component.js @@ -1,21 +1,21 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Button from '../../../../button' -import { INITIALIZE_SEED_PHRASE_ROUTE, INITIALIZE_END_OF_FLOW_ROUTE } from '../../../../../routes' +import { INITIALIZE_END_OF_FLOW_ROUTE } from '../../../../../routes' export default class UniqueImageScreen extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { history: PropTypes.object, - isImportedKeyring: PropTypes.bool, } render () { const { t } = this.context - const { history, isImportedKeyring } = this.props + const { history } = this.props return ( <div> @@ -37,11 +37,14 @@ export default class UniqueImageScreen extends PureComponent { type="confirm" className="first-time-flow__button" onClick={() => { - if (isImportedKeyring) { - history.push(INITIALIZE_END_OF_FLOW_ROUTE) - } else { - history.push(INITIALIZE_SEED_PHRASE_ROUTE) - } + 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') } diff --git a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js index 2ca5fd8ec..c0e2f59d9 100644 --- a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js +++ b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.component.js @@ -6,16 +6,18 @@ import { DEFAULT_ROUTE } from '../../../../routes' export default class EndOfFlowScreen extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { history: PropTypes.object, completeOnboarding: PropTypes.func, + completionMetaMetricsName: PropTypes.string, } render () { const { t } = this.context - const { history, completeOnboarding } = this.props + const { history, completeOnboarding, completionMetaMetricsName } = this.props return ( <div className="end-of-flow"> @@ -42,23 +44,44 @@ export default class EndOfFlowScreen extends PureComponent { <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"> + <div className="end-of-flow__text-3"> { '• ' + t('endOfFlowMessage3') } </div> - <div className="first-time-flow__text-block end-of-flow__text-4"> + <div className="end-of-flow__text-3"> { '• ' + t('endOfFlowMessage4') } </div> - <div className="first-time-flow__text-block end-of-flow__text-3"> - { t('endOfFlowMessage5') } + <div className="end-of-flow__text-3"> + { '• ' + t('endOfFlowMessage5') } + </div> + <div className="end-of-flow__text-3"> + { '• ' + t('endOfFlowMessage6') } </div> - <div className="first-time-flow__text-block end-of-flow__text-3"> - { '*' + t('endOfFlowMessage6') } + <div className="end-of-flow__text-3"> + { '• ' + t('endOfFlowMessage7') } + </div> + <div className="first-time-flow__text-block end-of-flow__text-4"> + *MetaMask cannot recover your seedphrase. <a + href="https://metamask.zendesk.com/hc/en-us/articles/360015489591-Basic-Safety-Tips" + target="_blank" + rel="noopener noreferrer" + > + <span className="first-time-flow__link-text"> + Learn More + </span> + </a>. </div> <Button type="confirm" className="first-time-flow__button" onClick={async () => { await completeOnboarding() + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Onboarding Complete', + name: completionMetaMetricsName, + }, + }) history.push(DEFAULT_ROUTE) }} > diff --git a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js index ffe2c0efb..91ae5a941 100644 --- a/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/components/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -2,10 +2,24 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../../actions' +const firstTimeFlowTypeNameMap = { + create: 'New Wallet Created', + 'import': 'New Wallet Imported', +} + +const mapStateToProps = ({ metamask }) => { + const { firstTimeFlowType } = metamask + + return { + completionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + } +} + + const mapDispatchToProps = dispatch => { return { completeOnboarding: () => dispatch(setCompletedOnboarding()), } } -export default connect(null, mapDispatchToProps)(EndOfFlow) +export default connect(mapStateToProps, mapDispatchToProps)(EndOfFlow) diff --git a/ui/app/components/pages/first-time-flow/end-of-flow/index.scss b/ui/app/components/pages/first-time-flow/end-of-flow/index.scss index 5f5cc5991..d7eb4513b 100644 --- a/ui/app/components/pages/first-time-flow/end-of-flow/index.scss +++ b/ui/app/components/pages/first-time-flow/end-of-flow/index.scss @@ -24,12 +24,18 @@ margin-top: 26px; } - &__text-3 { - margin-top: 26px; + &__text-3 { + margin-top: 2px; + margin-bottom: 2px; + + @media screen and (max-width: $break-small) { + margin-bottom: 16px; + font-size: .875rem; + } } - &__text-3 { - margin-top: 2px; + &__text-4 { + margin-top: 26px; } button { diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js index 43f792e06..5c2294393 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.component.js @@ -7,6 +7,7 @@ import { INITIALIZE_WELCOME_ROUTE, INITIALIZE_UNLOCK_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, } from '../../../../routes' export default class FirstTimeFlowSwitch extends PureComponent { @@ -15,6 +16,7 @@ export default class FirstTimeFlowSwitch extends PureComponent { isInitialized: PropTypes.bool, isUnlocked: PropTypes.bool, seedPhrase: PropTypes.string, + optInMetaMetrics: PropTypes.bool, } render () { @@ -23,6 +25,7 @@ export default class FirstTimeFlowSwitch extends PureComponent { isInitialized, isUnlocked, seedPhrase, + optInMetaMetrics, } = this.props if (completedOnboarding) { @@ -45,6 +48,10 @@ export default class FirstTimeFlowSwitch extends PureComponent { return <Redirect to={{ pathname: INITIALIZE_SEED_PHRASE_ROUTE }} /> } - return <Redirect to={{ pathname: INITIALIZE_WELCOME_ROUTE }} /> + if (optInMetaMetrics === null) { + return <Redirect to={{ pathname: INITIALIZE_WELCOME_ROUTE }} /> + } + + return <Redirect to={{ pathname: INITIALIZE_METAMETRICS_OPT_IN_ROUTE }} /> } } diff --git a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js index e44c216c0..d68f7a153 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow-switch/first-time-flow-switch.container.js @@ -6,12 +6,14 @@ const mapStateToProps = ({ metamask }) => { completedOnboarding, isInitialized, isUnlocked, + participateInMetaMetrics: optInMetaMetrics, } = metamask return { completedOnboarding, isInitialized, isUnlocked, + optInMetaMetrics, } } diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.component.js b/ui/app/components/pages/first-time-flow/first-time-flow.component.js index 82308dda2..a1f629431 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow.component.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow.component.js @@ -8,6 +8,7 @@ import EndOfFlow from './end-of-flow' import Unlock from '../unlock-page' import CreatePassword from './create-password' import SeedPhrase from './seed-phrase' +import MetaMetricsOptInScreen from './metametrics-opt-in' import { DEFAULT_ROUTE, INITIALIZE_WELCOME_ROUTE, @@ -16,6 +17,7 @@ import { INITIALIZE_UNLOCK_ROUTE, INITIALIZE_SELECT_ACTION_ROUTE, INITIALIZE_END_OF_FLOW_ROUTE, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, } from '../../../routes' export default class FirstTimeFlow extends PureComponent { @@ -27,6 +29,7 @@ export default class FirstTimeFlow extends PureComponent { isInitialized: PropTypes.bool, isUnlocked: PropTypes.bool, unlockAccount: PropTypes.func, + nextRoute: PropTypes.func, } state = { @@ -71,12 +74,12 @@ export default class FirstTimeFlow extends PureComponent { } handleUnlock = async password => { - const { unlockAccount, history } = this.props + const { unlockAccount, history, nextRoute } = this.props try { const seedPhrase = await unlockAccount(password) this.setState({ seedPhrase }, () => { - history.push(INITIALIZE_SEED_PHRASE_ROUTE) + history.push(nextRoute) }) } catch (error) { throw new Error(error.message) @@ -134,6 +137,11 @@ export default class FirstTimeFlow extends PureComponent { /> <Route exact + path={INITIALIZE_METAMETRICS_OPT_IN_ROUTE} + component={MetaMetricsOptInScreen} + /> + <Route + exact path="*" component={FirstTimeFlowSwitch} /> diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.container.js b/ui/app/components/pages/first-time-flow/first-time-flow.container.js index 1419dd59f..293f94c47 100644 --- a/ui/app/components/pages/first-time-flow/first-time-flow.container.js +++ b/ui/app/components/pages/first-time-flow/first-time-flow.container.js @@ -1,5 +1,6 @@ import { connect } from 'react-redux' import FirstTimeFlow from './first-time-flow.component' +import { getFirstTimeFlowTypeRoute } from './first-time-flow.selectors' import { createNewVaultAndGetSeedPhrase, createNewVaultAndRestore, @@ -13,6 +14,7 @@ const mapStateToProps = state => { completedOnboarding, isInitialized, isUnlocked, + nextRoute: getFirstTimeFlowTypeRoute(state), } } diff --git a/ui/app/components/pages/first-time-flow/first-time-flow.selectors.js b/ui/app/components/pages/first-time-flow/first-time-flow.selectors.js new file mode 100644 index 000000000..1286afed9 --- /dev/null +++ b/ui/app/components/pages/first-time-flow/first-time-flow.selectors.js @@ -0,0 +1,26 @@ +import { + INITIALIZE_CREATE_PASSWORD_ROUTE, + INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, + DEFAULT_ROUTE, +} from '../../../routes' + +const selectors = { + getFirstTimeFlowTypeRoute, +} + +module.exports = selectors + +function getFirstTimeFlowTypeRoute (state) { + const { firstTimeFlowType } = state.metamask + + let nextRoute + if (firstTimeFlowType === 'create') { + nextRoute = INITIALIZE_CREATE_PASSWORD_ROUTE + } else if (firstTimeFlowType === 'import') { + nextRoute = INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE + } else { + nextRoute = DEFAULT_ROUTE + } + + return nextRoute +} diff --git a/ui/app/components/pages/first-time-flow/index.scss b/ui/app/components/pages/first-time-flow/index.scss index e14d57f58..d41748575 100644 --- a/ui/app/components/pages/first-time-flow/index.scss +++ b/ui/app/components/pages/first-time-flow/index.scss @@ -6,6 +6,9 @@ @import './end-of-flow/index'; +@import './metametrics-opt-in/index'; + + .first-time-flow { width: 100%; background-color: $white; @@ -149,4 +152,8 @@ color: #939090; margin-left: 18px; } + + &__link-text { + color: $curious-blue; + } } diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.js new file mode 100644 index 000000000..4bc2fc3a7 --- /dev/null +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.js @@ -0,0 +1 @@ +export { default } from './metametrics-opt-in.container' diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss new file mode 100644 index 000000000..6c2e37785 --- /dev/null +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/index.scss @@ -0,0 +1,136 @@ +.metametrics-opt-in { + position: relative; + width: 100%; + + a { + color: #2f9ae0bf; + } + + &__main { + display: flex; + flex-direction: column; + margin-left: 26.26%; + margin-right: 28%; + color: black; + + @media screen and (max-width: 575px) { + justify-content: center; + margin-left: 2%; + margin-right: 0%; + } + + .app-header__logo-container { + margin-top: 3%; + } + } + + &__title { + position: relative; + margin-top: 20px; + + font-family: Roboto; + font-style: normal; + font-weight: normal; + line-height: normal; + font-size: 42px; + } + + &__body-graphic { + margin-top: 25px; + + .fa-bar-chart { + color: #C4C4C4; + } + } + + &__description { + font-family: Roboto; + font-style: normal; + font-weight: normal; + line-height: 21px; + font-size: 16px; + margin-top: 12px; + } + + &__committments { + display: flex; + flex-direction: column; + } + + &__content { + overflow-y: scroll; + flex: 1; + } + + &__row { + display: flex; + margin-top: 8px; + + .fa-check { + margin-right: 12px; + color: #1ACC56; + } + + .fa-times { + margin-right: 12px; + color: #D0021B; + } + } + + &__bold { + font-weight: bold; + } + + &__break-row { + margin-top: 30px; + } + + &__body { + position: relative; + display: flex; + max-width: 730px; + flex-direction: column; + } + + &__body-text { + max-width: 548px; + margin-left: 16px; + margin-right: 16px; + } + + &__bottom-text { + margin-top: 10px; + color: #9a9a9a; + } + + &__content { + overflow-y: auto; + } + + &__footer { + margin-top: 26px; + + @media screen and (max-width: 575px) { + margin-top: 10px; + justify-content: center; + margin-left: 2%; + max-height: 520px; + } + + .page-container__footer { + border-top: none; + max-width: 535px; + margin-bottom: 15px; + + button { + height: 44px; + min-height: 44px; + margin-right: 16px; + } + + header { + padding: 0px; + } + } + } +}
\ No newline at end of file diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js new file mode 100644 index 000000000..834516f5f --- /dev/null +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js @@ -0,0 +1,169 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import PageContainerFooter from '../../../page-container/page-container-footer' + +export default class MetaMetricsOptIn extends Component { + static propTypes = { + history: PropTypes.object, + setParticipateInMetaMetrics: PropTypes.func, + nextRoute: PropTypes.string, + firstTimeSelectionMetaMetricsName: PropTypes.string, + participateInMetaMetrics: PropTypes.bool, + } + + static contextTypes = { + metricsEvent: PropTypes.func, + } + + render () { + const { metricsEvent } = this.context + const { + nextRoute, + history, + setParticipateInMetaMetrics, + firstTimeSelectionMetaMetricsName, + participateInMetaMetrics, + } = this.props + + return ( + <div className="metametrics-opt-in"> + <div className="metametrics-opt-in__main"> + <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="metametrics-opt-in__body-graphic"> + <img src="images/metrics-chart.svg" /> + </div> + <div className="metametrics-opt-in__title">Help Us Improve MetaMask</div> + <div className="metametrics-opt-in__body"> + <div className="metametrics-opt-in__description"> + MetaMask would like to gather usage data to better understand how our users interact with the extension. This data + will be used to continually improve the usability and user experience of our product and the Etheruem ecosystem. + </div> + <div className="metametrics-opt-in__description"> + MetaMask will.. + </div> + + <div className="metametrics-opt-in__committments"> + <div className="metametrics-opt-in__row"> + <i className="fa fa-check" /> + <div className="metametrics-opt-in__row-description"> + Always allow you to opt-out via Settings + </div> + </div> + <div className="metametrics-opt-in__row"> + <i className="fa fa-check" /> + <div className="metametrics-opt-in__row-description"> + Send anonymized click & pageview events + </div> + </div> + <div className="metametrics-opt-in__row"> + <i className="fa fa-check" /> + <div className="metametrics-opt-in__row-description"> + Maintain a public aggregate dashboard to educate the community + </div> + </div> + <div className="metametrics-opt-in__row metametrics-opt-in__break-row"> + <i className="fa fa-times" /> + <div className="metametrics-opt-in__row-description"> + <span className="metametrics-opt-in__bold">Never</span> collect keys, addresses, transactions, balances, hashes, or any personal information + </div> + </div> + <div className="metametrics-opt-in__row"> + <i className="fa fa-times" /> + <div className="metametrics-opt-in__row-description"> + <span className="metametrics-opt-in__bold">Never</span> collect your full IP address + </div> + </div> + <div className="metametrics-opt-in__row"> + <i className="fa fa-times" /> + <div className="metametrics-opt-in__row-description"> + <span className="metametrics-opt-in__bold">Never</span> sell data for profit. Ever! + </div> + </div> + </div> + </div> + <div className="metametrics-opt-in__footer"> + <PageContainerFooter + onCancel={() => { + setParticipateInMetaMetrics(false) + .then(() => { + if (participateInMetaMetrics === null) { + return metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt Out', + }, + isOptIn: true, + }, { + excludeMetaMetricsId: true, + }) + .then(() => { + history.push(nextRoute) + }) + } + }) + }} + cancelText={'No Thanks'} + hideCancel={false} + onSubmit={() => { + setParticipateInMetaMetrics(true) + .then(([participateStatus, metaMetricsId]) => { + const promise = participateInMetaMetrics === null + ? metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Metrics Option', + name: 'Metrics Opt In', + }, + isOptIn: true, + }) + : Promise.resolve() + + promise + .then(() => { + return metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import or Create', + name: firstTimeSelectionMetaMetricsName, + }, + isOptIn: true, + metaMetricsId, + }) + }) + .then(() => { + history.push(nextRoute) + }) + }) + }} + submitText={'I agree'} + submitButtonType={'confirm'} + disabled={false} + /> + <div className="metametrics-opt-in__bottom-text"> + This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our <a + href="https://metamask.io/privacy.html" + target="_blank" + rel="noopener noreferrer" + > + Privacy Policy here + </a>. + </div> + </div> + </div> + </div> + ) + } +} diff --git a/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js new file mode 100644 index 000000000..b13af8bf6 --- /dev/null +++ b/ui/app/components/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.container.js @@ -0,0 +1,27 @@ +import { connect } from 'react-redux' +import MetaMetricsOptIn from './metametrics-opt-in.component' +import { setParticipateInMetaMetrics } from '../../../../actions' +import { getFirstTimeFlowTypeRoute } from '../first-time-flow.selectors' + +const firstTimeFlowTypeNameMap = { + create: 'Selected Create New Wallet', + 'import': 'Selected Import Wallet', +} + +const mapStateToProps = (state) => { + const { firstTimeFlowType, participateInMetaMetrics } = state.metamask + + return { + nextRoute: getFirstTimeFlowTypeRoute(state), + firstTimeSelectionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + participateInMetaMetrics, + } +} + +const mapDispatchToProps = dispatch => { + return { + setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(MetaMetricsOptIn) 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 index b5c4bf463..bd5ab8a84 100644 --- 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 @@ -3,12 +3,16 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import shuffle from 'lodash.shuffle' import Button from '../../../../button' -import { INITIALIZE_END_OF_FLOW_ROUTE, INITIALIZE_SEED_PHRASE_ROUTE } from '../../../../../routes' +import { + INITIALIZE_END_OF_FLOW_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 = { + metricsEvent: PropTypes.func, t: PropTypes.func, } @@ -47,6 +51,13 @@ export default class ConfirmSeedPhrase extends PureComponent { } try { + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Verify Complete', + }, + }) history.push(INITIALIZE_END_OF_FLOW_ROUTE) } catch (error) { console.error(error.message) 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 index 732ce14af..cb8a01322 100644 --- 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 @@ -9,6 +9,7 @@ import { exportAsFile } from '../../../../../../app/util' export default class RevealSeedPhrase extends PureComponent { static contextTypes = { t: PropTypes.func, + metricsEvent: PropTypes.func, } static propTypes = { @@ -29,6 +30,14 @@ export default class RevealSeedPhrase extends PureComponent { const { isShowingSeedPhrase } = this.state const { history } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Advance to Verify', + }, + }) + if (!isShowingSeedPhrase) { return } @@ -53,7 +62,16 @@ export default class RevealSeedPhrase extends PureComponent { !isShowingSeedPhrase && ( <div className="reveal-seed-phrase__secret-blocker" - onClick={() => this.setState({ isShowingSeedPhrase: true })} + onClick={() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Seed Phrase Setup', + name: 'Revealed Words', + }, + }) + this.setState({ isShowingSeedPhrase: true }) + }} > <LockIcon width="28px" diff --git a/ui/app/components/pages/first-time-flow/select-action/index.js b/ui/app/components/pages/first-time-flow/select-action/index.js index 3aa968834..4fbe1823b 100644 --- a/ui/app/components/pages/first-time-flow/select-action/index.js +++ b/ui/app/components/pages/first-time-flow/select-action/index.js @@ -1 +1 @@ -export { default } from './select-action.component' +export { default } from './select-action.container' diff --git a/ui/app/components/pages/first-time-flow/select-action/index.scss b/ui/app/components/pages/first-time-flow/select-action/index.scss index b9585eb3b..e1b22d05b 100644 --- a/ui/app/components/pages/first-time-flow/select-action/index.scss +++ b/ui/app/components/pages/first-time-flow/select-action/index.scss @@ -32,7 +32,7 @@ flex-direction: column; align-items: center; justify-content: space-evenly; - width: 269px; + width: 388px; height: 278px; border: 1px solid #D8D8D8; @@ -78,6 +78,7 @@ font-size: 14px; color: #7A7A7B; margin-top: 10px; + text-align: center; } button { diff --git a/ui/app/components/pages/first-time-flow/select-action/select-action.component.js b/ui/app/components/pages/first-time-flow/select-action/select-action.component.js index 385efe02a..b6a6942c3 100644 --- a/ui/app/components/pages/first-time-flow/select-action/select-action.component.js +++ b/ui/app/components/pages/first-time-flow/select-action/select-action.component.js @@ -2,15 +2,15 @@ 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, + INITIALIZE_METAMETRICS_OPT_IN_ROUTE, } from '../../../../routes' export default class SelectAction extends PureComponent { static propTypes = { history: PropTypes.object, isInitialized: PropTypes.bool, + setFirstTimeFlowType: PropTypes.func, + nextRoute: PropTypes.string, } static contextTypes = { @@ -18,19 +18,21 @@ export default class SelectAction extends PureComponent { } componentDidMount () { - const { history, isInitialized } = this.props + const { history, isInitialized, nextRoute } = this.props if (isInitialized) { - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + history.push(nextRoute) } } handleCreate = () => { - this.props.history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + this.props.setFirstTimeFlowType('create') + this.props.history.push(INITIALIZE_METAMETRICS_OPT_IN_ROUTE) } handleImport = () => { - this.props.history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) + this.props.setFirstTimeFlowType('import') + this.props.history.push(INITIALIZE_METAMETRICS_OPT_IN_ROUTE) } render () { @@ -68,6 +70,9 @@ export default class SelectAction extends PureComponent { <div className="select-action__button-text-big"> { t('noAlreadyHaveSeed') } </div> + <div className="select-action__button-text-small"> + { t('importYourExisting') } + </div> </div> <Button type="primary" @@ -85,6 +90,9 @@ export default class SelectAction extends PureComponent { <div className="select-action__button-text-big"> { t('letsGoSetUp') } </div> + <div className="select-action__button-text-small"> + { t('thisWillCreate') } + </div> </div> <Button type="confirm" diff --git a/ui/app/components/pages/first-time-flow/select-action/select-action.container.js b/ui/app/components/pages/first-time-flow/select-action/select-action.container.js index e69de29bb..42fac7af2 100644 --- a/ui/app/components/pages/first-time-flow/select-action/select-action.container.js +++ b/ui/app/components/pages/first-time-flow/select-action/select-action.container.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import { setFirstTimeFlowType } from '../../../../actions' +import { getFirstTimeFlowTypeRoute } from '../first-time-flow.selectors' +import Welcome from './select-action.component' + +const mapStateToProps = (state) => { + return { + nextRoute: getFirstTimeFlowTypeRoute(state), + } +} + +const mapDispatchToProps = dispatch => { + return { + setFirstTimeFlowType: type => dispatch(setFirstTimeFlowType(type)), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(Welcome) diff --git a/ui/app/components/pages/first-time-flow/welcome/welcome.component.js b/ui/app/components/pages/first-time-flow/welcome/welcome.component.js index 08eb86939..88cdb936c 100644 --- a/ui/app/components/pages/first-time-flow/welcome/welcome.component.js +++ b/ui/app/components/pages/first-time-flow/welcome/welcome.component.js @@ -3,12 +3,14 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Mascot from '../../../mascot' import Button from '../../../button' -import { INITIALIZE_SELECT_ACTION_ROUTE, INITIALIZE_UNIQUE_IMAGE_ROUTE } from '../../../../routes' +import { INITIALIZE_CREATE_PASSWORD_ROUTE, INITIALIZE_SELECT_ACTION_ROUTE } from '../../../../routes' export default class Welcome extends PureComponent { static propTypes = { history: PropTypes.object, isInitialized: PropTypes.bool, + participateInMetaMetrics: PropTypes.bool, + welcomeScreenSeen: PropTypes.bool, } static contextTypes = { @@ -22,10 +24,12 @@ export default class Welcome extends PureComponent { } componentDidMount () { - const { history, isInitialized } = this.props + const { history, participateInMetaMetrics, welcomeScreenSeen } = this.props - if (isInitialized) { - history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE) + if (welcomeScreenSeen && participateInMetaMetrics !== null) { + history.push(INITIALIZE_CREATE_PASSWORD_ROUTE) + } else if (welcomeScreenSeen) { + history.push(INITIALIZE_SELECT_ACTION_ROUTE) } } diff --git a/ui/app/components/pages/first-time-flow/welcome/welcome.container.js b/ui/app/components/pages/first-time-flow/welcome/welcome.container.js index 4362d89cb..47753e16f 100644 --- a/ui/app/components/pages/first-time-flow/welcome/welcome.container.js +++ b/ui/app/components/pages/first-time-flow/welcome/welcome.container.js @@ -5,11 +5,12 @@ import { closeWelcomeScreen } from '../../../../actions' import Welcome from './welcome.component' const mapStateToProps = ({ metamask }) => { - const { welcomeScreenSeen, isInitialized } = metamask + const { welcomeScreenSeen, isInitialized, participateInMetaMetrics } = metamask return { welcomeScreenSeen, isInitialized, + participateInMetaMetrics, } } |