Skip to content

Commit 574793f

Browse files
committed
feat: enhance SearcherBar to support initial search from location state
1 parent 9f4ba5a commit 574793f

File tree

4 files changed

+92
-9
lines changed

4 files changed

+92
-9
lines changed

src/modules/search/SearcherBar.tsx

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cn } from '@/lib/utils';
33
import { useMutation } from '@tanstack/react-query';
44
import { useNavigate } from '@tanstack/react-router';
55
import { Search } from 'lucide-react';
6-
import { useEffect, useState } from 'react';
6+
import { useEffect, useRef, useState } from 'react';
77
import { ChainLink } from '@/components/ChainLink';
88
import { Button } from '@/components/ui/button';
99
import { Input } from '@/components/ui/input';
@@ -12,11 +12,19 @@ import useUserStore from '@/stores/useUser.store';
1212
import { getChainFromId } from '@/utils/chain.utils';
1313
import { searchQuery } from './searchQuery';
1414

15-
export function SearcherBar({ className }: { className?: string }) {
15+
export function SearcherBar({
16+
className,
17+
initialSearch,
18+
}: {
19+
className?: string;
20+
initialSearch?: string;
21+
}) {
1622
const { isConnected, address: userAddress, chainId } = useUserStore();
1723
const [inputValue, setInputValue] = useState('');
1824
const [shake, setShake] = useState(false);
1925
const [errorCount, setErrorCount] = useState(0);
26+
const [localError, setLocalError] = useState<Error | null>(null);
27+
const inputRef = useRef<HTMLInputElement | null>(null);
2028

2129
const navigate = useNavigate();
2230

@@ -43,7 +51,7 @@ export function SearcherBar({ className }: { className?: string }) {
4351
}
4452
};
4553

46-
const { mutate, isPending, isError, error } = useMutation({
54+
const { mutate, mutateAsync, isPending, isError, error } = useMutation({
4755
mutationKey: ['search', inputValue],
4856
mutationFn: async (value: string) => {
4957
const isValid =
@@ -74,7 +82,6 @@ export function SearcherBar({ className }: { className?: string }) {
7482
if (isEmpty) {
7583
throw new Error('No data found');
7684
}
77-
console.log(result);
7885
return { result, id: resolvedValue };
7986
},
8087
onSuccess: (data) => {
@@ -85,12 +92,35 @@ export function SearcherBar({ className }: { className?: string }) {
8592

8693
onError: (err) => {
8794
console.error('Search error:', err);
95+
inputRef.current.focus();
8896
requestAnimationFrame(() => {
8997
setErrorCount((prev) => prev + 1);
9098
});
9199
},
92100
});
93101

102+
useEffect(() => {
103+
const run = async () => {
104+
if (initialSearch && chainId) {
105+
const normalized = initialSearch.trim().toLowerCase();
106+
setInputValue(normalized);
107+
try {
108+
setLocalError(null); // reset
109+
await mutateAsync(normalized);
110+
} catch (err) {
111+
console.error('Initial search error:', err);
112+
setErrorCount((prev) => prev + 1);
113+
// mutation don't return error when used in useEffect we need to use a local state
114+
setLocalError(
115+
err instanceof Error ? err : new Error('Unknown error')
116+
);
117+
}
118+
}
119+
};
120+
121+
run();
122+
}, [initialSearch, chainId]);
123+
94124
useEffect(() => {
95125
if (errorCount > 0) {
96126
setShake(true);
@@ -113,22 +143,23 @@ export function SearcherBar({ className }: { className?: string }) {
113143
<div className={cn('m-auto w-full', className)}>
114144
<div className="relative w-full">
115145
<Input
146+
ref={inputRef}
116147
value={inputValue}
117148
onChange={(e) => setInputValue(e.target.value)}
118149
onKeyDown={handleKeyDown}
119150
disabled={isPending}
120151
className={cn(
121152
'bg-input border-secondary w-full rounded-2xl py-5.5 pl-12 sm:py-6.5',
122153
isConnected && 'sm:pr-32',
123-
isError &&
154+
(isError || localError) &&
124155
'focus-visible:border-danger-border focus:outline-danger-border focus-visible:ring-danger-border',
125156
shake && 'animate-shake'
126157
)}
127158
placeholder="Search address, deal id, task id, transaction hash..."
128159
/>
129-
{isError && (
160+
{(localError || error) && (
130161
<p className="bg-danger text-danger-foreground border-danger-border absolute -bottom-8 rounded-full border px-4">
131-
{error.message}
162+
{localError ? localError.message : error?.message}
132163
</p>
133164
)}
134165
<Search

src/routeTree.gen.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { Route as ChainSlugLayoutAppsImport } from './routes/$chainSlug/_layout/
2424
import { Route as ChainSlugLayoutWorkerpoolWorkerpoolAddressImport } from './routes/$chainSlug/_layout/workerpool/$workerpoolAddress'
2525
import { Route as ChainSlugLayoutTxTxAddressImport } from './routes/$chainSlug/_layout/tx/$txAddress'
2626
import { Route as ChainSlugLayoutTaskTaskAddressImport } from './routes/$chainSlug/_layout/task/$taskAddress'
27+
import { Route as ChainSlugLayoutSearchSearchImport } from './routes/$chainSlug/_layout/search/$search'
2728
import { Route as ChainSlugLayoutDealDealAddressImport } from './routes/$chainSlug/_layout/deal/$dealAddress'
2829
import { Route as ChainSlugLayoutDatasetDatasetAddressImport } from './routes/$chainSlug/_layout/dataset/$datasetAddress'
2930
import { Route as ChainSlugLayoutAppAppAddressImport } from './routes/$chainSlug/_layout/app/$appAddress'
@@ -112,6 +113,13 @@ const ChainSlugLayoutTaskTaskAddressRoute =
112113
getParentRoute: () => ChainSlugLayoutRoute,
113114
} as any)
114115

116+
const ChainSlugLayoutSearchSearchRoute =
117+
ChainSlugLayoutSearchSearchImport.update({
118+
id: '/search/$search',
119+
path: '/search/$search',
120+
getParentRoute: () => ChainSlugLayoutRoute,
121+
} as any)
122+
115123
const ChainSlugLayoutDealDealAddressRoute =
116124
ChainSlugLayoutDealDealAddressImport.update({
117125
id: '/deal/$dealAddress',
@@ -235,6 +243,13 @@ declare module '@tanstack/react-router' {
235243
preLoaderRoute: typeof ChainSlugLayoutDealDealAddressImport
236244
parentRoute: typeof ChainSlugLayoutImport
237245
}
246+
'/$chainSlug/_layout/search/$search': {
247+
id: '/$chainSlug/_layout/search/$search'
248+
path: '/search/$search'
249+
fullPath: '/$chainSlug/search/$search'
250+
preLoaderRoute: typeof ChainSlugLayoutSearchSearchImport
251+
parentRoute: typeof ChainSlugLayoutImport
252+
}
238253
'/$chainSlug/_layout/task/$taskAddress': {
239254
id: '/$chainSlug/_layout/task/$taskAddress'
240255
path: '/task/$taskAddress'
@@ -272,6 +287,7 @@ interface ChainSlugLayoutRouteChildren {
272287
ChainSlugLayoutAppAppAddressRoute: typeof ChainSlugLayoutAppAppAddressRoute
273288
ChainSlugLayoutDatasetDatasetAddressRoute: typeof ChainSlugLayoutDatasetDatasetAddressRoute
274289
ChainSlugLayoutDealDealAddressRoute: typeof ChainSlugLayoutDealDealAddressRoute
290+
ChainSlugLayoutSearchSearchRoute: typeof ChainSlugLayoutSearchSearchRoute
275291
ChainSlugLayoutTaskTaskAddressRoute: typeof ChainSlugLayoutTaskTaskAddressRoute
276292
ChainSlugLayoutTxTxAddressRoute: typeof ChainSlugLayoutTxTxAddressRoute
277293
ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute: typeof ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute
@@ -290,6 +306,7 @@ const ChainSlugLayoutRouteChildren: ChainSlugLayoutRouteChildren = {
290306
ChainSlugLayoutDatasetDatasetAddressRoute:
291307
ChainSlugLayoutDatasetDatasetAddressRoute,
292308
ChainSlugLayoutDealDealAddressRoute: ChainSlugLayoutDealDealAddressRoute,
309+
ChainSlugLayoutSearchSearchRoute: ChainSlugLayoutSearchSearchRoute,
293310
ChainSlugLayoutTaskTaskAddressRoute: ChainSlugLayoutTaskTaskAddressRoute,
294311
ChainSlugLayoutTxTxAddressRoute: ChainSlugLayoutTxTxAddressRoute,
295312
ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute:
@@ -325,6 +342,7 @@ export interface FileRoutesByFullPath {
325342
'/$chainSlug/app/$appAddress': typeof ChainSlugLayoutAppAppAddressRoute
326343
'/$chainSlug/dataset/$datasetAddress': typeof ChainSlugLayoutDatasetDatasetAddressRoute
327344
'/$chainSlug/deal/$dealAddress': typeof ChainSlugLayoutDealDealAddressRoute
345+
'/$chainSlug/search/$search': typeof ChainSlugLayoutSearchSearchRoute
328346
'/$chainSlug/task/$taskAddress': typeof ChainSlugLayoutTaskTaskAddressRoute
329347
'/$chainSlug/tx/$txAddress': typeof ChainSlugLayoutTxTxAddressRoute
330348
'/$chainSlug/workerpool/$workerpoolAddress': typeof ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute
@@ -342,6 +360,7 @@ export interface FileRoutesByTo {
342360
'/$chainSlug/app/$appAddress': typeof ChainSlugLayoutAppAppAddressRoute
343361
'/$chainSlug/dataset/$datasetAddress': typeof ChainSlugLayoutDatasetDatasetAddressRoute
344362
'/$chainSlug/deal/$dealAddress': typeof ChainSlugLayoutDealDealAddressRoute
363+
'/$chainSlug/search/$search': typeof ChainSlugLayoutSearchSearchRoute
345364
'/$chainSlug/task/$taskAddress': typeof ChainSlugLayoutTaskTaskAddressRoute
346365
'/$chainSlug/tx/$txAddress': typeof ChainSlugLayoutTxTxAddressRoute
347366
'/$chainSlug/workerpool/$workerpoolAddress': typeof ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute
@@ -362,6 +381,7 @@ export interface FileRoutesById {
362381
'/$chainSlug/_layout/app/$appAddress': typeof ChainSlugLayoutAppAppAddressRoute
363382
'/$chainSlug/_layout/dataset/$datasetAddress': typeof ChainSlugLayoutDatasetDatasetAddressRoute
364383
'/$chainSlug/_layout/deal/$dealAddress': typeof ChainSlugLayoutDealDealAddressRoute
384+
'/$chainSlug/_layout/search/$search': typeof ChainSlugLayoutSearchSearchRoute
365385
'/$chainSlug/_layout/task/$taskAddress': typeof ChainSlugLayoutTaskTaskAddressRoute
366386
'/$chainSlug/_layout/tx/$txAddress': typeof ChainSlugLayoutTxTxAddressRoute
367387
'/$chainSlug/_layout/workerpool/$workerpoolAddress': typeof ChainSlugLayoutWorkerpoolWorkerpoolAddressRoute
@@ -382,6 +402,7 @@ export interface FileRouteTypes {
382402
| '/$chainSlug/app/$appAddress'
383403
| '/$chainSlug/dataset/$datasetAddress'
384404
| '/$chainSlug/deal/$dealAddress'
405+
| '/$chainSlug/search/$search'
385406
| '/$chainSlug/task/$taskAddress'
386407
| '/$chainSlug/tx/$txAddress'
387408
| '/$chainSlug/workerpool/$workerpoolAddress'
@@ -398,6 +419,7 @@ export interface FileRouteTypes {
398419
| '/$chainSlug/app/$appAddress'
399420
| '/$chainSlug/dataset/$datasetAddress'
400421
| '/$chainSlug/deal/$dealAddress'
422+
| '/$chainSlug/search/$search'
401423
| '/$chainSlug/task/$taskAddress'
402424
| '/$chainSlug/tx/$txAddress'
403425
| '/$chainSlug/workerpool/$workerpoolAddress'
@@ -416,6 +438,7 @@ export interface FileRouteTypes {
416438
| '/$chainSlug/_layout/app/$appAddress'
417439
| '/$chainSlug/_layout/dataset/$datasetAddress'
418440
| '/$chainSlug/_layout/deal/$dealAddress'
441+
| '/$chainSlug/_layout/search/$search'
419442
| '/$chainSlug/_layout/task/$taskAddress'
420443
| '/$chainSlug/_layout/tx/$txAddress'
421444
| '/$chainSlug/_layout/workerpool/$workerpoolAddress'
@@ -469,6 +492,7 @@ export const routeTree = rootRoute
469492
"/$chainSlug/_layout/app/$appAddress",
470493
"/$chainSlug/_layout/dataset/$datasetAddress",
471494
"/$chainSlug/_layout/deal/$dealAddress",
495+
"/$chainSlug/_layout/search/$search",
472496
"/$chainSlug/_layout/task/$taskAddress",
473497
"/$chainSlug/_layout/tx/$txAddress",
474498
"/$chainSlug/_layout/workerpool/$workerpoolAddress"
@@ -514,6 +538,10 @@ export const routeTree = rootRoute
514538
"filePath": "$chainSlug/_layout/deal/$dealAddress.tsx",
515539
"parent": "/$chainSlug/_layout"
516540
},
541+
"/$chainSlug/_layout/search/$search": {
542+
"filePath": "$chainSlug/_layout/search/$search.tsx",
543+
"parent": "/$chainSlug/_layout"
544+
},
517545
"/$chainSlug/_layout/task/$taskAddress": {
518546
"filePath": "$chainSlug/_layout/task/$taskAddress.tsx",
519547
"parent": "/$chainSlug/_layout"

src/routes/$chainSlug/_layout/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { SUPPORTED_CHAINS } from '@/config';
2-
import { createFileRoute } from '@tanstack/react-router';
2+
import { createFileRoute, useLocation } from '@tanstack/react-router';
3+
import { useEffect } from 'react';
34
import { AppsPreviewTable } from '@/modules/apps/AppsPreviewTable';
45
import { DatasetsPreviewTable } from '@/modules/datasets/DatasetsPreviewTable';
56
import { DealsPreviewTable } from '@/modules/deals/DealsPreviewTable';
@@ -14,6 +15,10 @@ export const Route = createFileRoute('/$chainSlug/_layout/')({
1415

1516
function Index() {
1617
const { chainId } = useUserStore();
18+
const location = useLocation();
19+
20+
const forwardedSearch = location.state?.forwardedSearch;
21+
1722
const currentChain = SUPPORTED_CHAINS.find((chain) => chain.id === chainId);
1823

1924
return (
@@ -22,7 +27,10 @@ function Index() {
2227
<h1 className="mb-2 text-lg font-extrabold md:text-2xl">
2328
The iExec Protocol Explorer
2429
</h1>
25-
<SearcherBar />
30+
<SearcherBar
31+
key={forwardedSearch ?? 'default'}
32+
initialSearch={forwardedSearch}
33+
/>
2634
<div className="absolute inset-0 -z-10 hidden blur-2xl sm:block sm:blur-[100px] lg:blur-[150px]">
2735
<div
2836
className="absolute top-1/2 -right-1/4 aspect-[23/30] w-1/2 rounded-full xl:-right-1/5"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createFileRoute, redirect } from '@tanstack/react-router';
2+
3+
export const Route = createFileRoute('/$chainSlug/_layout/search/$search')({
4+
loader: ({ params }) => {
5+
const { chainSlug, search } = params;
6+
console.log('$search', chainSlug, search);
7+
8+
return redirect({
9+
to: `/${chainSlug}`,
10+
replace: true,
11+
state: {
12+
forwardedSearch: search,
13+
},
14+
});
15+
},
16+
});

0 commit comments

Comments
 (0)