From ff3e57051f3820f0ae64773aecb854e157f5fa04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20L=C3=B3pez=20Coria?= Date: Fri, 3 Jul 2026 20:53:26 -0300 Subject: [PATCH] Contact form: name field letters-only validation, email field lowercase + format validation with error messages (ES/EN) --- src/pages/contacto.astro | 63 ++++++++++++++++++++++++++++++++++++++ src/pages/en/contact.astro | 63 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/src/pages/contacto.astro b/src/pages/contacto.astro index 516ebf6..e6b3a8b 100644 --- a/src/pages/contacto.astro +++ b/src/pages/contacto.astro @@ -84,9 +84,13 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; required autocomplete="name" maxlength="100" + pattern="[A-Za-záéíóúüñÁÉÍÓÚÜÑçÇ\s]+" + inputmode="text" + aria-describedby="name-error" class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors" style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);" /> +
@@ -100,9 +104,11 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; required autocomplete="email" maxlength="200" + aria-describedby="email-error" class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors" style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);" /> +
@@ -153,6 +159,47 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; const successMsg = isEn ? 'Message sent. We will reply soon.' : 'Mensaje enviado. Te respondemos pronto.'; const errorMsg = isEn ? 'Could not send. Please try again or contact us via WhatsApp.' : 'No se pudo enviar. Probá de nuevo o contactanos por WhatsApp.'; + const nameInput = document.getElementById('name'); + const emailInput = document.getElementById('email'); + const nameError = document.getElementById('name-error'); + const emailError = document.getElementById('email-error'); + const nameRegex = /[^a-záéíóúüñç\s]/gi; + const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/; + const nameErrMsg = isEn ? 'Letters and spaces only' : 'Solo letras y espacios'; + const emailErrMsg = isEn ? 'Invalid email address' : 'Dirección de correo no válida'; + + function showFieldError(input, errorEl, msg) { + errorEl.textContent = msg; + errorEl.classList.remove('hidden'); + input.style.borderColor = 'var(--color-hds-error)'; + } + function clearFieldError(input, errorEl) { + errorEl.classList.add('hidden'); + errorEl.textContent = ''; + input.style.borderColor = ''; + } + + nameInput.addEventListener('beforeinput', (e) => { + if (e.data && nameRegex.test(e.data)) { + e.preventDefault(); + } + }); + nameInput.addEventListener('input', () => { + const clean = nameInput.value.replace(nameRegex, ''); + if (clean !== nameInput.value) { + nameInput.value = clean; + } + }); + emailInput.addEventListener('input', () => { + emailInput.value = emailInput.value.toLowerCase(); + }); + [nameInput, emailInput].forEach(input => { + input.addEventListener('focus', () => { + const err = document.getElementById(input.id + '-error'); + if (err) clearFieldError(input, err); + }); + }); + window.addEventListener('load', () => { if (window.grecaptcha && window.grecaptcha.ready) { window.grecaptcha.ready(); @@ -169,6 +216,22 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; status.textContent = ''; status.style.color = 'var(--hds-fg-soft)'; + let hasError = false; + if (nameInput.value.trim() && nameRegex.test(nameInput.value)) { + showFieldError(nameInput, nameError, nameErrMsg); + hasError = true; + } + const emailVal = emailInput.value.trim(); + if (emailVal && !emailRegex.test(emailVal)) { + showFieldError(emailInput, emailError, emailErrMsg); + hasError = true; + } + if (hasError) { + label.textContent = originalLabel; + submit.disabled = false; + return; + } + try { let token = ''; if (window.grecaptcha && window.grecaptcha.execute) { diff --git a/src/pages/en/contact.astro b/src/pages/en/contact.astro index 516ebf6..e6b3a8b 100644 --- a/src/pages/en/contact.astro +++ b/src/pages/en/contact.astro @@ -84,9 +84,13 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; required autocomplete="name" maxlength="100" + pattern="[A-Za-záéíóúüñÁÉÍÓÚÜÑçÇ\s]+" + inputmode="text" + aria-describedby="name-error" class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors" style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);" /> +
@@ -100,9 +104,11 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; required autocomplete="email" maxlength="200" + aria-describedby="email-error" class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors" style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);" /> +
@@ -153,6 +159,47 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; const successMsg = isEn ? 'Message sent. We will reply soon.' : 'Mensaje enviado. Te respondemos pronto.'; const errorMsg = isEn ? 'Could not send. Please try again or contact us via WhatsApp.' : 'No se pudo enviar. Probá de nuevo o contactanos por WhatsApp.'; + const nameInput = document.getElementById('name'); + const emailInput = document.getElementById('email'); + const nameError = document.getElementById('name-error'); + const emailError = document.getElementById('email-error'); + const nameRegex = /[^a-záéíóúüñç\s]/gi; + const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/; + const nameErrMsg = isEn ? 'Letters and spaces only' : 'Solo letras y espacios'; + const emailErrMsg = isEn ? 'Invalid email address' : 'Dirección de correo no válida'; + + function showFieldError(input, errorEl, msg) { + errorEl.textContent = msg; + errorEl.classList.remove('hidden'); + input.style.borderColor = 'var(--color-hds-error)'; + } + function clearFieldError(input, errorEl) { + errorEl.classList.add('hidden'); + errorEl.textContent = ''; + input.style.borderColor = ''; + } + + nameInput.addEventListener('beforeinput', (e) => { + if (e.data && nameRegex.test(e.data)) { + e.preventDefault(); + } + }); + nameInput.addEventListener('input', () => { + const clean = nameInput.value.replace(nameRegex, ''); + if (clean !== nameInput.value) { + nameInput.value = clean; + } + }); + emailInput.addEventListener('input', () => { + emailInput.value = emailInput.value.toLowerCase(); + }); + [nameInput, emailInput].forEach(input => { + input.addEventListener('focus', () => { + const err = document.getElementById(input.id + '-error'); + if (err) clearFieldError(input, err); + }); + }); + window.addEventListener('load', () => { if (window.grecaptcha && window.grecaptcha.ready) { window.grecaptcha.ready(); @@ -169,6 +216,22 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG'; status.textContent = ''; status.style.color = 'var(--hds-fg-soft)'; + let hasError = false; + if (nameInput.value.trim() && nameRegex.test(nameInput.value)) { + showFieldError(nameInput, nameError, nameErrMsg); + hasError = true; + } + const emailVal = emailInput.value.trim(); + if (emailVal && !emailRegex.test(emailVal)) { + showFieldError(emailInput, emailError, emailErrMsg); + hasError = true; + } + if (hasError) { + label.textContent = originalLabel; + submit.disabled = false; + return; + } + try { let token = ''; if (window.grecaptcha && window.grecaptcha.execute) {