1 |
import { CircularProgress } from "@nextui-org/react"; |
2 |
import { useEffect, useState, type FC } from "react"; |
3 |
import Link from "../Navigation/Link"; |
4 |
|
5 |
type SearchResultsProps = { |
6 |
query: string; |
7 |
onClose: () => void; |
8 |
}; |
9 |
|
10 |
type SearchResultItem = { |
11 |
title?: string; |
12 |
description?: string; |
13 |
data: string; |
14 |
match: "title" | "description" | "data"; |
15 |
url: string; |
16 |
}; |
17 |
|
18 |
const SearchResults: FC<SearchResultsProps> = ({ query, onClose }) => { |
19 |
const [results, setResults] = useState<SearchResultItem[] | null>(null); |
20 |
const [isLoading, setIsLoading] = useState(false); |
21 |
const [isNotFound, setIsNotFound] = useState(false); |
22 |
|
23 |
useEffect(() => { |
24 |
if (!query?.trim()) { |
25 |
return; |
26 |
} |
27 |
|
28 |
const controller = new AbortController(); |
29 |
|
30 |
setIsLoading(true); |
31 |
|
32 |
fetch(`/search?q=${encodeURIComponent(query)}`, { |
33 |
signal: controller.signal, |
34 |
}) |
35 |
.then(response => response.json()) |
36 |
.then(data => { |
37 |
setIsNotFound(false); |
38 |
setIsLoading(false); |
39 |
setResults(data.results); |
40 |
setIsNotFound(data.results.length === 0); |
41 |
}) |
42 |
.catch(console.error); |
43 |
|
44 |
return () => controller.abort(); |
45 |
}, [query]); |
46 |
|
47 |
return ( |
48 |
<> |
49 |
{" "} |
50 |
{isLoading || |
51 |
isNotFound || |
52 |
(results?.length && ( |
53 |
<div |
54 |
className="max-lg:hidden fixed top-0 left-0 z-[1000000001] bg-transparent h-screen w-screen" |
55 |
onClick={onClose} |
56 |
/> |
57 |
))} |
58 |
<div className="lg:absolute w-full top-12 lg:z-[1000000002] lg:bg-neutral-900 lg:[box-shadow:0_0_3px_0_rgba(255,255,255,0.4)] rounded-lg p-2 lg:max-h-[60vh] lg:overflow-y-scroll"> |
59 |
{isLoading && ( |
60 |
<div className="flex items-center gap-2 justify-center"> |
61 |
<CircularProgress size={"sm"} /> |
62 |
<p>Loading...</p> |
63 |
</div> |
64 |
)} |
65 |
{isNotFound && !isLoading && ( |
66 |
<div className="text-center">No result found</div> |
67 |
)} |
68 |
{!!results?.length && |
69 |
!isLoading && |
70 |
!isNotFound && |
71 |
results.map(result => ( |
72 |
<Link |
73 |
key={result.url} |
74 |
href={result.url} |
75 |
onClick={onClose} |
76 |
className="p-2 hover:bg-neutral-800 rounded-lg cursor-pointer block" |
77 |
> |
78 |
<div>{result.title || "Home"}</div> |
79 |
|
80 |
<p className="text-[#999] text-sm"> |
81 |
May include information related to{" "} |
82 |
<strong className="text-white">{query}</strong> |
83 |
</p> |
84 |
</Link> |
85 |
))} |
86 |
</div> |
87 |
</> |
88 |
); |
89 |
}; |
90 |
|
91 |
export default SearchResults; |