Commit d58af66d authored by tom's avatar tom

support ENS in search

parent 831240f1
...@@ -55,6 +55,20 @@ export const address1: SearchResultAddressOrContract = { ...@@ -55,6 +55,20 @@ export const address1: SearchResultAddressOrContract = {
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', 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 = { export const contract1: SearchResultAddressOrContract = {
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
name: 'Unknown contract in this network', name: 'Unknown contract in this network',
......
...@@ -23,6 +23,12 @@ export interface SearchResultAddressOrContract { ...@@ -23,6 +23,12 @@ export interface SearchResultAddressOrContract {
address: string; address: string;
is_smart_contract_verified: boolean; 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
ens_info?: {
address_hash: string;
expiry_date: string;
name: string;
names_count: number;
};
} }
export interface SearchResultLabel { export interface SearchResultLabel {
......
...@@ -32,6 +32,7 @@ test.describe('search by name ', () => { ...@@ -32,6 +32,7 @@ test.describe('search by name ', () => {
searchMock.token1, searchMock.token1,
searchMock.token2, searchMock.token2,
searchMock.contract1, searchMock.contract1,
searchMock.address2,
searchMock.label1, searchMock.label1,
], ],
}), }),
......
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 React from 'react';
import xss from 'xss'; import xss from 'xss';
...@@ -13,6 +13,7 @@ import dayjs from 'lib/date/dayjs'; ...@@ -13,6 +13,7 @@ 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_REGEXP } from 'lib/validations/address';
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as BlockEntity from 'ui/shared/entities/block/BlockEntity'; import * as BlockEntity from 'ui/shared/entities/block/BlockEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
...@@ -75,7 +76,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -75,7 +76,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
case 'contract': case 'contract':
case 'address': { case 'address': {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase(); const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
const address = { const address = {
hash: data.address, hash: data.address,
is_contract: data.type === 'contract', is_contract: data.type === 'contract',
...@@ -267,8 +268,21 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -267,8 +268,21 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
} }
case 'contract': case 'contract':
case 'address': { case 'address': {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase(); const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
return data.name ? <span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(data.name) : highlightText(data.name, searchTerm) }}/> : null; 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: default:
......
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 React from 'react';
import xss from 'xss'; import xss from 'xss';
...@@ -13,6 +13,7 @@ import dayjs from 'lib/date/dayjs'; ...@@ -13,6 +13,7 @@ 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_REGEXP } from 'lib/validations/address';
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as BlockEntity from 'ui/shared/entities/block/BlockEntity'; import * as BlockEntity from 'ui/shared/entities/block/BlockEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity'; import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
...@@ -22,7 +23,6 @@ import LinkExternal from 'ui/shared/LinkExternal'; ...@@ -22,7 +23,6 @@ import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import type { SearchResultAppItem } from 'ui/shared/search/utils'; import type { SearchResultAppItem } from 'ui/shared/search/utils';
import { getItemCategory, searchItemTitles } from 'ui/shared/search/utils'; import { getItemCategory, searchItemTitles } from 'ui/shared/search/utils';
interface Props { interface Props {
data: SearchResultItem | SearchResultAppItem; data: SearchResultItem | SearchResultAppItem;
searchTerm: string; searchTerm: string;
...@@ -93,44 +93,8 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -93,44 +93,8 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
case 'contract': case 'contract':
case 'address': { case 'address': {
if (data.name) { const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase(); const addressName = data.name || data.ens_info?.name;
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 address = { const address = {
hash: data.address, hash: data.address,
is_contract: data.type === 'contract', is_contract: data.type === 'contract',
...@@ -141,24 +105,38 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -141,24 +105,38 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
}; };
return ( return (
<Td colSpan={ 3 } fontSize="sm"> <>
<AddressEntity.Container> <Td fontSize="sm" colSpan={ addressName ? 1 : 3 }>
<AddressEntity.Icon address={ address }/> <AddressEntity.Container>
<AddressEntity.Link <AddressEntity.Icon address={ address }/>
address={ address } <AddressEntity.Link
onClick={ handleLinkClick }
>
<AddressEntity.Content
asProp="mark"
address={ address } address={ address }
fontSize="sm" onClick={ handleLinkClick }
lineHeight={ 5 } >
fontWeight={ 700 } <AddressEntity.Content
/> asProp={ shouldHighlightHash ? 'mark' : 'span' }
</AddressEntity.Link> address={ address }
<AddressEntity.Copy address={ address }/> fontSize="sm"
</AddressEntity.Container> lineHeight={ 5 }
</Td> 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 }) => ...@@ -65,6 +65,7 @@ test('search by contract name +@mobile +@dark-mode', async({ mount, page }) =>
status: 200, status: 200,
body: JSON.stringify([ body: JSON.stringify([
searchMock.contract1, searchMock.contract1,
searchMock.address2,
]), ]),
})); }));
......
import { Box, Text, Flex } from '@chakra-ui/react'; import { chakra, Box, Text, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { SearchResultAddressOrContract } from 'types/api/search'; import type { SearchResultAddressOrContract } from 'types/api/search';
import dayjs from 'lib/date/dayjs';
import highlightText from 'lib/highlightText'; import highlightText from 'lib/highlightText';
import { ADDRESS_REGEXP } from 'lib/validations/address';
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity'; import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
...@@ -14,7 +16,8 @@ interface Props { ...@@ -14,7 +16,8 @@ interface Props {
} }
const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => { const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
const shouldHighlightHash = data.address.toLowerCase() === searchTerm.toLowerCase(); const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
const icon = ( const icon = (
<AddressEntity.Icon <AddressEntity.Icon
address={{ address={{
...@@ -27,17 +30,26 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => { ...@@ -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 <Text
variant="secondary" variant="secondary"
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
textOverflow="ellipsis" 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> </Text>
); );
const address = <HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>; const addressEl = <HashStringShortenDynamic hash={ data.address } isTooltipDisabled/>;
if (isMobile) { if (isMobile) {
return ( return (
...@@ -51,10 +63,10 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => { ...@@ -51,10 +63,10 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
whiteSpace="nowrap" whiteSpace="nowrap"
fontWeight={ 700 } fontWeight={ 700 }
> >
{ address } { addressEl }
</Box> </Box>
</Flex> </Flex>
{ name } { nameEl }
</> </>
); );
} }
...@@ -70,10 +82,10 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => { ...@@ -70,10 +82,10 @@ const SearchBarSuggestAddress = ({ data, isMobile, searchTerm }: Props) => {
whiteSpace="nowrap" whiteSpace="nowrap"
fontWeight={ 700 } fontWeight={ 700 }
> >
{ address } { addressEl }
</Box> </Box>
</Flex> </Flex>
{ name } { nameEl }
</Flex> </Flex>
); );
}; };
......
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