186 lines
5.6 KiB
TypeScript
186 lines
5.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { CONSENT_TEXT, ACKNOWLEDGMENT_TEXT } from "@/lib/consent";
|
|
|
|
type Status = "idle" | "submitting" | "success" | "error";
|
|
|
|
const FIELD =
|
|
"w-full rounded-control border border-line bg-card px-3.5 py-2.5 text-[15px] text-ink placeholder:text-ink-faint transition-colors focus:border-terracotta";
|
|
const LABEL = "block text-[13px] font-medium text-ink-soft mb-1.5";
|
|
|
|
export default function ContactForm() {
|
|
const [fullName, setFullName] = useState("");
|
|
const [companyName, setCompanyName] = useState("");
|
|
const [role, setRole] = useState("");
|
|
const [email, setEmail] = useState("");
|
|
const [consentGiven, setConsentGiven] = useState(false);
|
|
const [consentAcknowledged, setConsentAcknowledged] = useState(false);
|
|
const [website, setWebsite] = useState(""); // honeypot
|
|
const [status, setStatus] = useState<Status>("idle");
|
|
const [error, setError] = useState("");
|
|
|
|
const canSubmit =
|
|
fullName.trim() &&
|
|
companyName.trim() &&
|
|
role.trim() &&
|
|
email.trim() &&
|
|
consentGiven &&
|
|
consentAcknowledged &&
|
|
status !== "submitting";
|
|
|
|
async function handleSubmit() {
|
|
setStatus("submitting");
|
|
setError("");
|
|
try {
|
|
const res = await fetch("/api/lead", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
fullName,
|
|
companyName,
|
|
role,
|
|
email,
|
|
consentGiven,
|
|
consentAcknowledged,
|
|
website,
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
setError(data.error ?? "Something went wrong. Please try again.");
|
|
setStatus("error");
|
|
return;
|
|
}
|
|
setStatus("success");
|
|
} catch {
|
|
setError("Network error. Please try again.");
|
|
setStatus("error");
|
|
}
|
|
}
|
|
|
|
if (status === "success") {
|
|
return (
|
|
<div className="rounded-card border border-ok/30 bg-ok-bg px-6 py-8 text-center">
|
|
<p className="text-[17px] font-semibold text-ok">Thank you.</p>
|
|
<p className="mt-1.5 text-[15px] text-ink-soft">
|
|
We've got your details and will be in touch shortly.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-5">
|
|
<div className="grid gap-5 sm:grid-cols-2">
|
|
<div>
|
|
<label className={LABEL} htmlFor="fullName">
|
|
Full name
|
|
</label>
|
|
<input
|
|
id="fullName"
|
|
className={FIELD}
|
|
value={fullName}
|
|
onChange={(e) => setFullName(e.target.value)}
|
|
autoComplete="name"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className={LABEL} htmlFor="companyName">
|
|
Company
|
|
</label>
|
|
<input
|
|
id="companyName"
|
|
className={FIELD}
|
|
value={companyName}
|
|
onChange={(e) => setCompanyName(e.target.value)}
|
|
autoComplete="organization"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-5 sm:grid-cols-2">
|
|
<div>
|
|
<label className={LABEL} htmlFor="role">
|
|
Role
|
|
</label>
|
|
<input
|
|
id="role"
|
|
className={FIELD}
|
|
value={role}
|
|
onChange={(e) => setRole(e.target.value)}
|
|
autoComplete="organization-title"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className={LABEL} htmlFor="email">
|
|
Email
|
|
</label>
|
|
<input
|
|
id="email"
|
|
type="email"
|
|
className={FIELD}
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
autoComplete="email"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Honeypot — visually hidden, off the tab order */}
|
|
<div aria-hidden className="absolute left-[-9999px] h-0 w-0 overflow-hidden">
|
|
<label htmlFor="website">Website</label>
|
|
<input
|
|
id="website"
|
|
tabIndex={-1}
|
|
autoComplete="off"
|
|
value={website}
|
|
onChange={(e) => setWebsite(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-3 pt-1">
|
|
<label className="flex cursor-pointer items-start gap-3">
|
|
<input
|
|
type="checkbox"
|
|
className="mt-0.5 h-4 w-4 shrink-0 accent-terracotta"
|
|
checked={consentGiven}
|
|
onChange={(e) => setConsentGiven(e.target.checked)}
|
|
/>
|
|
<span className="text-[13px] leading-relaxed text-ink-soft">
|
|
{CONSENT_TEXT}
|
|
</span>
|
|
</label>
|
|
<label className="flex cursor-pointer items-start gap-3">
|
|
<input
|
|
type="checkbox"
|
|
className="mt-0.5 h-4 w-4 shrink-0 accent-terracotta"
|
|
checked={consentAcknowledged}
|
|
onChange={(e) => setConsentAcknowledged(e.target.checked)}
|
|
/>
|
|
<span className="text-[13px] leading-relaxed text-ink-soft">
|
|
{ACKNOWLEDGMENT_TEXT}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
{status === "error" && (
|
|
<div
|
|
role="alert"
|
|
className="rounded-control border border-danger/30 bg-danger-bg px-3.5 py-2.5 text-[13px] text-danger"
|
|
>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={handleSubmit}
|
|
disabled={!canSubmit}
|
|
className="w-full rounded-control bg-gradient-to-br from-terracotta to-terracotta-end px-4 py-3 text-[15px] font-medium text-white transition-opacity hover:opacity-95 disabled:cursor-not-allowed disabled:opacity-40"
|
|
>
|
|
{status === "submitting" ? "Sending…" : "Send my details"}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|