mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
* add export for address txs * transfer all export from page to dialog * remove old route and components * improve file name and fix loading state of icon button * basic implementation * refetch item status * status indicator for download items * changes for multichain * handle too many requests for downloads error * update button styles and spacing * fix tests * disable async export for multichain * Fix CSV export cancel flow and stabilize context updates Applied via @cursor push command * test and review fixes * design fixes * refactor file names --------- Co-authored-by: Hanzo Dev <[email protected]>
206 lines
7.5 KiB
TypeScript
206 lines
7.5 KiB
TypeScript
import { HStack } from '@chakra-ui/react';
|
|
import { useRouter } from 'next/router';
|
|
import React from 'react';
|
|
|
|
import type * as multichain from '@blockscout/multichain-aggregator-types';
|
|
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
|
|
|
|
import CsvExport from 'client/features/csv-export/components/CsvExport';
|
|
import multichainConfig from 'configs/multichain';
|
|
import getSocketUrl from 'lib/api/getSocketUrl';
|
|
import { MultichainProvider } from 'lib/contexts/multichain';
|
|
import useIsMobile from 'lib/hooks/useIsMobile';
|
|
import getQueryParamString from 'lib/router/getQueryParamString';
|
|
import { SocketProvider } from 'lib/socket/context';
|
|
import { EmptyState } from 'toolkit/chakra/empty-state';
|
|
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
|
|
import AddressAdvancedFilterLink from 'ui/address/AddressAdvancedFilterLink';
|
|
import AddressTokenTransfersLocal from 'ui/address/AddressTokenTransfersLocal';
|
|
import useAddressTokenTransfersQuery from 'ui/address/useAddressTokenTransfersQuery';
|
|
import useAddressCountersQuery from 'ui/address/utils/useAddressCountersQuery';
|
|
import ChainSelect from 'ui/multichain/components/ChainSelect';
|
|
import Pagination from 'ui/shared/pagination/Pagination';
|
|
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
|
|
import { getTokenFilterValue } from 'ui/tokens/utils';
|
|
|
|
import ListCounterText from '../components/ListCounterText';
|
|
import getAvailableChainIds from './getAvailableChainIds';
|
|
|
|
export const ADDRESS_MULTICHAIN_TOKEN_TRANSFERS_TAB_IDS = [ 'token_transfers_cross_chain' as const, 'token_transfers_local' as const ];
|
|
const TABS_RIGHT_SLOT_PROPS = {
|
|
display: 'flex',
|
|
justifyContent: { base: 'flex-end', lg: 'space-between' },
|
|
alignItems: 'center',
|
|
ml: { base: 0, lg: 8 },
|
|
widthAllocation: 'available' as const,
|
|
};
|
|
const TAB_LIST_PROPS = {
|
|
marginBottom: 0,
|
|
pt: 6,
|
|
pb: 3,
|
|
marginTop: -6,
|
|
};
|
|
|
|
interface Props {
|
|
addressData: multichain.GetAddressResponse | undefined;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
const MultichainAddressTokenTransfers = ({ addressData, isLoading }: Props) => {
|
|
const router = useRouter();
|
|
const isMobile = useIsMobile();
|
|
|
|
const hash = getQueryParamString(router.query.hash);
|
|
const tab = getQueryParamString(router.query.tab) as typeof ADDRESS_MULTICHAIN_TOKEN_TRANSFERS_TAB_IDS[number] | 'token_transfers' | undefined;
|
|
const isLocalTab = tab === 'token_transfers_local' || tab === 'token_transfers';
|
|
|
|
const chainIds = React.useMemo(() => getAvailableChainIds(addressData), [ addressData ]);
|
|
|
|
const transfersQueryLocal = useAddressTokenTransfersQuery({
|
|
currentAddress: hash,
|
|
enabled: isLocalTab && !isLoading && chainIds.length > 0,
|
|
isMultichain: true,
|
|
chainIds,
|
|
});
|
|
|
|
const chainId = transfersQueryLocal.query.chainValue?.[0];
|
|
const chainData = multichainConfig()?.chains.find(chain => chain.id === chainId);
|
|
|
|
const countersQueryLocal = useAddressCountersQuery({
|
|
hash,
|
|
isLoading: transfersQueryLocal.query.isPlaceholderData,
|
|
isEnabled: isLocalTab && !isLoading && chainIds.length > 0,
|
|
chain: chainData,
|
|
});
|
|
|
|
const handleChainValueChange = React.useCallback(({ value }: { value: Array<string> }) => {
|
|
transfersQueryLocal.query.onChainValueChange({ value });
|
|
const chainConfig = multichainConfig()?.chains.find(chain => chain.id === value[0]);
|
|
const tokenTypes = getTokenFilterValue(router.query.type, chainConfig?.app_config);
|
|
if (tokenTypes) {
|
|
const chainsTokenTypes = transfersQueryLocal.filters.type.filter(type => tokenTypes.includes(type));
|
|
if (chainsTokenTypes.length < transfersQueryLocal.filters.type.length) {
|
|
transfersQueryLocal.onTypeFilterChange(chainsTokenTypes);
|
|
}
|
|
}
|
|
}, [ transfersQueryLocal, router.query.type ]);
|
|
|
|
const countersText = (() => {
|
|
if (isLocalTab) {
|
|
return (
|
|
<ListCounterText
|
|
key={ chainId }
|
|
value={ countersQueryLocal.data?.token_transfers_count }
|
|
isLoading={ countersQueryLocal.isPlaceholderData || transfersQueryLocal.query.isPlaceholderData }
|
|
type="transfer"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
})();
|
|
|
|
const rightSlot = (() => {
|
|
if (isLocalTab) {
|
|
if (chainIds.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const chainSelect = (
|
|
<ChainSelect
|
|
loading={ transfersQueryLocal.query.pagination.isLoading }
|
|
value={ transfersQueryLocal.query.chainValue }
|
|
onValueChange={ handleChainValueChange }
|
|
chainIds={ chainIds }
|
|
/>
|
|
);
|
|
|
|
if (isMobile) {
|
|
return chainSelect;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<HStack gap={ 2 }>
|
|
<TokenTransferFilter
|
|
defaultTypeFilters={ transfersQueryLocal.filters.type }
|
|
onTypeFilterChange={ transfersQueryLocal.onTypeFilterChange }
|
|
appliedFiltersNum={ transfersQueryLocal.filters.type.length + (transfersQueryLocal.filters.filter ? 1 : 0) }
|
|
withAddressFilter
|
|
onAddressFilterChange={ transfersQueryLocal.onAddressFilterChange }
|
|
defaultAddressFilter={ transfersQueryLocal.filters.filter }
|
|
isLoading={ transfersQueryLocal.query.isPlaceholderData }
|
|
chainConfig={ chainData?.app_config }
|
|
/>
|
|
{ chainSelect }
|
|
<CsvExport
|
|
type="address_token_transfers"
|
|
resourceName="general:address_csv_export_token_transfers"
|
|
pathParams={{ hash }}
|
|
queryParams={ transfersQueryLocal.filters.filter ? {
|
|
filter_type: 'address',
|
|
filter_value: transfersQueryLocal.filters.filter,
|
|
} : undefined }
|
|
chainData={ chainData }
|
|
loadingInitial={ transfersQueryLocal.query.pagination.isLoading }
|
|
/>
|
|
<AddressAdvancedFilterLink
|
|
isLoading={ transfersQueryLocal.query.isPlaceholderData }
|
|
address={ hash }
|
|
typeFilter={ transfersQueryLocal.filters.type }
|
|
directionFilter={ transfersQueryLocal.filters.filter }
|
|
chainData={ chainData }
|
|
/>
|
|
</HStack>
|
|
{ countersText }
|
|
<Pagination ml="auto" { ...transfersQueryLocal.query.pagination }/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
})();
|
|
|
|
const tabs: Array<TabItemRegular> = [
|
|
{
|
|
id: 'token_transfers_cross_chain',
|
|
title: 'Cross-chain',
|
|
component: <EmptyState type="coming_soon"/>,
|
|
},
|
|
{
|
|
id: [ 'token_transfers_local', 'token_transfers' ],
|
|
title: 'Local',
|
|
component: chainIds.length > 0 ? (
|
|
<MultichainProvider chainId={ transfersQueryLocal.query.chainValue?.[0] }>
|
|
<SocketProvider url={ getSocketUrl(chainData?.app_config) }>
|
|
{ isMobile && countersText }
|
|
<AddressTokenTransfersLocal
|
|
query={ transfersQueryLocal.query }
|
|
filters={ transfersQueryLocal.filters }
|
|
onTypeFilterChange={ transfersQueryLocal.onTypeFilterChange }
|
|
onAddressFilterChange={ transfersQueryLocal.onAddressFilterChange }
|
|
addressHash={ hash }
|
|
/>
|
|
</SocketProvider>
|
|
</MultichainProvider>
|
|
) : <p>There are no token transfers.</p>,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<RoutedTabs
|
|
variant="secondary"
|
|
size="sm"
|
|
tabs={ tabs }
|
|
isLoading={ isLoading }
|
|
defaultTabId="token_transfers_local"
|
|
rightSlot={ rightSlot }
|
|
rightSlotProps={ TABS_RIGHT_SLOT_PROPS }
|
|
listProps={ isMobile ? undefined : TAB_LIST_PROPS }
|
|
stickyEnabled={ !isMobile }
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default React.memo(MultichainAddressTokenTransfers);
|