diff --git a/app/(builder)/builder/[categoryId]/[partId]/page.tsx b/app/(builder)/builder/[categoryId]/[partId]/page.tsx index f099f1b..7bde3b4 100644 --- a/app/(builder)/builder/[categoryId]/[partId]/page.tsx +++ b/app/(builder)/builder/[categoryId]/[partId]/page.tsx @@ -27,7 +27,10 @@ const API_BASE_URL = export default function PartDetailPage() { const params = useParams(); const categoryId = params.categoryId as CategoryId; - const partId = params.partId as string; // this is the UUID we passed in the link + + // Support URLs like /builder/lower/25969-m5-complete-lower-receiver + const rawPartParam = params.partId as string; + const partId = rawPartParam.split("-")[0]; // "25969-m5-..." -> "25969" const [product, setProduct] = useState(null); const [loading, setLoading] = useState(true); diff --git a/app/(builder)/builder/[categoryId]/page.tsx b/app/(builder)/builder/[categoryId]/page.tsx index 9ddbe7e..c0010c8 100644 --- a/app/(builder)/builder/[categoryId]/page.tsx +++ b/app/(builder)/builder/[categoryId]/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; -import { useParams, useRouter } from "next/navigation"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; import { CATEGORIES } from "@/data/gunbuilderParts"; import type { CategoryId, Part } from "@/types/gunbuilder"; @@ -26,6 +26,8 @@ type UiPart = Part & { inStock?: boolean; }; +const PLATFORMS = ["AR-15", "AR-10"]; + const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080"; @@ -37,10 +39,18 @@ type SortOption = "relevance" | "price-asc" | "price-desc" | "brand-asc"; type BuildState = Partial>; const STORAGE_KEY = "gunbuilder-build-state"; +// support for url id-slug +const slugify = (str: string) => + str + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)/g, ""); + export default function CategoryPage() { const params = useParams(); const categoryId = params.categoryId as CategoryId; const router = useRouter(); + const searchParams = useSearchParams(); const [viewMode, setViewMode] = useState("list"); const [parts, setParts] = useState([]); @@ -70,6 +80,10 @@ export default function CategoryPage() { } }); + const initialPlatform = + (searchParams.get("platform") as string) || "AR-15"; + const [platform, setPlatform] = useState(initialPlatform); + const category = useMemo( () => CATEGORIES.find((c) => c.id === categoryId), [categoryId], @@ -86,10 +100,9 @@ export default function CategoryPage() { setError(null); const search = new URLSearchParams(); - search.set("platform", "AR-15"); - search.set("categorySlug", categoryId); + search.set("platform", platform); - const url = `${API_BASE_URL}/api/gunbuilder/products/by-category?${search.toString()}`; + const url = `${API_BASE_URL}/api/products?${search.toString()}`; const res = await fetch(url, { signal: controller.signal }); @@ -123,7 +136,7 @@ export default function CategoryPage() { fetchCategoryParts(); return () => controller.abort(); - }, [categoryId]); + }, [categoryId, platform]); // persist build to localStorage whenever it changes useEffect(() => { @@ -275,10 +288,16 @@ export default function CategoryPage() { return filteredParts.slice(startIndex, startIndex + PAGE_SIZE); }, [filteredParts, currentPage, PAGE_SIZE]); - useEffect(() => { + useEffect(() => { // whenever the category or filters change, jump back to page 1 setCurrentPage(1); - }, [categoryId, brandFilter, sortBy, searchQuery, priceRange, inStockOnly]); + }, [categoryId, brandFilter, sortBy, searchQuery, priceRange, inStockOnly, platform]); + + useEffect(() => { + const nextPlatform = + (searchParams.get("platform") as string) || "AR-15"; + setPlatform(nextPlatform); + }, [searchParams]); const visibleRange = useMemo(() => { if (filteredParts.length === 0) { @@ -588,6 +607,26 @@ export default function CategoryPage() { )} +
+ + +