@naktor/contact-form
v1.2.0
Published
Reusable contact form with Turnstile CAPTCHA, Honeypot protection, and Zod validation
Maintainers
Readme
@naktor/contact-form
A reusable, customizable contact form component with built-in Cloudflare Turnstile CAPTCHA, honeypot protection, and Zod validation.
Features
- Cloudflare Turnstile - Invisible CAPTCHA protection
- Honeypot field - Additional bot protection
- Zod validation - Type-safe form validation
- i18n support - Built-in Spanish and English error messages
- Render props - Fully customizable UI
- TypeScript - Full type definitions included
Installation
npm install @naktor/contact-form
# or
pnpm add @naktor/contact-form
# or
yarn add @naktor/contact-formPeer Dependencies
Make sure you have these installed:
npm install react react-dom zodUsage
Client-side (React Component)
import { ContactForm, type ContactFormTranslations } from "@naktor/contact-form";
import { errorMessagesEN } from "@naktor/contact-form/schemas";
const translations: ContactFormTranslations = {
labels: {
name: "Name",
company: "Company",
email: "Email",
message: "Message",
},
placeholders: {
name: "Your name",
email: "[email protected]",
message: "Tell us about your project...",
},
submitButton: "Send Message",
submittingButton: "Sending...",
successTitle: "Message Sent!",
successMessage: "We'll get back to you soon.",
errorMessages: errorMessagesEN,
apiErrors: {
rateLimited: "Too many attempts. Please wait.",
verificationFailed: "Verification failed. Please reload.",
serverError: "Server error. Please try again.",
networkError: "Connection error. Check your internet.",
},
};
export default function ContactPage() {
return (
<ContactForm
translations={translations}
turnstileSiteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || ""}
apiEndpoint="/api/contact"
className="space-y-4"
onSuccess={() => console.log("Form submitted!")}
/>
);
}Server-side (API Route)
// app/api/contact/route.ts (Next.js App Router)
import { verifyTurnstileToken, isHoneypotFilled } from "@naktor/contact-form/server";
import { contactSchemaServer } from "@naktor/contact-form/schemas";
export async function POST(request: Request) {
const body = await request.json();
// Check honeypot
if (isHoneypotFilled(body.website)) {
return Response.json({ success: true }); // Silent success for bots
}
// Verify Turnstile token
const turnstileResult = await verifyTurnstileToken(body.turnstileToken);
if (!turnstileResult.success) {
return Response.json({ error: "Verification failed" }, { status: 403 });
}
// Validate form data
const validationResult = contactSchemaServer.safeParse(body);
if (!validationResult.success) {
return Response.json({ error: "Invalid data" }, { status: 400 });
}
// Process the form (send email, save to DB, etc.)
const { name, email, company, message } = validationResult.data;
// ... your logic here
return Response.json({ success: true });
}Customizing the UI
Use render props to fully customize the form appearance:
<ContactForm
translations={translations}
turnstileSiteKey="..."
renderInput={({ id, name, type, label, placeholder, value, onChange, error }) => (
<div className="my-custom-input">
<label htmlFor={id}>{label}</label>
<input
id={id}
name={name}
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
/>
{error && <span className="error">{error}</span>}
</div>
)}
renderButton={({ type, disabled, isSubmitting, text }) => (
<button type={type} disabled={disabled} className="my-button">
{text}
</button>
)}
renderSuccess={({ title, message }) => (
<div className="success-message">
<h3>{title}</h3>
<p>{message}</p>
</div>
)}
/>Environment Variables
# Client-side (public)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_site_key
# Server-side (secret)
TURNSTILE_SECRET_KEY=your_secret_keyAPI Reference
Exports
@naktor/contact-form (client)
ContactForm- Main form componentTurnstileWidget- Standalone Turnstile widgetresetTurnstile- Function to reset the widget
@naktor/contact-form/server
verifyTurnstileToken- Verify Turnstile token on serverisHoneypotFilled- Check if honeypot field was filled
@naktor/contact-form/schemas
createContactSchema- Create a Zod schema with custom messagescontactSchemaServer- Pre-configured schema for servererrorMessagesES- Spanish error messageserrorMessagesEN- English error messages
License
MIT
