Form now POSTs to Zoho

This commit is contained in:
null 2026-05-27 20:57:55 -05:00
parent 548e20e6f0
commit 033bdf6625
1 changed files with 114 additions and 119 deletions

View File

@ -1,49 +1,71 @@
import SEO from '@/components/SEO'
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import { toast } from 'sonner'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
import { Select } from '@/components/ui/Select'
import RecaptchaPlaceholder from '@/components/RecaptchaPlaceholder'
import { submitLead } from '@/lib/api'
import { useDebounce } from '@/hooks/useDebounce'
import { ArrowRight } from 'lucide-react'
const Contact = () => {
const formRef = useRef(null)
const [formState, setFormState] = useState({
company: '',
name: '',
email: '',
phone: '',
zip: '',
message: '',
service_interest: '',
recaptcha_token: '',
company_website: '',
'Last Name': '',
Company: '',
Email: '',
Phone: '',
'Zip Code': '',
Description: '',
})
const [errors, setErrors] = useState({
company: '',
name: '',
email: '',
zip: '',
message: '',
'Last Name': '',
Company: '',
Email: '',
'Zip Code': '',
Description: '',
recaptcha_token: '',
})
const debouncedErrors = useDebounce(errors, 300)
const [debouncedErrors, setDebouncedErrors] = useState(errors)
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
const t = setTimeout(() => setDebouncedErrors(errors), 300)
return () => clearTimeout(t)
}, [errors])
useEffect(() => {
const iframe = document.getElementById('zoho_webform_iframe')
if (!iframe) return
const handleLoad = () => {
if (!isSubmitting) return
setIsSubmitting(false)
toast.success("Thanks! We'll be in touch shortly.")
setFormState({ 'Last Name': '', Company: '', Email: '', Phone: '', 'Zip Code': '', Description: '' })
setErrors({ 'Last Name': '', Company: '', Email: '', 'Zip Code': '', Description: '', recaptcha_token: '' })
}
iframe.addEventListener('load', handleLoad)
return () => iframe.removeEventListener('load', handleLoad)
}, [isSubmitting])
useEffect(() => {
const script = document.createElement('script')
script.id = 'wf_anal'
script.src = 'https://crm.zohopublic.com/crm/WebFormAnalyticsServeServlet?rid=e44e9662530fc5bd9cdd3c43501fc243f89ba03759e7946c4b5e5016795b606b59b54d0e73c68671b2140fac5c8e788agid3b907524e85f9cba94899d77d7200771ee5d0ea567c43ec341d7b2ce40324d40gid26922a9cd1e8191a5f58ecb2524e0d22b8dd027eb943658ee681ab6890436af2gidefa1b1002d15951a0a2ac36cb33cdb4b5c6aeb110e6f4ac68b764345b9429653&tw=e048253ca680b107993ed5922e00cc1ebab3de97e797fce56fc6ad6af0dfc0bc'
document.body.appendChild(script)
return () => { document.getElementById('wf_anal')?.remove() }
}, [])
const validateForm = () => {
const newErrors = { company: '', name: '', email: '', zip: '', message: '', recaptcha_token: '' }
if (!formState.company.trim()) newErrors.company = 'Company name is required'
if (!formState.name.trim()) newErrors.name = 'Name is required'
if (!formState.zip.trim()) newErrors.zip = 'ZIP code is required'
if (!formState.message.trim()) newErrors.message = 'Message is required'
const newErrors = { 'Last Name': '', Company: '', Email: '', 'Zip Code': '', Description: '', recaptcha_token: '' }
if (!formState.Company.trim()) newErrors.Company = 'Company name is required'
if (!formState['Last Name'].trim()) newErrors['Last Name'] = 'Name is required'
if (!formState['Zip Code'].trim()) newErrors['Zip Code'] = 'ZIP code is required'
if (!formState.Description.trim()) newErrors.Description = 'Message is required'
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!formState.email.trim()) {
newErrors.email = 'Email is required'
} else if (!emailRegex.test(formState.email)) {
newErrors.email = 'Please enter a valid email address'
if (!formState.Email.trim()) {
newErrors.Email = 'Email is required'
} else if (!emailRegex.test(formState.Email)) {
newErrors.Email = 'Please enter a valid email address'
}
const hasErrors = Object.values(newErrors).some(error => error !== '')
setErrors(newErrors)
@ -57,28 +79,8 @@ const Contact = () => {
const handleSubmit = (e) => {
e.preventDefault()
if (!validateForm()) return
handleSubmitForm()
}
const handleSubmitForm = async () => {
setIsSubmitting(true)
try {
await submitLead(formState)
toast.success("Thanks! We'll be in touch shortly.")
setFormState({ company: '', name: '', email: '', phone: '', zip: '', message: '', service_interest: '', recaptcha_token: '', company_website: '' })
setErrors({ company: '', name: '', email: '', zip: '', message: '', recaptcha_token: '' })
} catch (error) {
if (error.response?.status === 409) {
toast.success("We already have your submission! We'll be in touch.")
} else if (error.response?.status === 400 && error.fields) {
setErrors(prev => ({ ...prev, ...error.fields }))
toast.error('Please fix the errors in the form')
} else {
toast.error(error.message || 'Failed to submit form. Please try again.')
}
} finally {
setIsSubmitting(false)
}
formRef.current.submit()
}
const handleChange = (e) => {
@ -221,147 +223,133 @@ const Contact = () => {
<h2 className="text-2xl font-bold text-primary-navy mb-1">Send Us a Message</h2>
<p className="text-soft-text text-sm mb-8">We typically respond within one business day.</p>
<form onSubmit={handleSubmit} noValidate className={`space-y-5 ${isSubmitting ? 'opacity-70 pointer-events-none' : ''}`}>
<form
ref={formRef}
id="contact-form"
name="WebToLeads7130861000000581796"
method="POST"
action="https://crm.zoho.com/crm/WebToLeadForm"
target="zoho_webform_iframe"
acceptCharset="UTF-8"
onSubmit={handleSubmit}
noValidate
className={`space-y-5 ${isSubmitting ? 'opacity-70 pointer-events-none' : ''}`}
>
{/* Zoho required hidden fields */}
<input type="hidden" name="xnQsjsdp" value="b78607b2ef073f134a736184c22aa442ba026b6b00cfdbcb8078d8dee0bb1bbd" />
<input type="hidden" name="zc_gad" id="zc_gad" value="" />
<input type="hidden" name="xmIwtLD" value="e1201f09c921b74ca7844fca8689433ad14277423595fe88de0e4cd6c58e43e743fb001043cb5229e129ff4ab8b2beea" />
<input type="hidden" name="actionType" value="TGVhZHM=" />
<input type="hidden" name="returnURL" value="null" />
{/* Honeypot */}
<input type="text" name="aG9uZXlwb3Q" defaultValue="" tabIndex={-1} autoComplete="off" aria-hidden="true" style={{ display: 'none' }} readOnly />
{/* Company */}
<div>
<label htmlFor="company" className="block text-sm font-medium text-text mb-1.5">
<label htmlFor="Company" className="block text-sm font-medium text-text mb-1.5">
Company Name <span className="text-red-500">*</span>
</label>
<Input
type="text"
id="company"
name="company"
value={formState.company}
id="Company"
name="Company"
value={formState.Company}
onChange={handleChange}
required
placeholder="Your company name"
className={debouncedErrors.company ? 'border-red-500 focus-visible:ring-red-500' : ''}
className={debouncedErrors.Company ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.company && <p className="text-xs text-red-500 mt-1">{debouncedErrors.company}</p>}
{debouncedErrors.Company && <p className="text-xs text-red-500 mt-1">{debouncedErrors.Company}</p>}
</div>
{/* Name + Email */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-text mb-1.5">
<label htmlFor="Last_Name" className="block text-sm font-medium text-text mb-1.5">
Name <span className="text-red-500">*</span>
</label>
<Input
type="text"
id="name"
name="name"
value={formState.name}
id="Last_Name"
name="Last Name"
value={formState['Last Name']}
onChange={handleChange}
required
placeholder="Your full name"
className={debouncedErrors.name ? 'border-red-500 focus-visible:ring-red-500' : ''}
className={debouncedErrors['Last Name'] ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.name && <p className="text-xs text-red-500 mt-1">{debouncedErrors.name}</p>}
{debouncedErrors['Last Name'] && <p className="text-xs text-red-500 mt-1">{debouncedErrors['Last Name']}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-text mb-1.5">
<label htmlFor="Email" className="block text-sm font-medium text-text mb-1.5">
Email <span className="text-red-500">*</span>
</label>
<Input
type="email"
id="email"
name="email"
value={formState.email}
id="Email"
name="Email"
value={formState.Email}
onChange={handleChange}
required
placeholder="you@company.com"
className={debouncedErrors.email ? 'border-red-500 focus-visible:ring-red-500' : ''}
className={debouncedErrors.Email ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.email && <p className="text-xs text-red-500 mt-1">{debouncedErrors.email}</p>}
{debouncedErrors.Email && <p className="text-xs text-red-500 mt-1">{debouncedErrors.Email}</p>}
</div>
</div>
{/* Phone + ZIP */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label htmlFor="phone" className="block text-sm font-medium text-text mb-1.5">
<label htmlFor="Phone" className="block text-sm font-medium text-text mb-1.5">
Phone <span className="text-soft-text font-normal">(optional)</span>
</label>
<Input
type="tel"
id="phone"
name="phone"
value={formState.phone}
id="Phone"
name="Phone"
value={formState.Phone}
onChange={handleChange}
placeholder="(555) 123-4567"
/>
</div>
<div>
<label htmlFor="zip" className="block text-sm font-medium text-text mb-1.5">
<label htmlFor="Zip_Code" className="block text-sm font-medium text-text mb-1.5">
ZIP Code <span className="text-red-500">*</span>
</label>
<Input
type="text"
id="zip"
name="zip"
value={formState.zip}
id="Zip_Code"
name="Zip Code"
value={formState['Zip Code']}
onChange={handleChange}
required
autoComplete="postal-code"
inputMode="numeric"
placeholder="33702"
className={debouncedErrors.zip ? 'border-red-500 focus-visible:ring-red-500' : ''}
className={debouncedErrors['Zip Code'] ? 'border-red-500 focus-visible:ring-red-500' : ''}
/>
{debouncedErrors.zip && <p className="text-xs text-red-500 mt-1">{debouncedErrors.zip}</p>}
{debouncedErrors['Zip Code'] && <p className="text-xs text-red-500 mt-1">{debouncedErrors['Zip Code']}</p>}
</div>
</div>
{/* Service Interest */}
<div>
<label htmlFor="service_interest" className="block text-sm font-medium text-text mb-1.5">
Service Interest <span className="text-soft-text font-normal">(optional)</span>
</label>
<Select
id="service_interest"
name="service_interest"
value={formState.service_interest}
onChange={handleChange}
>
<option value="">Select a service...</option>
<option value="unified-communications">Unified Communications</option>
<option value="contact-center">Contact Center</option>
<option value="managed-support">Managed Support</option>
<option value="consulting-training">Consulting & Training</option>
<option value="infrastructure-cabling">Infrastructure Cabling</option>
<option value="wireless-access">Wireless Access</option>
<option value="local-networking">Local Networking</option>
<option value="other">Other</option>
</Select>
</div>
{/* Message */}
<div>
<label htmlFor="message" className="block text-sm font-medium text-text mb-1.5">
<label htmlFor="Description" className="block text-sm font-medium text-text mb-1.5">
Message <span className="text-red-500">*</span>
</label>
<Textarea
id="message"
name="message"
value={formState.message}
id="Description"
name="Description"
value={formState.Description}
onChange={handleChange}
required
placeholder="Tell us about your needs..."
className={`w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-[#F8FAFC] placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${debouncedErrors.message ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
className={`w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-[#F8FAFC] placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${debouncedErrors.Description ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
rows={5}
/>
{debouncedErrors.message && <p className="text-xs text-red-500 mt-1">{debouncedErrors.message}</p>}
</div>
{/* Honeypot */}
<div className="absolute opacity-0 h-0 overflow-hidden" aria-hidden="true">
<input
type="text"
name="company_website"
tabIndex="-1"
autoComplete="off"
value={formState.company_website}
onChange={handleChange}
/>
{debouncedErrors.Description && <p className="text-xs text-red-500 mt-1">{debouncedErrors.Description}</p>}
</div>
<RecaptchaPlaceholder error={debouncedErrors.recaptcha_token} />
@ -380,6 +368,13 @@ const Contact = () => {
)}
</Button>
</form>
<iframe
name="zoho_webform_iframe"
id="zoho_webform_iframe"
title="Zoho form submission"
style={{ display: 'none' }}
/>
</div>
</div>
</div>