import { useState, useEffect, useRef } from "react"; import { motion } from "framer-motion"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { WEBHOOK_URL, WEBHOOK_SECRET } from "@/data/event-data"; import { CheckCircle, Loader2, AlertCircle, Search } from "lucide-react"; /** * FORMULARIO DE RESERVA * * Envía un POST al webhook de n8n con los datos del formulario. * Para configurar: * 1. En n8n, crea un workflow con nodo "Webhook" (POST) * 2. Pega la URL generada en WEBHOOK_URL (event-data.ts) * 3. Conecta el webhook a Google Sheets / Airtable / Email */ // Define pass types with prices const PASS_TYPES = [ { id: "full", name: "Full Pass", price: 150 }, { id: "party", name: "Party Pass", price: 80 }, { id: "single", name: "Single Day Pass", price: 40 }, ]; const bookingSchema = z.object({ requestId: z.string().optional(), name: z.string().trim().min(2, "El nombre debe tener al menos 2 caracteres").max(100), surname: z.string().trim().min(2, "El apellido debe tener al menos 2 caracteres").max(100), email: z.string().trim().email("Email no válido").max(255), passType: z.string().min(1, "Selecciona un tipo de pass"), amount: z.string().min(1, "Selecciona la cantidad"), price: z.number().optional(), country: z.string().trim().min(2, "Indica tu país").max(100), }); type BookingData = z.infer; const BookingSection = () => { const [form, setForm] = useState({ requestId: "", name: "", surname: "", email: "", passType: "", amount: "1", price: 0, country: "", }); const [errors, setErrors] = useState>>({}); const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); const [countrySearch, setCountrySearch] = useState(""); const [showCountryDropdown, setShowCountryDropdown] = useState(false); const countryDropdownRef = useRef(null); const countryInputRef = useRef(null); const sectionRef = useRef(null); // Generate unique requestId on component mount useEffect(() => { const uniqueId = `REQ-${Date.now()}-${Math.floor(Math.random() * 10000)}`; setForm(prev => ({ ...prev, requestId: uniqueId })); }, []); const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; // Calculate price when passType or amount changes if (name === "passType" || name === "amount") { // Update the form state first setForm((prev) => ({ ...prev, [name]: value })); // Get the updated form values const updatedForm = { ...form, [name]: value }; // Calculate new price if both passType and amount are set if ((updatedForm.passType || name === "passType") && (updatedForm.amount || name === "amount")) { const selectedPass = PASS_TYPES.find(pass => pass.id === (name === "passType" ? value : updatedForm.passType)); if (selectedPass) { const amountValue = name === "amount" ? parseInt(value) : parseInt(updatedForm.amount); const newPrice = selectedPass.price * amountValue; // Update the form with the calculated price setForm((prev) => ({ ...prev, [name]: value, price: newPrice })); } } } else { setForm((prev) => ({ ...prev, [name]: value })); } setErrors((prev) => ({ ...prev, [name]: undefined })); }; const handleCountrySelect = (country: string) => { setForm((prev) => ({ ...prev, country })); setCountrySearch(country); setShowCountryDropdown(false); setErrors((prev) => ({ ...prev, country: undefined })); }; const filteredCountries = COUNTRIES.filter(country => country.toLowerCase().includes(countrySearch.toLowerCase()) ).slice(0, 10); // Limit to 10 results for performance // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (showCountryDropdown && countryDropdownRef.current && countryInputRef.current) { const target = e.target as Node; if (!countryDropdownRef.current.contains(target) && !countryInputRef.current.contains(target)) { setShowCountryDropdown(false); } } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [showCountryDropdown]); // Ensure the booking section stays in view when status changes (e.g., after submit) useEffect(() => { if (status === "success" || status === "error") { sectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); } }, [status]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setErrors({}); const result = bookingSchema.safeParse(form); if (!result.success) { const fieldErrors: typeof errors = {}; result.error.errors.forEach((err) => { const field = err.path[0] as keyof BookingData; fieldErrors[field] = err.message; }); setErrors(fieldErrors); return; } setStatus("loading"); try { console.log("[Booking] Sending to:", WEBHOOK_URL); const res = await fetch(WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-Webhook-Secret": WEBHOOK_SECRET, }, body: JSON.stringify(result.data), }); console.log("[Booking] Response status:", res.status); if (!res.ok) throw new Error(`HTTP ${res.status}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); setStatus("success"); } catch (err) { console.error("[Booking] Error:", err); setStatus("error"); } }; if (status === "success") { return (

¡Reserva recibida!

Recibirás confirmación por email. ¡Nos vemos en la pista!

); } return (

Reserva tu pase

Selecciona tu pase y cantidad. Sin pago online, paga en el evento.

{/* Request ID - Hidden field for internal tracking */}
{/* Nombre */}
{errors.name &&

{errors.name}

}
{/* Apellido */}
{errors.surname &&

{errors.surname}

}
{/* Email */}
{errors.email &&

{errors.email}

}
{/* País - Searchable Selector */}
{ setCountrySearch(e.target.value); setShowCountryDropdown(true); }} onFocus={() => setShowCountryDropdown(true)} placeholder="Buscar país..." className="w-full rounded-lg border border-input bg-background px-4 py-3 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring pr-10" />
{showCountryDropdown && ( {filteredCountries.length > 0 ? ( filteredCountries.map((country) => (
handleCountrySelect(country)} className="px-4 py-2 text-sm text-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer" > {country}
)) ) : (
No se encontraron países
)}
)} {errors.country &&

{errors.country}

}
{/* Tipo de Pass - Cards */}
{PASS_TYPES.map((pass) => ( handleChange({ target: { name: "passType", value: pass.id } } as any)} >

{pass.name}

{pass.price}€

))}
{errors.passType &&

{errors.passType}

}
{/* Cantidad - Add/Subtract Buttons */}
{ if (!form.passType) return; const currentAmount = parseInt(form.amount); if (currentAmount > 1) { handleChange({ target: { name: "amount", value: String(currentAmount - 1) } } as any); } }} className={`w-12 h-12 rounded-full flex items-center justify-center text-xl font-bold transition-colors ${ form.passType ? "bg-secondary hover:bg-secondary/80 text-foreground cursor-pointer" : "bg-muted text-muted-foreground cursor-not-allowed" }`} disabled={!form.passType || parseInt(form.amount) <= 1} > −
{form.amount}
{ if (!form.passType) return; const currentAmount = parseInt(form.amount); if (currentAmount < 10) { handleChange({ target: { name: "amount", value: String(currentAmount + 1) } } as any); } }} className={`w-12 h-12 rounded-full flex items-center justify-center text-xl font-bold transition-colors ${ form.passType ? "bg-secondary hover:bg-secondary/80 text-foreground cursor-pointer" : "bg-muted text-muted-foreground cursor-not-allowed" }`} disabled={!form.passType || parseInt(form.amount) >= 10} > +
{errors.amount &&

{errors.amount}

}
{/* Precio Total - Beautiful bottom display */} {form.price > 0 && (

Precio Total

{form.price.toFixed(2)}€

Sin pago online • Paga en el evento

)} {status === "error" && (
Error al enviar. Inténtalo de nuevo.
)}
); }; export default BookingSection; // Country data for searchable selector const COUNTRIES = [ "Afganistán", "Albania", "Alemania", "Andorra", "Angola", "Antigua y Barbuda", "Arabia Saudita", "Argelia", "Argentina", "Armenia", "Australia", "Austria", "Azerbaiyán", "Bahamas", "Bangladés", "Barbados", "Baréin", "Bélgica", "Belice", "Benín", "Bielorrusia", "Birmania", "Bolivia", "Bosnia y Herzegovina", "Botsuana", "Brasil", "Brunéi", "Bulgaria", "Burkina Faso", "Burundi", "Bután", "Cabo Verde", "Camboya", "Camerún", "Canadá", "Catar", "Chad", "Chile", "China", "Chipre", "Ciudad del Vaticano", "Colombia", "Comoras", "Corea del Norte", "Corea del Sur", "Costa de Marfil", "Costa Rica", "Croacia", "Cuba", "Dinamarca", "Dominica", "Ecuador", "Egipto", "El Salvador", "Emiratos Árabes Unidos", "Eritrea", "Eslovaquia", "Eslovenia", "España", "Estados Unidos", "Estonia", "Etiopía", "Filipinas", "Finlandia", "Fiyi", "Francia", "Gabón", "Gambia", "Georgia", "Ghana", "Granada", "Grecia", "Guatemala", "Guyana", "Guinea", "Guinea-Bisáu", "Guinea Ecuatorial", "Haití", "Honduras", "Hungría", "India", "Indonesia", "Irak", "Irán", "Irlanda", "Islandia", "Islas Marshall", "Islas Salomón", "Israel", "Italia", "Jamaica", "Japón", "Jordania", "Kazajistán", "Kenia", "Kirguistán", "Kiribati", "Kuwait", "Laos", "Lesoto", "Letonia", "Líbano", "Liberia", "Libia", "Liechtenstein", "Lituania", "Luxemburgo", "Madagascar", "Malasia", "Malaui", "Maldivas", "Malí", "Malta", "Marruecos", "Mauricio", "Mauritania", "México", "Micronesia", "Moldavia", "Mónaco", "Mongolia", "Montenegro", "Mozambique", "Namibia", "Nauru", "Nepal", "Nicaragua", "Níger", "Nigeria", "Noruega", "Nueva Zelanda", "Omán", "Países Bajos", "Pakistán", "Palaos", "Panamá", "Papúa Nueva Guinea", "Paraguay", "Perú", "Polonia", "Portugal", "Reino Unido", "República Centroafricana", "República Checa", "República del Congo", "República Democrática del Congo", "República Dominicana", "Ruanda", "Rumanía", "Rusia", "Samoa", "San Cristóbal y Nieves", "San Marino", "San Vicente y las Granadinas", "Santa Lucía", "Santo Tomé y Príncipe", "Senegal", "Serbia", "Seychelles", "Sierra Leona", "Singapur", "Siria", "Somalia", "Sri Lanka", "Suazilandia", "Sudáfrica", "Sudán", "Sudán del Sur", "Suecia", "Suiza", "Surinam", "Tailandia", "Tanzania", "Tayikistán", "Timor Oriental", "Togo", "Tonga", "Trinidad y Tobago", "Túnez", "Turkmenistán", "Turquía", "Tuvalu", "Ucrania", "Uganda", "Uruguay", "Uzbekistán", "Vanuatu", "Venezuela", "Vietnam", "Yemen", "Yibuti", "Zambia", "Zimbabue" ];