mad cleanup.
This commit is contained in:
@@ -29,7 +29,7 @@ import { useAuth } from "@/context/AuthContext";
|
||||
|
||||
import { CATEGORIES } from "@/data/gunbuilderParts";
|
||||
import { BUILDER_SLOTS, isSlotSatisfied } from "@/data/builderSlots";
|
||||
import type { CategoryId, Part } from "@/types/gunbuilder";
|
||||
import type { BuilderSlotKey, Part } from "@/types/builderSlots";
|
||||
import { PART_ROLE_TO_CATEGORY } from "@/lib/catalogMappings";
|
||||
|
||||
// ✅ Centralized overlap rules + helpers
|
||||
@@ -64,7 +64,7 @@ const API_BASE_URL =
|
||||
const STORAGE_KEY = "gunbuilder-build-state";
|
||||
|
||||
// Categories that should NOT be filtered by platform (universal accessories / gear)
|
||||
const UNIVERSAL_CATEGORIES = new Set<CategoryId>([
|
||||
const UNIVERSAL_CATEGORIES = new Set<BuilderSlotKey>([
|
||||
"magazine",
|
||||
"weapon-light",
|
||||
"foregrip",
|
||||
@@ -82,7 +82,7 @@ const CATEGORY_GROUPS: {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
categoryIds: CategoryId[];
|
||||
categoryIds: BuilderSlotKey[];
|
||||
}[] = [
|
||||
{
|
||||
id: "lower-group",
|
||||
@@ -92,10 +92,10 @@ const CATEGORY_GROUPS: {
|
||||
categoryIds: [
|
||||
"lower-receiver",
|
||||
"complete-lower",
|
||||
"lower-parts",
|
||||
"lower-parts-kit", // ✅ was lower-parts
|
||||
"trigger",
|
||||
"grip",
|
||||
"safety",
|
||||
"safety-selector", // ✅ was safety
|
||||
"buffer",
|
||||
"stock",
|
||||
],
|
||||
@@ -219,8 +219,8 @@ export default function GunbuilderPage() {
|
||||
// -----------------------------
|
||||
// Derived collections
|
||||
// -----------------------------
|
||||
const partsByCategory: Record<CategoryId, Part[]> = useMemo(() => {
|
||||
const grouped = {} as Record<CategoryId, Part[]>;
|
||||
const partsByCategory: Record<BuilderSlotKey, Part[]> = useMemo(() => {
|
||||
const grouped = {} as Record<BuilderSlotKey, Part[]>;
|
||||
for (const category of CATEGORIES) {
|
||||
grouped[category.id] = parts.filter((p) => p.categoryId === category.id);
|
||||
}
|
||||
@@ -242,14 +242,14 @@ export default function GunbuilderPage() {
|
||||
});
|
||||
}, [build, parts]);
|
||||
|
||||
const selectedByCategory: Record<CategoryId, unknown> = useMemo(
|
||||
const selectedByCategory: Record<BuilderSlotKey, unknown> = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
Object.entries(build).map(([categoryId, partId]) => [
|
||||
categoryId as CategoryId,
|
||||
categoryId as BuilderSlotKey,
|
||||
partId ? true : false,
|
||||
])
|
||||
) as Record<CategoryId, unknown>,
|
||||
) as Record<BuilderSlotKey, unknown>,
|
||||
[build]
|
||||
);
|
||||
|
||||
@@ -262,7 +262,7 @@ export default function GunbuilderPage() {
|
||||
// Role -> Category resolver
|
||||
// NOTE: defensive while backend roles/mappings evolve
|
||||
// -----------------------------
|
||||
const resolveCategoryId = (normalizedRole: string): CategoryId | null => {
|
||||
const resolveCategoryId = (normalizedRole: string): BuilderSlotKey | null => {
|
||||
if (normalizedRole === "upper-receiver") return "upper-receiver";
|
||||
if (normalizedRole.includes("complete-upper")) return "complete-upper";
|
||||
|
||||
@@ -311,15 +311,23 @@ export default function GunbuilderPage() {
|
||||
|
||||
if (normalizedRole.includes("suppress")) return "suppressor";
|
||||
|
||||
// ✅ FIX: Lower Parts Kit canonical key
|
||||
if (
|
||||
normalizedRole.includes("lower-parts") ||
|
||||
normalizedRole.includes("lpk")
|
||||
)
|
||||
return "lower-parts";
|
||||
return "lower-parts-kit";
|
||||
|
||||
if (normalizedRole.includes("trigger")) return "trigger";
|
||||
if (normalizedRole.includes("grip")) return "grip";
|
||||
if (normalizedRole.includes("safety")) return "safety";
|
||||
|
||||
// ✅ FIX: Safety canonical key
|
||||
if (
|
||||
normalizedRole.includes("safety") ||
|
||||
normalizedRole.includes("selector")
|
||||
)
|
||||
return "safety-selector";
|
||||
|
||||
if (normalizedRole.includes("buffer")) return "buffer";
|
||||
if (normalizedRole.includes("stock")) return "stock";
|
||||
|
||||
@@ -328,6 +336,7 @@ export default function GunbuilderPage() {
|
||||
if (normalizedRole.includes("sight")) return "sights";
|
||||
|
||||
if (normalizedRole.includes("mag")) return "magazine";
|
||||
|
||||
if (
|
||||
normalizedRole.includes("weapon-light") ||
|
||||
(normalizedRole.includes("light") && !normalizedRole.includes("flight"))
|
||||
@@ -354,27 +363,6 @@ export default function GunbuilderPage() {
|
||||
return (PART_ROLE_TO_CATEGORY as any)[normalizedRole] ?? null;
|
||||
};
|
||||
|
||||
// -----------------------------
|
||||
// Selection handler (with hint warnings)
|
||||
// -----------------------------
|
||||
const handleSelectPart = useCallback(
|
||||
(categoryId: CategoryId, partId: string, _opts?: { confirm?: boolean }) => {
|
||||
const warn = (msg: string) => {
|
||||
setShareStatus(msg);
|
||||
window.setTimeout(() => setShareStatus(null), 4000);
|
||||
};
|
||||
|
||||
const hints = getSelectionHints(categoryId, build);
|
||||
if (hints.length > 0) warn(hints[0]);
|
||||
|
||||
setBuild((prev) => ({
|
||||
...prev,
|
||||
[categoryId]: partId,
|
||||
}));
|
||||
},
|
||||
[build]
|
||||
);
|
||||
|
||||
// -----------------------------
|
||||
// Fetch products (scoped + universal merge)
|
||||
// -----------------------------
|
||||
@@ -501,7 +489,7 @@ export default function GunbuilderPage() {
|
||||
setBuild((prevBuild) => {
|
||||
const next: BuildState = {};
|
||||
for (const [categoryId, partId] of Object.entries(prevBuild)) {
|
||||
const cid = categoryId as CategoryId;
|
||||
const cid = categoryId as BuilderSlotKey;
|
||||
if (UNIVERSAL_CATEGORIES.has(cid)) {
|
||||
next[cid] = partId;
|
||||
}
|
||||
@@ -661,6 +649,31 @@ export default function GunbuilderPage() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// -----------------------------
|
||||
// Selection handler (with hint warnings)
|
||||
// -----------------------------
|
||||
const handleSelectPart = useCallback(
|
||||
(
|
||||
categoryId: BuilderSlotKey,
|
||||
partId: string,
|
||||
_opts?: { confirm?: boolean }
|
||||
) => {
|
||||
const warn = (msg: string) => {
|
||||
setShareStatus(msg);
|
||||
window.setTimeout(() => setShareStatus(null), 4000);
|
||||
};
|
||||
|
||||
const hints = getSelectionHints(categoryId, build);
|
||||
if (hints.length > 0) warn(hints[0]);
|
||||
|
||||
setBuild((prev) => ({
|
||||
...prev,
|
||||
[categoryId]: partId,
|
||||
}));
|
||||
},
|
||||
[build]
|
||||
);
|
||||
// -----------------------------
|
||||
// Handle URL query parameters:
|
||||
// - ?select=categoryId:partId
|
||||
@@ -685,7 +698,7 @@ export default function GunbuilderPage() {
|
||||
|
||||
if (selectParam) {
|
||||
const [categoryIdRaw, partId] = selectParam.split(":");
|
||||
const requestedCategoryId = categoryIdRaw as CategoryId;
|
||||
const requestedCategoryId = categoryIdRaw as BuilderSlotKey;
|
||||
|
||||
if (requestedCategoryId && partId) {
|
||||
handleSelectPart(requestedCategoryId, partId, { confirm: false });
|
||||
@@ -696,7 +709,7 @@ export default function GunbuilderPage() {
|
||||
if (CATEGORIES.some((c) => c.id === removeParam)) {
|
||||
setBuild((prev) => {
|
||||
const next: BuildState = { ...prev };
|
||||
delete next[removeParam as CategoryId];
|
||||
delete next[removeParam as BuilderSlotKey];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
@@ -744,7 +757,7 @@ export default function GunbuilderPage() {
|
||||
}
|
||||
}, [build]);
|
||||
|
||||
const summaryCategoryOrder: CategoryId[] = useMemo(
|
||||
const summaryCategoryOrder: BuilderSlotKey[] = useMemo(
|
||||
() =>
|
||||
CATEGORY_GROUPS.flatMap((group) => group.categoryIds).filter((id) =>
|
||||
CATEGORIES.some((c) => c.id === id)
|
||||
@@ -1164,7 +1177,7 @@ export default function GunbuilderPage() {
|
||||
<div className="space-y-6">
|
||||
{CATEGORY_GROUPS.map((group) => {
|
||||
const groupCategories = CATEGORIES.filter((c) =>
|
||||
group.categoryIds.includes(c.id as CategoryId)
|
||||
group.categoryIds.includes(c.id as BuilderSlotKey)
|
||||
);
|
||||
|
||||
if (groupCategories.length === 0) return null;
|
||||
@@ -1260,7 +1273,7 @@ export default function GunbuilderPage() {
|
||||
{/* Overlap chips help explain conflicts / dependencies */}
|
||||
<div className="mt-1 flex flex-wrap gap-1.5">
|
||||
{getOverlapChipsForCategory(
|
||||
category.id as CategoryId,
|
||||
category.id as BuilderSlotKey,
|
||||
build
|
||||
).map((c) => (
|
||||
<OverlapChip
|
||||
|
||||
Reference in New Issue
Block a user