Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@ import type {ShopperSearchTypes} from 'commerce-sdk-isomorphic'
import ProductCard from '@/app/components/productCard'

export default function ProductGrid({
products
products,
isLoading = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gurpreetsainisalesforce Where is the isLoading coming from? With server components we should not actually need this. Which hints at AI hallucinated code 😶.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gurpreetsainisalesforce Where is the isLoading coming from? With server components we should not actually need this. Which hints at AI hallucinated code 😶.

I'll have to create a new PR on the new repo. Lets see if that will be any better :)

}: {
products: ShopperSearchTypes.ProductSearchHit[]
isLoading?: boolean
}): ReactElement {
if (isLoading) {
return (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-x-4 gap-y-8">
{Array.from({length: 8}).map((_, i) => (
<div key={i} className="animate-pulse">
<div className="aspect-square bg-gray-200 rounded-lg mb-4"></div>
<div className="h-4 bg-gray-200 rounded mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
</div>
))}
</div>
)
}

return (
<>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-x-4 gap-y-8">
Expand All @@ -18,7 +34,13 @@ export default function ProductGrid({
{/* Show a message when no products are found */}
{products.length === 0 && (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<svg className="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
</div>
<p className="text-lg text-gray-500">No products found.</p>
<p className="text-sm text-gray-400 mt-2">Try adjusting your search criteria or browse our categories.</p>
</div>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {ReactElement} from 'react'
import type {ShopperSearchTypes} from 'commerce-sdk-isomorphic'

export default function SearchResultsSummary({
searchTerm,
result,
className = ''
}: {
searchTerm: string
result: ShopperSearchTypes.ProductSearchResult
className?: string
}): ReactElement {
const totalResults = result.total || 0
const currentPage = Math.floor((result.offset || 0) / (result.limit || 24)) + 1
const totalPages = Math.ceil(totalResults / (result.limit || 24))

return (
<div className={`bg-gray-50 rounded-lg p-4 ${className}`}>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<div className="text-sm text-gray-600">
<span className="font-medium">{totalResults.toLocaleString()}</span> results for "{searchTerm}"
{totalPages > 1 && (
<span className="ml-2">
(Page {currentPage} of {totalPages})
</span>
)}
</div>

{result.suggestedSearchTerms && result.suggestedSearchTerms.length > 0 && (
<div className="text-sm">
<span className="text-gray-500">Did you mean: </span>
{result.suggestedSearchTerms.slice(0, 3).map((term: string, index: number) => (
<a
key={term}
href={`/search?q=${encodeURIComponent(term)}`}
className="text-blue-600 hover:text-blue-800 hover:underline"
>
{term}
{index < Math.min(2, result.suggestedSearchTerms!.length - 1) && ', '}
</a>
))}
</div>
)}
</div>
</div>
)
}
108 changes: 87 additions & 21 deletions packages/template-retail-rsc-app/src/app/routes/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ProductGrid from '@/app/components/productGrid'
import CategoryRefinements from '@/app/components/categoryRefinements'
import CategorySorting from '@/app/components/categorySorting'
import CategoryPagination from '@/app/components/categoryPagination'
import SearchResultsSummary from '@/app/components/searchResultsSummary'

const limit = 24

Expand All @@ -31,37 +32,102 @@ export default function Search({
searchResult: ShopperSearchTypes.ProductSearchResult
}
}) {
const hasResults = searchResult.hits && searchResult.hits.length > 0
const totalResults = searchResult.total || 0

return (
<div className="pb-16">
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<p>Search Results for</p>
<h1 className="text-3xl font-bold text-gray-900">
{searchTerm} ({searchResult.total})
</h1>
</div>
{/* Search Header */}
<div className="mb-8">
{searchTerm && (
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<p className="text-gray-600">Search Results for</p>
<h1 className="text-3xl font-bold text-gray-900">
"{searchTerm}" ({totalResults.toLocaleString()} {totalResults === 1 ? 'result' : 'results'})
</h1>
</div>

<div className="flex-shrink-0">
<CategorySorting result={searchResult} />
</div>
{hasResults && (
<div className="flex-shrink-0">
<CategorySorting result={searchResult} />
</div>
)}
</div>
)}
</div>

<div className="flex flex-col lg:flex-row gap-8">
<div className="hidden lg:block w-64 flex-shrink-0">
<CategoryRefinements result={searchResult} />
</div>
{/* Search Results */}
{searchTerm ? (
hasResults ? (
<div className="flex flex-col lg:flex-row gap-8">
{/* Filters Sidebar */}
<div className="hidden lg:block w-64 flex-shrink-0">
<CategoryRefinements result={searchResult} />
</div>

<div className="flex-grow">
<ProductGrid products={searchResult.hits ?? []} />
{/* Results Grid */}
<div className="flex-grow">
<SearchResultsSummary
searchTerm={searchTerm}
result={searchResult}
className="mb-6"
/>
<ProductGrid products={searchResult.hits ?? []} />

{searchResult.total > 1 && (
<div className="mt-10">
<CategoryPagination limit={limit} result={searchResult} />
{/* Pagination */}
{totalResults > limit && (
<div className="mt-10">
<CategoryPagination limit={limit} result={searchResult} />
</div>
)}
</div>
</div>
) : (
<div className="text-center py-16">
<div className="max-w-md mx-auto">
<div className="text-gray-400 mb-4">
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<h2 className="text-2xl font-semibold text-gray-900 mb-2">No results found</h2>
<p className="text-gray-600 mb-6">
We couldn't find any products matching "{searchTerm}". Try adjusting your search terms or browse our categories.
</p>
<div className="space-y-2">
<p className="text-sm text-gray-500">Try searching for:</p>
<div className="flex flex-wrap gap-2 justify-center">
{['shoes', 'electronics', 'clothing', 'accessories'].map((term) => (
<a
key={term}
href={`/search?q=${encodeURIComponent(term)}`}
className="inline-block px-3 py-1 text-sm bg-gray-100 text-gray-700 rounded-full hover:bg-gray-200 transition-colors"
>
{term}
</a>
))}
</div>
</div>
</div>
)}
</div>
)
) : (
<div className="text-center py-16">
<div className="max-w-md mx-auto">
<div className="text-gray-400 mb-4">
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<h2 className="text-2xl font-semibold text-gray-900 mb-2">Search for products</h2>
<p className="text-gray-600 mb-6">
Use the search bar in the header to find products, brands, or categories.
</p>
</div>
</div>
</div>
)}
</div>
</div>
)
Expand Down
Loading