subnav and other stuff

This commit is contained in:
2025-12-03 20:56:05 -05:00
parent 76ebb30d96
commit 7cee90381d
163 changed files with 1216 additions and 3515 deletions
+23 -34
View File
@@ -119,25 +119,29 @@ export default function GunbuilderPage() {
const data: GunbuilderProductFromApi[] = await res.json();
const normalized: Part[] = data
.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
return null;
}
.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
return null;
}
const buyUrl = p.buyUrl ?? undefined;
return {
id: String(p.id), // ALWAYS string
categoryId,
name: p.name,
brand: p.brand,
price: p.price ?? 0,
imageUrl: p.mainImageUrl ?? undefined,
affiliateUrl: buyUrl, // main link
url: buyUrl, // 👈optional alias if you still reference .url. maybe pretty url?
notes: undefined,
};
})
.filter((p): p is Part => p !== null);
return {
id: String(p.id), // ALWAYS string
categoryId,
name: p.name,
brand: p.brand,
price: p.price ?? 0,
imageUrl: p.mainImageUrl ?? undefined,
url: p.buyUrl ?? undefined,
notes: undefined,
};
})
.filter((p): p is Part => p !== null);
setParts(normalized);
} catch (err: any) {
@@ -327,22 +331,7 @@ export default function GunbuilderPage() {
return (
<main className="min-h-screen bg-black text-zinc-50">
<div className="border-b border-amber-500/20 bg-amber-500/5">
<div className="mx-auto flex max-w-6xl flex-col gap-1 px-4 py-2 text-[0.75rem] text-amber-100 md:flex-row md:items-center md:justify-between">
<div className="flex items-center gap-2">
<span className="rounded-sm bg-amber-500/20 px-2 py-0.5 text-[0.65rem] font-semibold uppercase tracking-[0.18em] text-amber-300">
Early Access Beta
</span>
<span className="hidden text-amber-100/90 md:inline">
You&apos;re using an early-access prototype of The Armory. Data,
pricing, and available parts are still evolving.
</span>
</div>
<span className="text-amber-100/90 md:hidden">
Early-access prototype. Data and pricing may change.
</span>
</div>
</div>
<div className="mx-auto max-w-6xl px-4 py-6 lg:py-10">
{/* Header */}
<header className="mb-6">
+9 -14
View File
@@ -1,25 +1,20 @@
"use client";
import "./globals.css";
import { Inter } from "next/font/google";
import type { ReactNode } from "react";
import { AuthProvider } from "@/context/AuthContext";
import { TopNav } from "@/components/TopNav";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Shadow Standard Co.",
description: "The Armory — Early Access",
};
import { TopNav } from "@/components/TopNav"; // or default import if that's how you exported it
import { BuilderNav } from "@/components/BuilderNav";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<body className="bg-black text-zinc-100">
{/* AuthProvider wraps everything that uses useAuth */}
<AuthProvider>
<div className="min-h-screen flex flex-col bg-black text-zinc-50">
<TopNav />
<main className="flex-1">{children}</main>
</div>
<TopNav />
<BuilderNav />
<main className="min-h-screen">{children}</main>
</AuthProvider>
</body>
</html>
+73
View File
@@ -0,0 +1,73 @@
import { apiFetch } from "@/lib/useApi";
export type AdminCategory = {
id: number;
slug: string;
name: string;
description?: string | null;
groupName?: string | null;
sortOrder?: number | null;
};
export type AdminPartRoleMapping = {
id: number;
platform: string;
partRole: string;
categorySlug: string;
categoryName: string;
groupName?: string | null;
notes?: string | null;
};
export async function fetchAdminCategories(token: string) {
return apiFetch<AdminCategory[]>("/api/admin/categories", {
token,
});
}
export async function fetchPartRoleMappings(token: string, platform = "AR-15") {
const params = new URLSearchParams({ platform });
return apiFetch<AdminPartRoleMapping[]>(`/api/admin/category-mappings?${params.toString()}`, {
token,
});
}
export async function createPartRoleMapping(
token: string,
payload: {
platform: string;
partRole: string;
categorySlug: string;
notes?: string;
},
) {
return apiFetch<AdminPartRoleMapping>("/api/admin/category-mappings", {
method: "POST",
token,
body: payload,
});
}
export async function updatePartRoleMapping(
token: string,
id: number,
payload: {
platform: string;
partRole: string;
categorySlug: string;
notes?: string;
},
) {
return apiFetch<AdminPartRoleMapping>(`/api/admin/category-mappings/${id}`, {
method: "PUT",
token,
body: payload,
});
}
export async function deletePartRoleMapping(token: string, id: number) {
await apiFetch<void>(`/api/admin/category-mappings/${id}`, {
method: "DELETE",
token,
});
}