From 774ffcb9c4d4071270ae5de5c64ba71331d7f9b7 Mon Sep 17 00:00:00 2001 From: Ben Poon Date: Sun, 11 Jan 2026 11:59:22 -0600 Subject: [PATCH 1/6] Add two-column contact section with email form Transform CTA component to offer two contact methods side-by-side: - Left: Calendly scheduling card with benefit bullets - Right: Email contact form with validation Features: - Client-side form validation (name, email, message) - Web3Forms integration for email submissions - Equal-height cards with aligned CTA buttons - Benefit bullets on Calendly card for visual balance - Email fallback link below form submit button - Hover animations (lift + shadow) - Full accessibility support (ARIA labels, keyboard nav) - Responsive: side-by-side on desktop, stacked on mobile Co-Authored-By: Claude Sonnet 4.5 --- components/CTA.tsx | 273 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 252 insertions(+), 21 deletions(-) diff --git a/components/CTA.tsx b/components/CTA.tsx index 46dff0d..6406e0b 100644 --- a/components/CTA.tsx +++ b/components/CTA.tsx @@ -1,3 +1,7 @@ +'use client' + +import { useState } from 'react' + interface CTAProps { headline?: string subheadline?: string @@ -5,12 +9,88 @@ interface CTAProps { gradientTo?: string } -export default function CTA({ +export default function CTA({ headline = "Let's figure out what you actually need.", - subheadline = "Book a free 20-minute call. No pitch, no pressure. Just an honest conversation about what's not working and how to fix it.", + subheadline = "Book a free 20-minute call or send me a message. No pitch, no pressure. Just an honest conversation about what's not working and how to fix it.", gradientFrom = "coral/5", gradientTo = "mustard/10" }: CTAProps) { + // Form state management + const [formData, setFormData] = useState({ name: '', email: '', message: '' }) + const [errors, setErrors] = useState>({}) + const [isSubmitting, setIsSubmitting] = useState(false) + const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle') + + // Handle input changes + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setFormData(prev => ({ ...prev, [name]: value })) + // Clear error for this field when user starts typing + if (errors[name]) { + setErrors(prev => { + const newErrors = { ...prev } + delete newErrors[name] + return newErrors + }) + } + } + + // Validate form + const validateForm = () => { + const newErrors: Record = {} + + if (!formData.name.trim() || formData.name.length < 2) { + newErrors.name = 'Name must be at least 2 characters' + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + if (!formData.email.trim() || !emailRegex.test(formData.email)) { + newErrors.email = 'Please enter a valid email address' + } + + if (!formData.message.trim() || formData.message.length < 10) { + newErrors.message = 'Message must be at least 10 characters' + } + + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + // Handle form submission + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!validateForm()) return + + setIsSubmitting(true) + setSubmitStatus('idle') + + try { + const response = await fetch('https://api.web3forms.com/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + access_key: 'YOUR_WEB3FORMS_ACCESS_KEY', // TODO: Replace with actual key from web3forms.com + name: formData.name, + email: formData.email, + message: formData.message, + subject: `New contact form submission from ${formData.name}` + }) + }) + + if (response.ok) { + setSubmitStatus('success') + setFormData({ name: '', email: '', message: '' }) + } else { + setSubmitStatus('error') + } + } catch (error) { + setSubmitStatus('error') + } finally { + setIsSubmitting(false) + } + } + return (
{/* Decorative elements */} @@ -18,7 +98,7 @@ export default function CTA({ {/* Large gradient blobs */}
- + {/* SVG decorative circles */} @@ -32,7 +112,7 @@ export default function CTA({ - + {/* Dotted pattern */}
{[...Array(10)].map((_, i) => ( @@ -46,24 +126,175 @@ export default function CTA({
- -
+ +
+ {/* Headline section */}

{headline}

-

{subheadline}

- - Book Your Free Call → - -

- Or email me directly at{' '} - - ben@goodrobotco.com - -

+

{subheadline}

+ + {/* Two-column grid */} +
+ + {/* Left Column: Calendly Card */} +
+

Schedule a Call

+

+ Book a free 20-minute conversation. No pitch, no pressure—just an honest discussion about your needs. +

+ + {/* Benefit bullets */} +
+

What to expect:

+
    +
  • + + Quick discussion about your specific needs +
  • +
  • + + Honest assessment of what will actually work +
  • +
  • + + Zero pressure or commitment required +
  • +
  • + + Clear next steps if we're a good fit +
  • +
+
+ +
+ + Book Your Free Call → + +

+ 20 minutes • No commitment +

+
+
+ + {/* Right Column: Contact Form Card */} +
+

Send a Message

+

+ Prefer email? Send me a message and I'll respond within 24 hours. +

+ +
+
+ {/* Name field */} +
+ + + {errors.name && ( +

{errors.name}

+ )} +
+ + {/* Email field */} +
+ + + {errors.email && ( +

{errors.email}

+ )} +
+ + {/* Message field */} +
+ +