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