full app is using live data now,

This commit is contained in:
2025-11-30 21:08:13 -05:00
parent d53a58e994
commit c858835044
111 changed files with 1551 additions and 644 deletions
+12 -17
View File
@@ -8,11 +8,9 @@ import type { CategoryId, Part } from "@/types/gunbuilder";
import { CategoryColumn } from "@/components/CategoryColumn";
type GunbuilderProductFromApi = {
id: number;
uuid: string;
slug: string;
id: number; // backend numeric id
name: string;
brandName: string;
brand: string;
platform: string;
partRole: string;
price: number | null;
@@ -24,7 +22,6 @@ const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
// Map backend partRole to your existing CategoryId values.
// Make sure these string values match the `id` fields in CATEGORIES.
const PART_ROLE_TO_CATEGORY: Record<string, CategoryId> = {
"upper-receiver": "upper" as CategoryId,
barrel: "barrel" as CategoryId,
@@ -46,7 +43,6 @@ export default function GunbuilderPage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [build, setBuild] = useState<BuildState>(() => {
// Initialize from localStorage if available
if (typeof window !== "undefined") {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
@@ -80,7 +76,7 @@ export default function GunbuilderPage() {
const data: GunbuilderProductFromApi[] = await res.json();
const normalized: Part[] = data
.map((p) => {
.map((p): Part | null => {
const categoryId = PART_ROLE_TO_CATEGORY[p.partRole];
if (!categoryId) {
// Skip any parts we don't know how to map yet
@@ -88,17 +84,17 @@ export default function GunbuilderPage() {
}
return {
// Use the backend UUID as the stable id for the UI
id: p.uuid,
id: String(p.id), // <<< ALWAYS string
categoryId,
name: p.name,
brand: p.brandName,
brand: p.brand,
price: p.price ?? 0,
imageUrl: p.mainImageUrl ?? undefined,
url: p.buyUrl ?? undefined,
} as Part;
notes: undefined,
};
})
.filter(Boolean) as Part[];
.filter((p): p is Part => p !== null);
setParts(normalized);
} catch (err: any) {
@@ -114,7 +110,7 @@ export default function GunbuilderPage() {
return () => controller.abort();
}, []);
// Handle URL query parameter for part selection
// Handle URL query parameter for part selection (?select=upper:165)
useEffect(() => {
const selectParam = searchParams.get("select");
if (selectParam) {
@@ -123,13 +119,12 @@ export default function GunbuilderPage() {
setBuild((prev) => {
const updated = {
...prev,
[categoryId]: partId,
[categoryId as CategoryId]: partId,
};
// Persist to localStorage
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
return updated;
});
// Remove query param from URL
router.replace("/gunbuilder", { scroll: false });
}
}
@@ -319,4 +314,4 @@ export default function GunbuilderPage() {
</div>
</main>
);
}
}