Skip to content

Commit c01844f

Browse files
committed
Support metadata_tag type in search
1 parent 079cffa commit c01844f

24 files changed

+228
-65
lines changed

mocks/search/index.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
SearchResultUserOp,
99
SearchResultBlob,
1010
SearchResultDomain,
11+
SearchResultMetadataTag,
1112
} from 'types/api/search';
1213

1314
export const token1: SearchResultToken = {
@@ -147,6 +148,42 @@ export const domain1: SearchResultDomain = {
147148
url: '/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
148149
};
149150

151+
export const metatag1: SearchResultMetadataTag = {
152+
...address1,
153+
type: 'metadata_tag',
154+
metadata: {
155+
name: 'utko',
156+
slug: 'utko',
157+
meta: {},
158+
tagType: 'name',
159+
ordinal: 1,
160+
},
161+
};
162+
163+
export const metatag2: SearchResultMetadataTag = {
164+
...address2,
165+
type: 'metadata_tag',
166+
metadata: {
167+
name: 'utko',
168+
slug: 'utko',
169+
meta: {},
170+
tagType: 'name',
171+
ordinal: 1,
172+
},
173+
};
174+
175+
export const metatag3: SearchResultMetadataTag = {
176+
...contract2,
177+
type: 'metadata_tag',
178+
metadata: {
179+
name: 'super utko',
180+
slug: 'super-utko',
181+
meta: {},
182+
tagType: 'protocol',
183+
ordinal: 1,
184+
},
185+
};
186+
150187
export const baseResponse: SearchResult = {
151188
items: [
152189
token1,
@@ -157,6 +194,8 @@ export const baseResponse: SearchResult = {
157194
tx1,
158195
blob1,
159196
domain1,
197+
metatag1,
198+
160199
],
161200
next_page_params: null,
162201
};

types/api/search.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import type * as bens from '@blockscout/bens-types';
22
import type { TokenType } from 'types/api/token';
33

4-
export type SearchResultType = 'token' | 'address' | 'block' | 'transaction' | 'contract';
4+
import type { AddressMetadataTagApi } from './addressMetadata';
5+
6+
export const SEARCH_RESULT_TYPES = {
7+
token: 'token',
8+
address: 'address',
9+
block: 'block',
10+
transaction: 'transaction',
11+
contract: 'contract',
12+
ens_domain: 'ens_domain',
13+
label: 'label',
14+
user_operation: 'user_operation',
15+
blob: 'blob',
16+
metadata_tag: 'metadata_tag',
17+
} as const;
18+
19+
export type SearchResultType = typeof SEARCH_RESULT_TYPES[keyof typeof SEARCH_RESULT_TYPES];
520

621
export interface SearchResultToken {
722
type: 'token';
@@ -20,29 +35,35 @@ export interface SearchResultToken {
2035
certified?: boolean;
2136
}
2237

23-
export interface SearchResultAddressOrContract {
24-
type: 'address' | 'contract';
38+
type SearchResultEnsInfo = {
39+
address_hash: string;
40+
expiry_date?: string;
41+
name: string;
42+
names_count: number;
43+
} | null;
44+
45+
interface SearchResultAddressData {
2546
name: string | null;
2647
address: string;
2748
is_smart_contract_verified: boolean;
2849
certified?: true;
2950
filecoin_robust_address?: string | null;
3051
url?: string; // not used by the frontend, we build the url ourselves
31-
ens_info?: {
32-
address_hash: string;
33-
expiry_date?: string;
34-
name: string;
35-
names_count: number;
36-
};
3752
}
3853

39-
export interface SearchResultDomain {
54+
export interface SearchResultAddressOrContract extends SearchResultAddressData {
55+
type: 'address' | 'contract';
56+
ens_info?: SearchResultEnsInfo;
57+
}
58+
59+
export interface SearchResultMetadataTag extends SearchResultAddressData {
60+
type: 'metadata_tag';
61+
ens_info?: SearchResultEnsInfo;
62+
metadata: AddressMetadataTagApi;
63+
}
64+
65+
export interface SearchResultDomain extends SearchResultAddressData {
4066
type: 'ens_domain';
41-
name: string | null;
42-
address: string;
43-
filecoin_robust_address?: string | null;
44-
is_smart_contract_verified: boolean;
45-
url?: string; // not used by the frontend, we build the url ourselves
4667
ens_info: {
4768
address_hash: string;
4869
expiry_date?: string;
@@ -90,8 +111,16 @@ export interface SearchResultUserOp {
90111
url?: string; // not used by the frontend, we build the url ourselves
91112
}
92113

93-
export type SearchResultItem = SearchResultToken | SearchResultAddressOrContract | SearchResultBlock | SearchResultTx | SearchResultLabel | SearchResultUserOp |
94-
SearchResultBlob | SearchResultDomain;
114+
export type SearchResultItem =
115+
SearchResultToken |
116+
SearchResultAddressOrContract |
117+
SearchResultBlock |
118+
SearchResultTx |
119+
SearchResultLabel |
120+
SearchResultUserOp |
121+
SearchResultBlob |
122+
SearchResultDomain |
123+
SearchResultMetadataTag;
95124

96125
export interface SearchResult {
97126
items: Array<SearchResultItem>;

ui/pages/SearchResults.pw.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ test.describe('search by name', () => {
2020
searchMock.token2,
2121
searchMock.contract1,
2222
searchMock.address2,
23+
searchMock.metatag1,
2324
searchMock.label1,
2425
],
2526
next_page_params: null,
@@ -52,6 +53,23 @@ test('search by address hash +@mobile', async({ render, mockApiResponse }) => {
5253
await expect(component.locator('main')).toHaveScreenshot();
5354
});
5455

56+
test('search by meta tag +@mobile', async({ render, mockApiResponse }) => {
57+
const hooksConfig = {
58+
router: {
59+
query: { q: 'utko' },
60+
},
61+
};
62+
const data = {
63+
items: [ searchMock.metatag1, searchMock.metatag2, searchMock.metatag3 ],
64+
next_page_params: null,
65+
};
66+
await mockApiResponse('search', data, { queryParams: { q: 'utko' } });
67+
68+
const component = await render(<SearchResults/>, { hooksConfig });
69+
70+
await expect(component.locator('main')).toHaveScreenshot();
71+
});
72+
5573
test('search by block number +@mobile', async({ render, mockApiResponse }) => {
5674
const hooksConfig = {
5775
router: {

ui/pages/SearchResults.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
33
import type { FormEvent } from 'react';
44
import React from 'react';
55

6+
import { SEARCH_RESULT_TYPES } from 'types/api/search';
67
import type { SearchResultItem } from 'types/client/search';
78

89
import config from 'configs/app';
@@ -95,6 +96,9 @@ const SearchResultsPageContent = () => {
9596

9697
const displayedItems: Array<SearchResultItem | SearchResultAppItem> = React.useMemo(() => {
9798
const apiData = (data?.items || []).filter((item) => {
99+
if (!SEARCH_RESULT_TYPES[item.type]) {
100+
return false;
101+
}
98102
if (!config.features.userOps.isEnabled && item.type === 'user_operation') {
99103
return false;
100104
}
Loading
Loading
Loading
Loading

ui/searchResults/SearchResultListItem.tsx

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
2121
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
2222
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
2323
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
24+
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
2425
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
2526
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
2627
import IconSvg from 'ui/shared/IconSvg';
@@ -80,6 +81,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
8081
);
8182
}
8283

84+
case 'metadata_tag':
8385
case 'contract':
8486
case 'address': {
8587
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
@@ -367,27 +369,39 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
367369
</Text>
368370
);
369371
}
372+
case 'metadata_tag':
370373
case 'contract':
371374
case 'address': {
372375
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
373376
const addressName = data.name || data.ens_info?.name;
374377
const expiresText = data.ens_info?.expiry_date ? ` (expires ${ dayjs(data.ens_info.expiry_date).fromNow() })` : '';
375378

376-
return addressName ? (
377-
<Flex alignItems="center">
378-
<Text
379-
overflow="hidden"
380-
whiteSpace="nowrap"
381-
textOverflow="ellipsis"
382-
>
383-
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
384-
{ data.ens_info && (
385-
data.ens_info.names_count > 1 ?
386-
<chakra.span color="text_secondary"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</chakra.span> :
387-
<chakra.span color="text_secondary">{ expiresText }</chakra.span>
388-
) }
389-
</Text>
390-
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
379+
return (addressName || data.type === 'metadata_tag') ? (
380+
<Flex alignItems="center" gap={ 2 } justifyContent="space-between" flexWrap="wrap">
381+
{ addressName && (
382+
<Flex alignItems="center" flexWrap="wrap" gap={ 2 }>
383+
<Text
384+
overflow="hidden"
385+
whiteSpace="nowrap"
386+
textOverflow="ellipsis"
387+
>
388+
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
389+
{ data.ens_info && (
390+
data.ens_info.names_count > 1 ?
391+
<chakra.span color="text_secondary"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</chakra.span> :
392+
<chakra.span color="text_secondary">{ expiresText }</chakra.span>
393+
) }
394+
</Text>
395+
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 }/> }
396+
</Flex>
397+
) }
398+
{ data.type === 'metadata_tag' && (
399+
// we show regular tag because we don't need all meta info here, but need to highlight search term
400+
<Tag display="flex" alignItems="center" alignSelf="flex-end">
401+
<EntityTagIcon data={ data.metadata } iconColor="gray.400"/>
402+
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
403+
</Tag>
404+
) }
391405
</Flex>
392406
) :
393407
null;

ui/searchResults/SearchResultTableItem.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
2121
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
2222
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
2323
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
24+
import EntityTagIcon from 'ui/shared/EntityTags/EntityTagIcon';
2425
import { ADDRESS_REGEXP } from 'ui/shared/forms/validators/address';
2526
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
2627
import IconSvg from 'ui/shared/IconSvg';
@@ -100,6 +101,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
100101
);
101102
}
102103

104+
case 'metadata_tag':
103105
case 'contract':
104106
case 'address': {
105107
const shouldHighlightHash = ADDRESS_REGEXP.test(searchTerm);
@@ -120,7 +122,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
120122

121123
return (
122124
<>
123-
<Td fontSize="sm" colSpan={ addressName ? 1 : 3 }>
125+
<Td fontSize="sm" colSpan={ addressName ? 1 : 2 } verticalAlign="middle">
124126
<AddressEntity.Container>
125127
<AddressEntity.Icon address={ address }/>
126128
<AddressEntity.Link
@@ -139,7 +141,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
139141
</AddressEntity.Container>
140142
</Td>
141143
{ addressName && (
142-
<Td colSpan={ 2 } fontSize="sm" verticalAlign="middle">
144+
<Td colSpan={ data.type === 'metadata_tag' ? 1 : 2 } fontSize="sm" verticalAlign="middle">
143145
<Flex alignItems="center">
144146
<Text
145147
overflow="hidden"
@@ -160,6 +162,17 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
160162
</Flex>
161163
</Td>
162164
) }
165+
{ data.type === 'metadata_tag' && (
166+
<Td fontSize="sm" verticalAlign="middle">
167+
<Flex justifyContent="flex-end">
168+
{ /* we show regular tag because we don't need all meta info here, but need to highlight search term */ }
169+
<Tag display="flex" alignItems="center">
170+
<EntityTagIcon data={ data.metadata } iconColor="gray.400"/>
171+
<span dangerouslySetInnerHTML={{ __html: highlightText(data.metadata.name, searchTerm) }}/>
172+
</Tag>
173+
</Flex>
174+
</Td>
175+
) }
163176
</>
164177
);
165178
}

ui/shared/EntityTags/EntityTag.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { ResponsiveValue } from '@chakra-ui/react';
2-
import { chakra, Image, Tag } from '@chakra-ui/react';
2+
import { Tag } from '@chakra-ui/react';
33
import React from 'react';
44

55
import type { EntityTag as TEntityTag } from './types';
66

77
import Skeleton from 'ui/shared/chakra/Skeleton';
8-
import IconSvg from 'ui/shared/IconSvg';
98
import TruncatedValue from 'ui/shared/TruncatedValue';
109

10+
import EntityTagIcon from './EntityTagIcon';
1111
import EntityTagLink from './EntityTagLink';
1212
import EntityTagPopover from './EntityTagPopover';
1313
import { getTagLinkParams } from './utils';
@@ -26,7 +26,6 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
2626
}
2727

2828
const hasLink = !noLink && Boolean(getTagLinkParams(data));
29-
const iconColor = data.meta?.textColor ?? 'gray.400';
3029

3130
const name = (() => {
3231
if (data.meta?.warpcastHandle) {
@@ -36,22 +35,6 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
3635
return data.name;
3736
})();
3837

39-
const icon = (() => {
40-
if (data.meta?.tagIcon) {
41-
return <Image boxSize={ 3 } mr={ 1 } flexShrink={ 0 } src={ data.meta.tagIcon } alt={ `${ data.name } icon` }/>;
42-
}
43-
44-
if (data.tagType === 'name') {
45-
return <IconSvg name="publictags_slim" boxSize={ 3 } mr={ 1 } flexShrink={ 0 } color={ iconColor }/>;
46-
}
47-
48-
if (data.tagType === 'protocol' || data.tagType === 'generic') {
49-
return <chakra.span color={ iconColor } whiteSpace="pre"># </chakra.span>;
50-
}
51-
52-
return null;
53-
})();
54-
5538
return (
5639
<EntityTagPopover data={ data }>
5740
<Tag
@@ -66,7 +49,7 @@ const EntityTag = ({ data, isLoading, maxW, noLink }: Props) => {
6649
_hover={ hasLink ? { opacity: 0.76 } : undefined }
6750
>
6851
<EntityTagLink data={ data } noLink={ noLink }>
69-
{ icon }
52+
<EntityTagIcon data={ data } iconColor={ data.meta?.textColor }/>
7053
<TruncatedValue value={ name } tooltipPlacement="top"/>
7154
</EntityTagLink>
7255
</Tag>

0 commit comments

Comments
 (0)