fix: search component and breadcrumbs fix

This commit is contained in:
gosangam
2026-05-10 21:56:43 +05:30
parent befcc44980
commit 96a83fe1cb
7 changed files with 263 additions and 143 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ export function BooksGrid({ books }: { books: BookInfo[] }) {
<Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search books..."
placeholder="Search books by name..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-9"
+1 -1
View File
@@ -91,7 +91,7 @@ export function EssaysGrid({ essays }: { essays: EssayInfo[] }) {
<Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search essays..."
placeholder="Search essays by title..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-9"
+1 -1
View File
@@ -82,7 +82,7 @@ export function OtherDocumentsGrid({
<Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search documents..."
placeholder="Search documents by title..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-9"
+58 -37
View File
@@ -199,9 +199,14 @@ function SearchInner() {
selectedTypes.size === 0 ? "all" : Array.from(selectedTypes).join(",");
const [data, setData] = useState<SearchResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [hasSearched, setHasSearched] = useState(!!initialQuery);
const debouncedQuery = useDebouncedValue(inputValue, 300);
const isQueryEmpty = !debouncedQuery.trim();
// When the query is empty, derive these as "no results" during render
// instead of setting state inside an effect.
const effectiveData = isQueryEmpty ? null : data;
const hasSearched = isQueryEmpty ? false : !!debouncedQuery.trim();
// Sync URL when state changes
const pushUrl = useCallback(
@@ -216,17 +221,25 @@ function SearchInner() {
[router],
);
// Reset page to 1 when query or type changes.
// Uses the "compare previous value during render" pattern
// recommended by React instead of a setState-in-effect.
const [prevQuery, setPrevQuery] = useState(debouncedQuery);
const [prevType, setPrevType] = useState(activeTypeParam);
if (prevQuery !== debouncedQuery || prevType !== activeTypeParam) {
setPrevQuery(debouncedQuery);
setPrevType(activeTypeParam);
setCurrentPage(1);
}
// Fetch search results
useEffect(() => {
const query = debouncedQuery.trim();
if (!query) {
setData(null);
setHasSearched(false);
pushUrl("", urlTypeParam, 1);
return;
}
setHasSearched(true);
setIsLoading(true);
pushUrl(query, urlTypeParam, currentPage);
@@ -264,11 +277,6 @@ function SearchInner() {
return () => controller.abort();
}, [debouncedQuery, activeTypeParam, urlTypeParam, currentPage, pushUrl]);
// Reset page to 1 when query or type changes
useEffect(() => {
setCurrentPage(1);
}, [debouncedQuery, activeTypeParam]);
// Auto-focus the input on mount
useEffect(() => {
inputRef.current?.focus();
@@ -351,17 +359,17 @@ function SearchInner() {
)}
>
{tab.label}
{data &&
{effectiveData &&
active &&
selectedTypes.size <= 1 &&
data.total > 0 && (
effectiveData.total > 0 && (
<span
className={cn(
"ml-1 inline-flex items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-semibold leading-none",
"bg-primary-foreground/20 text-primary-foreground",
)}
>
{data.total.toLocaleString()}
{effectiveData.total.toLocaleString()}
</span>
)}
</button>
@@ -391,33 +399,36 @@ function SearchInner() {
)}
{/* No results */}
{!isLoading && hasSearched && data && data.results.length === 0 && (
<div className="flex flex-col items-center justify-center py-16 text-center">
<Search className="size-12 text-muted-foreground/40 mb-4" />
<p className="text-lg text-muted-foreground">
No results found for &ldquo;{debouncedQuery}&rdquo;.
<br />
<span className="text-sm">
Try a different search term or content type.
</span>
</p>
</div>
)}
{!isLoading &&
hasSearched &&
effectiveData &&
effectiveData.results.length === 0 && (
<div className="flex flex-col items-center justify-center py-16 text-center">
<Search className="size-12 text-muted-foreground/40 mb-4" />
<p className="text-lg text-muted-foreground">
No results found for &ldquo;{debouncedQuery}&rdquo;.
<br />
<span className="text-sm">
Try a different search term or content type.
</span>
</p>
</div>
)}
{/* Results */}
{!isLoading && data && data.results.length > 0 && (
{!isLoading && effectiveData && effectiveData.results.length > 0 && (
<>
{/* Results count + top pagination */}
<div className="mb-4 flex items-center justify-between flex-wrap gap-3">
<p className="text-sm text-muted-foreground">
{data.total.toLocaleString()}{" "}
{data.total === 1 ? "result" : "results"} for &ldquo;
{data.query}&rdquo;
{effectiveData.total.toLocaleString()}{" "}
{effectiveData.total === 1 ? "result" : "results"} for &ldquo;
{effectiveData.query}&rdquo;
</p>
{data.total_pages > 1 && (
{effectiveData.total_pages > 1 && (
<PaginationControls
page={data.page}
totalPages={data.total_pages}
page={effectiveData.page}
totalPages={effectiveData.total_pages}
onPageChange={handlePageChange}
compact
/>
@@ -426,7 +437,7 @@ function SearchInner() {
{/* Result list */}
<div className="space-y-3">
{data.results.map((result, i) => (
{effectiveData.results.map((result, i) => (
<ResultCard
key={`${result.collection}-${result.id || i}`}
result={result}
@@ -435,12 +446,12 @@ function SearchInner() {
</div>
{/* Bottom pagination */}
{data.total_pages > 1 && (
{effectiveData.total_pages > 1 && (
<div className="mt-8">
<PaginationControls
page={data.page}
totalPages={data.total_pages}
total={data.total}
page={effectiveData.page}
totalPages={effectiveData.total_pages}
total={effectiveData.total}
onPageChange={handlePageChange}
/>
</div>
@@ -541,7 +552,17 @@ function ResultCard({ result }: { result: SearchResult }) {
const Icon = config.icon;
return (
<Link href={result.url} className="block group">
<Link
href={result.url}
className="block group"
onClick={() => {
try {
sessionStorage.setItem("spsays_from_search", "1");
} catch {
/* sessionStorage unavailable */
}
}}
>
<Card className="transition-all group-hover:shadow-md group-hover:ring-foreground/20 cursor-pointer">
<CardContent className="flex flex-col gap-2">
{/* Collection badge */}