feat(catalog): implement materials catalog, faceted filters, and dedicated product details view - #4
Conversation
…cated product details view
There was a problem hiding this comment.
🟡 Changes recommended
There are build-blocking issues (missing @/lib/api/catalog module references and non-standard params: Promise<...> typing), plus a manufacturer facet logic issue that makes filters incomplete/stale.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new materials catalog experience to the Next.js frontend, introducing catalog types and a set of UI components/pages for browsing products with filters and viewing product details.
Changes:
- Introduces catalog domain types (
Category,Product, query/response shapes) for the frontend. - Adds
/cataloglisting page with filter sidebar, debounced search, sorting, skeleton loading, and pagination. - Adds
/catalog/[slug]product details page plus reusable catalog UI components (cards, header, pagination, skeleton).
File summaries
| File | Description |
|---|---|
| frontend/src/types/catalog.ts | Adds shared TypeScript types for categories/products and API responses. |
| frontend/src/components/layout/Navbar.tsx | Updates navigation links (removes “Solution Architect/Start Build” entry points) and standardizes formatting. |
| frontend/src/components/catalog/ProductGridSkeleton.tsx | Adds a loading skeleton grid for catalog results. |
| frontend/src/components/catalog/ProductDetailSheet.tsx | Adds a slide-over product detail sheet component (currently standalone). |
| frontend/src/components/catalog/ProductCard.tsx | Adds catalog product card UI with spec highlight badges. |
| frontend/src/components/catalog/Pagination.tsx | Adds pagination controls for navigating result pages. |
| frontend/src/components/catalog/FilterSidebar.tsx | Adds faceted filter sidebar (categories, manufacturers, price range) with reset behavior. |
| frontend/src/components/catalog/CatalogHeader.tsx | Adds search, sort, and active filter chip UI for the catalog page. |
| frontend/src/app/catalog/page.tsx | Implements the catalog page state management, URL sync, data loading, and responsive layout. |
| frontend/src/app/catalog/[slug]/page.tsx | Implements the dedicated product details view and related-materials section. |
| frontend/next.config.ts | Adjusts formatting while retaining env loading and Next config values. |
Review details
Suppressed comments (3)
frontend/src/app/catalog/page.tsx:155
availableManufacturersis derived only once (when the list is empty) and only from the current page of results (response.data), so the Manufacturers facet will be incomplete for catalogs with >1 page and can become stale when filters change. Consider returning manufacturer facets from the API (or a dedicated endpoint) and settingavailableManufacturersfrom that, or recomputing from an unpaginated source of truth.
if (availableManufacturers.length === 0 && response.data.length > 0) {
const mfgs = Array.from(
new Set(
response.data
.map((p) => p.manufacturer)
.filter(Boolean) as string[]
)
).sort();
setAvailableManufacturers(mfgs);
}
frontend/src/app/catalog/[slug]/page.tsx:20
- In Next.js App Router,
paramsis provided as a plain object, not aPromise. Typing it asPromise<{ slug: string }>and awaiting it is non-standard and will likely cause type incompatibilities with Next's generated types.
interface ProductPageProps {
params: Promise<{
slug: string;
}>;
}
frontend/src/components/catalog/ProductDetailSheet.tsx:62
- Icon-only buttons should have an accessible name. Add an
aria-label(andtype="button") to this close button so screen readers can announce its purpose.
<button
onClick={onClose}
className="h-8 w-8 rounded-lg flex items-center justify-center text-zinc-400 hover:text-zinc-900 hover:bg-zinc-100 transition-colors cursor-pointer"
>
<X className="h-5 w-5" />
</button>
- Files reviewed: 11/11 changed files
- Comments generated: 7
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import { fetchCategories, fetchProducts } from '@/lib/api/catalog'; | ||
| import { Category, Product, ProductQueryParams } from '@/types/catalog'; |
| import { fetchProductByIdentifier, fetchProducts } from '@/lib/api/catalog'; | ||
| import { ProductCard } from '@/components/catalog/ProductCard'; |
| ); | ||
| } | ||
|
|
||
| function extractSpecHighlights(data: Record<string, any> = []) { |
| import { | ||
| X, | ||
| ArrowRight, | ||
| ShieldCheck, | ||
| FileText, | ||
| Sliders, | ||
| Check, | ||
| Building2, | ||
| Box, | ||
| } from 'lucide-react'; |
| <button | ||
| type="button" | ||
| onClick={() => onSearchChange('')} | ||
| className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-600 cursor-pointer" | ||
| > |
| <div className="flex items-center gap-1"> | ||
| {Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNum) => ( | ||
| <button | ||
| key={pageNum} | ||
| onClick={() => onPageChange(pageNum)} | ||
| className={`h-9 w-9 rounded-lg text-xs font-mono font-medium transition-colors cursor-pointer ${ | ||
| pageNum === currentPage | ||
| ? 'bg-zinc-900 text-white font-semibold shadow-2xs' | ||
| : 'text-zinc-700 hover:bg-zinc-100 hover:text-zinc-950' | ||
| }`} | ||
| > | ||
| {pageNum} | ||
| </button> | ||
| ))} | ||
| </div> |
| <Link | ||
| href={`/solutions?query=${encodeURIComponent(`Tell me all technical details and installation constraints for ${product.name}`)}`} | ||
| > | ||
| <Button | ||
| variant="outline" |
Summary of Changes
/catalog):/api/v1/core/productsand/categories.?category=...&search=...&page=...) for bookmarkable and shareable filter states./catalog/[slug]):Navbar.tsx):CatalogandSaved Projects, routing solution builds exclusively from the central home page hero prompt.