void;
onClose: () => void;
+ borderRadius?: BorderRadius;
+ textVariant?: TextVariant;
+ autoHideTime?: number;
+ onAutoHideToast?: () => void;
}) => {
const { theme } = document.documentElement.dataset;
+ const [shouldDisplay, setShouldDisplay] = useState(true);
+ useEffect(
+ function () {
+ if (!autoHideTime || autoHideTime === 0) {
+ return undefined;
+ }
+
+ const timeout = setTimeout(() => {
+ setShouldDisplay(false);
+ onAutoHideToast?.();
+ }, autoHideTime);
+
+ return function () {
+ clearTimeout(timeout);
+ };
+ },
+ [autoHideTime],
+ );
+
+ if (!shouldDisplay) {
+ return null;
+ }
return (
{startAdornment}
- {text}
+
+ {text}
+
{actionText && onActionClick ? (
{actionText}
) : null}
diff --git a/ui/components/ui/tabs/tabs.component.js b/ui/components/ui/tabs/tabs.component.js
index e96576711ceb..9795f697fce3 100644
--- a/ui/components/ui/tabs/tabs.component.js
+++ b/ui/components/ui/tabs/tabs.component.js
@@ -1,12 +1,14 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
+import { useDispatch } from 'react-redux';
import Box from '../box';
import {
BackgroundColor,
DISPLAY,
JustifyContent,
} from '../../../helpers/constants/design-system';
+import { detectNfts } from '../../../store/actions';
const Tabs = ({
defaultActiveTabKey,
@@ -20,6 +22,7 @@ const Tabs = ({
const _getValidChildren = () => {
return React.Children.toArray(children).filter(Boolean);
};
+ const dispatch = useDispatch();
/**
* Returns the index of the child with the given key
@@ -41,6 +44,10 @@ const Tabs = ({
setActiveTabIndex(tabIndex);
onTabClick?.(tabKey);
}
+
+ if (tabKey === 'nfts') {
+ dispatch(detectNfts());
+ }
};
const renderTabs = () => {
diff --git a/ui/ducks/app/app.ts b/ui/ducks/app/app.ts
index ba43decd429b..5c63ac25dcb7 100644
--- a/ui/ducks/app/app.ts
+++ b/ui/ducks/app/app.ts
@@ -48,6 +48,8 @@ type AppState = {
privateKey?: string;
};
isLoading: boolean;
+ isNftStillFetchingIndication: boolean;
+ showNftDetectionEnablementToast: boolean;
loadingMessage: string | null;
scrollToBottom: boolean;
warning: string | null | undefined;
@@ -132,6 +134,10 @@ const initialState: AppState = {
},
// Used to display loading indicator
isLoading: false,
+ // Used to show a spinner at the bottom of the page when we are still fetching nfts
+ isNftStillFetchingIndication: false,
+ // Used to display a toast after the user enables the nft auto detection from the notice banner
+ showNftDetectionEnablementToast: false,
loadingMessage: null,
// Used to display error text
warning: null,
@@ -432,6 +438,23 @@ export default function reduceApp(
isLoading: false,
};
+ case actionConstants.SHOW_NFT_STILL_FETCHING_INDICATION:
+ return {
+ ...appState,
+ isNftStillFetchingIndication: true,
+ };
+ case actionConstants.SHOW_NFT_DETECTION_ENABLEMENT_TOAST:
+ return {
+ ...appState,
+ showNftDetectionEnablementToast: action.payload,
+ };
+
+ case actionConstants.HIDE_NFT_STILL_FETCHING_INDICATION:
+ return {
+ ...appState,
+ isNftStillFetchingIndication: false,
+ };
+
case actionConstants.DISPLAY_WARNING:
return {
...appState,
diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js
index e8767bd816da..aba0f4f57d59 100644
--- a/ui/pages/home/home.component.js
+++ b/ui/pages/home/home.component.js
@@ -15,6 +15,7 @@ import WhatsNewPopup from '../../components/app/whats-new-popup';
import { FirstTimeFlowType } from '../../../shared/constants/onboarding';
import SmartTransactionsOptInModal from '../../components/app/smart-transactions/smart-transactions-opt-in-modal';
import AutoDetectTokenModal from '../../components/app/auto-detect-token/auto-detect-token-modal';
+import AutoDetectNftModal from '../../components/app/auto-detect-nft/auto-detect-nft-modal';
///: END:ONLY_INCLUDE_IF
import HomeNotification from '../../components/app/home-notification';
import MultipleNotifications from '../../components/app/multiple-notifications';
@@ -150,6 +151,7 @@ export default class Home extends PureComponent {
onboardedInThisUISession: PropTypes.bool,
isSmartTransactionsOptInModalAvailable: PropTypes.bool.isRequired,
isShowTokenAutodetectModal: PropTypes.bool.isRequired,
+ isShowNftAutodetectModal: PropTypes.bool.isRequired,
///: END:ONLY_INCLUDE_IF
newNetworkAddedConfigurationId: PropTypes.string,
isNotification: PropTypes.bool.isRequired,
@@ -197,6 +199,8 @@ export default class Home extends PureComponent {
setTokenAutodetectModal: PropTypes.func,
// eslint-disable-next-line react/no-unused-prop-types
setShowTokenAutodetectModalOnUpgrade: PropTypes.func,
+ // eslint-disable-next-line react/no-unused-prop-types
+ setNftAutodetectModal: PropTypes.func,
hasAllowedPopupRedirectApprovals: PropTypes.bool.isRequired,
useExternalServices: PropTypes.bool,
setBasicFunctionalityModalOpen: PropTypes.func,
@@ -936,6 +940,8 @@ export default class Home extends PureComponent {
isShowTokenAutodetectModal,
setTokenAutodetectModal,
setShowTokenAutodetectModalOnUpgrade,
+ isShowNftAutodetectModal,
+ setNftAutodetectModal,
///: END:ONLY_INCLUDE_IF
} = this.props;
@@ -967,6 +973,12 @@ export default class Home extends PureComponent {
isShowTokenAutodetectModal &&
!showSmartTransactionsOptInModal &&
!showWhatsNew;
+ // TODO show ths after token autodetect modal is merged
+ const showNftAutoDetectionModal =
+ canSeeModals &&
+ isShowNftAutodetectModal &&
+ !showSmartTransactionsOptInModal &&
+ !showWhatsNew;
const showTermsOfUse =
completedOnboarding && !onboardedInThisUISession && showTermsOfUsePopup;
@@ -1001,6 +1013,10 @@ export default class Home extends PureComponent {
}
/>
+
{showWhatsNew ? : null}
{!showWhatsNew && showRecoveryPhraseReminder ? (
{
isSmartTransactionsOptInModalAvailable:
getIsSmartTransactionsOptInModalAvailable(state),
isShowTokenAutodetectModal: getIsShowTokenAutodetectModal(state),
+ isShowNftAutodetectModal: getIsShowNftAutodetectModal(state),
};
};
@@ -272,6 +275,9 @@ const mapDispatchToProps = (dispatch) => {
setShowTokenAutodetectModalOnUpgrade: (val) => {
dispatch(setShowTokenAutodetectModalOnUpgrade(val));
},
+ setNftAutodetectModal: (val) => {
+ dispatch(setShowNftAutodetectModal(val));
+ },
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),
diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js
index 640cf27751b5..c13f81d7e8ec 100644
--- a/ui/pages/routes/routes.component.js
+++ b/ui/pages/routes/routes.component.js
@@ -126,8 +126,13 @@ import KeyringSnapRemovalResult from '../../components/app/modals/keyring-snap-r
import { SendPage } from '../../components/multichain/pages/send';
import { DeprecatedNetworkModal } from '../settings/deprecated-network-modal/DeprecatedNetworkModal';
import { getURLHost } from '../../helpers/utils/util';
-import { BorderColor, IconColor } from '../../helpers/constants/design-system';
-import { MILLISECOND } from '../../../shared/constants/time';
+import {
+ BorderColor,
+ BorderRadius,
+ IconColor,
+ TextVariant,
+} from '../../helpers/constants/design-system';
+import { MILLISECOND, SECOND } from '../../../shared/constants/time';
import { MultichainMetaFoxLogo } from '../../components/multichain/app-header/multichain-meta-fox-logo';
import NetworkConfirmationPopover from '../../components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover';
@@ -191,6 +196,9 @@ export default class Routes extends Component {
hideDeprecatedNetworkModal: PropTypes.func.isRequired,
addPermittedAccount: PropTypes.func.isRequired,
switchedNetworkDetails: PropTypes.object,
+ useNftDetection: PropTypes.bool,
+ showNftEnablementToast: PropTypes.bool,
+ setHideNftEnablementToast: PropTypes.func.isRequired,
clearSwitchedNetworkDetails: PropTypes.func.isRequired,
setSwitchedNetworkNeverShowMessage: PropTypes.func.isRequired,
networkToAutomaticallySwitchTo: PropTypes.object,
@@ -611,12 +619,20 @@ export default class Routes extends Component {
setNewPrivacyPolicyToastClickedOrClosed,
setSwitchedNetworkNeverShowMessage,
switchedNetworkDetails,
+ useNftDetection,
+ showNftEnablementToast,
+ setHideNftEnablementToast,
} = this.props;
const showAutoNetworkSwitchToast = this.getShowAutoNetworkSwitchTest();
const isPrivacyToastRecent = this.getIsPrivacyToastRecent();
const isPrivacyToastNotShown = !newPrivacyPolicyToastShownDate;
+ const autoHideToastDelay = 5 * SECOND;
+
+ const onAutoHideToast = () => {
+ setHideNftEnablementToast(false);
+ };
if (!this.onHomeScreen()) {
return null;
}
@@ -715,6 +731,19 @@ export default class Routes extends Component {
onClose={() => clearSwitchedNetworkDetails()}
/>
) : null}
+ {showNftEnablementToast && useNftDetection ? (
+
+ }
+ text={this.context.t('nftAutoDetectionEnabled')}
+ borderRadius={BorderRadius.LG}
+ textVariant={TextVariant.bodyMd}
+ autoHideTime={autoHideToastDelay}
+ onAutoHideToast={onAutoHideToast}
+ />
+ ) : null}
);
}
diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js
index efa7afad4908..d4334e00b4de 100644
--- a/ui/pages/routes/routes.container.js
+++ b/ui/pages/routes/routes.container.js
@@ -26,6 +26,8 @@ import {
getNewPrivacyPolicyToastShownDate,
getShowPrivacyPolicyToast,
getUseRequestQueue,
+ getUseNftDetection,
+ getNftDetectionEnablementToast,
} from '../../selectors';
import { getLocalNetworkMenuRedesignFeatureFlag } from '../../helpers/utils/feature-flags';
import { getSmartTransactionsOptInStatus } from '../../../shared/modules/selectors';
@@ -46,6 +48,7 @@ import {
automaticallySwitchNetwork,
clearSwitchedNetworkDetails,
neverShowSwitchedNetworkMessage,
+ setShowNftDetectionEnablementToast,
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
hideKeyringRemovalResultModal,
///: END:ONLY_INCLUDE_IF
@@ -86,6 +89,9 @@ function mapStateToProps(state) {
getNetworkToAutomaticallySwitchTo(state);
const switchedNetworkDetails = getSwitchedNetworkDetails(state);
+ const useNftDetection = getUseNftDetection(state);
+ const showNftEnablementToast = getNftDetectionEnablementToast(state);
+
return {
alertOpen,
alertMessage,
@@ -124,6 +130,8 @@ function mapStateToProps(state) {
isImportNftsModalOpen: state.appState.importNftsModal.open,
isIpfsModalOpen: state.appState.showIpfsModalOpen,
switchedNetworkDetails,
+ useNftDetection,
+ showNftEnablementToast,
networkToAutomaticallySwitchTo,
unapprovedTransactions:
getNumberOfAllUnapprovedTransactionsAndMessages(state),
@@ -172,6 +180,8 @@ function mapDispatchToProps(dispatch) {
hideShowKeyringSnapRemovalResultModal: () =>
dispatch(hideKeyringRemovalResultModal()),
///: END:ONLY_INCLUDE_IF
+ setHideNftEnablementToast: (value) =>
+ dispatch(setShowNftDetectionEnablementToast(value)),
};
}
diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js
index e0fd002d17a1..d1d6d55c3a36 100644
--- a/ui/selectors/selectors.js
+++ b/ui/selectors/selectors.js
@@ -750,6 +750,14 @@ export function getAppIsLoading(state) {
return state.appState.isLoading;
}
+export function getNftIsStillFetchingIndication(state) {
+ return state.appState.isNftStillFetchingIndication;
+}
+
+export function getNftDetectionEnablementToast(state) {
+ return state.appState.showNftDetectionEnablementToast;
+}
+
export function getCurrentCurrency(state) {
return state.metamask.currentCurrency;
}
diff --git a/ui/store/actionConstants.ts b/ui/store/actionConstants.ts
index 57ab34e800a0..658e5b30c296 100644
--- a/ui/store/actionConstants.ts
+++ b/ui/store/actionConstants.ts
@@ -67,6 +67,15 @@ export const SET_HARDWARE_WALLET_DEFAULT_HD_PATH =
export const SHOW_LOADING = 'SHOW_LOADING_INDICATION';
export const HIDE_LOADING = 'HIDE_LOADING_INDICATION';
+// Nft still fetching indication spinners
+export const SHOW_NFT_STILL_FETCHING_INDICATION =
+ 'SHOW_NFT_STILL_FETCHING_INDICATION';
+export const HIDE_NFT_STILL_FETCHING_INDICATION =
+ 'HIDE_NFT_STILL_FETCHING_INDICATION';
+
+export const SHOW_NFT_DETECTION_ENABLEMENT_TOAST =
+ 'SHOW_NFT_DETECTION_ENABLEMENT_TOAST';
+
export const TOGGLE_ACCOUNT_MENU = 'TOGGLE_ACCOUNT_MENU';
export const TOGGLE_NETWORK_MENU = 'TOGGLE_NETWORK_MENU';
@@ -148,3 +157,6 @@ export const SHOW_KEYRING_SNAP_REMOVAL_RESULT =
export const HIDE_KEYRING_SNAP_REMOVAL_RESULT =
'HIDE_KEYRING_SNAP_REMOVAL_RESULT';
///: END:ONLY_INCLUDE_IF
+
+export const SET_SHOW_NFT_AUTO_DETECT_MODAL_UPGRADE =
+ 'SET_SHOW_NFT_AUTO_DETECT_MODAL_UPGRADE';
diff --git a/ui/store/actions.ts b/ui/store/actions.ts
index 2a8d1a983159..c57776fd45ea 100644
--- a/ui/store/actions.ts
+++ b/ui/store/actions.ts
@@ -2779,6 +2779,21 @@ export function showLoadingIndication(
};
}
+export function showNftStillFetchingIndication(): Action {
+ return {
+ type: actionConstants.SHOW_NFT_STILL_FETCHING_INDICATION,
+ };
+}
+
+export function setShowNftDetectionEnablementToast(
+ value: boolean,
+): PayloadAction {
+ return {
+ type: actionConstants.SHOW_NFT_DETECTION_ENABLEMENT_TOAST,
+ payload: value,
+ };
+}
+
export function setHardwareWalletDefaultHdPath({
device,
path,
@@ -2798,6 +2813,12 @@ export function hideLoadingIndication(): Action {
};
}
+export function hideNftStillFetchingIndication(): Action {
+ return {
+ type: actionConstants.HIDE_NFT_STILL_FETCHING_INDICATION,
+ };
+}
+
/**
* An action creator for display a warning to the user in various places in the
* UI. It will not be cleared until a new warning replaces it or `hideWarning`
@@ -3469,10 +3490,13 @@ export function detectNfts(): ThunkAction<
AnyAction
> {
return async (dispatch: MetaMaskReduxDispatch) => {
- dispatch(showLoadingIndication());
+ dispatch(showNftStillFetchingIndication());
log.debug(`background.detectNfts`);
- await submitRequestToBackground('detectNfts');
- dispatch(hideLoadingIndication());
+ try {
+ await submitRequestToBackground('detectNfts');
+ } finally {
+ dispatch(hideNftStillFetchingIndication());
+ }
await forceUpdateMetamaskState(dispatch);
};
}
@@ -5595,6 +5619,10 @@ export function setIsProfileSyncingEnabled(
};
}
+export function setShowNftAutodetectModal(value: boolean) {
+ return setPreference('showNftAutodetectModal', value);
+}
+
export async function getNextAvailableAccountName(): Promise {
return await submitRequestToBackground(
'getNextAvailableAccountName',
diff --git a/yarn.lock b/yarn.lock
index 53e38866f75d..175872f95ac4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4780,7 +4780,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A30.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%3A%3Aversion=30.0.0&hash=9269c8#~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch":
+"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A30.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%3A%3Aversion=30.0.0&hash=9269c8#~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch::version=30.0.0&hash=1ba1a6":
version: 30.0.0
resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A30.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%3A%3Aversion=30.0.0&hash=9269c8#~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch::version=30.0.0&hash=1ba1a6"
dependencies:
@@ -4822,6 +4822,48 @@ __metadata:
languageName: node
linkType: hard
+"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@patch%253A@metamask/assets-controllers@npm%25253A30.0.0%2523~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%253A%253Aversion=30.0.0&hash=9269c8%23~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch%3A%3Aversion=30.0.0&hash=1ba1a6#~/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch":
+ version: 30.0.0
+ resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@patch%253A@metamask/assets-controllers@npm%25253A30.0.0%2523~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%253A%253Aversion=30.0.0&hash=9269c8%23~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch%3A%3Aversion=30.0.0&hash=1ba1a6#~/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch::version=30.0.0&hash=eccb6b"
+ dependencies:
+ "@ethereumjs/util": "npm:^8.1.0"
+ "@ethersproject/address": "npm:^5.7.0"
+ "@ethersproject/bignumber": "npm:^5.7.0"
+ "@ethersproject/contracts": "npm:^5.7.0"
+ "@ethersproject/providers": "npm:^5.7.0"
+ "@metamask/abi-utils": "npm:^2.0.2"
+ "@metamask/accounts-controller": "npm:^14.0.0"
+ "@metamask/approval-controller": "npm:^6.0.2"
+ "@metamask/base-controller": "npm:^5.0.2"
+ "@metamask/contract-metadata": "npm:^2.4.0"
+ "@metamask/controller-utils": "npm:^10.0.0"
+ "@metamask/eth-query": "npm:^4.0.0"
+ "@metamask/keyring-controller": "npm:^16.0.0"
+ "@metamask/metamask-eth-abis": "npm:^3.1.1"
+ "@metamask/network-controller": "npm:^18.1.2"
+ "@metamask/polling-controller": "npm:^6.0.2"
+ "@metamask/preferences-controller": "npm:^11.0.0"
+ "@metamask/rpc-errors": "npm:^6.2.1"
+ "@metamask/utils": "npm:^8.3.0"
+ "@types/bn.js": "npm:^5.1.5"
+ "@types/uuid": "npm:^8.3.0"
+ async-mutex: "npm:^0.2.6"
+ bn.js: "npm:^5.2.1"
+ cockatiel: "npm:^3.1.2"
+ lodash: "npm:^4.17.21"
+ multiformats: "npm:^9.5.2"
+ single-call-balance-checker-abi: "npm:^1.0.0"
+ uuid: "npm:^8.3.2"
+ peerDependencies:
+ "@metamask/accounts-controller": ^14.0.0
+ "@metamask/approval-controller": ^6.0.0
+ "@metamask/keyring-controller": ^16.0.0
+ "@metamask/network-controller": ^18.1.2
+ "@metamask/preferences-controller": ^11.0.0
+ checksum: 10/f6d5fc9021db8bd95d9b2c19cea5a2b77bf97f2bf0fc01d61d7e372f45c15be7bed434d6519aeaf6ba209e079c2b292121f8aa61ddf5a48edcfe4b711a1e1f3d
+ languageName: node
+ linkType: hard
+
"@metamask/auto-changelog@npm:^2.1.0":
version: 2.6.1
resolution: "@metamask/auto-changelog@npm:2.6.1"
@@ -24955,7 +24997,7 @@ __metadata:
"@metamask/address-book-controller": "npm:^4.0.1"
"@metamask/announcement-controller": "npm:^6.1.0"
"@metamask/approval-controller": "npm:^7.0.0"
- "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A30.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%3A%3Aversion=30.0.0&hash=9269c8#~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch"
+ "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@patch%253A@metamask/assets-controllers@npm%25253A30.0.0%2523~/.yarn/patches/@metamask-assets-controllers-npm-30.0.0-8747c20871.patch%253A%253Aversion=30.0.0&hash=9269c8%23~/.yarn/patches/@metamask-assets-controllers-patch-26d4328777.patch%3A%3Aversion=30.0.0&hash=1ba1a6#~/.yarn/patches/@metamask-assets-controllers-patch-a3b39b55a6.patch"
"@metamask/auto-changelog": "npm:^2.1.0"
"@metamask/base-controller": "npm:^5.0.1"
"@metamask/browser-passworder": "npm:^4.3.0"