diff --git a/server/index.js b/server/index.js index f2aa5b9..63da28f 100644 --- a/server/index.js +++ b/server/index.js @@ -255,7 +255,7 @@ const leadSchema = z.object({ name: z.string().min(1, 'Name is required').trim().max(100, 'Name must be 100 characters or less'), email: z.string().email('Valid email is required').trim().max(254, 'Email must be 254 characters or less'), phone: z.string().trim().max(50, 'Phone must be 50 characters or less').optional().or(z.literal('').transform(() => undefined)), - zip: z.string().trim().max(10, 'ZIP code must be 10 characters or less').optional().or(z.literal('').transform(() => undefined)), + zip: z.string({ required_error: 'ZIP code is required' }).trim().min(1, 'ZIP code is required').max(10, 'ZIP code must be 10 characters or less'), message: z.string().trim().max(5000, 'Message must be 5000 characters or less').optional().or(z.literal('').transform(() => undefined)), service_interest: z.string().trim().max(50, 'Service interest must be 50 characters or less').optional().or(z.literal('').transform(() => undefined)), company_website: z.string().optional(), // Honeypot field - bots fill this, humans don't see it diff --git a/src/lib/api.js b/src/lib/api.js index 7a4cf75..d996c74 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -10,6 +10,7 @@ export async function submitLead(data) { const errorData = await response.json().catch(() => ({})) const error = new Error(errorData.error || `API error: ${response.status}`) error.response = { status: response.status } + error.fields = errorData.fields throw error } return response.json() diff --git a/src/pages/Contact.jsx b/src/pages/Contact.jsx index b6a1f66..c2afd23 100644 --- a/src/pages/Contact.jsx +++ b/src/pages/Contact.jsx @@ -23,15 +23,17 @@ const Contact = () => { company: '', name: '', email: '', + zip: '', message: '', }) const debouncedErrors = useDebounce(errors, 300) const [isSubmitting, setIsSubmitting] = useState(false) const validateForm = () => { - const newErrors = { company: '', name: '', email: '', message: '' } + const newErrors = { company: '', name: '', email: '', zip: '', message: '' } 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 emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!formState.email.trim()) { @@ -59,11 +61,14 @@ const Contact = () => { try { await submitLead(formState) toast.success("Thanks! We'll be in touch shortly.") - setFormState({ company: '', name: '', email: '', phone: '', zip: '', message: '', service_interest: '' }) - setErrors({ company: '', name: '', email: '', message: '' }) + setFormState({ company: '', name: '', email: '', phone: '', zip: '', message: '', service_interest: '', company_website: '' }) + setErrors({ company: '', name: '', email: '', zip: '', message: '' }) } 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.') } @@ -247,7 +252,7 @@ const Contact = () => { - {/* Phone + Service Interest */} + {/* Phone + ZIP */}
-
+ {/* Service Interest */} +
+ + +
+ {/* Message */}