Commit 336fcf55 authored by isstuev's avatar isstuev

search

parent 09e4f139
import type { SearchResultToken, SearchResultBlock, SearchResultAddressOrContract, SearchResultTx, SearchResultLabel, SearchResult } from 'types/api/search'; import type {
SearchResultToken,
SearchResultBlock,
SearchResultAddressOrContract,
SearchResultTx,
SearchResultLabel,
SearchResult,
SearchResultUserOp,
} from 'types/api/search';
export const token1: SearchResultToken = { export const token1: SearchResultToken = {
address: '0x377c5F2B300B25a534d4639177873b7fEAA56d4B', address: '0x377c5F2B300B25a534d4639177873b7fEAA56d4B',
...@@ -101,6 +109,13 @@ export const tx1: SearchResultTx = { ...@@ -101,6 +109,13 @@ export const tx1: SearchResultTx = {
url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd',
}; };
export const userOp1: SearchResultUserOp = {
timestamp: '2024-01-11T14:15:48.000000Z',
type: 'user_operation',
user_operation_hash: '0xcb560d77b0f3af074fa05c1e5c691bcdfe457e630062b5907e9e71fc74b2ec61',
url: '/op/0xcb560d77b0f3af074fa05c1e5c691bcdfe457e630062b5907e9e71fc74b2ec61',
};
export const baseResponse: SearchResult = { export const baseResponse: SearchResult = {
items: [ items: [
token1, token1,
......
...@@ -55,7 +55,14 @@ export interface SearchResultTx { ...@@ -55,7 +55,14 @@ export interface SearchResultTx {
url?: string; // not used by the frontend, we build the url ourselves url?: string; // not used by the frontend, we build the url ourselves
} }
export type SearchResultItem = SearchResultToken | SearchResultAddressOrContract | SearchResultBlock | SearchResultTx | SearchResultLabel; export interface SearchResultUserOp {
type: 'user_operation';
user_operation_hash: string;
timestamp: string;
url?: string; // not used by the frontend, we build the url ourselves
}
export type SearchResultItem = SearchResultToken | SearchResultAddressOrContract | SearchResultBlock | SearchResultTx | SearchResultLabel | SearchResultUserOp;
export interface SearchResult { export interface SearchResult {
items: Array<SearchResultItem>; items: Array<SearchResultItem>;
...@@ -79,5 +86,5 @@ export interface SearchResultFilters { ...@@ -79,5 +86,5 @@ export interface SearchResultFilters {
export interface SearchRedirectResult { export interface SearchRedirectResult {
parameter: string | null; parameter: string | null;
redirect: boolean; redirect: boolean;
type: 'address' | 'block' | 'transaction' | null; type: 'address' | 'block' | 'transaction' | 'user_operation' | null;
} }
...@@ -8,6 +8,7 @@ import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; ...@@ -8,6 +8,7 @@ import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app'; import * as app from 'playwright/utils/app';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import SearchResults from './SearchResults'; import SearchResults from './SearchResults';
...@@ -157,6 +158,36 @@ test('search by tx hash +@mobile', async({ mount, page }) => { ...@@ -157,6 +158,36 @@ test('search by tx hash +@mobile', async({ mount, page }) => {
await expect(component.locator('main')).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
}); });
const testWithUserOps = test.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.userOps) as any,
});
testWithUserOps('search by user op hash +@mobile', async({ mount, page }) => {
const hooksConfig = {
router: {
query: { q: searchMock.userOp1.user_operation_hash },
},
};
await page.route(buildApiUrl('search') + `?q=${ searchMock.userOp1.user_operation_hash }`, (route) => route.fulfill({
status: 200,
body: JSON.stringify({
items: [
searchMock.userOp1,
],
}),
}));
const component = await mount(
<TestApp>
<SearchResults/>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
});
test.describe('with apps', () => { test.describe('with apps', () => {
const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || ''; const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
const extendedTest = test.extend({ const extendedTest = test.extend({
......
...@@ -3,6 +3,7 @@ import { useRouter } from 'next/router'; ...@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import React from 'react'; import React from 'react';
import config from 'configs/app';
import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps'; import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps';
import SearchResultListItem from 'ui/searchResults/SearchResultListItem'; import SearchResultListItem from 'ui/searchResults/SearchResultListItem';
import SearchResultsInput from 'ui/searchResults/SearchResultsInput'; import SearchResultsInput from 'ui/searchResults/SearchResultsInput';
...@@ -52,6 +53,10 @@ const SearchResultsPageContent = () => { ...@@ -52,6 +53,10 @@ const SearchResultsPageContent = () => {
router.replace({ pathname: '/tx/[hash]', query: { hash: redirectCheckQuery.data.parameter } }); router.replace({ pathname: '/tx/[hash]', query: { hash: redirectCheckQuery.data.parameter } });
return; return;
} }
case 'user_operation': {
router.replace({ pathname: '/op/[hash]', query: { hash: redirectCheckQuery.data.parameter } });
return;
}
} }
} }
...@@ -62,12 +67,19 @@ const SearchResultsPageContent = () => { ...@@ -62,12 +67,19 @@ const SearchResultsPageContent = () => {
event.preventDefault(); event.preventDefault();
}, [ ]); }, [ ]);
const dataToDisplay = (data?.items || []).filter((item) => {
if (!config.features.userOps.isEnabled && item.type === 'user_operation') {
return false;
}
return true;
});
const content = (() => { const content = (() => {
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
const hasData = data?.items.length || (pagination.page === 1 && marketplaceApps.displayedApps.length); const hasData = dataToDisplay.length || (pagination.page === 1 && marketplaceApps.displayedApps.length);
if (!hasData) { if (!hasData) {
return null; return null;
...@@ -83,7 +95,7 @@ const SearchResultsPageContent = () => { ...@@ -83,7 +95,7 @@ const SearchResultsPageContent = () => {
searchTerm={ debouncedSearchTerm } searchTerm={ debouncedSearchTerm }
/> />
)) } )) }
{ data && data.items.map((item, index) => ( { dataToDisplay.map((item, index) => (
<SearchResultListItem <SearchResultListItem
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index } key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
data={ item } data={ item }
...@@ -110,7 +122,7 @@ const SearchResultsPageContent = () => { ...@@ -110,7 +122,7 @@ const SearchResultsPageContent = () => {
searchTerm={ debouncedSearchTerm } searchTerm={ debouncedSearchTerm }
/> />
)) } )) }
{ data && data.items.map((item, index) => ( { dataToDisplay.map((item, index) => (
<SearchResultTableItem <SearchResultTableItem
key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index } key={ (isPlaceholderData ? 'placeholder_' : 'actual_') + index }
data={ item } data={ item }
...@@ -130,7 +142,7 @@ const SearchResultsPageContent = () => { ...@@ -130,7 +142,7 @@ const SearchResultsPageContent = () => {
return null; return null;
} }
const resultsCount = pagination.page === 1 && !data?.next_page_params ? (data?.items.length || 0) + marketplaceApps.displayedApps.length : '50+'; const resultsCount = pagination.page === 1 && !data?.next_page_params ? (dataToDisplay.length || 0) + marketplaceApps.displayedApps.length : '50+';
const text = isPlaceholderData && pagination.page === 1 ? ( const text = isPlaceholderData && pagination.page === 1 ? (
<Skeleton h={ 6 } w="280px" borderRadius="full" mb={ pagination.isVisible ? 0 : 6 }/> <Skeleton h={ 6 } w="280px" borderRadius="full" mb={ pagination.isVisible ? 0 : 6 }/>
...@@ -141,7 +153,7 @@ const SearchResultsPageContent = () => { ...@@ -141,7 +153,7 @@ const SearchResultsPageContent = () => {
<chakra.span fontWeight={ 700 }> <chakra.span fontWeight={ 700 }>
{ resultsCount } { resultsCount }
</chakra.span> </chakra.span>
<span> matching result{ (((data?.items.length || 0) + marketplaceApps.displayedApps.length) > 1) || pagination.page > 1 ? 's' : '' } for </span> <span> matching result{ (((dataToDisplay.length || 0) + marketplaceApps.displayedApps.length) > 1) || pagination.page > 1 ? 's' : '' } for </span>
<chakra.span fontWeight={ 700 }>{ debouncedSearchTerm }</chakra.span> <chakra.span fontWeight={ 700 }>{ debouncedSearchTerm }</chakra.span>
</Box> </Box>
) )
......
...@@ -15,6 +15,7 @@ import * as AddressEntity from 'ui/shared/entities/address/AddressEntity'; ...@@ -15,6 +15,7 @@ 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';
import * as TxEntity from 'ui/shared/entities/tx/TxEntity'; import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
import * as UserOpEtity from 'ui/shared/entities/userOp/UserOpEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
...@@ -56,7 +57,6 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -56,7 +57,6 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
wordBreak="break-all" wordBreak="break-all"
isLoading={ isLoading } isLoading={ isLoading }
onClick={ handleLinkClick } onClick={ handleLinkClick }
flexGrow={ 1 }
overflow="hidden" overflow="hidden"
> >
<Skeleton <Skeleton
...@@ -200,6 +200,26 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -200,6 +200,26 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
</TxEntity.Container> </TxEntity.Container>
); );
} }
case 'user_operation': {
return (
<UserOpEtity.Container>
<UserOpEtity.Icon/>
<UserOpEtity.Link
isLoading={ isLoading }
hash={ data.user_operation_hash }
onClick={ handleLinkClick }
>
<UserOpEtity.Content
asProp="mark"
hash={ data.user_operation_hash }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 700 }
/>
</UserOpEtity.Link>
</UserOpEtity.Container>
);
}
} }
})(); })();
...@@ -240,6 +260,12 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -240,6 +260,12 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text> <Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
); );
} }
case 'user_operation': {
return (
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
);
}
case 'label': { case 'label': {
return ( return (
<Flex alignItems="center"> <Flex alignItems="center">
...@@ -295,12 +321,12 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -295,12 +321,12 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
return ( return (
<ListItemMobile py={ 3 } fontSize="sm" rowGap={ 2 }> <ListItemMobile py={ 3 } fontSize="sm" rowGap={ 2 }>
<Flex justifyContent="space-between" w="100%" overflow="hidden" lineHeight={ 6 }> <Grid templateColumns="1fr auto" w="100%" overflow="hidden" lineHeight={ 6 }>
{ firstRow } { firstRow }
<Skeleton isLoaded={ !isLoading } color="text_secondary" ml={ 8 } textTransform="capitalize"> <Skeleton isLoaded={ !isLoading } color="text_secondary" ml={ 8 } textTransform="capitalize">
<span>{ category ? searchItemTitles[category].itemTitleShort : '' }</span> <span>{ category ? searchItemTitles[category].itemTitleShort : '' }</span>
</Skeleton> </Skeleton>
</Flex> </Grid>
{ Boolean(secondRow) && ( { Boolean(secondRow) && (
<Box w="100%" overflow="hidden" whiteSpace={ data.type !== 'app' ? 'nowrap' : undefined }> <Box w="100%" overflow="hidden" whiteSpace={ data.type !== 'app' ? 'nowrap' : undefined }>
{ secondRow } { secondRow }
......
...@@ -15,6 +15,7 @@ import * as AddressEntity from 'ui/shared/entities/address/AddressEntity'; ...@@ -15,6 +15,7 @@ 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';
import * as TxEntity from 'ui/shared/entities/tx/TxEntity'; import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
import * as UserOpEtity from 'ui/shared/entities/userOp/UserOpEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
...@@ -284,6 +285,33 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => { ...@@ -284,6 +285,33 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
</> </>
); );
} }
case 'user_operation': {
return (
<>
<Td colSpan={ 2 } fontSize="sm">
<UserOpEtity.Container>
<UserOpEtity.Icon/>
<UserOpEtity.Link
isLoading={ isLoading }
hash={ data.user_operation_hash }
onClick={ handleLinkClick }
>
<UserOpEtity.Content
asProp="mark"
hash={ data.user_operation_hash }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 700 }
/>
</UserOpEtity.Link>
</UserOpEtity.Container>
</Td>
<Td fontSize="sm" verticalAlign="middle" isNumeric>
<Text variant="secondary">{ dayjs(data.timestamp).format('llll') }</Text>
</Td>
</>
);
}
} }
})(); })();
......
import type { SearchResultItem } from 'types/api/search'; import type { SearchResultItem } from 'types/api/search';
import type { MarketplaceAppOverview } from 'types/client/marketplace'; import type { MarketplaceAppOverview } from 'types/client/marketplace';
export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block'; import config from 'configs/app';
export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block' | 'user_operation';
export type Category = ApiCategory | 'app'; export type Category = ApiCategory | 'app';
export type ItemsCategoriesMap = export type ItemsCategoriesMap =
...@@ -23,6 +25,10 @@ export const searchCategories: Array<{id: Category; title: string }> = [ ...@@ -23,6 +25,10 @@ export const searchCategories: Array<{id: Category; title: string }> = [
{ id: 'block', title: 'Blocks' }, { id: 'block', title: 'Blocks' },
]; ];
if (config.features.userOps.isEnabled) {
searchCategories.push({ id: 'user_operation', title: 'User operations' });
}
export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = { export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleShort: string }> = {
app: { itemTitle: 'App', itemTitleShort: 'App' }, app: { itemTitle: 'App', itemTitleShort: 'App' },
token: { itemTitle: 'Token', itemTitleShort: 'Token' }, token: { itemTitle: 'Token', itemTitleShort: 'Token' },
...@@ -31,6 +37,7 @@ export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleSh ...@@ -31,6 +37,7 @@ export const searchItemTitles: Record<Category, { itemTitle: string; itemTitleSh
public_tag: { itemTitle: 'Public tag', itemTitleShort: 'Tag' }, public_tag: { itemTitle: 'Public tag', itemTitleShort: 'Tag' },
transaction: { itemTitle: 'Transaction', itemTitleShort: 'Txn' }, transaction: { itemTitle: 'Transaction', itemTitleShort: 'Txn' },
block: { itemTitle: 'Block', itemTitleShort: 'Block' }, block: { itemTitle: 'Block', itemTitleShort: 'Block' },
user_operation: { itemTitle: 'User operation', itemTitleShort: 'User op' },
}; };
export function getItemCategory(item: SearchResultItem | SearchResultAppItem): Category | undefined { export function getItemCategory(item: SearchResultItem | SearchResultAppItem): Category | undefined {
...@@ -57,5 +64,8 @@ export function getItemCategory(item: SearchResultItem | SearchResultAppItem): C ...@@ -57,5 +64,8 @@ export function getItemCategory(item: SearchResultItem | SearchResultAppItem): C
case 'app': { case 'app': {
return 'app'; return 'app';
} }
case 'user_operation': {
return 'user_operation';
}
} }
} }
...@@ -9,6 +9,7 @@ import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; ...@@ -9,6 +9,7 @@ import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app'; import * as app from 'playwright/utils/app';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
...@@ -204,6 +205,35 @@ test('search by tx hash +@mobile', async({ mount, page }) => { ...@@ -204,6 +205,35 @@ test('search by tx hash +@mobile', async({ mount, page }) => {
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } }); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
}); });
const testWithUserOps = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.userOps) as any,
});
testWithUserOps('search by user op hash +@mobile', async({ mount, page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: JSON.stringify(textAdMock.duck),
}));
const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.tx1.tx_hash }`;
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify([
searchMock.userOp1,
]),
}));
await mount(
<TestApp>
<SearchBar/>
</TestApp>,
);
await page.getByPlaceholder(/search/i).type(searchMock.tx1.tx_hash);
await page.waitForResponse(API_URL);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
test('search with view all link', async({ mount, page }) => { test('search with view all link', async({ mount, page }) => {
const API_URL = buildApiUrl('quick_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({
......
...@@ -111,12 +111,12 @@ const SearchBarSuggest = ({ query, searchTerm, onItemClick, containerId }: Props ...@@ -111,12 +111,12 @@ 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 || query.data.length === 0) { const resultCategories = searchCategories.filter(cat => itemsGroups[cat.id]);
if (resultCategories.length === 0) {
return <Text>No results found.</Text>; return <Text>No results found.</Text>;
} }
const resultCategories = searchCategories.filter(cat => itemsGroups[cat.id]);
return ( return (
<> <>
{ resultCategories.length > 1 && ( { resultCategories.length > 1 && (
......
...@@ -12,6 +12,7 @@ import SearchBarSuggestItemLink from './SearchBarSuggestItemLink'; ...@@ -12,6 +12,7 @@ import SearchBarSuggestItemLink from './SearchBarSuggestItemLink';
import SearchBarSuggestLabel from './SearchBarSuggestLabel'; import SearchBarSuggestLabel from './SearchBarSuggestLabel';
import SearchBarSuggestToken from './SearchBarSuggestToken'; import SearchBarSuggestToken from './SearchBarSuggestToken';
import SearchBarSuggestTx from './SearchBarSuggestTx'; import SearchBarSuggestTx from './SearchBarSuggestTx';
import SearchBarSuggestUserOp from './SearchBarSuggestUserOp';
interface Props { interface Props {
data: SearchResultItem; data: SearchResultItem;
...@@ -38,6 +39,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) => ...@@ -38,6 +39,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) =>
case 'block': { case 'block': {
return route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } }); return route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.block_hash) } });
} }
case 'user_operation': {
return route({ pathname: '/op/[hash]', query: { hash: data.user_operation_hash } });
}
} }
})(); })();
...@@ -60,6 +64,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) => ...@@ -60,6 +64,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) =>
case 'transaction': { case 'transaction': {
return <SearchBarSuggestTx data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>; return <SearchBarSuggestTx data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
} }
case 'user_operation': {
return <SearchBarSuggestUserOp data={ data } searchTerm={ searchTerm } isMobile={ isMobile }/>;
}
} }
})(); })();
......
import { chakra, Text, Flex } from '@chakra-ui/react';
import React from 'react';
import type { SearchResultUserOp } from 'types/api/search';
import dayjs from 'lib/date/dayjs';
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props {
data: SearchResultUserOp;
isMobile: boolean | undefined;
searchTerm: string;
}
const SearchBarSuggestTx = ({ data, isMobile }: Props) => {
const icon = <UserOpEntity.Icon/>;
const hash = (
<chakra.mark overflow="hidden" whiteSpace="nowrap" fontWeight={ 700 }>
<HashStringShortenDynamic hash={ data.user_operation_hash } isTooltipDisabled/>
</chakra.mark>
);
const date = dayjs(data.timestamp).format('llll');
if (isMobile) {
return (
<>
<Flex alignItems="center">
{ icon }
{ hash }
</Flex>
<Text variant="secondary">{ date }</Text>
</>
);
}
return (
<Flex columnGap={ 2 }>
<Flex alignItems="center" minW={ 0 }>
{ icon }
{ hash }
</Flex>
<Text variant="secondary" textAlign="end" flexShrink={ 0 } ml="auto">{ date }</Text>
</Flex>
);
};
export default React.memo(SearchBarSuggestTx);
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