diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index ab747c86ec09..d015ace7a459 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -705,6 +705,10 @@ export default class MetamaskController extends EventEmitter {
this.assetsContractController,
),
addNft: this.nftController.addNft.bind(this.nftController),
+ updateNftFetchingProgressStatus:
+ this.nftController.updateNftFetchingProgressStatus.bind(
+ this.nftController,
+ ),
getNftApi: this.nftController.getNftApi.bind(this.nftController),
getNftState: () => this.nftController.state,
// added this to track previous value of useNftDetection, should be true on very first initializing of controller[]
@@ -3115,6 +3119,8 @@ export default class MetamaskController extends EventEmitter {
nftController.checkAndUpdateSingleNftOwnershipStatus.bind(
nftController,
),
+ updateNftFetchingProgressStatus:
+ nftController.updateNftFetchingProgressStatus.bind(nftController),
isNftOwner: nftController.isNftOwner.bind(nftController),
diff --git a/ui/components/app/nfts-tab/index.scss b/ui/components/app/nfts-tab/index.scss
index 2f5e266c136a..44c20656d417 100644
--- a/ui/components/app/nfts-tab/index.scss
+++ b/ui/components/app/nfts-tab/index.scss
@@ -1,7 +1,15 @@
.nfts-tab {
+ &__fetching {
+ display: flex;
+ height: 100px;
+ align-items: center;
+ justify-content: center;
+ padding: 30px;
+ }
+
&__loading {
display: flex;
- height: 250px;
+ height: 200px;
align-items: center;
justify-content: center;
padding: 30px;
diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js
index f9f87ea0f8a1..44be21a5201f 100644
--- a/ui/components/app/nfts-tab/nfts-tab.js
+++ b/ui/components/app/nfts-tab/nfts-tab.js
@@ -1,4 +1,4 @@
-import React, { useContext, useEffect } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import {
@@ -48,6 +48,8 @@ import {
RampsCard,
} from '../../multichain/ramps-card/ramps-card';
import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance';
+import { getIsStillNftsFetching } from '../../../ducks/metamask/metamask';
+import Spinner from '../../ui/spinner';
///: END:ONLY_INCLUDE_IF
export default function NftsTab() {
@@ -63,11 +65,16 @@ export default function NftsTab() {
const shouldHideZeroBalanceTokens = useSelector(
getShouldHideZeroBalanceTokens,
);
+
+ const isNftsStillFetched = useSelector(getIsStillNftsFetching);
+
const { totalFiatBalance } = useAccountTotalFiatBalance(
selectedAddress,
shouldHideZeroBalanceTokens,
);
const balanceIsZero = Number(totalFiatBalance) === 0;
+ const [showLoader, setShowLoader] = useState(true);
+ const [showRefreshLoader, setShowRefreshLoader] = useState(false);
const isBuyableChain = useSelector(getIsBuyableChain);
const showRampsCard = isBuyableChain && balanceIsZero;
///: END:ONLY_INCLUDE_IF
@@ -80,9 +87,15 @@ export default function NftsTab() {
};
const onRefresh = () => {
+ setShowRefreshLoader(true);
if (isMainnet) {
- dispatch(detectNfts());
+ detectNfts();
}
+ const timeoutForRefresh = setTimeout(() => {
+ setShowRefreshLoader(false);
+ clearTimeout(timeoutForRefresh);
+ }, 200);
+
checkAndUpdateAllNftsOwnershipStatus();
};
@@ -114,10 +127,34 @@ export default function NftsTab() {
currentLocale,
]);
- if (nftsLoading) {
- return
{t('loadingNFTs')}
;
+ useEffect(() => {
+ const timeoutId = setTimeout(() => {
+ setShowLoader(false);
+ }, 2000);
+ return () => clearTimeout(timeoutId);
+ }, []);
+
+ if (!hasAnyNfts && showLoader) {
+ return (
+
+
+
+ );
}
+ if (showRefreshLoader) {
+ return (
+
+
+
+ );
+ }
return (
<>
{
@@ -129,10 +166,21 @@ export default function NftsTab() {
}
{hasAnyNfts > 0 || previouslyOwnedCollection.nfts.length > 0 ? (
-
+
+
+
+ {isNftsStillFetched.isFetchingInProgress ? (
+
+
+
+ ) : null}
+
) : (
<>
{isMainnet && !useNftDetection ? (
diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-nft-tab.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-nft-tab.tsx
new file mode 100644
index 000000000000..e435b4bbbff7
--- /dev/null
+++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-nft-tab.tsx
@@ -0,0 +1,151 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import NftsItems from '../../../app/nfts-items/nfts-items';
+import {
+ Box,
+ Text,
+ ButtonLink,
+ ButtonLinkSize,
+} from '../../../component-library';
+import {
+ TextColor,
+ TextVariant,
+ TextAlign,
+ Display,
+ JustifyContent,
+ AlignItems,
+ FlexDirection,
+} from '../../../../helpers/constants/design-system';
+import { useI18nContext } from '../../../../hooks/useI18nContext';
+import { TokenStandard } from '../../../../../shared/constants/transaction';
+import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url';
+import Spinner from '../../../ui/spinner';
+import { useScrollRequired } from '../../../../hooks/useScrollRequired';
+import { getIsStillNftsFetching } from '../../../../ducks/metamask/metamask';
+import PropTypes from 'prop-types';
+
+type NFT = {
+ address: string;
+ description: string | null;
+ favorite: boolean;
+ image: string | null;
+ isCurrentlyOwned: boolean;
+ name: string | null;
+ standard: TokenStandard;
+ tokenId: string;
+ tokenURI?: string;
+};
+
+type Collection = {
+ collectionName: string;
+ collectionImage: string | null;
+ nfts: NFT[];
+};
+
+type AssetPickerModalNftTabProps = {
+ collectionDataFiltered: Collection[];
+ previouslyOwnedCollection: any;
+ onClose: () => void;
+};
+
+export function AssetPickerModalNftTab({
+ collectionDataFiltered,
+ previouslyOwnedCollection,
+ onClose
+}: AssetPickerModalNftTabProps) {
+ const t = useI18nContext();
+
+ const hasAnyNfts = Object.keys(collectionDataFiltered).length > 0;
+ const { isScrollable, isScrolledToBottom, ref, onScroll } =
+ useScrollRequired();
+ const isNftsStillFetched = useSelector(getIsStillNftsFetching);
+ const [showLoader, setShowLoader] = useState(true);
+
+ useEffect(() => {
+ // Use setTimeout to update the message after 2000 milliseconds (2 seconds)
+ const timeoutId = setTimeout(() => {
+ setShowLoader(false);
+ }, 2000);
+
+ // Cleanup function to clear the timeout if the component unmounts
+ return () => clearTimeout(timeoutId);
+ }, []); // Empty dependency array ensures the effect runs only once
+
+ if (!hasAnyNfts && showLoader) {
+ return (
+
+
+
+ );
+ }
+
+ if (hasAnyNfts) {
+ return (
+
+ onClose()}
+ showTokenId={true}
+ displayPreviouslyOwnedCollection={false}
+ />
+ {isScrollable &&
+ isScrolledToBottom &&
+ isNftsStillFetched.isFetchingInProgress ? (
+
+
+
+ ) : null}
+
+ );
+ }
+ return (
+
+
+
+
+
+
+ {t('noNFTs')}
+
+
+ {t('learnMoreUpperCase')}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
index 79e9ff299153..f2043635a6be 100644
--- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
+++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx
@@ -3,7 +3,6 @@ import { useSelector } from 'react-redux';
import classnames from 'classnames';
import { isEqual } from 'lodash';
import { Tab, Tabs } from '../../../ui/tabs';
-import NftsItems from '../../../app/nfts-items/nfts-items';
import {
Modal,
ModalContent,
@@ -11,9 +10,6 @@ import {
ModalHeader,
TextFieldSearch,
Box,
- Text,
- ButtonLink,
- ButtonLinkSize,
ButtonIconSize,
TextFieldSearchSize,
} from '../../../component-library';
@@ -21,13 +17,8 @@ import {
BlockSize,
BorderRadius,
BackgroundColor,
- TextColor,
- TextVariant,
- TextAlign,
Display,
- JustifyContent,
AlignItems,
- FlexDirection,
FlexWrap,
} from '../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../hooks/useI18nContext';
@@ -54,7 +45,7 @@ import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay';
import TokenCell from '../../../app/token-cell';
import { TokenListItem } from '../../token-list-item';
import { useNftsCollections } from '../../../../hooks/useNftsCollections';
-import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url';
+import { AssetPickerModalNftTab } from './asset-picker-modal-nft-tab';
type AssetPickerModalProps = {
isOpen: boolean;
@@ -121,8 +112,6 @@ export function AssetPickerModal({
const { collections, previouslyOwnedCollection } = useNftsCollections();
- const hasAnyNfts = Object.keys(collections).length > 0;
-
const {
currency: primaryCurrency,
numberOfDecimals: primaryNumberOfDecimals,
@@ -365,8 +354,17 @@ export function AssetPickerModal({
size={TextFieldSearchSize.Lg}
/>
- {hasAnyNfts ? (
-
+
+ {/* {hasAnyNfts ? (
+
+ {isScrollable &&
+ isScrolledToBottom &&
+ isNftsStillFetched.isFetchingInProgress ? (
+
+
+
+ ) : null}
) : (
- )}
+ )} */}
}
diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/index.scss b/ui/components/multichain/asset-picker-amount/asset-picker-modal/index.scss
index d7c0096ae8ef..23a39f6917c3 100644
--- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/index.scss
+++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/index.scss
@@ -77,5 +77,13 @@
border-bottom: 0;
padding: 16px;
}
+
+ &__loading {
+ display: flex;
+ height: 200px;
+ align-items: center;
+ justify-content: center;
+ padding: 15px;
+ }
}
}
diff --git a/ui/components/ui/tabs/tabs.component.js b/ui/components/ui/tabs/tabs.component.js
index 57e22994eb9d..fc7475310fee 100644
--- a/ui/components/ui/tabs/tabs.component.js
+++ b/ui/components/ui/tabs/tabs.component.js
@@ -1,14 +1,12 @@
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 { useThrottle } from '../../../hooks/useThrottle';
import { detectNfts } from '../../../store/actions';
const Tabs = ({
@@ -34,21 +32,18 @@ const Tabs = ({
const _findChildByKey = (tabKey) => {
return _getValidChildren().findIndex((c) => c?.props.tabKey === tabKey);
};
- const dispatch = useDispatch();
const [activeTabIndex, setActiveTabIndex] = useState(() =>
Math.max(_findChildByKey(defaultActiveTabKey), 0),
);
- const invokeDetectNfts = useThrottle(() => dispatch(detectNfts()), 60000);
-
const handleTabClick = (tabIndex, tabKey) => {
if (tabIndex !== activeTabIndex) {
setActiveTabIndex(tabIndex);
onTabClick?.(tabKey);
}
if (tabKey === 'nfts') {
- invokeDetectNfts();
+ detectNfts();
}
};
diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js
index ef45e19e7694..2aef24a71fd0 100644
--- a/ui/ducks/metamask/metamask.js
+++ b/ui/ducks/metamask/metamask.js
@@ -315,6 +315,17 @@ export const getNfts = (state) => {
return allNfts?.[selectedAddress]?.[chainId] ?? [];
};
+export const getIsStillNftsFetching = (state) => {
+ const {
+ metamask: { isNftFetchingInProgress },
+ } = state;
+ const { address: selectedAddress } = getSelectedInternalAccount(state);
+
+ const { chainId } = getProviderConfig(state);
+
+ return isNftFetchingInProgress?.[selectedAddress]?.[chainId];
+};
+
export const getNftContracts = (state) => {
const {
metamask: { allNftContracts },
diff --git a/ui/store/actions.ts b/ui/store/actions.ts
index 0503a87afc09..68b53ef6b04b 100644
--- a/ui/store/actions.ts
+++ b/ui/store/actions.ts
@@ -3451,23 +3451,8 @@ export function detectTokens(): ThunkAction<
};
}
-export function detectNfts(): ThunkAction<
- void,
- MetaMaskReduxState,
- unknown,
- AnyAction
-> {
- return async (dispatch: MetaMaskReduxDispatch) => {
- dispatch(showLoadingIndication());
- log.debug(`background.detectNfts`);
- try {
- await submitRequestToBackground('detectNfts');
- dispatch(hideLoadingIndication());
- await forceUpdateMetamaskState(dispatch);
- } finally {
- dispatch(hideLoadingIndication());
- }
- };
+export async function detectNfts() {
+ await submitRequestToBackground('detectNfts');
}
export function setAdvancedGasFee(