fix: search component and breadcrumbs fix
This commit is contained in:
@@ -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 “{debouncedQuery}”.
|
||||
<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 “{debouncedQuery}”.
|
||||
<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 “
|
||||
{data.query}”
|
||||
{effectiveData.total.toLocaleString()}{" "}
|
||||
{effectiveData.total === 1 ? "result" : "results"} for “
|
||||
{effectiveData.query}”
|
||||
</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 */}
|
||||
|
||||
Reference in New Issue
Block a user