diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 185c0c6423..f7fa0afd1d 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -24,7 +24,7 @@ import type { InternalTransactionsResponse } from 'types/api/internalTransaction import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { OutputRootsResponse } from 'types/api/outputRoots'; import type { RawTracesResponse } from 'types/api/rawTrace'; -import type { SearchResult, SearchResultFilters } from 'types/api/search'; +import type { SearchRedirectResult, SearchResult, SearchResultFilters } from 'types/api/search'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { TokenCounters, @@ -529,6 +529,7 @@ Q extends 'token_instance_transfers' ? TokenInstanceTransferResponse : Q extends 'token_inventory' ? TokenInventoryResponse : Q extends 'tokens' ? TokensResponse : Q extends 'search' ? SearchResult : +Q extends 'search_check_redirect' ? SearchRedirectResult : Q extends 'contract' ? SmartContract : Q extends 'contract_methods_read' ? Array : Q extends 'contract_methods_read_proxy' ? Array : diff --git a/pages/search-results.tsx b/pages/search-results.tsx index 28fb839d35..ec92ccccd4 100644 --- a/pages/search-results.tsx +++ b/pages/search-results.tsx @@ -1,16 +1,8 @@ -import type { GetServerSideProps, NextPage } from 'next'; +import type { NextPage } from 'next'; import Head from 'next/head'; -import { route } from 'nextjs-routes'; import React from 'react'; -import type { SearchRedirectResult } from 'types/api/search'; - -import buildUrlNode from 'lib/api/buildUrlNode'; -import fetchFactory from 'lib/api/nodeFetch'; import getNetworkTitle from 'lib/networks/getNetworkTitle'; -import type { Props } from 'lib/next/getServerSideProps'; -import { getServerSideProps as getServerSidePropsBase } from 'lib/next/getServerSideProps'; -import * as serverTiming from 'lib/next/serverTiming'; import SearchResults from 'ui/pages/SearchResults'; const SearchResultsPage: NextPage = () => { @@ -27,53 +19,4 @@ const SearchResultsPage: NextPage = () => { export default SearchResultsPage; -export const getServerSideProps: GetServerSideProps = async({ req, res, resolvedUrl, query }) => { - const start = Date.now(); - - try { - const q = String(query.q); - const url = buildUrlNode('search_check_redirect', undefined, { q }); - const redirectsResponse = await fetchFactory(req)(url, { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore:next-line timeout property exist for AbortSignal since Node.js 17 - https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal - // but @types/node has not updated their types yet, see issue https://github.com/DefinitelyTyped/DefinitelyTyped/issues/60868 - signal: AbortSignal.timeout(1_000), - }); - const payload = await redirectsResponse.json() as SearchRedirectResult; - - if (!payload || typeof payload !== 'object' || !payload.redirect) { - throw Error(); - } - - const redirectUrl = (() => { - switch (payload.type) { - case 'block': { - return route({ pathname: '/block/[height]', query: { height: q } }); - } - case 'address': { - return route({ pathname: '/address/[hash]', query: { hash: payload.parameter || q } }); - } - case 'transaction': { - return route({ pathname: '/tx/[hash]', query: { hash: q } }); - } - } - })(); - - if (!redirectUrl) { - throw Error(); - } - - return { - redirect: { - destination: redirectUrl, - permanent: false, - }, - }; - } catch (error) {} - - const end = Date.now(); - - serverTiming.appendValue(res, 'query.search.check-redirect', end - start); - - return getServerSidePropsBase({ req, res, resolvedUrl, query }); -}; +export { getServerSideProps } from 'lib/next/getServerSideProps'; diff --git a/ui/pages/SearchResults.pw.tsx b/ui/pages/SearchResults.pw.tsx index bad6aaac90..adf3f46a82 100644 --- a/ui/pages/SearchResults.pw.tsx +++ b/ui/pages/SearchResults.pw.tsx @@ -62,7 +62,7 @@ test('search by address hash +@mobile', async({ mount, page }) => { test('search by block number +@mobile', async({ mount, page }) => { const hooksConfig = { router: { - query: { q: searchMock.block1.block_number }, + query: { q: String(searchMock.block1.block_number) }, }, }; await page.route(buildApiUrl('search') + `?q=${ searchMock.block1.block_number }`, (route) => route.fulfill({ diff --git a/ui/pages/SearchResults.tsx b/ui/pages/SearchResults.tsx index 8767074e89..b06fccd9e1 100644 --- a/ui/pages/SearchResults.tsx +++ b/ui/pages/SearchResults.tsx @@ -1,4 +1,5 @@ import { Box, chakra, Table, Tbody, Tr, Th, Skeleton, Show, Hide } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; import type { FormEvent } from 'react'; import React from 'react'; @@ -17,9 +18,29 @@ import SearchBarInput from 'ui/snippets/searchBar/SearchBarInput'; import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery'; const SearchResultsPageContent = () => { - const { query, searchTerm, debouncedSearchTerm, handleSearchTermChange } = useSearchQuery(true); + const router = useRouter(); + const { query, redirectCheckQuery, searchTerm, debouncedSearchTerm, handleSearchTermChange } = useSearchQuery(true); const { data, isError, isLoading, pagination, isPaginationVisible } = query; + React.useEffect(() => { + if (redirectCheckQuery.data?.redirect && redirectCheckQuery.data.parameter) { + switch (redirectCheckQuery.data.type) { + case 'block': { + router.push({ pathname: '/block/[height]', query: { height: redirectCheckQuery.data.parameter } }); + return; + } + case 'address': { + router.push({ pathname: '/address/[hash]', query: { hash: redirectCheckQuery.data.parameter } }); + return; + } + case 'transaction': { + router.push({ pathname: '/tx/[hash]', query: { hash: redirectCheckQuery.data.parameter } }); + return; + } + } + } + }, [ redirectCheckQuery.data, router ]); + const handleSubmit = React.useCallback((event: FormEvent) => { event.preventDefault(); }, [ ]); @@ -29,7 +50,7 @@ const SearchResultsPageContent = () => { return ; } - if (isLoading) { + if (isLoading || redirectCheckQuery.isLoading) { return ( @@ -70,7 +91,7 @@ const SearchResultsPageContent = () => { return null; } - const text = isLoading ? ( + const text = isLoading || redirectCheckQuery.isLoading ? ( ) : ( ( @@ -129,7 +150,10 @@ const SearchResultsPageContent = () => { return ( - + { isLoading || redirectCheckQuery.isLoading ? + : + + } { bar } { content } diff --git a/ui/snippets/searchBar/useSearchQuery.tsx b/ui/snippets/searchBar/useSearchQuery.tsx index 763f213b86..6887a39bfd 100644 --- a/ui/snippets/searchBar/useSearchQuery.tsx +++ b/ui/snippets/searchBar/useSearchQuery.tsx @@ -1,13 +1,16 @@ import { useRouter } from 'next/router'; import React from 'react'; +import useApiQuery from 'lib/api/useApiQuery'; import useDebounce from 'lib/hooks/useDebounce'; import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useUpdateValueEffect from 'lib/hooks/useUpdateValueEffect'; +import getQueryParamString from 'lib/router/getQueryParamString'; export default function useSearchQuery(isSearchPage = false) { const router = useRouter(); - const initialValue = isSearchPage ? String(router.query.q || '') : ''; + const q = React.useRef(getQueryParamString(router.query.q)); + const initialValue = isSearchPage ? q.current : ''; const [ searchTerm, setSearchTerm ] = React.useState(initialValue); @@ -19,6 +22,11 @@ export default function useSearchQuery(isSearchPage = false) { options: { enabled: debouncedSearchTerm.trim().length > 0 }, }); + const redirectCheckQuery = useApiQuery('search_check_redirect', { + queryParams: { q: q.current }, + queryOptions: { enabled: isSearchPage && Boolean(q) }, + }); + useUpdateValueEffect(() => { if (isSearchPage) { query.onFilterChange({ q: debouncedSearchTerm }); @@ -30,5 +38,6 @@ export default function useSearchQuery(isSearchPage = false) { debouncedSearchTerm, handleSearchTermChange: setSearchTerm, query, + redirectCheckQuery, }; }