1/** 2 * SIEMPRE Contact Form Modal 3 * Handles modal display, form validation, and submission 4 */ 5 6const ContactModal = (function () { 7 'use strict'; 8 9 let modal = null; 10 let overlay = null; 11 let closeBtn = null; 12 let form = null; 13 let submitBtn = null; 14 15 /** 16 * Initialize the modal 17 */ 18 function init() { 19 modal = document.getElementById('contact-modal'); 20 if (!modal) { 21 console.error('Contact modal not found'); 22 return; 23 } 24 25 overlay = modal.querySelector('.modal-overlay'); 26 closeBtn = modal.querySelector('.modal-close'); 27 form = modal.querySelector('#contact-form'); 28 submitBtn = modal.querySelector('.submit-btn'); 29 30 // Attach event listeners 31 attachEventListeners(); 32 } 33 34 /** 35 * Attach all event listeners 36 */ 37 function attachEventListeners() { 38 // Close button 39 if (closeBtn) { 40 closeBtn.addEventListener('click', close); 41 } 42 43 // Overlay click 44 if (overlay) { 45 overlay.addEventListener('click', close); 46 } 47 48 // ESC key 49 document.addEventListener('keydown', (e) => { 50 if (e.key === 'Escape' && !modal.classList.contains('hidden')) { 51 close(); 52 } 53 }); 54 55 // Form submission 56 if (form) { 57 form.addEventListener('submit', handleSubmit); 58 } 59 60 // Attach to all trigger buttons 61 attachTriggerButtons(); 62 } 63 64 /** 65 * Attach click handlers to all contact trigger buttons 66 */ 67 function attachTriggerButtons() { 68 const triggers = document.querySelectorAll('[data-contact-trigger]'); 69 triggers.forEach(btn => { 70 btn.addEventListener('click', (e) => { 71 e.preventDefault(); 72 const queryType = btn.getAttribute('data-query-type') || ''; 73 open(queryType); 74 }); 75 }); 76 } 77 78 /** 79 * Open the modal with fade-in animation 80 */ 81 function open(queryType = '') { 82 if (!modal) return; 83 84 // Set query type if provided 85 if (queryType) { 86 const select = form.querySelector('#query-type'); 87 if (select) { 88 select.value = queryType; 89 } 90 } 91 92 // Show modal 93 modal.classList.remove('hidden'); 94 95 // Trigger animation 96 setTimeout(() => { 97 modal.classList.add('modal-active'); 98 }, 10); 99 100 // Prevent body scroll 101 document.body.style.overflow = 'hidden'; 102 } 103 104 /** 105 * Close the modal with fade-out animation 106 */ 107 function close() { 108 if (!modal) return; 109 110 // Trigger fade-out 111 modal.classList.remove('modal-active'); 112 113 // Hide after animation 114 setTimeout(() => { 115 modal.classList.add('hidden'); 116 document.body.style.overflow = ''; 117 resetForm(); 118 }, 300); 119 } 120 121 /** 122 * Reset the form 123 */ 124 function resetForm() { 125 if (form) { 126 form.reset(); 127 hideMessage(); 128 } 129 } 130 131 /** 132 * Handle form submission 133 */ 134 async function handleSubmit(e) { 135 e.preventDefault(); 136 137 // Validate form 138 if (!validateForm()) { 139 return; 140 } 141 142 // Get form data 143 const formData = new FormData(form); 144 145 // Show loading state 146 setLoading(true); 147 148 try { 149 // Submit to backend 150 const response = await fetch('backend/contact-handler.php', { 151 method: 'POST', 152 body: formData 153 }); 154 155 const result = await response.json(); 156 157 if (result.success) { 158 showSuccess(); 159 } else { 160 showError(result.message || 'Error al enviar el mensaje'); 161 } 162 } catch (error) { 163 console.error('Error:', error); 164 showError('Error de conexión. Por favor, intentá nuevamente.'); 165 } finally { 166 setLoading(false); 167 } 168 } 169 170 /** 171 * Validate form fields 172 */ 173 function validateForm() { 174 const nombre = form.querySelector('#nombre').value.trim(); 175 const telefono = form.querySelector('#telefono').value.trim(); 176 const queryType = form.querySelector('#query-type').value; 177 const email = form.querySelector('#email').value.trim(); 178 const comentarios = form.querySelector('#comentarios').value.trim(); 179 180 if (!nombre) { 181 showError('Por favor, ingresá tu nombre'); 182 return false;
183 } 184 185 if (!telefono) { 186 showError('Por favor, ingresá tu teléfono'); 187 return false; 188 } 189 190 if (!email) { 191 showError('Por favor, ingresá tu email'); 192 return false; 193 } 194 195 if (!queryType) { 196 showError('Por favor, seleccioná el tipo de consulta'); 197 return false; 198 } 199 200 if (!comentarios) { 201 showError('Por favor, ingresá tus comentarios'); 202 return false; 203 } 204 205 return true; 206 } 207 208 /** 209 * Set loading state 210 */ 211 function setLoading(loading) { 212 if (!submitBtn) return; 213 214 if (loading) { 215 submitBtn.disabled = true; 216 submitBtn.innerHTML = ` 217 <svg class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24"> 218 <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> 219 <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> 220 </svg> 221 <span>Enviando...</span> 222 `; 223 } else { 224 submitBtn.disabled = false; 225 submitBtn.innerHTML = ` 226 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 227 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path> 228 </svg> 229 <span>Enviar Consulta</span> 230 `; 231 } 232 } 233 234 /** 235 * Show error message 236 */ 237 function showError(message) { 238 const msgEl = modal.querySelector('.form-message'); 239 if (msgEl) { 240 msgEl.className = 'form-message error-message'; 241 msgEl.textContent = message; 242 msgEl.classList.remove('hidden'); 243 } 244 } 245 246 /** 247 * Show success message with animation 248 */ 249 function showSuccess() { 250 const content = modal.querySelector('.modal-content'); 251 252 // Fade out form 253 content.style.opacity = '0'; 254 content.style.transform = 'scale(0.95)'; 255 256 setTimeout(() => { 257 content.innerHTML = ` 258 <div class="success-animation text-center py-12"> 259 <div class="success-icon mb-6"> 260 <svg class="w-20 h-20 mx-auto text-brand-cyan" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 261 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> 262 </svg> 263 </div> 264 <h3 class="text-2xl font-bold text-white mb-3">¡Consulta Enviada!</h3> 265 <p class="text-brand-neutral">En breve nos estaremos comunicando con vos.</p> 266 </div> 267 `; 268 269 content.style.opacity = '1'; 270 content.style.transform = 'scale(1)'; 271 272 // Auto-close after 3 seconds 273 setTimeout(() => { 274 close(); 275 }, 3000); 276 }, 300); 277 } 278 279 /** 280 * Hide message 281 */ 282 function hideMessage() { 283 const msgEl = modal.querySelector('.form-message'); 284 if (msgEl) { 285 msgEl.classList.add('hidden'); 286 } 287 } 288 289 // Public API 290 return { 291 init, 292 open, 293 close 294 }; 295})(); 296 297// Initialize on DOM ready 298document.addEventListener('DOMContentLoaded', () => { 299 ContactModal.init(); 300 const urlParams = new URLSearchParams(window.location.search); 301 if (urlParams.has('contact')) { 302 ContactModal.open(urlParams.get('contact')); 303 } 304}); 305 306/** 307 * Terms & Conditions Modal 308 */ 309const TermsModal = { 310 modal: null, 311 overlay: null, 312 closeBtn: null, 313 closeX: null, 314 315 init() { 316 this.modal = document.getElementById('terms-modal'); 317 if (!this.modal) return; 318 319 this.overlay = this.modal.querySelector('.modal-overlay'); 320 this.closeBtn = this.modal.querySelector('.close-terms-btn'); 321 this.closeX = this.modal.querySelector('.modal-close-terms'); 322 323 // Attach event listeners 324 if (this.closeBtn) { 325 this.closeBtn.addEventListener('click', () => this.close()); 326 } 327 if (this.closeX) { 328 this.closeX.addEventListener('click', () => this.close()); 329 } 330 if (this.overlay) { 331 this.overlay.addEventListener('click', () => this.close()); 332 } 333 334 // ESC key 335 document.addEventListener('keydown', (e) => { 336 if (e.key === 'Escape' && !this.modal.classList.contains('hidden')) { 337 this.close(); 338 } 339 }); 340 341 // Attach to triggers 342 document.querySelectorAll('[data-terms-trigger]').forEach(trigger => { 343 trigger.addEventListener('click', (e) => { 344 e.preventDefault(); 345 this.open(); 346 }); 347 }); 348 }, 349 350 open() { 351 if (!this.modal) return; 352 this.modal.classList.remove('hidden'); 353 setTimeout(() => { 354 this.modal.classList.add('modal-active'); 355 }, 10); 356 document.body.style.overflow = 'hidden'; 357 }, 358 359 close() { 360 if (!this.modal) return; 361 this.modal.classList.remove('modal-active'); 362 setTimeout(() => { 363 this.modal.classList.add('hidden'); 364 document.body.style.overflow = ''; 365 }, 300); 366 } 367}; 368 369/** 370 * Cancellation Modal 371 */ 372const CancellationModal = { 373 modal: null, 374 overlay: null, 375 closeBtn: null, 376 form: null, 377 submitBtn: null, 378 379 init() { 380 this.modal = document.getElementById('cancellation-modal'); 381 if (!this.modal) return; 382
383 this.overlay = this.modal.querySelector('.modal-overlay'); 384 this.closeBtn = this.modal.querySelector('.modal-close-cancellation'); 385 this.form = this.modal.querySelector('#cancellation-form'); 386 this.submitBtn = this.modal.querySelector('.submit-cancellation-btn'); 387 388 // Attach event listeners 389 if (this.closeBtn) { 390 this.closeBtn.addEventListener('click', () => this.close()); 391 } 392 if (this.overlay) { 393 this.overlay.addEventListener('click', () => this.close()); 394 } 395 396 // ESC key 397 document.addEventListener('keydown', (e) => { 398 if (e.key === 'Escape' && !this.modal.classList.contains('hidden')) { 399 this.close(); 400 } 401 }); 402 403 // Form submission 404 if (this.form) { 405 this.form.addEventListener('submit', (e) => this.handleSubmit(e)); 406 } 407 408 // Attach to triggers 409 document.querySelectorAll('[data-cancellation-trigger]').forEach(trigger => { 410 trigger.addEventListener('click', (e) => { 411 e.preventDefault(); 412 this.open(); 413 }); 414 }); 415 }, 416 417 open() { 418 if (!this.modal) return; 419 this.modal.classList.remove('hidden'); 420 setTimeout(() => { 421 this.modal.classList.add('modal-active'); 422 }, 10); 423 document.body.style.overflow = 'hidden'; 424 }, 425 426 close() { 427 if (!this.modal) return; 428 this.modal.classList.remove('modal-active'); 429 setTimeout(() => { 430 this.modal.classList.add('hidden'); 431 document.body.style.overflow = ''; 432 this.resetForm(); 433 }, 300); 434 }, 435 436 resetForm() { 437 if (this.form) { 438 this.form.reset(); 439 this.hideMessage(); 440 } 441 }, 442 443 async handleSubmit(e) { 444 e.preventDefault(); 445 446 if (!this.validateForm()) { 447 return; 448 } 449 450 const formData = new FormData(this.form); 451 this.setLoading(true); 452 453 try { 454 const response = await fetch('backend/cancellation-handler.php', { 455 method: 'POST', 456 body: formData 457 }); 458 459 const result = await response.json(); 460 461 if (result.success) { 462 this.showSuccess(); 463 } else { 464 this.showError(result.message || 'Error al enviar la solicitud'); 465 } 466 } catch (error) { 467 console.error('Error:', error); 468 this.showError('Error de conexión. Por favor, intentá nuevamente.'); 469 } finally { 470 this.setLoading(false); 471 } 472 }, 473 474 validateForm() { 475 const nombre = this.form.querySelector('#cancel-nombre').value.trim(); 476 const dni = this.form.querySelector('#cancel-dni').value.trim(); 477 const domicilio = this.form.querySelector('#cancel-domicilio').value.trim(); 478 const telefono = this.form.querySelector('#cancel-telefono').value.trim(); 479 const motivo = this.form.querySelector('#cancel-motivo').value; 480 481 if (!nombre) { 482 this.showError('Por favor, ingresá tu nombre'); 483 return false; 484 } 485 if (!dni) { 486 this.showError('Por favor, ingresá tu DNI o Número de Suministro'); 487 return false; 488 } 489 if (!domicilio) { 490 this.showError('Por favor, ingresá el domicilio de instalación'); 491 return false; 492 } 493 if (!telefono) { 494 this.showError('Por favor, ingresá tu teléfono'); 495 return false; 496 } 497 if (!motivo) { 498 this.showError('Por favor, seleccioná el motivo de baja'); 499 return false; 500 } 501 502 return true; 503 }, 504 505 setLoading(loading) { 506 if (!this.submitBtn) return; 507 508 if (loading) { 509 this.submitBtn.disabled = true; 510 this.submitBtn.innerHTML = ` 511 <svg class="animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24"> 512 <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> 513 <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> 514 </svg>
515 <span>Enviando...</span> 516 `; 517 } else { 518 this.submitBtn.disabled = false; 519 this.submitBtn.innerHTML = ` 520 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 521 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> 522 </svg> 523 <span>Solicitar Baja del Servicio</span> 524 `; 525 } 526 }, 527 528 showError(message) { 529 const msgEl = this.modal.querySelector('.cancellation-message'); 530 if (msgEl) { 531 msgEl.className = 'cancellation-message p-3 rounded-lg text-sm'; 532 msgEl.style.background = 'rgba(239, 68, 68, 0.1)'; 533 msgEl.style.border = '1px solid rgba(239, 68, 68, 0.3)'; 534 msgEl.style.color = '#ef4444'; 535 msgEl.textContent = message; 536 msgEl.classList.remove('hidden'); 537 } 538 }, 539 540 showSuccess() { 541 const content = this.modal.querySelector('.modal-content'); 542 content.style.opacity = '0'; 543 content.style.transform = 'scale(0.95)'; 544 545 setTimeout(() => { 546 content.innerHTML = ` 547 <div class="success-animation text-center py-12"> 548 <div class="success-icon mb-6"> 549 <svg class="w-20 h-20 mx-auto text-brand-cyan" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 550 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> 551 </svg> 552 </div> 553 <h3 class="text-2xl font-bold text-white mb-3">Solicitud Recibida</h3> 554 <p class="text-brand-neutral">Tu solicitud de baja ha sido enviada. Nos contactaremos a la brevedad.</p> 555 </div> 556 `; 557 558 content.style.opacity = '1'; 559 content.style.transform = 'scale(1)'; 560 561 setTimeout(() => { 562 this.close(); 563 }, 3000); 564 }, 300); 565 }, 566 567 hideMessage() { 568 const msgEl = this.modal.querySelector('.cancellation-message'); 569 if (msgEl) { 570 msgEl.classList.add('hidden'); 571 } 572 } 573}; 574 575// Initialize all modals on DOM ready 576document.addEventListener('DOMContentLoaded', () => { 577 TermsModal.init(); 578 CancellationModal.init(); 579});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.