460 lines
14 KiB
JavaScript
460 lines
14 KiB
JavaScript
/**
|
|
* Módulo de gestión del formulario de cotización de vuelos
|
|
* @version 2.0.0
|
|
*/
|
|
|
|
// Variables globales del módulo
|
|
let itiPhoneInput = null;
|
|
let currentSubmitLock = false;
|
|
|
|
/**
|
|
* Inicializa el módulo cuando el DOM está listo
|
|
*/
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
initPhoneInput();
|
|
initFormValidation();
|
|
initShareButtons();
|
|
initScrollAnimations();
|
|
});
|
|
|
|
/**
|
|
* Inicializa el input de teléfono internacional
|
|
*/
|
|
function initPhoneInput() {
|
|
const phoneInput = document.querySelector("#telefono");
|
|
if (phoneInput && typeof intlTelInput !== "undefined") {
|
|
itiPhoneInput = window.intlTelInput(phoneInput, {
|
|
initialCountry: "es",
|
|
preferredCountries: ["es", "mx", "co", "ar", "pe", "cl"],
|
|
separateDialCode: true,
|
|
utilsScript:
|
|
"https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.min.js",
|
|
autoPlaceholder: "aggressive",
|
|
formatOnDisplay: true,
|
|
});
|
|
|
|
// Limpiar error al escribir
|
|
phoneInput.addEventListener("input", () => {
|
|
clearFieldError("telefono");
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inicializa la validación del formulario
|
|
*/
|
|
function initFormValidation() {
|
|
const form = document.getElementById("cotizacionForm");
|
|
if (!form) return;
|
|
|
|
// Agregar listener al formulario
|
|
form.addEventListener("submit", submitForm);
|
|
|
|
// Validación en tiempo real
|
|
const inputs = ["nombre", "email", "fecha"];
|
|
inputs.forEach((fieldId) => {
|
|
const input = document.getElementById(fieldId);
|
|
if (input) {
|
|
input.addEventListener("blur", () => validateField(fieldId));
|
|
input.addEventListener("input", () => {
|
|
clearFieldError(fieldId);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Valida un campo específico
|
|
* @param {string} fieldId - ID del campo a validar
|
|
* @returns {boolean} - True si es válido
|
|
*/
|
|
function validateField(fieldId) {
|
|
const input = document.getElementById(fieldId);
|
|
if (!input) return true;
|
|
|
|
let isValid = true;
|
|
let errorMessage = "";
|
|
|
|
switch (fieldId) {
|
|
case "nombre":
|
|
const nombre = input.value.trim();
|
|
if (!nombre) {
|
|
isValid = false;
|
|
errorMessage = "El nombre es requerido";
|
|
} else if (nombre.length < 3) {
|
|
isValid = false;
|
|
errorMessage = "El nombre debe tener al menos 3 caracteres";
|
|
} else if (!/^[a-zA-ZáéíóúñÁÉÍÓÚÑ\s]+$/.test(nombre)) {
|
|
isValid = false;
|
|
errorMessage = "El nombre solo puede contener letras";
|
|
}
|
|
break;
|
|
|
|
case "email":
|
|
const email = input.value.trim();
|
|
const emailRegex = /^[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,}$/;
|
|
if (!email) {
|
|
isValid = false;
|
|
errorMessage = "El correo electrónico es requerido";
|
|
} else if (!emailRegex.test(email)) {
|
|
isValid = false;
|
|
errorMessage = "Ingresa un correo electrónico válido";
|
|
}
|
|
break;
|
|
|
|
case "fecha":
|
|
const fecha = input.value;
|
|
if (!fecha) {
|
|
isValid = false;
|
|
errorMessage = "La fecha es requerida";
|
|
} else {
|
|
const selectedDate = new Date(fecha);
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
if (selectedDate < today) {
|
|
isValid = false;
|
|
errorMessage = "La fecha no puede ser anterior a hoy";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!isValid) {
|
|
showFieldError(fieldId, errorMessage);
|
|
} else {
|
|
clearFieldError(fieldId);
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Muestra error en un campo
|
|
* @param {string} fieldId - ID del campo
|
|
* @param {string} message - Mensaje de error
|
|
*/
|
|
function showFieldError(fieldId, message) {
|
|
const input = document.getElementById(fieldId);
|
|
const errorDiv = document.getElementById(`${fieldId}-error`);
|
|
|
|
if (input) {
|
|
input.classList.add("error");
|
|
}
|
|
if (errorDiv) {
|
|
errorDiv.textContent = message;
|
|
errorDiv.classList.add("show");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpia el error de un campo
|
|
* @param {string} fieldId - ID del campo
|
|
*/
|
|
function clearFieldError(fieldId) {
|
|
const input = document.getElementById(fieldId);
|
|
const errorDiv = document.getElementById(`${fieldId}-error`);
|
|
|
|
if (input) {
|
|
input.classList.remove("error");
|
|
}
|
|
if (errorDiv) {
|
|
errorDiv.classList.remove("show");
|
|
errorDiv.textContent = "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valida todo el formulario
|
|
* @returns {boolean} - True si el formulario es válido
|
|
*/
|
|
function validateForm() {
|
|
let isValid = true;
|
|
const fields = ["nombre", "email", "fecha"];
|
|
|
|
for (const field of fields) {
|
|
if (!validateField(field)) {
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
// Validar teléfono
|
|
const telefono = getFullPhoneNumber();
|
|
if (!telefono || telefono.trim() === "") {
|
|
showFieldError("telefono", "El número de teléfono es requerido");
|
|
isValid = false;
|
|
} else if (telefono.length < 8) {
|
|
showFieldError("telefono", "Ingresa un número de teléfono válido");
|
|
isValid = false;
|
|
} else {
|
|
clearFieldError("telefono");
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Obtiene el número de teléfono completo con código de país
|
|
* @returns {string} - Número completo
|
|
*/
|
|
function getFullPhoneNumber() {
|
|
if (itiPhoneInput) {
|
|
const number = itiPhoneInput.getNumber();
|
|
return number || "";
|
|
}
|
|
const phoneInput = document.getElementById("telefono");
|
|
return phoneInput ? phoneInput.value.trim() : "";
|
|
}
|
|
|
|
/**
|
|
* Maneja el envío del formulario
|
|
* @param {Event} event - Evento del formulario
|
|
*/
|
|
async function submitForm(event) {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
// Evitar envíos múltiples
|
|
if (currentSubmitLock) {
|
|
return;
|
|
}
|
|
|
|
// Validar formulario
|
|
if (!validateForm()) {
|
|
const firstError = document.querySelector(".form-control.error");
|
|
if (firstError) {
|
|
firstError.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
});
|
|
firstError.focus();
|
|
}
|
|
return;
|
|
}
|
|
|
|
currentSubmitLock = true;
|
|
const form = document.getElementById("cotizacionForm");
|
|
const submitBtn = document.getElementById("submitCotizacion");
|
|
|
|
// Deshabilitar botón y mostrar loading
|
|
const originalText = submitBtn.innerHTML;
|
|
submitBtn.disabled = true;
|
|
submitBtn.classList.add("btn-loading");
|
|
submitBtn.innerHTML = "<span>Enviando...</span>";
|
|
|
|
try {
|
|
const telefonoCompleto = getFullPhoneNumber();
|
|
|
|
const formData = {
|
|
_token: document.querySelector('input[name="_token"]').value,
|
|
id_vuelo: document.querySelector('input[name="id_vuelo"]').value,
|
|
nombre_vuelo: document.querySelector('input[name="nombre_vuelo"]')
|
|
.value,
|
|
slug: document.querySelector('input[name="slug"]').value,
|
|
nombre: document.getElementById("nombre").value.trim(),
|
|
email: document.getElementById("email").value.trim(),
|
|
telefono: telefonoCompleto,
|
|
pasajeros: document.getElementById("pasajeros").value,
|
|
fecha: document.getElementById("fecha").value,
|
|
};
|
|
|
|
const slug = formData.slug;
|
|
const url = `/cotizar-vuelo/${slug}`;
|
|
|
|
const response = await axios.post(url, formData, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-CSRF-TOKEN": formData._token,
|
|
},
|
|
timeout: 30000, // 30 segundos
|
|
});
|
|
|
|
if (response.status === 200) {
|
|
let mensaje =
|
|
"Pronto nos pondremos en contacto contigo para confirmar tu cotización.";
|
|
if (response.data && response.data.mensaje) {
|
|
mensaje = response.data.mensaje;
|
|
} else if (response.data && response.data.message) {
|
|
mensaje = response.data.message;
|
|
}
|
|
|
|
await Swal.fire({
|
|
icon: "success",
|
|
title: "¡Cotización enviada!",
|
|
text: mensaje,
|
|
confirmButtonColor: "#2563eb",
|
|
confirmButtonText: "Aceptar",
|
|
timer: 5000,
|
|
timerProgressBar: true,
|
|
showClass: {
|
|
popup: "animate__animated animate__fadeInDown",
|
|
},
|
|
hideClass: {
|
|
popup: "animate__animated animate__fadeOutUp",
|
|
},
|
|
});
|
|
|
|
// Resetear formulario
|
|
form.reset();
|
|
if (itiPhoneInput) {
|
|
itiPhoneInput.setNumber("");
|
|
}
|
|
const fechaInput = document.getElementById("fecha");
|
|
if (fechaInput && fechaInput._flatpickr) {
|
|
fechaInput._flatpickr.clear();
|
|
}
|
|
|
|
// Limpiar errores
|
|
["nombre", "email", "fecha", "telefono"].forEach(clearFieldError);
|
|
|
|
// Limpiar validación visual
|
|
document.querySelectorAll(".form-control").forEach((input) => {
|
|
input.classList.remove("error");
|
|
});
|
|
} else {
|
|
throw new Error(
|
|
response.data?.message || "Error en la respuesta del servidor",
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error en el envío:", error);
|
|
|
|
let errorMessage =
|
|
"Ocurrió un error al enviar la cotización. Por favor intenta nuevamente.";
|
|
|
|
if (error.response) {
|
|
console.log("Respuesta de error:", error.response.data);
|
|
if (error.response.data && error.response.data.message) {
|
|
errorMessage = error.response.data.message;
|
|
} else if (error.response.data && error.response.data.error) {
|
|
errorMessage = error.response.data.error;
|
|
} else if (typeof error.response.data === "string") {
|
|
errorMessage = error.response.data;
|
|
} else if (error.response.status === 422) {
|
|
errorMessage = "Por favor verifica los datos ingresados";
|
|
} else if (error.response.status === 429) {
|
|
errorMessage =
|
|
"Demasiadas solicitudes. Por favor espera un momento.";
|
|
} else if (error.response.status >= 500) {
|
|
errorMessage =
|
|
"Error en el servidor. Por favor intenta más tarde.";
|
|
}
|
|
} else if (error.request) {
|
|
errorMessage =
|
|
"No se pudo conectar con el servidor. Verifica tu conexión a internet.";
|
|
} else if (error.message) {
|
|
errorMessage = error.message;
|
|
}
|
|
|
|
await Swal.fire({
|
|
icon: "error",
|
|
title: "Error",
|
|
text: errorMessage,
|
|
confirmButtonColor: "#2563eb",
|
|
confirmButtonText: "Intentar de nuevo",
|
|
});
|
|
} finally {
|
|
currentSubmitLock = false;
|
|
submitBtn.disabled = false;
|
|
submitBtn.classList.remove("btn-loading");
|
|
submitBtn.innerHTML = originalText;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inicializa los botones de compartir
|
|
*/
|
|
function initShareButtons() {
|
|
const shareButtons = document.querySelectorAll(".share-btn");
|
|
const currentUrl = encodeURIComponent(window.location.href);
|
|
const currentTitle = encodeURIComponent(document.title);
|
|
|
|
shareButtons.forEach((btn) => {
|
|
btn.addEventListener("click", async () => {
|
|
const type = btn.dataset.share;
|
|
let shareUrl = "";
|
|
|
|
switch (type) {
|
|
case "facebook":
|
|
shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${currentUrl}`;
|
|
window.open(shareUrl, "_blank", "width=600,height=400");
|
|
break;
|
|
case "twitter":
|
|
shareUrl = `https://twitter.com/intent/tweet?text=${currentTitle}&url=${currentUrl}`;
|
|
window.open(shareUrl, "_blank", "width=600,height=400");
|
|
break;
|
|
case "whatsapp":
|
|
shareUrl = `https://wa.me/?text=${currentTitle}%20${currentUrl}`;
|
|
window.open(shareUrl, "_blank");
|
|
break;
|
|
case "copy":
|
|
try {
|
|
await navigator.clipboard.writeText(
|
|
window.location.href,
|
|
);
|
|
Swal.fire({
|
|
icon: "success",
|
|
title: "¡Enlace copiado!",
|
|
text: "El enlace ha sido copiado al portapapeles",
|
|
toast: true,
|
|
position: "top-end",
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
timerProgressBar: true,
|
|
});
|
|
} catch (err) {
|
|
Swal.fire({
|
|
icon: "error",
|
|
title: "Error",
|
|
text: "No se pudo copiar el enlace",
|
|
toast: true,
|
|
position: "top-end",
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Inicializa animaciones al hacer scroll
|
|
*/
|
|
function initScrollAnimations() {
|
|
const elements = document.querySelectorAll(".section, .sidebar-card");
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.style.opacity = "1";
|
|
entry.target.style.transform = "translateY(0)";
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
},
|
|
{
|
|
threshold: 0.1,
|
|
rootMargin: "0px 0px -50px 0px",
|
|
},
|
|
);
|
|
|
|
elements.forEach((element) => {
|
|
element.style.opacity = "0";
|
|
element.style.transform = "translateY(20px)";
|
|
element.style.transition = "all 0.6s ease-out";
|
|
observer.observe(element);
|
|
});
|
|
}
|
|
|
|
// Exportar funciones para debugging (opcional)
|
|
if (typeof window !== "undefined") {
|
|
window.FlightDetails = {
|
|
validateForm,
|
|
submitForm,
|
|
getFullPhoneNumber,
|
|
};
|
|
}
|