303 lines
11 KiB
TypeScript
303 lines
11 KiB
TypeScript
"use client";
|
||
|
||
import { FormEvent, useState } from "react";
|
||
import Image from "next/image";
|
||
import Link from "next/link";
|
||
|
||
export default function HomePage() {
|
||
const [email, setEmail] = useState("");
|
||
const [useCase, setUseCase] = useState("");
|
||
const [status, setStatus] = useState<
|
||
"idle" | "loading" | "success" | "error"
|
||
>("idle");
|
||
|
||
const [message, setMessage] = useState<string | null>(null);
|
||
const [accepted, setAccepted] = useState(false);
|
||
const TOS_VERSION = "2025-12-27";
|
||
|
||
async function handleSubmit(e: FormEvent) {
|
||
e.preventDefault();
|
||
|
||
if (!email) {
|
||
setMessage("Drop an email in first, operator.");
|
||
setStatus("error");
|
||
return;
|
||
}
|
||
|
||
if (!accepted) {
|
||
setMessage(
|
||
"You must accept the Terms and confirm you’re responsible for safe/legal assembly."
|
||
);
|
||
setStatus("error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setStatus("loading");
|
||
setMessage(null);
|
||
|
||
const res = await fetch("/api/beta-signup", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ email, useCase, acceptedTos: accepted, tosVersion: TOS_VERSION }),
|
||
});
|
||
|
||
if (!res.ok) {
|
||
const txt = await res.text().catch(() => "");
|
||
throw new Error(txt || "Failed to save your signup.");
|
||
}
|
||
|
||
setStatus("success");
|
||
setMessage(
|
||
"✅ You’re on the list. Invites drop soon — we’ll email your access link when it’s go-time."
|
||
);
|
||
setEmail("");
|
||
setUseCase("");
|
||
} catch (err) {
|
||
console.error(err);
|
||
setStatus("error");
|
||
setMessage("Something broke. Try again in a minute.");
|
||
}
|
||
}
|
||
|
||
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.55]">
|
||
<svg
|
||
width="100%"
|
||
height="100%"
|
||
preserveAspectRatio="xMidYMid slice"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
>
|
||
<defs>
|
||
<pattern
|
||
id="battlGrid"
|
||
x="0"
|
||
y="0"
|
||
width="80"
|
||
height="80"
|
||
patternUnits="userSpaceOnUse"
|
||
>
|
||
<path
|
||
d="M 80 0 L 0 0 0 80"
|
||
fill="none"
|
||
stroke="#fbbf24"
|
||
strokeWidth="0.5"
|
||
strokeOpacity="0.35"
|
||
/>
|
||
</pattern>
|
||
|
||
<pattern
|
||
id="battlHex"
|
||
width="140"
|
||
height="140"
|
||
patternUnits="userSpaceOnUse"
|
||
>
|
||
<path
|
||
d="M70 4 L136 40 L136 110 L70 146 L4 110 L4 40 Z"
|
||
fill="none"
|
||
stroke="#fbbf24"
|
||
strokeWidth="0.4"
|
||
strokeOpacity="0.25"
|
||
/>
|
||
</pattern>
|
||
</defs>
|
||
|
||
<rect width="100%" height="100%" fill="url(#battlGrid)" />
|
||
<rect width="100%" height="100%" fill="url(#battlHex)" />
|
||
</svg>
|
||
</div>
|
||
<div className="mx-auto flex min-h-screen max-w-5xl flex-col px-4 py-10">
|
||
{/* Top Bar / Logo */}
|
||
<header className="flex items-center justify-between gap-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="flex items-center">
|
||
<Image
|
||
src="/battl/battl-logo-mark-f.svg"
|
||
alt="Battl Builders Logo"
|
||
width={260} // adjust to taste
|
||
height={48} // adjust to taste
|
||
className="block"
|
||
/>
|
||
</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">
|
||
Private Beta • Coming Soon
|
||
</span>
|
||
</header>
|
||
|
||
{/* Hero */}
|
||
<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.
|
||
<span className="block text-amber-300 py-0.16em">
|
||
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.
|
||
</p>
|
||
|
||
<ul className="mt-6 space-y-2 text-sm text-zinc-300">
|
||
<li>• Side-by-side part & build comparisons</li>
|
||
<li>• Smart compatibility checks</li>
|
||
<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>
|
||
|
||
{/* Beta Form */}
|
||
<div className="rounded-lg border border-zinc-800 bg-zinc-950/70 p-5 shadow-[0_0_0_1px_rgba(24,24,27,0.9)]">
|
||
<h2 className="text-sm font-semibold uppercase tracking-[0.18em] text-zinc-500">
|
||
Join the Beta List
|
||
</h2>
|
||
<p className="mt-2 text-xs text-zinc-400">
|
||
Drop your email and we'll send early access when the Builder
|
||
goes hot. No spam, just builds.
|
||
</p>
|
||
|
||
<form onSubmit={handleSubmit} className="mt-4 space-y-3">
|
||
<div>
|
||
<label
|
||
htmlFor="email"
|
||
className="text-xs font-medium text-zinc-300"
|
||
>
|
||
Email
|
||
</label>
|
||
<input
|
||
id="email"
|
||
type="email"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
placeholder="you@gearjunkie.com"
|
||
disabled={status === "loading" || status === "success"}
|
||
className="mt-1 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 py-2 text-sm text-zinc-50 outline-none ring-amber-500/30 placeholder:text-zinc-600 focus:border-amber-400/80 focus:ring-2 disabled:opacity-60 disabled:cursor-not-allowed"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label
|
||
htmlFor="useCase"
|
||
className="text-xs font-medium text-zinc-300"
|
||
>
|
||
What will you use Battl Builders for?
|
||
<span className="text-zinc-500"> (optional)</span>
|
||
</label>
|
||
<textarea
|
||
id="useCase"
|
||
value={useCase}
|
||
onChange={(e) => setUseCase(e.target.value)}
|
||
rows={3}
|
||
disabled={status === "loading" || status === "success"}
|
||
placeholder="E.g. Comparing build costs, finding the best deals, sharing my builds, weird influencer shit..."
|
||
className="mt-1 w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 py-2 text-xs text-zinc-50 outline-none ring-amber-500/30 placeholder:text-zinc-600 focus:border-amber-400/80 focus:ring-2 disabled:opacity-60 disabled:cursor-not-allowed"
|
||
/>
|
||
</div>
|
||
|
||
<div className="rounded-md border border-zinc-800 bg-black/30 p-3">
|
||
<label className="flex items-start gap-2 text-[11px] text-zinc-400">
|
||
<input
|
||
type="checkbox"
|
||
checked={accepted}
|
||
onChange={(e) => setAccepted(e.target.checked)}
|
||
disabled={status === "loading" || status === "success"}
|
||
className="mt-0.5 h-4 w-4 rounded border-zinc-700 bg-zinc-950 text-amber-400 focus:ring-2 focus:ring-amber-500/40"
|
||
required
|
||
/>
|
||
<span>
|
||
I agree to the{" "}
|
||
<Link
|
||
href="/tos"
|
||
className="text-amber-300 hover:text-amber-200 underline"
|
||
>
|
||
Terms of Service
|
||
</Link>{" "}
|
||
and{" "}
|
||
<Link
|
||
href="/privacy"
|
||
className="text-amber-300 hover:text-amber-200 underline"
|
||
>
|
||
Privacy Policy
|
||
</Link>
|
||
. I understand Battl Builders is informational only and I’m
|
||
solely responsible for legality, compatibility, and safe
|
||
assembly/use of any firearm or components.
|
||
</span>
|
||
</label>
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
disabled={
|
||
status === "loading" || status === "success" || !accepted
|
||
}
|
||
className="flex w-full items-center justify-center rounded-md border border-amber-500/70 bg-amber-500/90 px-3 py-2 text-sm font-medium text-black transition hover:bg-amber-400 disabled:cursor-not-allowed disabled:opacity-70"
|
||
>
|
||
{status === "loading"
|
||
? "Enlisting…"
|
||
: status === "success"
|
||
? "On the List ✅"
|
||
: "Get Beta Access"}
|
||
</button>
|
||
|
||
{message && (
|
||
<p
|
||
className={`text-xs ${
|
||
status === "success"
|
||
? "text-emerald-400"
|
||
: status === "error"
|
||
? "text-red-400"
|
||
: "text-zinc-400"
|
||
}`}
|
||
>
|
||
{message}
|
||
</p>
|
||
)}
|
||
|
||
{status === "success" && (
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
setStatus("idle");
|
||
setMessage(null);
|
||
}}
|
||
className="w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 py-2 text-xs text-zinc-200 hover:bg-zinc-900"
|
||
>
|
||
Submit another email
|
||
</button>
|
||
)}
|
||
|
||
<div className="mt-6 text-center text-[11px] text-zinc-500">
|
||
Already invited?{" "}
|
||
<a href="/login" className="underline hover:text-zinc-300">
|
||
Log in
|
||
</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 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 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>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|