Onboarding V2 Privacy settings view (#12253)

* add Set Advanced Privacy Settings onboarding view
feature/default_network_editable
Alex Donesky 3 years ago committed by GitHub
parent 78b7f32d97
commit 3d36fbd8ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      app/_locales/en/messages.json
  2. 1
      ui/pages/onboarding-flow/index.scss
  3. 8
      ui/pages/onboarding-flow/onboarding-flow.js
  4. 50
      ui/pages/onboarding-flow/privacy-settings/index.scss
  5. 113
      ui/pages/onboarding-flow/privacy-settings/privacy-settings.js
  6. 15
      ui/pages/onboarding-flow/privacy-settings/privacy-settings.stories.js
  7. 66
      ui/pages/onboarding-flow/privacy-settings/privacy-settings.test.js
  8. 33
      ui/pages/onboarding-flow/privacy-settings/setting.js

@ -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"
},

@ -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%;

@ -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() {
<Unlock {...routeProps} onSubmit={handleUnlock} />
)}
/>
<Route
path={ONBOARDING_PRIVACY_SETTINGS_ROUTE}
component={PrivacySettings}
/>
<Route exact path="*" component={OnboardingFlowSwitch} />
</Switch>
</div>

@ -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;
}
}

@ -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 (
<>
<div className="privacy-settings">
<div className="privacy-settings__header">
<Typography variant={TYPOGRAPHY.H2} fontWeight={FONT_WEIGHT.BOLD}>
{t('setAdvancedPrivacySettings')}
</Typography>
<Typography variant={TYPOGRAPHY.H4}>
{t('setAdvancedPrivacySettingsDetails')}
</Typography>
</div>
<div
className="privacy-settings__settings"
data-testid="privacy-settings-settings"
>
<Setting
value={showIncomingTransactions}
setValue={setShowIncomingTransactions}
title={t('showIncomingTransactions')}
description={t('onboardingShowIncomingTransactionsDescription', [
<a
key="etherscan"
href="https://etherscan.io/"
target="_blank"
rel="noreferrer"
>
{t('etherscan')}
</a>,
<a
href="https://cn.etherscan.com/privacyPolicy"
target="_blank"
rel="noreferrer"
key="privacyMsg"
>
{t('privacyMsg')}
</a>,
])}
/>
<Setting
value={usePhishingDetection}
setValue={setUsePhishingDetection}
title={t('usePhishingDetection')}
description={t('onboardingUsePhishingDetectionDescription', [
<a
href="https://www.jsdelivr.com"
target="_blank"
rel="noreferrer"
key="jsDeliver"
>
{t('jsDeliver')}
</a>,
<a
href="https://www.jsdelivr.com/terms/privacy-policy-jsdelivr-com"
target="_blank"
rel="noreferrer"
key="privacyMsg"
>
{t('privacyMsg')}
</a>,
])}
/>
<Setting
value={turnOnTokenDetection}
setValue={setTurnOnTokenDetection}
title={t('turnOnTokenDetection')}
description={t('useTokenDetectionDescription')}
/>
</div>
<Button type="primary" rounded onClick={handleSubmit}>
{t('done')}
</Button>
</div>
</>
);
}

@ -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 (
<div style={{ maxHeight: '2000px' }}>
<PrivacySettings />
</div>
);
};

@ -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(
<PrivacySettings />,
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);
});
});

@ -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 (
<Box justifyContent={JUSTIFY_CONTENT.CENTER} margin={3}>
<div className="privacy-settings__setting">
<Typography variant={TYPOGRAPHY.H5} fontWeight={FONT_WEIGHT.BOLD}>
{title}
</Typography>
<Typography variant={TYPOGRAPHY.H6}>{description}</Typography>
</div>
<div className="privacy-settings__setting__toggle">
<ToggleButton value={value} onToggle={(val) => setValue(!val)} />
</div>
</Box>
);
};
Setting.propTypes = {
value: PropTypes.bool,
setValue: PropTypes.func,
title: PropTypes.string,
description: PropTypes.string,
};
Loading…
Cancel
Save