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';
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 { TokensResponse, TokensFilters, TokenInstance, TokenInstanceTransfersCount, TokenInstanceTransferResponse } 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';
......@@ -243,6 +243,11 @@ export const RESOURCES = {
token_instance_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_stats: {
......@@ -317,7 +322,8 @@ 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' | 'tokens' |
'token_instance_transfers';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
......@@ -367,6 +373,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_instance_transfers' ? TokenInstanceTransferResponse :
Q extends 'tokens' ? TokensResponse :
Q extends 'search' ? SearchResult :
Q extends 'contract' ? SmartContract :
......
......@@ -46,7 +46,6 @@ export type TokenTransferPagination = {
block_number: number;
index: number;
items_count: number;
transaction_hash: string;
}
export interface TokenTransferResponse {
......
import type { AddressParam } from './addressParams';
import type { TokenInfo, TokenType } from './tokenInfo';
import type { TokenTransfer } from './tokenTransfer';
export type TokensResponse = {
items: Array<TokenInfo>;
......@@ -27,3 +28,15 @@ export interface TokenInstance {
export interface TokenInstanceTransfersCount {
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 = () => {
});
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 }/> },
];
......
import { Box, Icon, Link, chakra } from '@chakra-ui/react';
import { Box, Icon, chakra } from '@chakra-ui/react';
import React from 'react';
import nftPlaceholder from 'icons/nft_shield.svg';
import link from 'lib/link/link';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
hash: string;
id: string;
className?: string;
isDisabled?: boolean;
}
const TokenTransferNft = ({ hash, id, className }: Props) => {
const TokenTransferNft = ({ hash, id, className, isDisabled }: Props) => {
const Component = isDisabled ? Box : LinkInternal;
return (
<Link
href={ link('token_instance_item', { hash, id }) }
<Component
href={ isDisabled ? undefined : link('token_instance_item', { hash, id }) }
overflow="hidden"
whiteSpace="nowrap"
display="flex"
......@@ -26,7 +30,7 @@ const TokenTransferNft = ({ hash, id, className }: Props) => {
<Box maxW="calc(100% - 34px)">
<HashStringShortenDynamic hash={ id } fontWeight={ 500 }/>
</Box>
</Link>
</Component>
);
};
......
......@@ -4,7 +4,6 @@ import { useRouter } from 'next/router';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { TokenInfo } from 'types/api/tokenInfo';
import type { TokenTransferResponse } from 'types/api/tokenTransfer';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
......@@ -23,14 +22,14 @@ import TokenTransferList from 'ui/token/TokenTransfer/TokenTransferList';
import TokenTransferTable from 'ui/token/TokenTransfer/TokenTransferTable';
type Props = {
token?: TokenInfo;
transfersQuery: UseQueryResult<TokenTransferResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
tokenId?: string;
}
const TokenTransfer = ({ transfersQuery, token }: Props) => {
const TokenTransfer = ({ transfersQuery, tokenId }: Props) => {
const isMobile = useIsMobile();
const router = useRouter();
const { isError, isLoading, data, pagination, isPaginationVisible } = transfersQuery;
......@@ -92,12 +91,10 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
<TokenTransferTable
data={ items }
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 }
socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount }
tokenId={ tokenId }
/>
</Hide>
<Show below="lg" ssr={ false }>
......@@ -110,7 +107,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
borderBottomRadius={ 0 }
/>
) }
<TokenTransferList data={ items }/>
<TokenTransferList data={ items } tokenId={ tokenId }/>
</Show>
</>
);
......
......@@ -7,15 +7,17 @@ import TokenTransferListItem from 'ui/token/TokenTransfer/TokenTransferListItem'
interface Props {
data: Array<TokenTransfer>;
tokenId?: string;
}
const TokenTransferList = ({ data }: Props) => {
const TokenTransferList = ({ data, tokenId }: Props) => {
return (
<Box>
{ data.map((item, index) => (
<TokenTransferListItem
key={ index }
{ ...item }
tokenId={ tokenId }
/>
)) }
</Box>
......
......@@ -14,7 +14,7 @@ import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
type Props = TokenTransfer;
type Props = TokenTransfer & {tokenId?: string};
const TokenTransferListItem = ({
token,
......@@ -24,6 +24,7 @@ const TokenTransferListItem = ({
to,
method,
timestamp,
tokenId,
}: Props) => {
const value = (() => {
if (!('value' in total)) {
......@@ -78,7 +79,7 @@ const TokenTransferListItem = ({
</Flex>
) }
{ '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>
);
};
......
import { Table, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import React from 'react';
import type { TokenInfo } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
......@@ -12,13 +11,16 @@ import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableIte
interface Props {
data: Array<TokenTransfer>;
top: number;
token: TokenInfo;
showSocketInfo: boolean;
socketInfoAlert?: string;
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 (
<Table variant="simple" size="sm">
<Thead top={ top }>
......@@ -28,8 +30,8 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert,
<Th width="148px">From</Th>
<Th width="36px" px={ 0 }/>
<Th width="218px" >To</Th>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && <Th width="20%" isNumeric={ token.type === 'ERC-721' }>Token ID</Th> }
{ (token.type === 'ERC-20' || token.type === 'ERC-1155') && <Th width="20%" isNumeric>Value { trimTokenSymbol(token.symbol) }</Th> }
{ (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
{ (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric>Value { trimTokenSymbol(tokenSymbol) }</Th> }
</Tr>
</Thead>
<Tbody>
......@@ -48,7 +50,7 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert,
</Tr>
) }
{ data.map((item, index) => (
<TokenTransferTableItem key={ index } { ...item }/>
<TokenTransferTableItem key={ index } { ...item } tokenId={ tokenId }/>
)) }
</Tbody>
</Table>
......
......@@ -11,7 +11,7 @@ import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
type Props = TokenTransfer
type Props = TokenTransfer & { tokenId?: string }
const TokenTransferTableItem = ({
token,
......@@ -21,6 +21,7 @@ const TokenTransferTableItem = ({
to,
method,
timestamp,
tokenId,
}: Props) => {
const value = (() => {
if (!('value' in total)) {
......@@ -82,7 +83,9 @@ const TokenTransferTableItem = ({
<TokenTransferNft
hash={ token.address }
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>
......
......@@ -7,14 +7,18 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TokenLogo from 'ui/shared/TokenLogo';
import TokenInstanceDetails from 'ui/tokenInstance/TokenInstanceDetails';
import TokenInstanceSkeleton from 'ui/tokenInstance/TokenInstanceSkeleton';
import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer';
import TokenInstanceDetails from './TokenInstanceDetails';
import TokenInstanceSkeleton from './TokenInstanceSkeleton';
export type TokenTabs = 'token_transfers' | 'holders'
......@@ -25,6 +29,7 @@ const TokenInstanceContent = () => {
const hash = router.query.hash?.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');
......@@ -35,9 +40,19 @@ const TokenInstanceContent = () => {
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> = [
{ id: 'token_transfers', title: 'Token transfers', component: <span>Token transfers</span> },
{ id: 'holders', title: 'Holders', component: <span>Holders</span> },
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } tokenId={ id }/> },
// there is no api for this tab yet
// { id: 'holders', title: 'Holders', component: <span>Holders</span> },
{ id: 'metadata', title: 'Metadata', component: <span>Metadata</span> },
];
......@@ -81,6 +96,8 @@ const TokenInstanceContent = () => {
<RoutedTabs
tabs={ tabs }
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