tmp
@@ -8,6 +8,10 @@ export const token1: SearchResultToken = {
|
||||
token_url: '/token/0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
|
||||
type: 'token' as const,
|
||||
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 = {
|
||||
@@ -18,12 +22,17 @@ export const token2: SearchResultToken = {
|
||||
token_url: '/token/0xC35Cc7223B0175245E9964f2E3119c261E8e21F9',
|
||||
type: 'token' as const,
|
||||
icon_url: null,
|
||||
token_type: 'ERC-20',
|
||||
total_supply: '10000001',
|
||||
exchange_rate: '1.11',
|
||||
is_smart_contract_verified: false,
|
||||
};
|
||||
|
||||
export const block1: SearchResultBlock = {
|
||||
block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
|
||||
block_number: 8198536,
|
||||
type: 'block' as const,
|
||||
timestamp: '2022-12-11T17:55:20Z',
|
||||
url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd1',
|
||||
};
|
||||
|
||||
@@ -31,6 +40,7 @@ export const address1: SearchResultAddressOrContract = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
name: null,
|
||||
type: 'address' as const,
|
||||
is_smart_contract_verified: false,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
@@ -38,6 +48,7 @@ export const contract1: SearchResultAddressOrContract = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
name: 'Unknown contract in this network',
|
||||
type: 'contract' as const,
|
||||
is_smart_contract_verified: true,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
@@ -45,12 +56,14 @@ export const label1: SearchResultLabel = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
name: 'utko',
|
||||
type: 'label' as const,
|
||||
is_smart_contract_verified: true,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
export const tx1: SearchResultTx = {
|
||||
tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
|
||||
type: 'transaction' as const,
|
||||
timestamp: '2022-12-11T17:55:20Z',
|
||||
url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ export const SEARCH_RESULT_ITEM: SearchResultItem = {
|
||||
token_url: '/token/0x3714A8C7824B22271550894f7555f0a672f97809',
|
||||
type: 'token',
|
||||
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'] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { TokenType } from 'types/api/token';
|
||||
|
||||
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
|
||||
|
||||
export interface SearchResultToken {
|
||||
@@ -8,12 +10,17 @@ export interface SearchResultToken {
|
||||
token_url: string;
|
||||
address_url: string;
|
||||
icon_url: string | null;
|
||||
token_type: TokenType;
|
||||
exchange_rate: string | null;
|
||||
total_supply: string | null;
|
||||
is_smart_contract_verified: boolean;
|
||||
}
|
||||
|
||||
export interface SearchResultAddressOrContract {
|
||||
type: 'address' | 'contract';
|
||||
name: string | null;
|
||||
address: string;
|
||||
is_smart_contract_verified: boolean;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
}
|
||||
|
||||
@@ -21,6 +28,7 @@ export interface SearchResultLabel {
|
||||
type: 'label';
|
||||
address: string;
|
||||
name: string;
|
||||
is_smart_contract_verified: boolean;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
}
|
||||
|
||||
@@ -28,12 +36,14 @@ export interface SearchResultBlock {
|
||||
type: 'block';
|
||||
block_number: number | string;
|
||||
block_hash: string;
|
||||
timestamp: string;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
}
|
||||
|
||||
export interface SearchResultTx {
|
||||
type: 'transaction';
|
||||
tx_hash: string;
|
||||
timestamp: string;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,39 @@ import Pagination from 'ui/shared/pagination/Pagination';
|
||||
import Thead from 'ui/shared/TheadSticky';
|
||||
import Header from 'ui/snippets/header/Header';
|
||||
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 router = useRouter();
|
||||
@@ -68,7 +101,8 @@ const SearchResultsPageContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }>
|
||||
{ data.items.map((item, index) => (
|
||||
{ mock.map((item, index) => (
|
||||
// { data.items.map((item, index) => (
|
||||
<SearchResultListItem
|
||||
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
|
||||
data={ item }
|
||||
@@ -81,13 +115,15 @@ const SearchResultsPageContent = () => {
|
||||
<Table variant="simple" size="md" fontWeight={ 500 }>
|
||||
<Thead top={ pagination.isVisible ? 80 : 0 }>
|
||||
<Tr>
|
||||
<Th width="50%">Search Result</Th>
|
||||
<Th width="50%"/>
|
||||
<Th width="30%">Search Result</Th>
|
||||
<Th width="35%"/>
|
||||
<Th width="35%"/>
|
||||
<Th width="150px">Category</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{ data.items.map((item, index) => (
|
||||
{ /* { data.items.map((item, index) => ( */ }
|
||||
{ mock.map((item, index) => (
|
||||
<SearchResultTableItem
|
||||
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
|
||||
data={ item }
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 196 KiB |
@@ -1,4 +1,4 @@
|
||||
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 React from 'react';
|
||||
|
||||
@@ -6,13 +6,16 @@ import type { SearchResultItem } from 'types/api/search';
|
||||
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import labelIcon from 'icons/publictags.svg';
|
||||
import iconSuccess from 'icons/status/success.svg';
|
||||
import txIcon from 'icons/transactions.svg';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
|
||||
import Address from 'ui/shared/address/Address';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
import HashStringShorten from 'ui/shared/HashStringShorten';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import LinkInternal from 'ui/shared/LinkInternal';
|
||||
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
|
||||
@@ -41,7 +44,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
const name = data.name + (data.symbol ? ` (${ data.symbol })` : '');
|
||||
|
||||
return (
|
||||
<Flex alignItems="flex-start" overflow="hidden">
|
||||
<Flex alignItems="flex-start" flexGrow={ 1 } overflow="hidden">
|
||||
<TokenLogo boxSize={ 6 } data={ data } flexShrink={ 0 } isLoading={ isLoading }/>
|
||||
<LinkInternal
|
||||
ml={ 2 }
|
||||
@@ -50,6 +53,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
wordBreak="break-all"
|
||||
isLoading={ isLoading }
|
||||
onClick={ handleLinkClick }
|
||||
flexGrow={ 1 }
|
||||
overflow="hidden"
|
||||
>
|
||||
<Skeleton
|
||||
@@ -68,12 +72,15 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
case 'address': {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<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 type="address" hash={ data.address } fontWeight={ 700 } display="block" w="100%" onClick={ handleLinkClick }/>
|
||||
</Box>
|
||||
</Address>
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
<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 type="address" hash={ data.address } fontWeight={ 700 } display="block" w="100%" onClick={ handleLinkClick }/>
|
||||
</Box>
|
||||
</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) => {
|
||||
fontWeight={ 700 }
|
||||
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } }) }
|
||||
onClick={ handleLinkClick }
|
||||
mr={ 4 }
|
||||
>
|
||||
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
|
||||
</LinkInternal>
|
||||
@@ -128,17 +136,49 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
switch (data.type) {
|
||||
case 'token': {
|
||||
return (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<HashStringShortenDynamic hash={ data.address }/>
|
||||
</Skeleton>
|
||||
<Grid templateColumns="1fr auto" overflow="hidden" gap={ 5 }>
|
||||
<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>
|
||||
{ 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': {
|
||||
const shouldHighlightHash = data.block_hash.toLowerCase() === searchTerm.toLowerCase();
|
||||
return (
|
||||
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block" w="100%" whiteSpace="nowrap" overflow="hidden">
|
||||
<HashStringShortenDynamic hash={ data.block_hash }/>
|
||||
</Box>
|
||||
<Flex alignItems="center" justifyContent="space-between" w="100%">
|
||||
<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>
|
||||
</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':
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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 React from 'react';
|
||||
|
||||
@@ -6,7 +6,9 @@ import type { SearchResultItem } from 'types/api/search';
|
||||
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import labelIcon from 'icons/publictags.svg';
|
||||
import iconSuccess from 'icons/status/success.svg';
|
||||
import txIcon from 'icons/transactions.svg';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
|
||||
@@ -63,9 +65,26 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
</LinkInternal>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
<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 }/>
|
||||
</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">
|
||||
<HashStringShortenDynamic hash={ data.address }/>
|
||||
{ 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>
|
||||
</Td>
|
||||
</>
|
||||
@@ -93,9 +112,10 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
<HashStringShortenDynamic hash={ data.address }/>
|
||||
</Box>
|
||||
</LinkInternal>
|
||||
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? data.name : highlightText(data.name, searchTerm) }}/>
|
||||
</Td>
|
||||
</>
|
||||
@@ -103,13 +123,16 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Td colSpan={ 2 } fontSize="sm">
|
||||
<Address>
|
||||
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
|
||||
<mark>
|
||||
<AddressLink hash={ data.address } type="address" fontWeight={ 700 } onClick={ handleLinkClick }/>
|
||||
</mark>
|
||||
</Address>
|
||||
<Td colSpan={ 3 } fontSize="sm">
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
<Address>
|
||||
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
|
||||
<mark>
|
||||
<AddressLink hash={ data.address } type="address" fontWeight={ 700 } onClick={ handleLinkClick }/>
|
||||
</mark>
|
||||
</Address>
|
||||
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
|
||||
</Flex>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
@@ -121,7 +144,6 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
<Flex alignItems="center">
|
||||
<Icon as={ labelIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<LinkInternal
|
||||
ml={ 2 }
|
||||
href={ route({ pathname: '/address/[hash]', query: { hash: data.address } }) }
|
||||
fontWeight={ 700 }
|
||||
wordBreak="break-all"
|
||||
@@ -132,10 +154,11 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
</LinkInternal>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
<Skeleton isLoaded={ !isLoading } whiteSpace="nowrap" overflow="hidden">
|
||||
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
<HashStringShortenDynamic hash={ data.address }/>
|
||||
</Skeleton>
|
||||
{ data.is_smart_contract_verified && <Icon as={ iconSuccess } color="green.500" ml={ 2 }/> }
|
||||
</Flex>
|
||||
</Td>
|
||||
</>
|
||||
);
|
||||
@@ -163,20 +186,28 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
<HashStringShortenDynamic hash={ data.block_hash }/>
|
||||
</Box>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
|
||||
</Td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
case 'transaction': {
|
||||
return (
|
||||
<Td colSpan={ 2 } fontSize="sm">
|
||||
<Flex alignItems="center">
|
||||
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<mark>
|
||||
<AddressLink hash={ data.tx_hash } type="transaction" fontWeight={ 700 } onClick={ handleLinkClick }/>
|
||||
</mark>
|
||||
</Flex>
|
||||
</Td>
|
||||
<>
|
||||
<Td colSpan={ 2 } fontSize="sm">
|
||||
<Flex alignItems="center">
|
||||
<Icon as={ txIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<chakra.mark overflow="hidden">
|
||||
<AddressLink display="block" hash={ data.tx_hash } type="transaction" fontWeight={ 700 } onClick={ handleLinkClick } truncation="dynamic"/>
|
||||
</chakra.mark>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td fontSize="sm" verticalAlign="middle">
|
||||
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
|
||||
</Td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
await page.route(API_URL, (route) => route.fulfill({
|
||||
status: 200,
|
||||
@@ -36,6 +36,27 @@ test('search by name +@mobile +@dark-mode', async({ mount, page }) => {
|
||||
items: [
|
||||
searchMock.token1,
|
||||
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,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -13,7 +13,7 @@ import LinkInternal from 'ui/shared/LinkInternal';
|
||||
|
||||
import SearchBarInput from './SearchBarInput';
|
||||
import SearchBarRecentKeywords from './SearchBarRecentKeywords';
|
||||
import SearchBarSuggest from './SearchBarSuggest';
|
||||
import SearchBarSuggest from './SearchBarSuggest/SearchBarSuggest';
|
||||
import useSearchQuery from './useSearchQuery';
|
||||
|
||||
type Props = {
|
||||
@@ -130,7 +130,7 @@ const SearchBar = ({ isHomepage }: Props) => {
|
||||
>
|
||||
<PopoverBody py={ 0 } color="chakra-body-text">
|
||||
<Box
|
||||
maxH=" 50vh"
|
||||
maxH="50vh"
|
||||
overflowY="scroll"
|
||||
id={ SCROLL_CONTAINER_ID }
|
||||
ref={ scrollRef }
|
||||
|
||||
@@ -15,9 +15,8 @@ import SearchBarSuggestItem from './SearchBarSuggestItem';
|
||||
type Category = 'token' | 'nft' | 'address' | 'app' | 'public_tag' | 'transaction' | 'block';
|
||||
|
||||
const CATEGORIES: Array<{id: Category; title: string }> = [
|
||||
{ id: 'token', title: 'Tokens' },
|
||||
// { id: 'tokens', title: 'Tokens (ERC-20)' },
|
||||
// { id: 'nfts', title: 'NFTs (ERC-721 & ERC-1155)' },
|
||||
{ id: 'token', title: 'Tokens (ERC-20)' },
|
||||
{ id: 'nft', title: 'NFTs (ERC-721 & ERC-1155)' },
|
||||
{ id: 'address', title: 'Addresses' },
|
||||
{ id: 'app', title: 'Apps' },
|
||||
{ id: 'public_tag', title: 'Public tags' },
|
||||
@@ -32,7 +31,10 @@ const getItemCategory = (item: SearchResultItem): Category | undefined => {
|
||||
return 'address';
|
||||
}
|
||||
case 'token': {
|
||||
return 'token';
|
||||
if (item.token_type === 'ERC-20') {
|
||||
return 'token';
|
||||
}
|
||||
return 'nft';
|
||||
}
|
||||
case 'block': {
|
||||
return 'block';
|
||||
@@ -54,53 +56,38 @@ interface Props {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import-helpers/order-imports
|
||||
// import * as searchMock from 'mocks/search/index';
|
||||
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,
|
||||
// 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 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 SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
@@ -146,8 +133,8 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
|
||||
return {};
|
||||
}
|
||||
const map: Partial<Record<Category, Array<SearchResultItem>>> = {};
|
||||
query.data?.items.forEach(item => {
|
||||
// mock.forEach(item => {
|
||||
// query.data?.items.forEach(item => {
|
||||
mock.forEach(item => {
|
||||
const cat = getItemCategory(item);
|
||||
if (cat) {
|
||||
if (cat in map) {
|
||||
@@ -174,7 +161,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
|
||||
|
||||
const content = (() => {
|
||||
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) {
|
||||
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
@@ -0,0 +1,67 @@
|
||||
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);
|
||||
@@ -0,0 +1,93 @@
|
||||
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);
|
||||
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
@@ -0,0 +1,89 @@
|
||||
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);
|
||||
@@ -0,0 +1,42 @@
|
||||
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);
|
||||
@@ -1,196 +0,0 @@
|
||||
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);
|
||||
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |