fixed layouts, routing and the platform switcher.

This commit is contained in:
2025-12-22 08:36:19 -05:00
parent b1a8dae8ed
commit 34e915f904
21 changed files with 254 additions and 268 deletions
@@ -936,7 +936,7 @@ export default function GunbuilderPage() {
BATTL BUILDERS
</p>
<h1 className="mt-1 text-2xl md:text-3xl font-semibold tracking-tight">
BattlBuilder <span className="text-amber-300">Early Access</span>
Builder <span className="text-amber-300">Early Access</span>
</h1>
<p className="mt-2 text-sm text-zinc-400 max-w-xl">
Explore components from trusted brands, choose one part per
@@ -1317,10 +1317,7 @@ export default function GunbuilderPage() {
</>
) : hasParts ? (
<Link
href={{
pathname: `/parts/p/${platform}/${category.id}`,
query: platform ? { platform } : {},
}}
href={`/parts/p/${encodeURIComponent(platform)}/${encodeURIComponent(category.id)}`}
className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-md border border-zinc-700 bg-zinc-900/70 px-3 py-1.5 text-xs font-medium text-zinc-300 hover:bg-zinc-800 hover:border-zinc-600 transition-colors"
>
<svg
@@ -0,0 +1,16 @@
import PartsBrowseClient from "@/components/parts/PartsBrowseClient";
export default async function PartsBrowsePage({
params,
}: {
params: Promise<{ partRole: string }>;
}) {
const { partRole } = await params;
return (
<PartsBrowseClient
partRole={decodeURIComponent(partRole)}
platform={null} // let client read ?platform=... or default
/>
);
}
@@ -204,7 +204,7 @@ export default function ProductDetailsPage() {
href={{ pathname: "/builder", query: platform ? { platform } : {} }}
className="hover:text-zinc-300"
>
The Armory
The Builder
</Link>
<span className="text-zinc-700">/</span>
<Link
@@ -370,7 +370,7 @@ export default function CategoryPage() {
href={{ pathname: "/builder", query: platform ? { platform } : {} }}
className="text-xs text-zinc-400 hover:text-zinc-300 mb-4 inline-block"
>
Back to The Armory
Back to the Builder
</Link>
<div className="flex items-start justify-between gap-4">
<div>
@@ -17,6 +17,7 @@ type BuildDto = {
slot: string;
productId: number;
productName?: string | null;
productBrand?: string | null;
bestPrice?: number | string | null; // backend may serialize BigDecimal as string
}>;
photos?: BuildPhotoDto[];
@@ -68,7 +69,7 @@ export default function VaultBuildEditPage() {
return () => {
previews.forEach((p) => URL.revokeObjectURL(p.url));
};
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const authed = !!token;
@@ -311,54 +312,62 @@ export default function VaultBuildEditPage() {
{!build.items?.length ? (
<div className="text-sm opacity-70">No parts saved yet.</div>
) : (
<div className="space-y-4">
{Object.entries(
build.items.reduce<Record<string, typeof build.items>>(
(acc, it) => {
(acc[it.slot] ||= []).push(it);
return acc;
},
{}
)
).map(([slot, items]) => (
<div key={slot}>
<div className="mb-2 text-xs font-semibold uppercase tracking-wider text-zinc-400">
{slot}
</div>
<div className="overflow-hidden rounded-lg border border-white/10 bg-black/20">
{/* header */}
<div className="grid grid-cols-12 gap-3 border-b border-white/10 px-3 py-2 text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
<div className="col-span-5">Product</div>
<div className="col-span-4">Component (Role)</div>
<div className="col-span-2">Brand</div>
<div className="col-span-1 text-right">Price</div>
</div>
<div className="rounded-lg border border-white/10 bg-black/20">
{items.map((it, idx) => (
<div
key={`${it.slot}-${it.productId}-${idx}`}
className="flex items-center justify-between gap-4 px-3 py-2 text-sm border-b border-white/5 last:border-b-0"
>
<div className="min-w-0">
<div className="truncate text-zinc-100">
{it.productName ?? `Product #${it.productId}`}
</div>
<div className="text-xs text-zinc-400">
ID:{" "}
<span className="font-mono">{it.productId}</span>
</div>
{/* rows */}
{build.items
.slice()
.sort((a, b) => a.slot.localeCompare(b.slot))
.map((it, idx) => {
const price =
typeof it.bestPrice === "number"
? it.bestPrice
: it.bestPrice
? Number(it.bestPrice)
: null;
return (
<div
key={`${it.slot}-${it.productId}-${idx}`}
className="grid grid-cols-12 gap-2 border-b border-white/5 px-3 py-2 text-sm last:border-b-0"
>
{/* Product */}
<div className="col-span-5 min-w-0">
<div className="truncate text-zinc-100">
{it.productName ?? `Product #${it.productId}`}
</div>
<div className="shrink-0 text-right">
<div className="font-semibold">
{typeof it.bestPrice === "number"
? `$${it.bestPrice.toFixed(2)}`
: it.bestPrice
? `$${Number(it.bestPrice).toFixed(2)}`
: "—"}
</div>
<div className="text-[11px] text-zinc-500">
Best price
</div>
<div className="text-xs text-zinc-500">
ID:{" "}
<span className="font-mono">{it.productId}</span>
</div>
</div>
))}
</div>
</div>
))}
{/* Component (Role) */}
<div className="col-span-4 truncate text-zinc-200">
{it.slot}
</div>
{/* Brand */}
<div className="col-span-2 truncate text-zinc-300">
{it.productBrand ?? "—"}
</div>
{/* Price */}
<div className="col-span-1 text-right font-semibold text-zinc-100">
{price != null && !Number.isNaN(price)
? `$${price.toFixed(2)}`
: "—"}
</div>
</div>
);
})}
</div>
)}
</div>
+1 -1
View File
@@ -91,7 +91,7 @@ export default function LoginPage() {
<main className="min-h-screen bg-black text-zinc-50">
<div className="mx-auto flex max-w-md flex-col px-4 py-10">
<h1 className="text-2xl font-semibold tracking-tight">
Log In to <span className="text-amber-300">The Armory</span>
Log In to <span className="text-amber-300">The Builder</span>
</h1>
<p className="mt-2 text-sm text-zinc-400">
Use your beta credentials to get back to your saved builds.