Commit 064c44d6 authored by isstuev's avatar isstuev

add counts to address tabs

parent 1c21cfe3
......@@ -14,6 +14,7 @@ import type {
import type {
Address,
AddressCounters,
AddressTabsCounters,
AddressTransactionsResponse,
AddressTokenTransferResponse,
AddressCoinBalanceHistoryResponse,
......@@ -248,6 +249,10 @@ export const RESOURCES = {
path: '/api/v2/addresses/:hash/counters',
pathParams: [ 'hash' as const ],
},
address_tabs_counters: {
path: '/api/v2/addresses/:hash/tabs-counters',
pathParams: [ 'hash' as const ],
},
// this resource doesn't have pagination, so causing huge problems on some addresses page
// address_token_balances: {
// path: '/api/v2/addresses/:hash/token-balances',
......@@ -577,6 +582,7 @@ Q extends 'tx_state_changes' ? TxStateChanges :
Q extends 'addresses' ? AddressesResponse :
Q extends 'address' ? Address :
Q extends 'address_counters' ? AddressCounters :
Q extends 'address_tabs_counters' ? AddressTabsCounters :
Q extends 'address_txs' ? AddressTransactionsResponse :
Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
......
import type { Address, AddressCoinBalanceHistoryItem, AddressCounters, AddressTokenBalance } from 'types/api/address';
import type { Address, AddressCoinBalanceHistoryItem, AddressCounters, AddressTabsCounters, AddressTokenBalance } from 'types/api/address';
import type { AddressesItem } from 'types/api/addresses';
import { ADDRESS_HASH } from './addressParams';
......@@ -42,6 +42,17 @@ export const ADDRESS_COUNTERS: AddressCounters = {
validations_count: '0',
};
export const ADDRESS_TABS_COUNTERS: AddressTabsCounters = {
coin_balances_count: 10,
internal_txs_count: 10,
logs_count: 10,
token_balances_count: 10,
token_transfers_count: 10,
transactions_count: 10,
validations_count: 10,
withdrawals_count: 10,
};
export const TOP_ADDRESS: AddressesItem = {
coin_balance: '11886682377162664596540805',
tx_count: '1835',
......
......@@ -12,6 +12,7 @@ export interface Address extends UserTags {
creator_address_hash: string | null;
creation_tx_hash: string | null;
exchange_rate: string | null;
// TODO: if we are happy with tabs-counters method, should we delete has_something fields?
has_beacon_chain_withdrawals?: boolean;
has_custom_methods_read: boolean;
has_custom_methods_write: boolean;
......@@ -149,3 +150,14 @@ export type AddressWithdrawalsItem = {
timestamp: string;
validator_index: number;
}
export type AddressTabsCounters = {
coin_balances_count: number;
internal_txs_count: number;
logs_count: number;
token_balances_count: number;
token_transfers_count: number;
transactions_count: number;
validations_count: number;
withdrawals_count: number;
}
......@@ -12,7 +12,7 @@ import { useAppContext } from 'lib/contexts/app';
import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString';
import { ADDRESS_INFO } from 'stubs/address';
import { ADDRESS_INFO, ADDRESS_TABS_COUNTERS } from 'stubs/address';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance';
import AddressContract from 'ui/address/AddressContract';
......@@ -54,24 +54,71 @@ const AddressPageContent = () => {
},
});
const addressTabsCountersQuery = useApiQuery('address_tabs_counters', {
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash),
placeholderData: ADDRESS_TABS_COUNTERS,
},
});
const contractTabs = useContractTabs(addressQuery.data);
const tabs: Array<RoutedTab> = React.useMemo(() => {
return [
{ id: 'txs', title: 'Transactions', component: <AddressTxs scrollRef={ tabsScrollRef }/> },
config.features.beaconChain.isEnabled && addressQuery.data?.has_beacon_chain_withdrawals ?
{ id: 'withdrawals', title: 'Withdrawals', component: <AddressWithdrawals scrollRef={ tabsScrollRef }/> } :
undefined,
addressQuery.data?.has_token_transfers ?
{ id: 'token_transfers', title: 'Token transfers', component: <AddressTokenTransfers scrollRef={ tabsScrollRef }/> } :
{
id: 'txs',
title: 'Transactions',
count: addressTabsCountersQuery.data?.transactions_count,
component: <AddressTxs scrollRef={ tabsScrollRef }/>,
},
config.features.beaconChain.isEnabled ?
{
id: 'withdrawals',
title: 'Withdrawals',
count: addressTabsCountersQuery.data?.withdrawals_count,
component: <AddressWithdrawals scrollRef={ tabsScrollRef }/>,
} :
undefined,
addressQuery.data?.has_tokens ? { id: 'tokens', title: 'Tokens', component: <AddressTokens/>, subTabs: TOKEN_TABS } : undefined,
{ id: 'internal_txns', title: 'Internal txns', component: <AddressInternalTxs scrollRef={ tabsScrollRef }/> },
{ id: 'coin_balance_history', title: 'Coin balance history', component: <AddressCoinBalance/> },
addressQuery.data?.has_validated_blocks ?
{ id: 'blocks_validated', title: 'Blocks validated', component: <AddressBlocksValidated scrollRef={ tabsScrollRef }/> } :
{
id: 'token_transfers',
title: 'Token transfers',
count: addressTabsCountersQuery.data?.token_transfers_count,
component: <AddressTokenTransfers scrollRef={ tabsScrollRef }/>,
},
{
id: 'tokens',
title: 'Tokens',
count: addressTabsCountersQuery.data?.token_balances_count,
component: <AddressTokens/>,
subTabs: TOKEN_TABS,
},
{
id: 'internal_txns',
title: 'Internal txns',
count: addressTabsCountersQuery.data?.internal_txs_count,
component: <AddressInternalTxs scrollRef={ tabsScrollRef }/>,
},
{
id: 'coin_balance_history',
title: 'Coin balance history',
count: addressTabsCountersQuery.data?.coin_balances_count,
component: <AddressCoinBalance/>,
},
config.chain.verificationType === 'validation' ?
{
id: 'blocks_validated',
title: 'Blocks validated',
count: addressTabsCountersQuery.data?.validations_count,
component: <AddressBlocksValidated scrollRef={ tabsScrollRef }/>,
} :
undefined,
addressQuery.data?.has_logs ? { id: 'logs', title: 'Logs', component: <AddressLogs scrollRef={ tabsScrollRef }/> } : undefined,
{
id: 'logs',
title: 'Logs',
count: addressTabsCountersQuery.data?.logs_count,
component: <AddressLogs scrollRef={ tabsScrollRef }/>,
},
addressQuery.data?.is_contract ? {
id: 'contract',
title: () => {
......@@ -90,7 +137,7 @@ const AddressPageContent = () => {
subTabs: contractTabs.map(tab => tab.id),
} : undefined,
].filter(Boolean);
}, [ addressQuery.data, contractTabs ]);
}, [ addressQuery.data, contractTabs, addressTabsCountersQuery.data ]);
const tags = (
<EntityTags
......@@ -134,7 +181,7 @@ const AddressPageContent = () => {
<AddressDetails addressQuery={ addressQuery } scrollRef={ tabsScrollRef }/>
{ /* should stay before tabs to scroll up with pagination */ }
<Box ref={ tabsScrollRef }></Box>
{ addressQuery.isPlaceholderData ? <TabsSkeleton tabs={ tabs }/> : content }
{ (addressQuery.isPlaceholderData || addressTabsCountersQuery.isPlaceholderData) ? <TabsSkeleton tabs={ tabs }/> : content }
</>
);
};
......
import type { SystemStyleObject } from '@chakra-ui/react';
import { Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
type Props = {
count?: number;
parentClassName: string;
}
const TasCounter = ({ count, parentClassName }: Props) => {
const zeroCountColor = useColorModeValue('blackAlpha.400', 'whiteAlpha.400');
if (count === undefined) {
return null;
}
const sx: SystemStyleObject = {};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
sx[`.${ parentClassName }:hover &`] = { color: 'inherit' };
return (
<Text
color={ count > 0 ? 'text_secondary' : zeroCountColor }
ml={ 1 }
sx={ sx }
transitionProperty="color"
transitionDuration="normal"
transitionTimingFunction="ease"
>
{ count }
</Text>
);
};
export default TasCounter;
......@@ -12,8 +12,11 @@ import React from 'react';
import type { MenuButton, TabItem } from './types';
import TabCounter from './TabCounter';
import { menuButton } from './utils';
const BUTTON_CLASSNAME = 'button-item';
interface Props {
tabs: Array<TabItem | MenuButton>;
activeTab?: TabItem;
......@@ -59,8 +62,10 @@ const TabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRef, act
isActive={ activeTab ? activeTab.id === tab.id : false }
justifyContent="left"
data-index={ index }
className={ BUTTON_CLASSNAME }
>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabCounter count={ tab.count } parentClassName={ BUTTON_CLASSNAME }/>
</Button>
)) }
</PopoverBody>
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { TabItem } from './types';
import TestApp from 'playwright/TestApp';
import TabsWithScroll from './TabsWithScroll';
test('with counters', async({ mount }) => {
const tabs: Array<TabItem> = [
{
id: 'tab1',
title: 'First tab',
count: 11,
component: null,
},
{
id: 'tab2',
title: 'Second tab',
count: 0,
component: null,
},
{
id: 'tab3',
title: 'Third tab',
count: 1,
component: null,
},
];
const component = await mount(
<TestApp>
<TabsWithScroll tabs={ tabs }/>
</TestApp>,
);
await component.getByText('Third tab').hover();
await expect(component).toHaveScreenshot();
});
......@@ -19,9 +19,12 @@ import { useScrollDirection } from 'lib/contexts/scrollDirection';
import useIsMobile from 'lib/hooks/useIsMobile';
import useIsSticky from 'lib/hooks/useIsSticky';
import TabCounter from './TabCounter';
import TabsMenu from './TabsMenu';
import useAdaptiveTabs from './useAdaptiveTabs';
const TAB_CLASSNAME = 'tab-item';
const hiddenItemStyles: StyleProps = {
position: 'absolute',
top: '-9999px',
......@@ -167,8 +170,10 @@ const TabsWithScroll = ({
{ ...(index < tabsCut ? {} : hiddenItemStyles) }
scrollSnapAlign="start"
flexShrink={ 0 }
className={ TAB_CLASSNAME }
>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
<TabCounter count={ tab.count } parentClassName={ TAB_CLASSNAME }/>
</Tab>
);
}) }
......
......@@ -3,6 +3,7 @@ import type React from 'react';
export interface TabItem {
id: string;
title: string | (() => React.ReactNode);
count?: number;
component: React.ReactNode;
}
......@@ -13,5 +14,6 @@ export type RoutedSubTab = Omit<TabItem, 'subTabs'>;
export interface MenuButton {
id: null;
title: string;
count?: never;
component: null;
}
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