"use client"; import React, { useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Form, FormControl, FormField, FormItem, FormMessage, } from "@/components/ui/form"; import { Button } from "./ui/button"; interface KleapFormField { name: string; label: string; type: | "text" | "email" | "tel" | "textarea" | "select" | "checkbox" | "radio" | "url" | "number"; placeholder?: string; required?: boolean; options?: string[]; // For select, radio rows?: number; // For textarea validation?: { min?: number; max?: number; pattern?: RegExp; message?: string; }; } /** * KleapForm - Universal form component with automatic submission to Kleap API * * @example * ```tsx * * ``` */ interface KleapFormProps { formId: string; title?: string; description?: string; fields: KleapFormField[]; /** Text for submit button - Use "submitText" NOT "submitButtonText" */ submitText?: string; /** Message shown after successful submission */ successMessage?: string; className?: string; honeypot?: boolean; // Anti-spam honeypot field } export function KleapForm({ formId, title, description, fields, submitText = "Submit", successMessage = "Thank you! Your submission has been received.", className = "", honeypot = true, }: KleapFormProps) { const [submitted, setSubmitted] = useState(false); const [submitError, setSubmitError] = useState(""); // Dynamically create Zod schema based on fields const schemaShape: Record = {}; fields.forEach((field) => { let fieldSchema: z.ZodType; switch (field.type) { case "email": fieldSchema = z .string() .email(field.validation?.message || "Please enter a valid email"); break; case "number": fieldSchema = z.coerce.number(); if (field.validation?.min !== undefined) { fieldSchema = (fieldSchema as z.ZodNumber).min( field.validation.min, field.validation.message, ); } if (field.validation?.max !== undefined) { fieldSchema = (fieldSchema as z.ZodNumber).max( field.validation.max, field.validation.message, ); } break; case "url": fieldSchema = z .string() .url(field.validation?.message || "Please enter a valid URL"); break; case "checkbox": fieldSchema = z.boolean(); break; default: fieldSchema = z.string(); if (field.validation?.min !== undefined) { fieldSchema = (fieldSchema as z.ZodString).min( field.validation.min, field.validation.message, ); } if (field.validation?.max !== undefined) { fieldSchema = (fieldSchema as z.ZodString).max( field.validation.max, field.validation.message, ); } if (field.validation?.pattern) { fieldSchema = (fieldSchema as z.ZodString).regex( field.validation.pattern, field.validation.message, ); } } if (field.required && field.type !== "checkbox") { fieldSchema = fieldSchema.refine((val) => val !== "", { message: `${field.label} is required`, }); } schemaShape[field.name] = field.required || field.type === "checkbox" ? fieldSchema : fieldSchema.optional(); }); const formSchema = z.object(schemaShape); type FormData = z.infer; const form = useForm({ resolver: zodResolver(formSchema), defaultValues: fields.reduce( (acc, field) => { acc[field.name] = field.type === "checkbox" ? false : ""; return acc; }, {} as Record, ), }); async function onSubmit(values: FormData) { setSubmitError(""); try { // Get app_id from environment or URL const envAppId = process.env.NEXT_PUBLIC_APP_ID; const urlAppId = new URLSearchParams(window.location.search).get( "app_id", ); const appId = envAppId || urlAppId || ""; // 🔍 DEBUG HELPER - Log app_id sources console.group("🔍 KLEAP FORM DEBUG - App ID Detection"); if (!appId) { console.error( "🚨 CRITICAL: app_id is empty! Form submission will fail.", ); } console.groupEnd(); // Create form data const formData = new FormData(); formData.append("app_id", appId); formData.append("form_id", formId); formData.append("form_name", title || formId); // Add all form values Object.entries(values).forEach(([key, value]) => { formData.append(key, String(value)); }); // 📤 DEBUG HELPER - Log request details console.group("📤 KLEAP FORM REQUEST"); for (const [_key, _value] of formData.entries()) { // Debug logging entries } console.groupEnd(); // Submit to Kleap's form API const response = await fetch("https://form.kleap.co", { method: "POST", body: formData, }); // 📥 DEBUG HELPER - Log response details const responseText = await response.text(); console.group("📥 KLEAP FORM RESPONSE"); try { const responseJson = JSON.parse(responseText); if (!responseJson.success && responseJson.error) { console.error("❌ API Error:", responseJson.error); } } catch { // Handle error silently } console.groupEnd(); if (response.ok) { setSubmitted(true); form.reset(); } else { throw new Error( `Failed to submit form: ${response.status} ${responseText}`, ); } } catch (e) { console.error("Form submission error:", e); setSubmitError( "Sorry, there was an error submitting your form. Please try again.", ); } } if (submitted) { return (

{successMessage}

); } return (
{title && (

{title}

)} {description && (

{description}

)} {fields.map((field) => ( (
{field.type === "textarea" ? (