1import { analytics, logEvent } from './firebase-analytics.js'; 2 3document.addEventListener('DOMContentLoaded', () => { 4 const form = document.getElementById('contactForm'); 5 const nameInput = document.getElementById('name'); 6 const emailInput = document.getElementById('email'); 7 const messageInput = document.getElementById('message'); 8 const subjectInput = document.getElementById('subject'); 9 const submitBtn = document.getElementById('submitBtn'); 10 const statusMessage = document.getElementById('statusMessage'); 11 12 // Auto-fill hidden subject with context 13 const setSubject = () => { 14 const time = new Date().toLocaleString(); 15 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 16 subjectInput.value = `New Support Request from Website | ${time} | ${timezone}`; 17 }; 18 setSubject(); 19 20 const showError = (element, message) => { 21 const errorText = document.getElementById(`${element.id}Error`); 22 element.classList.add('border-red-500'); 23 errorText.
23innerText = message; 24 errorText.classList.remove('hidden'); 25 }; 26 27 const clearError = (element) => { 28 const errorText = document.getElementById(`${element.id}Error`); 29 element.classList.remove('border-red-500'); 30 errorText.classList.add('hidden'); 31 }; 32 33 const validate = () => { 34 let isValid = true; 35 if (!nameInput.value.trim()) { 36 showError(nameInput, 'Name is required'); 37 isValid = false; 38 } else { 39 clearError(nameInput); 40 } 41 42 const email = emailInput.value.trim(); 43 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 44 if (!email) { 45 showError(emailInput, 'Email is required'); 46 isValid = false; 47 } else if (!emailRegex.test(email)) { 48 showError(emailInput, 'Please enter a valid email address'); 49 isValid = false; 50 } else { 51 clearError(emailInput); 52 } 53 54 if (!messageInput.value.trim()) { 55 showError(messageInput, 'Message cannot be empty'); 56 isValid = false; 57 } else { 58 clearError(messageInput); 59 } 60 return isValid; 61 }; 62 63 form.addEventListener('submit', async (e) => { 64 e.preventDefault(); 65 66 if (!validate()) return; 67 68 // Robot Check (Honeypot) - if filled, it's a bot 69 const honeypot = document.getElementById('honeypot').value; 70 if (honeypot) { 71 console.log("Bot detected."); 72 return; 73 } 74 75 // UI Loading State 76 const originalBtnText = submitBtn.innerHTML; 77 submitBtn.innerHTML = `<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><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></svg>`; 78 submitBtn.disabled = true; 79 statusMessage.classList.add('hidden'); 80 81 try { 82 const token = await new Promise((resolve) => { 83 grecaptcha.enterprise.ready(() => { 84 grecaptcha.enterprise.execute('6LeMq6gtAAAAAKi9m2mgG5s4uIcBR9WBBdBwegqa', { action: 'submit' }).then(resolve); 85 }); 86 }); 87 88 // Verify reCAPTCHA via worker (secret key never leaves the worker) 89 const captchaRes = await fetch('https://fakevoice.zczvwz6pw7.workers.dev/recaptcha', { 90 method: 'POST', 91 headers: { 'Content-Type': 'application/json' }, 92 body: JSON.stringify({ token }), 93 }); 94 const captchaData = await captchaRes.json(); 95 if (!captchaData.success) { 96 throw new Error(captchaData.message || 'Bot detected (score low).'); 97 } 98 99 const formData = new FormData(form); 100 101 const response = await fetch('/send_email.php', { 102 method: 'POST', 103 body: formData 104 }); 105 106 const result = await response.json(); 107 108 if (result.success) { 109 // Log Analytics Event 110 logEvent(analytics, 'contact_form_send', { 111 page_location: window.location.href, 112 form_id: 'contactForm' 113 }); 114 115 statusMessage.className = 'mt-4 p-4 rounded-xl text-sm font-medium bg-green-50 text-green-700 border border-green-200'; 116 statusMessage.innerHTML = '⨠Message sent successfully! We will get back to you soon.'; 117 form.reset(); 118 setSubject(); // Reset subject timestamp 119 } else { 120 throw new Error(result.message || 'Failed to send message.'); 121 } 122 } catch (error) { 123 statusMessage.className = 'mt-4 p-4 rounded-xl text-sm font-medium bg-red-50 text-red-700 border border-red-200'; 124 statusMessage.innerHTML = 'â ï¸ ' + error.message; 125 } finally { 126 statusMessage.classList.remove('hidden'); 127 submitBtn.innerHTML = originalBtnText; 128 submitBtn.disabled = false;
129 } 130 }); 131 132 // Initial check 133 const updateSubmitButton = () => { 134 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 135 const isValid = nameInput.value.trim() && 136 emailInput.value.trim() && 137 emailRegex.test(emailInput.value.trim()) && 138 messageInput.value.trim(); 139 140 submitBtn.disabled = !isValid; 141 if (isValid) { 142 submitBtn.classList.remove('opacity-50', 'cursor-not-allowed'); 143 submitBtn.classList.add('hover:bg-blue-700', 'shadow-lg'); 144 } else { 145 submitBtn.classList.add('opacity-50', 'cursor-not-allowed'); 146 submitBtn.classList.remove('hover:bg-blue-700', 'shadow-lg'); 147 } 148 }; 149 150 // Real-time validation on input 151 [nameInput, emailInput, messageInput].forEach(input => { 152 input.addEventListener('input', () => { 153 clearError(input); 154 updateSubmitButton(); 155 }); 156 }); 157 158 // Initialize button state 159 updateSubmitButton(); 160});
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.