122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
|
import { useSearchParams } from "@remix-run/react";
|
|
import {
|
|
ApiSearchV1ListRequest,
|
|
} from "../../src/api/types";
|
|
import {
|
|
ProductSearchResult,
|
|
SearchResponse,
|
|
SellerSearchResult,
|
|
} from "../../src/api/types/models";
|
|
import { createSearchSystemApi } from "~/utils/api-client-factory";
|
|
|
|
/**
|
|
* Hook to fetch autocomplete suggestions from the API
|
|
* Cancels previous requests when a new search is initiated
|
|
*/
|
|
export const useSearchAutocomplete = (query: string) => {
|
|
return useQuery({
|
|
queryKey: ["searchAutocomplete", query],
|
|
queryFn: async ({ signal }) => {
|
|
if (!query || query.trim() === "") {
|
|
return { suggestions: [] };
|
|
}
|
|
|
|
const searchApi = createSearchSystemApi();
|
|
|
|
// Create an init override function that includes the signal
|
|
const initOverrides = { signal };
|
|
|
|
try {
|
|
return await searchApi.apiSearchV1AutocompleteList(
|
|
{ q: query },
|
|
initOverrides
|
|
);
|
|
} catch (error) {
|
|
// If the request was aborted, return empty results
|
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
return { suggestions: [] };
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
enabled: query.trim().length > 0,
|
|
staleTime: 1000 * 10, // 10 seconds
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to fetch search results with infinite scrolling
|
|
* Reads search params from URL and fetches data accordingly
|
|
*/
|
|
export const useSearchInfinite = (routeQueryParam?: string) => {
|
|
const [searchParams] = useSearchParams();
|
|
|
|
// Get search query from route param and decode it
|
|
const query = routeQueryParam ? decodeURIComponent(routeQueryParam) : "";
|
|
|
|
// Get other search parameters from URL
|
|
const orderParam = searchParams.get("order");
|
|
const priceMinParam = searchParams.get("priceMin");
|
|
const priceMaxParam = searchParams.get("priceMax");
|
|
|
|
// Build search parameters
|
|
const searchQueryParams: ApiSearchV1ListRequest = {
|
|
q: query,
|
|
};
|
|
|
|
// Add sortBy parameter based on order
|
|
if (orderParam) {
|
|
// Map our order values to what the API expects in sortBy
|
|
switch (orderParam) {
|
|
case "cheap":
|
|
searchQueryParams.sortBy = "price";
|
|
break;
|
|
case "expensive":
|
|
searchQueryParams.sortBy = "-price"; // Negative for descending
|
|
break;
|
|
case "newest":
|
|
searchQueryParams.sortBy = "newest";
|
|
break;
|
|
default:
|
|
searchQueryParams.sortBy = orderParam;
|
|
}
|
|
}
|
|
|
|
// Add price filters if they exist
|
|
if (priceMinParam) searchQueryParams.priceMin = parseInt(priceMinParam, 10);
|
|
if (priceMaxParam) searchQueryParams.priceMax = parseInt(priceMaxParam, 10);
|
|
|
|
return useInfiniteQuery({
|
|
queryKey: ["search", searchQueryParams],
|
|
queryFn: async ({ pageParam = 1 }) => {
|
|
const searchApi = createSearchSystemApi();
|
|
return await searchApi.apiSearchV1List({
|
|
...searchQueryParams,
|
|
page: pageParam as number,
|
|
});
|
|
},
|
|
getNextPageParam: (lastPage: SearchResponse, allPages) => {
|
|
if (!lastPage || !lastPage.products || !lastPage.products.length) {
|
|
return undefined;
|
|
}
|
|
|
|
// Use the length of allPages to determine the next page number
|
|
const currentPage = allPages.length;
|
|
|
|
// If there are products and there should be more pages, return next page number
|
|
if (lastPage.products.length > 0) {
|
|
return currentPage + 1;
|
|
}
|
|
|
|
return undefined;
|
|
},
|
|
initialPageParam: 1,
|
|
enabled: !!query, // Only run query if search query exists
|
|
});
|
|
};
|
|
|
|
// Re-export types from the API for convenience
|
|
export type { ProductSearchResult, SellerSearchResult, SearchResponse };
|