fixing platform routing. not done. updated logo on builder
This commit is contained in:
@@ -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<GunbuilderProductFromApi | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -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<Record<CategoryId, string>>;
|
||||
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<ViewMode>("list");
|
||||
const [parts, setParts] = useState<UiPart[]>([]);
|
||||
@@ -70,6 +80,10 @@ export default function CategoryPage() {
|
||||
}
|
||||
});
|
||||
|
||||
const initialPlatform =
|
||||
(searchParams.get("platform") as string) || "AR-15";
|
||||
const [platform, setPlatform] = useState<string>(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() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="platform-select"
|
||||
className="text-xs text-zinc-500"
|
||||
>
|
||||
Platform
|
||||
</label>
|
||||
<select
|
||||
id="platform-select"
|
||||
value={platform}
|
||||
onChange={(e) => setPlatform(e.target.value)}
|
||||
className="rounded-md border border-zinc-700 bg-zinc-900/70 px-2 py-1 text-xs text-zinc-200 focus:outline-none focus:ring-1 focus:ring-amber-400"
|
||||
>
|
||||
{PLATFORMS.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{p}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="sort-by"
|
||||
@@ -660,7 +699,10 @@ export default function CategoryPage() {
|
||||
</div>
|
||||
<div className="mt-2 flex gap-2">
|
||||
<Link
|
||||
href={`/builder/${categoryId}/${part.id}`}
|
||||
href={{
|
||||
pathname: `/builder/${categoryId}/${part.id}-${slugify(part.name)}`,
|
||||
query: { platform },
|
||||
}}
|
||||
className="flex-1 rounded-md border border-zinc-700 bg-zinc-800/50 px-3 py-2 text-center text-xs font-medium text-zinc-300 transition-colors hover:border-zinc-600 hover:bg-zinc-700"
|
||||
>
|
||||
View Details
|
||||
@@ -723,7 +765,10 @@ export default function CategoryPage() {
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Link
|
||||
href={`/builder/${categoryId}/${part.id}`}
|
||||
href={{
|
||||
pathname: `/builder/${categoryId}/${part.id}-${slugify(part.name)}`,
|
||||
query: { platform },
|
||||
}}
|
||||
className="whitespace-nowrap rounded-md border border-zinc-700 bg-zinc-800/50 px-3 py-1.5 text-xs font-medium text-zinc-300 transition-colors hover:border-zinc-600 hover:bg-zinc-700"
|
||||
>
|
||||
View Details
|
||||
|
||||
Reference in New Issue
Block a user