Commit 0e4294da authored by tom's avatar tom

transfers tab for token instance page

parent 68193e1b
...@@ -25,7 +25,7 @@ import type { RawTracesResponse } from 'types/api/rawTrace'; ...@@ -25,7 +25,7 @@ import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchResult, SearchResultFilters } from 'types/api/search'; import type { SearchResult, SearchResultFilters } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type { TokenCounters, TokenInfo, TokenHolders } from 'types/api/tokenInfo'; import type { TokenCounters, TokenInfo, TokenHolders } from 'types/api/tokenInfo';
import type { TokensResponse, TokensFilters, TokenInstance, TokenInstanceTransfersCount } from 'types/api/tokens'; import type { TokensResponse, TokensFilters, TokenInstance, TokenInstanceTransfersCount, TokenInstanceTransferResponse } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction'; import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
...@@ -243,6 +243,11 @@ export const RESOURCES = { ...@@ -243,6 +243,11 @@ export const RESOURCES = {
token_instance_transfers_count: { token_instance_transfers_count: {
path: '/api/v2/tokens/:hash/instances/:id/transfers-count', path: '/api/v2/tokens/:hash/instances/:id/transfers-count',
}, },
token_instance_transfers: {
path: '/api/v2/tokens/:hash/instances/:id/transfers',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'token_id' as const ],
filterFields: [],
},
// HOMEPAGE // HOMEPAGE
homepage_stats: { homepage_stats: {
...@@ -317,7 +322,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -317,7 +322,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' |
'token_transfers' | 'token_holders' | 'tokens'; 'token_transfers' | 'token_holders' | 'tokens' |
'token_instance_transfers';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -367,6 +373,7 @@ Q extends 'token_transfers' ? TokenTransferResponse : ...@@ -367,6 +373,7 @@ Q extends 'token_transfers' ? TokenTransferResponse :
Q extends 'token_holders' ? TokenHolders : Q extends 'token_holders' ? TokenHolders :
Q extends 'token_instance' ? TokenInstance : Q extends 'token_instance' ? TokenInstance :
Q extends 'token_instance_transfers_count' ? TokenInstanceTransfersCount : Q extends 'token_instance_transfers_count' ? TokenInstanceTransfersCount :
Q extends 'token_instance_transfers' ? TokenInstanceTransferResponse :
Q extends 'tokens' ? TokensResponse : Q extends 'tokens' ? TokensResponse :
Q extends 'search' ? SearchResult : Q extends 'search' ? SearchResult :
Q extends 'contract' ? SmartContract : Q extends 'contract' ? SmartContract :
......
...@@ -46,7 +46,6 @@ export type TokenTransferPagination = { ...@@ -46,7 +46,6 @@ export type TokenTransferPagination = {
block_number: number; block_number: number;
index: number; index: number;
items_count: number; items_count: number;
transaction_hash: string;
} }
export interface TokenTransferResponse { export interface TokenTransferResponse {
......
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { TokenInfo, TokenType } from './tokenInfo'; import type { TokenInfo, TokenType } from './tokenInfo';
import type { TokenTransfer } from './tokenTransfer';
export type TokensResponse = { export type TokensResponse = {
items: Array<TokenInfo>; items: Array<TokenInfo>;
...@@ -27,3 +28,15 @@ export interface TokenInstance { ...@@ -27,3 +28,15 @@ export interface TokenInstance {
export interface TokenInstanceTransfersCount { export interface TokenInstanceTransfersCount {
transfers_count: number; transfers_count: number;
} }
export interface TokenInstanceTransferResponse {
items: Array<TokenTransfer>;
next_page_params: TokenInstanceTransferPagination | null;
}
export interface TokenInstanceTransferPagination {
block_number: number;
index: number;
items_count: number;
token_id: string;
}
...@@ -73,7 +73,7 @@ const TokenPageContent = () => { ...@@ -73,7 +73,7 @@ const TokenPageContent = () => {
}); });
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } token={ tokenQuery.data }/> }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery }/> },
{ id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> }, { id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> },
]; ];
......
import { Box, Icon, Link, chakra } from '@chakra-ui/react'; import { Box, Icon, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import nftPlaceholder from 'icons/nft_shield.svg'; import nftPlaceholder from 'icons/nft_shield.svg';
import link from 'lib/link/link'; import link from 'lib/link/link';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
hash: string; hash: string;
id: string; id: string;
className?: string; className?: string;
isDisabled?: boolean;
} }
const TokenTransferNft = ({ hash, id, className }: Props) => { const TokenTransferNft = ({ hash, id, className, isDisabled }: Props) => {
const Component = isDisabled ? Box : LinkInternal;
return ( return (
<Link <Component
href={ link('token_instance_item', { hash, id }) } href={ isDisabled ? undefined : link('token_instance_item', { hash, id }) }
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
display="flex" display="flex"
...@@ -26,7 +30,7 @@ const TokenTransferNft = ({ hash, id, className }: Props) => { ...@@ -26,7 +30,7 @@ const TokenTransferNft = ({ hash, id, className }: Props) => {
<Box maxW="calc(100% - 34px)"> <Box maxW="calc(100% - 34px)">
<HashStringShortenDynamic hash={ id } fontWeight={ 500 }/> <HashStringShortenDynamic hash={ id } fontWeight={ 500 }/>
</Box> </Box>
</Link> </Component>
); );
}; };
......
...@@ -4,7 +4,6 @@ import { useRouter } from 'next/router'; ...@@ -4,7 +4,6 @@ import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { SocketMessage } from 'lib/socket/types';
import type { TokenInfo } from 'types/api/tokenInfo';
import type { TokenTransferResponse } from 'types/api/tokenTransfer'; import type { TokenTransferResponse } from 'types/api/tokenTransfer';
import useGradualIncrement from 'lib/hooks/useGradualIncrement'; import useGradualIncrement from 'lib/hooks/useGradualIncrement';
...@@ -23,14 +22,14 @@ import TokenTransferList from 'ui/token/TokenTransfer/TokenTransferList'; ...@@ -23,14 +22,14 @@ import TokenTransferList from 'ui/token/TokenTransfer/TokenTransferList';
import TokenTransferTable from 'ui/token/TokenTransfer/TokenTransferTable'; import TokenTransferTable from 'ui/token/TokenTransfer/TokenTransferTable';
type Props = { type Props = {
token?: TokenInfo;
transfersQuery: UseQueryResult<TokenTransferResponse> & { transfersQuery: UseQueryResult<TokenTransferResponse> & {
pagination: PaginationProps; pagination: PaginationProps;
isPaginationVisible: boolean; isPaginationVisible: boolean;
}; };
tokenId?: string;
} }
const TokenTransfer = ({ transfersQuery, token }: Props) => { const TokenTransfer = ({ transfersQuery, tokenId }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const router = useRouter(); const router = useRouter();
const { isError, isLoading, data, pagination, isPaginationVisible } = transfersQuery; const { isError, isLoading, data, pagination, isPaginationVisible } = transfersQuery;
...@@ -92,12 +91,10 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -92,12 +91,10 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
<TokenTransferTable <TokenTransferTable
data={ items } data={ items }
top={ isPaginationVisible ? 80 : 0 } top={ isPaginationVisible ? 80 : 0 }
// token transfers query depends on token data
// so if we are here, we definitely have token data
token={ token as TokenInfo }
showSocketInfo={ pagination.page === 1 } showSocketInfo={ pagination.page === 1 }
socketInfoAlert={ socketAlert } socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
tokenId={ tokenId }
/> />
</Hide> </Hide>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
...@@ -110,7 +107,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -110,7 +107,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
borderBottomRadius={ 0 } borderBottomRadius={ 0 }
/> />
) } ) }
<TokenTransferList data={ items }/> <TokenTransferList data={ items } tokenId={ tokenId }/>
</Show> </Show>
</> </>
); );
......
...@@ -7,15 +7,17 @@ import TokenTransferListItem from 'ui/token/TokenTransfer/TokenTransferListItem' ...@@ -7,15 +7,17 @@ import TokenTransferListItem from 'ui/token/TokenTransfer/TokenTransferListItem'
interface Props { interface Props {
data: Array<TokenTransfer>; data: Array<TokenTransfer>;
tokenId?: string;
} }
const TokenTransferList = ({ data }: Props) => { const TokenTransferList = ({ data, tokenId }: Props) => {
return ( return (
<Box> <Box>
{ data.map((item, index) => ( { data.map((item, index) => (
<TokenTransferListItem <TokenTransferListItem
key={ index } key={ index }
{ ...item } { ...item }
tokenId={ tokenId }
/> />
)) } )) }
</Box> </Box>
......
...@@ -14,7 +14,7 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -14,7 +14,7 @@ import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft'; import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
type Props = TokenTransfer; type Props = TokenTransfer & {tokenId?: string};
const TokenTransferListItem = ({ const TokenTransferListItem = ({
token, token,
...@@ -24,6 +24,7 @@ const TokenTransferListItem = ({ ...@@ -24,6 +24,7 @@ const TokenTransferListItem = ({
to, to,
method, method,
timestamp, timestamp,
tokenId,
}: Props) => { }: Props) => {
const value = (() => { const value = (() => {
if (!('value' in total)) { if (!('value' in total)) {
...@@ -78,7 +79,7 @@ const TokenTransferListItem = ({ ...@@ -78,7 +79,7 @@ const TokenTransferListItem = ({
</Flex> </Flex>
) } ) }
{ 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && { 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') &&
<TokenTransferNft hash={ token.address } id={ total.token_id }/> } <TokenTransferNft hash={ token.address } id={ total.token_id } isDisabled={ Boolean(tokenId && tokenId === total.token_id) }/> }
</ListItemMobile> </ListItemMobile>
); );
}; };
......
import { Table, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
...@@ -12,13 +11,16 @@ import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableIte ...@@ -12,13 +11,16 @@ import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableIte
interface Props { interface Props {
data: Array<TokenTransfer>; data: Array<TokenTransfer>;
top: number; top: number;
token: TokenInfo;
showSocketInfo: boolean; showSocketInfo: boolean;
socketInfoAlert?: string; socketInfoAlert?: string;
socketInfoNum?: number; socketInfoNum?: number;
tokenId?: string;
} }
const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert, socketInfoNum }: Props) => { const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socketInfoNum, tokenId }: Props) => {
const tokenType = data[0].token.type;
const tokenSymbol = data[0].token.symbol;
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ top }> <Thead top={ top }>
...@@ -28,8 +30,8 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert, ...@@ -28,8 +30,8 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert,
<Th width="148px">From</Th> <Th width="148px">From</Th>
<Th width="36px" px={ 0 }/> <Th width="36px" px={ 0 }/>
<Th width="218px" >To</Th> <Th width="218px" >To</Th>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && <Th width="20%" isNumeric={ token.type === 'ERC-721' }>Token ID</Th> } { (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
{ (token.type === 'ERC-20' || token.type === 'ERC-1155') && <Th width="20%" isNumeric>Value { trimTokenSymbol(token.symbol) }</Th> } { (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric>Value { trimTokenSymbol(tokenSymbol) }</Th> }
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
...@@ -48,7 +50,7 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert, ...@@ -48,7 +50,7 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert,
</Tr> </Tr>
) } ) }
{ data.map((item, index) => ( { data.map((item, index) => (
<TokenTransferTableItem key={ index } { ...item }/> <TokenTransferTableItem key={ index } { ...item } tokenId={ tokenId }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
...@@ -11,7 +11,7 @@ import AddressIcon from 'ui/shared/address/AddressIcon'; ...@@ -11,7 +11,7 @@ import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft'; import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
type Props = TokenTransfer type Props = TokenTransfer & { tokenId?: string }
const TokenTransferTableItem = ({ const TokenTransferTableItem = ({
token, token,
...@@ -21,6 +21,7 @@ const TokenTransferTableItem = ({ ...@@ -21,6 +21,7 @@ const TokenTransferTableItem = ({
to, to,
method, method,
timestamp, timestamp,
tokenId,
}: Props) => { }: Props) => {
const value = (() => { const value = (() => {
if (!('value' in total)) { if (!('value' in total)) {
...@@ -82,7 +83,9 @@ const TokenTransferTableItem = ({ ...@@ -82,7 +83,9 @@ const TokenTransferTableItem = ({
<TokenTransferNft <TokenTransferNft
hash={ token.address } hash={ token.address }
id={ total.token_id } id={ total.token_id }
justifyContent={ token.type === 'ERC-721' ? 'end' : 'start' }/> justifyContent={ token.type === 'ERC-721' ? 'end' : 'start' }
isDisabled={ Boolean(tokenId && tokenId === total.token_id) }
/>
) : '-' ) : '-'
} }
</Td> </Td>
......
...@@ -7,14 +7,18 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -7,14 +7,18 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
import TokenInstanceDetails from 'ui/tokenInstance/TokenInstanceDetails'; import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer';
import TokenInstanceSkeleton from 'ui/tokenInstance/TokenInstanceSkeleton';
import TokenInstanceDetails from './TokenInstanceDetails';
import TokenInstanceSkeleton from './TokenInstanceSkeleton';
export type TokenTabs = 'token_transfers' | 'holders' export type TokenTabs = 'token_transfers' | 'holders'
...@@ -25,6 +29,7 @@ const TokenInstanceContent = () => { ...@@ -25,6 +29,7 @@ const TokenInstanceContent = () => {
const hash = router.query.hash?.toString(); const hash = router.query.hash?.toString();
const id = router.query.id?.toString(); const id = router.query.id?.toString();
const tab = router.query.tab?.toString();
const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`) && !appProps.referrer.includes('instance'); const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`) && !appProps.referrer.includes('instance');
...@@ -35,9 +40,19 @@ const TokenInstanceContent = () => { ...@@ -35,9 +40,19 @@ const TokenInstanceContent = () => {
queryOptions: { enabled: Boolean(hash && id) }, queryOptions: { enabled: Boolean(hash && id) },
}); });
const transfersQuery = useQueryWithPages({
resourceName: 'token_instance_transfers',
pathParams: { hash, id },
scrollRef,
options: {
enabled: Boolean(hash && (!tab || tab === 'token_transfers') && tokenInstanceQuery.data),
},
});
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <span>Token transfers</span> }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } tokenId={ id }/> },
{ id: 'holders', title: 'Holders', component: <span>Holders</span> }, // there is no api for this tab yet
// { id: 'holders', title: 'Holders', component: <span>Holders</span> },
{ id: 'metadata', title: 'Metadata', component: <span>Metadata</span> }, { id: 'metadata', title: 'Metadata', component: <span>Metadata</span> },
]; ];
...@@ -81,6 +96,8 @@ const TokenInstanceContent = () => { ...@@ -81,6 +96,8 @@ const TokenInstanceContent = () => {
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } } tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
rightSlot={ !isMobile && transfersQuery.isPaginationVisible ? <Pagination { ...transfersQuery.pagination }/> : null }
stickyEnabled={ !isMobile }
/> />
</> </>
); );
......
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