Remove auth/db stack, migrate contact email flow, and update for Next 16
This commit is contained in:
+388
-94
@@ -1,103 +1,397 @@
|
||||
import { headers } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { LatestPost } from "~/app/_components/post";
|
||||
import { auth } from "~/server/better-auth";
|
||||
import { getSession } from "~/server/better-auth/server";
|
||||
import { api, HydrateClient } from "~/trpc/server";
|
||||
import { ContactForm } from "~/app/_components/contact-form";
|
||||
|
||||
export default async function Home() {
|
||||
const hello = await api.post.hello({ text: "from tRPC" });
|
||||
const session = await getSession();
|
||||
const services = [
|
||||
{
|
||||
title: "Networks & Infrastructure",
|
||||
body: "UniFi networks designed, deployed, and managed end to end — wired, wireless, VLANs, and the hardware that has to stay up.",
|
||||
tag: "UniFi",
|
||||
},
|
||||
{
|
||||
title: "Servers & Cloud",
|
||||
body: "Provisioning and running local and cloud servers — web servers, object storage (S3), and automated backups that actually restore.",
|
||||
tag: "Cloud",
|
||||
},
|
||||
{
|
||||
title: "Databases",
|
||||
body: "Design, deployment, and day-to-day operation. Reliable data, backed up, monitored, and recoverable when it counts.",
|
||||
tag: "Data",
|
||||
},
|
||||
{
|
||||
title: "Web & Applications",
|
||||
body: "Marketing sites to production web apps — built, hosted, and maintained by the same person who runs the servers under them.",
|
||||
tag: "Web",
|
||||
},
|
||||
{
|
||||
title: "Commerce & Ticketing",
|
||||
body: "Shopify storefronts plus a custom ticket-sales platform built from scratch to handle real event-day volume.",
|
||||
tag: "Commerce",
|
||||
},
|
||||
{
|
||||
title: "On-Site Operations",
|
||||
body: "When it's race day, whatever it takes to keep things moving — up to and including working the counter and selling tires.",
|
||||
tag: "On-site",
|
||||
},
|
||||
];
|
||||
|
||||
if (session) {
|
||||
void api.post.getLatest.prefetch();
|
||||
}
|
||||
const stats = [
|
||||
{ value: "5+ yrs", label: "Running the stack at Riverhead Raceway" },
|
||||
{ value: "1 partner", label: "From the wall jack to the checkout button" },
|
||||
{ value: "24/7", label: "Systems built to stay online" },
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<HydrateClient>
|
||||
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
|
||||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
|
||||
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
|
||||
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
|
||||
</h1>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/usage/first-steps"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">First Steps →</h3>
|
||||
<div className="text-lg">
|
||||
Just the basics - Everything you need to know to set up your
|
||||
database and authentication.
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">Documentation →</h3>
|
||||
<div className="text-lg">
|
||||
Learn more about Create T3 App, the libraries it uses, and how
|
||||
to deploy it.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<p className="text-2xl text-white">
|
||||
{hello ? hello.greeting : "Loading tRPC query..."}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<p className="text-center text-2xl text-white">
|
||||
{session && <span>Logged in as {session.user?.name}</span>}
|
||||
</p>
|
||||
{!session ? (
|
||||
<form>
|
||||
<button
|
||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
|
||||
formAction={async () => {
|
||||
"use server";
|
||||
const res = await auth.api.signInSocial({
|
||||
body: {
|
||||
provider: "github",
|
||||
callbackURL: "/",
|
||||
},
|
||||
});
|
||||
if (!res.url) {
|
||||
throw new Error("No URL returned from signInSocial");
|
||||
}
|
||||
redirect(res.url);
|
||||
}}
|
||||
>
|
||||
Sign in with Github
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<form>
|
||||
<button
|
||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
|
||||
formAction={async () => {
|
||||
"use server";
|
||||
await auth.api.signOut({
|
||||
headers: await headers(),
|
||||
});
|
||||
redirect("/");
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{session?.user && <LatestPost />}
|
||||
</div>
|
||||
<div className="relative min-h-screen overflow-x-hidden">
|
||||
<Header />
|
||||
<main>
|
||||
<Hero />
|
||||
<Stats />
|
||||
<Services />
|
||||
<Approach />
|
||||
<NameStory />
|
||||
<Contact />
|
||||
</main>
|
||||
</HydrateClient>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Reflected-"H" monogram: an H standing on a waterline, mirrored below like a
|
||||
// reflection on still water — a nod to Hadlock Pond.
|
||||
function Mark({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" fill="none" className={className} aria-hidden="true">
|
||||
<g
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M10 7v8M22 7v8M10 11h12" />
|
||||
</g>
|
||||
<line
|
||||
x1="6"
|
||||
y1="16.5"
|
||||
x2="26"
|
||||
y2="16.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<g
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
opacity="0.3"
|
||||
>
|
||||
<path d="M10 18v6M22 18v6" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function Wordmark() {
|
||||
return (
|
||||
<Link href="/" className="group flex items-center gap-2.5">
|
||||
<span className="flex h-8 w-8 items-center justify-center rounded-md border border-brand-400/40 bg-brand-400/10 text-brand-300 transition group-hover:border-brand-400/70">
|
||||
<Mark className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="text-[15px] font-semibold tracking-tight text-white">
|
||||
Hadlock<span className="text-brand-400"> Technologies</span>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<header className="absolute inset-x-0 top-0 z-50">
|
||||
<div className="mx-auto flex h-20 max-w-6xl items-center justify-between px-6">
|
||||
<Wordmark />
|
||||
<nav className="hidden items-center gap-1 rounded-full border border-white/10 bg-white/5 p-1 text-sm text-slate-200 backdrop-blur-md md:flex">
|
||||
<a
|
||||
href="#services"
|
||||
className="rounded-full px-4 py-1.5 transition hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
Services
|
||||
</a>
|
||||
<a
|
||||
href="#approach"
|
||||
className="rounded-full px-4 py-1.5 transition hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
Approach
|
||||
</a>
|
||||
<a
|
||||
href="#name"
|
||||
className="rounded-full px-4 py-1.5 transition hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
The name
|
||||
</a>
|
||||
</nav>
|
||||
<a
|
||||
href="#contact"
|
||||
className="rounded-full bg-white px-4 py-2 text-sm font-semibold text-ink-950 transition hover:bg-slate-200"
|
||||
>
|
||||
Get in touch
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
const capabilities = [
|
||||
"Networks",
|
||||
"Servers",
|
||||
"Cloud",
|
||||
"Storage",
|
||||
"Databases",
|
||||
"Web",
|
||||
"Commerce",
|
||||
];
|
||||
|
||||
function Hero() {
|
||||
return (
|
||||
<section className="relative isolate flex min-h-screen flex-col overflow-hidden">
|
||||
{/* Hadlock Pond, full-bleed with a slight darkening filter */}
|
||||
<div className="absolute inset-0 -z-20">
|
||||
<Image
|
||||
src="/hadlock-pond.jpg"
|
||||
alt="Hadlock Pond at dawn in Fort Ann, New York"
|
||||
fill
|
||||
priority
|
||||
sizes="100vw"
|
||||
className="object-cover brightness-[0.68] saturate-[0.95]"
|
||||
/>
|
||||
</div>
|
||||
{/* Scrims: darken top for the nav, left for the text, and fade the base into the page */}
|
||||
<div className="absolute inset-0 -z-10 bg-gradient-to-b from-ink-950/70 via-transparent to-ink-950" />
|
||||
<div className="absolute inset-0 -z-10 bg-gradient-to-r from-ink-950/85 via-ink-950/30 to-transparent" />
|
||||
|
||||
<div className="mx-auto flex w-full max-w-6xl flex-1 flex-col justify-center px-6 pt-32 pb-16">
|
||||
<div className="inline-flex w-fit items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3.5 py-1.5 text-xs font-medium text-slate-200 backdrop-blur-sm">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-brand-400" />
|
||||
Full-stack technical partner · Long Island, NY
|
||||
</div>
|
||||
|
||||
<h1 className="mt-7 max-w-4xl font-mono text-4xl font-semibold tracking-tight text-white uppercase sm:text-6xl sm:leading-[1.05]">
|
||||
Everything technical,{" "}
|
||||
<span className="text-brand-400">handled.</span>
|
||||
</h1>
|
||||
|
||||
<p className="mt-6 max-w-xl text-lg leading-relaxed text-slate-300">
|
||||
The networks, servers, and software that keep a business running —
|
||||
designed, built, and maintained under one accountable name, instead of
|
||||
five vendors pointing at each other.
|
||||
</p>
|
||||
|
||||
<div className="mt-9 flex flex-wrap items-center gap-4">
|
||||
<a
|
||||
href="#contact"
|
||||
className="rounded-full bg-brand-400 px-6 py-3 text-sm font-semibold text-ink-950 transition hover:bg-brand-300"
|
||||
>
|
||||
Start a conversation
|
||||
</a>
|
||||
<a
|
||||
href="#services"
|
||||
className="rounded-full border border-white/20 bg-white/5 px-6 py-3 text-sm font-semibold text-white backdrop-blur-sm transition hover:border-white/40 hover:bg-white/10"
|
||||
>
|
||||
See what we do
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Capability strip — where DUNE puts its client logos */}
|
||||
<div className="relative border-t border-white/10 bg-ink-950/40 backdrop-blur-sm">
|
||||
<div className="mx-auto flex max-w-6xl flex-wrap items-center gap-x-6 gap-y-2 px-6 py-5 font-mono text-xs tracking-widest text-slate-400 uppercase">
|
||||
<span className="text-slate-500">Working across</span>
|
||||
{capabilities.map((c) => (
|
||||
<span key={c} className="text-slate-300">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Stats() {
|
||||
return (
|
||||
<section className="border-y border-white/5 bg-ink-900/50">
|
||||
<div className="mx-auto grid max-w-6xl grid-cols-1 divide-y divide-white/5 px-6 sm:grid-cols-3 sm:divide-x sm:divide-y-0">
|
||||
{stats.map((s) => (
|
||||
<div key={s.label} className="px-2 py-8 sm:px-6">
|
||||
<div className="text-2xl font-bold text-white">{s.value}</div>
|
||||
<div className="mt-1 text-sm text-slate-400">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Services() {
|
||||
return (
|
||||
<section id="services" className="scroll-mt-20">
|
||||
<div className="mx-auto max-w-6xl px-6 py-24">
|
||||
<SectionHeading
|
||||
eyebrow="What we do"
|
||||
title="One partner. The whole stack."
|
||||
description="Most businesses stitch together a network vendor, a hosting company, a web agency, and a point-of-sale provider. Hadlock is all of it — and the same hands that wire the network ship the app on top of it."
|
||||
/>
|
||||
|
||||
<div className="mt-14 grid grid-cols-1 gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{services.map((s) => (
|
||||
<div
|
||||
key={s.title}
|
||||
className="group relative bg-ink-900 p-7 transition hover:bg-ink-850"
|
||||
>
|
||||
<div className="mb-4 inline-flex items-center rounded-md border border-brand-400/30 bg-brand-400/10 px-2.5 py-1 text-xs font-medium text-brand-300">
|
||||
{s.tag}
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white">{s.title}</h3>
|
||||
<p className="mt-2.5 text-sm leading-relaxed text-slate-400">
|
||||
{s.body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Approach() {
|
||||
const points = [
|
||||
{
|
||||
title: "Full ownership",
|
||||
body: "From the physical layer up to the interface. Nothing gets lost in the handoff between vendors, because there is no handoff.",
|
||||
},
|
||||
{
|
||||
title: "Hands-on, not hands-off",
|
||||
body: "The person who designed it is the person who fixes it — on-site when it matters, no ticket queue between you and a resolution.",
|
||||
},
|
||||
{
|
||||
title: "Built to stay up",
|
||||
body: "Backups that restore, monitoring that warns you first, and systems sized for the busy day, not the average one.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
id="approach"
|
||||
className="scroll-mt-20 border-y border-white/5 bg-ink-900/40"
|
||||
>
|
||||
<div className="mx-auto max-w-6xl px-6 py-24">
|
||||
<div className="grid grid-cols-1 gap-14 lg:grid-cols-[1fr_1.2fr]">
|
||||
<div>
|
||||
<SectionHeading
|
||||
eyebrow="The approach"
|
||||
title="A titleless everything-engineer, on your side."
|
||||
description="Software engineer, network admin, systems operator, and — on the right day — the person selling tires at the counter. The job is whatever keeps the operation running."
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10">
|
||||
{points.map((p) => (
|
||||
<div key={p.title} className="bg-ink-900 p-7">
|
||||
<h3 className="text-base font-semibold text-white">
|
||||
{p.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-slate-400">
|
||||
{p.body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function NameStory() {
|
||||
return (
|
||||
<section id="name" className="scroll-mt-20">
|
||||
<div className="mx-auto max-w-3xl px-6 py-24 text-center">
|
||||
<div className="font-mono text-sm font-semibold tracking-widest text-brand-400 uppercase">
|
||||
The name
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
Named for still water in the Adirondacks.
|
||||
</h2>
|
||||
<p className="mx-auto mt-4 max-w-xl text-base leading-relaxed text-slate-300">
|
||||
The water at the top of this page is Hadlock Pond in Fort Ann, New
|
||||
York — quiet, dependable, and built to last. It's a good standard
|
||||
for infrastructure: you shouldn't have to think about it for it to
|
||||
be working.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Contact() {
|
||||
return (
|
||||
<section
|
||||
id="contact"
|
||||
className="scroll-mt-20 border-t border-white/5 bg-ink-900/50"
|
||||
>
|
||||
<div className="mx-auto max-w-3xl px-6 py-24">
|
||||
<div className="relative overflow-hidden rounded-3xl border border-white/10 bg-gradient-to-br from-ink-850 to-ink-900 p-8 md:p-12">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(50%_60%_at_50%_0%,rgba(45,212,191,0.12),transparent_70%)]" />
|
||||
<div className="mb-8 text-center">
|
||||
<div className="font-mono text-sm font-semibold tracking-widest text-brand-400 uppercase">
|
||||
Get in touch
|
||||
</div>
|
||||
<h2 className="mx-auto mt-3 max-w-2xl text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
Have something technical that needs an owner?
|
||||
</h2>
|
||||
</div>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-white/5">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-4 px-6 py-10 sm:flex-row">
|
||||
<Wordmark />
|
||||
<p className="text-sm text-slate-500">
|
||||
Hadlock Technologies LLC · New York · © {new Date().getFullYear()}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionHeading({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div className="font-mono text-sm font-semibold tracking-widest text-brand-400 uppercase">
|
||||
{eyebrow}
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="mt-4 text-base leading-relaxed text-slate-300">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user