249 lines
6.6 KiB
TypeScript
249 lines
6.6 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { useForm } from "react-hook-form"
|
|
import { CheckCircle, AlertCircle } from "lucide-react"
|
|
|
|
import { FormInput } from "./form-input"
|
|
import { FormTextarea } from "./form-textarea"
|
|
import { FormButton } from "./form-button"
|
|
import { WebhookClient } from "@/lib/webhook-client"
|
|
|
|
interface ContactFormData {
|
|
firstName: string
|
|
lastName: string
|
|
email: string
|
|
phone: string
|
|
company: string
|
|
message: string
|
|
source?: string
|
|
page?: string
|
|
confirmEmail?: string
|
|
}
|
|
|
|
interface SubmittedContactFormData extends ContactFormData {
|
|
timestamp: string
|
|
url: string
|
|
}
|
|
|
|
interface ContactFormProps {
|
|
onSubmit?: (data: SubmittedContactFormData) => void
|
|
className?: string
|
|
}
|
|
|
|
export function ContactForm({ onSubmit, className }: ContactFormProps) {
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [isSubmitted, setIsSubmitted] = useState(false)
|
|
const [submitError, setSubmitError] = useState<string | null>(null)
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
reset,
|
|
setValue,
|
|
watch,
|
|
} = useForm<ContactFormData>({
|
|
defaultValues: {
|
|
source: "website",
|
|
page: "",
|
|
},
|
|
})
|
|
|
|
const watchedValues = watch()
|
|
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined') {
|
|
setValue("page", window.location.pathname)
|
|
}
|
|
}, [setValue])
|
|
|
|
const onFormSubmit = async (data: ContactFormData) => {
|
|
setIsSubmitting(true)
|
|
setSubmitError(null)
|
|
|
|
try {
|
|
// Add URL and timestamp
|
|
const formData: SubmittedContactFormData = {
|
|
...data,
|
|
timestamp: new Date().toISOString(),
|
|
url: window.location.href,
|
|
}
|
|
|
|
// Call the webhook client directly
|
|
const result = await WebhookClient.submitContactForm(formData)
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || "Failed to submit form")
|
|
}
|
|
|
|
setIsSubmitted(true)
|
|
reset()
|
|
|
|
// Call custom onSubmit handler if provided
|
|
if (onSubmit) {
|
|
onSubmit(formData)
|
|
}
|
|
} catch (error) {
|
|
console.error("Form submission error:", error)
|
|
setSubmitError("Failed to submit form. Please try again.")
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
const onFormReset = () => {
|
|
setIsSubmitted(false)
|
|
setSubmitError(null)
|
|
reset()
|
|
}
|
|
|
|
if (isSubmitted) {
|
|
return (
|
|
<div className={`space-y-4 text-center ${className}`}>
|
|
<div className="flex justify-center">
|
|
<CheckCircle className="h-12 w-12 text-green-500" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold">Thank You!</h3>
|
|
<p className="text-muted-foreground">
|
|
We've received your message and will get back to you within 24 hours.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={onFormReset}
|
|
className="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2"
|
|
>
|
|
Submit Another Message
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit(onFormSubmit)} className={`space-y-4 ${className}`}>
|
|
{/* Honeypot field for spam prevention */}
|
|
<div className="sr-only">
|
|
<label htmlFor="confirm-email">
|
|
Confirm Email (leave empty)
|
|
</label>
|
|
<input
|
|
id="confirm-email"
|
|
type="email"
|
|
{...register("confirmEmail")}
|
|
className="sr-only"
|
|
tabIndex={-1}
|
|
/>
|
|
</div>
|
|
|
|
{/* Name Fields */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormInput
|
|
id="firstName"
|
|
label="First Name"
|
|
placeholder="John"
|
|
error={errors.firstName?.message}
|
|
{...register("firstName", {
|
|
required: "First name is required",
|
|
})}
|
|
/>
|
|
<FormInput
|
|
id="lastName"
|
|
label="Last Name"
|
|
placeholder="Doe"
|
|
error={errors.lastName?.message}
|
|
{...register("lastName", {
|
|
required: "Last name is required",
|
|
})}
|
|
/>
|
|
</div>
|
|
|
|
{/* Contact Information */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormInput
|
|
id="email"
|
|
type="email"
|
|
label="Email"
|
|
placeholder="john@example.com"
|
|
error={errors.email?.message}
|
|
{...register("email", {
|
|
required: "Email is required",
|
|
pattern: {
|
|
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
|
message: "Please enter a valid email address",
|
|
},
|
|
})}
|
|
/>
|
|
<FormInput
|
|
id="phone"
|
|
type="tel"
|
|
inputMode="tel"
|
|
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}|[0-9]{10}"
|
|
label="Phone"
|
|
placeholder="(435) 233-9668"
|
|
error={errors.phone?.message}
|
|
{...register("phone", {
|
|
required: "Phone number is required",
|
|
validate: (value) => {
|
|
// Remove all non-digit characters
|
|
const phoneDigits = value.replace(/\D/g, '')
|
|
return phoneDigits.length >= 10 && phoneDigits.length <= 15 || "Please enter a valid phone number (10-15 digits)"
|
|
},
|
|
})}
|
|
/>
|
|
</div>
|
|
|
|
{/* Company (Optional) */}
|
|
<FormInput
|
|
id="company"
|
|
label="Company (Optional)"
|
|
placeholder="Your Company Name"
|
|
error={errors.company?.message}
|
|
{...register("company")}
|
|
/>
|
|
|
|
{/* Message */}
|
|
<FormTextarea
|
|
id="message"
|
|
label="Message"
|
|
placeholder="Tell us more about your needs and we'll get back to you within 24 hours."
|
|
error={errors.message?.message}
|
|
{...register("message", {
|
|
required: "Message is required",
|
|
minLength: {
|
|
value: 10,
|
|
message: "Message must be at least 10 characters",
|
|
},
|
|
})}
|
|
/>
|
|
|
|
{/* Submit Error */}
|
|
{submitError && (
|
|
<div className="flex items-center gap-2 text-destructive">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<span className="text-sm">{submitError}</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Submit Button */}
|
|
<FormButton
|
|
type="submit"
|
|
className="w-full"
|
|
loading={isSubmitting}
|
|
size="lg"
|
|
>
|
|
{isSubmitting ? "Sending..." : "Send Message"}
|
|
</FormButton>
|
|
|
|
<input
|
|
type="hidden"
|
|
{...register("source")}
|
|
value={watchedValues.source || "website"}
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
{...register("page")}
|
|
value={watchedValues.page || ""}
|
|
/>
|
|
</form>
|
|
)
|
|
}
|