support ENS in search
@@ -55,6 +55,20 @@ export const address1: SearchResultAddressOrContract = {
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
export const address2: SearchResultAddressOrContract = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b',
|
||||
name: null,
|
||||
type: 'address' as const,
|
||||
is_smart_contract_verified: false,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b',
|
||||
ens_info: {
|
||||
address_hash: '0x1234567890123456789012345678901234567890',
|
||||
expiry_date: '2022-12-11T17:55:20Z',
|
||||
name: 'utko.eth',
|
||||
names_count: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const contract1: SearchResultAddressOrContract = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
name: 'Unknown contract in this network',
|
||||
|
||||
@@ -23,6 +23,12 @@ export interface SearchResultAddressOrContract {
|
||||
address: string;
|
||||
is_smart_contract_verified: boolean;
|
||||
url?: string; // not used by the frontend, we build the url ourselves
|
||||
ens_info?: {
|
||||
address_hash: string;
|
||||
expiry_date: string;
|
||||
name: string;
|
||||
names_count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SearchResultLabel {
|
||||
|
||||
@@ -32,6 +32,7 @@ test.describe('search by name ', () => {
|
||||
searchMock.token1,
|
||||
searchMock.token2,
|
||||
searchMock.contract1,
|
||||
searchMock.address2,
|
||||
searchMock.label1,
|
||||
],
|
||||
}),
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 40 KiB |
@@ -1,4 +1,4 @@
|
||||
import { Flex, Grid, Icon, Image, Box, Text, Skeleton, useColorMode, Tag } from '@chakra-ui/react';
|
||||
import { chakra, Flex, Grid, Icon, Image, Box, Text, Skeleton, useColorMode, Tag } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import xss from 'xss';
|
||||
|
||||
@@ -13,6 +13,7 @@ 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_REGEXP } from 'lib/validations/address';
|
||||
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import * as BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
@@ -75,7 +76,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
const address = {
|
||||
hash: data.address,
|
||||
is_contract: data.type === 'contract',
|
||||
@@ -267,8 +268,21 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
}
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
return data.name ? <span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(data.name) : highlightText(data.name, searchTerm) }}/> : null;
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
const addressName = data.name || data.ens_info?.name;
|
||||
return addressName ? (
|
||||
<>
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
|
||||
{ data.ens_info &&
|
||||
(
|
||||
data.ens_info.names_count > 1 ?
|
||||
<chakra.span color="text_secondary"> (+{ data.ens_info.names_count - 1 })</chakra.span> :
|
||||
<chakra.span color="text_secondary"> (expires { dayjs(data.ens_info.expiry_date).fromNow() })</chakra.span>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) :
|
||||
null;
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tr, Td, Text, Flex, Icon, Image, Box, Skeleton, useColorMode, Tag } from '@chakra-ui/react';
|
||||
import { chakra, Tr, Td, Text, Flex, Icon, Image, Box, Skeleton, useColorMode, Tag } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import xss from 'xss';
|
||||
|
||||
@@ -13,6 +13,7 @@ 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_REGEXP } from 'lib/validations/address';
|
||||
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import * as BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
@@ -22,7 +23,6 @@ import LinkExternal from 'ui/shared/LinkExternal';
|
||||
import LinkInternal from 'ui/shared/LinkInternal';
|
||||
import type { SearchResultAppItem } from 'ui/shared/search/utils';
|
||||
import { getItemCategory, searchItemTitles } from 'ui/shared/search/utils';
|
||||
|
||||
interface Props {
|
||||
data: SearchResultItem | SearchResultAppItem;
|
||||
searchTerm: string;
|
||||
@@ -93,44 +93,8 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
|
||||
case 'contract':
|
||||
case 'address': {
|
||||
if (data.name) {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
const address = {
|
||||
hash: data.address,
|
||||
is_contract: data.type === 'contract',
|
||||
is_verified: data.is_smart_contract_verified,
|
||||
name: null,
|
||||
implementation_name: null,
|
||||
ens_domain_name: null,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Td fontSize="sm">
|
||||
<AddressEntity.Container>
|
||||
<AddressEntity.Icon address={ address }/>
|
||||
<AddressEntity.Link
|
||||
address={ address }
|
||||
onClick={ handleLinkClick }
|
||||
>
|
||||
<AddressEntity.Content
|
||||
asProp={ shouldHighlightHash ? 'mark' : 'span' }
|
||||
address={ address }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 700 }
|
||||
/>
|
||||
</AddressEntity.Link>
|
||||
<AddressEntity.Copy address={ address }/>
|
||||
</AddressEntity.Container>
|
||||
</Td>
|
||||
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(data.name) : highlightText(data.name, searchTerm) }}/>
|
||||
</Td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
const addressName = data.name || data.ens_info?.name;
|
||||
const address = {
|
||||
hash: data.address,
|
||||
is_contract: data.type === 'contract',
|
||||
@@ -141,24 +105,38 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Td colSpan={ 3 } fontSize="sm">
|
||||
<AddressEntity.Container>
|
||||
<AddressEntity.Icon address={ address }/>
|
||||
<AddressEntity.Link
|
||||
address={ address }
|
||||
onClick={ handleLinkClick }
|
||||
>
|
||||
<AddressEntity.Content
|
||||
asProp="mark"
|
||||
<>
|
||||
<Td fontSize="sm" colSpan={ addressName ? 1 : 3 }>
|
||||
<AddressEntity.Container>
|
||||
<AddressEntity.Icon address={ address }/>
|
||||
<AddressEntity.Link
|
||||
address={ address }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 700 }
|
||||
/>
|
||||
</AddressEntity.Link>
|
||||
<AddressEntity.Copy address={ address }/>
|
||||
</AddressEntity.Container>
|
||||
</Td>
|
||||
onClick={ handleLinkClick }
|
||||
>
|
||||
<AddressEntity.Content
|
||||
asProp={ shouldHighlightHash ? 'mark' : 'span' }
|
||||
address={ address }
|
||||
fontSize="sm"
|
||||
lineHeight={ 5 }
|
||||
fontWeight={ 700 }
|
||||
/>
|
||||
</AddressEntity.Link>
|
||||
<AddressEntity.Copy address={ address }/>
|
||||
</AddressEntity.Container>
|
||||
</Td>
|
||||
{ addressName && (
|
||||
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
|
||||
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
|
||||
{ data.ens_info &&
|
||||
(
|
||||
data.ens_info.names_count > 1 ?
|
||||
<chakra.span color="text_secondary"> (+{ data.ens_info.names_count - 1 })</chakra.span> :
|
||||
<chakra.span color="text_secondary"> (expires { dayjs(data.ens_info.expiry_date).fromNow() })</chakra.span>
|
||||
)
|
||||
}
|
||||
</Td>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ test('search by contract name +@mobile +@dark-mode', async({ mount, page }) =>
|
||||
status: 200,
|
||||
body: JSON.stringify([
|
||||
searchMock.contract1,
|
||||
searchMock.address2,
|
||||
]),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Box, Text, Flex } from '@chakra-ui/react';
|
||||
import { chakra, Box, Text, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { SearchResultAddressOrContract } from 'types/api/search';
|
||||
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import highlightText from 'lib/highlightText';
|
||||
import { ADDRESS_REGEXP } from 'lib/validations/address';
|
||||
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
|
||||
@@ -14,7 +16,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
|
||||
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase();
|
||||
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
|
||||
|
||||
const icon = (
|
||||
<AddressEntity.Icon
|
||||
address={{
|
||||
@@ -27,17 +30,26 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const name = data.name && (
|
||||
const addressName = data.name || data.ens_info?.name;
|
||||
|
||||
const nameEl = addressName && (
|
||||
<Text
|
||||
variant="secondary"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightText(data.name, searchTerm) }}/>
|
||||
<chakra.span fontWeight={ 500 } dangerouslySetInnerHTML={{ __html: highlightText(addressName, searchTerm) }}/>
|
||||
{ data.ens_info &&
|
||||
(
|
||||
data.ens_info.names_count > 1 ?
|
||||
<span> (+{ data.ens_info.names_count - 1 })</span> :
|
||||
<span> (expires { dayjs(data.ens_info.expiry_date).fromNow() })</span>
|
||||
)
|
||||
}
|
||||
</Text>
|
||||
);
|
||||
const address = <HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>;
|
||||
const addressEl = <HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>;
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
@@ -51,10 +63,10 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
|
||||
whiteSpace="nowrap"
|
||||
fontWeight={ 700 }
|
||||
>
|
||||
{ address }
|
||||
{ addressEl }
|
||||
</Box>
|
||||
</Flex>
|
||||
{ name }
|
||||
{ nameEl }
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -70,10 +82,10 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
|
||||
whiteSpace="nowrap"
|
||||
fontWeight={ 700 }
|
||||
>
|
||||
{ address }
|
||||
{ addressEl }
|
||||
</Box>
|
||||
</Flex>
|
||||
{ name }
|
||||
{ nameEl }
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 20 KiB |