fixed layouts, routing and the platform switcher.
This commit is contained in:
@@ -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
|
||||
/>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user