1document.addEventListener("DOMContentLoaded", function () { 2 const forms = document.querySelectorAll("form.needs-validation"); 3 4 forms.forEach((form) => { 5 form.addEventListener("submit", function (e) { 6 let valid = true; 7 8 // Full Name 9 const fullName = form.querySelector("[name='fullname']"); 10 if (fullName) { 11 const value = fullName.value.trim(); 12 if (!value) { 13 showError(fullName, "Name is required."); 14 valid = false; 15 } else if (/^\s/.test(fullName.value)) { 16 showError(fullName, "Name cannot start with a space."); 17 valid = false; 18 } else if (!/^[A-Za-z\s]+$/.test(value)) { 19 showError(fullName, "Name must contain only letters and spaces."); 20 valid = false; 21 } else if (value.length < 2 || value.length > 50) { 22 showError(fullName, "Name must be between 2 and 50 characters."); 23 valid = false; 24 } else { 25 clearError(fullName); 26 } 27 } 28 29 // Company field validation 30 const company = form.querySelector("[name='company']"); 31 if (company) { 32 const value = company.value.trim(); 33 // Allow letters, numbers, spaces, &, , . â - 34 const allowedRegex = /^[A-Za-z0-9&,.â'\-\s]+$/; 35 if (!value) { 36 showError(company, "Company/Organisation name is required."); 37 valid = false; 38 } else if (!allowedRegex.test(value)) { 39 showError( 40 company, 41 "Only letters, numbers, spaces, and (& , . â -) allowed." 42 ); 43 valid = false; 44 } else if (value.length < 2 || value.length > 100) { 45 showError(company, "Name must be between 2 and 100 characters."); 46 valid = false; 47 } else { 48 clearError(company); 49 } 50 } 51 52 // Email 53 const email = form.querySelector("[name='email']"); 54 if (email) { 55 const value = email.value.trim(); 56 const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; 57 58 if (!value) { 59 showError(email, "Email address is required."); 60 valid = false; 61 } else if (value.length < 5 || value.length > 50) { 62 showError(email, "Email must be between 5 and 50 characters."); 63 valid = false; 64 } else if (!emailPattern.test(value)) { 65 showError(email, "Enter a valid email address (example: [email protected])."); 66 valid = false; 67 } else { 68 clearError(email); 69 } 70 } 71 72 // Phone 73 const phone = form.querySelector("[name='phone']"); 74 if (phone) { 75 const value = phone.value.trim(); 76 const phonePattern = /^\+?\d{7,15}$/; 77 if (!value) { 78 showError(phone, "Phone number is required."); 79 valid = false; 80 } else if (!phonePattern.test(value)) { 81 showError(phone, "Enter a valid phone number (7â15 digits)."); 82 valid = false; 83 } else { 84 clearError(phone); 85 } 86 } 87 88 // Service (dropdown) 89 const service = form.querySelector("[name='interest']"); 90 if (service && !service.value) { 91 showError(service, "Selection required"); 92 valid = false; 93 } else { 94 clearError(service); 95 } 96 97 // Project Timeline (dropdown) 98 const timeline = form.querySelector("[name='project_timeline']"); 99 if (timeline && !timeline.value) { 100 showError(timeline, "Selection required"); 101 valid = false; 102 } else { 103 clearError(timeline); 104 } 105 106 107 //// File field validation 108const resume = form.querySelector("[name='resume']"); // change name if needed 109if (resume) { 110 const file = resume.files[0]; 111 112 // Allowed file types 113 const allowedTypes = [ 114 "application/pdf", 115 "application/msword", // .doc 116 "application/vnd.openxmlformats-officedocument.wordprocessingml.document" // .docx 117 ]; 118 119 const maxSize = 5 * 1024 * 1024; // 5MB 120 121 if (!file) { 122 showError(resume, "Resume attachment is required."); 123 valid = false; 124 } else if (!allowedTypes.includes(file.type)) { 125 showError( 126 resume, 127 "Only PDF, DOC, or DOCX files are allowed." 128 ); 129 valid = false;
130 } else if (file.size > maxSize) { 131 showError( 132 resume, 133 "File size must be less than 5MB." 134 ); 135 valid = false; 136 } else { 137 clearError(resume); 138 } 139} 140 141 142 // Tell us about your project (textarea, optional, max 500) 143 const projectText = form.querySelector("[name='message']"); 144 if (projectText && projectText.value.length > 500) { 145 showError(projectText, "Maximum 500 characters allowed"); 146 valid = false; 147 } else { 148 clearError(projectText); 149 } 150 151 // Stop form submit if invalid 152 if (!valid) { 153 e.preventDefault(); 154 return; 155 } 156 157 // Hide submit button and show loader 158 const submitBtn = form.querySelector("button[type='submit']"); 159 const loaderImg = form.querySelector(".form-loader-img"); 160 if (submitBtn) submitBtn.style.display = "none"; 161 if (loaderImg) loaderImg.style.display = "block"; 162 e.preventDefault(); // Prevent default form submission 163 164 grecaptcha.ready(async function () { 165 const showMessage = (msg, isError = true) => { 166 if (loaderImg) loaderImg.style.display = "none"; 167 if (submitBtn) submitBtn.style.display = "none"; 168 169 let box = form.querySelector(".form-global-message"); 170 if (!box) { 171 box = document.createElement("div"); 172 box.className = "form-global-message"; 173 Object.assign(box.style, { 174 fontSize: "14px", 175 marginTop: "8px", 176 }); 177 form.appendChild(box); 178 } 179 180 box.textContent = msg; 181 box.style.color = isError ? "red" : "green"; 182 box.style.display = "block"; 183 184 setTimeout(() => { 185 box.style.display = "none"; 186 if (submitBtn) submitBtn.style.display = "block"; 187 }, 3000); 188 }; 189 190 const hideLoader = () => { 191 if (loaderImg) loaderImg.style.display = "none"; 192 }; 193 194 try { 195 // Get token 196 const token = await grecaptcha.execute( 197 "6LcoAZorAAAAABagcN95ntcpzfgpcIlFLA3_4_aV", 198 { action: "submit" } 199 ); 200 201 // Build payload 202 const formDataObj = Object.fromEntries(new FormData(form).entries()); 203 formDataObj["g-recaptcha-response"] = token; 204 205 // Send 206 const response = await fetch("https://dgapi.digitalgravity.ae/submit/quote", { 207 //const response = await fetch("http://localhost:3005/submit/career", { 208 method: "POST", 209 headers: { "Content-Type": "application/json" }, 210 body: JSON.stringify(formDataObj), 211 }); 212 213 if (!response.ok) { 214 let msg = "Your message couldn't be delivered. Please try again."; 215 try { 216 const data = await response.json(); 217 if (data?.message) msg = data.message; 218 } catch (_) { } 219 throw new Error(msg); 220 } 221 222 // Success 223 hideLoader(); 224 form.reset(); 225 window.location.href = window.DG_THANKYOU_URL || "/thankyou/"; 226 } catch (err) { 227 hideLoader(); 228 229 // Map technical errors â user-friendly messages 230 let userMessage = "Something went wrong. Please try again."; 231 if (err.message?.includes("site key")) { 232 userMessage = "Captcha could not be initialized."; 233 } else if (err.message?.toLowerCase().includes("captcha")) { 234 userMessage = "Captcha verification failed. Please try again."; 235 } else if (err.message?.toLowerCase().includes("network")) { 236 userMessage = "Network error. Please check your connection."; 237 } else if (err.message) { 238 userMessage = err.message; // fallback if it's already user-friendly 239 } 240 241 showMessage(userMessage); 242 console.error("Form error (debug):", err); // raw error in console only 243 } 244 }); 245 }); 246 247 // Error handling functions 248 function showError(input, message) { 249 clearError(input); 250 const error = document.createElement("div"); 251 error.className = "error-message"; 252 error.style.color = "red"; 253 error.style.fontSize = "13px"; 254 error.textContent = message; 255 input.insertAdjacentElement("afterend", error); 256 } 257 258 function clearError(input) { 259 if (!input) return; 260 const next = input.nextElementSibling; 261 if (next && next.classList.contains("error-message")) { 262 next.remove(); 263 } 264 } 265 }); 266 267 // 1ï¸â£ Function to remove hidden contact person input 268 function removeContactPersonInput() { 269 const popupWrapper = document.querySelector(".formPopupWrapper.active"); 270 if (!popupWrapper) return; 271 const form = popupWrapper.querySelector("form.needs-validation"); 272 if (!form) return; 273 const hiddenInput = form.querySelector('input[name="contact_person"]'); 274 if (hiddenInput) hiddenInput.remove(); 275 } 276 277 // 2ï¸â£ Handle all clicks 278 document.addEventListener("click", function (event) { 279 const target = event.target; 280 281 // â If clicked a contact person button 282 const btn = target.closest(".scheduleCallBtn[data-person]"); 283 if (btn) { 284 const popupWrapper = document.querySelector(".formPopupWrapper.active"); 285 if (!popupWrapper) return; 286 const form = popupWrapper.querySelector("form.needs-validation"); 287 if (!form) return; 288 289 // Create hidden input if not exists 290 let hiddenInput = form.querySelector('input[name="contact_person"]'); 291 if (!hiddenInput) { 292 hiddenInput = document.createElement("input"); 293 hiddenInput.type = "hidden"; 294 hiddenInput.name = "contact_person"; 295 form.appendChild(hiddenInput); 296 } 297 298 // Set value from button 299 hiddenInput.value = btn.getAttribute("data-person"); 300 return; // stop further processing 301 } 302 303 // â Remove hidden input when clicking close button 304 if (target.closest(".closeFormPopup")) { 305 removeContactPersonInput(); 306 return; 307 } 308 309 // â Remove hidden input when clicking outside popup 310 const popupWrapper = document.querySelector(".formPopupWrapper.active"); 311 if (popupWrapper && !target.closest(".formPopupContainer")) { 312 removeContactPersonInput(); 313 } 314 }); 315 316}); 317
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.