diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 689c002bae8f..b1b6d96882b9 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2061,7 +2061,7 @@ "message": "down arrow" }, "downloadAppDescription": { - "message": "Bring MetaMask with you everywhere you go with the mobile app. Plus, turn on Face ID so you always have access, even if you forget your password." + "message": "Bring MetaMask with you everywhere you go. Turn on Face ID so you always have access, even if you forget your password." }, "downloadAppTitle": { "message": "Scan QR code and download the app" @@ -4756,6 +4756,12 @@ "message": "Pin", "description": "Pin label used in multichain account menu" }, + "pinMetaMask": { + "message": "Pin the MetaMask extension" + }, + "pinMetaMaskDescription": { + "message": "Click on $1 and then $2 to pin it." + }, "pinToTop": { "message": "Pin to top" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 689c002bae8f..b1b6d96882b9 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -2061,7 +2061,7 @@ "message": "down arrow" }, "downloadAppDescription": { - "message": "Bring MetaMask with you everywhere you go with the mobile app. Plus, turn on Face ID so you always have access, even if you forget your password." + "message": "Bring MetaMask with you everywhere you go. Turn on Face ID so you always have access, even if you forget your password." }, "downloadAppTitle": { "message": "Scan QR code and download the app" @@ -4756,6 +4756,12 @@ "message": "Pin", "description": "Pin label used in multichain account menu" }, + "pinMetaMask": { + "message": "Pin the MetaMask extension" + }, + "pinMetaMaskDescription": { + "message": "Click on $1 and then $2 to pin it." + }, "pinToTop": { "message": "Pin to top" }, diff --git a/test/e2e/page-objects/pages/onboarding/onboarding-complete-page.ts b/test/e2e/page-objects/pages/onboarding/onboarding-complete-page.ts index ccb1a65d50fa..69b0905a8de2 100644 --- a/test/e2e/page-objects/pages/onboarding/onboarding-complete-page.ts +++ b/test/e2e/page-objects/pages/onboarding/onboarding-complete-page.ts @@ -94,20 +94,9 @@ class OnboardingCompletePage { ); } - async completeOnboarding(isSocialImportFlow: boolean = false): Promise { + async completeOnboarding(): Promise { console.log('Complete onboarding'); - if (!isSocialImportFlow) { - await this.clickCreateWalletDoneButton(); - } - - await this.displayDownloadAppPageAndContinue(); - - await this.driver.waitForSelector(this.installCompleteMessage); - await this.driver.waitForSelector(this.pinExtensionMessage); - - await this.driver.clickElementAndWaitToDisappear( - this.pinExtensionDoneButton, - ); + await this.clickCreateWalletDoneButton(); } async completeBackup(): Promise { diff --git a/test/e2e/tests/account/unlock-wallet.spec.ts b/test/e2e/tests/account/unlock-wallet.spec.ts index 9624a10e4706..a828ef6c1766 100644 --- a/test/e2e/tests/account/unlock-wallet.spec.ts +++ b/test/e2e/tests/account/unlock-wallet.spec.ts @@ -9,7 +9,6 @@ import LoginPage from '../../page-objects/pages/login-page'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; import { MOCK_GOOGLE_ACCOUNT, WALLET_PASSWORD } from '../../constants'; import { OAuthMockttpService } from '../../helpers/seedless-onboarding/mocks'; -import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; import { importWalletWithSocialLoginOnboardingFlow } from '../../page-objects/flows/onboarding.flow'; import SettingsPage from '../../page-objects/pages/settings/settings-page'; import PrivacySettings from '../../page-objects/pages/settings/privacy-settings'; @@ -62,10 +61,6 @@ describe('Unlock wallet - ', function () { driver, }); - const onboardingCompletePage = new OnboardingCompletePage(driver); - const isSocialImportFlow = true; - await onboardingCompletePage.completeOnboarding(isSocialImportFlow); - const homePage = new HomePage(driver); await homePage.checkPageIsLoaded(); diff --git a/test/e2e/tests/metrics/wallet-created.spec.ts b/test/e2e/tests/metrics/wallet-created.spec.ts index 82e7422c2b9d..f24866e805c0 100644 --- a/test/e2e/tests/metrics/wallet-created.spec.ts +++ b/test/e2e/tests/metrics/wallet-created.spec.ts @@ -281,6 +281,7 @@ describe('Wallet Created Events', function () { await createNewWalletWithSocialLoginOnboardingFlow(onboardingOptions); const onboardingCompletePage = new OnboardingCompletePage(driver); + await onboardingCompletePage.displayDownloadAppPageAndContinue(); await onboardingCompletePage.checkPageIsLoaded(); await onboardingCompletePage.checkWalletReadyMessageIsDisplayed(); await onboardingCompletePage.completeOnboarding(); diff --git a/test/e2e/tests/onboarding/seedless-onboarding.spec.ts b/test/e2e/tests/onboarding/seedless-onboarding.spec.ts index b42f226c3f14..c4b9f9a1572e 100644 --- a/test/e2e/tests/onboarding/seedless-onboarding.spec.ts +++ b/test/e2e/tests/onboarding/seedless-onboarding.spec.ts @@ -35,6 +35,7 @@ describe('Metamask onboarding (with social login)', function () { }); const onboardingCompletePage = new OnboardingCompletePage(driver); + await onboardingCompletePage.displayDownloadAppPageAndContinue(); await onboardingCompletePage.checkPageIsLoaded(); await onboardingCompletePage.checkWalletReadyMessageIsDisplayed(); await onboardingCompletePage.completeOnboarding(); @@ -64,10 +65,6 @@ describe('Metamask onboarding (with social login)', function () { driver, }); - const onboardingCompletePage = new OnboardingCompletePage(driver); - const isSocialImportFlow = true; - await onboardingCompletePage.completeOnboarding(isSocialImportFlow); - const homePage = new HomePage(driver); await homePage.checkPageIsLoaded(); const displayedWalletAddress = await homePage.getAccountAddress(); diff --git a/test/e2e/tests/settings/change-password.spec.ts b/test/e2e/tests/settings/change-password.spec.ts index 5985a2b4b0af..c4bbf7a5d910 100644 --- a/test/e2e/tests/settings/change-password.spec.ts +++ b/test/e2e/tests/settings/change-password.spec.ts @@ -13,7 +13,6 @@ import { } from '../../page-objects/flows/onboarding.flow'; import { OAuthMockttpService } from '../../helpers/seedless-onboarding/mocks'; import { Driver } from '../../webdriver/driver'; -import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page'; import { MOCK_GOOGLE_ACCOUNT, WALLET_PASSWORD } from '../../constants'; async function doPasswordChangeAndLockWallet( @@ -107,10 +106,6 @@ describe('Change wallet password', function () { password: OLD_PASSWORD, }); - const onboardingCompletePage = new OnboardingCompletePage(driver); - const isSocialImportFlow = true; - await onboardingCompletePage.completeOnboarding(isSocialImportFlow); - const homePage = new HomePage(driver); await homePage.checkPageIsLoaded(); diff --git a/test/integration/onboarding/import-wallet.test.tsx b/test/integration/onboarding/import-wallet.test.tsx index 2ee80740ed8e..58038625b9a7 100644 --- a/test/integration/onboarding/import-wallet.test.tsx +++ b/test/integration/onboarding/import-wallet.test.tsx @@ -10,7 +10,6 @@ import { import { clickElementById, createMockImplementation, - waitForElementById, waitForElementByText, } from '../helpers'; @@ -65,15 +64,6 @@ describe('Import Wallet Events', () => { await waitForElementByText('Your wallet is ready!'); await clickElementById('onboarding-complete-done'); - await waitForElementByText('Scan QR code and download the app'); - await clickElementById('download-app-continue'); - - await waitForElementById('pin-extension-next'); - await clickElementById('pin-extension-next'); - - await waitForElementById('pin-extension-done'); - await clickElementById('pin-extension-done'); - // Verify both completeOnboarding and ExtensionPinned event are called let completeOnboardingCall; let extensionPinnedEvent; diff --git a/test/integration/onboarding/wallet-created.test.tsx b/test/integration/onboarding/wallet-created.test.tsx index c3ad5c1553cc..497232dde6cf 100644 --- a/test/integration/onboarding/wallet-created.test.tsx +++ b/test/integration/onboarding/wallet-created.test.tsx @@ -10,7 +10,6 @@ import { import { clickElementById, createMockImplementation, - waitForElementById, waitForElementByText, } from '../helpers'; @@ -73,15 +72,6 @@ describe('Wallet Created Events', () => { await waitForElementByText('Your wallet is ready!'); await clickElementById('onboarding-complete-done'); - await waitForElementByText('Scan QR code and download the app'); - await clickElementById('download-app-continue'); - - await waitForElementById('pin-extension-next'); - await clickElementById('pin-extension-next'); - - await waitForElementById('pin-extension-done'); - await clickElementById('pin-extension-done'); - // Verify both completeOnboarding and ExtensionPinned event are called let completeOnboardingCall; let extensionPinnedEvent; diff --git a/ui/pages/onboarding-flow/create-password/create-password.js b/ui/pages/onboarding-flow/create-password/create-password.js index 64eef5fb6366..b7a7fae489e5 100644 --- a/ui/pages/onboarding-flow/create-password/create-password.js +++ b/ui/pages/onboarding-flow/create-password/create-password.js @@ -17,6 +17,7 @@ import { } from '../../../helpers/constants/design-system'; import { ONBOARDING_COMPLETION_ROUTE, + ONBOARDING_DOWNLOAD_APP_ROUTE, ONBOARDING_IMPORT_WITH_SRP_ROUTE, ONBOARDING_METAMETRICS, ONBOARDING_REVIEW_SRP_ROUTE, @@ -254,7 +255,7 @@ export default function CreatePassword({ dispatch(setMarketingConsent(true)); dispatch(setDataCollectionForMarketing(true)); } - navigate(ONBOARDING_COMPLETION_ROUTE, { replace: true }); + navigate(ONBOARDING_DOWNLOAD_APP_ROUTE, { replace: true }); } else { navigate(ONBOARDING_REVIEW_SRP_ROUTE, { replace: true }); } diff --git a/ui/pages/onboarding-flow/creation-successful/creation-successful.js b/ui/pages/onboarding-flow/creation-successful/creation-successful.js index 3510d683786e..880cd6f30c63 100644 --- a/ui/pages/onboarding-flow/creation-successful/creation-successful.js +++ b/ui/pages/onboarding-flow/creation-successful/creation-successful.js @@ -1,6 +1,6 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useContext } from 'react'; import { useNavigate, useLocation } from 'react-router-dom-v5-compat'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { capitalize } from 'lodash'; import { Button, @@ -33,19 +33,38 @@ import { ONBOARDING_PRIVACY_SETTINGS_ROUTE, DEFAULT_ROUTE, SECURITY_ROUTE, - ONBOARDING_DOWNLOAD_APP_ROUTE, } from '../../../helpers/constants/routes'; -import { getSocialLoginType } from '../../../selectors'; +import { + getSocialLoginType, + getExternalServicesOnboardingToggleState, + getFirstTimeFlowType, +} from '../../../selectors'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { FirstTimeFlowType } from '../../../../shared/constants/onboarding'; import { getIsPrimarySeedPhraseBackedUp } from '../../../ducks/metamask/metamask'; - +import { + toggleExternalServices, + setCompletedOnboarding, +} from '../../../store/actions'; import { LottieAnimation } from '../../../components/component-library/lottie-animation'; export default function CreationSuccessful() { const navigate = useNavigate(); + const dispatch = useDispatch(); const t = useI18nContext(); const { search } = useLocation(); const isWalletReady = useSelector(getIsPrimarySeedPhraseBackedUp); const userSocialLoginType = useSelector(getSocialLoginType); + const externalServicesOnboardingToggleState = useSelector( + getExternalServicesOnboardingToggleState, + ); + const trackEvent = useContext(MetaMetricsContext); + const firstTimeFlowType = useSelector(getFirstTimeFlowType); + const learnMoreLink = 'https://support.metamask.io/stay-safe/safety-in-web3/basic-safety-and-security-tips-for-metamask/'; @@ -124,13 +143,40 @@ export default function CreationSuccessful() { ); }, [isWalletReady, isFromReminder]); - const onDone = useCallback(() => { + const onDone = useCallback(async () => { + if (isWalletReady) { + trackEvent({ + category: MetaMetricsEventCategory.Onboarding, + event: MetaMetricsEventName.ExtensionPinned, + properties: { + // eslint-disable-next-line @typescript-eslint/naming-convention + wallet_setup_type: + firstTimeFlowType === FirstTimeFlowType.import ? 'import' : 'new', + // eslint-disable-next-line @typescript-eslint/naming-convention + new_wallet: firstTimeFlowType === FirstTimeFlowType.create, + }, + }); + } + if (isFromReminder) { navigate(isFromSettingsSecurity ? SECURITY_ROUTE : DEFAULT_ROUTE); return; } - navigate(ONBOARDING_DOWNLOAD_APP_ROUTE); - }, [navigate, isFromReminder, isFromSettingsSecurity]); + await dispatch( + toggleExternalServices(externalServicesOnboardingToggleState), + ); + await dispatch(setCompletedOnboarding()); + navigate(DEFAULT_ROUTE); + }, [ + isWalletReady, + isFromReminder, + dispatch, + externalServicesOnboardingToggleState, + navigate, + trackEvent, + firstTimeFlowType, + isFromSettingsSecurity, + ]); return ( { firstTimeFlowType: FirstTimeFlowType.create, seedPhraseBackedUp: true, }, + appState: { + externalServicesOnboardingToggleState: true, + }, }; it('should render the wallet ready content if the seed phrase is backed up', () => { @@ -98,9 +101,7 @@ describe('Wallet Ready Page', () => { const doneButton = getByTestId('onboarding-complete-done'); fireEvent.click(doneButton); await waitFor(() => { - expect(mockUseNavigate).toHaveBeenCalledWith( - ONBOARDING_DOWNLOAD_APP_ROUTE, - ); + expect(mockUseNavigate).toHaveBeenCalledWith(DEFAULT_ROUTE); }); }); }); diff --git a/ui/pages/onboarding-flow/download-app/download-app.test.tsx b/ui/pages/onboarding-flow/download-app/download-app.test.tsx index 3c2b0fbe89e6..c909b96423e6 100644 --- a/ui/pages/onboarding-flow/download-app/download-app.test.tsx +++ b/ui/pages/onboarding-flow/download-app/download-app.test.tsx @@ -4,7 +4,7 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { renderWithProvider } from '../../../../test/lib/render-helpers-navigate'; import { - ONBOARDING_PIN_EXTENSION_ROUTE, + ONBOARDING_COMPLETION_ROUTE, ONBOARDING_WELCOME_ROUTE, } from '../../../helpers/constants/routes'; import { FirstTimeFlowType } from '../../../../shared/constants/onboarding'; @@ -110,7 +110,10 @@ describe('Download App Onboarding View', () => { expect(mockUseNavigate).toHaveBeenCalledTimes(1); expect(mockUseNavigate).toHaveBeenCalledWith( - ONBOARDING_PIN_EXTENSION_ROUTE, + ONBOARDING_COMPLETION_ROUTE, + { + replace: true, + }, ); }); }); diff --git a/ui/pages/onboarding-flow/download-app/download-app.tsx b/ui/pages/onboarding-flow/download-app/download-app.tsx index 79231677d6bf..441d90e49fb9 100644 --- a/ui/pages/onboarding-flow/download-app/download-app.tsx +++ b/ui/pages/onboarding-flow/download-app/download-app.tsx @@ -9,9 +9,10 @@ import { BlockSize, FlexDirection, AlignItems, + TextColor, } from '../../../helpers/constants/design-system'; import { - ONBOARDING_PIN_EXTENSION_ROUTE, + ONBOARDING_COMPLETION_ROUTE, ONBOARDING_WELCOME_ROUTE, } from '../../../helpers/constants/routes'; import { @@ -31,7 +32,7 @@ export default function OnboardingDownloadApp() { const currentKeyring = useSelector(getCurrentKeyring); const handleClick = async () => { - navigate(ONBOARDING_PIN_EXTENSION_ROUTE); + navigate(ONBOARDING_COMPLETION_ROUTE, { replace: true }); }; useEffect(() => { @@ -66,7 +67,9 @@ export default function OnboardingDownloadApp() { alt="Download the app" /> - {t('downloadAppDescription')} + + {t('downloadAppDescription')} + +
+
+
+ +
+
+

+ Pin the MetaMask extension +

+

+ + + Click on + + and then + + to pin it. + + +

+
+
+
+ + + +`; + exports[`OnboardingAppHeader should match snapshot 1`] = `
{ return { @@ -56,17 +71,63 @@ export default function OnboardingAppHeader({ isWelcomePage }) { unsetIconHeight isOnboarding /> - - dispatch(updateCurrentLocale(newLocale)) - } - /> + + {pathname === ONBOARDING_COMPLETION_ROUTE ? ( + + + + {t('pinMetaMaskDescription', [ + , + , + ])} + + + + ) : ( + + dispatch(updateCurrentLocale(newLocale)) + } + /> + )} ); diff --git a/ui/pages/onboarding-flow/onboarding-app-header/onboarding-app-header.test.js b/ui/pages/onboarding-flow/onboarding-app-header/onboarding-app-header.test.js index ab6009645af9..c93685059c4b 100644 --- a/ui/pages/onboarding-flow/onboarding-app-header/onboarding-app-header.test.js +++ b/ui/pages/onboarding-flow/onboarding-app-header/onboarding-app-header.test.js @@ -3,6 +3,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { renderWithProvider } from '../../../../test/lib/render-helpers-navigate'; +import { ONBOARDING_COMPLETION_ROUTE } from '../../../helpers/constants/routes'; import OnboardingAppHeader from './onboarding-app-header'; const mockUpdateCurrentLocale = jest.fn(); @@ -15,6 +16,18 @@ jest.mock('../../../store/actions.ts', () => ({ updateCurrentLocale: () => mockUpdateCurrentLocale, })); +const mockUseLocation = { + pathname: '/test', + search: '', + hash: '', + state: null, +}; + +jest.mock('react-router-dom-v5-compat', () => ({ + ...jest.requireActual('react-router-dom-v5-compat'), + useLocation: () => mockUseLocation, +})); + describe('OnboardingAppHeader', () => { const mockState = { localeMessages: { @@ -38,4 +51,16 @@ describe('OnboardingAppHeader', () => { expect(mockUpdateCurrentLocale).toHaveBeenCalled(); }); + + it('shoul match snapshot on onboarding completion page', () => { + mockUseLocation.pathname = ONBOARDING_COMPLETION_ROUTE; + const { container } = renderWithProvider(, store); + expect(container).toMatchSnapshot(); + }); + + it('should render the pin extension banner tip on onboarding completion page', () => { + mockUseLocation.pathname = ONBOARDING_COMPLETION_ROUTE; + const { getByText } = renderWithProvider(, store); + expect(getByText('Pin the MetaMask extension')).toBeInTheDocument(); + }); }); diff --git a/ui/pages/onboarding-flow/onboarding-flow.js b/ui/pages/onboarding-flow/onboarding-flow.js index b4ca68b154be..80abcb76f030 100644 --- a/ui/pages/onboarding-flow/onboarding-flow.js +++ b/ui/pages/onboarding-flow/onboarding-flow.js @@ -40,6 +40,7 @@ import { createNewVaultAndRestore, restoreSocialBackupAndGetSeedPhrase, createNewVaultAndSyncWithSocial, + setCompletedOnboarding, } from '../../store/actions'; import { getFirstTimeFlowType, @@ -210,6 +211,9 @@ export default function OnboardingFlow() { } setSecretRecoveryPhrase(retrievedSecretRecoveryPhrase); + if (firstTimeFlowType === FirstTimeFlowType.socialImport) { + await dispatch(setCompletedOnboarding()); + } navigate(nextRoute, { replace: true }); } finally { setIsLoading(false); diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js index 144c1c17e7e3..d4bb54429b94 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js @@ -180,8 +180,7 @@ export default function PrivacySettings() { turnon_token_detection: turnOnTokenDetection, }, }); - - navigate(ONBOARDING_COMPLETION_ROUTE); + navigate(ONBOARDING_COMPLETION_ROUTE, { replace: true }); }; const handleIPFSChange = (url) => { diff --git a/ui/pages/onboarding-flow/welcome/welcome.js b/ui/pages/onboarding-flow/welcome/welcome.js index 0514834184f6..4fe4202e78c0 100644 --- a/ui/pages/onboarding-flow/welcome/welcome.js +++ b/ui/pages/onboarding-flow/welcome/welcome.js @@ -66,20 +66,14 @@ export default function OnboardingWelcome() { if (currentKeyring && !newAccountCreationInProgress) { if ( firstTimeFlowType === FirstTimeFlowType.import || - firstTimeFlowType === FirstTimeFlowType.socialImport || firstTimeFlowType === FirstTimeFlowType.restore ) { - if (isFireFox || firstTimeFlowType !== FirstTimeFlowType.socialImport) { - navigate( - isParticipateInMetaMetricsSet - ? ONBOARDING_COMPLETION_ROUTE - : ONBOARDING_METAMETRICS, - { replace: true }, - ); - } else { - // we don't display the metametrics screen for social login flows if the user is not on firefox - navigate(ONBOARDING_COMPLETION_ROUTE, { replace: true }); - } + navigate( + isParticipateInMetaMetricsSet + ? ONBOARDING_COMPLETION_ROUTE + : ONBOARDING_METAMETRICS, + { replace: true }, + ); } else if (firstTimeFlowType === FirstTimeFlowType.socialCreate) { navigate(ONBOARDING_COMPLETION_ROUTE, { replace: true }); } else { diff --git a/ui/selectors/first-time-flow.js b/ui/selectors/first-time-flow.js index 45cf0d5775fd..05364e911f7f 100644 --- a/ui/selectors/first-time-flow.js +++ b/ui/selectors/first-time-flow.js @@ -44,10 +44,7 @@ export function getFirstTimeFlowTypeRouteAfterUnlock(state) { return ONBOARDING_IMPORT_WITH_SRP_ROUTE; } else if (firstTimeFlowType === FirstTimeFlowType.restore) { return ONBOARDING_METAMETRICS; - } else if ( - firstTimeFlowType === FirstTimeFlowType.socialCreate || - firstTimeFlowType === FirstTimeFlowType.socialImport - ) { + } else if (firstTimeFlowType === FirstTimeFlowType.socialCreate) { return ONBOARDING_DOWNLOAD_APP_ROUTE; } return DEFAULT_ROUTE; @@ -67,7 +64,6 @@ export function getFirstTimeFlowTypeRouteAfterUnlock(state) { */ export function getFirstTimeFlowTypeRouteAfterMetaMetricsOptIn(state) { const { firstTimeFlowType } = state.metamask; - if (firstTimeFlowType === FirstTimeFlowType.create) { return ONBOARDING_COMPLETION_ROUTE; } else if (firstTimeFlowType === FirstTimeFlowType.import) {