import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Redirect, Route } from 'react-router-dom'; ///: BEGIN:ONLY_INCLUDE_IN(main) import { SUPPORT_LINK } from '../../helpers/constants/common'; import { EVENT, EVENT_NAMES, CONTEXT_PROPS, } from '../../../shared/constants/metametrics'; ///: END:ONLY_INCLUDE_IN import { formatDate } from '../../helpers/utils/util'; import AssetList from '../../components/app/asset-list'; import CollectiblesTab from '../../components/app/collectibles-tab'; import HomeNotification from '../../components/app/home-notification'; import MultipleNotifications from '../../components/app/multiple-notifications'; import TransactionList from '../../components/app/transaction-list'; import MenuBar from '../../components/app/menu-bar'; import Popover from '../../components/ui/popover'; import Button from '../../components/ui/button'; import Box from '../../components/ui/box'; import ConnectedSites from '../connected-sites'; import ConnectedAccounts from '../connected-accounts'; import { Tabs, Tab } from '../../components/ui/tabs'; import { EthOverview } from '../../components/app/wallet-overview'; import WhatsNewPopup from '../../components/app/whats-new-popup'; import RecoveryPhraseReminder from '../../components/app/recovery-phrase-reminder'; import ActionableMessage from '../../components/ui/actionable-message/actionable-message'; import Typography from '../../components/ui/typography/typography'; import { TYPOGRAPHY, FONT_WEIGHT, DISPLAY, COLORS, } from '../../helpers/constants/design-system'; import { ASSET_ROUTE, RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, CONNECT_ROUTE, CONNECTED_ROUTE, CONNECTED_ACCOUNTS_ROUTE, AWAITING_SWAP_ROUTE, BUILD_QUOTE_ROUTE, VIEW_QUOTE_ROUTE, CONFIRMATION_V_NEXT_ROUTE, ADD_COLLECTIBLE_ROUTE, } from '../../helpers/constants/routes'; import ZENDESK_URLS from '../../helpers/constants/zendesk-url'; ///: BEGIN:ONLY_INCLUDE_IN(beta) import BetaHomeFooter from './beta/beta-home-footer.component'; ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(flask) import FlaskHomeFooter from './flask/flask-home-footer.component'; ///: END:ONLY_INCLUDE_IN const LEARN_MORE_URL = 'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension'; function shouldCloseNotificationPopup({ isNotification, totalUnapprovedCount, isSigningQRHardwareTransaction, }) { return ( isNotification && totalUnapprovedCount === 0 && !isSigningQRHardwareTransaction ); } export default class Home extends PureComponent { static contextTypes = { t: PropTypes.func, trackEvent: PropTypes.func, }; static propTypes = { history: PropTypes.object, forgottenPassword: PropTypes.bool, suggestedAssets: PropTypes.array, unconfirmedTransactionsCount: PropTypes.number, shouldShowSeedPhraseReminder: PropTypes.bool.isRequired, isPopup: PropTypes.bool, isNotification: PropTypes.bool.isRequired, threeBoxSynced: PropTypes.bool, setupThreeBox: PropTypes.func, turnThreeBoxSyncingOn: PropTypes.func, showRestorePrompt: PropTypes.bool, selectedAddress: PropTypes.string, restoreFromThreeBox: PropTypes.func, setShowRestorePromptToFalse: PropTypes.func, threeBoxLastUpdated: PropTypes.number, firstPermissionsRequestId: PropTypes.string, // This prop is used in the `shouldCloseNotificationPopup` function // eslint-disable-next-line react/no-unused-prop-types totalUnapprovedCount: PropTypes.number.isRequired, setConnectedStatusPopoverHasBeenShown: PropTypes.func, connectedStatusPopoverHasBeenShown: PropTypes.bool, defaultHomeActiveTabName: PropTypes.string, firstTimeFlowType: PropTypes.string, completedOnboarding: PropTypes.bool, onTabClick: PropTypes.func.isRequired, haveSwapsQuotes: PropTypes.bool.isRequired, showAwaitingSwapScreen: PropTypes.bool.isRequired, swapsFetchParams: PropTypes.object, shouldShowWeb3ShimUsageNotification: PropTypes.bool.isRequired, setWeb3ShimUsageAlertDismissed: PropTypes.func.isRequired, originOfCurrentTab: PropTypes.string, disableWeb3ShimUsageAlert: PropTypes.func.isRequired, pendingConfirmations: PropTypes.arrayOf(PropTypes.object).isRequired, infuraBlocked: PropTypes.bool.isRequired, showWhatsNewPopup: PropTypes.bool.isRequired, hideWhatsNewPopup: PropTypes.func.isRequired, announcementsToShow: PropTypes.bool.isRequired, ///: BEGIN:ONLY_INCLUDE_IN(flask) errorsToShow: PropTypes.object.isRequired, shouldShowErrors: PropTypes.bool.isRequired, removeSnapError: PropTypes.func.isRequired, ///: END:ONLY_INCLUDE_IN showRecoveryPhraseReminder: PropTypes.bool.isRequired, setRecoveryPhraseReminderHasBeenShown: PropTypes.func.isRequired, setRecoveryPhraseReminderLastShown: PropTypes.func.isRequired, seedPhraseBackedUp: (props) => { if ( props.seedPhraseBackedUp !== null && typeof props.seedPhraseBackedUp !== 'boolean' ) { throw new Error( `seedPhraseBackedUp is required to be null or boolean. Received ${props.seedPhraseBackedUp}`, ); } }, newNetworkAdded: PropTypes.string, setNewNetworkAdded: PropTypes.func.isRequired, // This prop is used in the `shouldCloseNotificationPopup` function // eslint-disable-next-line react/no-unused-prop-types isSigningQRHardwareTransaction: PropTypes.bool.isRequired, newCollectibleAddedMessage: PropTypes.string, setNewCollectibleAddedMessage: PropTypes.func.isRequired, closeNotificationPopup: PropTypes.func.isRequired, newTokensImported: PropTypes.string, setNewTokensImported: PropTypes.func.isRequired, newCustomNetworkAdded: PropTypes.object, clearNewCustomNetworkAdded: PropTypes.func, setRpcTarget: PropTypes.func, }; state = { canShowBlockageNotification: true, notificationClosing: false, redirecting: false, }; constructor(props) { super(props); const { closeNotificationPopup, firstPermissionsRequestId, haveSwapsQuotes, isNotification, showAwaitingSwapScreen, suggestedAssets = [], swapsFetchParams, unconfirmedTransactionsCount, } = this.props; if (shouldCloseNotificationPopup(props)) { this.state.notificationClosing = true; closeNotificationPopup(); } else if ( firstPermissionsRequestId || unconfirmedTransactionsCount > 0 || suggestedAssets.length > 0 || (!isNotification && (showAwaitingSwapScreen || haveSwapsQuotes || swapsFetchParams)) ) { this.state.redirecting = true; } } checkStatusAndNavigate() { const { firstPermissionsRequestId, history, isNotification, suggestedAssets = [], unconfirmedTransactionsCount, haveSwapsQuotes, showAwaitingSwapScreen, swapsFetchParams, pendingConfirmations, } = this.props; if (!isNotification && showAwaitingSwapScreen) { history.push(AWAITING_SWAP_ROUTE); } else if (!isNotification && haveSwapsQuotes) { history.push(VIEW_QUOTE_ROUTE); } else if (!isNotification && swapsFetchParams) { history.push(BUILD_QUOTE_ROUTE); } else if (firstPermissionsRequestId) { history.push(`${CONNECT_ROUTE}/${firstPermissionsRequestId}`); } else if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE); } else if (suggestedAssets.length > 0) { history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE); } else if (pendingConfirmations.length > 0) { history.push(CONFIRMATION_V_NEXT_ROUTE); } } componentDidMount() { this.checkStatusAndNavigate(); } static getDerivedStateFromProps(props) { if (shouldCloseNotificationPopup(props)) { return { notificationClosing: true }; } return null; } componentDidUpdate(_prevProps, prevState) { const { closeNotificationPopup, setupThreeBox, showRestorePrompt, threeBoxLastUpdated, threeBoxSynced, isNotification, } = this.props; const { notificationClosing } = this.state; if (notificationClosing && !prevState.notificationClosing) { closeNotificationPopup(); } else if (isNotification) { this.checkStatusAndNavigate(); } else if ( threeBoxSynced && showRestorePrompt && threeBoxLastUpdated === null ) { setupThreeBox(); } } onRecoveryPhraseReminderClose = () => { const { setRecoveryPhraseReminderHasBeenShown, setRecoveryPhraseReminderLastShown, } = this.props; setRecoveryPhraseReminderHasBeenShown(true); setRecoveryPhraseReminderLastShown(new Date().getTime()); }; renderNotifications() { const { t } = this.context; const { history, shouldShowSeedPhraseReminder, isPopup, selectedAddress, restoreFromThreeBox, turnThreeBoxSyncingOn, setShowRestorePromptToFalse, showRestorePrompt, threeBoxLastUpdated, shouldShowWeb3ShimUsageNotification, setWeb3ShimUsageAlertDismissed, originOfCurrentTab, disableWeb3ShimUsageAlert, ///: BEGIN:ONLY_INCLUDE_IN(flask) removeSnapError, errorsToShow, shouldShowErrors, ///: END:ONLY_INCLUDE_IN infuraBlocked, newNetworkAdded, setNewNetworkAdded, newCollectibleAddedMessage, setNewCollectibleAddedMessage, newTokensImported, setNewTokensImported, newCustomNetworkAdded, clearNewCustomNetworkAdded, setRpcTarget, } = this.props; return ( { ///: BEGIN:ONLY_INCLUDE_IN(flask) shouldShowErrors ? Object.entries(errorsToShow).map(([errorId, error]) => { return ( {t('somethingWentWrong')} {t('snapError', [error.message, error.code])} } onIgnore={async () => { await removeSnapError(errorId); }} ignoreText="Dismiss" key="home-error-message" /> ); }) : null ///: END:ONLY_INCLUDE_IN } {newCollectibleAddedMessage === 'success' ? ( {t('newCollectibleAddedMessage')} )} ); } renderPopover = () => { const { setConnectedStatusPopoverHasBeenShown } = this.props; const { t } = this.context; return ( { return (
); }} footer={ <> {t('learnMore')} } >
{t('metaMaskConnectStatusParagraphOne')}
{t('metaMaskConnectStatusParagraphTwo')}
{t('metaMaskConnectStatusParagraphThree')}
); }; render() { const { t } = this.context; const { defaultHomeActiveTabName, onTabClick, forgottenPassword, history, connectedStatusPopoverHasBeenShown, isPopup, announcementsToShow, showWhatsNewPopup, hideWhatsNewPopup, seedPhraseBackedUp, showRecoveryPhraseReminder, firstTimeFlowType, completedOnboarding, } = this.props; if (forgottenPassword) { return ; } else if (this.state.notificationClosing || this.state.redirecting) { return null; } const showWhatsNew = ((completedOnboarding && firstTimeFlowType === 'import') || !completedOnboarding) && announcementsToShow && showWhatsNewPopup; return (
{showWhatsNew ? : null} {!showWhatsNew && showRecoveryPhraseReminder ? ( ) : null} {isPopup && !connectedStatusPopoverHasBeenShown ? this.renderPopover() : null}
history.push(`${ASSET_ROUTE}/${asset}`) } /> {process.env.COLLECTIBLES_V1 ? ( { history.push(ADD_COLLECTIBLE_ROUTE); }} /> ) : null}
{ ///: BEGIN:ONLY_INCLUDE_IN(main) t('needHelp', [ { this.context.trackEvent( { category: EVENT.CATEGORIES.HOME, event: EVENT_NAMES.SUPPORT_LINK_CLICKED, properties: { url: SUPPORT_LINK, }, }, { contextPropsIntoEventProperties: [ CONTEXT_PROPS.PAGE_TITLE, ], }, ); }} > {t('needHelpLinkText')} , ]) ///: END:ONLY_INCLUDE_IN } { ///: BEGIN:ONLY_INCLUDE_IN(beta) ///: END:ONLY_INCLUDE_IN } { ///: BEGIN:ONLY_INCLUDE_IN(flask) ///: END:ONLY_INCLUDE_IN }
{this.renderNotifications()}
); } }