Commit e4d901c8 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #532 from blockscout/address+token

Address+token
parents fe554059 f7334a74
......@@ -61,6 +61,10 @@ const oldUrls = [
oldPath: '/address/:id/validations',
newPath: `${ PATHS.address_index }?tab=blocks_validated`,
},
{
oldPath: '/address/:id/tokens/:hash/token-transfers',
newPath: `${ PATHS.address_index }?tab=token_transfers&token=:hash`,
},
];
async function redirects() {
......
......@@ -151,7 +151,7 @@ export const RESOURCES = {
address_token_transfers: {
path: '/api/v2/addresses/:id/token-transfers',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ 'filter' as const, 'type' as const ],
filterFields: [ 'filter' as const, 'type' as const, 'token' as const ],
},
address_blocks_validated: {
path: '/api/v2/addresses/:id/blocks-validated',
......
......@@ -81,8 +81,9 @@ export interface AddressTokenTransferResponse {
}
export type AddressTokenTransferFilters = {
filter: AddressFromToFilter;
type: Array<TokenType>;
filter?: AddressFromToFilter;
type?: Array<TokenType>;
token?: string;
}
export type AddressTokensFilter = {
......
......@@ -100,7 +100,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
title="Creator"
hint="Transaction and address of creation."
>
<AddressLink hash={ addressQuery.data.creator_address_hash } truncation="constant"/>
<AddressLink type="address" hash={ addressQuery.data.creator_address_hash } truncation="constant"/>
<Text whiteSpace="pre"> at txn </Text>
<AddressLink hash={ addressQuery.data.creation_tx_hash } type="transaction" truncation="constant"/>
</DetailsInfoItem>
......
import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { erc1155 } from 'mocks/tokens/tokenTransfer';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressTokenTransfers from './AddressTokenTransfers';
const API_URL = buildApiUrl('address_token_transfers', { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' }) +
'?token=0x1189a607CEac2f0E14867de4EB15b15C9FFB5859';
const hooksConfig = {
router: {
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', token: '0x1189a607CEac2f0E14867de4EB15b15C9FFB5859' },
},
};
test('with token filter and pagination +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ items: [ erc1155 ], next_page_params: { block_number: 1 } }),
}));
const component = await mount(
<TestApp>
<Box h={{ base: '134px', lg: 6 }}/>
<AddressTokenTransfers/>
</TestApp>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
test('with token filter and no pagination +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ items: [ erc1155 ] }),
}));
const component = await mount(
<TestApp>
<Box h={{ base: '134px', lg: 6 }}/>
<AddressTokenTransfers/>
</TestApp>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
import { Hide, Show, Text } from '@chakra-ui/react';
import { Flex, Hide, Icon, Show, Text, Tooltip, useColorModeValue } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -9,9 +9,11 @@ import type { AddressFromToFilter, AddressTokenTransferResponse } from 'types/ap
import type { TokenType } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import crossIcon from 'icons/cross.svg';
import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import useSocketChannel from 'lib/socket/useSocketChannel';
......@@ -24,6 +26,7 @@ import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TokenLogo from 'ui/shared/TokenLogo';
import { flattenTotal } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
......@@ -62,20 +65,26 @@ const matchFilters = (filters: Filters, tokenTransfer: TokenTransfer, address?:
const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter();
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const currentAddress = router.query.id?.toString();
const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const tokenFilter = router.query.token ? router.query.token.toString() : undefined;
const [ filters, setFilters ] = React.useState<Filters>(
{ type: getTokenFilterValue(router.query.type) || [], filter: getAddressFilterValue(router.query.filter) },
{
type: getTokenFilterValue(router.query.type) || [],
filter: getAddressFilterValue(router.query.filter),
},
);
const { isError, isLoading, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_token_transfers',
pathParams: { id: currentAddress },
filters: filters,
filters: tokenFilter ? { token: tokenFilter } : filters,
scrollRef,
});
......@@ -90,6 +99,13 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
setFilters((prevState) => ({ ...prevState, filter: filterVal }));
}, [ filters, onFilterChange ]);
const resetTokenFilter = React.useCallback(() => {
onFilterChange({});
}, [ onFilterChange ]);
const resetTokenIconColor = useColorModeValue('blue.600', 'blue.300');
const resetTokenIconHoverColor = useColorModeValue('blue.400', 'blue.200');
const handleNewSocketMessage: SocketMessage.AddressTokenTransfer['handler'] = (payload) => {
setSocketAlert('');
......@@ -132,7 +148,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
topic: `addresses:${ (router.query.id as string).toLowerCase() }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: pagination.page !== 1,
isDisabled: pagination.page !== 1 || Boolean(tokenFilter),
});
useSocketMessage({
......@@ -142,7 +158,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
});
const numActiveFilters = (filters.type?.length || 0) + (filters.filter ? 1 : 0);
const isActionBarHidden = !numActiveFilters && !data?.items.length;
const isActionBarHidden = !tokenFilter && !numActiveFilters && !data?.items.length;
const content = (() => {
if (isLoading) {
......@@ -180,13 +196,13 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
showTxInfo
top={ 80 }
enableTimeIncrement
showSocketInfo={ pagination.page === 1 }
showSocketInfo={ pagination.page === 1 && !tokenFilter }
socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount }
/>
</Hide>
<Show below="lg">
{ pagination.page === 1 && (
{ pagination.page === 1 && !tokenFilter && (
<SocketNewItemsNotice
url={ window.location.href }
num={ newItemsCount }
......@@ -206,18 +222,43 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
);
})();
const tokenFilterComponent = tokenFilter && (
<Flex alignItems="center" py={ 1 } flexWrap="wrap" mb={{ base: isPaginationVisible ? 6 : 3, lg: 0 }}>
Filtered by token
<TokenLogo hash={ tokenFilter } boxSize={ 6 } mx={ 2 }/>
{ isMobile ? tokenFilter.slice(0, 4) + '...' + tokenFilter.slice(-4) : tokenFilter }
<Tooltip label="Reset filter">
<Flex>
<Icon
as={ crossIcon }
boxSize={ 6 }
ml={ 1 }
color={ resetTokenIconColor }
cursor="pointer"
_hover={{ color: resetTokenIconHoverColor }}
onClick={ resetTokenFilter }
/>
</Flex>
</Tooltip>
</Flex>
);
return (
<>
{ isMobile && tokenFilterComponent }
{ !isActionBarHidden && (
<ActionBar mt={ -6 }>
<TokenTransferFilter
defaultTypeFilters={ filters.type }
onTypeFilterChange={ handleTypeFilterChange }
appliedFiltersNum={ numActiveFilters }
withAddressFilter
onAddressFilterChange={ handleAddressFilterChange }
defaultAddressFilter={ filters.filter }
/>
{ !isMobile && tokenFilterComponent }
{ !tokenFilter && (
<TokenTransferFilter
defaultTypeFilters={ filters.type }
onTypeFilterChange={ handleTypeFilterChange }
appliedFiltersNum={ numActiveFilters }
withAddressFilter
onAddressFilterChange={ handleAddressFilterChange }
defaultAddressFilter={ filters.filter }
/>
) }
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
) }
......
......@@ -123,7 +123,7 @@ const ContractCode = () => {
<span>Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB </span>
<Address>
<AddressIcon address={{ hash: data.verified_twin_address_hash, is_contract: true, implementation_name: null }}/>
<AddressLink hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/>
<AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/>
</Address>
<chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span>
<Link href={ link('address_contract_verification', { id: data.verified_twin_address_hash }) }>Verify & Publish</Link>
......@@ -135,7 +135,7 @@ const ContractCode = () => {
<span>Minimal Proxy Contract for </span>
<Address>
<AddressIcon address={{ hash: data.minimal_proxy_address_hash, is_contract: true, implementation_name: null }}/>
<AddressLink hash={ data.minimal_proxy_address_hash } truncation="constant" ml={ 2 }/>
<AddressLink type="address" hash={ data.minimal_proxy_address_hash } truncation="constant" ml={ 2 }/>
</Address>
<span>. </span>
<Box>
......
......@@ -36,7 +36,7 @@ const ContractConnectWallet = () => {
<Flex alignItems="center">
<span>Connected to </span>
<AddressIcon address={{ hash: address, is_contract: false, implementation_name: null }} mx={ 2 }/>
<AddressLink fontWeight={ 600 } hash={ address } truncation={ isMobile ? 'constant' : 'dynamic' }/>
<AddressLink type="address" fontWeight={ 600 } hash={ address } truncation={ isMobile ? 'constant' : 'dynamic' }/>
</Flex>
<Button onClick={ handleDisconnect } size="sm" variant="outline">Disconnect</Button>
</Flex>
......
......@@ -24,7 +24,7 @@ const ContractImplementationAddress = ({ hash }: Props) => {
return (
<Address whiteSpace="pre-wrap" flexWrap="wrap" mb={ 6 }>
<span>Implementation address: </span>
<AddressLink hash={ data.implementation_address }/>
<AddressLink type="address" hash={ data.implementation_address }/>
</Address>
);
};
......
......@@ -34,7 +34,7 @@ const ContractMethodStatic = ({ data }: Props) => {
const content = (() => {
if (data.type === 'address' && data.value) {
return <AddressLink hash={ data.value }/>;
return <AddressLink type="address" hash={ data.value }/>;
}
return <chakra.span wordBreak="break-all">({ data.type }): { value }</chakra.span>;
......
......@@ -54,7 +54,7 @@ const TxInternalsListItem = ({
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ isOut }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ isOut }/>
</Address>
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut }/> :
......@@ -62,7 +62,7 @@ const TxInternalsListItem = ({
}
<Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash } isDisabled={ isIn }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ toData.hash } isDisabled={ isIn }/>
</Address>
</Box>
<HStack spacing={ 3 }>
......
......@@ -62,7 +62,7 @@ const AddressIntTxsTableItem = ({
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ isOut }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ isOut }/>
</Address>
</Td>
<Td px={ 0 } verticalAlign="middle">
......@@ -74,7 +74,7 @@ const AddressIntTxsTableItem = ({
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon address={ toData }/>
<AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 } isDisabled={ isIn }/>
<AddressLink type="address" hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 } isDisabled={ isIn }/>
</Address>
</Td>
<Td isNumeric verticalAlign="middle">
......
......@@ -129,7 +129,7 @@ const BlockDetails = () => {
hint="A block producer who successfully included the block onto the blockchain."
columnGap={ 1 }
>
<AddressLink hash={ data.miner.hash }/>
<AddressLink type="address" hash={ data.miner.hash }/>
{ data.miner.name && <Text>{ `(${ capitalize(validatorTitle) }: ${ data.miner.name })` }</Text> }
{ /* api doesn't return the block processing time yet */ }
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
......
......@@ -48,7 +48,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>{ capitalize(getNetworkValidatorTitle()) }</Text>
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/>
<AddressLink type="address" alias={ data.miner.name } hash={ data.miner.hash } truncation="constant"/>
</Flex>
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Txn</Text>
......
......@@ -50,7 +50,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Td>
<Td fontSize="sm">{ data.size.toLocaleString('en') }</Td>
<Td fontSize="sm">
<AddressLink alias={ data.miner.name } hash={ data.miner.hash } truncation="constant" display="inline-flex" maxW="100%"/>
<AddressLink type="address" alias={ data.miner.name } hash={ data.miner.hash } truncation="constant" display="inline-flex" maxW="100%"/>
</Td>
<Td isNumeric fontSize="sm">{ data.tx_count }</Td>
<Td fontSize="sm">
......
......@@ -61,7 +61,7 @@ const LatestBlocksItem = ({ block, h }: Props) => {
<GridItem>Reward</GridItem>
<GridItem><Text variant="secondary">{ totalReward.toFixed() }</Text></GridItem>
<GridItem>Miner</GridItem>
<GridItem><AddressLink alias={ block.miner.name } hash={ block.miner.hash } truncation="constant" maxW="100%"/></GridItem>
<GridItem><AddressLink type="address" alias={ block.miner.name } hash={ block.miner.hash } truncation="constant" maxW="100%"/></GridItem>
</Grid>
</Box>
);
......
......@@ -111,6 +111,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Address>
<AddressIcon address={ tx.from }/>
<AddressLink
type="address"
hash={ tx.from.hash }
alias={ tx.from.name }
fontWeight="500"
......@@ -128,6 +129,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Address>
<AddressIcon address={ dataTo }/>
<AddressLink
type="address"
hash={ dataTo.hash }
alias={ dataTo.name }
fontWeight="500"
......
......@@ -43,7 +43,7 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
<Address>
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" whiteSpace="nowrap" overflow="hidden">
<AddressLink hash={ data.address } fontWeight={ 700 } display="block" w="100%"/>
<AddressLink type="address" hash={ data.address } fontWeight={ 700 } display="block" w="100%"/>
</Box>
</Address>
);
......
......@@ -18,6 +18,10 @@ const ActionBar = ({ children, className }: Props) => {
const isSticky = useIsSticky(ref, TOP_UP + 5);
const bgColor = useColorModeValue('white', 'black');
if (!React.Children.toArray(children).filter(Boolean).length) {
return null;
}
return (
<Flex
className={ className }
......
......@@ -18,7 +18,7 @@ const AddressSnippet = ({ address, subtitle }: Props) => {
<Box maxW="100%">
<Address>
<AddressIcon address={ address }/>
<AddressLink hash={ address.hash } fontWeight="600" ml={ 2 }/>
<AddressLink type="address" hash={ address.hash } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address.hash } ml={ 1 }/>
</Address>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> }
......
......@@ -11,7 +11,7 @@ import {
} from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/styled-system';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import type { RoutedTab } from './types';
......@@ -43,6 +43,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
const [ activeTabIndex, setActiveTabIndex ] = useState<number>(tabs.length + 1);
const isMobile = useIsMobile();
const tabsRef = useRef<HTMLDivElement>(null);
const { tabsCut, tabsList, tabsRefs, listRef, rightSlotRef } = useAdaptiveTabs(tabs, isMobile);
const isSticky = useIsSticky(listRef, 5, stickyEnabled);
const listBgColor = useColorModeValue('white', 'black');
......@@ -57,6 +58,23 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
);
}, [ tabs, router ]);
useEffect(() => {
if (router.query.scroll_to_tabs) {
tabsRef?.current?.scrollIntoView(true);
delete router.query.scroll_to_tabs;
router.push(
{
pathname: router.pathname,
query: router.query,
},
undefined,
{ shallow: true },
);
}
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (router.isReady) {
let tabIndex = 0;
......@@ -104,6 +122,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
index={ activeTabIndex }
position="relative"
size={ themeProps.size || 'md' }
ref={ tabsRef }
>
<TabList
marginBottom={{ base: 6, lg: 8 }}
......
......@@ -81,7 +81,7 @@ const TokenTransferListItem = ({
<Flex w="100%" columnGap={ 3 }>
<Address width={ addressWidth }>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ baseAddress === from.hash }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } isDisabled={ baseAddress === from.hash }/>
</Address>
{ baseAddress ?
<InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center"/> :
......@@ -89,7 +89,7 @@ const TokenTransferListItem = ({
}
<Address width={ addressWidth }>
<AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } isDisabled={ baseAddress === to.hash }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ to.hash } isDisabled={ baseAddress === to.hash }/>
</Address>
</Flex>
{ value && (
......
......@@ -70,7 +70,7 @@ const TokenTransferTableItem = ({
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ baseAddress === from.hash }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } isDisabled={ baseAddress === from.hash }/>
</Address>
</Td>
{ baseAddress && (
......@@ -81,7 +81,7 @@ const TokenTransferTableItem = ({
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 } isDisabled={ baseAddress === to.hash }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 } isDisabled={ baseAddress === to.hash }/>
</Address>
</Td>
<Td isNumeric verticalAlign="top" lineHeight="30px">
......
......@@ -9,32 +9,49 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TruncatedTextTooltip from '../TruncatedTextTooltip';
interface Props {
type?: 'address' | 'transaction' | 'token' | 'block' | 'token_instance_item';
alias?: string | null;
type CommonProps = {
className?: string;
hash: string;
truncation?: 'constant' | 'dynamic'| 'none';
fontWeight?: string;
id?: string;
target?: HTMLAttributeAnchorTarget;
isDisabled?: boolean;
fontWeight?: string;
alias?: string | null;
}
type AddressTokenTxProps = {
type: 'address' | 'token' | 'transaction';
hash: 'hash';
}
const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight, target = '_self', isDisabled }: Props) => {
type BlockProps = {
type: 'block';
hash: string;
id: string;
}
type AddressTokenProps = {
type: 'address_token';
hash: string;
tokenHash: string;
}
type Props = CommonProps & (AddressTokenTxProps | BlockProps | AddressTokenProps);
const AddressLink = (props: Props) => {
const { alias, type, className, truncation = 'dynamic', hash, fontWeight, target = '_self', isDisabled } = props;
const isMobile = useIsMobile();
let url;
if (type === 'transaction') {
url = link('tx', { id: id || hash });
url = link('tx', { id: hash });
} else if (type === 'token') {
url = link('token_index', { hash: id || hash });
} else if (type === 'token_instance_item') {
url = link('token_instance_item', { hash, id });
url = link('token_index', { hash: hash });
} else if (type === 'block') {
url = link('block', { id: id || hash });
url = link('block', { id: props.id });
} else if (type === 'address_token') {
url = link('address_index', { id: hash }, { tab: 'token_transfers', token: props.tokenHash, scroll_to_tabs: 'true' });
} else {
url = link('address_index', { id: id || hash });
url = link('address_index', { id: hash });
}
const content = (() => {
......@@ -55,11 +72,11 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
}
switch (truncation) {
case 'constant':
return <HashStringShorten hash={ id || hash } isTooltipDisabled={ isMobile }/>;
return <HashStringShorten hash={ hash } isTooltipDisabled={ isMobile }/>;
case 'dynamic':
return <HashStringShortenDynamic hash={ id || hash } fontWeight={ fontWeight } isTooltipDisabled={ isMobile }/>;
return <HashStringShortenDynamic hash={ hash } fontWeight={ fontWeight } isTooltipDisabled={ isMobile }/>;
case 'none':
return <span>{ id || hash }</span>;
return <span>{ hash }</span>;
}
})();
......
......@@ -159,7 +159,7 @@ const LogDecodedInputData = ({ data }: Props) => {
<TableRow key={ name } name={ name } type={ type } isLast={ index === data.parameters.length - 1 } indexed={ indexed }>
{ type === 'address' ? (
<Address justifyContent="space-between">
<AddressLink hash={ value }/>
<AddressLink type="address" hash={ value }/>
<CopyToClipboard text={ value }/>
</Address>
) : (
......
......@@ -53,7 +53,7 @@ const TxLogItem = ({ address, index, topics, data, decoded, type }: Props) => {
<GridItem display="flex" alignItems="center">
<Address mr={{ base: 9, lg: 0 }}>
<AddressIcon address={ address }/>
<AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/>
<AddressLink type="address" hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address>
{ /* api doesn't have find topic feature yet */ }
{ /* <Tooltip label="Find matches topic">
......
......@@ -51,7 +51,7 @@ const LogTopic = ({ hex, index }: Props) => {
case 'address': {
return (
<Address>
<AddressLink hash={ value }/>
<AddressLink type="address" hash={ value }/>
<CopyToClipboard text={ value }/>
</Address>
);
......
......@@ -48,7 +48,7 @@ const TokenContractInfo = ({ tokenQuery }: Props) => {
return (
<Flex alignItems="center">
<AddressContractIcon/>
<AddressLink hash={ hash } ml={ 2 } truncation={ isMobile ? 'constant' : 'none' }/>
<AddressLink type="address" hash={ hash } ml={ 2 } truncation={ isMobile ? 'constant' : 'none' }/>
<CopyToClipboard text={ hash } ml={ 1 }/>
{ contractQuery.data?.token && <AddressAddToMetaMask token={ contractQuery.data?.token } ml={ 2 }/> }
<AddressQrCode hash={ hash } ml={ 2 }/>
......
......@@ -22,7 +22,7 @@ const TokenHoldersListItem = ({ holder, token }: Props) => {
<ListItemMobile rowGap={ 3 }>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ holder.address }/>
<AddressLink ml={ 2 } fontWeight="700" hash={ holder.address.hash } alias={ holder.address.name } flexGrow={ 1 }/>
<AddressLink type="address" ml={ 2 } fontWeight="700" hash={ holder.address.hash } alias={ holder.address.name } flexGrow={ 1 }/>
</Address>
<Flex justifyContent="space-between" alignItems="center" width="100%">
{ quantity }
......
......@@ -22,7 +22,7 @@ const TokenTransferTableItem = ({ holder, token }: Props) => {
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ holder.address }/>
<AddressLink ml={ 2 } fontWeight="700" hash={ holder.address.hash } alias={ holder.address.name } flexGrow={ 1 }/>
<AddressLink type="address" ml={ 2 } fontWeight="700" hash={ holder.address.hash } alias={ holder.address.name } flexGrow={ 1 }/>
</Address>
</Td>
<Td isNumeric>
......
......@@ -61,12 +61,12 @@ const TokenTransferListItem = ({
<Flex w="100%" columnGap={ 3 }>
<Address width="50%">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } type="address_token" tokenHash={ token.address }/>
</Address>
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
<Address width="50%">
<AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } type="address_token" tokenHash={ token.address }/>
</Address>
</Flex>
{ value && (
......
......@@ -46,7 +46,16 @@ const TokenTransferTableItem = ({
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 } truncation="constant"/>
<AddressLink
ml={ 2 }
flexGrow={ 1 }
fontWeight="500"
type="address_token"
hash={ from.hash }
alias={ from.name }
tokenHash={ token.address }
truncation="constant"
/>
</Address>
</Td>
<Td px={ 0 }>
......@@ -55,7 +64,16 @@ const TokenTransferTableItem = ({
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 } truncation="constant"/>
<AddressLink
ml={ 2 }
flexGrow={ 1 }
fontWeight="500"
type="address_token"
hash={ to.hash }
alias={ to.name }
tokenHash={ token.address }
truncation="constant"
/>
</Address>
</Td>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && (
......
......@@ -197,7 +197,7 @@ const TxDetails = () => {
>
<Address>
<AddressIcon address={ data.from }/>
<AddressLink ml={ 2 } hash={ data.from.hash }/>
<AddressLink type="address" ml={ 2 } hash={ data.from.hash }/>
<CopyToClipboard text={ data.from.hash }/>
</Address>
{ data.from.name && <Text>{ data.from.name }</Text> }
......@@ -216,7 +216,7 @@ const TxDetails = () => {
{ data.to && data.to.hash ? (
<Address alignItems="center">
<AddressIcon address={ toAddress }/>
<AddressLink ml={ 2 } hash={ toAddress.hash }/>
<AddressLink type="address" ml={ 2 } hash={ toAddress.hash }/>
{ executionSuccessBadge }
{ executionFailedBadge }
<CopyToClipboard text={ toAddress.hash }/>
......@@ -224,7 +224,7 @@ const TxDetails = () => {
) : (
<Flex width={{ base: '100%', lg: 'auto' }} whiteSpace="pre" alignItems="center">
<span>[Contract </span>
<AddressLink hash={ toAddress.hash }/>
<AddressLink type="address" hash={ toAddress.hash }/>
<span> created]</span>
{ executionSuccessBadge }
{ executionFailedBadge }
......
......@@ -77,9 +77,9 @@ const TxDetailsTokenTransfer = ({ token, total, to, from }: Props) => {
flexDir={ isColumnLayout ? 'column' : 'row' }
>
<Flex alignItems="center">
<AddressLink fontWeight="500" hash={ from.hash } truncation="constant"/>
<AddressLink type="address" fontWeight="500" hash={ from.hash } truncation="constant"/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
<AddressLink fontWeight="500" hash={ to.hash } truncation="constant"/>
<AddressLink type="address" fontWeight="500" hash={ to.hash } truncation="constant"/>
</Flex>
<Flex flexDir="column" rowGap={ 5 }>
{ content }
......
......@@ -28,12 +28,12 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address>
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ toData.hash }/>
</Address>
</Box>
<HStack spacing={ 3 }>
......
......@@ -33,7 +33,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
<AddressLink type="address" ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address>
</Td>
<Td px={ 0 } verticalAlign="middle">
......@@ -42,7 +42,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon address={ toData }/>
<AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/>
<AddressLink type="address" hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/>
</Address>
</Td>
<Td isNumeric verticalAlign="middle">
......
......@@ -53,7 +53,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
<Address flexGrow={ 1 }>
{ /* ??? */ }
{ /* <AddressIcon hash={ address }/> */ }
<AddressLink hash={ address } ml={ 2 }/>
<AddressLink type="address" hash={ address } ml={ 2 }/>
</Address>
</Flex>
{ hasStorageData && (
......
......@@ -60,7 +60,7 @@ const TxStateTableItem = ({ txStateItem }: { txStateItem: TTxStateItem }) => {
<Address height="30px">
{ /* ??? */ }
{ /* <AddressIcon hash={ txStateItem.address }/> */ }
<AddressLink hash={ txStateItem.address } fontWeight="500" truncation="constant" ml={ 2 }/>
<AddressLink type="address" hash={ txStateItem.address } fontWeight="500" truncation="constant" ml={ 2 }/>
</Address>
</Td>
<Td border={ 0 } lineHeight="30px"><Link>{ txStateItem.miner }</Link></Td>
......
......@@ -104,6 +104,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
<Address width={ `calc((100%-${ currentAddress ? TAG_WIDTH : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon address={ tx.from }/>
<AddressLink
type="address"
hash={ tx.from.hash }
alias={ tx.from.name }
fontWeight="500"
......@@ -123,6 +124,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
<Address width="calc((100%-40px)/2)">
<AddressIcon address={ dataTo }/>
<AddressLink
type="address"
hash={ dataTo.hash }
alias={ dataTo.name }
fontWeight="500"
......
......@@ -51,7 +51,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressFrom = (
<Address>
<AddressIcon address={ tx.from }/>
<AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isOut }/>
<AddressLink type="address" hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isOut }/>
</Address>
);
......@@ -60,7 +60,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressTo = (
<Address>
<AddressIcon address={ dataTo }/>
<AddressLink hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isIn }/>
<AddressLink type="address" hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isIn }/>
</Address>
);
......
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