Files
aigrowthtech/src/components/content/contact-form.tsx
T
2026-10-12 12:13:20 -04:00

103 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import Script from "next/script";
import { useActionState, useEffect, useRef } from "react";
import { sendContact, type ContactState } from "@/app/contact/actions";
import { CONTACT_EMAIL, serviceNav } from "@/lib/routes";
const interests = [...serviceNav.map((s) => s.name), "Not sure yet"];
const initial: ContactState = { status: "idle" };
// Cloudflare Turnstile renders only once a site key is configured.
const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
declare global {
interface Window {
turnstile?: { reset: (el?: Element | string) => void };
}
}
export default function ContactForm() {
const [state, action, pending] = useActionState(sendContact, initial);
const startedRef = useRef<HTMLInputElement>(null);
// When the visitor started filling the form; the server rejects instant (bot) submissions.
useEffect(() => {
if (startedRef.current && !startedRef.current.value) startedRef.current.value = String(Date.now());
}, []);
// A Turnstile token is single-use, so get a fresh one after a failed attempt.
useEffect(() => {
if (state.status === "error") window.turnstile?.reset();
}, [state]);
if (state.status === "sent") {
return (
<div className="contact-form form-sent" role="status">
<svg className="sent-mark" viewBox="0 0 48 48" aria-hidden="true">
<circle cx="24" cy="24" r="23" />
<path d="M14 24.5l7 7 13-14" />
</svg>
<h3 className="card-title">Your message was sent successfully.</h3>
<p className="copy">Thank you for reaching out. Our team will review what you shared and get back to you shortly.</p>
</div>
);
}
return (
<form className="contact-form" action={action}>
<div className="field-row">
<label className="field">
<span>Name</span>
<input name="name" autoComplete="name" required maxLength={120} />
</label>
<label className="field">
<span>Work email</span>
<input name="email" type="email" autoComplete="email" required maxLength={200} />
</label>
</div>
<div className="field-row">
<label className="field">
<span>Company</span>
<input name="company" autoComplete="organization" maxLength={160} />
</label>
<label className="field">
<span>Website</span>
<input name="website" type="url" inputMode="url" placeholder="https://" maxLength={300} />
</label>
</div>
{/* Honeypot for bots — hidden from people and assistive tech. */}
<input name="company_url" className="hp" tabIndex={-1} autoComplete="off" aria-hidden="true" />
<input ref={startedRef} type="hidden" name="started" />
<fieldset className="field">
<legend>What are you looking at?</legend>
<div className="chips">
{interests.map((i) => (
<label key={i} className="chip">
<input type="checkbox" name="interest" value={i} />
<span>{i}</span>
</label>
))}
</div>
</fieldset>
<label className="field">
<span>What’s happening in the business?</span>
<textarea name="message" rows={5} required maxLength={5000} />
</label>
{TURNSTILE_SITE_KEY && (
<>
<Script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer />
<div className="cf-turnstile" data-sitekey={TURNSTILE_SITE_KEY} data-theme="dark" data-size="flexible" />
</>
)}
<button type="submit" className="btn btn-solid" disabled={pending}>
{pending ? "Sending…" : "Send to the Team"} <span className="arrow">→</span>
</button>
<p className={`form-note${state.status === "error" ? " is-error" : ""}`} role="status">
{state.status === "error"
? `${state.message} You can also write to ${CONTACT_EMAIL}.`
: "A strategist reads every message and replies personally."}
</p>
</form>
);
}