mirror of
https://github.com/hanzonet/explore.git
synced 2026-10-30 18:16:57 +00:00
262 lines
8.6 KiB
TypeScript
262 lines
8.6 KiB
TypeScript
import { Box, Flex } from '@chakra-ui/react';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import { useRouter } from 'next/router';
|
|
import React from 'react';
|
|
|
|
import type { SocketMessage } from 'lib/socket/types';
|
|
import { AddressFromToFilterValues } from 'types/api/address';
|
|
import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/api/address';
|
|
import type { TokenType } from 'types/api/token';
|
|
import type { TokenTransfer } from 'types/api/tokenTransfer';
|
|
|
|
import { getResourceKey } from 'lib/api/useApiQuery';
|
|
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
|
|
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
|
|
import useIsMounted from 'lib/hooks/useIsMounted';
|
|
import getQueryParamString from 'lib/router/getQueryParamString';
|
|
import useSocketChannel from 'lib/socket/useSocketChannel';
|
|
import useSocketMessage from 'lib/socket/useSocketMessage';
|
|
import { TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
|
|
import { getTokenTransfersStub } from 'stubs/token';
|
|
import { apos } from 'toolkit/utils/htmlEntities';
|
|
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
|
|
import DataListDisplay from 'ui/shared/DataListDisplay';
|
|
import Pagination from 'ui/shared/pagination/Pagination';
|
|
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
|
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
|
|
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
|
|
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
|
|
import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable';
|
|
|
|
import AddressAdvancedFilterLink from './AddressAdvancedFilterLink';
|
|
import AddressCsvExportLink from './AddressCsvExportLink';
|
|
|
|
type Filters = {
|
|
type: Array<TokenType>;
|
|
filter: AddressFromToFilter | undefined;
|
|
};
|
|
|
|
const getTokenFilterValue = (getFilterValuesFromQuery<TokenType>).bind(null, TOKEN_TYPE_IDS);
|
|
const getAddressFilterValue = (getFilterValueFromQuery<AddressFromToFilter>).bind(null, AddressFromToFilterValues);
|
|
|
|
const OVERLOAD_COUNT = 75;
|
|
|
|
const matchFilters = (filters: Filters, tokenTransfer: TokenTransfer, address?: string) => {
|
|
if (filters.filter) {
|
|
if (filters.filter === 'from' && tokenTransfer.from.hash !== address) {
|
|
return false;
|
|
}
|
|
if (filters.filter === 'to' && tokenTransfer.to.hash !== address) {
|
|
return false;
|
|
}
|
|
}
|
|
if (filters.type && filters.type.length) {
|
|
if (!tokenTransfer.token || !filters.type.includes(tokenTransfer.token.type)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
type Props = {
|
|
shouldRender?: boolean;
|
|
isQueryEnabled?: boolean;
|
|
// for tests only
|
|
overloadCount?: number;
|
|
};
|
|
|
|
const AddressTokenTransfers = ({ overloadCount = OVERLOAD_COUNT, shouldRender = true, isQueryEnabled = true }: Props) => {
|
|
const router = useRouter();
|
|
const queryClient = useQueryClient();
|
|
const isMounted = useIsMounted();
|
|
|
|
const currentAddress = getQueryParamString(router.query.hash);
|
|
|
|
const [ socketAlert, setSocketAlert ] = React.useState('');
|
|
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
|
|
|
|
const [ filters, setFilters ] = React.useState<Filters>(
|
|
{
|
|
type: getTokenFilterValue(router.query.type) || [],
|
|
filter: getAddressFilterValue(router.query.filter),
|
|
},
|
|
);
|
|
|
|
const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({
|
|
resourceName: 'general:address_token_transfers',
|
|
pathParams: { hash: currentAddress },
|
|
filters,
|
|
options: {
|
|
enabled: isQueryEnabled,
|
|
placeholderData: getTokenTransfersStub(undefined, {
|
|
block_number: 7793535,
|
|
index: 46,
|
|
items_count: 50,
|
|
}),
|
|
},
|
|
});
|
|
|
|
const handleTypeFilterChange = React.useCallback((nextValue: Array<TokenType>) => {
|
|
onFilterChange({ ...filters, type: nextValue });
|
|
setFilters((prevState) => ({ ...prevState, type: nextValue }));
|
|
}, [ filters, onFilterChange ]);
|
|
|
|
const handleAddressFilterChange = React.useCallback((nextValue: string) => {
|
|
const filterVal = getAddressFilterValue(nextValue);
|
|
onFilterChange({ ...filters, filter: filterVal });
|
|
setFilters((prevState) => ({ ...prevState, filter: filterVal }));
|
|
}, [ filters, onFilterChange ]);
|
|
|
|
const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => {
|
|
setSocketAlert('');
|
|
|
|
const newItems: Array<TokenTransfer> = [];
|
|
let newCount = 0;
|
|
|
|
payload.token_transfers.forEach(transfer => {
|
|
if (data?.items && data.items.length + newItems.length >= overloadCount) {
|
|
if (matchFilters(filters, transfer, currentAddress)) {
|
|
newCount++;
|
|
}
|
|
} else {
|
|
if (matchFilters(filters, transfer, currentAddress)) {
|
|
newItems.push(transfer);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (newCount > 0) {
|
|
setNewItemsCount(prev => prev + newCount);
|
|
}
|
|
|
|
if (newItems.length > 0) {
|
|
queryClient.setQueryData(
|
|
getResourceKey('general:address_token_transfers', { pathParams: { hash: currentAddress }, queryParams: { ...filters } }),
|
|
(prevData: AddressTokenTransferResponse | undefined) => {
|
|
if (!prevData) {
|
|
return;
|
|
}
|
|
|
|
return {
|
|
...prevData,
|
|
items: [
|
|
...newItems,
|
|
...prevData.items,
|
|
],
|
|
};
|
|
},
|
|
);
|
|
}
|
|
|
|
};
|
|
|
|
const handleSocketClose = React.useCallback(() => {
|
|
setSocketAlert('Connection is lost. Please refresh the page to load new token transfers.');
|
|
}, []);
|
|
|
|
const handleSocketError = React.useCallback(() => {
|
|
setSocketAlert('An error has occurred while fetching new token transfers. Please refresh the page.');
|
|
}, []);
|
|
|
|
const channel = useSocketChannel({
|
|
topic: `addresses:${ currentAddress.toLowerCase() }`,
|
|
onSocketClose: handleSocketClose,
|
|
onSocketError: handleSocketError,
|
|
isDisabled: pagination.page !== 1,
|
|
});
|
|
|
|
useSocketMessage({
|
|
channel,
|
|
event: 'token_transfer',
|
|
handler: handleNewSocketMessage,
|
|
});
|
|
|
|
if (!isMounted || !shouldRender) {
|
|
return null;
|
|
}
|
|
|
|
const numActiveFilters = (filters.type?.length || 0) + (filters.filter ? 1 : 0);
|
|
const isActionBarHidden = !numActiveFilters && !data?.items.length && !currentAddress;
|
|
|
|
const content = data?.items ? (
|
|
<>
|
|
<Box hideBelow="lg">
|
|
<TokenTransferTable
|
|
data={ data?.items }
|
|
baseAddress={ currentAddress }
|
|
showTxInfo
|
|
top={ isActionBarHidden ? 0 : ACTION_BAR_HEIGHT_DESKTOP }
|
|
enableTimeIncrement
|
|
showSocketInfo={ pagination.page === 1 }
|
|
socketInfoAlert={ socketAlert }
|
|
socketInfoNum={ newItemsCount }
|
|
isLoading={ isPlaceholderData }
|
|
/>
|
|
</Box>
|
|
<Box hideFrom="lg">
|
|
{ pagination.page === 1 && (
|
|
<SocketNewItemsNotice.Mobile
|
|
num={ newItemsCount }
|
|
alert={ socketAlert }
|
|
type="token_transfer"
|
|
isLoading={ isPlaceholderData }
|
|
/>
|
|
) }
|
|
<TokenTransferList
|
|
data={ data?.items }
|
|
baseAddress={ currentAddress }
|
|
showTxInfo
|
|
enableTimeIncrement
|
|
isLoading={ isPlaceholderData }
|
|
/>
|
|
</Box>
|
|
</>
|
|
) : null;
|
|
|
|
const actionBar = !isActionBarHidden ? (
|
|
<ActionBar mt={ -6 }>
|
|
<TokenTransferFilter
|
|
defaultTypeFilters={ filters.type }
|
|
onTypeFilterChange={ handleTypeFilterChange }
|
|
appliedFiltersNum={ numActiveFilters }
|
|
withAddressFilter
|
|
onAddressFilterChange={ handleAddressFilterChange }
|
|
defaultAddressFilter={ filters.filter }
|
|
isLoading={ isPlaceholderData }
|
|
/>
|
|
<Flex columnGap={{ base: 2, lg: 6 }} ml={{ base: 2, lg: 'auto' }} _empty={{ display: 'none' }}>
|
|
<AddressAdvancedFilterLink
|
|
isLoading={ isPlaceholderData }
|
|
address={ currentAddress }
|
|
typeFilter={ filters.type }
|
|
directionFilter={ filters.filter }
|
|
/>
|
|
<AddressCsvExportLink
|
|
address={ currentAddress }
|
|
params={{ type: 'token-transfers', filterType: 'address', filterValue: filters.filter }}
|
|
isLoading={ isPlaceholderData }
|
|
/>
|
|
</Flex>
|
|
<Pagination ml={{ base: 'auto', lg: 8 }} { ...pagination }/>
|
|
</ActionBar>
|
|
) : null;
|
|
|
|
return (
|
|
<DataListDisplay
|
|
isError={ isError }
|
|
itemsNum={ data?.items?.length }
|
|
emptyText="There are no token transfers."
|
|
filterProps={{
|
|
emptyFilteredText: `Couldn${ apos }t find any token transfer that matches your query.`,
|
|
hasActiveFilters: Boolean(numActiveFilters),
|
|
}}
|
|
actionBar={ actionBar }
|
|
>
|
|
{ content }
|
|
</DataListDisplay>
|
|
);
|
|
};
|
|
|
|
export default AddressTokenTransfers;
|