1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-26 12:29:06 +01:00

Privacy - Show default selected network after a custom network has been added during onboarding (#16789)

* Privacy - Allow users to set a custom RPC from the onboarding process (#16767)

* Privacy - Allow adding custom IPFS from onboarding (#16782)

* Privacy - Show default selected network after a custom network has been added during onboarding

* WIP: Show dropdown list of networks

* Add network switcher to the onboarding advanced privacy screen

* Fix duplicate imports

* Provide default for networks

* Update ui/helpers/utils/ipfs.js

Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com>

* Fix lint

* Remove unwanted changes

* Fix lint

Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com>
This commit is contained in:
David Walsh 2022-12-08 10:42:23 -06:00 committed by PeterYinusa
parent 5d15026f2f
commit 9dbc8b8ec0
15 changed files with 279 additions and 36 deletions

View File

@ -2586,6 +2586,27 @@
"on": {
"message": "On"
},
"onboardingAdvancedPrivacyIPFSDescription": {
"message": "The IPFS gateway makes it possible to access and view data hosted by third parties. You can add a custom IPFS gateway or continue using the default."
},
"onboardingAdvancedPrivacyIPFSInvalid": {
"message": "Please enter a valid URL"
},
"onboardingAdvancedPrivacyIPFSTitle": {
"message": "Add custom IPFS Gateway"
},
"onboardingAdvancedPrivacyIPFSValid": {
"message": "IPFS gateway URL is valid"
},
"onboardingAdvancedPrivacyNetworkButton": {
"message": "Add custom network"
},
"onboardingAdvancedPrivacyNetworkDescription": {
"message": "We use Infura as our remote procedure call (RPC) provider to offer the most reliable and private access to Ethereum data we can. You can choose your own RPC, but remember that any RPC will receive your IP address and Ethereum wallet to make transactions. Read our $1 to learn more about how Infura handles data."
},
"onboardingAdvancedPrivacyNetworkTitle": {
"message": "Choose your network"
},
"onboardingCreateWallet": {
"message": "Create a new wallet"
},
@ -2620,6 +2641,9 @@
"onboardingMetametricsInfuraTermsPolicyLink": {
"message": "here"
},
"onboardingMetametricsModalTitle": {
"message": "Add custom network"
},
"onboardingMetametricsNeverCollect": {
"message": "$1 collect information we dont need to provide the service (such as keys, addresses, transaction hashes, or balances)",
"description": "$1 represents `onboardingMetametricsNeverEmphasis`"

View File

@ -101,6 +101,9 @@ class NetworkDropdown extends Component {
showTestnetMessageInDropdown: PropTypes.bool.isRequired,
hideTestNetMessage: PropTypes.func.isRequired,
history: PropTypes.object,
dropdownStyles: PropTypes.object,
hideElementsForOnboarding: PropTypes.bool,
onAddClick: PropTypes.func,
};
handleClick(newProviderType) {
@ -122,16 +125,21 @@ class NetworkDropdown extends Component {
}
renderAddCustomButton() {
const { onAddClick } = this.props;
return (
<div className="network__add-network-button">
<Button
type="secondary"
onClick={() => {
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
? global.platform.openExtensionInBrowser(
ADD_POPULAR_CUSTOM_NETWORK,
)
: this.props.history.push(ADD_POPULAR_CUSTOM_NETWORK);
if (onAddClick) {
onAddClick();
} else {
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
? global.platform.openExtensionInBrowser(
ADD_POPULAR_CUSTOM_NETWORK,
)
: this.props.history.push(ADD_POPULAR_CUSTOM_NETWORK);
}
this.props.hideNetworkDropdown();
}}
>
@ -263,6 +271,7 @@ class NetworkDropdown extends Component {
render() {
const {
history,
hideElementsForOnboarding,
hideNetworkDropdown,
shouldShowTestNetworks,
showTestnetMessageInDropdown,
@ -294,20 +303,26 @@ class NetworkDropdown extends Component {
}}
containerClassName="network-droppo"
zIndex={55}
style={{
position: 'absolute',
top: '58px',
width: '309px',
zIndex: '55px',
}}
style={
this.props.dropdownStyles || {
position: 'absolute',
top: '58px',
width: '309px',
zIndex: '55',
}
}
innerStyle={{
padding: '16px 0',
}}
>
<div className="network-dropdown-header">
<div className="network-dropdown-title">{t('networks')}</div>
<div className="network-dropdown-divider" />
{showTestnetMessageInDropdown ? (
{hideElementsForOnboarding ? null : (
<div className="network-dropdown-title">{t('networks')}</div>
)}
{hideElementsForOnboarding ? null : (
<div className="network-dropdown-divider" />
)}
{showTestnetMessageInDropdown && !hideElementsForOnboarding ? (
<div className="network-dropdown-content">
{t('toggleTestNetworks', [
<a

View File

@ -8,6 +8,7 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app';
// Modal Components
import AddNetworkModal from '../../../pages/onboarding-flow/add-network-modal';
import AccountDetailsModal from './account-details-modal';
import ExportPrivateKeyModal from './export-private-key-modal';
import HideTokenConfirmationModal from './hide-token-confirmation-modal';
@ -76,6 +77,10 @@ const accountModalStyle = {
};
const MODALS = {
ONBOARDING_ADD_NETWORK: {
contents: <AddNetworkModal />,
...accountModalStyle,
},
NEW_ACCOUNT: {
contents: <NewAccountModal />,
mobileModalStyle: {

3
ui/helpers/utils/ipfs.js Normal file
View File

@ -0,0 +1,3 @@
export function addUrlProtocolPrefix(urlString) {
return urlString.match(/^https?:\/\//u) ? urlString : `https://${urlString}`;
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { hideModal } from '../../../store/actions';
import Typography from '../../../components/ui/typography/typography';
import Box from '../../../components/ui/box/box';
import {
TEXT_ALIGN,
TYPOGRAPHY,
FONT_WEIGHT,
} from '../../../helpers/constants/design-system';
import NetworksForm from '../../settings/networks-tab/networks-form/networks-form';
export default function AddNetworkModal() {
const dispatch = useDispatch();
const t = useI18nContext();
const closeCallback = () =>
dispatch(hideModal({ name: 'ONBOARDING_ADD_NETWORK' }));
return (
<>
<Box paddingTop={4}>
<Typography
variant={TYPOGRAPHY.H4}
align={TEXT_ALIGN.CENTER}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('onboardingMetametricsModalTitle')}
</Typography>
</Box>
<NetworksForm
addNewNetwork
networksToRender={[]}
cancelCallback={closeCallback}
submitCallback={closeCallback}
/>
</>
);
}

View File

@ -56,6 +56,15 @@
}
}
&__network {
position: relative;
.dropdown-menu-item {
font-size: 14px !important;
padding: 8px !important;
}
}
& button {
max-width: 50%;
padding: 15px;

View File

@ -1,22 +1,32 @@
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import Box from '../../../components/ui/box/box';
import Button from '../../../components/ui/button';
import Typography from '../../../components/ui/typography';
import {
COLORS,
FONT_WEIGHT,
TYPOGRAPHY,
} from '../../../helpers/constants/design-system';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { addUrlProtocolPrefix } from '../../../helpers/utils/ipfs';
import {
setCompletedOnboarding,
setFeatureFlag,
setUseMultiAccountBalanceChecker,
setUsePhishDetect,
setUseTokenDetection,
showModal,
setIpfsGateway,
showNetworkDropdown,
} from '../../../store/actions';
import { ONBOARDING_PIN_EXTENSION_ROUTE } from '../../../helpers/constants/routes';
import { Icon, TextField } from '../../../components/component-library';
import NetworkDropdown from '../../../components/app/dropdowns/network-dropdown';
import NetworkDisplay from '../../../components/app/network-display/network-display';
import { Setting } from './setting';
export default function PrivacySettings() {
@ -31,6 +41,12 @@ export default function PrivacySettings() {
isMultiAccountBalanceCheckerEnabled,
setMultiAccountBalanceCheckerEnabled,
] = useState(true);
const [ipfsURL, setIPFSURL] = useState('');
const [ipfsError, setIPFSError] = useState(null);
const networks = useSelector(
(state) => state.metamask.frequentRpcListDetail || [],
);
const handleSubmit = () => {
dispatch(
@ -42,9 +58,28 @@ export default function PrivacySettings() {
setUseMultiAccountBalanceChecker(isMultiAccountBalanceCheckerEnabled),
);
dispatch(setCompletedOnboarding());
if (ipfsURL && !ipfsError) {
const { host } = new URL(addUrlProtocolPrefix(ipfsURL));
dispatch(setIpfsGateway(host));
}
history.push(ONBOARDING_PIN_EXTENSION_ROUTE);
};
const handleIPFSChange = (url) => {
setIPFSURL(url);
try {
const { host } = new URL(addUrlProtocolPrefix(url));
if (!host || host === 'gateway.ipfs.io') {
throw new Error();
}
setIPFSError(null);
} catch (error) {
setIPFSError(t('onboardingAdvancedPrivacyIPFSInvalid'));
}
};
return (
<>
<div className="privacy-settings" data-testid="privacy-settings">
@ -118,6 +153,94 @@ export default function PrivacySettings() {
title={t('useMultiAccountBalanceChecker')}
description={t('useMultiAccountBalanceCheckerDescription')}
/>
<Setting
title={t('onboardingAdvancedPrivacyNetworkTitle')}
showToggle={false}
description={
<>
{t('onboardingAdvancedPrivacyNetworkDescription', [
<a
href="https://consensys.net/privacy-policy/"
key="link"
target="_blank"
rel="noopener noreferrer"
>
{t('privacyMsg')}
</a>,
])}
<Box paddingTop={2}>
{networks.length > 1 ? (
<div className="privacy-settings__network">
<>
<NetworkDisplay
onClick={() => dispatch(showNetworkDropdown())}
/>
<NetworkDropdown
hideElementsForOnboarding
dropdownStyles={{
position: 'absolute',
top: '40px',
left: '0',
width: '309px',
zIndex: '55',
}}
onAddClick={() => {
dispatch(
showModal({ name: 'ONBOARDING_ADD_NETWORK' }),
);
}}
/>
</>
</div>
) : null}
{networks.length === 1 ? (
<Button
type="secondary"
rounded
large
onClick={(e) => {
e.preventDefault();
dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' }));
}}
icon={<Icon name="add-outline" marginRight={2} />}
>
{t('onboardingAdvancedPrivacyNetworkButton')}
</Button>
) : null}
</Box>
</>
}
/>
<Setting
title={t('onboardingAdvancedPrivacyIPFSTitle')}
showToggle={false}
description={
<>
{t('onboardingAdvancedPrivacyIPFSDescription')}
<Box paddingTop={2}>
<TextField
style={{ width: '100%' }}
onChange={(e) => {
handleIPFSChange(e.target.value);
}}
/>
{ipfsURL ? (
<Typography
variant={TYPOGRAPHY.H7}
color={
ipfsError
? COLORS.ERROR_DEFAULT
: COLORS.SUCCESS_DEFAULT
}
>
{ipfsError || t('onboardingAdvancedPrivacyIPFSValid')}
</Typography>
) : null}
</Box>
</>
}
/>
</div>
<Button type="primary" rounded onClick={handleSubmit}>
{t('done')}

View File

@ -11,6 +11,7 @@ import PrivacySettings from './privacy-settings';
describe('Privacy Settings Onboarding View', () => {
const mockStore = {
metamask: {
frequentRpcListDetail: [],
provider: {
type: 'test',
},

View File

@ -9,7 +9,13 @@ import {
FONT_WEIGHT,
} from '../../../helpers/constants/design-system';
export const Setting = ({ value, setValue, title, description }) => {
export const Setting = ({
value,
setValue,
title,
description,
showToggle = true,
}) => {
return (
<Box justifyContent={JUSTIFY_CONTENT.CENTER} margin={3}>
<div className="privacy-settings__setting">
@ -18,9 +24,11 @@ export const Setting = ({ value, setValue, title, description }) => {
</Typography>
<Typography variant={TYPOGRAPHY.H6}>{description}</Typography>
</div>
<div className="privacy-settings__setting__toggle">
<ToggleButton value={value} onToggle={(val) => setValue(!val)} />
</div>
{showToggle ? (
<div className="privacy-settings__setting__toggle">
<ToggleButton value={value} onToggle={(val) => setValue(!val)} />
</div>
) : null}
</Box>
);
};
@ -30,4 +38,5 @@ Setting.propTypes = {
setValue: PropTypes.func,
title: PropTypes.string,
description: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
showToggle: PropTypes.bool,
};

View File

@ -115,6 +115,7 @@ export default class Routes extends Component {
portfolioTooltipIsBeingShown: PropTypes.bool,
forgottenPassword: PropTypes.bool,
isCurrentProviderCustom: PropTypes.bool,
completedOnboarding: PropTypes.bool,
};
static contextTypes = {
@ -379,6 +380,7 @@ export default class Routes extends Component {
shouldShowSeedPhraseReminder,
portfolioTooltipIsBeingShown,
isCurrentProviderCustom,
completedOnboarding,
} = this.props;
const loadMessage =
loadingMessage || isNetworkLoading
@ -391,6 +393,7 @@ export default class Routes extends Component {
!isTestNet &&
!isNetworkUsed &&
!isCurrentProviderCustom &&
completedOnboarding &&
allAccountsOnNetworkAreEmpty;
const windowType = getEnvironmentType();
@ -436,7 +439,7 @@ export default class Routes extends Component {
{process.env.ONBOARDING_V2 && this.showOnboardingHeader() && (
<OnboardingAppHeader />
)}
<NetworkDropdown />
{completedOnboarding ? <NetworkDropdown /> : null}
<AccountMenu />
<div className="main-container-wrapper">
{isLoading ? <Loading loadingMessage={loadMessage} /> : null}

View File

@ -29,6 +29,7 @@ function mapStateToProps(state) {
const { appState } = state;
const { alertOpen, alertMessage, isLoading, loadingMessage } = appState;
const { autoLockTimeLimit = 0 } = getPreferences(state);
const { completedOnboarding } = state.metamask;
return {
alertOpen,
@ -55,6 +56,7 @@ function mapStateToProps(state) {
portfolioTooltipIsBeingShown: getShowPortfolioTooltip(state),
forgottenPassword: state.metamask.forgottenPassword,
isCurrentProviderCustom: isCurrentProviderCustom(state),
completedOnboarding,
};
}

View File

@ -14,6 +14,7 @@ import {
getNumberOfSettingsInSection,
handleSettingsRefs,
} from '../../../helpers/utils/settings-search';
import { addUrlProtocolPrefix } from '../../../helpers/utils/ipfs';
import {
LEDGER_TRANSPORT_TYPES,
@ -842,10 +843,3 @@ export default class AdvancedTab extends PureComponent {
);
}
}
function addUrlProtocolPrefix(urlString) {
if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) {
return `https://${urlString}`;
}
return urlString;
}

View File

@ -5,7 +5,6 @@ import React, {
useRef,
useState,
} from 'react';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import validUrl from 'valid-url';
@ -29,10 +28,6 @@ import {
showModal,
setNewNetworkAdded,
} from '../../../../store/actions';
import {
DEFAULT_ROUTE,
NETWORKS_ROUTE,
} from '../../../../helpers/constants/routes';
import fetchWithCache from '../../../../../shared/lib/fetch-with-cache';
import { usePrevious } from '../../../../hooks/usePrevious';
import { MetaMetricsContext } from '../../../../contexts/metametrics';
@ -86,10 +81,11 @@ const NetworksForm = ({
isCurrentRpcTarget,
networksToRender,
selectedNetwork,
cancelCallback,
submitCallback,
}) => {
const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext);
const history = useHistory();
const dispatch = useDispatch();
const { label, labelKey, viewOnly, rpcPrefs } = selectedNetwork;
const selectedNetworkName = label || (labelKey && t(labelKey));
@ -544,7 +540,8 @@ const NetworksForm = ({
},
});
dispatch(setNewNetworkAdded(networkName));
history.push(DEFAULT_ROUTE);
submitCallback?.();
}
} catch (error) {
setIsSubmitting(false);
@ -555,7 +552,7 @@ const NetworksForm = ({
const onCancel = () => {
if (addNewNetwork) {
dispatch(setSelectedSettingsRpcUrl(''));
history.push(NETWORKS_ROUTE);
cancelCallback?.();
} else {
resetForm();
}
@ -709,6 +706,8 @@ NetworksForm.propTypes = {
isCurrentRpcTarget: PropTypes.bool,
networksToRender: PropTypes.array.isRequired,
selectedNetwork: PropTypes.object,
cancelCallback: PropTypes.func,
submitCallback: PropTypes.func,
};
NetworksForm.defaultProps = {

View File

@ -1,10 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import NetworksForm from '../networks-form';
import NetworksList from '../networks-list';
import { getProvider } from '../../../../selectors';
import {
DEFAULT_ROUTE,
NETWORKS_ROUTE,
} from '../../../../helpers/constants/routes';
const NetworksTabContent = ({
networkDefaultedToProvider,
networkIsSelected,
@ -13,6 +19,7 @@ const NetworksTabContent = ({
shouldRenderNetworkForm,
}) => {
const provider = useSelector(getProvider);
const history = useHistory();
return (
<>
@ -27,6 +34,8 @@ const NetworksTabContent = ({
isCurrentRpcTarget={provider.rpcUrl === selectedNetwork.rpcUrl}
networksToRender={networksToRender}
selectedNetwork={selectedNetwork}
submitCallback={() => history.push(DEFAULT_ROUTE)}
cancelCallback={() => history.push(NETWORKS_ROUTE)}
/>
) : null}
</>

View File

@ -7,6 +7,8 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
import {
ADD_POPULAR_CUSTOM_NETWORK,
NETWORKS_FORM_ROUTE,
DEFAULT_ROUTE,
NETWORKS_ROUTE,
} from '../../../helpers/constants/routes';
import { setSelectedSettingsRpcUrl } from '../../../store/actions';
import Button from '../../../components/ui/button';
@ -106,6 +108,8 @@ const NetworksTab = ({ addNewNetwork }) => {
<NetworksForm
networksToRender={networksToRender}
addNewNetwork={addNewNetwork}
submitCallback={() => history.push(DEFAULT_ROUTE)}
cancelCallback={() => history.push(NETWORKS_ROUTE)}
/>
) : (
<>