Commit f167d80b authored by isstuev's avatar isstuev

inventory start

parent 68193e1b
......@@ -24,8 +24,8 @@ import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchResult, SearchResultFilters } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type { TokenCounters, TokenInfo, TokenHolders } from 'types/api/tokenInfo';
import type { TokensResponse, TokensFilters, TokenInstance, TokenInstanceTransfersCount } from 'types/api/tokens';
import type { TokenCounters, TokenInfo, TokenHolders, TokenInventory, TokenInstance, TokenInstanceTransfersCount } from 'types/api/token';
import type { TokensResponse, TokensFilters } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
......@@ -230,6 +230,11 @@ export const RESOURCES = {
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
token_inventory: {
path: '/api/v2/tokens/:hash/instances',
paginationFields: [ 'unique_token' as const ],
filterFields: [],
},
tokens: {
path: '/api/v2/tokens',
paginationFields: [ 'holder_count' as const, 'items_count' as const, 'name' as const ],
......@@ -317,7 +322,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' |
'address_logs' | 'address_tokens' |
'token_transfers' | 'token_holders' | 'tokens';
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
......@@ -367,6 +372,7 @@ Q extends 'token_transfers' ? TokenTransferResponse :
Q extends 'token_holders' ? TokenHolders :
Q extends 'token_instance' ? TokenInstance :
Q extends 'token_instance_transfers_count' ? TokenInstanceTransfersCount :
Q extends 'token_inventory' ? TokenInventory :
Q extends 'tokens' ? TokensResponse :
Q extends 'search' ? SearchResult :
Q extends 'contract' ? SmartContract :
......
......@@ -34,3 +34,28 @@ export type TokenHoldersPagination = {
items_count: number;
value: string;
}
export interface TokenInstance {
is_unique: boolean;
id: string;
holder_address_hash: string | null;
image_url: string | null;
animation_url: string | null;
external_app_url: string | null;
metadata: unknown;
owner: AddressParam;
token: TokenInfo;
}
export interface TokenInstanceTransfersCount {
transfers_count: number;
}
export interface TokenInventory {
items: Array<TokenInstance>;
next_page_params: TokenInventoryPagination;
}
export type TokenInventoryPagination = {
unique_token: number;
}
import type { AddressParam } from './addressParams';
import type { TokenInfo, TokenType } from './tokenInfo';
import type { TokenInfo, TokenType } from './token';
export type TokensResponse = {
items: Array<TokenInfo>;
......@@ -11,19 +10,3 @@ export type TokensResponse = {
}
export type TokensFilters = { filter: string; type: Array<TokenType> | undefined };
export interface TokenInstance {
is_unique: boolean;
id: string;
holder_address_hash: string | null;
image_url: string | null;
animation_url: string | null;
external_app_url: string | null;
metadata: unknown;
owner: AddressParam;
token: TokenInfo;
}
export interface TokenInstanceTransfersCount {
transfers_count: number;
}
......@@ -39,9 +39,9 @@ const TokensWithIds = ({ tokensQuery }: Props) => {
<>
{ bar }
<Flex columnGap={ 6 } rowGap={ 6 } flexWrap="wrap">
<Skeleton w="210px" h="272px"/>
<Skeleton w="210px" h="272px"/>
<Skeleton w="210px" h="272px"/>
<Skeleton w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }} h="272px"/>
</Flex>
</>
);
......@@ -54,7 +54,7 @@ const TokensWithIds = ({ tokensQuery }: Props) => {
return (
<>
{ bar }
<Flex columnGap={{ base: 3, lg: 6 }} rowGap={{ base: 3, lg: 6 }} flexWrap="wrap">
<Flex columnGap={{ base: 3, lg: 6 }} rowGap={{ base: 3, lg: 6 }} flexWrap="wrap" justifyContent="space-between">
{ data.items.map(item => <NFTItem key={ item.token.address } { ...item }/>) }
</Flex>
</>
......
......@@ -16,13 +16,15 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import SkeletonTabs from 'ui/shared/skeletons/SkeletonTabs';
import TokenLogo from 'ui/shared/TokenLogo';
import TokenContractInfo from 'ui/token/TokenContractInfo';
import TokenDetails from 'ui/token/TokenDetails';
import TokenHolders from 'ui/token/TokenHolders/TokenHolders';
import TokenInventory from 'ui/token/TokenInventory';
import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer';
export type TokenTabs = 'token_transfers' | 'holders'
export type TokenTabs = 'token_transfers' | 'holders' | 'inventory';
const TokenPageContent = () => {
const router = useRouter();
......@@ -72,11 +74,24 @@ const TokenPageContent = () => {
},
});
const inventoryQuery = useQueryWithPages({
resourceName: 'token_inventory',
pathParams: { hash: router.query.hash?.toString() },
scrollRef,
options: {
enabled: Boolean(router.query.hash && router.query.tab === 'inventory' && tokenQuery.data),
},
});
const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } token={ tokenQuery.data }/> },
{ id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> },
];
if (tokenQuery.data?.type === 'ERC-1155' || tokenQuery.data?.type === 'ERC-721') {
tabs.push({ id: 'inventory', title: 'Inventory', component: <TokenInventory tokenQuery={ tokenQuery } inventoryQuery={ inventoryQuery }/> });
}
let hasPagination;
let pagination;
......@@ -90,6 +105,11 @@ const TokenPageContent = () => {
pagination = holdersQuery.pagination;
}
if (router.query.tab === 'inventory') {
hasPagination = inventoryQuery.isPaginationVisible;
pagination = inventoryQuery.pagination;
}
const tokenSymbolText = tokenQuery.data?.symbol ? ` (${ trimTokenSymbol(tokenQuery.data.symbol) })` : '';
return (
......@@ -118,12 +138,14 @@ const TokenPageContent = () => {
{ /* should stay before tabs to scroll up whith pagination */ }
<Box ref={ scrollRef }></Box>
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
rightSlot={ !isMobile && hasPagination ? <Pagination { ...(pagination as PaginationProps) }/> : null }
stickyEnabled={ !isMobile }
/>
{ tokenQuery.isLoading ? <SkeletonTabs/> : (
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
rightSlot={ !isMobile && hasPagination ? <Pagination { ...(pagination as PaginationProps) }/> : null }
stickyEnabled={ !isMobile }
/>
) }
</Page>
);
};
......
import { Flex, Text, Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import { TokenInventory } from 'types/api/token';
import useIsMobile from 'lib/hooks/useIsMobile';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import TokenInventoryItem from './TokenInventoryItem';
type Props = {
inventoryQuery: UseQueryResult<TokenInventory> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
}
const TokenInventory = ({ inventoryQuery }: Props) => {
const isMobile = useIsMobile();
if (inventoryQuery.isError) {
return <DataFetchAlert/>;
}
const bar = isMobile && inventoryQuery.isPaginationVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...inventoryQuery.pagination }/>
</ActionBar>
);
if (inventoryQuery.isLoading) {
return (
<>
{ bar }
<Flex columnGap={{ base: 3, lg: 6 }} rowGap={{ base: 3, lg: 6 }} flexWrap="wrap">
<Skeleton w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }} h="272px"/>
<Skeleton w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }} h="272px"/>
</Flex>
</>
);
}
const items = inventoryQuery.data.items;
if (!items?.length) {
return <Text as="span">There are no tokens.</Text>;
}
return (
<Flex columnGap={{ base: 3, lg: 6 }} rowGap={{ base: 3, lg: 6 }} flexWrap="wrap" justifyContent="space-between">
{ items.map((item) => <TokenInventoryItem key={ item.token.address } item={ item }/>) }
</Flex>
);
};
export default TokenInventory;
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue, Hide } from '@chakra-ui/react';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
import link from 'lib/link/link';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import NftMedia from 'ui/shared/nft/NftMedia';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = { item: TokenInstance };
const NFTItem = ({ item }: Props) => {
const tokenLink = link('token_instance_item', { hash: item.token.address, id: item.id });
return (
<LinkBox
w={{ base: 'calc((100% - 12px)/2)', lg: '210px' }}
border="1px solid"
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
borderRadius="12px"
p="10px"
_hover={{ boxShadow: 'md' }}
fontSize="sm"
fontWeight={ 500 }
lineHeight="20px"
>
<LinkOverlay href={ tokenLink }/>
<NftMedia
mb="18px"
imageUrl={ item.image_url }
animationUrl={ item.animation_url }
/>
{ item.id && (
<Flex mb={ 2 } ml={ 1 }>
<Text whiteSpace="pre" variant="secondary">ID# </Text>
<TruncatedTextTooltip label={ item.id }>
<Link
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
{ item.id }
</Link>
</TruncatedTextTooltip>
</Flex>
) }
{ item.owner && (
<Flex mb={ 2 } ml={ 1 }>
<Text whiteSpace="pre" variant="secondary" mr={ 2 }>Owner</Text>
<Address>
<Hide below="lg" ssr={ false }><AddressIcon address={ item.owner } mr={ 1 }/></Hide>
<AddressLink hash={ item.owner.hash } alias={ item.owner.name } type="address" truncation="constant"/>
</Address>
</Flex>
) }
</LinkBox>
);
};
export default NFTItem;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment