Commit 4bf1d410 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge branch 'main' into tabs-counters

parents 064c44d6 d7b7b83b
...@@ -41,7 +41,7 @@ import type { L2TxnBatchesResponse } from 'types/api/l2TxnBatches'; ...@@ -41,7 +41,7 @@ import type { L2TxnBatchesResponse } from 'types/api/l2TxnBatches';
import type { L2WithdrawalsResponse } from 'types/api/l2Withdrawals'; import type { L2WithdrawalsResponse } from 'types/api/l2Withdrawals';
import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters } from 'types/api/search'; import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type { import type {
TokenCounters, TokenCounters,
...@@ -425,6 +425,10 @@ export const RESOURCES = { ...@@ -425,6 +425,10 @@ export const RESOURCES = {
}, },
// SEARCH // SEARCH
quick_search: {
path: '/api/v2/search/quick',
filterFields: [ 'q' ],
},
search: { search: {
path: '/api/v2/search', path: '/api/v2/search',
filterFields: [ 'q' ], filterFields: [ 'q' ],
...@@ -603,6 +607,7 @@ Q extends 'token_instance_transfers' ? TokenInstanceTransferResponse : ...@@ -603,6 +607,7 @@ Q extends 'token_instance_transfers' ? TokenInstanceTransferResponse :
Q extends 'token_instance_holders' ? TokenHolders : Q extends 'token_instance_holders' ? TokenHolders :
Q extends 'token_inventory' ? TokenInventoryResponse : Q extends 'token_inventory' ? TokenInventoryResponse :
Q extends 'tokens' ? TokensResponse : Q extends 'tokens' ? TokensResponse :
Q extends 'quick_search' ? Array<SearchResultItem> :
Q extends 'search' ? SearchResult : Q extends 'search' ? SearchResult :
Q extends 'search_check_redirect' ? SearchRedirectResult : Q extends 'search_check_redirect' ? SearchRedirectResult :
Q extends 'contract' ? SmartContract : Q extends 'contract' ? SmartContract :
......
...@@ -11,6 +11,7 @@ export const token1: SearchResultToken = { ...@@ -11,6 +11,7 @@ export const token1: SearchResultToken = {
token_type: 'ERC-721', token_type: 'ERC-721',
total_supply: '10000001', total_supply: '10000001',
exchange_rate: null, exchange_rate: null,
is_verified_via_admin_panel: true,
is_smart_contract_verified: true, is_smart_contract_verified: true,
}; };
...@@ -25,6 +26,7 @@ export const token2: SearchResultToken = { ...@@ -25,6 +26,7 @@ export const token2: SearchResultToken = {
token_type: 'ERC-20', token_type: 'ERC-20',
total_supply: '10000001', total_supply: '10000001',
exchange_rate: '1.11', exchange_rate: '1.11',
is_verified_via_admin_panel: false,
is_smart_contract_verified: false, is_smart_contract_verified: false,
}; };
......
...@@ -10,6 +10,7 @@ export const SEARCH_RESULT_ITEM: SearchResultItem = { ...@@ -10,6 +10,7 @@ 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_verified_via_admin_panel: false,
is_smart_contract_verified: false, is_smart_contract_verified: false,
exchange_rate: '1.11', exchange_rate: '1.11',
total_supply: null, total_supply: null,
......
...@@ -13,6 +13,7 @@ export interface SearchResultToken { ...@@ -13,6 +13,7 @@ export interface SearchResultToken {
token_type: TokenType; token_type: TokenType;
exchange_rate: string | null; exchange_rate: string | null;
total_supply: string | null; total_supply: string | null;
is_verified_via_admin_panel: boolean;
is_smart_contract_verified: boolean; is_smart_contract_verified: boolean;
} }
......
...@@ -21,7 +21,7 @@ import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery'; ...@@ -21,7 +21,7 @@ import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery';
const SearchResultsPageContent = () => { const SearchResultsPageContent = () => {
const router = useRouter(); const router = useRouter();
const { query, redirectCheckQuery, searchTerm, debouncedSearchTerm, handleSearchTermChange } = useSearchQuery(true); const { query, redirectCheckQuery, searchTerm, debouncedSearchTerm, handleSearchTermChange } = useSearchQuery();
const { data, isError, isPlaceholderData, pagination } = query; const { data, isError, isPlaceholderData, pagination } = query;
const [ showContent, setShowContent ] = React.useState(false); const [ showContent, setShowContent ] = React.useState(false);
......
...@@ -7,6 +7,7 @@ import { route } from 'nextjs-routes'; ...@@ -7,6 +7,7 @@ import { route } from 'nextjs-routes';
import labelIcon from 'icons/publictags.svg'; import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg'; import iconSuccess from 'icons/status/success.svg';
import verifiedToken from 'icons/verified_token.svg';
import dayjs from 'lib/date/dayjs'; 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';
...@@ -49,7 +50,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -49,7 +50,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" flexGrow={ 1 } overflow="hidden"> <Flex alignItems="center" 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 }
...@@ -69,6 +70,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -69,6 +70,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
textOverflow="ellipsis" textOverflow="ellipsis"
/> />
</LinkInternal> </LinkInternal>
{ data.is_verified_via_admin_panel && <Icon as={ verifiedToken } boxSize={ 4 } ml={ 1 } color="green.500"/> }
</Flex> </Flex>
); );
} }
......
...@@ -7,6 +7,7 @@ import { route } from 'nextjs-routes'; ...@@ -7,6 +7,7 @@ import { route } from 'nextjs-routes';
import labelIcon from 'icons/publictags.svg'; import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg'; import iconSuccess from 'icons/status/success.svg';
import verifiedToken from 'icons/verified_token.svg';
import dayjs from 'lib/date/dayjs'; 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';
...@@ -69,6 +70,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -69,6 +70,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }} dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}
/> />
</LinkInternal> </LinkInternal>
{ data.is_verified_via_admin_panel && <Icon as={ verifiedToken } boxSize={ 4 } ml={ 1 } color="green.500"/> }
</Flex> </Flex>
</Td> </Td>
<Td fontSize="sm" verticalAlign="middle"> <Td fontSize="sm" verticalAlign="middle">
......
...@@ -30,15 +30,13 @@ test.beforeEach(async({ page }) => { ...@@ -30,15 +30,13 @@ test.beforeEach(async({ page }) => {
}); });
test('search by token 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('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, ]),
],
}),
})); }));
await mount( await mount(
...@@ -53,14 +51,12 @@ test('search by token name +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -53,14 +51,12 @@ test('search by token name +@mobile +@dark-mode', async({ mount, page }) => {
}); });
test('search by contract name +@mobile +@dark-mode', async({ mount, page }) => { test('search by contract name +@mobile +@dark-mode', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.contract1,
searchMock.contract1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -75,16 +71,14 @@ test('search by contract name +@mobile +@dark-mode', async({ mount, page }) => ...@@ -75,16 +71,14 @@ test('search by contract name +@mobile +@dark-mode', async({ mount, page }) =>
}); });
test('search by name homepage +@dark-mode', async({ mount, page }) => { test('search by name homepage +@dark-mode', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, searchMock.contract1,
searchMock.contract1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -101,14 +95,12 @@ test('search by name homepage +@dark-mode', async({ mount, page }) => { ...@@ -101,14 +95,12 @@ test('search by name homepage +@dark-mode', async({ mount, page }) => {
}); });
test('search by tag +@mobile +@dark-mode', async({ mount, page }) => { test('search by tag +@mobile +@dark-mode', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.label1,
searchMock.label1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -123,14 +115,12 @@ test('search by tag +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -123,14 +115,12 @@ test('search by tag +@mobile +@dark-mode', async({ mount, page }) => {
}); });
test('search by address hash +@mobile', async({ mount, page }) => { test('search by address hash +@mobile', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + `?q=${ searchMock.address1.address }`; const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.address1.address }`;
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.address1,
searchMock.address1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -145,14 +135,12 @@ test('search by address hash +@mobile', async({ mount, page }) => { ...@@ -145,14 +135,12 @@ test('search by address hash +@mobile', async({ mount, page }) => {
}); });
test('search by block number +@mobile', async({ mount, page }) => { test('search by block number +@mobile', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + `?q=${ searchMock.block1.block_number }`; const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.block1.block_number }`;
await page.route(buildApiUrl('search') + `?q=${ searchMock.block1.block_number }`, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.block1,
searchMock.block1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -167,14 +155,12 @@ test('search by block number +@mobile', async({ mount, page }) => { ...@@ -167,14 +155,12 @@ test('search by block number +@mobile', async({ mount, page }) => {
}); });
test('search by block hash +@mobile', async({ mount, page }) => { test('search by block hash +@mobile', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + `?q=${ searchMock.block1.block_hash }`; const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.block1.block_hash }`;
await page.route(buildApiUrl('search') + `?q=${ searchMock.block1.block_hash }`, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.block1,
searchMock.block1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -189,14 +175,12 @@ test('search by block hash +@mobile', async({ mount, page }) => { ...@@ -189,14 +175,12 @@ test('search by block hash +@mobile', async({ mount, page }) => {
}); });
test('search by tx hash +@mobile', async({ mount, page }) => { test('search by tx hash +@mobile', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + `?q=${ searchMock.tx1.tx_hash }`; const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.tx1.tx_hash }`;
await page.route(buildApiUrl('search') + `?q=${ searchMock.tx1.tx_hash }`, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.tx1,
searchMock.tx1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -211,17 +195,15 @@ test('search by tx hash +@mobile', async({ mount, page }) => { ...@@ -211,17 +195,15 @@ test('search by tx hash +@mobile', async({ mount, page }) => {
}); });
test('search with view all link', async({ mount, page }) => { test('search with view all link', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, searchMock.contract1,
searchMock.contract1, ...Array(47).fill(searchMock.contract1),
], ]),
next_page_params: { foo: 'bar' },
}),
})); }));
await mount( await mount(
...@@ -237,25 +219,23 @@ test('search with view all link', async({ mount, page }) => { ...@@ -237,25 +219,23 @@ test('search with view all link', async({ mount, page }) => {
}); });
test('scroll suggest to category', async({ mount, page }) => { test('scroll suggest to category', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, searchMock.contract1,
searchMock.contract1, searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, searchMock.contract1,
searchMock.contract1, searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, searchMock.contract1,
searchMock.contract1, searchMock.token1,
searchMock.token1, searchMock.token2,
searchMock.token2, searchMock.contract1,
searchMock.contract1, ]),
],
}),
})); }));
await mount( await mount(
...@@ -287,15 +267,12 @@ test.describe('with apps', () => { ...@@ -287,15 +267,12 @@ test.describe('with apps', () => {
const MARKETPLACE_CONFIG_URL = 'https://localhost:3000/marketplace-config.json'; const MARKETPLACE_CONFIG_URL = 'https://localhost:3000/marketplace-config.json';
test('default view +@mobile', async({ mount, page }) => { test('default view +@mobile', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o'; const API_URL = buildApiUrl('quick_search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ body: JSON.stringify([
items: [ searchMock.token1,
searchMock.token1, ]),
],
next_page_params: { foo: 'bar' },
}),
})); }));
await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({ await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({
......
...@@ -15,7 +15,7 @@ import LinkInternal from 'ui/shared/LinkInternal'; ...@@ -15,7 +15,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/SearchBarSuggest'; import SearchBarSuggest from './SearchBarSuggest/SearchBarSuggest';
import useSearchQuery from './useSearchQuery'; import useQuickSearchQuery from './useQuickSearchQuery';
type Props = { type Props = {
isHomepage?: boolean; isHomepage?: boolean;
...@@ -34,7 +34,7 @@ const SearchBar = ({ isHomepage }: Props) => { ...@@ -34,7 +34,7 @@ const SearchBar = ({ isHomepage }: Props) => {
const recentSearchKeywords = getRecentSearchKeywords(); const recentSearchKeywords = getRecentSearchKeywords();
const { searchTerm, debouncedSearchTerm, handleSearchTermChange, query, pathname } = useSearchQuery(); const { searchTerm, debouncedSearchTerm, handleSearchTermChange, query, pathname } = useQuickSearchQuery();
const handleSubmit = React.useCallback((event: FormEvent<HTMLFormElement>) => { const handleSubmit = React.useCallback((event: FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
...@@ -160,7 +160,7 @@ const SearchBar = ({ isHomepage }: Props) => { ...@@ -160,7 +160,7 @@ const SearchBar = ({ isHomepage }: Props) => {
) } ) }
</Box> </Box>
</PopoverBody> </PopoverBody>
{ searchTerm.trim().length > 0 && query.data?.next_page_params && ( { searchTerm.trim().length > 0 && query.data && query.data.length >= 50 && (
<PopoverFooter> <PopoverFooter>
<LinkInternal <LinkInternal
href={ route({ pathname: '/search-results', query: { q: searchTerm } }) } href={ route({ pathname: '/search-results', query: { q: searchTerm } }) }
......
import { Box, Tab, TabList, Tabs, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Tab, TabList, Tabs, Text, useColorModeValue } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
import type { SearchResultItem } from 'types/api/search';
import type { ResourceError } from 'lib/api/resources';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps'; import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import type { ApiCategory, ItemsCategoriesMap } from 'ui/shared/search/utils'; import type { ApiCategory, ItemsCategoriesMap } from 'ui/shared/search/utils';
import { getItemCategory, searchCategories } from 'ui/shared/search/utils'; import { getItemCategory, searchCategories } from 'ui/shared/search/utils';
...@@ -15,7 +18,7 @@ import SearchBarSuggestApp from './SearchBarSuggestApp'; ...@@ -15,7 +18,7 @@ import SearchBarSuggestApp from './SearchBarSuggestApp';
import SearchBarSuggestItem from './SearchBarSuggestItem'; import SearchBarSuggestItem from './SearchBarSuggestItem';
interface Props { interface Props {
query: QueryWithPagesResult<'search'>; query: UseQueryResult<Array<SearchResultItem>, ResourceError<unknown>>;
searchTerm: string; searchTerm: string;
onItemClick: (event: React.MouseEvent<HTMLAnchorElement>) => void; onItemClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
containerId: string; containerId: string;
...@@ -33,7 +36,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -33,7 +36,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
const handleScroll = React.useCallback(() => { const handleScroll = React.useCallback(() => {
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
if (!container || !query.data?.items.length) { if (!container || !query.data?.length) {
return; return;
} }
const topLimit = container.getBoundingClientRect().y + (tabsRef.current?.clientHeight || 0) + 24; const topLimit = container.getBoundingClientRect().y + (tabsRef.current?.clientHeight || 0) + 24;
...@@ -47,7 +50,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -47,7 +50,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
break; break;
} }
} }
}, [ containerId, query.data?.items ]); }, [ containerId, query.data ]);
React.useEffect(() => { React.useEffect(() => {
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
...@@ -63,11 +66,11 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -63,11 +66,11 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
}, [ containerId, handleScroll ]); }, [ containerId, handleScroll ]);
const itemsGroups = React.useMemo(() => { const itemsGroups = React.useMemo(() => {
if (!query.data?.items && !marketplaceApps.displayedApps) { if (!query.data && !marketplaceApps.displayedApps) {
return {}; return {};
} }
const map: Partial<ItemsCategoriesMap> = {}; const map: Partial<ItemsCategoriesMap> = {};
query.data?.items.forEach(item => { query.data?.forEach(item => {
const cat = getItemCategory(item) as ApiCategory; const cat = getItemCategory(item) as ApiCategory;
if (cat) { if (cat) {
if (cat in map) { if (cat in map) {
...@@ -81,7 +84,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -81,7 +84,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
map.app = marketplaceApps.displayedApps; map.app = marketplaceApps.displayedApps;
} }
return map; return map;
}, [ query.data?.items, marketplaceApps.displayedApps ]); }, [ query.data, marketplaceApps.displayedApps ]);
const scrollToCategory = React.useCallback((index: number) => () => { const scrollToCategory = React.useCallback((index: number) => () => {
setTabIndex(index); setTabIndex(index);
...@@ -104,7 +107,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -104,7 +107,7 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props
return <Text>Something went wrong. Try refreshing the page or come back later.</Text>; return <Text>Something went wrong. Try refreshing the page or come back later.</Text>;
} }
if (!query.data.items || query.data.items.length === 0) { if (!query.data || query.data.length === 0) {
return <Text>No results found.</Text>; return <Text>No results found.</Text>;
} }
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import type { SearchResultToken } from 'types/api/search'; import type { SearchResultToken } from 'types/api/search';
import iconSuccess from 'icons/status/success.svg'; import iconSuccess from 'icons/status/success.svg';
import verifiedToken from 'icons/verified_token.svg';
import highlightText from 'lib/highlightText'; import highlightText from 'lib/highlightText';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
...@@ -16,6 +17,7 @@ interface Props { ...@@ -16,6 +17,7 @@ interface Props {
const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => { const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => {
const icon = <TokenLogo boxSize={ 6 } data={ data }/>; const icon = <TokenLogo boxSize={ 6 } data={ data }/>;
const verifiedIcon = <Icon as={ verifiedToken } boxSize={ 4 } color="green.500"/>;
const name = ( const name = (
<Text <Text
fontWeight={ 700 } fontWeight={ 700 }
...@@ -49,7 +51,10 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => { ...@@ -49,7 +51,10 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => {
<> <>
<Flex alignItems="center" gap={ 2 }> <Flex alignItems="center" gap={ 2 }>
{ icon } { icon }
{ name } <Flex alignItems="center" gap={ 1 }>
{ name }
{ data.is_verified_via_admin_panel && verifiedIcon }
</Flex>
</Flex> </Flex>
<Grid templateColumns={ templateCols } alignItems="center" gap={ 2 }> <Grid templateColumns={ templateCols } alignItems="center" gap={ 2 }>
<Flex alignItems="center" overflow="hidden"> <Flex alignItems="center" overflow="hidden">
...@@ -65,7 +70,10 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => { ...@@ -65,7 +70,10 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm }: Props) => {
return ( return (
<Grid templateColumns="24px 200px 1fr auto" gap={ 2 }> <Grid templateColumns="24px 200px 1fr auto" gap={ 2 }>
{ icon } { icon }
{ name } <Flex alignItems="center" gap={ 1 }>
{ name }
{ data.is_verified_via_admin_panel && verifiedIcon }
</Flex>
<Flex alignItems="center" overflow="hidden"> <Flex alignItems="center" overflow="hidden">
{ address } { address }
{ contractVerifiedIcon } { contractVerifiedIcon }
......
import { useRouter } from 'next/router';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useDebounce from 'lib/hooks/useDebounce';
export default function useQuickSearchQuery() {
const router = useRouter();
const [ searchTerm, setSearchTerm ] = React.useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 300);
const pathname = router.pathname;
const query = useApiQuery('quick_search', {
queryParams: { q: debouncedSearchTerm },
queryOptions: { enabled: debouncedSearchTerm.trim().length > 0 },
});
const redirectCheckQuery = useApiQuery('search_check_redirect', {
// on pages with regular search bar we check redirect on every search term change
// in order to prepend its result to suggest list since this resource is much faster than regular search
queryParams: { q: debouncedSearchTerm },
queryOptions: { enabled: Boolean(debouncedSearchTerm) },
});
return React.useMemo(() => ({
searchTerm,
debouncedSearchTerm,
handleSearchTermChange: setSearchTerm,
query,
redirectCheckQuery,
pathname,
}), [ debouncedSearchTerm, pathname, query, redirectCheckQuery, searchTerm ]);
}
...@@ -9,10 +9,10 @@ import { SEARCH_RESULT_ITEM, SEARCH_RESULT_NEXT_PAGE_PARAMS } from 'stubs/search ...@@ -9,10 +9,10 @@ import { SEARCH_RESULT_ITEM, SEARCH_RESULT_NEXT_PAGE_PARAMS } from 'stubs/search
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
export default function useSearchQuery(isSearchPage = false) { export default function useSearchQuery() {
const router = useRouter(); const router = useRouter();
const q = React.useRef(getQueryParamString(router.query.q)); const q = React.useRef(getQueryParamString(router.query.q));
const initialValue = isSearchPage ? q.current : ''; const initialValue = q.current;
const [ searchTerm, setSearchTerm ] = React.useState(initialValue); const [ searchTerm, setSearchTerm ] = React.useState(initialValue);
...@@ -24,24 +24,18 @@ export default function useSearchQuery(isSearchPage = false) { ...@@ -24,24 +24,18 @@ export default function useSearchQuery(isSearchPage = false) {
filters: { q: debouncedSearchTerm }, filters: { q: debouncedSearchTerm },
options: { options: {
enabled: debouncedSearchTerm.trim().length > 0, enabled: debouncedSearchTerm.trim().length > 0,
placeholderData: isSearchPage ? placeholderData: generateListStub<'search'>(SEARCH_RESULT_ITEM, 50, { next_page_params: SEARCH_RESULT_NEXT_PAGE_PARAMS }),
generateListStub<'search'>(SEARCH_RESULT_ITEM, 50, { next_page_params: SEARCH_RESULT_NEXT_PAGE_PARAMS }) :
undefined,
}, },
}); });
const redirectCheckQuery = useApiQuery('search_check_redirect', { const redirectCheckQuery = useApiQuery('search_check_redirect', {
// on search result page we check redirect only once on mount // on search result page we check redirect only once on mount
// on pages with regular search bar we check redirect on every search term change queryParams: { q: q.current },
// in order to prepend its result to suggest list since this resource is much faster than regular search queryOptions: { enabled: Boolean(q.current) },
queryParams: { q: isSearchPage ? q.current : debouncedSearchTerm },
queryOptions: { enabled: Boolean(isSearchPage ? q.current : debouncedSearchTerm) },
}); });
useUpdateValueEffect(() => { useUpdateValueEffect(() => {
if (isSearchPage) { query.onFilterChange({ q: debouncedSearchTerm });
query.onFilterChange({ q: debouncedSearchTerm });
}
}, debouncedSearchTerm); }, debouncedSearchTerm);
return React.useMemo(() => ({ return React.useMemo(() => ({
......
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