From aea176329e3e01c975e97c6a4ea7dbd692f1788f Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 3 Mar 2021 12:13:35 -0330 Subject: [PATCH 01/48] Add 'What's New' notification popup --- app/_locales/en/messages.json | 4 + app/images/swaps-logos-small.svg | 101 +++++++++++++ app/scripts/controllers/app-state.js | 9 -- app/scripts/metamask-controller.js | 20 ++- app/scripts/platforms/extension.js | 18 ++- shared/notifications/index.js | 94 ++++++++++++ ui/app/components/app/app-components.scss | 1 + .../components/app/whats-new-popup/index.js | 1 + .../components/app/whats-new-popup/index.scss | 47 ++++++ .../app/whats-new-popup/whats-new-popup.js | 137 ++++++++++++++++++ ui/app/components/ui/popover/index.scss | 4 + .../ui/popover/popover.component.js | 14 +- ui/app/ducks/app/app.js | 13 ++ .../pages/confirm-approve/confirm-approve.js | 1 + ui/app/pages/home/home.component.js | 20 ++- ui/app/pages/home/home.container.js | 19 +-- ui/app/pages/routes/routes.component.js | 4 +- ui/app/pages/swaps/build-quote/build-quote.js | 21 ++- ui/app/pages/swaps/index.scss | 1 - ui/app/pages/swaps/intro-popup/index.js | 1 - ui/app/pages/swaps/intro-popup/index.scss | 71 --------- ui/app/pages/swaps/intro-popup/intro-popup.js | 108 -------------- ui/app/selectors/selectors.js | 4 + ui/app/store/actionConstants.js | 3 + ui/app/store/actions.js | 16 +- 25 files changed, 498 insertions(+), 234 deletions(-) create mode 100644 app/images/swaps-logos-small.svg create mode 100644 shared/notifications/index.js create mode 100644 ui/app/components/app/whats-new-popup/index.js create mode 100644 ui/app/components/app/whats-new-popup/index.scss create mode 100644 ui/app/components/app/whats-new-popup/whats-new-popup.js delete mode 100644 ui/app/pages/swaps/intro-popup/index.js delete mode 100644 ui/app/pages/swaps/intro-popup/index.scss delete mode 100644 ui/app/pages/swaps/intro-popup/intro-popup.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 450190d54bc3..168b3104b75c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2310,6 +2310,10 @@ "welcomeBack": { "message": "Welcome Back!" }, + "whatsNew": { + "message": "What's new", + "description": "This is the title of a popup that gives users notifications about new features and updates to MetaMask." + }, "whatsThis": { "message": "What's this?" }, diff --git a/app/images/swaps-logos-small.svg b/app/images/swaps-logos-small.svg new file mode 100644 index 000000000000..a484d7db0134 --- /dev/null +++ b/app/images/swaps-logos-small.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 73173bf685db..b1c1df7cd927 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -112,15 +112,6 @@ export default class AppStateController extends EventEmitter { }); } - /** - * Record that the user has seen the swap screen welcome message - */ - setSwapsWelcomeMessageHasBeenShown() { - this.store.updateState({ - swapsWelcomeMessageHasBeenShown: true, - }); - } - /** * Sets the last active time to the current time * @returns {void} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c5c181400299..61291e6c460a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -22,9 +22,12 @@ import { ApprovalController, CurrencyRateController, PhishingController, + NotificationController, } from '@metamask/controllers'; import { TRANSACTION_STATUSES } from '../../shared/constants/transaction'; import { MAINNET_CHAIN_ID } from '../../shared/constants/network'; +import { UI_NOTIFICATIONS } from '../../shared/notifications'; + import ComposableObservableStore from './lib/ComposableObservableStore'; import AccountTracker from './lib/account-tracker'; import createLoggerMiddleware from './lib/createLoggerMiddleware'; @@ -160,6 +163,11 @@ export default class MetamaskController extends EventEmitter { this.phishingController = new PhishingController(); + this.notificationController = new NotificationController( + { allNotifications: UI_NOTIFICATIONS }, + initState.NotificationController, + ); + // now we can initialize the RPC provider, which other controllers require this.initializeProvider(); this.provider = this.networkController.getProviderAndBlockTracker().provider; @@ -426,6 +434,7 @@ export default class MetamaskController extends EventEmitter { PermissionsController: this.permissionsController.permissions, PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, + NotificationController: this.notificationController, }); this.memStore = new ComposableObservableStore(null, { @@ -454,6 +463,7 @@ export default class MetamaskController extends EventEmitter { SwapsController: this.swapsController.store, EnsController: this.ensController.store, ApprovalController: this.approvalController, + NotificationController: this.notificationController, }); this.memStore.subscribe(this.sendUpdate.bind(this)); @@ -736,10 +746,6 @@ export default class MetamaskController extends EventEmitter { this.appStateController.setConnectedStatusPopoverHasBeenShown, this.appStateController, ), - setSwapsWelcomeMessageHasBeenShown: nodeify( - this.appStateController.setSwapsWelcomeMessageHasBeenShown, - this.appStateController, - ), // EnsController tryReverseResolveAddress: nodeify( @@ -946,6 +952,12 @@ export default class MetamaskController extends EventEmitter { approvalController.reject, approvalController, ), + + // Notifications + updateViewedNotifications: nodeify( + this.notificationController.updateViewed, + this.notificationController, + ), }; } diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 0eaeefaa8188..be8ff5798f4c 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -82,18 +82,26 @@ export default class ExtensionPlatform { return extension.runtime.getManifest().version; } - openExtensionInBrowser(route = null, queryString = null) { + openExtensionInBrowser( + route = null, + queryString = null, + keepCurrentTabOpen = false, + ) { let extensionURL = extension.runtime.getURL('home.html'); + if (route) { + extensionURL += `#${route}`; + } + if (queryString) { extensionURL += `?${queryString}`; } - if (route) { - extensionURL += `#${route}`; - } this.openTab({ url: extensionURL }); - if (getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND) { + if ( + getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND && + !keepCurrentTabOpen + ) { window.close(); } } diff --git a/shared/notifications/index.js b/shared/notifications/index.js new file mode 100644 index 000000000000..f0bd29e605a5 --- /dev/null +++ b/shared/notifications/index.js @@ -0,0 +1,94 @@ +import { getSwapsFeatureLiveness } from '../../ui/app/ducks/swaps/swaps'; +import { getSwapsEthToken, getIsMainnet } from '../../ui/app/selectors'; +import { BUILD_QUOTE_ROUTE } from '../../ui/app/helpers/constants/routes'; + +export const UI_NOTIFICATIONS = { + 1: { + id: 1, + title: 'Now Swap tokens directly in your wallet!', + description: + 'MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.', + date: '02/22/2020', + image: 'images/swaps-logos-small.svg', + actionText: 'Start swapping', + }, + 2: { + id: 2, + title: 'MetaMask Mobile is here!', + description: + 'Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.', + date: '02/22/2020', + actionText: 'Get the mobile app', + }, + 3: { + id: 3, + title: 'Help improve MetaMask', + description: 'Please share your experience in this 5 minute survey.', + date: '02/22/2020', + actionText: 'Start survey', + }, +}; + +function getNotificationFilters(state) { + const currentNetworkIsMainnet = getIsMainnet(state); + const swapsIsEnabled = getSwapsFeatureLiveness(state); + + return { + 1: !currentNetworkIsMainnet || !swapsIsEnabled, + }; +} + +export function getSortedNotificationsToShow(state) { + const notifications = Object.values(state.metamask.notifications) || []; + const notificationFilters = getNotificationFilters(state); + const notificationsToShow = notifications.filter( + (notification) => + !notification.isShown && !notificationFilters[notification.id], + ); + const notificationsSortedByDate = notificationsToShow.sort( + (a, b) => new Date(b.date) - new Date(a.date), + ); + return notificationsSortedByDate; +} + +export function notifcationActionFunctions( + // eslint-disable-next-line no-unused-vars + dispatch, + // eslint-disable-next-line no-unused-vars + state, + // eslint-disable-next-line no-unused-vars + history, + // eslint-disable-next-line no-unused-vars + metricsEvent, +) { + const swapsEthToken = getSwapsEthToken(state); + + const actionFunctions = { + 1: () => { + metricsEvent({ + event: 'Swaps Opened', + properties: { source: 'Main View', active_currency: 'ETH' }, + category: 'swaps', + }); + global.platform.openExtensionInBrowser( + BUILD_QUOTE_ROUTE, + `fromAddress=${swapsEthToken.address}`, + ); + }, + 2: () => { + global.platform.openTab({ + url: 'https://metamask.io/download.html', + }); + }, + 3: () => { + global.platform.openTab({ + url: + 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', + }); + }, + }; + + return (id) => { + return actionFunctions[id]; + }; +} diff --git a/ui/app/components/app/app-components.scss b/ui/app/components/app/app-components.scss index 07eac6ce137d..4f407569d69c 100644 --- a/ui/app/components/app/app-components.scss +++ b/ui/app/components/app/app-components.scss @@ -36,3 +36,4 @@ @import 'transaction-list/index'; @import 'transaction-status/index'; @import 'wallet-overview/index'; +@import 'whats-new-popup/index'; diff --git a/ui/app/components/app/whats-new-popup/index.js b/ui/app/components/app/whats-new-popup/index.js new file mode 100644 index 000000000000..2f20e4f4f748 --- /dev/null +++ b/ui/app/components/app/whats-new-popup/index.js @@ -0,0 +1 @@ +export { default } from './whats-new-popup'; diff --git a/ui/app/components/app/whats-new-popup/index.scss b/ui/app/components/app/whats-new-popup/index.scss new file mode 100644 index 000000000000..2a6623267a00 --- /dev/null +++ b/ui/app/components/app/whats-new-popup/index.scss @@ -0,0 +1,47 @@ +.whats-new-popup { + &__notifications { + display: flex; + flex-direction: column; + align-items: center; + } + + &__notification, + &__first-notification { + display: flex; + flex-direction: column; + align-items: left; + margin: 0 24px 24px 24px; + border-bottom: 1px solid $Grey-100; + } + + &__notification-image { + margin-bottom: 16px; + } + + &__notification-description { + margin-bottom: 16px; + } + + &__button { + margin-right: auto; + } + + &__button, + &__link { + margin-bottom: 24px; + } + + &__link { + @include H6; + + color: $Blue-500; + cursor: pointer; + } + + &__notification-title { + @include H4; + + font-weight: bold; + margin-bottom: 8px; + } +} diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js new file mode 100644 index 000000000000..3fcd96e5875e --- /dev/null +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -0,0 +1,137 @@ +import React, { useContext, useMemo, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { I18nContext } from '../../../contexts/i18n'; +import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; +import { MetaMetricsContext } from '../../../contexts/metametrics.new'; +import Button from '../../ui/button'; +import Popover from '../../ui/popover'; +import { updateViewedNotifications } from '../../../store/actions'; +import { + UI_NOTIFICATIONS, + notifcationActionFunctions, + getSortedNotificationsToShow, +} from '../../../../../shared/notifications'; + +export default function WhatsNewPopup({ onClose }) { + const dispatch = useDispatch(useDispatch); + const history = useHistory(); + const metricsEvent = useContext(MetaMetricsContext); + const t = useContext(I18nContext); + const state = useSelector((_state) => _state); + + const getNotifcationActionFunctionsById = notifcationActionFunctions( + dispatch, + state, + history, + metricsEvent, + ); + + const notifications = useSelector(getSortedNotificationsToShow); + + const contentRef = useRef(); + + const memoizedNotifications = useEqualityCheck(notifications); + const idRefMap = useMemo( + () => + memoizedNotifications.reduce( + (_idRefMap, notification) => ({ + ..._idRefMap, + [notification.id]: React.createRef(), + }), + {}, + ), + [memoizedNotifications], + ); + + return ( +
+ { + const { + bottom: containerBottom, + } = contentRef.current.getBoundingClientRect(); + + const currentlySeenNotifications = {}; + Object.keys(idRefMap).forEach((notificationId) => { + const { bottom: descriptionBottom } = idRefMap[ + notificationId + ].current.getBoundingClientRect(); + + if (descriptionBottom < containerBottom) { + currentlySeenNotifications[notificationId] = true; + } + }); + + updateViewedNotifications(currentlySeenNotifications); + + onClose(); + }} + contentRef={contentRef} + mediumHeight + > +
+ {notifications.map((notification, index) => { + const isFirstNotification = index === 0; + return ( +
+ {isFirstNotification && notification.image && ( + + )} +
+ {notification.title} +
+
+ {notification.description} +
+ {isFirstNotification && + UI_NOTIFICATIONS[notification.id].actionText && ( + + )} + {!isFirstNotification && + UI_NOTIFICATIONS[notification.id].actionText && ( +
+ getNotifcationActionFunctionsById(notification.id)() + } + > + {UI_NOTIFICATIONS[notification.id].actionText} +
+ )} +
+ ); + })} +
+
+
+ ); +} + +WhatsNewPopup.propTypes = { + onClose: PropTypes.func.isRequired, +}; diff --git a/ui/app/components/ui/popover/index.scss b/ui/app/components/ui/popover/index.scss index d2869b484f0f..2ab7743e585b 100644 --- a/ui/app/components/ui/popover/index.scss +++ b/ui/app/components/ui/popover/index.scss @@ -18,6 +18,10 @@ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.25); border-radius: 10px; background: white; + + &--medium-height { + max-height: 600px; + } } &-header { diff --git a/ui/app/components/ui/popover/popover.component.js b/ui/app/components/ui/popover/popover.component.js index 0f93c71838ca..b8e591daa531 100644 --- a/ui/app/components/ui/popover/popover.component.js +++ b/ui/app/components/ui/popover/popover.component.js @@ -13,9 +13,11 @@ const Popover = ({ onBack, onClose, className, + mediumHeight, contentClassName, showArrow, CustomBackground, + contentRef, }) => { const t = useI18nContext(); return ( @@ -25,7 +27,11 @@ const Popover = ({ ) : (
)} -
+
{showArrow ?
: null}
@@ -50,7 +56,10 @@ const Popover = ({ ) : null}
{children ? ( -
+
{children}
) : null} @@ -76,6 +85,7 @@ Popover.propTypes = { contentClassName: PropTypes.string, className: PropTypes.string, showArrow: PropTypes.bool, + mediumHeight: PropTypes.bool, }; export default class PopoverPortal extends PureComponent { diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index c927e70c9869..ae42ad967d36 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -50,6 +50,7 @@ export default function reduceApp(state = {}, action) { requestAccountTabs: {}, openMetaMaskTabs: {}, currentWindowTab: {}, + showWhatsNewPopup: true, ...state, }; @@ -352,6 +353,12 @@ export default function reduceApp(state = {}, action) { currentWindowTab: action.value, }; + case actionConstants.HIDE_SHOW_WHATS_NEW_POPUP: + return { + ...appState, + showWhatsNewPopup: false, + }; + default: return appState; } @@ -364,3 +371,9 @@ export function setThreeBoxLastUpdated(lastUpdated) { value: lastUpdated, }; } + +export function hideShowWhatsNewPopup() { + return { + type: actionConstants.HIDE_SHOW_WHATS_NEW_POPUP, + }; +} diff --git a/ui/app/pages/confirm-approve/confirm-approve.js b/ui/app/pages/confirm-approve/confirm-approve.js index 9eb51bd2ad46..8624af9c4df8 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.js +++ b/ui/app/pages/confirm-approve/confirm-approve.js @@ -124,6 +124,7 @@ export default function ConfirmApprove() { toAddress={toAddress} identiconAddress={tokenAddress} showAccountInHeader + useNonceField title={tokensText} contentComponent={
- {!swapsWelcomeMessageHasBeenShown && swapsEnabled && isMainnet ? ( - + {notificationsToShow && showWhatsNewPopup ? ( + ) : null} {isPopup && !connectedStatusPopoverHasBeenShown ? this.renderPopover() diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index 721c7a15ea77..75b4f683d5a9 100644 --- a/ui/app/pages/home/home.container.js +++ b/ui/app/pages/home/home.container.js @@ -12,6 +12,7 @@ import { getWeb3ShimUsageStateForOrigin, unconfirmedTransactionsCountSelector, getInfuraBlocked, + getShowWhatsNewPopup, } from '../../selectors'; import { @@ -21,16 +22,15 @@ import { setShowRestorePromptToFalse, setConnectedStatusPopoverHasBeenShown, setDefaultHomeActiveTabName, - setSwapsWelcomeMessageHasBeenShown, setWeb3ShimUsageAlertDismissed, setAlertEnabledness, } from '../../store/actions'; -import { setThreeBoxLastUpdated } from '../../ducks/app/app'; -import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask'; import { - getSwapsWelcomeMessageSeenStatus, - getSwapsFeatureLiveness, -} from '../../ducks/swaps/swaps'; + setThreeBoxLastUpdated, + hideShowWhatsNewPopup, +} from '../../ducks/app/app'; +import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask'; +import { getSwapsFeatureLiveness } from '../../ducks/swaps/swaps'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_NOTIFICATION, @@ -40,6 +40,7 @@ import { ALERT_TYPES, WEB3_SHIM_USAGE_ALERT_STATES, } from '../../../../shared/constants/alerts'; +import { getSortedNotificationsToShow } from '../../../../shared/notifications'; import Home from './home.component'; const mapStateToProps = (state) => { @@ -97,7 +98,6 @@ const mapStateToProps = (state) => { totalUnapprovedCount, connectedStatusPopoverHasBeenShown, defaultHomeActiveTabName, - swapsWelcomeMessageHasBeenShown: getSwapsWelcomeMessageSeenStatus(state), haveSwapsQuotes: Boolean(Object.values(swapsState.quotes || {}).length), swapsFetchParams: swapsState.fetchParams, showAwaitingSwapScreen: swapsState.routeState === 'awaiting', @@ -106,6 +106,8 @@ const mapStateToProps = (state) => { shouldShowWeb3ShimUsageNotification, pendingConfirmations, infuraBlocked: getInfuraBlocked(state), + notificationsToShow: getSortedNotificationsToShow(state).length > 0, + showWhatsNewPopup: getShowWhatsNewPopup(state), }; }; @@ -126,12 +128,11 @@ const mapDispatchToProps = (dispatch) => ({ setConnectedStatusPopoverHasBeenShown: () => dispatch(setConnectedStatusPopoverHasBeenShown()), onTabClick: (name) => dispatch(setDefaultHomeActiveTabName(name)), - setSwapsWelcomeMessageHasBeenShown: () => - dispatch(setSwapsWelcomeMessageHasBeenShown()), setWeb3ShimUsageAlertDismissed: (origin) => setWeb3ShimUsageAlertDismissed(origin), disableWeb3ShimUsageAlert: () => setAlertEnabledness(ALERT_TYPES.web3ShimUsage, false), + hideShowWhatsNewPopup: () => dispatch(hideShowWhatsNewPopup()), }); export default compose( diff --git a/ui/app/pages/routes/routes.component.js b/ui/app/pages/routes/routes.component.js index 09b5c2fca97f..5648e4e20c65 100644 --- a/ui/app/pages/routes/routes.component.js +++ b/ui/app/pages/routes/routes.component.js @@ -102,13 +102,13 @@ export default class Routes extends Component { currentCurrency, pageChanged, setCurrentCurrencyToUSD, + history, } = this.props; - if (!currentCurrency) { setCurrentCurrencyToUSD(); } - this.props.history.listen((locationObj, action) => { + history.listen((locationObj, action) => { if (action === 'PUSH') { pageChanged(locationObj.pathname); } diff --git a/ui/app/pages/swaps/build-quote/build-quote.js b/ui/app/pages/swaps/build-quote/build-quote.js index d93ab6a9b59b..8fb8be820fa1 100644 --- a/ui/app/pages/swaps/build-quote/build-quote.js +++ b/ui/app/pages/swaps/build-quote/build-quote.js @@ -1,6 +1,7 @@ import React, { useContext, useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; +import { useLocation, useHistory } from 'react-router-dom'; import classnames from 'classnames'; import { uniqBy, isEqual } from 'lodash'; import { useHistory } from 'react-router-dom'; @@ -87,6 +88,10 @@ export default function BuildQuote({ ); const [verificationClicked, setVerificationClicked] = useState(false); + const { search } = useLocation(); + const urlParams = new window.URLSearchParams(search); + const queryFromAddress = urlParams.get('fromAddress'); + const balanceError = useSelector(getBalanceError); const fetchParams = useSelector(getFetchParams); const { sourceTokenInfo = {}, destinationTokenInfo = {} } = @@ -125,8 +130,22 @@ export default function BuildQuote({ ); const memoizedUsersTokens = useEqualityCheck(usersTokens); + const queryFromToken = + queryFromAddress && + (queryFromAddress === swapsEthToken.address + ? swapsEthToken + : memoizedUsersTokens.find( + (token) => token.address === queryFromAddress, + )); + + const providedFromToken = [ + fromToken, + fetchParamsFromToken, + queryFromToken, + ].find((token) => token?.address); + const selectedFromToken = getRenderableTokenData( - fromToken || fetchParamsFromToken, + providedFromToken || {}, tokenConversionRates, conversionRate, currentCurrency, diff --git a/ui/app/pages/swaps/index.scss b/ui/app/pages/swaps/index.scss index cb08d457ba52..7f1fe894a12e 100644 --- a/ui/app/pages/swaps/index.scss +++ b/ui/app/pages/swaps/index.scss @@ -6,7 +6,6 @@ @import 'dropdown-search-list/index'; @import 'exchange-rate-display/index'; @import 'fee-card/index'; -@import 'intro-popup/index'; @import 'loading-swaps-quotes/index'; @import 'main-quote-summary/index'; @import 'searchable-item-list/index'; diff --git a/ui/app/pages/swaps/intro-popup/index.js b/ui/app/pages/swaps/intro-popup/index.js deleted file mode 100644 index 6460538b9776..000000000000 --- a/ui/app/pages/swaps/intro-popup/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './intro-popup'; diff --git a/ui/app/pages/swaps/intro-popup/index.scss b/ui/app/pages/swaps/intro-popup/index.scss deleted file mode 100644 index 48d79c5f99bd..000000000000 --- a/ui/app/pages/swaps/intro-popup/index.scss +++ /dev/null @@ -1,71 +0,0 @@ -.intro-popup { - &__liquidity-sources-label { - @include H7; - - font-weight: bold; - margin-bottom: 6px; - color: $Black-100; - - @media screen and (min-width: 576px) { - @include H6; - } - } - - &__learn-more-header { - @include H4; - - font-weight: bold; - margin-bottom: 12px; - margin-top: 16px; - } - - &__learn-more-link { - @include H6; - - color: $Blue-500; - margin-bottom: 8px; - cursor: pointer; - } - - &__content { - margin-left: 24px; - - > img { - width: 96%; - margin-left: -9px; - } - } - - &__footer { - border-top: none; - } - - &__button { - border-radius: 100px; - height: 44px; - } - - &__source-logo-container { - width: 276px; - display: flex; - justify-content: center; - align-items: center; - padding: 20px 16px; - background: $Grey-000; - border-radius: 8px; - - @media screen and (min-width: 576px) { - width: 412px; - - img { - width: 364px; - } - } - } - - &__popover { - @media screen and (min-width: 576px) { - width: 460px; - } - } -} diff --git a/ui/app/pages/swaps/intro-popup/intro-popup.js b/ui/app/pages/swaps/intro-popup/intro-popup.js deleted file mode 100644 index 658c84bb9aa0..000000000000 --- a/ui/app/pages/swaps/intro-popup/intro-popup.js +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useContext } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; -import { I18nContext } from '../../../contexts/i18n'; -import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; -import { useNewMetricEvent } from '../../../hooks/useMetricEvent'; -import { getSwapsDefaultToken } from '../../../selectors'; -import Button from '../../../components/ui/button'; -import Popover from '../../../components/ui/popover'; - -export default function IntroPopup({ onClose }) { - const dispatch = useDispatch(useDispatch); - const history = useHistory(); - const t = useContext(I18nContext); - - const swapsDefaultToken = useSelector(getSwapsDefaultToken); - const enteredSwapsEvent = useNewMetricEvent({ - event: 'Swaps Opened', - properties: { - source: 'Intro popup', - active_currency: swapsDefaultToken.symbol, - }, - category: 'swaps', - }); - const blogPostVisitedEvent = useNewMetricEvent({ - event: 'Blog Post Visited ', - category: 'swaps', - }); - const contractAuditVisitedEvent = useNewMetricEvent({ - event: 'Contract Audit Visited', - category: 'swaps', - }); - const productOverviewDismissedEvent = useNewMetricEvent({ - event: 'Product Overview Dismissed', - category: 'swaps', - }); - - return ( -
- { - productOverviewDismissedEvent(); - onClose(); - }} - footerClassName="intro-popup__footer" - footer={ - - } - > -
-
- {t('swapIntroLiquiditySourcesLabel')} -
-
- -
-
- {t('swapIntroLearnMoreHeader')} -
-
{ - global.platform.openTab({ - url: - 'https://medium.com/metamask/introducing-metamask-swaps-84318c643785', - }); - blogPostVisitedEvent(); - }} - > - {t('swapIntroLearnMoreLink')} -
-
{ - global.platform.openTab({ - url: - 'https://diligence.consensys.net/audits/private/lsjipyllnw2/', - }); - contractAuditVisitedEvent(); - }} - > - {t('swapLearnMoreContractsAuditReview')} -
-
-
-
- ); -} - -IntroPopup.propTypes = { - onClose: PropTypes.func.isRequired, -}; diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 6b26d18f94fc..c1698be64ea3 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -494,3 +494,7 @@ export function getNativeCurrencyImage(state) { export function getNextSuggestedNonce(state) { return Number(state.metamask.nextNonce); } + +export function getShowWhatsNewPopup(state) { + return state.appState.showWhatsNewPopup; +} diff --git a/ui/app/store/actionConstants.js b/ui/app/store/actionConstants.js index 2fe280251d09..6ef2c51f4a04 100644 --- a/ui/app/store/actionConstants.js +++ b/ui/app/store/actionConstants.js @@ -112,3 +112,6 @@ export const LOADING_TOKEN_PARAMS_FINISHED = 'LOADING_TOKEN_PARAMS_FINISHED'; export const SET_REQUEST_ACCOUNT_TABS = 'SET_REQUEST_ACCOUNT_TABS'; export const SET_CURRENT_WINDOW_TAB = 'SET_CURRENT_WINDOW_TAB'; export const SET_OPEN_METAMASK_TAB_IDS = 'SET_OPEN_METAMASK_TAB_IDS'; + +// Home Screen +export const HIDE_SHOW_WHATS_NEW_POPUP = 'HIDE_SHOW_WHATS_NEW_POPUP'; diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 8d193afa65e8..7d73228f5312 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -2573,16 +2573,6 @@ export function setConnectedStatusPopoverHasBeenShown() { }; } -export function setSwapsWelcomeMessageHasBeenShown() { - return () => { - background.setSwapsWelcomeMessageHasBeenShown((err) => { - if (err) { - throw new Error(err.message); - } - }); - }; -} - export async function setAlertEnabledness(alertId, enabledness) { await promisifiedBackground.setAlertEnabledness(alertId, enabledness); } @@ -2888,3 +2878,9 @@ export function trackMetaMetricsEvent(payload, options) { export function trackMetaMetricsPage(payload, options) { return promisifiedBackground.trackMetaMetricsPage(payload, options); } + +export function updateViewedNotifications(notificationIdViewedStatusMap) { + return promisifiedBackground.updateViewedNotifications( + notificationIdViewedStatusMap, + ); +} From b1f1fb72962a57839d4f7d71010797ef756e1d1c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 5 Mar 2021 13:32:37 -0330 Subject: [PATCH 02/48] Move selectors from shared/notifications into ui/ directory --- shared/notifications/index.js | 68 ------------------- .../app/whats-new-popup/whats-new-popup.js | 47 ++++++++++--- ui/app/selectors/selectors.js | 23 +++++++ 3 files changed, 61 insertions(+), 77 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index f0bd29e605a5..3a48add09caa 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,7 +1,3 @@ -import { getSwapsFeatureLiveness } from '../../ui/app/ducks/swaps/swaps'; -import { getSwapsEthToken, getIsMainnet } from '../../ui/app/selectors'; -import { BUILD_QUOTE_ROUTE } from '../../ui/app/helpers/constants/routes'; - export const UI_NOTIFICATIONS = { 1: { id: 1, @@ -28,67 +24,3 @@ export const UI_NOTIFICATIONS = { actionText: 'Start survey', }, }; - -function getNotificationFilters(state) { - const currentNetworkIsMainnet = getIsMainnet(state); - const swapsIsEnabled = getSwapsFeatureLiveness(state); - - return { - 1: !currentNetworkIsMainnet || !swapsIsEnabled, - }; -} - -export function getSortedNotificationsToShow(state) { - const notifications = Object.values(state.metamask.notifications) || []; - const notificationFilters = getNotificationFilters(state); - const notificationsToShow = notifications.filter( - (notification) => - !notification.isShown && !notificationFilters[notification.id], - ); - const notificationsSortedByDate = notificationsToShow.sort( - (a, b) => new Date(b.date) - new Date(a.date), - ); - return notificationsSortedByDate; -} - -export function notifcationActionFunctions( - // eslint-disable-next-line no-unused-vars - dispatch, - // eslint-disable-next-line no-unused-vars - state, - // eslint-disable-next-line no-unused-vars - history, - // eslint-disable-next-line no-unused-vars - metricsEvent, -) { - const swapsEthToken = getSwapsEthToken(state); - - const actionFunctions = { - 1: () => { - metricsEvent({ - event: 'Swaps Opened', - properties: { source: 'Main View', active_currency: 'ETH' }, - category: 'swaps', - }); - global.platform.openExtensionInBrowser( - BUILD_QUOTE_ROUTE, - `fromAddress=${swapsEthToken.address}`, - ); - }, - 2: () => { - global.platform.openTab({ - url: 'https://metamask.io/download.html', - }); - }, - 3: () => { - global.platform.openTab({ - url: - 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', - }); - }, - }; - - return (id) => { - return actionFunctions[id]; - }; -} diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 3fcd96e5875e..424153106a60 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -1,6 +1,5 @@ import React, { useContext, useMemo, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { I18nContext } from '../../../contexts/i18n'; @@ -9,23 +8,53 @@ import { MetaMetricsContext } from '../../../contexts/metametrics.new'; import Button from '../../ui/button'; import Popover from '../../ui/popover'; import { updateViewedNotifications } from '../../../store/actions'; +import { UI_NOTIFICATIONS } from '../../../../../shared/notifications'; import { - UI_NOTIFICATIONS, - notifcationActionFunctions, getSortedNotificationsToShow, -} from '../../../../../shared/notifications'; + getSwapsEthToken, +} from '../../../selectors'; +import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; + +function notifcationActionFunctions(state, metricsEvent) { + const swapsEthToken = getSwapsEthToken(state); + + const actionFunctions = { + 1: () => { + metricsEvent({ + event: 'Swaps Opened', + properties: { source: 'Main View', active_currency: 'ETH' }, + category: 'swaps', + }); + global.platform.openExtensionInBrowser( + BUILD_QUOTE_ROUTE, + `fromAddress=${swapsEthToken.address}`, + ); + }, + 2: () => { + global.platform.openTab({ + url: 'https://metamask.io/download.html', + }); + }, + 3: () => { + global.platform.openTab({ + url: + 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', + }); + }, + }; + + return (id) => { + return actionFunctions[id]; + }; +} export default function WhatsNewPopup({ onClose }) { - const dispatch = useDispatch(useDispatch); - const history = useHistory(); const metricsEvent = useContext(MetaMetricsContext); const t = useContext(I18nContext); const state = useSelector((_state) => _state); const getNotifcationActionFunctionsById = notifcationActionFunctions( - dispatch, state, - history, metricsEvent, ); diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index c1698be64ea3..50648d9becbe 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -24,6 +24,7 @@ import { } from '../helpers/utils/conversions.util'; import { TEMPLATED_CONFIRMATION_MESSAGE_TYPES } from '../pages/confirmation/templates'; +import { getSwapsFeatureLiveness } from '../ducks/swaps/swaps'; import { getNativeCurrency } from './send'; @@ -498,3 +499,25 @@ export function getNextSuggestedNonce(state) { export function getShowWhatsNewPopup(state) { return state.appState.showWhatsNewPopup; } + +function getNotificationFilters(state) { + const currentNetworkIsMainnet = getIsMainnet(state); + const swapsIsEnabled = getSwapsFeatureLiveness(state); + + return { + 1: !currentNetworkIsMainnet || !swapsIsEnabled, + }; +} + +export function getSortedNotificationsToShow(state) { + const notifications = Object.values(state.metamask.notifications) || []; + const notificationFilters = getNotificationFilters(state); + const notificationsToShow = notifications.filter( + (notification) => + !notification.isShown && !notificationFilters[notification.id], + ); + const notificationsSortedByDate = notificationsToShow.sort( + (a, b) => new Date(b.date) - new Date(a.date), + ); + return notificationsSortedByDate; +} From 6b3cb876e33406cb8afa5976fb404251c5570313 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 8 Mar 2021 06:05:01 -0330 Subject: [PATCH 03/48] Use keys for localized message in whats new notifications objects, to ensure notifications will be translated. --- app/_locales/en/messages.json | 36 +++++++++++ shared/notifications/index.js | 63 +++++++++++++++---- .../app/whats-new-popup/whats-new-popup.js | 8 +-- 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 168b3104b75c..688d626c7f7a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1232,6 +1232,42 @@ "notCurrentAccount": { "message": "Is this the correct account? It's different from the currently selected account in your wallet" }, + "notifications#1Title": { + "message": "Now Swap tokens directly in your wallet!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." + }, + "notifications#1Description": { + "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", + "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." + }, + "notifications#1ActionText": { + "message": "Start swapping", + "description": "The 'call to action' label on the button, or link, of the swap 'See What's New' notification. Upon clicking, users will be taken to the swaps page." + }, + "notifications#2Title": { + "message": "MetaMask Mobile is here!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." + }, + "notifications#2Description": { + "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", + "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." + }, + "notifications#2ActionText": { + "message": "Get the mobile app", + "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." + }, + "notifications#3Title": { + "message": "Help improve MetaMask", + "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." + }, + "notifications#3Description": { + "message": "Please share your experience in this 5 minute survey.", + "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." + }, + "notifications#3ActionText": { + "message": "Start survey", + "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." + }, "notEnoughGas": { "message": "Not Enough Gas" }, diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 3a48add09caa..e62d8dae3767 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,26 +1,67 @@ export const UI_NOTIFICATIONS = { 1: { id: 1, - title: 'Now Swap tokens directly in your wallet!', - description: - 'MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.', + title: 'notifications#1Title', + description: 'notifications#1Description', date: '02/22/2020', image: 'images/swaps-logos-small.svg', - actionText: 'Start swapping', + actionText: 'notifications#1ActionText', }, 2: { id: 2, - title: 'MetaMask Mobile is here!', - description: - 'Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.', + title: 'notifications#2Title', + description: 'notifications#2Description', date: '02/22/2020', - actionText: 'Get the mobile app', + actionText: 'notifications#2ActionText', }, 3: { id: 3, - title: 'Help improve MetaMask', - description: 'Please share your experience in this 5 minute survey.', + title: 'notifications#3Title', + description: 'notifications#3Description', date: '02/22/2020', - actionText: 'Start survey', + actionText: 'notifications#3ActionText', }, }; + +/** +* Messages and descriptions for all locale keys above are in app/_locales/en/messages.json. +* For convenience, they are copied below. If you need to change them, you will have to edit +* the aforementioned json file + + "notifications#1Title": { + "message": "Now Swap tokens directly in your wallet!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." + } + "notifications#1Description": { + "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", + "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." + } + "notifications#1ActionText": { + "message": "Start swapping", + "description": "The 'call to action' label on the button, or link, of the swap 'See What's New' notification. Upon clicking, users will be taken to the swaps page." + } + "notifications#2Title": { + "message": "MetaMask Mobile is here!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." + } + "notifications#2Description": { + "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", + "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." + } + "notifications#2ActionText": { + "message": "Get the mobile app", + "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." + } + "notifications#3Title": { + "message": "Help improve MetaMask", + "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." + } + "notifications#3Description": { + "message": "Please share your experience in this 5 minute survey.", + "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." + } + "notifications#3ActionText": { + "message": "Start survey", + "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." + } +*/ diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 424153106a60..f247d354f471 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -120,13 +120,13 @@ export default function WhatsNewPopup({ onClose }) { /> )}
- {notification.title} + {t(notification.title)}
- {notification.description} + {t(notification.description)}
{isFirstNotification && UI_NOTIFICATIONS[notification.id].actionText && ( @@ -138,7 +138,7 @@ export default function WhatsNewPopup({ onClose }) { getNotifcationActionFunctionsById(notification.id)() } > - {UI_NOTIFICATIONS[notification.id].actionText} + {t(UI_NOTIFICATIONS[notification.id].actionText)} )} {!isFirstNotification && @@ -149,7 +149,7 @@ export default function WhatsNewPopup({ onClose }) { getNotifcationActionFunctionsById(notification.id)() } > - {UI_NOTIFICATIONS[notification.id].actionText} + {t(UI_NOTIFICATIONS[notification.id].actionText)}
)}
From df3cadfd02c120d71cd4f6057ba33861711ecf89 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 8 Mar 2021 06:05:52 -0330 Subject: [PATCH 04/48] Remove unused swaps intro popup locale messages --- app/_locales/en/messages.json | 21 --------------------- app/_locales/es/messages.json | 21 --------------------- app/_locales/es_419/messages.json | 21 --------------------- app/_locales/hi/messages.json | 21 --------------------- app/_locales/id/messages.json | 21 --------------------- app/_locales/it/messages.json | 21 --------------------- app/_locales/ja/messages.json | 21 --------------------- app/_locales/ko/messages.json | 21 --------------------- app/_locales/ru/messages.json | 21 --------------------- app/_locales/tl/messages.json | 21 --------------------- app/_locales/vi/messages.json | 21 --------------------- app/_locales/zh_CN/messages.json | 21 --------------------- 12 files changed, 252 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 688d626c7f7a..8efc77773db7 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1850,24 +1850,6 @@ "swapHighSlippageWarning": { "message": "Slippage amount is very high. Make sure you know what you are doing!" }, - "swapIntroLearnMoreHeader": { - "message": "Want to learn more?" - }, - "swapIntroLearnMoreLink": { - "message": "Learn more about MetaMask Swaps" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Liquidity sources include:" - }, - "swapIntroPopupSubTitle": { - "message": "You can now swap tokens directly in your MetaMask wallet. MetaMask Swaps combines multiple decentralized exchange aggregators, professional market makers, and individual DEXs to ensure MetaMask users always get the best price with the lowest network fees." - }, - "swapIntroPopupTitle": { - "message": "Token swapping is here!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Review our official contracts audit" - }, "swapLowSlippageError": { "message": "Transaction may fail, max slippage too low." }, @@ -1994,9 +1976,6 @@ "swapSourceInfo": { "message": "We search multiple liquidity sources (exchanges, aggregators and professional market makers) to find the best rates and lowest network fees." }, - "swapStartSwapping": { - "message": "Start swapping" - }, "swapSwapFrom": { "message": "Swap from" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index b14d2744fb0a..2944fbceac56 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1579,24 +1579,6 @@ "swapHighSlippageWarning": { "message": "La cantidad de deslizamiento es muy alta. ¡Asegúrate de saber lo que estás haciendo!" }, - "swapIntroLearnMoreHeader": { - "message": "¿Quiere aprender más?" - }, - "swapIntroLearnMoreLink": { - "message": "Más información sobre los Intercambios MetaMask" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Las fuentes de liquidez incluyen:" - }, - "swapIntroPopupSubTitle": { - "message": "Ahora puede intercambiar tokens directamente en su monedero MetaMask. Intercambios MetaMask combina múltiples agregadores de intercambio descentralizados, creadores de mercado profesionales y DEX individuales para garantizar que los usuarios de MetaMask siempre obtengan el mejor precio con las tarifas de red más bajas." - }, - "swapIntroPopupTitle": { - "message": "¡El intercambio de tokens está aquí!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Revise nuestra auditoría de contratos oficiales" - }, "swapLowSlippageError": { "message": "La transacción puede fallar, el deslizamiento máximo es demasiado bajo." }, @@ -1717,9 +1699,6 @@ "swapSourceInfo": { "message": "Buscamos múltiples fuentes de liquidez (exchanges, agregadores y creadores de mercado profesionales) para encontrar las mejores tarifas y las tarifas de red más bajas." }, - "swapStartSwapping": { - "message": "Comenzar intercambio" - }, "swapSwapFrom": { "message": "Intercambiar desde" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index c336611d0d0d..d40fb00eaee6 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1579,24 +1579,6 @@ "swapHighSlippageWarning": { "message": "La cantidad de deslizamiento es muy alta. ¡Asegúrate de saber lo que estás haciendo!" }, - "swapIntroLearnMoreHeader": { - "message": "¿Quiere aprender más?" - }, - "swapIntroLearnMoreLink": { - "message": "Más información sobre los Intercambios MetaMask" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Las fuentes de liquidez incluyen:" - }, - "swapIntroPopupSubTitle": { - "message": "Ahora puede intercambiar tokens directamente en su billetera MetaMask. Intercambios MetaMask combina múltiples agregadores de intercambio descentralizados, creadores de mercado profesionales y DEX individuales para garantizar que los usuarios de MetaMask siempre obtengan el mejor precio con las tarifas de red más bajas." - }, - "swapIntroPopupTitle": { - "message": "¡El intercambio de tokens está aquí!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Revise nuestra auditoría de contratos oficiales" - }, "swapLowSlippageError": { "message": "La transacción puede fallar, el deslizamiento máximo es demasiado bajo." }, @@ -1717,9 +1699,6 @@ "swapSourceInfo": { "message": "Buscamos múltiples fuentes de liquidez (exchanges, agregadores y creadores de mercado profesionales) para encontrar las mejores tarifas y las tarifas de red más bajas." }, - "swapStartSwapping": { - "message": "Comenzar intercambio" - }, "swapSwapFrom": { "message": "Intercambiar desde" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 89f5f6893b83..c26385666cb6 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1567,24 +1567,6 @@ "swapHighSlippageWarning": { "message": "स्लिपेज राशि बहुत अधिक है। सुनिश्चित करें कि आप जानते हैं कि आप क्या कर रहे हैं!" }, - "swapIntroLearnMoreHeader": { - "message": "अधिक सीखना चाहते हैं?" - }, - "swapIntroLearnMoreLink": { - "message": "MetaMask स्वैप के बारे में अधिक जानें" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "चलनिधि स्रोतों में निम्न शामिल हैं:" - }, - "swapIntroPopupSubTitle": { - "message": "अब आप अपने MetaMask वॉलेट में सीधे टोकन स्वैप कर सकते हैं। MetaMask स्वैप कई विकेंद्रीकृत विनिमय एग्रीगेटर, पेशेवर बाजार निर्माताओं और व्यक्तिगत DEX को जोड़ता है, ताकि MetaMask उपयोगकर्ताओं को हमेशा सबसे कम नेटवर्क शुल्क के साथ सबसे अच्छा मूल्य मिल सके।" - }, - "swapIntroPopupTitle": { - "message": "टोकन स्वैपिंग यहाँ उपलब्ध है!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "हमारे आधिकारिक अनुबंधों के ऑडिट की समीक्षा करें" - }, "swapLowSlippageError": { "message": "लेनदेन विफल हो सकता है, अधिकतम स्लिपेज बहुत कम हो सकता है।" }, @@ -1687,9 +1669,6 @@ "swapSourceInfo": { "message": "हम सर्वोत्तम दरों और न्यूनतम नेटवर्क शुल्क का पता लगाने के लिए कई चलनिधि स्रोतों (एक्सचेंज, एग्रीगेटर और पेशेवर बाजार निर्माताओं) की खोज करते हैं।" }, - "swapStartSwapping": { - "message": "स्वैप करना शुरू करें" - }, "swapSwapFrom": { "message": "इससे स्वैप करें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index c2994711f93b..91bcf003d07b 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1567,24 +1567,6 @@ "swapHighSlippageWarning": { "message": "Jumlah slippage sangat tinggi. Pastikan Anda mengetahui yang Anda kerjakan!" }, - "swapIntroLearnMoreHeader": { - "message": "Ingin mempelajari selengkapnya?" - }, - "swapIntroLearnMoreLink": { - "message": "Pelajari selengkapnya tentang Penukaran MetaMask" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Sumber likuiditas mencakup:" - }, - "swapIntroPopupSubTitle": { - "message": "Sekarang, Anda bisa menukar token secara langsung di dompet MetaMask Anda. Penukaran MetaMask menggabungkan beberapa agregator penukaran terdesentralisasi, pembuat pasar profesional, dan DEX individu untuk memastikan pengguna MetaMask selalu mendapatkan harga terbaik dengan biaya jaringan terendah." - }, - "swapIntroPopupTitle": { - "message": "Penukaran token ada di sini!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Tinjau audit kontrak resmi kami" - }, "swapLowSlippageError": { "message": "Transaksi bisa gagal, slippage maks. terlalu rendah." }, @@ -1687,9 +1669,6 @@ "swapSourceInfo": { "message": "Kami mencari beberapa sumber likuiditas (penukaran, agregator, dan pembuat pasar profesional) untuk menemukan tarif terbaik dan biaya jaringan terendah." }, - "swapStartSwapping": { - "message": "Mulai menukar" - }, "swapSwapFrom": { "message": "Tukar dari" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index ea9f973befd5..c7bb7b3502b4 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -1585,24 +1585,6 @@ "swapHighSlippageWarning": { "message": "L'importo di slippage è molto alto. Assicurati di sapere cosa stai facendo!" }, - "swapIntroLearnMoreHeader": { - "message": "Vuoi sapere di più?" - }, - "swapIntroLearnMoreLink": { - "message": "Scopri di più su MetaMask Swaps" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Sorgenti di liquidità incluse:" - }, - "swapIntroPopupSubTitle": { - "message": "Adesso puoi scambiare token direttamente dal tuo portafgolio MetaMask. MetaMask Swaps combina vari siti di scambio decentralizzati, aggregatori e market maker professionisti per assicurare che gli utenti di MetaMask ottengano sempre il miglior prezzo con le tasse di rete minori." - }, - "swapIntroPopupTitle": { - "message": "Lo scambio di token è qui!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Esamina l'audit ufficiale dei nostri smart contracts" - }, "swapLowSlippageError": { "message": "La transazione può fallire, il massimo slippage è troppo basso." }, @@ -1729,9 +1711,6 @@ "swapSourceInfo": { "message": "Cerchiamo sorgenti di liquidità multiple (siti di scambio, aggregatori, market maker professionisti) per trovare le tariffe migliori e le tasse di rete minori." }, - "swapStartSwapping": { - "message": "Inizia a scambiare" - }, "swapSwapFrom": { "message": "Scambia da" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 1644e58970f4..a0f8547c9e6b 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1579,24 +1579,6 @@ "swapHighSlippageWarning": { "message": "非常に大きいスリッページ額です。本当に実行するか確認してください。" }, - "swapIntroLearnMoreHeader": { - "message": "詳細を表示しますか?" - }, - "swapIntroLearnMoreLink": { - "message": "MetaMask Swapsの詳細" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "流動性ソースには以下が含まれます。" - }, - "swapIntroPopupSubTitle": { - "message": "トークンをMetaMaskで直接スワップできるようになりました。MetaMask Swapsは、多数の分散型取引所アグリゲーター、専門のマーケットメーカー、DEX取引所を統合し、ユーザーは常に最低のネットワーク手数料、最適な価格で取引できます。" - }, - "swapIntroPopupTitle": { - "message": "トークンのスワップはこちら!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "MetaSwapのコントラクト監査のレビュー" - }, "swapLowSlippageError": { "message": "トランザクションが失敗する可能性があります。最大スリッページが少なすぎます。" }, @@ -1717,9 +1699,6 @@ "swapSourceInfo": { "message": "最良のレートと最小のネットワーク手数料を探すため、複数の流動性ソース(取引所、アグリゲーター、専門のマーケットメーカー)を検索します。" }, - "swapStartSwapping": { - "message": "スワップの開始" - }, "swapSwapFrom": { "message": "スワップ元" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index ea089e71b136..847623a315eb 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1567,24 +1567,6 @@ "swapHighSlippageWarning": { "message": "슬리패지 금액이 아주 큽니다. 현재 어떤 작업을 하고 있는지 확인하세요!" }, - "swapIntroLearnMoreHeader": { - "message": "자세한 정보를 확인하고 싶으신가요?" - }, - "swapIntroLearnMoreLink": { - "message": "MetaMask Swaps에 대해 자세히 알아보기" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "다음을 포함한 유동성 소스:" - }, - "swapIntroPopupSubTitle": { - "message": "이제 MetaMask 지갑에서 토큰을 바로 스왑할 수 있습니다. MetaMask Swaps는 다양한 분산형 교환 애그리게이터, 투자전문기관, 개별 DEX를 결합하여 MetaMask 사용자가 언제든 최저 네트워크 요금으로 최상의 가격을 얻을 수 있게 합니다." - }, - "swapIntroPopupTitle": { - "message": "토큰 스왑은 여기서 진행됩니다!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "당사의 공식 계약 감사 검토" - }, "swapLowSlippageError": { "message": "거래가 실패할 수도 있습니다. 최대 슬리패지가 너무 낮습니다." }, @@ -1687,9 +1669,6 @@ "swapSourceInfo": { "message": "당사에서는 여러 유동성 소스(교환, 애그리게이터, 투자전문기관)를 검색하여 최상의 요율과 최저 네트워크 요금을 찾습니다." }, - "swapStartSwapping": { - "message": "스왑 시작" - }, "swapSwapFrom": { "message": "다음에서 스왑" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 7fce60d664a4..f87eb3002a78 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1567,24 +1567,6 @@ "swapHighSlippageWarning": { "message": "Величина проскальзывания очень велика. Убедитесь, что вы знаете, что делаете!" }, - "swapIntroLearnMoreHeader": { - "message": "Хотите узнать больше?" - }, - "swapIntroLearnMoreLink": { - "message": "Подробнее о свопах MetaMask" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Источники ликвидности включают:" - }, - "swapIntroPopupSubTitle": { - "message": "Теперь вы можете обменивать токены прямо в кошельке MetaMask. MetaMask Swaps объединяет несколько децентрализованных агрегаторов обменов, профессиональных торговцев и отдельные DEX, чтобы пользователи MetaMask всегда получали лучшую цену с минимальными комиссиями сети." - }, - "swapIntroPopupTitle": { - "message": "Обмен токенов здесь!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Ознакомьтесь с нашим официальным аудитом контрактов" - }, "swapLowSlippageError": { "message": "Транзакции могут завершиться неудачей, максимальное проскальзывание слишком мало." }, @@ -1687,9 +1669,6 @@ "swapSourceInfo": { "message": "Мы ищем несколько источников ликвидности (биржи, агрегаторы и профессиональные продавцы), чтобы найти лучшие цены и самые низкие сетевые комиссии." }, - "swapStartSwapping": { - "message": "Начать обмен" - }, "swapSwapFrom": { "message": "Своп с" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 15ad973eeaa9..37aa0cadbcc1 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -1564,24 +1564,6 @@ "swapHighSlippageWarning": { "message": "Sobrang laki ng halaga ng slippage. Tiyaking alam mo ang ginagawa mo!" }, - "swapIntroLearnMoreHeader": { - "message": "Gusto mo bang matuto pa?" - }, - "swapIntroLearnMoreLink": { - "message": "Matuto pa tungkol sa MetaMask Swaps" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Kasama sa mga pinagkunan ng liquidity ang:" - }, - "swapIntroPopupSubTitle": { - "message": "Puwede mo nang direktang i-swap ang mga token sa iyong MetaMask wallet. Pinagsasama-sama ng MetaMask Swaps ang maraming decentralized exchange aggregator, propesyonal na market maker, at indibidwal na DEX para matiyak na makukuha palagi ng mga user ng MetaMask ang pinakasulit na presyo nang may pinakamababang bayarin sa network." - }, - "swapIntroPopupTitle": { - "message": "Ito na ang pag-swap ng token!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Suriin ang aming audit ng mga opisyal na kontrata" - }, "swapLowSlippageError": { "message": "Maaaring hindi magtagumpay ang transaksyon, masyadong mababa ang max na slippage." }, @@ -1684,9 +1666,6 @@ "swapSourceInfo": { "message": "Naghahanap kami ng maraming pinagkukunan ng liquidity (mga exchange, aggregator at propesyonal na market maker) para mahanap ang mga pinakasulit na rate at pinakamababang bayarin sa network." }, - "swapStartSwapping": { - "message": "Simulang mag-swap" - }, "swapSwapFrom": { "message": "Ipalit mula sa" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 988745ab92b0..38bd1128cdde 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1567,24 +1567,6 @@ "swapHighSlippageWarning": { "message": "Số tiền trượt giá rất cao. Hãy chắc chắn rằng bạn hiểu những gì mình đang làm!" }, - "swapIntroLearnMoreHeader": { - "message": "Bạn muốn tìm hiểu thêm?" - }, - "swapIntroLearnMoreLink": { - "message": "Tìm hiểu thêm về MetaMask Swaps" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "Các nguồn thanh khoản bao gồm:" - }, - "swapIntroPopupSubTitle": { - "message": "Giờ đây bạn có thể hoán đổi token ngay trong ví MetaMask của mình. MetaMask Swaps quy tụ nhiều trình tổng hợp sàn giao dịch phi tập trung, các nhà tạo lập thị trường chuyên nghiệp và các sàn giao dịch phi tập trung dành cho cá nhân nhằm đảm bảo người dùng MetaMask luôn nhận được mức giá tốt nhất với phí mạng thấp nhất." - }, - "swapIntroPopupTitle": { - "message": "Tính năng hoán đổi token đã sẵn sàng!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "Xem xét quy trình kiểm tra hợp đồng chính thức của chúng tôi" - }, "swapLowSlippageError": { "message": "Giao dịch có thể không thành công, mức trượt giá tối đa quá thấp." }, @@ -1687,9 +1669,6 @@ "swapSourceInfo": { "message": "Chúng tôi tìm kiếm nhiều nguồn thanh khoản (các sàn giao dịch, trình tổng hợp và nhà tạo lập thị trường) để tìm được mức tỷ lệ tốt nhất và phí mạng thấp nhất." }, - "swapStartSwapping": { - "message": "Bắt đầu hoán đổi" - }, "swapSwapFrom": { "message": "Hoán đổi từ" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index e8286a67536f..2ced8daf22fa 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1579,24 +1579,6 @@ "swapHighSlippageWarning": { "message": "滑点数量非常大。确保您知道您的操作!" }, - "swapIntroLearnMoreHeader": { - "message": "想了解更多信息?" - }, - "swapIntroLearnMoreLink": { - "message": "了解更多关于 MetaMask Swap(兑换)" - }, - "swapIntroLiquiditySourcesLabel": { - "message": "流动资金来源包括:" - }, - "swapIntroPopupSubTitle": { - "message": "现在您可以直接在 MetaMask 钱包中兑换代币。MetaMask Swaps(兑换)结合了多个去中心化交易所聚合商、专业做市商和个人 DEX,确保 MetaMask 用户始终以最低的网络费用获得最佳价格。" - }, - "swapIntroPopupTitle": { - "message": "代币兑换来了!" - }, - "swapLearnMoreContractsAuditReview": { - "message": "查看我们的官方合约审计" - }, "swapLowSlippageError": { "message": "交易可能失败,最大滑点过低。" }, @@ -1717,9 +1699,6 @@ "swapSourceInfo": { "message": "我们搜索多个流动性来源(交易所、聚合商和专业做市商),以找到最好的利率和最低的网络手续费。" }, - "swapStartSwapping": { - "message": "开始兑换" - }, "swapSwapFrom": { "message": "兑换自" }, From 67127baec9ee96d3e351ef93028ec8bc08f62612 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 17 Mar 2021 03:14:51 -0230 Subject: [PATCH 05/48] Fix keys of whats new notification locales --- app/_locales/en/messages.json | 18 +++++++++--------- shared/notifications/index.js | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8efc77773db7..1534c006ba25 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1232,39 +1232,39 @@ "notCurrentAccount": { "message": "Is this the correct account? It's different from the currently selected account in your wallet" }, - "notifications#1Title": { + "notificationsTitle1": { "message": "Now Swap tokens directly in your wallet!", "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." }, - "notifications#1Description": { + "notificationsDescription1": { "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." }, - "notifications#1ActionText": { + "notificationsActionText1": { "message": "Start swapping", "description": "The 'call to action' label on the button, or link, of the swap 'See What's New' notification. Upon clicking, users will be taken to the swaps page." }, - "notifications#2Title": { + "notificationsTitle2": { "message": "MetaMask Mobile is here!", "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." }, - "notifications#2Description": { + "notificationsDescription2": { "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." }, - "notifications#2ActionText": { + "notificationsActionText2": { "message": "Get the mobile app", "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." }, - "notifications#3Title": { + "notificationsTitle3": { "message": "Help improve MetaMask", "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." }, - "notifications#3Description": { + "notificationsDescription3": { "message": "Please share your experience in this 5 minute survey.", "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." }, - "notifications#3ActionText": { + "notificationsActionText3": { "message": "Start survey", "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." }, diff --git a/shared/notifications/index.js b/shared/notifications/index.js index e62d8dae3767..7fe56bbc0ef6 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,25 +1,25 @@ export const UI_NOTIFICATIONS = { 1: { id: 1, - title: 'notifications#1Title', - description: 'notifications#1Description', - date: '02/22/2020', + title: 'notificationsTitle1', + description: 'notificationsDescription1', + date: '2020-03-17', image: 'images/swaps-logos-small.svg', - actionText: 'notifications#1ActionText', + actionText: 'notificationsActionText1', }, 2: { id: 2, - title: 'notifications#2Title', - description: 'notifications#2Description', - date: '02/22/2020', - actionText: 'notifications#2ActionText', + title: 'notificationsTitle2', + description: 'notificationsDescription2', + date: '2020-03-17', + actionText: 'notificationsActionText2', }, 3: { id: 3, - title: 'notifications#3Title', - description: 'notifications#3Description', - date: '02/22/2020', - actionText: 'notifications#3ActionText', + title: 'notificationsTitle3', + description: 'notificationsDescription3', + date: '2020-03-17', + actionText: 'notificationsActionText3', }, }; From 89173e32bba2f529277859c56d5580895cddbb83 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 17 Mar 2021 03:17:43 -0230 Subject: [PATCH 06/48] Remove notifications messages and descriptions from comment in shared/notifications --- shared/notifications/index.js | 42 +---------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 7fe56bbc0ef6..768fa22c4486 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,3 +1,4 @@ +// Messages and descriptions for these locale keys are in app/_locales/en/messages.json export const UI_NOTIFICATIONS = { 1: { id: 1, @@ -23,45 +24,4 @@ export const UI_NOTIFICATIONS = { }, }; -/** -* Messages and descriptions for all locale keys above are in app/_locales/en/messages.json. -* For convenience, they are copied below. If you need to change them, you will have to edit -* the aforementioned json file - "notifications#1Title": { - "message": "Now Swap tokens directly in your wallet!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." - } - "notifications#1Description": { - "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", - "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." - } - "notifications#1ActionText": { - "message": "Start swapping", - "description": "The 'call to action' label on the button, or link, of the swap 'See What's New' notification. Upon clicking, users will be taken to the swaps page." - } - "notifications#2Title": { - "message": "MetaMask Mobile is here!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." - } - "notifications#2Description": { - "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", - "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." - } - "notifications#2ActionText": { - "message": "Get the mobile app", - "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." - } - "notifications#3Title": { - "message": "Help improve MetaMask", - "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." - } - "notifications#3Description": { - "message": "Please share your experience in this 5 minute survey.", - "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." - } - "notifications#3ActionText": { - "message": "Start survey", - "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." - } -*/ From a7b435eb23d7422f9abc86e41985a8f9e23e7be7 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 17 Mar 2021 03:18:55 -0230 Subject: [PATCH 07/48] Move notifcationActionFunctions to shared/notifications and make it stateless --- shared/notifications/index.js | 32 ++++++++++++++ .../app/whats-new-popup/whats-new-popup.js | 44 ++----------------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 768fa22c4486..e935f6d73af2 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,3 +1,6 @@ +import { ETH_SWAPS_TOKEN_OBJECT } from '../constants/swaps'; +import { BUILD_QUOTE_ROUTE } from '../../ui/app/helpers/constants/routes'; + // Messages and descriptions for these locale keys are in app/_locales/en/messages.json export const UI_NOTIFICATIONS = { 1: { @@ -24,4 +27,33 @@ export const UI_NOTIFICATIONS = { }, }; +export function notifcationActionFunctions(metricsEvent) { + const actionFunctions = { + 1: () => { + metricsEvent({ + event: 'Swaps Opened', + properties: { source: 'Main View', active_currency: 'ETH' }, + category: 'swaps', + }); + global.platform.openExtensionInBrowser( + BUILD_QUOTE_ROUTE, + `fromAddress=${ETH_SWAPS_TOKEN_OBJECT.address}`, + ); + }, + 2: () => { + global.platform.openTab({ + url: 'https://metamask.io/download.html', + }); + }, + 3: () => { + global.platform.openTab({ + url: + 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', + }); + }, + }; + return (id) => { + return actionFunctions[id]; + }; +} diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index f247d354f471..770d1b18fc32 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -8,53 +8,17 @@ import { MetaMetricsContext } from '../../../contexts/metametrics.new'; import Button from '../../ui/button'; import Popover from '../../ui/popover'; import { updateViewedNotifications } from '../../../store/actions'; -import { UI_NOTIFICATIONS } from '../../../../../shared/notifications'; import { - getSortedNotificationsToShow, - getSwapsEthToken, -} from '../../../selectors'; -import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; - -function notifcationActionFunctions(state, metricsEvent) { - const swapsEthToken = getSwapsEthToken(state); - - const actionFunctions = { - 1: () => { - metricsEvent({ - event: 'Swaps Opened', - properties: { source: 'Main View', active_currency: 'ETH' }, - category: 'swaps', - }); - global.platform.openExtensionInBrowser( - BUILD_QUOTE_ROUTE, - `fromAddress=${swapsEthToken.address}`, - ); - }, - 2: () => { - global.platform.openTab({ - url: 'https://metamask.io/download.html', - }); - }, - 3: () => { - global.platform.openTab({ - url: - 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', - }); - }, - }; - - return (id) => { - return actionFunctions[id]; - }; -} + UI_NOTIFICATIONS, + notifcationActionFunctions, +} from '../../../../../shared/notifications'; +import { getSortedNotificationsToShow } from '../../../selectors'; export default function WhatsNewPopup({ onClose }) { const metricsEvent = useContext(MetaMetricsContext); const t = useContext(I18nContext); - const state = useSelector((_state) => _state); const getNotifcationActionFunctionsById = notifcationActionFunctions( - state, metricsEvent, ); From fadcc709ced0aa941e3c58f7b5560df6c3e6d36f Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 17 Mar 2021 03:20:45 -0230 Subject: [PATCH 08/48] Get notification data from constants instead of state in whats-new-popup --- .../components/app/whats-new-popup/index.scss | 6 +- .../app/whats-new-popup/whats-new-popup.js | 58 ++++++++++--------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/index.scss b/ui/app/components/app/whats-new-popup/index.scss index 2a6623267a00..277f1e44b98f 100644 --- a/ui/app/components/app/whats-new-popup/index.scss +++ b/ui/app/components/app/whats-new-popup/index.scss @@ -18,10 +18,14 @@ margin-bottom: 16px; } - &__notification-description { + &__description-and-date { margin-bottom: 16px; } + &__notification-date { + color: $Grey-500; + } + &__button { margin-right: auto; } diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 770d1b18fc32..4048fc4679cd 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -68,7 +68,8 @@ export default function WhatsNewPopup({ onClose }) { mediumHeight >
- {notifications.map((notification, index) => { + {notifications.map(({ id, date }, index) => { + const notification = UI_NOTIFICATIONS[id]; const isFirstNotification = index === 0; return (
- {t(notification.description)} +
+ {t(notification.description)} +
+
+ {date} +
- {isFirstNotification && - UI_NOTIFICATIONS[notification.id].actionText && ( - - )} - {!isFirstNotification && - UI_NOTIFICATIONS[notification.id].actionText && ( -
- getNotifcationActionFunctionsById(notification.id)() - } - > - {t(UI_NOTIFICATIONS[notification.id].actionText)} -
- )} + {isFirstNotification && notification.actionText && ( + + )} + {!isFirstNotification && UI_NOTIFICATIONS[id].actionText && ( +
+ {t(notification.actionText)} +
+ )}
); })} From 7611756864b2b237e43cb3a62aa7bd126fb250a1 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 17 Mar 2021 03:21:32 -0230 Subject: [PATCH 09/48] Code cleanup --- app/_locales/en/messages.json | 46 +++++++++---------- .../ui/popover/popover.component.js | 3 ++ ui/app/ducks/app/app.js | 2 +- ui/app/pages/home/home.component.js | 6 +-- ui/app/pages/home/home.container.js | 9 ++-- ui/app/pages/swaps/build-quote/build-quote.js | 1 - 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1534c006ba25..954e9a985091 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1232,44 +1232,44 @@ "notCurrentAccount": { "message": "Is this the correct account? It's different from the currently selected account in your wallet" }, - "notificationsTitle1": { - "message": "Now Swap tokens directly in your wallet!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." - }, - "notificationsDescription1": { - "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", - "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." + "notEnoughGas": { + "message": "Not Enough Gas" }, "notificationsActionText1": { "message": "Start swapping", "description": "The 'call to action' label on the button, or link, of the swap 'See What's New' notification. Upon clicking, users will be taken to the swaps page." }, - "notificationsTitle2": { - "message": "MetaMask Mobile is here!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." - }, - "notificationsDescription2": { - "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", - "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." - }, "notificationsActionText2": { "message": "Get the mobile app", "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." }, - "notificationsTitle3": { - "message": "Help improve MetaMask", - "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." + "notificationsActionText3": { + "message": "Start survey", + "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." + }, + "notificationsDescription1": { + "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", + "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." + }, + "notificationsDescription2": { + "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", + "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." }, "notificationsDescription3": { "message": "Please share your experience in this 5 minute survey.", "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." }, - "notificationsActionText3": { - "message": "Start survey", - "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." + "notificationsTitle1": { + "message": "Now Swap tokens directly in your wallet!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." }, - "notEnoughGas": { - "message": "Not Enough Gas" + "notificationsTitle2": { + "message": "MetaMask Mobile is here!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." + }, + "notificationsTitle3": { + "message": "Help improve MetaMask", + "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." }, "ofTextNofM": { "message": "of" diff --git a/ui/app/components/ui/popover/popover.component.js b/ui/app/components/ui/popover/popover.component.js index b8e591daa531..158e7c0bdc07 100644 --- a/ui/app/components/ui/popover/popover.component.js +++ b/ui/app/components/ui/popover/popover.component.js @@ -86,6 +86,9 @@ Popover.propTypes = { className: PropTypes.string, showArrow: PropTypes.bool, mediumHeight: PropTypes.bool, + contentRef: PropTypes.shape({ + current: PropTypes.instanceOf(window.Element), + }), }; export default class PopoverPortal extends PureComponent { diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index ae42ad967d36..102a3500a837 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -372,7 +372,7 @@ export function setThreeBoxLastUpdated(lastUpdated) { }; } -export function hideShowWhatsNewPopup() { +export function hideWhatsNewPopup() { return { type: actionConstants.HIDE_SHOW_WHATS_NEW_POPUP, }; diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index bf122101f36a..faf86eb91634 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -74,7 +74,7 @@ export default class Home extends PureComponent { pendingConfirmations: PropTypes.arrayOf(PropTypes.object).isRequired, infuraBlocked: PropTypes.bool.isRequired, showWhatsNewPopup: PropTypes.bool.isRequired, - hideShowWhatsNewPopup: PropTypes.func.isRequired, + hideWhatsNewPopup: PropTypes.func.isRequired, notificationsToShow: PropTypes.bool.isRequired, }; @@ -324,7 +324,7 @@ export default class Home extends PureComponent { isPopup, notificationsToShow, showWhatsNewPopup, - hideShowWhatsNewPopup, + hideWhatsNewPopup, } = this.props; if (forgottenPassword) { @@ -343,7 +343,7 @@ export default class Home extends PureComponent { />
{notificationsToShow && showWhatsNewPopup ? ( - + ) : null} {isPopup && !connectedStatusPopoverHasBeenShown ? this.renderPopover() diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index 75b4f683d5a9..6020c151059e 100644 --- a/ui/app/pages/home/home.container.js +++ b/ui/app/pages/home/home.container.js @@ -13,6 +13,7 @@ import { unconfirmedTransactionsCountSelector, getInfuraBlocked, getShowWhatsNewPopup, + getSortedNotificationsToShow, } from '../../selectors'; import { @@ -25,10 +26,7 @@ import { setWeb3ShimUsageAlertDismissed, setAlertEnabledness, } from '../../store/actions'; -import { - setThreeBoxLastUpdated, - hideShowWhatsNewPopup, -} from '../../ducks/app/app'; +import { setThreeBoxLastUpdated, hideWhatsNewPopup } from '../../ducks/app/app'; import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask'; import { getSwapsFeatureLiveness } from '../../ducks/swaps/swaps'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; @@ -40,7 +38,6 @@ import { ALERT_TYPES, WEB3_SHIM_USAGE_ALERT_STATES, } from '../../../../shared/constants/alerts'; -import { getSortedNotificationsToShow } from '../../../../shared/notifications'; import Home from './home.component'; const mapStateToProps = (state) => { @@ -132,7 +129,7 @@ const mapDispatchToProps = (dispatch) => ({ setWeb3ShimUsageAlertDismissed(origin), disableWeb3ShimUsageAlert: () => setAlertEnabledness(ALERT_TYPES.web3ShimUsage, false), - hideShowWhatsNewPopup: () => dispatch(hideShowWhatsNewPopup()), + hideWhatsNewPopup: () => dispatch(hideWhatsNewPopup()), }); export default compose( diff --git a/ui/app/pages/swaps/build-quote/build-quote.js b/ui/app/pages/swaps/build-quote/build-quote.js index 8fb8be820fa1..e4ed9578c53c 100644 --- a/ui/app/pages/swaps/build-quote/build-quote.js +++ b/ui/app/pages/swaps/build-quote/build-quote.js @@ -4,7 +4,6 @@ import { useDispatch, useSelector } from 'react-redux'; import { useLocation, useHistory } from 'react-router-dom'; import classnames from 'classnames'; import { uniqBy, isEqual } from 'lodash'; -import { useHistory } from 'react-router-dom'; import { createCustomTokenTrackerLink, createTokenTrackerLinkForChain, From 45779eedf8a91840bece39b16477655d683c33b4 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 19 Mar 2021 12:00:20 -0230 Subject: [PATCH 10/48] Fix build quote reference to swapsEthToken, broken during rebase --- ui/app/pages/swaps/build-quote/build-quote.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/pages/swaps/build-quote/build-quote.js b/ui/app/pages/swaps/build-quote/build-quote.js index e4ed9578c53c..1d45c17da26a 100644 --- a/ui/app/pages/swaps/build-quote/build-quote.js +++ b/ui/app/pages/swaps/build-quote/build-quote.js @@ -131,8 +131,8 @@ export default function BuildQuote({ const queryFromToken = queryFromAddress && - (queryFromAddress === swapsEthToken.address - ? swapsEthToken + (isSwapsDefaultTokenAddress(queryFromAddress, chainId) + ? defaultSwapsToken : memoizedUsersTokens.find( (token) => token.address === queryFromAddress, )); From c83cd717576dfd7ae4ec51bb8c2e7c2c04bb6014 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 19 Mar 2021 12:31:25 -0230 Subject: [PATCH 11/48] Rename notificationFilters to notificationToExclude to clarify its purpose --- ui/app/selectors/selectors.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 50648d9becbe..74802def1b1e 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -500,7 +500,7 @@ export function getShowWhatsNewPopup(state) { return state.appState.showWhatsNewPopup; } -function getNotificationFilters(state) { +function getNotificationToExclude(state) { const currentNetworkIsMainnet = getIsMainnet(state); const swapsIsEnabled = getSwapsFeatureLiveness(state); @@ -511,10 +511,10 @@ function getNotificationFilters(state) { export function getSortedNotificationsToShow(state) { const notifications = Object.values(state.metamask.notifications) || []; - const notificationFilters = getNotificationFilters(state); + const notificationToExclude = getNotificationToExclude(state); const notificationsToShow = notifications.filter( (notification) => - !notification.isShown && !notificationFilters[notification.id], + !notification.isShown && !notificationToExclude[notification.id], ); const notificationsSortedByDate = notificationsToShow.sort( (a, b) => new Date(b.date) - new Date(a.date), From c317bc89ec9357e4740bfdd82a51b898989d4ba2 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 19 Mar 2021 12:31:40 -0230 Subject: [PATCH 12/48] Documentation for getSortedNotificationsToShow --- ui/app/selectors/selectors.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 74802def1b1e..a8b3bae92094 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -509,6 +509,25 @@ function getNotificationToExclude(state) { }; } +/** + * @typedef {Object} Notification + * @property {number} id - A unique identifier for the notification + * @property {string} date - A date in YYYY-MM-DD format, identifying when the notification was first committed + */ + +/** + * Notifications are managed by the notification controller and referenced by + * `state.metamask.notifications`. This function returns a list of notifications + * the can be shown to the user. This list includes all notifications that do not + * have a truthy `isShown` property, and also which are not filtered out due to + * conditions encoded in the `getNotificationToExclude` function. + * + * The returned notifcations are sorted by date. + * + * @param {object} state - the redux state object + * @returns {Notification[]} An array of notifications that can be shown to the user + */ + export function getSortedNotificationsToShow(state) { const notifications = Object.values(state.metamask.notifications) || []; const notificationToExclude = getNotificationToExclude(state); From 42a12c6bda68cbe90a8403dea6eed1c4c3d8d15b Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 23 Mar 2021 06:45:59 -0230 Subject: [PATCH 13/48] Move notification action functions from shared/ to whats-new-popup.js --- shared/notifications/index.js | 34 -------------- .../app/whats-new-popup/whats-new-popup.js | 46 ++++++++++++++----- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index e935f6d73af2..d3e45e183e57 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,6 +1,3 @@ -import { ETH_SWAPS_TOKEN_OBJECT } from '../constants/swaps'; -import { BUILD_QUOTE_ROUTE } from '../../ui/app/helpers/constants/routes'; - // Messages and descriptions for these locale keys are in app/_locales/en/messages.json export const UI_NOTIFICATIONS = { 1: { @@ -26,34 +23,3 @@ export const UI_NOTIFICATIONS = { actionText: 'notificationsActionText3', }, }; - -export function notifcationActionFunctions(metricsEvent) { - const actionFunctions = { - 1: () => { - metricsEvent({ - event: 'Swaps Opened', - properties: { source: 'Main View', active_currency: 'ETH' }, - category: 'swaps', - }); - global.platform.openExtensionInBrowser( - BUILD_QUOTE_ROUTE, - `fromAddress=${ETH_SWAPS_TOKEN_OBJECT.address}`, - ); - }, - 2: () => { - global.platform.openTab({ - url: 'https://metamask.io/download.html', - }); - }, - 3: () => { - global.platform.openTab({ - url: - 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', - }); - }, - }; - - return (id) => { - return actionFunctions[id]; - }; -} diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 4048fc4679cd..d7838a7392b9 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -8,19 +8,45 @@ import { MetaMetricsContext } from '../../../contexts/metametrics.new'; import Button from '../../ui/button'; import Popover from '../../ui/popover'; import { updateViewedNotifications } from '../../../store/actions'; -import { - UI_NOTIFICATIONS, - notifcationActionFunctions, -} from '../../../../../shared/notifications'; +import { UI_NOTIFICATIONS } from '../../../../../shared/notifications'; +import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../../shared/constants/swaps'; +import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; import { getSortedNotificationsToShow } from '../../../selectors'; +function getActionFunctions(metricsEvent) { + const actionFunctions = { + 1: () => { + metricsEvent({ + event: 'Swaps Opened', + properties: { source: 'Main View', active_currency: 'ETH' }, + category: 'swaps', + }); + global.platform.openExtensionInBrowser( + BUILD_QUOTE_ROUTE, + `fromAddress=${ETH_SWAPS_TOKEN_OBJECT.address}`, + ); + }, + 2: () => { + global.platform.openTab({ + url: 'https://metamask.io/download.html', + }); + }, + 3: () => { + global.platform.openTab({ + url: + 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', + }); + }, + }; + + return actionFunctions; +} + export default function WhatsNewPopup({ onClose }) { const metricsEvent = useContext(MetaMetricsContext); const t = useContext(I18nContext); - const getNotifcationActionFunctionsById = notifcationActionFunctions( - metricsEvent, - ); + const actionFunctions = getActionFunctions(metricsEvent); const notifications = useSelector(getSortedNotificationsToShow); @@ -103,9 +129,7 @@ export default function WhatsNewPopup({ onClose }) { type="secondary" className="whats-new-popup__button" rounded - onClick={() => - getNotifcationActionFunctionsById(notification.id)() - } + onClick={actionFunctions[notification.id]} > {t(notification.actionText)} @@ -113,7 +137,7 @@ export default function WhatsNewPopup({ onClose }) { {!isFirstNotification && UI_NOTIFICATIONS[id].actionText && (
{t(notification.actionText)}
From e051769b2b070f9239cda6a47b1293ebdf309c41 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 23 Mar 2021 12:25:15 -0230 Subject: [PATCH 14/48] Stop setting swapsWelcomeMessageHasBeenShown to state in app-state controller --- app/scripts/controllers/app-state.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index b1c1df7cd927..261b735dca12 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -22,7 +22,6 @@ export default class AppStateController extends EventEmitter { this.store = new ObservableStore({ timeoutMinutes: 0, connectedStatusPopoverHasBeenShown: true, - swapsWelcomeMessageHasBeenShown: false, defaultHomeActiveTabName: null, ...initState, }); From 71cb104eff9ba6d4b8d457564eb381fdca7c14f0 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 23 Mar 2021 12:26:46 -0230 Subject: [PATCH 15/48] Update e2e tests for whats new popup changes --- test/e2e/fixtures/connected-state/state.json | 16 ++++++++++++++-- test/e2e/fixtures/imported-account/state.json | 16 ++++++++++++++-- test/e2e/fixtures/localization/state.json | 16 ++++++++++++++-- test/e2e/fixtures/metrics-enabled/state.json | 16 ++++++++++++++-- test/e2e/metamask-ui.spec.js | 18 ++++++++++++++++++ test/e2e/tests/from-import-ui.spec.js | 5 +++++ test/e2e/tests/incremental-security.spec.js | 7 +++++++ .../components/ui/popover/popover.component.js | 1 + 8 files changed, 87 insertions(+), 8 deletions(-) diff --git a/test/e2e/fixtures/connected-state/state.json b/test/e2e/fixtures/connected-state/state.json index dcf4624c8233..a88a31a744f1 100644 --- a/test/e2e/fixtures/connected-state/state.json +++ b/test/e2e/fixtures/connected-state/state.json @@ -1,8 +1,7 @@ { "data": { "AppStateController": { - "connectedStatusPopoverHasBeenShown": false, - "swapsWelcomeMessageHasBeenShown": true + "connectedStatusPopoverHasBeenShown": false }, "CachedBalancesController": { "cachedBalances": { @@ -41,6 +40,19 @@ }, "network": "1337" }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": false diff --git a/test/e2e/fixtures/imported-account/state.json b/test/e2e/fixtures/imported-account/state.json index 1af251b51b1d..a3d51e5d54f1 100644 --- a/test/e2e/fixtures/imported-account/state.json +++ b/test/e2e/fixtures/imported-account/state.json @@ -1,8 +1,7 @@ { "data": { "AppStateController": { - "mkrMigrationReminderTimestamp": null, - "swapsWelcomeMessageHasBeenShown": true + "mkrMigrationReminderTimestamp": null }, "CachedBalancesController": { "cachedBalances": { @@ -37,6 +36,19 @@ "type": "rpc" } }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": false diff --git a/test/e2e/fixtures/localization/state.json b/test/e2e/fixtures/localization/state.json index 8b60ffc9df0d..22d76151b0df 100644 --- a/test/e2e/fixtures/localization/state.json +++ b/test/e2e/fixtures/localization/state.json @@ -1,8 +1,7 @@ { "data": { "AppStateController": { - "mkrMigrationReminderTimestamp": null, - "swapsWelcomeMessageHasBeenShown": true + "mkrMigrationReminderTimestamp": null }, "CachedBalancesController": { "cachedBalances": { @@ -37,6 +36,19 @@ "type": "rpc" } }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": false diff --git a/test/e2e/fixtures/metrics-enabled/state.json b/test/e2e/fixtures/metrics-enabled/state.json index 4b0e52e70d38..2fa9931c5cfd 100644 --- a/test/e2e/fixtures/metrics-enabled/state.json +++ b/test/e2e/fixtures/metrics-enabled/state.json @@ -1,8 +1,7 @@ { "data": { "AppStateController": { - "connectedStatusPopoverHasBeenShown": false, - "swapsWelcomeMessageHasBeenShown": true + "connectedStatusPopoverHasBeenShown": false }, "CachedBalancesController": { "cachedBalances": { @@ -41,6 +40,19 @@ }, "network": "1337" }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": false diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 7d02db81365c..14ef136fb98c 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -137,6 +137,24 @@ describe('MetaMask', function () { }); }); + describe("Close the what's new popup", function () { + it("should show the what's new popover", async function () { + const popoverTitle = await driver.findElement( + '.popover-header__title h2', + ); + + assert.equal(await popoverTitle.getText(), "What's new"); + }); + + it("should close the what's new popup", async function () { + const popover = await driver.findElement('.popover-container'); + + await driver.clickElement('[data-testid="popover-close"]'); + + await popover.waitForElementState('hidden'); + }); + }); + describe('Show account information', function () { it('shows the QR code for the account', async function () { await driver.clickElement('[data-testid="account-options-menu-button"]'); diff --git a/test/e2e/tests/from-import-ui.spec.js b/test/e2e/tests/from-import-ui.spec.js index 4c973454ad9f..7bef782947d3 100644 --- a/test/e2e/tests/from-import-ui.spec.js +++ b/test/e2e/tests/from-import-ui.spec.js @@ -60,6 +60,11 @@ describe('Metamask Import UI', function () { tag: 'button', }); + // close the what's new popup + const popover = await driver.findElement('.popover-container'); + await driver.clickElement('[data-testid="popover-close"]'); + await popover.waitForElementState('hidden'); + // Show account information await driver.clickElement( '[data-testid="account-options-menu-button"]', diff --git a/test/e2e/tests/incremental-security.spec.js b/test/e2e/tests/incremental-security.spec.js index 573330319d0e..1705c27314ed 100644 --- a/test/e2e/tests/incremental-security.spec.js +++ b/test/e2e/tests/incremental-security.spec.js @@ -65,6 +65,13 @@ describe('Incremental Security', function () { tag: 'button', }); + // closes the what's new popup + const popover = await driver.findElement('.popover-container'); + + await driver.clickElement('[data-testid="popover-close"]'); + + await popover.waitForElementState('hidden'); + await driver.clickElement( '[data-testid="account-options-menu-button"]', ); diff --git a/ui/app/components/ui/popover/popover.component.js b/ui/app/components/ui/popover/popover.component.js index 158e7c0bdc07..16706428b91c 100644 --- a/ui/app/components/ui/popover/popover.component.js +++ b/ui/app/components/ui/popover/popover.component.js @@ -48,6 +48,7 @@ const Popover = ({
From a5a7bcf39f600c5ef2f9e760232389a73ae5abcc Mon Sep 17 00:00:00 2001 From: ryanml Date: Tue, 13 Apr 2021 01:00:42 -0700 Subject: [PATCH 16/48] Updating migration files --- app/scripts/migrations/058.js | 43 +++++++++++ app/scripts/migrations/058.test.js | 116 +++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + test/e2e/metamask-ui.spec.js | 1 + 4 files changed, 161 insertions(+) create mode 100644 app/scripts/migrations/058.js create mode 100644 app/scripts/migrations/058.test.js diff --git a/app/scripts/migrations/058.js b/app/scripts/migrations/058.js new file mode 100644 index 000000000000..7b25afc2ee7a --- /dev/null +++ b/app/scripts/migrations/058.js @@ -0,0 +1,43 @@ +import { cloneDeep } from 'lodash'; +import { UI_NOTIFICATIONS } from '../../../shared/notifications'; + +const SWAPS_NOTIFICATION_ID = 1; + +const version = 58; + +/** + * Set the new swaps notification isShown property to true if swapsWelcomeMessageHasBeenShown is true, and delete the swapsWelcomeMessageHasBeenShown property in either case + */ +export default { + version, + async migrate(originalVersionedData) { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + const state = versionedData.data; + versionedData.data = transformState(state); + return versionedData; + }, +}; + +function transformState(state) { + const { swapsWelcomeMessageHasBeenShown } = state?.AppStateController || {}; + let notifications = state.NotificationController?.notifications; + + if (swapsWelcomeMessageHasBeenShown) { + notifications = { + ...notifications, + [SWAPS_NOTIFICATION_ID]: { + ...UI_NOTIFICATIONS[SWAPS_NOTIFICATION_ID], + isShown: true, + }, + }; + state.NotificationController = { + ...state.NotificationController, + notifications, + }; + } + + delete state.AppStateController?.swapsWelcomeMessageHasBeenShown; + + return state; +} diff --git a/app/scripts/migrations/058.test.js b/app/scripts/migrations/058.test.js new file mode 100644 index 000000000000..115daf3e748f --- /dev/null +++ b/app/scripts/migrations/058.test.js @@ -0,0 +1,116 @@ +import { strict as assert } from 'assert'; +import { UI_NOTIFICATIONS } from '../../../shared/notifications'; +import migration58 from './058'; + +describe('migration #58', function () { + it('should update the version metadata', async function () { + const oldStorage = { + meta: { + version: 57, + }, + data: {}, + }; + + const newStorage = await migration58.migrate(oldStorage); + assert.deepEqual(newStorage.meta, { + version: 58, + }); + }); + + describe('setting swaps notification to shown', function () { + it(`should set the swaps notification to shown if swapsWelcomeMessageHasBeenShown is true and the notification state has not been initialized`, async function () { + const oldStorage = { + meta: {}, + data: { + AppStateController: { + swapsWelcomeMessageHasBeenShown: true, + }, + foo: 'bar', + }, + }; + const newStorage = await migration58.migrate(oldStorage); + assert.strictEqual( + newStorage.data.NotificationController.notifications[1].isShown, + true, + ); + }); + + it(`should set the swaps notification to shown if swapsWelcomeMessageHasBeenShown is true and the notification state has been initialized`, async function () { + const oldStorage = { + meta: {}, + data: { + AppStateController: { + swapsWelcomeMessageHasBeenShown: true, + }, + NotificationController: { + notifications: { + 1: { + isShown: false, + }, + 2: { + isShown: false, + }, + }, + bar: 'baz', + }, + foo: 'bar', + }, + }; + const newStorage = await migration58.migrate(oldStorage); + assert.deepEqual(newStorage.data.NotificationController, { + ...oldStorage.data.NotificationController, + notifications: { + ...oldStorage.data.NotificationController.notifications, + 1: { + ...UI_NOTIFICATIONS[1], + isShown: true, + }, + }, + }); + }); + + it(`should not set the swaps notification to shown if swapsWelcomeMessageHasBeenShown is false`, async function () { + const oldStorage = { + meta: {}, + data: { + AppStateController: { + swapsWelcomeMessageHasBeenShown: false, + }, + NotificationController: { + 1: { + fizz: 'buzz', + isShown: false, + }, + 2: { + fizz: 'buzz', + isShown: false, + }, + }, + foo: 'bar', + }, + }; + const newStorage = await migration58.migrate(oldStorage); + assert.deepEqual( + newStorage.data.NotificationController, + oldStorage.data.NotificationController, + ); + }); + }); + + describe('deleting swapsWelcomeMessageHasBeenShown', function () { + it('should delete the swapsWelcomeMessageHasBeenShown property', async function () { + const oldStorage = { + meta: {}, + data: { + AppStateController: { + swapsWelcomeMessageHasBeenShown: false, + bar: 'baz', + }, + foo: 'bar', + }, + }; + const newStorage = await migration58.migrate(oldStorage); + assert.deepEqual(newStorage.data.AppStateController, { bar: 'baz' }); + }); + }); +}); diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 8d56dcc85bf2..b0c1716f5310 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -62,6 +62,7 @@ const migrations = [ require('./055').default, require('./056').default, require('./057').default, + require('./058').default, ]; export default migrations; diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 14ef136fb98c..5e2ece94f4a3 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -1,4 +1,5 @@ const assert = require('assert'); +const { until } = require('selenium-webdriver'); const enLocaleMessages = require('../../app/_locales/en/messages.json'); const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers'); From 4f3621ef0dcb4ccc1bfa613371293d3b93d6362d Mon Sep 17 00:00:00 2001 From: ryanml Date: Tue, 13 Apr 2021 03:51:55 -0700 Subject: [PATCH 17/48] Addressing feedback part 1 --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 6 +++--- ui/app/selectors/selectors.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index d7838a7392b9..7c2c3f6cb46c 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -129,15 +129,15 @@ export default function WhatsNewPopup({ onClose }) { type="secondary" className="whats-new-popup__button" rounded - onClick={actionFunctions[notification.id]} + onClick={actionFunctions[id]} > {t(notification.actionText)} )} - {!isFirstNotification && UI_NOTIFICATIONS[id].actionText && ( + {!isFirstNotification && notification.actionText && (
{t(notification.actionText)}
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index a8b3bae92094..88037672fd34 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -522,7 +522,7 @@ function getNotificationToExclude(state) { * have a truthy `isShown` property, and also which are not filtered out due to * conditions encoded in the `getNotificationToExclude` function. * - * The returned notifcations are sorted by date. + * The returned notifications are sorted by date. * * @param {object} state - the redux state object * @returns {Notification[]} An array of notifications that can be shown to the user From 9bc6bf5a9fcb652b623dab65e9927773338bc230 Mon Sep 17 00:00:00 2001 From: ryanml Date: Tue, 20 Apr 2021 23:20:34 -0700 Subject: [PATCH 18/48] Addressing feedback part 2 --- .../app/whats-new-popup/whats-new-popup.js | 63 ++++++++++++------- ui/app/ducks/app/app.js | 4 +- ui/app/store/actionConstants.js | 2 +- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 7c2c3f6cb46c..5de1dd14ea47 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -65,6 +65,41 @@ export default function WhatsNewPopup({ onClose }) { [memoizedNotifications], ); + const maybeRenderNotificationItem = (id, isFirstNotification, property) => { + const notification = UI_NOTIFICATIONS[id]; + if (isFirstNotification && property === 'image') { + return ( + + ); + } + + if (isFirstNotification && property === 'actionText') { + return ( + + ); + } + + if (!isFirstNotification && property === 'actionText') { + return ( +
+ {t(notification[property])} +
+ ); + } + + return null; + }; + return (
- {isFirstNotification && notification.image && ( - - )} + {maybeRenderNotificationItem(id, isFirstNotification, 'image')}
{t(notification.title)}
@@ -124,23 +154,10 @@ export default function WhatsNewPopup({ onClose }) { {date}
- {isFirstNotification && notification.actionText && ( - - )} - {!isFirstNotification && notification.actionText && ( -
- {t(notification.actionText)} -
+ {maybeRenderNotificationItem( + id, + isFirstNotification, + 'actionText', )}
); diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js index 102a3500a837..20bab88f7213 100644 --- a/ui/app/ducks/app/app.js +++ b/ui/app/ducks/app/app.js @@ -353,7 +353,7 @@ export default function reduceApp(state = {}, action) { currentWindowTab: action.value, }; - case actionConstants.HIDE_SHOW_WHATS_NEW_POPUP: + case actionConstants.HIDE_WHATS_NEW_POPUP: return { ...appState, showWhatsNewPopup: false, @@ -374,6 +374,6 @@ export function setThreeBoxLastUpdated(lastUpdated) { export function hideWhatsNewPopup() { return { - type: actionConstants.HIDE_SHOW_WHATS_NEW_POPUP, + type: actionConstants.HIDE_WHATS_NEW_POPUP, }; } diff --git a/ui/app/store/actionConstants.js b/ui/app/store/actionConstants.js index 6ef2c51f4a04..8613318c8ff1 100644 --- a/ui/app/store/actionConstants.js +++ b/ui/app/store/actionConstants.js @@ -114,4 +114,4 @@ export const SET_CURRENT_WINDOW_TAB = 'SET_CURRENT_WINDOW_TAB'; export const SET_OPEN_METAMASK_TAB_IDS = 'SET_OPEN_METAMASK_TAB_IDS'; // Home Screen -export const HIDE_SHOW_WHATS_NEW_POPUP = 'HIDE_SHOW_WHATS_NEW_POPUP'; +export const HIDE_WHATS_NEW_POPUP = 'HIDE_WHATS_NEW_POPUP'; From 1bc843d94e2432a1d38190e72aabb6b4aa65ca5c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 10:57:09 -0230 Subject: [PATCH 19/48] Remove unnecessary div in whats-new-popup --- .../app/whats-new-popup/whats-new-popup.js | 116 +++++++++--------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 5de1dd14ea47..8de898404ad5 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -101,70 +101,66 @@ export default function WhatsNewPopup({ onClose }) { }; return ( -
- { - const { - bottom: containerBottom, - } = contentRef.current.getBoundingClientRect(); - - const currentlySeenNotifications = {}; - Object.keys(idRefMap).forEach((notificationId) => { - const { bottom: descriptionBottom } = idRefMap[ - notificationId - ].current.getBoundingClientRect(); - - if (descriptionBottom < containerBottom) { - currentlySeenNotifications[notificationId] = true; - } - }); - - updateViewedNotifications(currentlySeenNotifications); - - onClose(); - }} - contentRef={contentRef} - mediumHeight - > -
- {notifications.map(({ id, date }, index) => { - const notification = UI_NOTIFICATIONS[id]; - const isFirstNotification = index === 0; - return ( + { + const { + bottom: containerBottom, + } = contentRef.current.getBoundingClientRect(); + + const currentlySeenNotifications = {}; + Object.keys(idRefMap).forEach((notificationId) => { + const { bottom: descriptionBottom } = idRefMap[ + notificationId + ].current.getBoundingClientRect(); + + if (descriptionBottom < containerBottom) { + currentlySeenNotifications[notificationId] = true; + } + }); + + updateViewedNotifications(currentlySeenNotifications); + + onClose(); + }} + contentRef={contentRef} + mediumHeight + > +
+ {notifications.map(({ id, date }, index) => { + const notification = UI_NOTIFICATIONS[id]; + const isFirstNotification = index === 0; + return ( +
+ {maybeRenderNotificationItem(id, isFirstNotification, 'image')} +
+ {t(notification.title)} +
- {maybeRenderNotificationItem(id, isFirstNotification, 'image')} -
- {t(notification.title)} -
-
-
- {t(notification.description)} -
-
- {date} -
+
+ {t(notification.description)}
- {maybeRenderNotificationItem( - id, - isFirstNotification, - 'actionText', - )} +
{date}
- ); - })} -
- -
+ {maybeRenderNotificationItem( + id, + isFirstNotification, + 'actionText', + )} +
+ ); + })} +
+
); } From b6f68d035e5924a0dfbd561826e28172e79adea0 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 11:04:36 -0230 Subject: [PATCH 20/48] Change getNotificationsToExclude to getNotificationsToInclude for use in the getSortedNotificationsToShow selector --- ui/app/selectors/selectors.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 88037672fd34..d1b64e8e8375 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -500,12 +500,14 @@ export function getShowWhatsNewPopup(state) { return state.appState.showWhatsNewPopup; } -function getNotificationToExclude(state) { +function getNotificationsToInclude(state) { const currentNetworkIsMainnet = getIsMainnet(state); const swapsIsEnabled = getSwapsFeatureLiveness(state); return { - 1: !currentNetworkIsMainnet || !swapsIsEnabled, + 1: currentNetworkIsMainnet && swapsIsEnabled, + 2: true, + 3: true, }; } @@ -520,7 +522,7 @@ function getNotificationToExclude(state) { * `state.metamask.notifications`. This function returns a list of notifications * the can be shown to the user. This list includes all notifications that do not * have a truthy `isShown` property, and also which are not filtered out due to - * conditions encoded in the `getNotificationToExclude` function. + * conditions encoded in the `getNotificationsToInclude` function. * * The returned notifications are sorted by date. * @@ -530,10 +532,10 @@ function getNotificationToExclude(state) { export function getSortedNotificationsToShow(state) { const notifications = Object.values(state.metamask.notifications) || []; - const notificationToExclude = getNotificationToExclude(state); + const notificationToExclude = getNotificationsToInclude(state); const notificationsToShow = notifications.filter( (notification) => - !notification.isShown && !notificationToExclude[notification.id], + !notification.isShown && notificationToExclude[notification.id], ); const notificationsSortedByDate = notificationsToShow.sort( (a, b) => new Date(b.date) - new Date(a.date), From f8d333dca825e491918e51333b896b1926fc26a7 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 11:08:11 -0230 Subject: [PATCH 21/48] Delete intro-popup directory and test files --- .../__snapshots__/intro-popup.test.js.snap | 9 ------- .../swaps/intro-popup/intro-popup.test.js | 24 ------------------- 2 files changed, 33 deletions(-) delete mode 100644 ui/app/pages/swaps/intro-popup/__snapshots__/intro-popup.test.js.snap delete mode 100644 ui/app/pages/swaps/intro-popup/intro-popup.test.js diff --git a/ui/app/pages/swaps/intro-popup/__snapshots__/intro-popup.test.js.snap b/ui/app/pages/swaps/intro-popup/__snapshots__/intro-popup.test.js.snap deleted file mode 100644 index d9d0324df2bc..000000000000 --- a/ui/app/pages/swaps/intro-popup/__snapshots__/intro-popup.test.js.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`IntroPopup renders the component with initial props 1`] = ` -
-
-
-`; diff --git a/ui/app/pages/swaps/intro-popup/intro-popup.test.js b/ui/app/pages/swaps/intro-popup/intro-popup.test.js deleted file mode 100644 index 2ceec7e0eacd..000000000000 --- a/ui/app/pages/swaps/intro-popup/intro-popup.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import configureMockStore from 'redux-mock-store'; - -import { - renderWithProvider, - createSwapsMockStore, -} from '../../../../../test/jest'; -import IntroPopup from '.'; - -const createProps = (customProps = {}) => { - return { - onClose: jest.fn(), - ...customProps, - }; -}; - -describe('IntroPopup', () => { - it('renders the component with initial props', () => { - const store = configureMockStore()(createSwapsMockStore()); - const props = createProps(); - const { container } = renderWithProvider(, store); - expect(container).toMatchSnapshot(); - }); -}); From e6115456c807da8211016fbef4b20ae932c78669 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 11:09:10 -0230 Subject: [PATCH 22/48] Lint fix --- test/e2e/metamask-ui.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 5e2ece94f4a3..14ef136fb98c 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -1,5 +1,4 @@ const assert = require('assert'); -const { until } = require('selenium-webdriver'); const enLocaleMessages = require('../../app/_locales/en/messages.json'); const { tinyDelayMs, regularDelayMs, largeDelayMs } = require('./helpers'); From cbcd78589b759e72c1471c91655bad26a8b504f6 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 11:36:46 -0230 Subject: [PATCH 23/48] Add notifiction state to address-entry fixture --- test/e2e/fixtures/address-entry/state.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/e2e/fixtures/address-entry/state.json b/test/e2e/fixtures/address-entry/state.json index 26da39502601..e53e3a9224cf 100644 --- a/test/e2e/fixtures/address-entry/state.json +++ b/test/e2e/fixtures/address-entry/state.json @@ -50,6 +50,19 @@ "type": "rpc" } }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": false From 1fdee5ed0a63d8faff9d56af33a957b0c082a9bf Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 12:00:27 -0230 Subject: [PATCH 24/48] Use two separate functions for rendering first and subsequent notifications in the whats-new-popup --- .../app/whats-new-popup/whats-new-popup.js | 119 +++++++++--------- 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 8de898404ad5..0e7e5e012832 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -65,39 +65,69 @@ export default function WhatsNewPopup({ onClose }) { [memoizedNotifications], ); - const maybeRenderNotificationItem = (id, isFirstNotification, property) => { - const notification = UI_NOTIFICATIONS[id]; - if (isFirstNotification && property === 'image') { - return ( - - ); - } - - if (isFirstNotification && property === 'actionText') { - return ( - - ); - } +
+ {t(notification.description)} +
+
{date}
+
+ {notification.actionText && ( + + )} +
+ ); + }; - if (!isFirstNotification && property === 'actionText') { - return ( + const renderSubsequentNotification = (notification, id, date, index) => { + return ( +
+
+ {t(notification.title)} +
+
+
+ {t(notification.description)} +
+
{date}
+
- {t(notification[property])} + {t(notification.actionText)}
- ); - } - - return null; +
+ ); }; return ( @@ -130,34 +160,9 @@ export default function WhatsNewPopup({ onClose }) {
{notifications.map(({ id, date }, index) => { const notification = UI_NOTIFICATIONS[id]; - const isFirstNotification = index === 0; - return ( -
- {maybeRenderNotificationItem(id, isFirstNotification, 'image')} -
- {t(notification.title)} -
-
-
- {t(notification.description)} -
-
{date}
-
- {maybeRenderNotificationItem( - id, - isFirstNotification, - 'actionText', - )} -
- ); + return index === 0 + ? renderFirstNotification(notification, id, date) + : renderSubsequentNotification(notification, id, date, index); })}
From 782d9c9ab254268400d94f80e44ec30e8764ed4c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 12:06:15 -0230 Subject: [PATCH 25/48] Ensure that string literals are passed to t for whats new popup text --- shared/notifications/index.js | 23 +++++++++++++++++++ .../app/whats-new-popup/whats-new-popup.js | 16 ++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index d3e45e183e57..bcfea33267ee 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -23,3 +23,26 @@ export const UI_NOTIFICATIONS = { actionText: 'notificationsActionText3', }, }; + +export const getTranslatedUINoficiations = (t) => { + return { + 1: { + ...UI_NOTIFICATIONS[1], + title: t('notificationsTitle1'), + description: t('notificationsDescription1'), + actionText: t('notificationsActionText1'), + }, + 2: { + ...UI_NOTIFICATIONS[2], + title: t('notificationsTitle2'), + description: t('notificationsDescription2'), + actionText: t('notificationsActionText2'), + }, + 3: { + ...UI_NOTIFICATIONS[3], + title: t('notificationsTitle3'), + description: t('notificationsDescription3'), + actionText: t('notificationsActionText3'), + }, + }; +}; diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 0e7e5e012832..0dc5fba4c20f 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -8,7 +8,7 @@ import { MetaMetricsContext } from '../../../contexts/metametrics.new'; import Button from '../../ui/button'; import Popover from '../../ui/popover'; import { updateViewedNotifications } from '../../../store/actions'; -import { UI_NOTIFICATIONS } from '../../../../../shared/notifications'; +import { getTranslatedUINoficiations } from '../../../../../shared/notifications'; import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../../shared/constants/swaps'; import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; import { getSortedNotificationsToShow } from '../../../selectors'; @@ -80,14 +80,14 @@ export default function WhatsNewPopup({ onClose }) { /> )}
- {t(notification.title)} + {notification.title}
- {t(notification.description)} + {notification.description}
{date}
@@ -98,7 +98,7 @@ export default function WhatsNewPopup({ onClose }) { rounded onClick={actionFunctions[id]} > - {t(notification.actionText)} + {notification.actionText} )}
@@ -112,19 +112,19 @@ export default function WhatsNewPopup({ onClose }) { key={`whats-new-popop-notificatiion-${index}`} >
- {t(notification.title)} + {notification.title}
- {t(notification.description)} + {notification.description}
{date}
- {t(notification.actionText)} + {notification.actionText}
); @@ -159,7 +159,7 @@ export default function WhatsNewPopup({ onClose }) { >
{notifications.map(({ id, date }, index) => { - const notification = UI_NOTIFICATIONS[id]; + const notification = getTranslatedUINoficiations(t)[id]; return index === 0 ? renderFirstNotification(notification, id, date) : renderSubsequentNotification(notification, id, date, index); From e74929ef27d950787c5ba90fff6881698885887c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 23 Apr 2021 18:28:13 -0230 Subject: [PATCH 26/48] Update import-ui fixtures to include notificaiton controller state --- test/e2e/fixtures/import-ui/state.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/e2e/fixtures/import-ui/state.json b/test/e2e/fixtures/import-ui/state.json index 34f2e1152a48..350ed88488a8 100644 --- a/test/e2e/fixtures/import-ui/state.json +++ b/test/e2e/fixtures/import-ui/state.json @@ -91,6 +91,19 @@ }, "network": "1337" }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "CurrencyController": { "conversionDate": 1618940438.187, "conversionRate": 2254.54, From 1ecbe205ab3eea968ec8d8c8e3a6ec7e2f557997 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 26 Apr 2021 08:56:36 -0230 Subject: [PATCH 27/48] Remove unnecessary, accidental change confirm-approve --- ui/app/pages/confirm-approve/confirm-approve.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/pages/confirm-approve/confirm-approve.js b/ui/app/pages/confirm-approve/confirm-approve.js index 8624af9c4df8..9eb51bd2ad46 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.js +++ b/ui/app/pages/confirm-approve/confirm-approve.js @@ -124,7 +124,6 @@ export default function ConfirmApprove() { toAddress={toAddress} identiconAddress={tokenAddress} showAccountInHeader - useNonceField title={tokensText} contentComponent={ Date: Mon, 26 Apr 2021 10:31:52 -0230 Subject: [PATCH 28/48] Remove swaps notification in favour of mobile swaps as first notifcation and TBD 3rd notification --- app/_locales/en/messages.json | 36 ++++----- app/scripts/migrations/058.js | 22 +---- app/scripts/migrations/058.test.js | 81 ------------------- app/scripts/platforms/extension.js | 18 ++--- .../app/whats-new-popup/whats-new-popup.js | 24 ++---- ui/app/pages/swaps/build-quote/build-quote.js | 22 +---- ui/app/selectors/selectors.js | 19 +---- 7 files changed, 35 insertions(+), 187 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 954e9a985091..006bb5418737 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1236,41 +1236,41 @@ "message": "Not Enough Gas" }, "notificationsActionText1": { - "message": "Start swapping", - "description": "The 'call to action' label on the button, or link, of the swap 'See What's New' notification. Upon clicking, users will be taken to the swaps page." - }, - "notificationsActionText2": { - "message": "Get the mobile app", + "message": "Get the mobile app and start swapping", "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." }, - "notificationsActionText3": { + "notificationsActionText2": { "message": "Start survey", "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." }, + "notificationsActionText3": { + "message": "Placeholder (change before merge)", + "description": "The 'call to action'" + }, "notificationsDescription1": { - "message": "MetaMask now aggregates multiple decentralized exchange aggregators to ensure you always get the best swap price with the lowest netwrok fees.", - "description": "Description of a notification in the 'See What's New' popup. Describes the swap feature to users." + "message": "MetaMask Mobile users can now swap tokens inside their wallet, with a single swipe, using the Swaps feature.", + "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." }, "notificationsDescription2": { - "message": "Sync with your extension wallet in seconds. Scan the QR code with your phone camera to download the app.", - "description": "Description of a notification in the 'See What's New' popup. Describes how to sync their MetaMask extension wallet with the mobile wallet." - }, - "notificationsDescription3": { "message": "Please share your experience in this 5 minute survey.", "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." }, + "notificationsDescription3": { + "message": "Placeholder for security message (change before merge)", + "description": "Description of a notification in the 'See What's New' popup." + }, "notificationsTitle1": { - "message": "Now Swap tokens directly in your wallet!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that the swap feature is available in MetaMask." + "message": "Swapping on mobile is here!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." }, "notificationsTitle2": { - "message": "MetaMask Mobile is here!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that the can now get MetaMask on Mobile." - }, - "notificationsTitle3": { "message": "Help improve MetaMask", "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." }, + "notificationsTitle3": { + "message": "Placeholder (change before merge)", + "description": "Title for a notification in the 'See What's New' popup." + }, "ofTextNofM": { "message": "of" }, diff --git a/app/scripts/migrations/058.js b/app/scripts/migrations/058.js index 7b25afc2ee7a..247d11bf7dc3 100644 --- a/app/scripts/migrations/058.js +++ b/app/scripts/migrations/058.js @@ -1,12 +1,9 @@ import { cloneDeep } from 'lodash'; -import { UI_NOTIFICATIONS } from '../../../shared/notifications'; - -const SWAPS_NOTIFICATION_ID = 1; const version = 58; /** - * Set the new swaps notification isShown property to true if swapsWelcomeMessageHasBeenShown is true, and delete the swapsWelcomeMessageHasBeenShown property in either case + * Deletes the swapsWelcomeMessageHasBeenShown property from state */ export default { version, @@ -20,23 +17,6 @@ export default { }; function transformState(state) { - const { swapsWelcomeMessageHasBeenShown } = state?.AppStateController || {}; - let notifications = state.NotificationController?.notifications; - - if (swapsWelcomeMessageHasBeenShown) { - notifications = { - ...notifications, - [SWAPS_NOTIFICATION_ID]: { - ...UI_NOTIFICATIONS[SWAPS_NOTIFICATION_ID], - isShown: true, - }, - }; - state.NotificationController = { - ...state.NotificationController, - notifications, - }; - } - delete state.AppStateController?.swapsWelcomeMessageHasBeenShown; return state; diff --git a/app/scripts/migrations/058.test.js b/app/scripts/migrations/058.test.js index 115daf3e748f..65478a39f904 100644 --- a/app/scripts/migrations/058.test.js +++ b/app/scripts/migrations/058.test.js @@ -1,5 +1,4 @@ import { strict as assert } from 'assert'; -import { UI_NOTIFICATIONS } from '../../../shared/notifications'; import migration58 from './058'; describe('migration #58', function () { @@ -17,86 +16,6 @@ describe('migration #58', function () { }); }); - describe('setting swaps notification to shown', function () { - it(`should set the swaps notification to shown if swapsWelcomeMessageHasBeenShown is true and the notification state has not been initialized`, async function () { - const oldStorage = { - meta: {}, - data: { - AppStateController: { - swapsWelcomeMessageHasBeenShown: true, - }, - foo: 'bar', - }, - }; - const newStorage = await migration58.migrate(oldStorage); - assert.strictEqual( - newStorage.data.NotificationController.notifications[1].isShown, - true, - ); - }); - - it(`should set the swaps notification to shown if swapsWelcomeMessageHasBeenShown is true and the notification state has been initialized`, async function () { - const oldStorage = { - meta: {}, - data: { - AppStateController: { - swapsWelcomeMessageHasBeenShown: true, - }, - NotificationController: { - notifications: { - 1: { - isShown: false, - }, - 2: { - isShown: false, - }, - }, - bar: 'baz', - }, - foo: 'bar', - }, - }; - const newStorage = await migration58.migrate(oldStorage); - assert.deepEqual(newStorage.data.NotificationController, { - ...oldStorage.data.NotificationController, - notifications: { - ...oldStorage.data.NotificationController.notifications, - 1: { - ...UI_NOTIFICATIONS[1], - isShown: true, - }, - }, - }); - }); - - it(`should not set the swaps notification to shown if swapsWelcomeMessageHasBeenShown is false`, async function () { - const oldStorage = { - meta: {}, - data: { - AppStateController: { - swapsWelcomeMessageHasBeenShown: false, - }, - NotificationController: { - 1: { - fizz: 'buzz', - isShown: false, - }, - 2: { - fizz: 'buzz', - isShown: false, - }, - }, - foo: 'bar', - }, - }; - const newStorage = await migration58.migrate(oldStorage); - assert.deepEqual( - newStorage.data.NotificationController, - oldStorage.data.NotificationController, - ); - }); - }); - describe('deleting swapsWelcomeMessageHasBeenShown', function () { it('should delete the swapsWelcomeMessageHasBeenShown property', async function () { const oldStorage = { diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index be8ff5798f4c..0eaeefaa8188 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -82,26 +82,18 @@ export default class ExtensionPlatform { return extension.runtime.getManifest().version; } - openExtensionInBrowser( - route = null, - queryString = null, - keepCurrentTabOpen = false, - ) { + openExtensionInBrowser(route = null, queryString = null) { let extensionURL = extension.runtime.getURL('home.html'); - if (route) { - extensionURL += `#${route}`; - } - if (queryString) { extensionURL += `?${queryString}`; } + if (route) { + extensionURL += `#${route}`; + } this.openTab({ url: extensionURL }); - if ( - getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND && - !keepCurrentTabOpen - ) { + if (getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND) { window.close(); } } diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 0dc5fba4c20f..b49e65c187c6 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -4,37 +4,28 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { I18nContext } from '../../../contexts/i18n'; import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; -import { MetaMetricsContext } from '../../../contexts/metametrics.new'; import Button from '../../ui/button'; import Popover from '../../ui/popover'; import { updateViewedNotifications } from '../../../store/actions'; import { getTranslatedUINoficiations } from '../../../../../shared/notifications'; -import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../../shared/constants/swaps'; -import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; import { getSortedNotificationsToShow } from '../../../selectors'; -function getActionFunctions(metricsEvent) { +function getActionFunctions() { const actionFunctions = { 1: () => { - metricsEvent({ - event: 'Swaps Opened', - properties: { source: 'Main View', active_currency: 'ETH' }, - category: 'swaps', + global.platform.openTab({ + url: 'https://metamask.io/download.html', }); - global.platform.openExtensionInBrowser( - BUILD_QUOTE_ROUTE, - `fromAddress=${ETH_SWAPS_TOKEN_OBJECT.address}`, - ); }, 2: () => { global.platform.openTab({ - url: 'https://metamask.io/download.html', + url: + 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', }); }, 3: () => { global.platform.openTab({ - url: - 'https://survey.alchemer.com/s3/6173069/MetaMask-Extension-NPS-January-2021', + url: 'https://community.metamask.io/t/about-the-security-category/72', }); }, }; @@ -43,10 +34,9 @@ function getActionFunctions(metricsEvent) { } export default function WhatsNewPopup({ onClose }) { - const metricsEvent = useContext(MetaMetricsContext); const t = useContext(I18nContext); - const actionFunctions = getActionFunctions(metricsEvent); + const actionFunctions = getActionFunctions(); const notifications = useSelector(getSortedNotificationsToShow); diff --git a/ui/app/pages/swaps/build-quote/build-quote.js b/ui/app/pages/swaps/build-quote/build-quote.js index 1d45c17da26a..d93ab6a9b59b 100644 --- a/ui/app/pages/swaps/build-quote/build-quote.js +++ b/ui/app/pages/swaps/build-quote/build-quote.js @@ -1,9 +1,9 @@ import React, { useContext, useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import { useLocation, useHistory } from 'react-router-dom'; import classnames from 'classnames'; import { uniqBy, isEqual } from 'lodash'; +import { useHistory } from 'react-router-dom'; import { createCustomTokenTrackerLink, createTokenTrackerLinkForChain, @@ -87,10 +87,6 @@ export default function BuildQuote({ ); const [verificationClicked, setVerificationClicked] = useState(false); - const { search } = useLocation(); - const urlParams = new window.URLSearchParams(search); - const queryFromAddress = urlParams.get('fromAddress'); - const balanceError = useSelector(getBalanceError); const fetchParams = useSelector(getFetchParams); const { sourceTokenInfo = {}, destinationTokenInfo = {} } = @@ -129,22 +125,8 @@ export default function BuildQuote({ ); const memoizedUsersTokens = useEqualityCheck(usersTokens); - const queryFromToken = - queryFromAddress && - (isSwapsDefaultTokenAddress(queryFromAddress, chainId) - ? defaultSwapsToken - : memoizedUsersTokens.find( - (token) => token.address === queryFromAddress, - )); - - const providedFromToken = [ - fromToken, - fetchParamsFromToken, - queryFromToken, - ].find((token) => token?.address); - const selectedFromToken = getRenderableTokenData( - providedFromToken || {}, + fromToken || fetchParamsFromToken, tokenConversionRates, conversionRate, currentCurrency, diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index d1b64e8e8375..967a1006a4e0 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -24,7 +24,6 @@ import { } from '../helpers/utils/conversions.util'; import { TEMPLATED_CONFIRMATION_MESSAGE_TYPES } from '../pages/confirmation/templates'; -import { getSwapsFeatureLiveness } from '../ducks/swaps/swaps'; import { getNativeCurrency } from './send'; @@ -500,17 +499,6 @@ export function getShowWhatsNewPopup(state) { return state.appState.showWhatsNewPopup; } -function getNotificationsToInclude(state) { - const currentNetworkIsMainnet = getIsMainnet(state); - const swapsIsEnabled = getSwapsFeatureLiveness(state); - - return { - 1: currentNetworkIsMainnet && swapsIsEnabled, - 2: true, - 3: true, - }; -} - /** * @typedef {Object} Notification * @property {number} id - A unique identifier for the notification @@ -521,8 +509,7 @@ function getNotificationsToInclude(state) { * Notifications are managed by the notification controller and referenced by * `state.metamask.notifications`. This function returns a list of notifications * the can be shown to the user. This list includes all notifications that do not - * have a truthy `isShown` property, and also which are not filtered out due to - * conditions encoded in the `getNotificationsToInclude` function. + * have a truthy `isShown` property. * * The returned notifications are sorted by date. * @@ -532,10 +519,8 @@ function getNotificationsToInclude(state) { export function getSortedNotificationsToShow(state) { const notifications = Object.values(state.metamask.notifications) || []; - const notificationToExclude = getNotificationsToInclude(state); const notificationsToShow = notifications.filter( - (notification) => - !notification.isShown && notificationToExclude[notification.id], + (notification) => !notification.isShown, ); const notificationsSortedByDate = notificationsToShow.sort( (a, b) => new Date(b.date) - new Date(a.date), From 1d51a8f3c50f47715be8b3fc8d0fc6672754dbf6 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 26 Apr 2021 17:32:58 -0230 Subject: [PATCH 29/48] Update whats-new-popup to use intersection observer api to detect if notification has been seen --- .../app/whats-new-popup/whats-new-popup.js | 66 +++++++++++-------- .../ui/popover/popover.component.js | 10 ++- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index b49e65c187c6..526161cb3cc2 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -1,4 +1,4 @@ -import React, { useContext, useMemo, useRef } from 'react'; +import React, { useContext, useMemo, useRef, useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; @@ -40,7 +40,7 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedNotificationsToShow); - const contentRef = useRef(); + const containerRef = useRef(); const memoizedNotifications = useEqualityCheck(notifications); const idRefMap = useMemo( @@ -55,6 +55,36 @@ export default function WhatsNewPopup({ onClose }) { [memoizedNotifications], ); + const [seenNotifications, setSeenNotifications] = useState({}); + const observationsCreated = useRef(false); + + useEffect(() => { + const observers = []; + observationsCreated.current = true; + + Object.entries(idRefMap).forEach(([id, ref]) => { + const observer = new window.IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setSeenNotifications((_seenNotifications) => ({ + ..._seenNotifications, + [id]: true, + })); + } + }, + { + root: containerRef.current, + threshold: 1.0, + }, + ); + observers.push(observer); + observer.observe(ref.current); + }); + return () => { + observers.forEach((observer) => observer.disconnect()); + }; + }, [idRefMap, setSeenNotifications]); + const renderFirstNotification = (notification, id, date) => { return (
{notification.image && ( {notification.title}
-
+
{notification.description}
@@ -100,14 +128,12 @@ export default function WhatsNewPopup({ onClose }) {
{notification.title}
-
+
{notification.description}
@@ -125,26 +151,10 @@ export default function WhatsNewPopup({ onClose }) { className="whats-new-popup__popover" title={t('whatsNew')} onClose={() => { - const { - bottom: containerBottom, - } = contentRef.current.getBoundingClientRect(); - - const currentlySeenNotifications = {}; - Object.keys(idRefMap).forEach((notificationId) => { - const { bottom: descriptionBottom } = idRefMap[ - notificationId - ].current.getBoundingClientRect(); - - if (descriptionBottom < containerBottom) { - currentlySeenNotifications[notificationId] = true; - } - }); - - updateViewedNotifications(currentlySeenNotifications); - + updateViewedNotifications(seenNotifications); onClose(); }} - contentRef={contentRef} + containerRef={containerRef} mediumHeight >
diff --git a/ui/app/components/ui/popover/popover.component.js b/ui/app/components/ui/popover/popover.component.js index 16706428b91c..2e21575e2a3d 100644 --- a/ui/app/components/ui/popover/popover.component.js +++ b/ui/app/components/ui/popover/popover.component.js @@ -17,7 +17,7 @@ const Popover = ({ contentClassName, showArrow, CustomBackground, - contentRef, + containerRef, }) => { const t = useI18nContext(); return ( @@ -31,6 +31,7 @@ const Popover = ({ className={classnames('popover-wrap', className, { 'popover-wrap--medium-height': mediumHeight, })} + ref={containerRef} > {showArrow ?
: null}
@@ -57,10 +58,7 @@ const Popover = ({ ) : null}
{children ? ( -
+
{children}
) : null} @@ -87,7 +85,7 @@ Popover.propTypes = { className: PropTypes.string, showArrow: PropTypes.bool, mediumHeight: PropTypes.bool, - contentRef: PropTypes.shape({ + containerRef: PropTypes.shape({ current: PropTypes.instanceOf(window.Element), }), }; From d3602fd9223e506f79bede1b6cdcd42ea7234ad4 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 26 Apr 2021 17:35:02 -0230 Subject: [PATCH 30/48] Add notifications to send-edit and threebox e2e test fixtures --- test/e2e/fixtures/send-edit/state.json | 13 +++++++++++++ test/e2e/fixtures/threebox-enabled/state.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/test/e2e/fixtures/send-edit/state.json b/test/e2e/fixtures/send-edit/state.json index 77fd20720414..c5c548f4ce96 100644 --- a/test/e2e/fixtures/send-edit/state.json +++ b/test/e2e/fixtures/send-edit/state.json @@ -37,6 +37,19 @@ "type": "rpc" } }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": false diff --git a/test/e2e/fixtures/threebox-enabled/state.json b/test/e2e/fixtures/threebox-enabled/state.json index 602b8d94ce44..8bac5f889344 100644 --- a/test/e2e/fixtures/threebox-enabled/state.json +++ b/test/e2e/fixtures/threebox-enabled/state.json @@ -47,6 +47,19 @@ }, "network": "1337" }, + "NotificationController": { + "notifications": { + "1": { + "isShown": true + }, + "2": { + "isShown": true + }, + "3": { + "isShown": true + } + } + }, "OnboardingController": { "onboardingTabs": {}, "seedPhraseBackedUp": true From 70a07caacd6d3d328420a1bddf510b1d42c354a2 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 27 Apr 2021 17:06:13 -0230 Subject: [PATCH 31/48] Update ui/app/selectors/selectors.js Co-authored-by: Mark Stacey --- ui/app/selectors/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 967a1006a4e0..30ddacfc09d6 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -513,7 +513,7 @@ export function getShowWhatsNewPopup(state) { * * The returned notifications are sorted by date. * - * @param {object} state - the redux state object + * @param {Object} state - the redux state object * @returns {Notification[]} An array of notifications that can be shown to the user */ From 85fe17aabbdbaaa016e95ca1725efc06a92e8767 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 27 Apr 2021 17:06:39 -0230 Subject: [PATCH 32/48] Update ui/app/selectors/selectors.js Co-authored-by: Mark Stacey --- ui/app/selectors/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 30ddacfc09d6..73cebf8dbc56 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -518,7 +518,7 @@ export function getShowWhatsNewPopup(state) { */ export function getSortedNotificationsToShow(state) { - const notifications = Object.values(state.metamask.notifications) || []; + const notifications = Object.values(state.metamask.notifications); const notificationsToShow = notifications.filter( (notification) => !notification.isShown, ); From a67a8072bcd6006cf9933ef27e5aa46f1d625fe7 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 17:16:41 -0230 Subject: [PATCH 33/48] Clean up locale code for whats-new-popup notifications --- app/_locales/en/messages.json | 42 +++++++++++++++++------------------ shared/notifications/index.js | 27 ++++++++-------------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 006bb5418737..f1f73fa6efb7 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1235,39 +1235,39 @@ "notEnoughGas": { "message": "Not Enough Gas" }, - "notificationsActionText1": { + "notifications1ActionText": { "message": "Get the mobile app and start swapping", "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." }, - "notificationsActionText2": { - "message": "Start survey", - "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." - }, - "notificationsActionText3": { - "message": "Placeholder (change before merge)", - "description": "The 'call to action'" - }, - "notificationsDescription1": { + "notifications1Description": { "message": "MetaMask Mobile users can now swap tokens inside their wallet, with a single swipe, using the Swaps feature.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." }, - "notificationsDescription2": { + "notifications1Title": { + "message": "Swapping on mobile is here!", + "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." + }, + "notifications2ActionText": { + "message": "Start survey", + "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." + }, + "notifications2Description": { + "notifications2Title": { + "message": "Help improve MetaMask", + "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." + }, "message": "Please share your experience in this 5 minute survey.", "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." }, - "notificationsDescription3": { + "notifications3ActionText": { + "message": "Placeholder (change before merge)", + "description": "The 'call to action'" + }, + "notifications3Description": { "message": "Placeholder for security message (change before merge)", "description": "Description of a notification in the 'See What's New' popup." }, - "notificationsTitle1": { - "message": "Swapping on mobile is here!", - "description": "Title for a notification in the 'See What's New' popup. Tells users that they can now use MetaMask Swaps on Mobile." - }, - "notificationsTitle2": { - "message": "Help improve MetaMask", - "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." - }, - "notificationsTitle3": { + "notifications3Title": { "message": "Placeholder (change before merge)", "description": "Title for a notification in the 'See What's New' popup." }, diff --git a/shared/notifications/index.js b/shared/notifications/index.js index bcfea33267ee..5a3456247e4c 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -2,25 +2,16 @@ export const UI_NOTIFICATIONS = { 1: { id: 1, - title: 'notificationsTitle1', - description: 'notificationsDescription1', date: '2020-03-17', image: 'images/swaps-logos-small.svg', - actionText: 'notificationsActionText1', }, 2: { id: 2, - title: 'notificationsTitle2', - description: 'notificationsDescription2', date: '2020-03-17', - actionText: 'notificationsActionText2', }, 3: { id: 3, - title: 'notificationsTitle3', - description: 'notificationsDescription3', date: '2020-03-17', - actionText: 'notificationsActionText3', }, }; @@ -28,21 +19,21 @@ export const getTranslatedUINoficiations = (t) => { return { 1: { ...UI_NOTIFICATIONS[1], - title: t('notificationsTitle1'), - description: t('notificationsDescription1'), - actionText: t('notificationsActionText1'), + title: t('notifications1Title'), + description: t('notifications1Description'), + actionText: t('notifications1ActionText'), }, 2: { ...UI_NOTIFICATIONS[2], - title: t('notificationsTitle2'), - description: t('notificationsDescription2'), - actionText: t('notificationsActionText2'), + title: t('notifications2Title'), + description: t('notifications2Description'), + actionText: t('notifications2ActionText'), }, 3: { ...UI_NOTIFICATIONS[3], - title: t('notificationsTitle3'), - description: t('notificationsDescription3'), - actionText: t('notificationsActionText3'), + title: t('notifications3Title'), + description: t('notifications3Description'), + actionText: t('notifications3ActionText'), }, }; }; From af2e0c6674f97d1fa1c5caa651d73da9157d7752 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 17:23:46 -0230 Subject: [PATCH 34/48] Disconnect observers in whats-new-popup when their callback is first called --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 526161cb3cc2..04df2c945139 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -64,12 +64,13 @@ export default function WhatsNewPopup({ onClose }) { Object.entries(idRefMap).forEach(([id, ref]) => { const observer = new window.IntersectionObserver( - ([entry]) => { + ([entry], _observer) => { if (entry.isIntersecting) { setSeenNotifications((_seenNotifications) => ({ ..._seenNotifications, [id]: true, })); + _observer.disconnect(); } }, { From 997588aa6f5eb3861cfafd64c76bc90d0073f734 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 17:27:44 -0230 Subject: [PATCH 35/48] Add test case for migration 58 for when the AppStateController does not exist --- app/scripts/migrations/058.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/scripts/migrations/058.test.js b/app/scripts/migrations/058.test.js index 65478a39f904..02efdcdd74aa 100644 --- a/app/scripts/migrations/058.test.js +++ b/app/scripts/migrations/058.test.js @@ -31,5 +31,16 @@ describe('migration #58', function () { const newStorage = await migration58.migrate(oldStorage); assert.deepEqual(newStorage.data.AppStateController, { bar: 'baz' }); }); + + it('should not modify state if the AppStateController does not exist', async function () { + const oldStorage = { + meta: {}, + data: { + foo: 'bar', + }, + }; + const newStorage = await migration58.migrate(oldStorage); + assert.deepEqual(newStorage.data, oldStorage.data); + }); }); }); From 49c96471a3ec0dc2d8b90ff27e7a60dffb1f59ab Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 17:30:01 -0230 Subject: [PATCH 36/48] Rename popover components containerRef to popoverWrapRef --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 6 +++--- ui/app/components/ui/popover/popover.component.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 04df2c945139..95288cfc9d63 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -40,7 +40,7 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedNotificationsToShow); - const containerRef = useRef(); + const popoverWrapRef = useRef(); const memoizedNotifications = useEqualityCheck(notifications); const idRefMap = useMemo( @@ -74,7 +74,7 @@ export default function WhatsNewPopup({ onClose }) { } }, { - root: containerRef.current, + root: popoverWrapRef.current, threshold: 1.0, }, ); @@ -155,7 +155,7 @@ export default function WhatsNewPopup({ onClose }) { updateViewedNotifications(seenNotifications); onClose(); }} - containerRef={containerRef} + popoverWrapRef={popoverWrapRef} mediumHeight >
diff --git a/ui/app/components/ui/popover/popover.component.js b/ui/app/components/ui/popover/popover.component.js index 2e21575e2a3d..17cac0068aee 100644 --- a/ui/app/components/ui/popover/popover.component.js +++ b/ui/app/components/ui/popover/popover.component.js @@ -17,7 +17,7 @@ const Popover = ({ contentClassName, showArrow, CustomBackground, - containerRef, + popoverWrapRef, }) => { const t = useI18nContext(); return ( @@ -31,7 +31,7 @@ const Popover = ({ className={classnames('popover-wrap', className, { 'popover-wrap--medium-height': mediumHeight, })} - ref={containerRef} + ref={popoverWrapRef} > {showArrow ?
: null}
@@ -85,7 +85,7 @@ Popover.propTypes = { className: PropTypes.string, showArrow: PropTypes.bool, mediumHeight: PropTypes.bool, - containerRef: PropTypes.shape({ + popoverWrapRef: PropTypes.shape({ current: PropTypes.instanceOf(window.Element), }), }; From cac9c3efc739681479f5e361f7beac22bbeb9866 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 17:38:03 -0230 Subject: [PATCH 37/48] Fix messages.json --- app/_locales/en/messages.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f1f73fa6efb7..b91650568f91 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1252,13 +1252,13 @@ "description": "The 'call to action' label on the button, or link, of the 'Help improve MetaMask' 'See What's New' notification. Upon clicking, users will be taken to an external page where they can complete a survey." }, "notifications2Description": { - "notifications2Title": { - "message": "Help improve MetaMask", - "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." - }, "message": "Please share your experience in this 5 minute survey.", "description": "Description of a notification in the 'See What's New' popup. Further clarifies how the users can help: by completing a 5 minute survey about MetaMask." }, + "notifications2Title": { + "message": "Help improve MetaMask", + "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." + }, "notifications3ActionText": { "message": "Placeholder (change before merge)", "description": "The 'call to action'" From 63128a1f7df65e45f3c75f5d6f5750959191c35c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 20:10:00 -0230 Subject: [PATCH 38/48] Update notification messages and images --- app/_locales/en/messages.json | 18 +++--- app/images/mobile-link-qr.svg | 1 + app/images/swaps-logos-small.svg | 101 ------------------------------- shared/notifications/index.js | 3 +- 4 files changed, 9 insertions(+), 114 deletions(-) create mode 100644 app/images/mobile-link-qr.svg delete mode 100644 app/images/swaps-logos-small.svg diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b91650568f91..b82bfe7b0b93 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1235,12 +1235,8 @@ "notEnoughGas": { "message": "Not Enough Gas" }, - "notifications1ActionText": { - "message": "Get the mobile app and start swapping", - "description": "The 'call to action' label on the button, or link, of the mobile 'See What's New' notification. Upon clicking, users will be taken to the downloads page on the MetaMask website." - }, "notifications1Description": { - "message": "MetaMask Mobile users can now swap tokens inside their wallet, with a single swipe, using the Swaps feature.", + "message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.", "description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature." }, "notifications1Title": { @@ -1260,16 +1256,16 @@ "description": "Title for a notification in the 'See What's New' popup. Asks users to take action to make MetaMask better." }, "notifications3ActionText": { - "message": "Placeholder (change before merge)", - "description": "The 'call to action'" + "message": "Read more", + "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." }, "notifications3Description": { - "message": "Placeholder for security message (change before merge)", - "description": "Description of a notification in the 'See What's New' popup." + "message": "Stay up to date on MetaMask security best practices and get the latest security tips from official MetaMask support.", + "description": "Description of a notification in the 'See What's New' popup. Describes the information they can get on security from the linked support page." }, "notifications3Title": { - "message": "Placeholder (change before merge)", - "description": "Title for a notification in the 'See What's New' popup." + "message": "Stay secure", + "description": "Title for a notification in the 'See What's New' popup. Encourages users to consider security." }, "ofTextNofM": { "message": "of" diff --git a/app/images/mobile-link-qr.svg b/app/images/mobile-link-qr.svg new file mode 100644 index 000000000000..3141bbe5fa94 --- /dev/null +++ b/app/images/mobile-link-qr.svg @@ -0,0 +1 @@ + diff --git a/app/images/swaps-logos-small.svg b/app/images/swaps-logos-small.svg deleted file mode 100644 index a484d7db0134..000000000000 --- a/app/images/swaps-logos-small.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 5a3456247e4c..19e0f57f4a0f 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -3,7 +3,7 @@ export const UI_NOTIFICATIONS = { 1: { id: 1, date: '2020-03-17', - image: 'images/swaps-logos-small.svg', + image: 'images/mobile-link-qr.svg', }, 2: { id: 2, @@ -21,7 +21,6 @@ export const getTranslatedUINoficiations = (t) => { ...UI_NOTIFICATIONS[1], title: t('notifications1Title'), description: t('notifications1Description'), - actionText: t('notifications1ActionText'), }, 2: { ...UI_NOTIFICATIONS[2], From 1cb1827e3c8cd8781aa60b50cb0c8e8d08f7d7b7 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 20:18:12 -0230 Subject: [PATCH 39/48] Rename popoverWrapRef -> popoverRef in whats-new-popup and popover.component --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 6 +++--- ui/app/components/ui/popover/popover.component.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 95288cfc9d63..f0f99a5e58fd 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -40,7 +40,7 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedNotificationsToShow); - const popoverWrapRef = useRef(); + const popoverRef = useRef(); const memoizedNotifications = useEqualityCheck(notifications); const idRefMap = useMemo( @@ -74,7 +74,7 @@ export default function WhatsNewPopup({ onClose }) { } }, { - root: popoverWrapRef.current, + root: popoverRef.current, threshold: 1.0, }, ); @@ -155,7 +155,7 @@ export default function WhatsNewPopup({ onClose }) { updateViewedNotifications(seenNotifications); onClose(); }} - popoverWrapRef={popoverWrapRef} + popoverRef={popoverRef} mediumHeight >
diff --git a/ui/app/components/ui/popover/popover.component.js b/ui/app/components/ui/popover/popover.component.js index 17cac0068aee..1c5c0dd1a9d0 100644 --- a/ui/app/components/ui/popover/popover.component.js +++ b/ui/app/components/ui/popover/popover.component.js @@ -17,7 +17,7 @@ const Popover = ({ contentClassName, showArrow, CustomBackground, - popoverWrapRef, + popoverRef, }) => { const t = useI18nContext(); return ( @@ -31,7 +31,7 @@ const Popover = ({ className={classnames('popover-wrap', className, { 'popover-wrap--medium-height': mediumHeight, })} - ref={popoverWrapRef} + ref={popoverRef} > {showArrow ?
: null}
@@ -85,7 +85,7 @@ Popover.propTypes = { className: PropTypes.string, showArrow: PropTypes.bool, mediumHeight: PropTypes.bool, - popoverWrapRef: PropTypes.shape({ + popoverRef: PropTypes.shape({ current: PropTypes.instanceOf(window.Element), }), }; From 233b70e5e82db08930ccdbb7bb786b76ac865f80 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 27 Apr 2021 22:06:06 -0230 Subject: [PATCH 40/48] Only create one observer, and only after images have loaded, in whats-new-popup --- .../app/whats-new-popup/whats-new-popup.js | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index f0f99a5e58fd..2abb9bdd661d 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -40,6 +40,9 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedNotificationsToShow); + const [seenNotifications, setSeenNotifications] = useState({}); + const [imageLoaded, setImageLoaded] = useState(!notifications[0].image); + const popoverRef = useRef(); const memoizedNotifications = useEqualityCheck(notifications); @@ -55,36 +58,41 @@ export default function WhatsNewPopup({ onClose }) { [memoizedNotifications], ); - const [seenNotifications, setSeenNotifications] = useState({}); - const observationsCreated = useRef(false); - useEffect(() => { - const observers = []; - observationsCreated.current = true; - - Object.entries(idRefMap).forEach(([id, ref]) => { - const observer = new window.IntersectionObserver( - ([entry], _observer) => { - if (entry.isIntersecting) { - setSeenNotifications((_seenNotifications) => ({ - ..._seenNotifications, - [id]: true, - })); - _observer.disconnect(); - } + let observer; + if (imageLoaded) { + observer = new window.IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const [id, ref] = Object.entries(idRefMap).find(([_, _ref]) => + _ref.current.isSameNode(entry.target), + ); + + setSeenNotifications((_seenNotifications) => ({ + ..._seenNotifications, + [id]: true, + })); + + _observer.unobserve(ref.current); + } + }); }, { root: popoverRef.current, threshold: 1.0, }, ); - observers.push(observer); - observer.observe(ref.current); - }); + + Object.values(idRefMap).forEach((ref) => { + observer.observe(ref.current); + }); + } + return () => { - observers.forEach((observer) => observer.disconnect()); + observer?.disconnect(); }; - }, [idRefMap, setSeenNotifications]); + }, [idRefMap, setSeenNotifications, imageLoaded]); const renderFirstNotification = (notification, id, date) => { return ( @@ -99,6 +107,7 @@ export default function WhatsNewPopup({ onClose }) { setImageLoaded(true)} /> )}
From e805cf2fc9229db195c6b9efe0c1169adcc390d3 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 28 Apr 2021 10:26:19 -0230 Subject: [PATCH 41/48] Set width and height on whats-new-popup image, instead of setting state on img load --- shared/notifications/index.js | 6 +- .../app/whats-new-popup/whats-new-popup.js | 63 +++++++++---------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 19e0f57f4a0f..482ce510bc01 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -3,7 +3,11 @@ export const UI_NOTIFICATIONS = { 1: { id: 1, date: '2020-03-17', - image: 'images/mobile-link-qr.svg', + image: { + src: 'images/mobile-link-qr.svg', + height: '270px', + width: '270px', + }, }, 2: { id: 2, diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 2abb9bdd661d..f1ba22a6003a 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -41,7 +41,6 @@ export default function WhatsNewPopup({ onClose }) { const notifications = useSelector(getSortedNotificationsToShow); const [seenNotifications, setSeenNotifications] = useState({}); - const [imageLoaded, setImageLoaded] = useState(!notifications[0].image); const popoverRef = useRef(); @@ -59,40 +58,37 @@ export default function WhatsNewPopup({ onClose }) { ); useEffect(() => { - let observer; - if (imageLoaded) { - observer = new window.IntersectionObserver( - (entries, _observer) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const [id, ref] = Object.entries(idRefMap).find(([_, _ref]) => - _ref.current.isSameNode(entry.target), - ); - - setSeenNotifications((_seenNotifications) => ({ - ..._seenNotifications, - [id]: true, - })); - - _observer.unobserve(ref.current); - } - }); - }, - { - root: popoverRef.current, - threshold: 1.0, - }, - ); - - Object.values(idRefMap).forEach((ref) => { - observer.observe(ref.current); - }); - } + const observer = new window.IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const [id, ref] = Object.entries(idRefMap).find(([_, _ref]) => + _ref.current.isSameNode(entry.target), + ); + + setSeenNotifications((_seenNotifications) => ({ + ..._seenNotifications, + [id]: true, + })); + + _observer.unobserve(ref.current); + } + }); + }, + { + root: popoverRef.current, + threshold: 1.0, + }, + ); + + Object.values(idRefMap).forEach((ref) => { + observer.observe(ref.current); + }); return () => { observer?.disconnect(); }; - }, [idRefMap, setSeenNotifications, imageLoaded]); + }, [idRefMap, setSeenNotifications]); const renderFirstNotification = (notification, id, date) => { return ( @@ -106,8 +102,9 @@ export default function WhatsNewPopup({ onClose }) { {notification.image && ( setImageLoaded(true)} + src={notification.image.src} + height={notification.image.height} + width={notification.image.width} /> )}
From 4fbab19ee44d05e11ed2355d1ea2498957fb885b Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 28 Apr 2021 11:02:35 -0230 Subject: [PATCH 42/48] Update ui/app/components/app/whats-new-popup/whats-new-popup.js Co-authored-by: Mark Stacey --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index f1ba22a6003a..92b0e1c331ed 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -86,7 +86,7 @@ export default function WhatsNewPopup({ onClose }) { }); return () => { - observer?.disconnect(); + observer.disconnect(); }; }, [idRefMap, setSeenNotifications]); From b4849b5021ce8caa4dc33d1501684e99d9bdcb50 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 28 Apr 2021 11:10:46 -0230 Subject: [PATCH 43/48] Code clean up in whats new popup re: notification rendering and action functions --- .../app/whats-new-popup/whats-new-popup.js | 157 +++++++++--------- 1 file changed, 83 insertions(+), 74 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 92b0e1c331ed..c7b688b3bba8 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -10,13 +10,8 @@ import { updateViewedNotifications } from '../../../store/actions'; import { getTranslatedUINoficiations } from '../../../../../shared/notifications'; import { getSortedNotificationsToShow } from '../../../selectors'; -function getActionFunctions() { +function getActionFunctionById(id) { const actionFunctions = { - 1: () => { - global.platform.openTab({ - url: 'https://metamask.io/download.html', - }); - }, 2: () => { global.platform.openTab({ url: @@ -30,14 +25,85 @@ function getActionFunctions() { }, }; - return actionFunctions; + return actionFunctions[id]; } +const renderFirstNotification = ({ notification, id, date, idRefMap }) => { + const actionFunction = getActionFunctionById(id); + return ( +
+ {notification.image && ( + + )} +
+ {notification.title} +
+
+
+ {notification.description} +
+
{date}
+
+ {notification.actionText && ( + + )} +
+ ); +}; + +const renderSubsequentNotification = ({ + notification, + id, + date, + index, + idRefMap, +}) => { + const actionFunction = getActionFunctionById(id); + return ( +
+
+ {notification.title} +
+
+
+ {notification.description} +
+
{date}
+
+ {notification.actionText && ( +
+ {notification.actionText} +
+ )} +
+ ); +}; + export default function WhatsNewPopup({ onClose }) { const t = useContext(I18nContext); - const actionFunctions = getActionFunctions(); - const notifications = useSelector(getSortedNotificationsToShow); const [seenNotifications, setSeenNotifications] = useState({}); @@ -90,69 +156,6 @@ export default function WhatsNewPopup({ onClose }) { }; }, [idRefMap, setSeenNotifications]); - const renderFirstNotification = (notification, id, date) => { - return ( -
- {notification.image && ( - - )} -
- {notification.title} -
-
-
- {notification.description} -
-
{date}
-
- {notification.actionText && ( - - )} -
- ); - }; - - const renderSubsequentNotification = (notification, id, date, index) => { - return ( -
-
- {notification.title} -
-
-
- {notification.description} -
-
{date}
-
-
- {notification.actionText} -
-
- ); - }; - return ( { const notification = getTranslatedUINoficiations(t)[id]; return index === 0 - ? renderFirstNotification(notification, id, date) - : renderSubsequentNotification(notification, id, date, index); + ? renderFirstNotification({ notification, id, date, idRefMap }) + : renderSubsequentNotification({ + notification, + id, + date, + index, + idRefMap, + }); })}
From e51ea34a97595e8936ce28175ff0d7197fe3726b Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 28 Apr 2021 11:27:58 -0230 Subject: [PATCH 44/48] Code cleanup in render notification functions of whats-new-popup --- .../app/whats-new-popup/whats-new-popup.js | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index c7b688b3bba8..b30e3e0ad3ef 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -28,73 +28,66 @@ function getActionFunctionById(id) { return actionFunctions[id]; } -const renderFirstNotification = ({ notification, id, date, idRefMap }) => { +const renderFirstNotification = (notification, idRefMap) => { + const { id, date, title, description, image, actionText } = notification; const actionFunction = getActionFunctionById(id); return (
- {notification.image && ( + {image && ( )} -
- {notification.title} -
+
{title}
- {notification.description} + {description}
{date}
- {notification.actionText && ( + {actionText && ( )}
); }; -const renderSubsequentNotification = ({ - notification, - id, - date, - index, - idRefMap, -}) => { +const renderSubsequentNotification = (notification, idRefMap) => { + const { id, date, title, description, actionText } = notification; + const actionFunction = getActionFunctionById(id); return (
-
- {notification.title} -
+
{title}
- {notification.description} + {description}
{date}
- {notification.actionText && ( + {actionText && (
- {notification.actionText} + {actionText}
)}
@@ -168,15 +161,12 @@ export default function WhatsNewPopup({ onClose }) { mediumHeight >
- {notifications.map(({ id, date }, index) => { + {notifications.map(({ id }, index) => { const notification = getTranslatedUINoficiations(t)[id]; return index === 0 - ? renderFirstNotification({ notification, id, date, idRefMap }) + ? renderFirstNotification(notification, idRefMap) : renderSubsequentNotification({ notification, - id, - date, - index, idRefMap, }); })} From 9ceff95773652ea54f4e355cc7c59ca9ef7fa3cb Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 28 Apr 2021 11:34:21 -0230 Subject: [PATCH 45/48] Update ui/app/components/app/whats-new-popup/whats-new-popup.js Co-authored-by: Mark Stacey --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index b30e3e0ad3ef..907435e203ac 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -165,10 +165,10 @@ export default function WhatsNewPopup({ onClose }) { const notification = getTranslatedUINoficiations(t)[id]; return index === 0 ? renderFirstNotification(notification, idRefMap) - : renderSubsequentNotification({ + : renderSubsequentNotification( notification, idRefMap, - }); + ); })}
From 190fbcb00802b4f45c70ec2fcbc9919b94399774 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 28 Apr 2021 11:44:27 -0230 Subject: [PATCH 46/48] lint fix --- ui/app/components/app/whats-new-popup/whats-new-popup.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 907435e203ac..79ac33ba79e4 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -165,10 +165,7 @@ export default function WhatsNewPopup({ onClose }) { const notification = getTranslatedUINoficiations(t)[id]; return index === 0 ? renderFirstNotification(notification, idRefMap) - : renderSubsequentNotification( - notification, - idRefMap, - ); + : renderSubsequentNotification(notification, idRefMap); })}
From 7b120a4af4ea16be7130c6573399cac41f5e810a Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 28 Apr 2021 12:40:12 -0230 Subject: [PATCH 47/48] Update and localize notification dates --- shared/notifications/index.js | 17 +++++++++++++---- .../app/whats-new-popup/whats-new-popup.js | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 482ce510bc01..6a240e205e18 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,8 +1,14 @@ +const notificationDates = { + 1: new Date(2021, 2, 17), + 2: new Date(2020, 7, 31), + 3: new Date(2021, 2, 8), +}; + // Messages and descriptions for these locale keys are in app/_locales/en/messages.json export const UI_NOTIFICATIONS = { 1: { id: 1, - date: '2020-03-17', + date: notificationDates[1].toString(), image: { src: 'images/mobile-link-qr.svg', height: '270px', @@ -11,32 +17,35 @@ export const UI_NOTIFICATIONS = { }, 2: { id: 2, - date: '2020-03-17', + date: notificationDates[2].toString(), }, 3: { id: 3, - date: '2020-03-17', + date: notificationDates[3].toString(), }, }; -export const getTranslatedUINoficiations = (t) => { +export const getTranslatedUINoficiations = (t, locale) => { return { 1: { ...UI_NOTIFICATIONS[1], title: t('notifications1Title'), description: t('notifications1Description'), + date: new Intl.DateTimeFormat(locale).format(notificationDates[1]), }, 2: { ...UI_NOTIFICATIONS[2], title: t('notifications2Title'), description: t('notifications2Description'), actionText: t('notifications2ActionText'), + date: new Intl.DateTimeFormat(locale).format(notificationDates[2]), }, 3: { ...UI_NOTIFICATIONS[3], title: t('notifications3Title'), description: t('notifications3Description'), actionText: t('notifications3ActionText'), + date: new Intl.DateTimeFormat(locale).format(notificationDates[3]), }, }; }; diff --git a/ui/app/components/app/whats-new-popup/whats-new-popup.js b/ui/app/components/app/whats-new-popup/whats-new-popup.js index 79ac33ba79e4..4849be291672 100644 --- a/ui/app/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/app/components/app/whats-new-popup/whats-new-popup.js @@ -2,6 +2,7 @@ import React, { useContext, useMemo, useRef, useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import { getCurrentLocale } from '../../../ducks/metamask/metamask'; import { I18nContext } from '../../../contexts/i18n'; import { useEqualityCheck } from '../../../hooks/useEqualityCheck'; import Button from '../../ui/button'; @@ -98,6 +99,7 @@ export default function WhatsNewPopup({ onClose }) { const t = useContext(I18nContext); const notifications = useSelector(getSortedNotificationsToShow); + const locale = useSelector(getCurrentLocale); const [seenNotifications, setSeenNotifications] = useState({}); @@ -162,7 +164,7 @@ export default function WhatsNewPopup({ onClose }) { >
{notifications.map(({ id }, index) => { - const notification = getTranslatedUINoficiations(t)[id]; + const notification = getTranslatedUINoficiations(t, locale)[id]; return index === 0 ? renderFirstNotification(notification, idRefMap) : renderSubsequentNotification(notification, idRefMap); From 507263b3ec0f748486f7fafd5d803f52d41e6546 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Wed, 28 Apr 2021 13:55:00 -0230 Subject: [PATCH 48/48] Clean up date code in shred/notifications/index.js --- shared/notifications/index.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 6a240e205e18..a85cb0f4cbc9 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -1,14 +1,8 @@ -const notificationDates = { - 1: new Date(2021, 2, 17), - 2: new Date(2020, 7, 31), - 3: new Date(2021, 2, 8), -}; - // Messages and descriptions for these locale keys are in app/_locales/en/messages.json export const UI_NOTIFICATIONS = { 1: { id: 1, - date: notificationDates[1].toString(), + date: '2021-03-17', image: { src: 'images/mobile-link-qr.svg', height: '270px', @@ -17,11 +11,11 @@ export const UI_NOTIFICATIONS = { }, 2: { id: 2, - date: notificationDates[2].toString(), + date: '2020-08-31', }, 3: { id: 3, - date: notificationDates[3].toString(), + date: '2021-03-8', }, }; @@ -31,21 +25,27 @@ export const getTranslatedUINoficiations = (t, locale) => { ...UI_NOTIFICATIONS[1], title: t('notifications1Title'), description: t('notifications1Description'), - date: new Intl.DateTimeFormat(locale).format(notificationDates[1]), + date: new Intl.DateTimeFormat(locale).format( + new Date(UI_NOTIFICATIONS[1].date), + ), }, 2: { ...UI_NOTIFICATIONS[2], title: t('notifications2Title'), description: t('notifications2Description'), actionText: t('notifications2ActionText'), - date: new Intl.DateTimeFormat(locale).format(notificationDates[2]), + date: new Intl.DateTimeFormat(locale).format( + new Date(UI_NOTIFICATIONS[2].date), + ), }, 3: { ...UI_NOTIFICATIONS[3], title: t('notifications3Title'), description: t('notifications3Description'), actionText: t('notifications3ActionText'), - date: new Intl.DateTimeFormat(locale).format(notificationDates[3]), + date: new Intl.DateTimeFormat(locale).format( + new Date(UI_NOTIFICATIONS[3].date), + ), }, }; };