mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Updating Terms of Use, Adding popover and onboarding flow check (#18221)
* WIP commit * Moving copy out of messages.json, styling changes * handling scroll button click and disable logic * moving scrollButton up to popover component, adding logic for accepting terms of use in popover and onboarding flows * adding terms of use to e2e wallet creation/import * adjusting failing unit test * fixing QR code e2e * updating welcome test * setting app state in fixtures * Update app/scripts/controllers/app-state.js removing console log Co-authored-by: Nidhi Kumari <nidhi.kumari@consensys.net> * Update ui/components/app/terms-of-use-popup/terms-of-use-popup.stories.js adding args to ToU popup storybook Co-authored-by: Nidhi Kumari <nidhi.kumari@consensys.net> * Update ui/components/app/terms-of-use-popup/terms-of-use-popup.js Co-authored-by: Nidhi Kumari <nidhi.kumari@consensys.net> * updating DS components in terms of use * popover styling changes * adding metametrics tracking * editing scrollbutton behavior * adding unit test * code fencing --------- Co-authored-by: Nidhi Kumari <nidhi.kumari@consensys.net>
This commit is contained in:
parent
26db0aee46
commit
40e4a3653f
16
app/_locales/en/messages.json
generated
16
app/_locales/en/messages.json
generated
@ -109,6 +109,9 @@
|
||||
"about": {
|
||||
"message": "About"
|
||||
},
|
||||
"accept": {
|
||||
"message": "Accept"
|
||||
},
|
||||
"acceptTermsOfUse": {
|
||||
"message": "I have read and agree to the $1",
|
||||
"description": "$1 is the `terms` message"
|
||||
@ -305,6 +308,10 @@
|
||||
"advancedPriorityFeeToolTip": {
|
||||
"message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction."
|
||||
},
|
||||
"agreeTermsOfUse": {
|
||||
"message": "I agree to Metamask's $1",
|
||||
"description": "$1 is the `terms` link"
|
||||
},
|
||||
"airgapVault": {
|
||||
"message": "AirGap Vault"
|
||||
},
|
||||
@ -4310,6 +4317,15 @@
|
||||
"termsOfUse": {
|
||||
"message": "terms of use"
|
||||
},
|
||||
"termsOfUseAgreeText": {
|
||||
"message": " I agree to the Terms of Use, which apply to my use of MetaMask and all of its features"
|
||||
},
|
||||
"termsOfUseFooterText": {
|
||||
"message": "Please scroll to read all sections"
|
||||
},
|
||||
"termsOfUseTitle": {
|
||||
"message": "Our Terms of Use have updated"
|
||||
},
|
||||
"testNetworks": {
|
||||
"message": "Test networks"
|
||||
},
|
||||
|
@ -172,6 +172,17 @@ export default class AppStateController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the timestamp of the last time the user has acceoted the terms of use
|
||||
*
|
||||
* @param {number} lastAgreed - timestamp when user last accepted the terms of use
|
||||
*/
|
||||
setTermsOfUseLastAgreed(lastAgreed) {
|
||||
this.store.updateState({
|
||||
termsOfUseLastAgreed: lastAgreed,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the timestamp of the last time the user has seen the outdated browser warning
|
||||
*
|
||||
|
@ -2032,6 +2032,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
appStateController.setRecoveryPhraseReminderLastShown.bind(
|
||||
appStateController,
|
||||
),
|
||||
setTermsOfUseLastAgreed:
|
||||
appStateController.setTermsOfUseLastAgreed.bind(appStateController),
|
||||
setOutdatedBrowserWarningLastShown:
|
||||
appStateController.setOutdatedBrowserWarningLastShown.bind(
|
||||
appStateController,
|
||||
|
@ -510,6 +510,8 @@ export enum MetaMetricsEventName {
|
||||
SignatureFailed = 'Signature Failed',
|
||||
SignatureRejected = 'Signature Rejected',
|
||||
SignatureRequested = 'Signature Requested',
|
||||
TermsOfUseShown = 'Terms of Use Shown',
|
||||
TermsOfUseAccepted = 'Terms of Use Accepted',
|
||||
TokenImportButtonClicked = 'Import Token Button Clicked',
|
||||
TokenScreenOpened = 'Token Screen Opened',
|
||||
SupportLinkClicked = 'Support Link Clicked',
|
||||
|
1
shared/constants/terms.js
Normal file
1
shared/constants/terms.js
Normal file
@ -0,0 +1 @@
|
||||
export const TERMS_OF_USE_LAST_UPDATED = '2023-03-25';
|
@ -140,6 +140,7 @@ function defaultFixture() {
|
||||
browserEnvironment: {},
|
||||
nftsDropdownState: {},
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
termsOfUseLastAgreed: 86400000000000,
|
||||
defaultHomeActiveTabName: null,
|
||||
fullScreenGasPollTokens: [],
|
||||
notificationGasPollTokens: [],
|
||||
|
@ -222,6 +222,9 @@ const getWindowHandles = async (driver, handlesCount) => {
|
||||
};
|
||||
|
||||
const importSRPOnboardingFlow = async (driver, seedPhrase, password) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
@ -262,6 +265,9 @@ const completeImportSRPOnboardingFlowWordByWord = async (
|
||||
seedPhrase,
|
||||
password,
|
||||
) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
@ -293,6 +299,9 @@ const completeImportSRPOnboardingFlowWordByWord = async (
|
||||
};
|
||||
|
||||
const completeCreateNewWalletOnboardingFlow = async (driver, password) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
||||
@ -342,6 +351,9 @@ const completeCreateNewWalletOnboardingFlow = async (driver, password) => {
|
||||
};
|
||||
|
||||
const importWrongSRPOnboardingFlow = async (driver, seedPhrase) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
|
@ -84,6 +84,7 @@ describe('MetaMask', function () {
|
||||
|
||||
describe('Going through the first time flow', function () {
|
||||
it('clicks the "Create New Wallet" button on the welcome screen', async function () {
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
});
|
||||
|
||||
|
@ -29,6 +29,8 @@ describe('Incremental Security', function () {
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
@ -15,6 +15,8 @@ describe('MetaMask Responsive UI', function () {
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
@ -108,6 +108,9 @@ describe('MetaMask onboarding', function () {
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
// accept terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
@ -145,6 +148,7 @@ describe('MetaMask onboarding', function () {
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
||||
// metrics
|
||||
@ -208,6 +212,7 @@ describe('MetaMask onboarding', function () {
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
||||
// metrics
|
||||
|
@ -83,6 +83,7 @@
|
||||
@import 'transaction-status-label/index';
|
||||
@import 'wallet-overview/index';
|
||||
@import 'whats-new-popup/index';
|
||||
@import 'terms-of-use-popup/index';
|
||||
@import 'loading-network-screen/index';
|
||||
@import 'transaction-decoding/index';
|
||||
@import 'advanced-gas-fee-popover/index';
|
||||
|
1
ui/components/app/terms-of-use-popup/index.js
Normal file
1
ui/components/app/terms-of-use-popup/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './terms-of-use-popup';
|
30
ui/components/app/terms-of-use-popup/index.scss
Normal file
30
ui/components/app/terms-of-use-popup/index.scss
Normal file
@ -0,0 +1,30 @@
|
||||
.popover-wrap.terms-of-use__popover {
|
||||
.terms-of-use {
|
||||
&__terms-list {
|
||||
list-style: decimal none outside;
|
||||
}
|
||||
|
||||
&__footer-text {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
&__title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-footer {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@include screen-sm-min {
|
||||
max-height: 750px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
@include screen-sm-max {
|
||||
max-height: 568px;
|
||||
}
|
||||
}
|
1181
ui/components/app/terms-of-use-popup/terms-of-use-popup.js
Normal file
1181
ui/components/app/terms-of-use-popup/terms-of-use-popup.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import TermsOfUsePopup from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/TermsOfUsePopup',
|
||||
component: TermsOfUsePopup,
|
||||
argTypes: {
|
||||
onAccept: {
|
||||
action: 'onAccept',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <TermsOfUsePopup {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { renderWithProvider } from '../../../../test/jest';
|
||||
import configureStore from '../../../store/store';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import TermsOfUsePopup from './terms-of-use-popup';
|
||||
|
||||
const render = () => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
},
|
||||
});
|
||||
const onAccept = jest.fn();
|
||||
return renderWithProvider(<TermsOfUsePopup onAccept={onAccept} />, store);
|
||||
};
|
||||
|
||||
describe('TermsOfUsePopup', () => {
|
||||
beforeEach(() => {
|
||||
const mockIntersectionObserver = jest.fn();
|
||||
mockIntersectionObserver.mockReturnValue({
|
||||
observe: () => null,
|
||||
unobserve: () => null,
|
||||
disconnect: () => null,
|
||||
});
|
||||
window.IntersectionObserver = mockIntersectionObserver;
|
||||
});
|
||||
|
||||
it('renders TermsOfUse component and shows Terms of Use text', () => {
|
||||
render();
|
||||
expect(
|
||||
screen.getByText('Our Terms of Use have updated'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('scrolls down when handleScrollDownClick is called', () => {
|
||||
render();
|
||||
const mockScrollIntoView = jest.fn();
|
||||
window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView;
|
||||
const button = document.querySelector(
|
||||
"[data-testid='popover-scroll-button']",
|
||||
);
|
||||
fireEvent.click(button);
|
||||
expect(mockScrollIntoView).toHaveBeenCalledWith({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
});
|
@ -66,28 +66,6 @@
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__scroll-button {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--color-border-default);
|
||||
background: var(--color-background-alternative);
|
||||
color: var(--color-icon-default);
|
||||
z-index: 201;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-wrap.whats-new-popup__popover {
|
||||
|
@ -62,6 +62,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-scroll-button {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--color-primary-default);
|
||||
z-index: 201;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
|
@ -127,6 +127,16 @@ const Popover = ({
|
||||
className={classnames('popover-wrap', className)}
|
||||
ref={popoverRef}
|
||||
>
|
||||
{showArrow ? <div className="popover-arrow" /> : null}
|
||||
{showHeader && <Header />}
|
||||
{children ? (
|
||||
<Box
|
||||
className={classnames('popover-content', contentClassName)}
|
||||
{...{ ...defaultContentProps, ...contentProps }}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
) : null}
|
||||
{showScrollDown ? (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
@ -136,8 +146,9 @@ const Popover = ({
|
||||
backgroundColor={BackgroundColor.backgroundDefault}
|
||||
color={Color.iconDefault}
|
||||
onClick={onScrollDownButtonClick}
|
||||
className="whats-new-popup__scroll-button"
|
||||
data-testid="whats-new-popup-scroll-button"
|
||||
className="popover-scroll-button"
|
||||
style={{ bottom: footer ? '140px' : '12px' }}
|
||||
data-testid="popover-scroll-button"
|
||||
>
|
||||
<Icon
|
||||
name={ICON_NAMES.ARROW_DOWN}
|
||||
@ -147,16 +158,6 @@ const Popover = ({
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
{showArrow ? <div className="popover-arrow" /> : null}
|
||||
{showHeader && <Header />}
|
||||
{children ? (
|
||||
<Box
|
||||
className={classnames('popover-content', contentClassName)}
|
||||
{...{ ...defaultContentProps, ...contentProps }}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
) : null}
|
||||
{footer ? (
|
||||
<Box
|
||||
className={classnames('popover-footer', footerClassName)}
|
||||
|
@ -47,6 +47,7 @@ interface AppState {
|
||||
openMetaMaskTabs: Record<string, boolean>; // openMetamaskTabsIDs[tab.id]): true/false
|
||||
currentWindowTab: Record<string, any>; // tabs.tab https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/Tab
|
||||
showWhatsNewPopup: boolean;
|
||||
showTermsOfUsePopup: boolean;
|
||||
singleExceptions: {
|
||||
testKey: string | null;
|
||||
};
|
||||
@ -109,6 +110,7 @@ const initialState: AppState = {
|
||||
openMetaMaskTabs: {},
|
||||
currentWindowTab: {},
|
||||
showWhatsNewPopup: true,
|
||||
showTermsOfUsePopup: true,
|
||||
singleExceptions: {
|
||||
testKey: null,
|
||||
},
|
||||
|
@ -2,12 +2,14 @@ import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(main)
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { MetaMetricsContextProp } from '../../../shared/constants/metametrics';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import {
|
||||
MetaMetricsContextProp,
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
} from '../../../shared/constants/metametrics';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import AssetList from '../../components/app/asset-list';
|
||||
import NftsTab from '../../components/app/nfts-tab';
|
||||
import HomeNotification from '../../components/app/home-notification';
|
||||
@ -22,6 +24,7 @@ import ConnectedAccounts from '../connected-accounts';
|
||||
import { Tabs, Tab } from '../../components/ui/tabs';
|
||||
import { EthOverview } from '../../components/app/wallet-overview';
|
||||
import WhatsNewPopup from '../../components/app/whats-new-popup';
|
||||
import TermsOfUsePopup from '../../components/app/terms-of-use-popup';
|
||||
import RecoveryPhraseReminder from '../../components/app/recovery-phrase-reminder';
|
||||
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
|
||||
import {
|
||||
@ -111,6 +114,7 @@ export default class Home extends PureComponent {
|
||||
infuraBlocked: PropTypes.bool.isRequired,
|
||||
showWhatsNewPopup: PropTypes.bool.isRequired,
|
||||
hideWhatsNewPopup: PropTypes.func.isRequired,
|
||||
showTermsOfUsePopup: PropTypes.bool.isRequired,
|
||||
announcementsToShow: PropTypes.bool.isRequired,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
errorsToShow: PropTypes.object.isRequired,
|
||||
@ -120,6 +124,7 @@ export default class Home extends PureComponent {
|
||||
showRecoveryPhraseReminder: PropTypes.bool.isRequired,
|
||||
setRecoveryPhraseReminderHasBeenShown: PropTypes.func.isRequired,
|
||||
setRecoveryPhraseReminderLastShown: PropTypes.func.isRequired,
|
||||
setTermsOfUseLastAgreed: PropTypes.func.isRequired,
|
||||
showOutdatedBrowserWarning: PropTypes.bool.isRequired,
|
||||
setOutdatedBrowserWarningLastShown: PropTypes.func.isRequired,
|
||||
seedPhraseBackedUp: (props) => {
|
||||
@ -243,6 +248,18 @@ export default class Home extends PureComponent {
|
||||
setRecoveryPhraseReminderLastShown(new Date().getTime());
|
||||
};
|
||||
|
||||
onAcceptTermsOfUse = () => {
|
||||
const { setTermsOfUseLastAgreed } = this.props;
|
||||
setTermsOfUseLastAgreed(new Date().getTime());
|
||||
this.context.trackEvent({
|
||||
category: MetaMetricsEventCategory.Onboarding,
|
||||
event: MetaMetricsEventName.TermsOfUseAccepted,
|
||||
properties: {
|
||||
location: 'Terms Of Use Popover',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onOutdatedBrowserWarningClose = () => {
|
||||
const { setOutdatedBrowserWarningLastShown } = this.props;
|
||||
setOutdatedBrowserWarningLastShown(new Date().getTime());
|
||||
@ -600,6 +617,7 @@ export default class Home extends PureComponent {
|
||||
announcementsToShow,
|
||||
showWhatsNewPopup,
|
||||
hideWhatsNewPopup,
|
||||
showTermsOfUsePopup,
|
||||
seedPhraseBackedUp,
|
||||
showRecoveryPhraseReminder,
|
||||
firstTimeFlowType,
|
||||
@ -621,6 +639,10 @@ export default class Home extends PureComponent {
|
||||
showWhatsNewPopup &&
|
||||
!process.env.IN_TEST &&
|
||||
!newNetworkAddedConfigurationId;
|
||||
|
||||
const showTermsOfUse =
|
||||
completedOnboarding && !onboardedInThisUISession && showTermsOfUsePopup;
|
||||
|
||||
return (
|
||||
<div className="main-container">
|
||||
<Route path={CONNECTED_ROUTE} component={ConnectedSites} exact />
|
||||
@ -637,6 +659,9 @@ export default class Home extends PureComponent {
|
||||
onConfirm={this.onRecoveryPhraseReminderClose}
|
||||
/>
|
||||
) : null}
|
||||
{showTermsOfUse ? (
|
||||
<TermsOfUsePopup onAccept={this.onAcceptTermsOfUse} />
|
||||
) : null}
|
||||
{isPopup && !connectedStatusPopoverHasBeenShown
|
||||
? this.renderPopover()
|
||||
: null}
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
getShowWhatsNewPopup,
|
||||
getSortedAnnouncementsToShow,
|
||||
getShowRecoveryPhraseReminder,
|
||||
getShowTermsOfUse,
|
||||
getShowOutdatedBrowserWarning,
|
||||
getNewNetworkAdded,
|
||||
hasUnsignedQRHardwareTransaction,
|
||||
@ -35,6 +36,7 @@ import {
|
||||
setAlertEnabledness,
|
||||
setRecoveryPhraseReminderHasBeenShown,
|
||||
setRecoveryPhraseReminderLastShown,
|
||||
setTermsOfUseLastAgreed,
|
||||
setOutdatedBrowserWarningLastShown,
|
||||
setNewNetworkAdded,
|
||||
setNewNftAddedMessage,
|
||||
@ -136,6 +138,7 @@ const mapStateToProps = (state) => {
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
showWhatsNewPopup: getShowWhatsNewPopup(state),
|
||||
showRecoveryPhraseReminder: getShowRecoveryPhraseReminder(state),
|
||||
showTermsOfUsePopup: getShowTermsOfUse(state),
|
||||
showOutdatedBrowserWarning:
|
||||
getIsBrowserDeprecated() && getShowOutdatedBrowserWarning(state),
|
||||
seedPhraseBackedUp,
|
||||
@ -166,6 +169,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setRecoveryPhraseReminderHasBeenShown()),
|
||||
setRecoveryPhraseReminderLastShown: (lastShown) =>
|
||||
dispatch(setRecoveryPhraseReminderLastShown(lastShown)),
|
||||
setTermsOfUseLastAgreed: (lastAgreed) => {
|
||||
dispatch(setTermsOfUseLastAgreed(lastAgreed));
|
||||
},
|
||||
setOutdatedBrowserWarningLastShown: (lastShown) => {
|
||||
dispatch(setOutdatedBrowserWarningLastShown(lastShown));
|
||||
},
|
||||
|
@ -51,4 +51,9 @@
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&__terms-checkbox {
|
||||
margin: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,13 @@ import { Carousel } from 'react-responsive-carousel';
|
||||
import Mascot from '../../../components/ui/mascot';
|
||||
import Button from '../../../components/ui/button';
|
||||
import { Text } from '../../../components/component-library';
|
||||
import CheckBox from '../../../components/ui/check-box';
|
||||
import Box from '../../../components/ui/box';
|
||||
import {
|
||||
FONT_WEIGHT,
|
||||
TEXT_ALIGN,
|
||||
TextVariant,
|
||||
AlignItems,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||
@ -17,7 +20,10 @@ import {
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import { setFirstTimeFlowType } from '../../../store/actions';
|
||||
import {
|
||||
setFirstTimeFlowType,
|
||||
setTermsOfUseLastAgreed,
|
||||
} from '../../../store/actions';
|
||||
import {
|
||||
ONBOARDING_METAMETRICS,
|
||||
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
|
||||
@ -33,6 +39,7 @@ export default function OnboardingWelcome() {
|
||||
const [eventEmitter] = useState(new EventEmitter());
|
||||
const currentKeyring = useSelector(getCurrentKeyring);
|
||||
const firstTimeFlowType = useSelector(getFirstTimeFlowType);
|
||||
const [termsChecked, setTermsChecked] = useState(false);
|
||||
|
||||
// Don't allow users to come back to this screen after they
|
||||
// have already imported or created a wallet
|
||||
@ -56,8 +63,23 @@ export default function OnboardingWelcome() {
|
||||
account_type: 'metamask',
|
||||
},
|
||||
});
|
||||
dispatch(setTermsOfUseLastAgreed(new Date().getTime()));
|
||||
history.push(ONBOARDING_METAMETRICS);
|
||||
};
|
||||
const toggleTermsCheck = () => {
|
||||
setTermsChecked((currentTermsChecked) => !currentTermsChecked);
|
||||
};
|
||||
const termsOfUse = t('agreeTermsOfUse', [
|
||||
<a
|
||||
className="create-new-vault__terms-link"
|
||||
key="create-new-vault__link-text"
|
||||
href="https://metamask.io/terms.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('terms')}
|
||||
</a>,
|
||||
]);
|
||||
|
||||
const onImportClick = () => {
|
||||
dispatch(setFirstTimeFlowType('import'));
|
||||
@ -68,6 +90,7 @@ export default function OnboardingWelcome() {
|
||||
account_type: 'imported',
|
||||
},
|
||||
});
|
||||
dispatch(setTermsOfUseLastAgreed(new Date().getTime()));
|
||||
history.push(ONBOARDING_METAMETRICS);
|
||||
};
|
||||
|
||||
@ -147,11 +170,35 @@ export default function OnboardingWelcome() {
|
||||
</div>
|
||||
</Carousel>
|
||||
<ul className="onboarding-welcome__buttons">
|
||||
<li>
|
||||
<Box
|
||||
alignItems={AlignItems.center}
|
||||
className="onboarding__terms-of-use"
|
||||
>
|
||||
<CheckBox
|
||||
id="onboarding__terms-checkbox"
|
||||
className="onboarding__terms-checkbox"
|
||||
dataTestId="onboarding-terms-checkbox"
|
||||
checked={termsChecked}
|
||||
onClick={toggleTermsCheck}
|
||||
/>
|
||||
<label
|
||||
className="onboarding__terms-label"
|
||||
htmlFor="onboarding__terms-checkbox"
|
||||
>
|
||||
<Text variant={TextVariant.bodyMd} marginLeft={2} as="span">
|
||||
{termsOfUse}
|
||||
</Text>
|
||||
</label>
|
||||
</Box>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Button
|
||||
data-testid="onboarding-create-wallet"
|
||||
type="primary"
|
||||
onClick={onCreateClick}
|
||||
disabled={!termsChecked}
|
||||
>
|
||||
{t('onboardingCreateWallet')}
|
||||
</Button>
|
||||
@ -161,6 +208,7 @@ export default function OnboardingWelcome() {
|
||||
data-testid="onboarding-import-wallet"
|
||||
type="secondary"
|
||||
onClick={onImportClick}
|
||||
disabled={!termsChecked}
|
||||
>
|
||||
{t('onboardingImportWallet')}
|
||||
</Button>
|
||||
|
@ -4,7 +4,10 @@ import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import initializedMockState from '../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import { setFirstTimeFlowType } from '../../../store/actions';
|
||||
import {
|
||||
setFirstTimeFlowType,
|
||||
setTermsOfUseLastAgreed,
|
||||
} from '../../../store/actions';
|
||||
import {
|
||||
ONBOARDING_METAMETRICS,
|
||||
ONBOARDING_SECURE_YOUR_WALLET_ROUTE,
|
||||
@ -21,6 +24,11 @@ jest.mock('../../../store/actions.ts', () => ({
|
||||
return type;
|
||||
}),
|
||||
),
|
||||
setTermsOfUseLastAgreed: jest.fn().mockReturnValue(
|
||||
jest.fn((type) => {
|
||||
return type;
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
@ -81,19 +89,24 @@ describe('Onboarding Welcome Component', () => {
|
||||
|
||||
it('should set first time flow to create and route to metametrics', () => {
|
||||
renderWithProvider(<OnboardingWelcome />, mockStore);
|
||||
|
||||
const termsCheckbox = screen.getByTestId('onboarding-terms-checkbox');
|
||||
fireEvent.click(termsCheckbox);
|
||||
const createWallet = screen.getByTestId('onboarding-create-wallet');
|
||||
fireEvent.click(createWallet);
|
||||
|
||||
expect(setTermsOfUseLastAgreed).toHaveBeenCalled();
|
||||
expect(setFirstTimeFlowType).toHaveBeenCalledWith('create');
|
||||
});
|
||||
|
||||
it('should set first time flow to import and route to metametrics', () => {
|
||||
renderWithProvider(<OnboardingWelcome />, mockStore);
|
||||
const termsCheckbox = screen.getByTestId('onboarding-terms-checkbox');
|
||||
fireEvent.click(termsCheckbox);
|
||||
|
||||
const createWallet = screen.getByTestId('onboarding-import-wallet');
|
||||
fireEvent.click(createWallet);
|
||||
|
||||
expect(setTermsOfUseLastAgreed).toHaveBeenCalled();
|
||||
expect(setFirstTimeFlowType).toHaveBeenCalledWith('import');
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith(ONBOARDING_METAMETRICS);
|
||||
});
|
||||
|
@ -57,6 +57,7 @@ import {
|
||||
import { TEMPLATED_CONFIRMATION_MESSAGE_TYPES } from '../pages/confirmation/templates';
|
||||
import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens';
|
||||
import { DAY } from '../../shared/constants/time';
|
||||
import { TERMS_OF_USE_LAST_UPDATED } from '../../shared/constants/terms';
|
||||
import {
|
||||
getNativeCurrency,
|
||||
getConversionRate,
|
||||
@ -995,6 +996,18 @@ export function getShowRecoveryPhraseReminder(state) {
|
||||
return currentTime - recoveryPhraseReminderLastShown >= frequency;
|
||||
}
|
||||
|
||||
export function getShowTermsOfUse(state) {
|
||||
const { termsOfUseLastAgreed } = state.metamask;
|
||||
|
||||
if (!termsOfUseLastAgreed) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
new Date(termsOfUseLastAgreed).getTime() <
|
||||
new Date(TERMS_OF_USE_LAST_UPDATED).getTime()
|
||||
);
|
||||
}
|
||||
|
||||
export function getShowOutdatedBrowserWarning(state) {
|
||||
const { outdatedBrowserWarningLastShown } = state.metamask;
|
||||
if (!outdatedBrowserWarningLastShown) {
|
||||
|
@ -3980,6 +3980,12 @@ export function setRecoveryPhraseReminderLastShown(
|
||||
};
|
||||
}
|
||||
|
||||
export function setTermsOfUseLastAgreed(lastAgreed: number) {
|
||||
return async () => {
|
||||
await submitRequestToBackground('setTermsOfUseLastAgreed', [lastAgreed]);
|
||||
};
|
||||
}
|
||||
|
||||
export function setOutdatedBrowserWarningLastShown(lastShown: number) {
|
||||
return async () => {
|
||||
await submitRequestToBackground('setOutdatedBrowserWarningLastShown', [
|
||||
|
Loading…
Reference in New Issue
Block a user