Commit ebd5aa67 authored by isstuev's avatar isstuev

tmp

parent f3f365b8
...@@ -8,6 +8,10 @@ export const token1: SearchResultToken = { ...@@ -8,6 +8,10 @@ export const token1: SearchResultToken = {
token_url: '/token/0x377c5F2B300B25a534d4639177873b7fEAA56d4B', token_url: '/token/0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
type: 'token' as const, type: 'token' as const,
icon_url: 'http://localhost:3000/token-icon.png', icon_url: 'http://localhost:3000/token-icon.png',
token_type: 'ERC-721',
total_supply: '10000001',
exchange_rate: null,
is_smart_contract_verified: true,
}; };
export const token2: SearchResultToken = { export const token2: SearchResultToken = {
...@@ -18,12 +22,17 @@ export const token2: SearchResultToken = { ...@@ -18,12 +22,17 @@ export const token2: SearchResultToken = {
token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9', token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
type: 'token' as const, type: 'token' as const,
icon_url: null, icon_url: null,
token_type: 'ERC-20',
total_supply: '10000001',
exchange_rate: '1.11',
is_smart_contract_verified: false,
}; };
export const block1: SearchResultBlock = { export const block1: SearchResultBlock = {
block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1', block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
block_number: 8198536, block_number: 8198536,
type: 'block' as const, type: 'block' as const,
timestamp: '2022-12-11T17:55:20Z',
url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1', url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
}; };
...@@ -31,6 +40,7 @@ export const address1: SearchResultAddressOrContract = { ...@@ -31,6 +40,7 @@ export const address1: SearchResultAddressOrContract = {
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
name: null, name: null,
type: 'address' as const, type: 'address' as const,
is_smart_contract_verified: false,
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
}; };
...@@ -38,6 +48,7 @@ export const contract1: SearchResultAddressOrContract = { ...@@ -38,6 +48,7 @@ export const contract1: SearchResultAddressOrContract = {
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
name: 'Unknown contract in this network', name: 'Unknown contract in this network',
type: 'contract' as const, type: 'contract' as const,
is_smart_contract_verified: true,
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
}; };
...@@ -45,12 +56,14 @@ export const label1: SearchResultLabel = { ...@@ -45,12 +56,14 @@ export const label1: SearchResultLabel = {
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
name: 'utko', name: 'utko',
type: 'label' as const, type: 'label' as const,
is_smart_contract_verified: true,
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
}; };
export const tx1: SearchResultTx = { export const tx1: SearchResultTx = {
tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
type: 'transaction' as const, type: 'transaction' as const,
timestamp: '2022-12-11T17:55:20Z',
url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
}; };
......
...@@ -10,6 +10,10 @@ export const SEARCH_RESULT_ITEM: SearchResultItem = { ...@@ -10,6 +10,10 @@ export const SEARCH_RESULT_ITEM: SearchResultItem = {
token_url: '/token/0x3714A8C7824B22271550894f7555f0a672f97809', token_url: '/token/0x3714A8C7824B22271550894f7555f0a672f97809',
type: 'token', type: 'token',
icon_url: null, icon_url: null,
is_smart_contract_verified: false,
exchange_rate: '1.11',
total_supply: null,
token_type: 'ERC-20',
}; };
export const SEARCH_RESULT_NEXT_PAGE_PARAMS: SearchResult['next_page_params'] = { export const SEARCH_RESULT_NEXT_PAGE_PARAMS: SearchResult['next_page_params'] = {
......
import type { TokenType } from 'types/api/token';
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract'; export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
export interface SearchResultToken { export interface SearchResultToken {
...@@ -8,12 +10,17 @@ export interface SearchResultToken { ...@@ -8,12 +10,17 @@ export interface SearchResultToken {
token_url: string; token_url: string;
address_url: string; address_url: string;
icon_url: string | null; icon_url: string | null;
token_type: TokenType;
exchange_rate: string | null;
total_supply: string | null;
is_smart_contract_verified: boolean;
} }
export interface SearchResultAddressOrContract { export interface SearchResultAddressOrContract {
type: 'address' | 'contract'; type: 'address' | 'contract';
name: string | null; name: string | null;
address: string; address: string;
is_smart_contract_verified: boolean;
url?: string; // not used by the frontend, we build the url ourselves url?: string; // not used by the frontend, we build the url ourselves
} }
...@@ -21,6 +28,7 @@ export interface SearchResultLabel { ...@@ -21,6 +28,7 @@ export interface SearchResultLabel {
type: 'label'; type: 'label';
address: string; address: string;
name: string; name: string;
is_smart_contract_verified: boolean;
url?: string; // not used by the frontend, we build the url ourselves url?: string; // not used by the frontend, we build the url ourselves
} }
...@@ -28,12 +36,14 @@ export interface SearchResultBlock { ...@@ -28,12 +36,14 @@ export interface SearchResultBlock {
type: 'block'; type: 'block';
block_number: number | string; block_number: number | string;
block_hash: string; block_hash: string;
timestamp: string;
url?: string; // not used by the frontend, we build the url ourselves url?: string; // not used by the frontend, we build the url ourselves
} }
export interface SearchResultTx { export interface SearchResultTx {
type: 'transaction'; type: 'transaction';
tx_hash: string; tx_hash: string;
timestamp: string;
url?: string; // not used by the frontend, we build the url ourselves url?: string; // not used by the frontend, we build the url ourselves
} }
......
...@@ -15,6 +15,39 @@ import Pagination from 'ui/shared/pagination/Pagination'; ...@@ -15,6 +15,39 @@ import Pagination from 'ui/shared/pagination/Pagination';
import Thead from 'ui/shared/TheadSticky'; import Thead from 'ui/shared/TheadSticky';
import Header from 'ui/snippets/header/Header'; import Header from 'ui/snippets/header/Header';
import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery'; import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery';
// eslint-disable-next-line import-helpers/order-imports
import * as searchMock from 'mocks/search/index';
const mock = [
searchMock.address1,
searchMock.block1,
searchMock.contract1,
searchMock.label1,
searchMock.token1,
searchMock.token2,
searchMock.tx1,
searchMock.address1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.block1,
searchMock.contract1,
searchMock.label1,
searchMock.token1,
searchMock.token2,
searchMock.tx1,
searchMock.address1,
searchMock.block1,
searchMock.contract1,
searchMock.label1,
searchMock.token1,
searchMock.token2,
];
const SearchResultsPageContent = () => { const SearchResultsPageContent = () => {
const router = useRouter(); const router = useRouter();
...@@ -68,7 +101,8 @@ const SearchResultsPageContent = () => { ...@@ -68,7 +101,8 @@ const SearchResultsPageContent = () => {
return ( return (
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
{ data.items.map((item, index) => ( { mock.map((item, index) => (
// { data.items.map((item, index) => (
<SearchResultListItem <SearchResultListItem
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index } key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
data={ item } data={ item }
...@@ -81,13 +115,15 @@ const SearchResultsPageContent = () => { ...@@ -81,13 +115,15 @@ const SearchResultsPageContent = () => {
<Table variant="simple" size="md" fontWeight={ 500 }> <Table variant="simple" size="md" fontWeight={ 500 }>
<Thead top={ pagination.isVisible ? 80 : 0 }> <Thead top={ pagination.isVisible ? 80 : 0 }>
<Tr> <Tr>
<Th width="50%">Search Result</Th> <Th width="30%">Search Result</Th>
<Th width="50%"/> <Th width="35%"/>
<Th width="35%"/>
<Th width="150px">Category</Th> <Th width="150px">Category</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ data.items.map((item, index) => ( { /* { data.items.map((item, index) => ( */ }
{ mock.map((item, index) => (
<SearchResultTableItem <SearchResultTableItem
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index } key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
data={ item } data={ item }
......
import { Flex, Icon, Box, chakra, Skeleton } from '@chakra-ui/react'; import { Flex, Grid, Icon, Box, Text, chakra, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -6,13 +6,16 @@ import type { SearchResultItem } from 'types/api/search'; ...@@ -6,13 +6,16 @@ import type { SearchResultItem } from 'types/api/search';
import blockIcon from 'icons/block.svg'; import blockIcon from 'icons/block.svg';
import labelIcon from 'icons/publictags.svg'; import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg';
import txIcon from 'icons/transactions.svg'; import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import highlightText from 'lib/highlightText'; import highlightText from 'lib/highlightText';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { saveToRecentKeywords } from 'lib/recentSearchKeywords'; import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShorten from 'ui/shared/HashStringShorten';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
...@@ -41,7 +44,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -41,7 +44,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
const name = data.name + (data.symbol ? ` (${ data.symbol })` : ''); const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
return ( return (
<Flex alignItems="flex-start" overflow="hidden"> <Flex alignItems="flex-start" flexGrow={ 1 } overflow="hidden">
<TokenLogo boxSize={ 6 } data={ data } flexShrink={ 0 } isLoading={ isLoading }/> <TokenLogo boxSize={ 6 } data={ data } flexShrink={ 0 } isLoading={ isLoading }/>
<LinkInternal <LinkInternal
ml={ 2 } ml={ 2 }
...@@ -50,6 +53,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -50,6 +53,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
wordBreak="break-all" wordBreak="break-all"
isLoading={ isLoading } isLoading={ isLoading }
onClick={ handleLinkClick } onClick={ handleLinkClick }
flexGrow={ 1 }
overflow="hidden" overflow="hidden"
> >
<Skeleton <Skeleton
...@@ -68,12 +72,15 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -68,12 +72,15 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
case 'address': { case 'address': {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase(); const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
return ( return (
<Flex alignItems="center" overflow="hidden">
<Address> <Address>
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/> <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"> <Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" whiteSpace="nowrap" overflow="hidden">
<AddressLink type="address" hash={ data.address } fontWeight={ 700 } display="block" w="100%" onClick={ handleLinkClick }/> <AddressLink type="address" hash={ data.address } fontWeight={ 700 } display="block" w="100%" onClick={ handleLinkClick }/>
</Box> </Box>
</Address> </Address>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
); );
} }
...@@ -104,6 +111,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -104,6 +111,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
fontWeight={ 700 } fontWeight={ 700 }
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } }) } href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } }) }
onClick={ handleLinkClick } onClick={ handleLinkClick }
mr={ 4 }
> >
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box> <Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
</LinkInternal> </LinkInternal>
...@@ -128,17 +136,49 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -128,17 +136,49 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
switch (data.type) { switch (data.type) {
case 'token': { case 'token': {
return ( return (
<Skeleton isLoaded={ !isLoading }> <Grid templateColumns="1fr auto" overflow="hidden" gap={ 5 }>
<HashStringShortenDynamic hash={ data.address }/> <Skeleton isLoaded={ !isLoading } overflow="hidden" display="flex" alignItems="center">
<HashStringShorten hash={ data.address }/>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Skeleton> </Skeleton>
{ data.token_type === 'ERC-20' && data.exchange_rate && (
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" fontWeight={ 700 }>
${ Number(data.exchange_rate).toLocaleString() }
</Text>
) }
{ data.token_type !== 'ERC-20' && data.total_supply && (
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" variant="secondary">
Items { Number(data.total_supply).toLocaleString() }
</Text>
) }
</Grid>
); );
} }
case 'block': { case 'block': {
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase(); const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
return ( return (
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" w="100%" whiteSpace="nowrap" overflow="hidden"> <Flex alignItems="center" justifyContent="space-between" w="100%">
<HashStringShortenDynamic hash={ data.block_hash }/> <Text variant="secondary" mr={ 2 }>{ dayjs(data.timestamp).format('llll') }</Text>
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" whiteSpace="nowrap" overflow="hidden">
<HashStringShorten hash={ data.block_hash }/>
</Box> </Box>
</Flex>
);
}
case 'transaction': {
return (
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
);
}
case 'label': {
return (
<Flex alignItems="center" justifyContent="space-between">
<Box overflow="hidden">
<HashStringShortenDynamic hash={ data.address }/>
</Box>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
); );
} }
case 'contract': case 'contract':
......
import { Tr, Td, Flex, Icon, Box, Skeleton } from '@chakra-ui/react'; import { chakra, Tr, Td, Text, Flex, Icon, Box, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
...@@ -6,7 +6,9 @@ import type { SearchResultItem } from 'types/api/search'; ...@@ -6,7 +6,9 @@ import type { SearchResultItem } from 'types/api/search';
import blockIcon from 'icons/block.svg'; import blockIcon from 'icons/block.svg';
import labelIcon from 'icons/publictags.svg'; import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg';
import txIcon from 'icons/transactions.svg'; import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import highlightText from 'lib/highlightText'; import highlightText from 'lib/highlightText';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { saveToRecentKeywords } from 'lib/recentSearchKeywords'; import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
...@@ -64,8 +66,25 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -64,8 +66,25 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td fontSize="sm" verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } whiteSpace="nowrap" overflow="hidden"> <Skeleton isLoaded={ !isLoading } whiteSpace="nowrap" overflow="hidden" display="flex" alignItems="center">
<Box overflow="hidden" whiteSpace="nowrap" w={ data.is_smart_contract_verified ? 'calc(100%-32px)' : 'unset' }>
<HashStringShortenDynamic hash={ data.address }/> <HashStringShortenDynamic hash={ data.address }/>
</Box>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Skeleton>
</Td>
<Td fontSize="sm" verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } whiteSpace="nowrap" overflow="hidden">
{ data.token_type === 'ERC-20' && data.exchange_rate && (
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" fontWeight={ 700 }>
${ Number(data.exchange_rate).toLocaleString() }
</Text>
) }
{ data.token_type !== 'ERC-20' && data.total_supply && (
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" variant="secondary">
Items { Number(data.total_supply).toLocaleString() }
</Text>
) }
</Skeleton> </Skeleton>
</Td> </Td>
</> </>
...@@ -93,9 +112,10 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -93,9 +112,10 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
<HashStringShortenDynamic hash={ data.address }/> <HashStringShortenDynamic hash={ data.address }/>
</Box> </Box>
</LinkInternal> </LinkInternal>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/> <span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
</Td> </Td>
</> </>
...@@ -103,13 +123,16 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -103,13 +123,16 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
} }
return ( return (
<Td colSpan={ 2 } fontSize="sm"> <Td colSpan={ 3 } fontSize="sm">
<Flex alignItems="center" overflow="hidden">
<Address> <Address>
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/> <AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
<mark> <mark>
<AddressLink hash={ data.address } type="address" fontWeight={ 700 } onClick={ handleLinkClick }/> <AddressLink hash={ data.address } type="address" fontWeight={ 700 } onClick={ handleLinkClick }/>
</mark> </mark>
</Address> </Address>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
</Td> </Td>
); );
} }
...@@ -121,7 +144,6 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -121,7 +144,6 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
<Flex alignItems="center"> <Flex alignItems="center">
<Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/> <Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<LinkInternal <LinkInternal
ml={ 2 }
href={ route({ pathname: '/address/[hash]', query: { hash: data.address } }) } href={ route({ pathname: '/address/[hash]', query: { hash: data.address } }) }
fontWeight={ 700 } fontWeight={ 700 }
wordBreak="break-all" wordBreak="break-all"
...@@ -132,10 +154,11 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -132,10 +154,11 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
</LinkInternal> </LinkInternal>
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } whiteSpace="nowrap" overflow="hidden"> <Flex alignItems="center" overflow="hidden">
<HashStringShortenDynamic hash={ data.address }/> <HashStringShortenDynamic hash={ data.address }/>
</Skeleton> { data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
</Td> </Td>
</> </>
); );
...@@ -163,20 +186,28 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -163,20 +186,28 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
<HashStringShortenDynamic hash={ data.block_hash }/> <HashStringShortenDynamic hash={ data.block_hash }/>
</Box> </Box>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle">
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</Td>
</> </>
); );
} }
case 'transaction': { case 'transaction': {
return ( return (
<>
<Td colSpan={ 2 } fontSize="sm"> <Td colSpan={ 2 } fontSize="sm">
<Flex alignItems="center"> <Flex alignItems="center">
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/> <Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<mark> <chakra.mark overflow="hidden">
<AddressLink hash={ data.tx_hash } type="transaction" fontWeight={ 700 } onClick={ handleLinkClick }/> <AddressLink display="block" hash={ data.tx_hash } type="transaction" fontWeight={ 700 } onClick={ handleLinkClick } truncation="dynamic"/>
</mark> </chakra.mark>
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle">
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</Td>
</>
); );
} }
} }
......
...@@ -28,7 +28,7 @@ test.beforeEach(async({ page }) => { ...@@ -28,7 +28,7 @@ test.beforeEach(async({ page }) => {
}); });
}); });
test('search by name +@mobile +@dark-mode', async({ mount, page }) => { test('search by token name +@mobile +@dark-mode', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
...@@ -36,6 +36,27 @@ test('search by name +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -36,6 +36,27 @@ test('search by name +@mobile +@dark-mode', async({ mount, page }) => {
items: [ items: [
searchMock.token1, searchMock.token1,
searchMock.token2, searchMock.token2,
],
}),
}));
await mount(
<TestApp>
<SearchBar/>
</TestApp>,
);
await page.getByPlaceholder(/search/i).type('o');
await page.waitForResponse(API_URL);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
test('search by contract name +@mobile +@dark-mode', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({
items: [
searchMock.contract1, searchMock.contract1,
], ],
}), }),
......
...@@ -13,7 +13,7 @@ import LinkInternal from 'ui/shared/LinkInternal'; ...@@ -13,7 +13,7 @@ import LinkInternal from 'ui/shared/LinkInternal';
import SearchBarInput from './SearchBarInput'; import SearchBarInput from './SearchBarInput';
import SearchBarRecentKeywords from './SearchBarRecentKeywords'; import SearchBarRecentKeywords from './SearchBarRecentKeywords';
import SearchBarSuggest from './SearchBarSuggest'; import SearchBarSuggest from './SearchBarSuggest/SearchBarSuggest';
import useSearchQuery from './useSearchQuery'; import useSearchQuery from './useSearchQuery';
type Props = { type Props = {
...@@ -130,7 +130,7 @@ const SearchBar = ({ isHomepage }: Props) => { ...@@ -130,7 +130,7 @@ const SearchBar = ({ isHomepage }: Props) => {
> >
<PopoverBody py={ 0 } color="chakra-body-text"> <PopoverBody py={ 0 } color="chakra-body-text">
<Box <Box
maxH=" 50vh" maxH="50vh"
overflowY="scroll" overflowY="scroll"
id={ SCROLL_CONTAINER_ID } id={ SCROLL_CONTAINER_ID }
ref={ scrollRef } ref={ scrollRef }
......
...@@ -15,9 +15,8 @@ import SearchBarSuggestItem from './SearchBarSuggestItem'; ...@@ -15,9 +15,8 @@ import SearchBarSuggestItem from './SearchBarSuggestItem';
type Category = 'token' | 'nft' | 'address' | 'app' | 'public_tag' | 'transaction' | 'block'; type Category = 'token' | 'nft' | 'address' | 'app' | 'public_tag' | 'transaction' | 'block';
const CATEGORIES: Array<{id: Category; title: string }> = [ const CATEGORIES: Array<{id: Category; title: string }> = [
{ id: 'token', title: 'Tokens' }, { id: 'token', title: 'Tokens (ERC-20)' },
// { id: 'tokens', title: 'Tokens (ERC-20)' }, { id: 'nft', title: 'NFTs (ERC-721 & ERC-1155)' },
// { id: 'nfts', title: 'NFTs (ERC-721 & ERC-1155)' },
{ id: 'address', title: 'Addresses' }, { id: 'address', title: 'Addresses' },
{ id: 'app', title: 'Apps' }, { id: 'app', title: 'Apps' },
{ id: 'public_tag', title: 'Public tags' }, { id: 'public_tag', title: 'Public tags' },
...@@ -32,8 +31,11 @@ const getItemCategory = (item: SearchResultItem): Category | undefined => { ...@@ -32,8 +31,11 @@ const getItemCategory = (item: SearchResultItem): Category | undefined => {
return 'address'; return 'address';
} }
case 'token': { case 'token': {
if (item.token_type === 'ERC-20') {
return 'token'; return 'token';
} }
return 'nft';
}
case 'block': { case 'block': {
return 'block'; return 'block';
} }
...@@ -54,53 +56,38 @@ interface Props { ...@@ -54,53 +56,38 @@ interface Props {
} }
// eslint-disable-next-line import-helpers/order-imports // eslint-disable-next-line import-helpers/order-imports
// import * as searchMock from 'mocks/search/index'; import * as searchMock from 'mocks/search/index';
// const mock = [ const mock = [
// searchMock.address1, searchMock.address1,
// searchMock.block1, searchMock.block1,
// searchMock.contract1, searchMock.contract1,
// searchMock.label1, searchMock.label1,
// searchMock.token1, searchMock.token1,
// searchMock.token2, searchMock.token2,
// searchMock.tx1, searchMock.tx1,
// searchMock.address1, searchMock.address1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.block1, searchMock.block1,
// searchMock.contract1, searchMock.contract1,
// searchMock.label1, searchMock.label1,
// searchMock.token1, searchMock.token1,
// searchMock.token2, searchMock.token2,
// searchMock.tx1, searchMock.tx1,
// searchMock.address1, searchMock.address1,
// searchMock.block1, searchMock.block1,
// searchMock.contract1, searchMock.contract1,
// searchMock.label1, searchMock.label1,
// searchMock.token1, searchMock.token1,
// searchMock.token2, searchMock.token2,
// searchMock.tx1, ];
// searchMock.address1,
// searchMock.block1,
// searchMock.contract1,
// searchMock.label1,
// searchMock.token1,
// searchMock.token2,
// searchMock.tx1,
// searchMock.address1,
// searchMock.block1,
// searchMock.contract1,
// searchMock.label1,
// searchMock.token1,
// searchMock.token2,
// searchMock.tx1,
// ];
const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props) => { const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -146,8 +133,8 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -146,8 +133,8 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
return {}; return {};
} }
const map: Partial<Record<Category, Array<SearchResultItem>>> = {}; const map: Partial<Record<Category, Array<SearchResultItem>>> = {};
query.data?.items.forEach(item => { // query.data?.items.forEach(item => {
// mock.forEach(item => { mock.forEach(item => {
const cat = getItemCategory(item); const cat = getItemCategory(item);
if (cat) { if (cat) {
if (cat in map) { if (cat in map) {
...@@ -174,7 +161,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -174,7 +161,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
const content = (() => { const content = (() => {
if (query.isLoading) { if (query.isLoading) {
return <ContentLoader text="We are searching, please wait... " fontSize="sm" my={ 5 }/>; return <ContentLoader text="We are searching, please wait... " fontSize="sm"/>;
} }
if (query.isError) { if (query.isError) {
......
import { Box, Text, Flex, Icon } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultAddressOrContract } from 'types/api/search';
import iconSuccess from 'icons/status/success.svg';
import highlightText from 'lib/highlightText';
import AddressIcon from 'ui/shared/address/AddressIcon';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props {
data: SearchResultAddressOrContract;
isMobile: boolean | undefined;
searchTerm: string;
}
const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
if (isMobile) {
return (
<>
<Flex alignItems="center" justifyContent="space-between">
<AddressIcon
address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }}
mr={ 2 }
flexShrink={ 0 }
/>
{ data.name && (
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
flexGrow={ 1 }
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.name, searchTerm) }}/>
</Text>
) }
{ !data.name && (
<Box
as={ shouldHighlightHash ? 'mark' : 'span' }
display="block"
overflow="hidden"
whiteSpace="nowrap"
fontWeight={ 700 }
flexGrow={ 1 }
>
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Box>
) }
{ !data.name && data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
{ data.name && (
<Flex alignItems="center" justifyContent="space-between">
<Text
as={ shouldHighlightHash ? 'mark' : 'span' }
overflow="hidden"
whiteSpace="nowrap"
variant="secondary"
flexGrow={ 1 }
>
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
) }
</>
);
}
return (
<Flex alignItems="center">
<AddressIcon
address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }}
mr={ 2 }
flexShrink={ 0 }
/>
{ data.name && (
<>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
flexGrow={ 0 }
w="200px"
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.name, searchTerm) }}/>
</Text>
<Text
as={ shouldHighlightHash ? 'mark' : 'span' }
overflow="hidden"
whiteSpace="nowrap"
variant="secondary"
ml={ 2 }
>
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</>
) }
{ !data.name && (
<>
<Box
as={ shouldHighlightHash ? 'mark' : 'span' }
display="block"
overflow="hidden"
whiteSpace="nowrap"
fontWeight={ 700 }
>
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Box>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</>
) }
</Flex>
);
};
export default React.memo(SearchBarSuggestAddress);
import { Text, Icon, Grid } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultBlock } from 'types/api/search';
import blockIcon from 'icons/block.svg';
import dayjs from 'lib/date/dayjs';
import highlightText from 'lib/highlightText';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props {
data: SearchResultBlock;
isMobile: boolean | undefined;
searchTerm: string;
}
const SearchBarSuggestBlock = ({ data, isMobile, searchTerm }: Props) => {
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
if (isMobile) {
return (
<>
<Grid alignItems="center" templateColumns="24px auto 1fr" gap={ 2 }>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.block_number.toString(), searchTerm) }}/>
</Text>
<Text variant="secondary" overflow="hidden" whiteSpace="nowrap" as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/>
</Text>
</Grid>
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</>
);
}
return (
<Grid templateColumns="24px auto 1fr auto" gap={ 2 }>
<Icon as={ blockIcon } boxSize={ 6 } color="gray.500"/>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.block_number.toString(), searchTerm) }}/>
</Text>
<Text
variant="secondary"
overflow="hidden"
whiteSpace="nowrap"
as={ shouldHighlightHash ? 'mark' : 'span' }
display="block"
>
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/>
</Text>
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</Grid>
);
};
export default React.memo(SearchBarSuggestBlock);
import { chakra, useColorModeValue } from '@chakra-ui/react';
import type { LinkProps as NextLinkProps } from 'next/link';
import NextLink from 'next/link';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
import SearchBarSuggestAddress from './SearchBarSuggestAddress';
import SearchBarSuggestBlock from './SearchBarSuggestBlock';
import SearchBarSuggestLabel from './SearchBarSuggestLabel';
import SearchBarSuggestToken from './SearchBarSuggestToken';
import SearchBarSuggestTx from './SearchBarSuggestTx';
interface Props {
data: SearchResultItem;
isMobile: boolean | undefined;
searchTerm: string;
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
}
const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) => {
const url = (() => {
switch (data.type) {
case 'token': {
return route({ pathname: '/token/[hash]', query: { hash: data.address } });
}
case 'contract':
case 'address':
case 'label': {
return route({ pathname: '/address/[hash]', query: { hash: data.address } });
}
case 'transaction': {
return route({ pathname: '/tx/[hash]', query: { hash: data.tx_hash } });
}
case 'block': {
return route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } });
}
}
})();
const content = (() => {
switch (data.type) {
case 'token': {
return <SearchBarSuggestToken data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
}
case 'contract':
case 'address': {
return <SearchBarSuggestAddress data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
}
case 'label': {
return <SearchBarSuggestLabel data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
}
case 'block': {
return <SearchBarSuggestBlock data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
}
case 'transaction': {
return <SearchBarSuggestTx data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
}
}
})();
return (
<NextLink href={ url as NextLinkProps['href'] } passHref legacyBehavior>
<chakra.a
py={ 3 }
px={ 1 }
display="flex"
flexDir="column"
rowGap={ 2 }
borderColor="divider"
borderBottomWidth="1px"
_last={{
borderBottomWidth: '0',
}}
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
}}
fontSize="sm"
_first={{
mt: 2,
}}
onClick={ onClick }
>
{ content }
</chakra.a>
</NextLink>
);
};
export default React.memo(SearchBarSuggestItem);
import { Text, Flex, Icon } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultLabel } from 'types/api/search';
import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg';
import highlightText from 'lib/highlightText';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props {
data: SearchResultLabel;
isMobile: boolean | undefined;
searchTerm: string;
}
const SearchBarSuggestLabel = ({ data, isMobile, searchTerm }: Props) => {
if (isMobile) {
return (
<>
<Flex alignItems="center" overflow="hidden">
<Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
flexGrow={ 1 }
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.name, searchTerm) }}/>
</Text>
</Flex>
<Flex alignItems="center" justifyContent="space-between" overflow="hidden">
<Text overflow="hidden" whiteSpace="nowrap" variant="secondary">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
</>
);
}
return (
<Flex alignItems="center">
<Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
flexGrow={ 0 }
w="200px"
>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.name, searchTerm) }}/>
</Text>
<Text
overflow="hidden"
whiteSpace="nowrap"
variant="secondary"
ml={ 2 }
>
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
);
};
export default React.memo(SearchBarSuggestLabel);
import { Grid, Text, Flex, Icon } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultToken } from 'types/api/search';
import iconSuccess from 'icons/status/success.svg';
import highlightText from 'lib/highlightText';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TokenLogo from 'ui/shared/TokenLogo';
interface Props {
data: SearchResultToken;
isMobile: boolean | undefined;
searchTerm: string;
}
const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => {
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
if (isMobile) {
return (
<>
<Flex alignItems="center" justifyContent="space-between">
<TokenLogo boxSize={ 6 } data={ data } flexShrink={ 0 }/>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
ml={ 2 }
flexGrow={ 1 }
>
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</Text>
</Flex>
<Grid templateColumns="1fr auto auto" alignItems="center">
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
{ data.token_type === 'ERC-20' && data.exchange_rate && (
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis"ml={ 2 } fontWeight={ 700 } maxW="200px">
${ Number(data.exchange_rate).toLocaleString() }
</Text>
) }
{ data.token_type !== 'ERC-20' && data.total_supply && (
<Text overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis"ml={ 2 } variant="secondary" maxW="200px" >
Items { Number(data.total_supply).toLocaleString() }
</Text>
) }
</Grid>
</>
);
}
return (
<Grid templateColumns="24px 200px 1fr auto">
<TokenLogo boxSize={ 6 } data={ data }/>
<Text
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
ml={ 2 }
flexGrow={ 0 }
>
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</Text>
<Flex alignItems="center">
<Text overflow="hidden" whiteSpace="nowrap" ml={ 2 } variant="secondary">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
</Flex>
{ data.token_type === 'ERC-20' && data.exchange_rate && (
<Text overflow="hidden" whiteSpace="nowrap" ml={ 2 } fontWeight={ 700 }>
${ Number(data.exchange_rate).toLocaleString() }
</Text>
) }
{ data.token_type !== 'ERC-20' && data.total_supply && (
<Text overflow="hidden" whiteSpace="nowrap" ml={ 2 } variant="secondary">
Items { Number(data.total_supply).toLocaleString() }
</Text>
) }
</Grid>
);
};
export default React.memo(SearchBarSuggestToken);
import { chakra, Grid, Text, Flex, Icon } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultTx } from 'types/api/search';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props {
data: SearchResultTx;
isMobile: boolean | undefined;
searchTerm: string;
}
const SearchBarSuggestTx = ({ data, isMobile }: Props) => {
if (isMobile) {
return (
<>
<Flex alignItems="center" justifyContent="space-between">
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.tx_hash } isTooltipDisabled/>
</chakra.mark>
</Flex>
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</>
);
}
return (
<Grid templateColumns="24px 1fr auto" gap={ 2 }>
<Icon as={ txIcon } boxSize={ 6 } color="gray.500"/>
<chakra.mark overflow="hidden" whiteSpace="nowrap" display="block" fontWeight={ 700 } width="fit-content">
<HashStringShortenDynamic hash={ data.tx_hash } isTooltipDisabled/>
</chakra.mark>
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</Grid>
);
};
export default React.memo(SearchBarSuggestTx);
import { chakra, Text, Flex, useColorModeValue, Icon, Box } from '@chakra-ui/react';
import type { LinkProps as NextLinkProps } from 'next/link';
import NextLink from 'next/link';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
import blockIcon from 'icons/block.svg';
import labelIcon from 'icons/publictags.svg';
import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import AddressIcon from 'ui/shared/address/AddressIcon';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TokenLogo from 'ui/shared/TokenLogo';
interface Props {
data: SearchResultItem;
isMobile: boolean | undefined;
searchTerm: string;
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
}
const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) => {
const url = (() => {
switch (data.type) {
case 'token': {
return route({ pathname: '/token/[hash]', query: { hash: data.address } });
}
case 'contract':
case 'address':
case 'label': {
return route({ pathname: '/address/[hash]', query: { hash: data.address } });
}
case 'transaction': {
return route({ pathname: '/tx/[hash]', query: { hash: data.tx_hash } });
}
case 'block': {
return route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } });
}
}
})();
const firstRow = (() => {
switch (data.type) {
case 'token': {
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
return (
<>
<TokenLogo boxSize={ 6 } data={ data } flexShrink={ 0 }/>
<Text fontWeight={ 700 } ml={ 2 } w="200px" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" flexShrink={ 0 }>
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</Text>
{ !isMobile && (
<Text overflow="hidden" whiteSpace="nowrap" ml={ 2 } variant="secondary">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
) }
</>
);
}
case 'contract':
case 'address': {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
return (
<>
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Box>
{ !isMobile && data.name && (
<Text variant="secondary" ml={ 2 }>
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
</Text>
) }
</>
);
}
case 'label': {
return (
<>
<Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Text fontWeight={ 700 } ml={ 2 } w="200px" overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" flexShrink={ 0 }>
<span dangerouslySetInnerHTML={{ __html: highlightText(data.name, searchTerm) }}/>
</Text>
{ !isMobile && (
<Text overflow="hidden" whiteSpace="nowrap" ml={ 2 } variant="secondary">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
) }
</>
);
}
case 'block': {
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
return (
<>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Box fontWeight={ 700 } as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
{ !isMobile && (
<Text variant="secondary" overflow="hidden" whiteSpace="nowrap" ml={ 2 } as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/>
</Text>
) }
</>
);
}
case 'transaction': {
return (
< >
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.tx_hash } isTooltipDisabled/>
</chakra.mark>
</>
);
}
}
})();
const secondRow = (() => {
if (!isMobile) {
return null;
}
switch (data.type) {
case 'token':
case 'label': {
return (
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>
</Text>
);
}
case 'block': {
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
return (
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden" as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
<HashStringShortenDynamic hash={ data.block_hash } isTooltipDisabled/>
</Text>
);
}
case 'contract':
case 'address': {
if (!data.name) {
return null;
}
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
return (
<Text variant="secondary" whiteSpace="nowrap" overflow="hidden">
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
</Text>
);
}
default: {
return null;
}
}
})();
return (
<NextLink href={ url as NextLinkProps['href'] } passHref legacyBehavior>
<chakra.a
py={ 3 }
px={ 1 }
display="flex"
flexDir="column"
rowGap={ 2 }
borderColor="divider"
borderBottomWidth="1px"
_last={{
borderBottomWidth: '0',
}}
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
}}
fontSize="sm"
_first={{
mt: 2,
}}
onClick={ onClick }
>
<Flex display="flex" alignItems="center">
{ firstRow }
</Flex>
{ secondRow }
</chakra.a>
</NextLink>
);
};
export default React.memo(SearchBarSuggestItem);
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