fixed logo, fixed product details page image and api
This commit is contained in:
@@ -17,7 +17,7 @@ type GunbuilderProductFromApi = {
|
||||
platform: string;
|
||||
partRole: string;
|
||||
price: number | null;
|
||||
mainImageUrl: string | null;
|
||||
imageUrl: string | null;
|
||||
buyUrl: string | null;
|
||||
};
|
||||
|
||||
@@ -55,21 +55,20 @@ export default function PartDetailPage() {
|
||||
|
||||
// For now, pull the full AR-15 list and find by UUID.
|
||||
// We can optimize with a dedicated /api/products/{uuid} later.
|
||||
const url = `${API_BASE_URL}/api/products/gunbuilder?platform=AR-15`;
|
||||
const url = `${API_BASE_URL}/api/products/gunbuilder/products/${partId}`;
|
||||
const res = await fetch(url, { signal: controller.signal });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load product (${res.status})`);
|
||||
}
|
||||
|
||||
const data: GunbuilderProductFromApi[] = await res.json();
|
||||
const found = data.find((p) => p.id === partId) ?? null;
|
||||
const data: GunbuilderProductFromApi = await res.json();
|
||||
|
||||
if (!found) {
|
||||
if (!data) {
|
||||
setError("Product not found");
|
||||
}
|
||||
|
||||
setProduct(found);
|
||||
setProduct(data);
|
||||
} catch (err: any) {
|
||||
if (err.name === "AbortError") return;
|
||||
setError(err.message ?? "Failed to load product");
|
||||
@@ -77,7 +76,7 @@ export default function PartDetailPage() {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fetchProduct();
|
||||
|
||||
return () => controller.abort();
|
||||
@@ -187,11 +186,11 @@ export default function PartDetailPage() {
|
||||
<div className="grid gap-6 md:grid-cols-[minmax(0,2fr)_minmax(0,1.3fr)] items-start">
|
||||
{/* Left: image + meta */}
|
||||
<div className="space-y-4">
|
||||
{product.mainImageUrl && (
|
||||
{product.imageUrl && (
|
||||
<div className="overflow-hidden rounded-lg border border-zinc-800 bg-zinc-950">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={product.mainImageUrl}
|
||||
src={product.imageUrl}
|
||||
alt={product.name}
|
||||
className="w-full object-contain max-h-80 bg-zinc-950"
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useParams, useRouter } from "next/navigation";
|
||||
import { CATEGORIES } from "@/data/gunbuilderParts";
|
||||
import type { CategoryId, Part } from "@/types/gunbuilder";
|
||||
|
||||
|
||||
type ViewMode = "card" | "list";
|
||||
|
||||
type GunbuilderProductFromApi = {
|
||||
@@ -134,11 +135,6 @@ export default function CategoryPage() {
|
||||
}
|
||||
}, [build]);
|
||||
|
||||
useEffect(() => {
|
||||
// whenever the category or filters change, jump back to page 1
|
||||
setCurrentPage(1);
|
||||
}, [categoryId, brandFilter, sortBy, searchQuery]);
|
||||
|
||||
// handler to toggle Add / Remove for this category
|
||||
const handleTogglePart = (categoryId: CategoryId, partId: string) => {
|
||||
const isSelected = build[categoryId] === partId;
|
||||
@@ -293,6 +289,7 @@ export default function CategoryPage() {
|
||||
return { start, end };
|
||||
}, [filteredParts.length, currentPage, paginatedParts.length, PAGE_SIZE]);
|
||||
|
||||
|
||||
if (!category) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black text-zinc-50">
|
||||
|
||||
+19
-24
@@ -6,9 +6,9 @@ import Image from "next/image";
|
||||
export default function HomePage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [useCase, setUseCase] = useState("");
|
||||
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">(
|
||||
"idle"
|
||||
);
|
||||
const [status, setStatus] = useState<
|
||||
"idle" | "loading" | "success" | "error"
|
||||
>("idle");
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
|
||||
async function handleSubmit(e: FormEvent) {
|
||||
@@ -47,7 +47,7 @@ export default function HomePage() {
|
||||
return (
|
||||
<main className="min-h-screen bg-black text-zinc-50">
|
||||
{/* Abstract Brand Background */}
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-[0.065]">
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden opacity-[0.55]">
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
@@ -97,20 +97,15 @@ export default function HomePage() {
|
||||
<header className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Logo slot – replace src with your actual logo file */}
|
||||
<div className="relative h-10 w-10 overflow-hidden rounded-md border border-zinc-800 bg-zinc-900">
|
||||
<div className="flex items-center">
|
||||
<Image
|
||||
src="/battl-logo-mark.svg"
|
||||
alt="Battl Builders logo"
|
||||
fill
|
||||
className="object-contain p-1.5"
|
||||
src="/battl/battl-logo-mark-2.svg"
|
||||
alt="Battl Builders Logo"
|
||||
width={260} // adjust to taste
|
||||
height={48} // adjust to taste
|
||||
className="block"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.18em] text-zinc-500">
|
||||
Battl Builders
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="rounded-full border border-amber-500/40 bg-amber-500/10 px-3 py-1 text-xs font-medium text-amber-300">
|
||||
@@ -122,14 +117,15 @@ export default function HomePage() {
|
||||
<section className="mt-16 grid gap-10 md:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)] md:items-center">
|
||||
<div>
|
||||
<h1 className="text-balance text-3xl font-semibold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Stop Guessing.
|
||||
Stop Guessing.
|
||||
<span className="block text-amber-300 py-0.16em">
|
||||
Start Building
|
||||
Start Building.
|
||||
</span>
|
||||
</h1>
|
||||
<p className="mt-4 max-w-xl text-sm text-zinc-400 sm:text-base">
|
||||
From part comparisons to full-build matchups, Battl Builders helps you find the right components, catch compatibility issues, and score the best deals — all without juggling tabs or spreadsheets.
|
||||
|
||||
From part comparisons to full-build matchups, Battl Builders helps
|
||||
you find the right components, catch compatibility issues, and
|
||||
score the best deals — all without juggling tabs or spreadsheets.
|
||||
</p>
|
||||
|
||||
<ul className="mt-6 space-y-2 text-sm text-zinc-300">
|
||||
@@ -138,7 +134,6 @@ export default function HomePage() {
|
||||
<li>• Live pricing from vetted merchants</li>
|
||||
<li>• Save & share builds with your crew</li>
|
||||
<li>• Vote on the cleanest, meanest setups</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -212,8 +207,8 @@ export default function HomePage() {
|
||||
)}
|
||||
|
||||
<p className="pt-1 text-[10px] leading-relaxed text-zinc-500">
|
||||
You can one-click unsubscribe
|
||||
any time with one-click. No data games, no surprise newsletters.
|
||||
You can one-click unsubscribe any time with one-click. No data
|
||||
games, no surprise newsletters.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
@@ -222,7 +217,7 @@ export default function HomePage() {
|
||||
{/* Footer strip */}
|
||||
<footer className="mt-12 border-t border-zinc-900 pt-4 text-xs text-zinc-500">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<span>© {new Date().getFullYear()} BATTL BUILDERS</span>
|
||||
<span>© {new Date().getFullYear()} BATTL BUILDERS<span className="align-super text-[0.9em] ml-0.5">™</span></span>
|
||||
<span className="text-zinc-600">
|
||||
v0.1 • Import engine online • Builder UI in progress
|
||||
</span>
|
||||
@@ -231,4 +226,4 @@ export default function HomePage() {
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1001.79 154.88">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
font-family: AgencyFB-Bold, AgencyFB;
|
||||
font-size: 134.88px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
fill: #f0efef;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
letter-spacing: .06em;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.cls-5 {
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.cls-6 {
|
||||
letter-spacing: 0em;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<text class="cls-1" transform="translate(202.12 121.28)"><tspan class="cls-5" x="0" y="0">B</tspan><tspan class="cls-6" x="69.06" y="0">A</tspan><tspan class="cls-3" x="130.83" y="0">T</tspan><tspan class="cls-4" x="191.79" y="0">TLBUILDERS</tspan></text>
|
||||
<path class="cls-2" d="M155.87,0v108.05l-46.85,46.84H0V46.84L46.85,0h109.02ZM128.96,14.84H62.16v31.53h35.72l31.08-31.53ZM46.39,46.37v-24.11l-24.12,24.11h24.12ZM140.1,92.75V25.97l-31.55,31.07v35.71h31.55ZM46.39,61.21H15.77v66.78l30.62-30.14v-36.63ZM93.71,61.21h-31.55v31.53h31.55v-31.53ZM93.71,108.51h-36.65c-8.09,9.81-21.24,18.92-28.77,28.74-.72.93-1.79,1.17-1.39,2.79h65.41l1.39-1.39v-30.14ZM133.6,108.51h-25.05v24.11c1.62.4,1.85-.67,2.79-1.39,7.57-5.8,15.12-16.08,22.26-22.73Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
Reference in New Issue
Block a user