diff --git a/mocks/tokens/tokenInstance.ts b/mocks/tokens/tokenInstance.ts index b28361868f..4173c0a609 100644 --- a/mocks/tokens/tokenInstance.ts +++ b/mocks/tokens/tokenInstance.ts @@ -2,7 +2,6 @@ import type { TokenInstance } from 'types/api/token'; import * as addressMock from '../address/address'; -import { tokenInfoERC721a } from './tokenInfo'; export const base: TokenInstance = { animation_url: null, @@ -74,7 +73,6 @@ export const base: TokenInstance = { name: 'GENESIS #188848, 22a5f8bbb1602995. Blockchain pixel PFP NFT + "on music video" trait inspired by God', }, owner: addressMock.withName, - token: tokenInfoERC721a, }; export const withRichMetadata: TokenInstance = { diff --git a/stubs/token.ts b/stubs/token.ts index 30b8b487bf..e60e004bad 100644 --- a/stubs/token.ts +++ b/stubs/token.ts @@ -108,6 +108,5 @@ export const TOKEN_INSTANCE: TokenInstance = { name: 'GENESIS #188882, 8a77ca1bcaa4036f. Blockchain pixel PFP NFT + "on music video" trait inspired by God', }, owner: ADDRESS_PARAMS, - token: TOKEN_INFO_ERC_1155, holder_address_hash: ADDRESS_HASH, }; diff --git a/types/api/token.ts b/types/api/token.ts index ebc5198d92..681dde2075 100644 --- a/types/api/token.ts +++ b/types/api/token.ts @@ -61,7 +61,6 @@ export interface TokenInstance { external_app_url: string | null; metadata: Record | null; owner: AddressParam | null; - token: TokenInfo; } export interface TokenInstanceTransfersCount { diff --git a/ui/pages/Token.tsx b/ui/pages/Token.tsx index a2e3dd8c58..18e9f2dd05 100644 --- a/ui/pages/Token.tsx +++ b/ui/pages/Token.tsx @@ -174,7 +174,7 @@ const TokenPageContent = () => { const tabs: Array = [ (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') ? - { id: 'inventory', title: 'Inventory', component: } : + { id: 'inventory', title: 'Inventory', component: } : undefined, { id: 'token_transfers', title: 'Token transfers', component: }, { id: 'holders', title: 'Holders', component: }, diff --git a/ui/pages/TokenInstance.tsx b/ui/pages/TokenInstance.tsx index 63fadd075b..38e938e504 100644 --- a/ui/pages/TokenInstance.tsx +++ b/ui/pages/TokenInstance.tsx @@ -10,7 +10,7 @@ import { useAppContext } from 'lib/contexts/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import * as metadata from 'lib/metadata'; import * as regexp from 'lib/regexp'; -import { TOKEN_INSTANCE } from 'stubs/token'; +import { TOKEN_INSTANCE, TOKEN_INFO_ERC_1155 } from 'stubs/token'; import * as tokenStubs from 'stubs/token'; import { generateListStub } from 'stubs/utils'; import AddressQrCode from 'ui/address/details/AddressQrCode'; @@ -43,6 +43,14 @@ const TokenInstanceContent = () => { const scrollRef = React.useRef(null); + const tokenQuery = useApiQuery('token', { + pathParams: { hash }, + queryOptions: { + enabled: Boolean(hash && id), + placeholderData: TOKEN_INFO_ERC_1155, + }, + }); + const tokenInstanceQuery = useApiQuery('token_instance', { pathParams: { hash, id }, queryOptions: { @@ -58,14 +66,18 @@ const TokenInstanceContent = () => { options: { enabled: Boolean(hash && id && (!tab || tab === 'token_transfers') && !tokenInstanceQuery.isPlaceholderData && tokenInstanceQuery.data), placeholderData: generateListStub<'token_instance_transfers'>( - tokenInstanceQuery.data?.token.type === 'ERC-1155' ? tokenStubs.TOKEN_TRANSFER_ERC_1155 : tokenStubs.TOKEN_TRANSFER_ERC_721, + tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_TRANSFER_ERC_1155 : tokenStubs.TOKEN_TRANSFER_ERC_721, 10, { next_page_params: null }, ), }, }); - const shouldFetchHolders = !tokenInstanceQuery.isPlaceholderData && tokenInstanceQuery.data && !tokenInstanceQuery.data.is_unique; + const shouldFetchHolders = + !tokenQuery.isPlaceholderData && + !tokenInstanceQuery.isPlaceholderData && + tokenInstanceQuery.data && + !tokenInstanceQuery.data.is_unique; const holdersQuery = useQueryWithPages({ resourceName: 'token_instance_holders', @@ -74,18 +86,18 @@ const TokenInstanceContent = () => { options: { enabled: Boolean(hash && tab === 'holders' && shouldFetchHolders), placeholderData: generateListStub<'token_instance_holders'>( - tokenInstanceQuery.data?.token.type === 'ERC-1155' ? tokenStubs.TOKEN_HOLDER_ERC_1155 : tokenStubs.TOKEN_HOLDER_ERC_20, 10, { next_page_params: null }), + tokenQuery.data?.type === 'ERC-1155' ? tokenStubs.TOKEN_HOLDER_ERC_1155 : tokenStubs.TOKEN_HOLDER_ERC_20, 10, { next_page_params: null }), }, }); React.useEffect(() => { - if (tokenInstanceQuery.data && !tokenInstanceQuery.isPlaceholderData) { + if (tokenInstanceQuery.data && !tokenInstanceQuery.isPlaceholderData && tokenQuery.data && !tokenQuery.isPlaceholderData) { metadata.update( - { pathname: '/token/[hash]/instance/[id]', query: { hash: tokenInstanceQuery.data.token.address, id: tokenInstanceQuery.data.id } }, - { symbol: tokenInstanceQuery.data.token.symbol ?? '' }, + { pathname: '/token/[hash]/instance/[id]', query: { hash: tokenQuery.data.address, id: tokenInstanceQuery.data.id } }, + { symbol: tokenQuery.data.symbol ?? '' }, ); } - }, [ tokenInstanceQuery.data, tokenInstanceQuery.isPlaceholderData ]); + }, [ tokenInstanceQuery.data, tokenInstanceQuery.isPlaceholderData, tokenQuery.data, tokenQuery.isPlaceholderData ]); const backLink = React.useMemo(() => { const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`) && !appProps.referrer.includes('instance'); @@ -104,10 +116,10 @@ const TokenInstanceContent = () => { { id: 'token_transfers', title: 'Token transfers', - component: , + component: , }, shouldFetchHolders ? - { id: 'holders', title: 'Holders', component: } : + { id: 'holders', title: 'Holders', component: } : undefined, { id: 'metadata', title: 'Metadata', component: ( { throw Error('Token instance fetch failed', { cause: tokenInstanceQuery.error }); } - const tokenTag = { tokenInstanceQuery.data?.token.type }; + const tokenTag = { tokenQuery.data?.type }; const address = { hash: hash || '', @@ -131,7 +143,7 @@ const TokenInstanceContent = () => { watchlist_address_id: null, }; - const isLoading = tokenInstanceQuery.isPlaceholderData; + const isLoading = tokenInstanceQuery.isPlaceholderData || tokenQuery.isPlaceholderData; const appLink = (() => { if (!tokenInstanceQuery.data?.external_app_url) { @@ -168,7 +180,7 @@ const TokenInstanceContent = () => { const titleSecondRow = ( { w="auto" maxW="700px" /> - { !isLoading && tokenInstanceQuery.data && } + { !isLoading && tokenInstanceQuery.data && } { appLink } @@ -197,7 +209,7 @@ const TokenInstanceContent = () => { isLoading={ isLoading } /> - + { /* should stay before tabs to scroll up with pagination */ } diff --git a/ui/token/TokenInventory.pw.tsx b/ui/token/TokenInventory.pw.tsx index 053ad772b8..d7b75dee28 100644 --- a/ui/token/TokenInventory.pw.tsx +++ b/ui/token/TokenInventory.pw.tsx @@ -2,6 +2,7 @@ import { Box } from '@chakra-ui/react'; import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; +import { tokenInfoERC721a } from 'mocks/tokens/tokenInfo'; import { base as tokenInstanse } from 'mocks/tokens/tokenInstance'; import TestApp from 'playwright/TestApp'; @@ -23,6 +24,11 @@ test('base view +@mobile', async({ mount }) => { // @ts-ignore: pagination: { page: 1, isVisible: true }, }} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: + tokenQuery={{ + data: tokenInfoERC721a, + }} /> , ); diff --git a/ui/token/TokenInventory.tsx b/ui/token/TokenInventory.tsx index 36c17612bf..2a99a5e828 100644 --- a/ui/token/TokenInventory.tsx +++ b/ui/token/TokenInventory.tsx @@ -1,6 +1,10 @@ import { Grid } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; +import type { TokenInfo } from 'types/api/token'; + +import type { ResourceError } from 'lib/api/resources'; import useIsMobile from 'lib/hooks/useIsMobile'; import ActionBar from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; @@ -11,9 +15,10 @@ import TokenInventoryItem from './TokenInventoryItem'; type Props = { inventoryQuery: QueryWithPagesResult<'token_inventory'>; + tokenQuery: UseQueryResult>; } -const TokenInventory = ({ inventoryQuery }: Props) => { +const TokenInventory = ({ inventoryQuery, tokenQuery }: Props) => { const isMobile = useIsMobile(); const actionBar = isMobile && inventoryQuery.pagination.isVisible && ( @@ -23,8 +28,9 @@ const TokenInventory = ({ inventoryQuery }: Props) => { ); const items = inventoryQuery.data?.items; + const token = tokenQuery.data; - const content = items ? ( + const content = items && token ? ( { > { items.map((item, index) => ( )) } diff --git a/ui/token/TokenInventoryItem.tsx b/ui/token/TokenInventoryItem.tsx index d816cc3f54..72f650d2b8 100644 --- a/ui/token/TokenInventoryItem.tsx +++ b/ui/token/TokenInventoryItem.tsx @@ -1,7 +1,7 @@ import { Box, Flex, Text, Link, useColorModeValue, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import type { TokenInstance } from 'types/api/token'; +import type { TokenInfo, TokenInstance } from 'types/api/token'; import { route } from 'nextjs-routes'; @@ -11,9 +11,9 @@ import LinkInternal from 'ui/shared/LinkInternal'; import NftMedia from 'ui/shared/nft/NftMedia'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; -type Props = { item: TokenInstance; isLoading: boolean }; +type Props = { item: TokenInstance; token: TokenInfo; isLoading: boolean }; -const NFTItem = ({ item, isLoading }: Props) => { +const TokenInventoryItem = ({ item, token, isLoading }: Props) => { const isMobile = useIsMobile(); @@ -25,7 +25,7 @@ const NFTItem = ({ item, isLoading }: Props) => { /> ); - const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: item.token.address, id: item.id } }); + const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: item.id } }); return ( { ); }; -export default NFTItem; +export default TokenInventoryItem; diff --git a/ui/tokenInstance/TokenInstanceDetails.pw.tsx b/ui/tokenInstance/TokenInstanceDetails.pw.tsx index 11ae3e3a0b..d4d31085e4 100644 --- a/ui/tokenInstance/TokenInstanceDetails.pw.tsx +++ b/ui/tokenInstance/TokenInstanceDetails.pw.tsx @@ -2,6 +2,7 @@ import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as addressMock from 'mocks/address/address'; +import { tokenInfoERC721a } from 'mocks/tokens/tokenInfo'; import * as tokenInstanceMock from 'mocks/tokens/tokenInstance'; import TestApp from 'playwright/TestApp'; import buildApiUrl from 'playwright/utils/buildApiUrl'; @@ -9,10 +10,12 @@ import * as configs from 'playwright/utils/configs'; import TokenInstanceDetails from './TokenInstanceDetails'; -const API_URL_ADDRESS = buildApiUrl('address', { hash: tokenInstanceMock.base.token.address }); +const hash = tokenInfoERC721a.address; + +const API_URL_ADDRESS = buildApiUrl('address', { hash }); const API_URL_TOKEN_TRANSFERS_COUNT = buildApiUrl('token_instance_transfers_count', { id: tokenInstanceMock.unique.id, - hash: tokenInstanceMock.unique.token.address, + hash, }); test('base view +@dark-mode +@mobile', async({ mount, page }) => { @@ -31,7 +34,7 @@ test('base view +@dark-mode +@mobile', async({ mount, page }) => { const component = await mount( - + , ); diff --git a/ui/tokenInstance/TokenInstanceDetails.tsx b/ui/tokenInstance/TokenInstanceDetails.tsx index aae2b1302c..6896f1cc4c 100644 --- a/ui/tokenInstance/TokenInstanceDetails.tsx +++ b/ui/tokenInstance/TokenInstanceDetails.tsx @@ -1,7 +1,7 @@ import { Flex, Grid, Skeleton } from '@chakra-ui/react'; import React from 'react'; -import type { TokenInstance } from 'types/api/token'; +import type { TokenInfo, TokenInstance } from 'types/api/token'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; @@ -18,11 +18,12 @@ import TokenInstanceTransfersCount from './details/TokenInstanceTransfersCount'; interface Props { data?: TokenInstance; + token?: TokenInfo; isLoading?: boolean; scrollRef?: React.RefObject; } -const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { +const TokenInstanceDetails = ({ data, token, scrollRef, isLoading }: Props) => { const handleCounterItemClick = React.useCallback(() => { window.setTimeout(() => { // cannot do scroll instantly, have to wait a little @@ -30,7 +31,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { }, 500); }, [ scrollRef ]); - if (!data) { + if (!data || !token) { return null; } @@ -56,7 +57,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => { /> ) } - + { - - + +