diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 887ab9e42..e1d91e249 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -1210,6 +1210,9 @@
"ipfsGatewayDescription": {
"message": "Enter the URL of the IPFS CID gateway to use for ENS content resolution."
},
+ "jsDeliver": {
+ "message": "jsDeliver"
+ },
"jsonFile": {
"message": "JSON File",
"description": "format for importing an account"
@@ -1620,6 +1623,14 @@
"message": "\"$1\" will close this tab and direct back to $2",
"description": "Return the user to the site that initiated onboarding"
},
+ "onboardingShowIncomingTransactionsDescription": {
+ "message": "Showing incoming transactions in your wallet relies on communication with $1. Etherscan will have access to your Ethereum address and your IP address. View $2.",
+ "description": "$1 is a clickable link with text defined by the 'etherscan' key. $2 is a clickable link with text defined by the 'privacyMsg' key."
+ },
+ "onboardingUsePhishingDetectionDescription": {
+ "message": "Phishing detection alerts rely on communication with $1. jsDeliver will have access to your IP address. View $2.",
+ "description": "The $1 is the word 'jsDeliver', from key 'jsDeliver' and $2 is the words Privacy Policy from key 'privacyMsg', both separated here so that it can be wrapped as a link"
+ },
"onlyAddTrustedNetworks": {
"message": "A malicious network provider can lie about the state of the blockchain and record your network activity. Only add custom networks you trust."
},
@@ -2020,6 +2031,12 @@
"separateEachWord": {
"message": "Separate each word with a single space"
},
+ "setAdvancedPrivacySettings": {
+ "message": "Set advanced privacy settings"
+ },
+ "setAdvancedPrivacySettingsDetails": {
+ "message": "MetaMask uses these trusted third-party services to enhance product usability and safety."
+ },
"settings": {
"message": "Settings"
},
@@ -2718,6 +2735,9 @@
"tryAgain": {
"message": "Try again"
},
+ "turnOnTokenDetection": {
+ "message": "Turn on Token Detection"
+ },
"typePassword": {
"message": "Type your MetaMask password"
},
diff --git a/ui/pages/onboarding-flow/index.scss b/ui/pages/onboarding-flow/index.scss
index 8d5a5cd0c..afcf7b2fa 100644
--- a/ui/pages/onboarding-flow/index.scss
+++ b/ui/pages/onboarding-flow/index.scss
@@ -2,6 +2,7 @@
@import 'new-account/index';
@import 'onboarding-app-header/index';
@import 'secure-your-wallet/index';
+@import 'privacy-settings/index';
.onboarding-flow {
width: 100%;
diff --git a/ui/pages/onboarding-flow/onboarding-flow.js b/ui/pages/onboarding-flow/onboarding-flow.js
index 82c1b00e6..c00af0977 100644
--- a/ui/pages/onboarding-flow/onboarding-flow.js
+++ b/ui/pages/onboarding-flow/onboarding-flow.js
@@ -9,6 +9,7 @@ import {
ONBOARDING_UNLOCK_ROUTE,
DEFAULT_ROUTE,
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
+ ONBOARDING_PRIVACY_SETTINGS_ROUTE,
} from '../../helpers/constants/routes';
import {
getCompletedOnboarding,
@@ -26,6 +27,7 @@ import NewAccount from './new-account/new-account';
import ReviewRecoveryPhrase from './recovery-phrase/review-recovery-phrase';
import SecureYourWallet from './secure-your-wallet/secure-your-wallet';
import ConfirmRecoveryPhrase from './recovery-phrase/confirm-recovery-phrase';
+import PrivacySettings from './privacy-settings/privacy-settings';
export default function OnboardingFlow() {
const [seedPhrase, setSeedPhrase] = useState('');
@@ -41,7 +43,7 @@ export default function OnboardingFlow() {
// For ONBOARDING_V2 dev purposes,
// Remove when ONBOARDING_V2 dev complete
if (process.env.ONBOARDING_V2) {
- history.push(ONBOARDING_SECURE_YOUR_WALLET_ROUTE);
+ history.push(ONBOARDING_PRIVACY_SETTINGS_ROUTE);
return;
}
@@ -108,6 +110,10 @@ export default function OnboardingFlow() {
)}
/>
+
diff --git a/ui/pages/onboarding-flow/privacy-settings/index.scss b/ui/pages/onboarding-flow/privacy-settings/index.scss
new file mode 100644
index 000000000..e5a6d059b
--- /dev/null
+++ b/ui/pages/onboarding-flow/privacy-settings/index.scss
@@ -0,0 +1,50 @@
+.privacy-settings {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ &__header {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ text-align: center;
+ max-width: 500px;
+ margin: 24px;
+ }
+
+ &__settings {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ text-align: center;
+ max-width: 620px;
+ margin-bottom: 20px;
+
+ a {
+ color: $Blue-500;
+
+ &:hover {
+ cursor: pointer;
+ color: $Blue-300;
+ }
+ }
+ }
+
+ &__setting {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ text-align: left;
+ max-width: 430px;
+
+ &__toggle {
+ margin-left: 42px;
+ }
+ }
+
+ & button {
+ max-width: 50%;
+ padding: 15px;
+ }
+}
diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js
new file mode 100644
index 000000000..e372e1130
--- /dev/null
+++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js
@@ -0,0 +1,113 @@
+import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { useDispatch } from 'react-redux';
+
+import Button from '../../../components/ui/button';
+import Typography from '../../../components/ui/typography';
+import {
+ FONT_WEIGHT,
+ TYPOGRAPHY,
+} from '../../../helpers/constants/design-system';
+import { useI18nContext } from '../../../hooks/useI18nContext';
+import {
+ setFeatureFlag,
+ setUsePhishDetect,
+ setUseTokenDetection,
+} from '../../../store/actions';
+import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes';
+import { Setting } from './setting';
+
+export default function PrivacySettings() {
+ const t = useI18nContext();
+ const dispatch = useDispatch();
+ const history = useHistory();
+ const [usePhishingDetection, setUsePhishingDetection] = useState(true);
+ const [turnOnTokenDetection, setTurnOnTokenDetection] = useState(true);
+ const [showIncomingTransactions, setShowIncomingTransactions] = useState(
+ true,
+ );
+
+ const handleSubmit = () => {
+ dispatch(
+ setFeatureFlag('showIncomingTransactions', showIncomingTransactions),
+ );
+ dispatch(setUsePhishDetect(usePhishingDetection));
+ dispatch(setUseTokenDetection(turnOnTokenDetection));
+ history.push(ONBOARDING_PIN_EXTENSION_ROUTE);
+ };
+
+ return (
+ <>
+
+
+
+ {t('setAdvancedPrivacySettings')}
+
+
+ {t('setAdvancedPrivacySettingsDetails')}
+
+
+
+
+
+ >
+ );
+}
diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.stories.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.stories.js
new file mode 100644
index 000000000..c0f775439
--- /dev/null
+++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.stories.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import PrivacySettings from './privacy-settings';
+
+export default {
+ title: 'Onboarding - Privacy Settings',
+ id: __filename,
+};
+
+export const Base = () => {
+ return (
+
+ );
+};
diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js
new file mode 100644
index 000000000..51c9df987
--- /dev/null
+++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/react';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import * as actions from '../../../store/actions';
+import { renderWithProvider } from '../../../../test/jest';
+import PrivacySettings from './privacy-settings';
+
+describe('Privacy Settings Onboarding View', () => {
+ const mockStore = {
+ metamask: {
+ provider: {
+ type: 'test',
+ },
+ },
+ };
+
+ const store = configureMockStore([thunk])(mockStore);
+ const setFeatureFlagStub = jest.fn();
+ const setUsePhishDetectStub = jest.fn();
+ const setUseTokenDetectionStub = jest.fn();
+
+ actions._setBackgroundConnection({
+ setFeatureFlag: setFeatureFlagStub,
+ setUsePhishDetect: setUsePhishDetectStub,
+ setUseTokenDetection: setUseTokenDetectionStub,
+ });
+
+ it('should update preferences', () => {
+ const { container, getByText } = renderWithProvider(
+ ,
+ store,
+ );
+ // All settings are initialized toggled to true
+ expect(setFeatureFlagStub).toHaveBeenCalledTimes(0);
+ expect(setUsePhishDetectStub).toHaveBeenCalledTimes(0);
+ expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(0);
+ const toggles = container.querySelectorAll('input[type=checkbox]');
+ const submitButton = getByText('Done');
+
+ // toggle to false
+ fireEvent.click(toggles[0]);
+ fireEvent.click(toggles[1]);
+ fireEvent.click(toggles[2]);
+ fireEvent.click(submitButton);
+
+ expect(setFeatureFlagStub).toHaveBeenCalledTimes(1);
+ expect(setUsePhishDetectStub).toHaveBeenCalledTimes(1);
+ expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(1);
+ expect(setFeatureFlagStub.mock.calls[0][1]).toStrictEqual(false);
+ expect(setUsePhishDetectStub.mock.calls[0][0]).toStrictEqual(false);
+ expect(setUseTokenDetectionStub.mock.calls[0][0]).toStrictEqual(false);
+
+ // toggle back to true
+ fireEvent.click(toggles[0]);
+ fireEvent.click(toggles[1]);
+ fireEvent.click(toggles[2]);
+ fireEvent.click(submitButton);
+ expect(setFeatureFlagStub).toHaveBeenCalledTimes(2);
+ expect(setUsePhishDetectStub).toHaveBeenCalledTimes(2);
+ expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(2);
+ expect(setFeatureFlagStub.mock.calls[1][1]).toStrictEqual(true);
+ expect(setUsePhishDetectStub.mock.calls[1][0]).toStrictEqual(true);
+ expect(setUseTokenDetectionStub.mock.calls[1][0]).toStrictEqual(true);
+ });
+});
diff --git a/ui/pages/onboarding-flow/privacy-settings/setting.js b/ui/pages/onboarding-flow/privacy-settings/setting.js
new file mode 100644
index 000000000..82ba1275c
--- /dev/null
+++ b/ui/pages/onboarding-flow/privacy-settings/setting.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Box from '../../../components/ui/box';
+import Typography from '../../../components/ui/typography';
+import ToggleButton from '../../../components/ui/toggle-button';
+import {
+ JUSTIFY_CONTENT,
+ TYPOGRAPHY,
+ FONT_WEIGHT,
+} from '../../../helpers/constants/design-system';
+
+export const Setting = ({ value, setValue, title, description }) => {
+ return (
+
+
+
+ {title}
+
+ {description}
+
+
+ setValue(!val)} />
+
+
+ );
+};
+
+Setting.propTypes = {
+ value: PropTypes.bool,
+ setValue: PropTypes.func,
+ title: PropTypes.string,
+ description: PropTypes.string,
+};