From 061d737c6c7bee623ae32ca4aa20d7d417e69cf4 Mon Sep 17 00:00:00 2001
From: Sean
Date: Mon, 22 Dec 2025 09:03:32 -0500
Subject: [PATCH] fixing platform switcher bugs
---
components/parts/PartsBrowseClient.tsx | 46 +++++++++----
components/parts/PlatformSwitcher.tsx | 91 ++++++++++++++++----------
2 files changed, 89 insertions(+), 48 deletions(-)
diff --git a/components/parts/PartsBrowseClient.tsx b/components/parts/PartsBrowseClient.tsx
index 2c412c6..d0a2f7e 100644
--- a/components/parts/PartsBrowseClient.tsx
+++ b/components/parts/PartsBrowseClient.tsx
@@ -21,7 +21,10 @@ import Pagination from "@/components/parts/Pagination";
import PlatformSwitcher from "@/components/parts/PlatformSwitcher";
import type { CategoryId } from "@/types/gunbuilder";
-import { PART_ROLE_TO_CATEGORY, normalizePartRole } from "@/lib/catalogMappings";
+import {
+ PART_ROLE_TO_CATEGORY,
+ normalizePartRole,
+} from "@/lib/catalogMappings";
type ViewMode = "card" | "list";
type SortOption = "relevance" | "price-asc" | "price-desc" | "brand-asc";
@@ -196,7 +199,8 @@ export default function PartsBrowseClient(props: {
);
const priceBounds = useMemo(() => {
- if (!parts.length) return { min: null as number | null, max: null as number | null };
+ if (!parts.length)
+ return { min: null as number | null, max: null as number | null };
return {
min: Math.min(...parts.map((p) => p.price)),
max: Math.max(...parts.map((p) => p.price)),
@@ -208,11 +212,13 @@ export default function PartsBrowseClient(props: {
const minBound = priceBounds.min;
const maxBound = priceBounds.max;
if (minBound == null || maxBound == null) return;
-
+
setPriceRange((prev) => {
- const nextMin = prev.min == null ? minBound : Math.max(minBound, prev.min);
- const nextMax = prev.max == null ? maxBound : Math.min(maxBound, prev.max);
-
+ const nextMin =
+ prev.min == null ? minBound : Math.max(minBound, prev.min);
+ const nextMax =
+ prev.max == null ? maxBound : Math.min(maxBound, prev.max);
+
// Ensure min never exceeds max (in case user typed weird values)
return {
min: Math.min(nextMin, nextMax),
@@ -232,15 +238,13 @@ export default function PartsBrowseClient(props: {
if (brandFilter.length)
result = result.filter((p) => brandFilter.includes(p.brand));
- if (inStockOnly)
- result = result.filter((p) => p.inStock ?? true);
+ if (inStockOnly) result = result.filter((p) => p.inStock ?? true);
const q = searchQuery.toLowerCase().trim();
if (q)
result = result.filter(
(p) =>
- p.name.toLowerCase().includes(q) ||
- p.brand.toLowerCase().includes(q)
+ p.name.toLowerCase().includes(q) || p.brand.toLowerCase().includes(q)
);
switch (sortBy) {
@@ -318,12 +322,22 @@ export default function PartsBrowseClient(props: {
{!isBuilderMode && (
-
+
+
+
+ Start New Build →
+
)}
@@ -331,7 +345,9 @@ export default function PartsBrowseClient(props: {
{isBuilderMode && (
← Back to Build
@@ -399,7 +415,9 @@ export default function PartsBrowseClient(props: {
)}
{loading ? (
-
Loading…
+
+ Loading…
+
) : error ? (
{error}
) : filteredParts.length === 0 ? (
@@ -434,4 +452,4 @@ export default function PartsBrowseClient(props: {
);
-}
\ No newline at end of file
+}
diff --git a/components/parts/PlatformSwitcher.tsx b/components/parts/PlatformSwitcher.tsx
index 3cc2f1e..f98373a 100644
--- a/components/parts/PlatformSwitcher.tsx
+++ b/components/parts/PlatformSwitcher.tsx
@@ -1,52 +1,75 @@
"use client";
-import { useMemo } from "react";
-import { usePathname, useRouter, useSearchParams } from "next/navigation";
+import Link from "next/link";
+import { usePathname, useSearchParams } from "next/navigation";
+
+const PLATFORMS = ["AR-15", "AR-10", "AR-9"] as const;
+
+type Platform = (typeof PLATFORMS)[number];
export default function PlatformSwitcher(props: {
currentPlatform: string;
partRole: string;
- preserveQuery?: boolean; // keep sort/page/etc (but NOT platform)
+ /**
+ * If true, preserve *all* existing query params (except platform will be overwritten).
+ * Useful when you want to keep things like ?from=builder or future filters.
+ */
+ preserveQuery?: boolean;
+ /**
+ * Force route behavior.
+ * - "browse": /parts/[role]?platform=...
+ * - "detail": /parts/p/[platform]/[role]
+ * Default: auto based on current pathname
+ */
+ mode?: "browse" | "detail";
}) {
- const { currentPlatform, partRole, preserveQuery = true } = props;
-
- const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
- const queryString = useMemo(() => {
- if (!preserveQuery) return "";
+ const inferredMode: "browse" | "detail" = pathname.startsWith("/parts/p/")
+ ? "detail"
+ : "browse";
- // Copy params and DROP legacy platform param so it can’t conflict with the path
- const sp = new URLSearchParams(searchParams?.toString());
- sp.delete("platform");
+ const mode = props.mode ?? inferredMode;
- const q = sp.toString();
- return q ? `?${q}` : "";
- }, [searchParams, preserveQuery]);
+ const buildHref = (nextPlatform: Platform) => {
+ if (mode === "detail") {
+ return `/parts/p/${encodeURIComponent(nextPlatform)}/${encodeURIComponent(
+ props.partRole
+ )}`;
+ }
- function go(platform: string) {
- router.replace(
- `/parts/p/${encodeURIComponent(platform)}/${encodeURIComponent(partRole)}${queryString}`
- );
- }
+ // browse mode => keep on /parts/[role] and just change the query param
+ const qp = props.preserveQuery
+ ? new URLSearchParams(searchParams.toString())
+ : new URLSearchParams();
+
+ qp.set("platform", nextPlatform);
+
+ return {
+ pathname: `/parts/${encodeURIComponent(props.partRole)}`,
+ query: Object.fromEntries(qp.entries()),
+ } as const;
+ };
return (
-
-
Platform
-
-
- {/* debug only */}
-
{pathname}
+
+ {PLATFORMS.map((p) => {
+ const active = p === props.currentPlatform;
+ return (
+
+ {p}
+
+ );
+ })}
);
}
\ No newline at end of file