feat(search): show sellers and collections in search results
Render a فروشگاهها row (store cards with logo via SellerLogo, linking to the store) and a کالکشنها row (reusing HorizontalCollectionsList) above the product grid on the search page. Sellers/collections read off the first page (they don't paginate); products paginate as before. Types extended (SellerSearchResult.logo, SearchResponse.collections). Degrades gracefully on older backends. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fea80647fa
commit
8b51c3252a
@ -6,6 +6,8 @@ import { PackageSearch } from "lucide-react";
|
|||||||
import { useSearchInfinite } from "../requestHandler/use-search-hooks";
|
import { useSearchInfinite } from "../requestHandler/use-search-hooks";
|
||||||
import MondrianProductList from "~/components/MondrianProductList";
|
import MondrianProductList from "~/components/MondrianProductList";
|
||||||
import SearchTopBar from "~/components/SearchTopBar";
|
import SearchTopBar from "~/components/SearchTopBar";
|
||||||
|
import SellerLogo from "~/components/SellerLogo";
|
||||||
|
import HorizontalCollectionsList from "~/components/HorizontalCollectionsList";
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
const { query } = useParams();
|
const { query } = useParams();
|
||||||
@ -30,6 +32,13 @@ export default function Search() {
|
|||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
} = useSearchInfinite(query);
|
} = useSearchInfinite(query);
|
||||||
|
|
||||||
|
// Sellers & collections are the same for every page of a query, so read them
|
||||||
|
// off the first page. Products paginate as usual.
|
||||||
|
const firstPage = data?.pages?.[0];
|
||||||
|
const sellers = firstPage?.sellers ?? [];
|
||||||
|
const collections = firstPage?.collections ?? [];
|
||||||
|
const hasNonProduct = sellers.length > 0 || collections.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 bg-WHITE lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-6">
|
<div className="flex flex-col gap-6 bg-WHITE lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-6">
|
||||||
<div className="lg:hidden">
|
<div className="lg:hidden">
|
||||||
@ -57,6 +66,49 @@ export default function Search() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sellers */}
|
||||||
|
{sellers.length > 0 && (
|
||||||
|
<section className="flex flex-col gap-3">
|
||||||
|
<p className="text-B16 lg:text-B24 font-bold px-4 lg:px-0">فروشگاهها</p>
|
||||||
|
<div className="flex gap-4 overflow-x-auto px-4 lg:px-0 pb-1">
|
||||||
|
{sellers.map((seller) => (
|
||||||
|
<Link
|
||||||
|
key={seller.id}
|
||||||
|
to={`/seller/${seller.id}`}
|
||||||
|
className="flex flex-col items-center gap-2 shrink-0 w-[76px]"
|
||||||
|
>
|
||||||
|
<SellerLogo src={seller.logo} alt={seller.name} size="xl" />
|
||||||
|
<p className="text-R12 font-bold text-center line-clamp-1 w-full">
|
||||||
|
{seller.name}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Collections */}
|
||||||
|
{collections.length > 0 && (
|
||||||
|
<section className="flex flex-col gap-3">
|
||||||
|
<p className="text-B16 lg:text-B24 font-bold px-4 lg:px-0">کالکشنها</p>
|
||||||
|
<HorizontalCollectionsList
|
||||||
|
collections={collections.map((c) => ({
|
||||||
|
id: c.id,
|
||||||
|
title: c.title,
|
||||||
|
coverImageUrl: c.coverImageUrl,
|
||||||
|
productCount: c.productCount,
|
||||||
|
}))}
|
||||||
|
isLoading={false}
|
||||||
|
isError={false}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasNonProduct && (
|
||||||
|
<p className="text-B16 lg:text-B24 font-bold px-4 lg:px-0">محصولات</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<MondrianProductList
|
<MondrianProductList
|
||||||
products={
|
products={
|
||||||
data?.pages
|
data?.pages
|
||||||
|
|||||||
@ -4329,6 +4329,24 @@ export interface SearchResponse {
|
|||||||
* @memberof SearchResponse
|
* @memberof SearchResponse
|
||||||
*/
|
*/
|
||||||
sellers: Array<SellerSearchResult>;
|
sellers: Array<SellerSearchResult>;
|
||||||
|
/**
|
||||||
|
* Public collections matching the query.
|
||||||
|
* @type {Array<CollectionSearchResult>}
|
||||||
|
* @memberof SearchResponse
|
||||||
|
*/
|
||||||
|
collections?: Array<CollectionSearchResult>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A collection result in search.
|
||||||
|
* @export
|
||||||
|
* @interface CollectionSearchResult
|
||||||
|
*/
|
||||||
|
export interface CollectionSearchResult {
|
||||||
|
type?: string;
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
coverImageUrl?: string | null;
|
||||||
|
productCount?: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -5546,6 +5564,12 @@ export interface SellerSearchResult {
|
|||||||
* @memberof SellerSearchResult
|
* @memberof SellerSearchResult
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* Store logo URL (may be null).
|
||||||
|
* @type {string}
|
||||||
|
* @memberof SellerSearchResult
|
||||||
|
*/
|
||||||
|
logo?: string | null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user