feat: Add Formbricks integration, update forms with webhooks, enhance navigation

- Integrate @formbricks/js for future surveys (FormbricksProvider)
- Add WebhookForm component for unified form submission (contact/franchise/membership)
- Update contact form with reason dropdown field
- Update franchise form with new fields: estado, ciudad, socios checkbox
- Update franchise benefits: manuals, training platform, RH system, investment $100k
- Add Contacto link to desktop/mobile nav and footer
- Update membership form to use WebhookForm with membership_id select
- Update hero buttons to use #3E352E color consistently
- Refactor contact/franchise pages to use new hero layout and components
- Add webhook utility (lib/webhook.ts) for parallel submission to test+prod
- Add email receipt hooks to booking endpoints
- Update globals.css with new color variables and navigation styles
- Docker configuration for deployment
This commit is contained in:
Marco Gallegos
2026-01-17 22:54:20 -06:00
parent b7d6e51d67
commit 66e20d25a7
60 changed files with 4534 additions and 791 deletions

171
components/webhook-form.tsx Normal file
View File

@@ -0,0 +1,171 @@
'use client'
import { useState } from 'react'
import { CheckCircle } from 'lucide-react'
import { getDeviceType, sendWebhookPayload } from '@/lib/webhook'
interface WebhookFormProps {
formType: 'contact' | 'franchise' | 'membership'
title: string
successMessage?: string
successSubtext?: string
submitButtonText?: string
fields: {
name: string
label: string
type: 'text' | 'email' | 'tel' | 'textarea' | 'select'
required?: boolean
placeholder?: string
options?: { value: string; label: string }[]
rows?: number
}[]
additionalData?: Record<string, string>
}
export function WebhookForm({
formType,
title,
successMessage = 'Mensaje Enviado',
successSubtext = 'Gracias por contactarnos. Te responderemos lo antes posible.',
submitButtonText = 'Enviar',
fields,
additionalData
}: WebhookFormProps) {
const [isSubmitting, setIsSubmitting] = useState(false)
const [submitted, setSubmitted] = useState(false)
const [showThankYou, setShowThankYou] = useState(false)
const [submitError, setSubmitError] = useState<string | null>(null)
const formData = fields.reduce(
(acc, field) => ({ ...acc, [field.name]: '' }),
{} as Record<string, string>
)
const [values, setValues] = useState(formData)
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
setValues((prev) => ({
...prev,
[e.target.name]: e.target.value
}))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSubmitting(true)
setSubmitError(null)
const payload = {
form: formType,
...values,
timestamp_utc: new Date().toISOString(),
device_type: getDeviceType(),
...additionalData
}
try {
await sendWebhookPayload(payload)
setSubmitted(true)
setShowThankYou(true)
window.setTimeout(() => setShowThankYou(false), 3500)
setValues(formData)
} catch (error) {
setSubmitError('No pudimos enviar tu solicitud. Intenta de nuevo.')
} finally {
setIsSubmitting(false)
}
}
return (
<>
{showThankYou && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white rounded-2xl p-8 max-w-md w-full text-center shadow-2xl animate-in fade-in zoom-in duration-300">
<CheckCircle className="w-16 h-16 text-green-600 mx-auto mb-4" />
<h3 className="text-2xl font-bold mb-2">¡Gracias!</h3>
<p className="text-gray-600">{successSubtext}</p>
</div>
</div>
)}
{submitted ? (
<div className="p-8 bg-green-50 border border-green-200 rounded-xl text-center">
<CheckCircle className="w-12 h-12 text-green-900 mb-4 mx-auto" />
<h4 className="text-xl font-semibold text-green-900 mb-2">
{successMessage}
</h4>
<p className="text-green-800">
{successSubtext}
</p>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-6 p-8 bg-white rounded-2xl shadow-lg border border-gray-100">
<div className="grid md:grid-cols-2 gap-6">
{fields.map((field) => (
<div key={field.name} className={field.type === 'textarea' ? 'md:col-span-2' : ''}>
<label htmlFor={field.name} className="block text-sm font-medium text-gray-700 mb-2">
{field.label}
</label>
{field.type === 'textarea' ? (
<textarea
id={field.name}
name={field.name}
value={values[field.name]}
onChange={handleChange}
required={field.required}
rows={field.rows || 4}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-900 focus:border-transparent resize-none"
placeholder={field.placeholder}
/>
) : field.type === 'select' ? (
<select
id={field.name}
name={field.name}
value={values[field.name]}
onChange={handleChange}
required={field.required}
className="w-full px-4 py-3 border border-gray-300 rounded-lg bg-white focus:ring-2 focus:ring-gray-900 focus:border-transparent"
>
<option value="">{field.placeholder || 'Selecciona una opción'}</option>
{field.options?.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : (
<input
type={field.type}
id={field.name}
name={field.name}
value={values[field.name]}
onChange={handleChange}
required={field.required}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-900 focus:border-transparent"
placeholder={field.placeholder}
/>
)}
</div>
))}
</div>
{submitError && (
<p className="text-sm text-red-600 text-center">
{submitError}
</p>
)}
<button
type="submit"
className="bg-[#3E352E] text-white hover:bg-[#3E352E]/90 px-8 py-3 rounded-lg font-semibold shadow-md hover:shadow-lg transition-all duration-300 inline-flex items-center justify-center w-full"
disabled={isSubmitting}
>
{isSubmitting ? 'Enviando...' : submitButtonText}
</button>
</form>
)}
</>
)
}