1/* 2 * eSIGN: wspolna obsluga formularzy Formspark. 3 * 4 * Konfiguracja po utworzeniu konta: 5 * - w kazdym formularzu uzupelnij tylko data-formspark-form-id; 6 * - data-turnstile-site-key uzupelnij po skonfigurowaniu Turnstile; 7 * - sekret Turnstile zostaje w panelu Formspark. Nigdy nie trafia do tego pliku. 8 */ 9(function () { 10 'use strict'; 11 12 var FORM_SELECTOR = '[data-esign-lead-form]'; 13 var TURNSTILE_SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; 14 var EMAIL_NOTIFICATION_SENDER = 'Zapytanie ze strony WWW'; 15 var turnstileLoader; 16 17 function currentLanguageIsEnglish() { 18 return !!(document.body && document.body.classList.contains('lang-en')); 19 } 20 21 function getCopy(form, kind) { 22 var language = currentLanguageIsEnglish() ? 'en' : 'pl'; 23 var attribute = 'data-' + kind + '-' + language; 24 return form.getAttribute(attribute) || form.getAttribute('data-' + kind + '-pl') || ''; 25 } 26 27 function getEndpoint(form) { 28 var formId = (form.getAttribute('data-formspark-form-id') || '').trim(); 29 return formId ? 'https://submit-form.com/' + encodeURIComponent(formId) : ''; 30 } 31 32 function getStatus(form) { 33 return form.parentElement && form.parentElement.querySelector('.form-status-message'); 34 } 35 36 function showStatus(form, state, message) { 37 var status = getStatus(form); 38 if (!status) return; 39 40 status.textContent = message; 41 status.dataset.state = state; 42 if (state === 'error') { 43 status.style.background = '#fef2f2'; 44 status.style.color = '#991b1b'; 45 status.style.border = '1px solid #fecaca'; 46 } else { 47 status.style.background = ''; 48 status.style.color = ''; 49 status.style.border = ''; 50 } 51 status.classList.add('visible'); 52 } 53 54 function clearStatus(form) { 55 var status = getStatus(form); 56 if (!status) return; 57 58 status.classList.remove('visible'); 59 delete status.dataset.state; 60 status.style.background = ''; 61 status.style.color = ''; 62 status.style.border = ''; 63 status.textContent = ''; 64 } 65 66 function setSubmitting(form, isSubmitting) { 67 var button = form.querySelector('button[type="submit"]'); 68 if (!button) return; 69 70 if (!button.dataset.originalLabel) { 71 button.dataset.originalLabel = button.innerHTML; 72 } 73 74 button.disabled = isSubmitting; 75 button.setAttribute('aria-busy', isSubmitting ? 'true' : 'false'); 76 button.innerHTML = isSubmitting ? getCopy(form, 'sending') : button.dataset.originalLabel; 77 } 78 79 function validateForm(form) { 80 var name = form.querySelector('[name="imie"]'); 81 var phone = form.querySelector('[name="telefon"]'); 82 var problem = form.querySelector('[name="problem"]'); 83 var email = form.querySelector('[name="email"]'); 84 var marketingConsent = form.querySelector('[name="zgoda_marketingowa"]'); 85 86 [name, phone, problem, email].forEach(function (field) { 87 if (field) field.setCustomValidity(''); 88 }); 89 90 if (name && name.value.trim().length < 2) { 91 name.setCustomValidity(getCopy(form, 'name-error')); 92 } 93 94 if (phone) { 95 var digits = phone.value.replace(/\D/g, ''); 96 if (digits.length < 7 || digits.length > 15) { 97 phone.setCustomValidity(getCopy(form, 'phone-error')); 98 } 99 } 100 101 if (problem && problem.value.trim().length < 3) { 102 problem.setCustomValidity(getCopy(form, 'problem-error')); 103 } 104 105 if (marketingConsent && marketingConsent.checked && email && !email.validity.valid) { 106 email.setCustomValidity(getCopy(form, 'marketing-email-error')); 107 } 108 109 return form.checkValidity(); 110 } 111 112 function syncMarketingEmailRequirement(form) { 113 var marketingConsent = form.querySelector('[name="zgoda_marketingowa"]'); 114 var email = form.querySelector('[name="email"]'); 115 var emailAlwaysRequired = form.getAttribute('data-email-required-always') === 'true'; 116 117 if (!marketingConsent || !email) return; 118 119 email.required = emailAlwaysRequired || marketingConsent.checked; 120 email.setAttribute('aria-required', email.required ? 'true' : 'false'); 121 } 122 123 function getEmailNotificationSettings(form) { 124 var service = form.getAttribute('data-form-name') || 'UsÅuga'; 125 var name = form.querySelector('[name="imie"]'); 126 var clientName = name ? name.value.trim() : ''; 127 128 if (service === 'Odnowienia podpisu') service = 'Odnowienie podpisu'; 129 return { 130 from: EMAIL_NOTIFICATION_SENDER, 131 subject: service + ' | ' + clientName 132 }; 133 } 134 135 function formatNotificationPhone(phone, form) { 136 var original = phone ? String(phone).trim() : ''; 137 var digits = original.replace(/\D/g, ''); 138 var isForeignForm = form.getAttribute('data-form-name') === 'Podpis dla obcokrajowca'; 139 var polishNumber = ''; 140 141 if (/^0048\d{9}$/.test(digits)) { 142 polishNumber = digits.slice(4); 143 } else if (/^48\d{9}$/.test(digits)) { 144 polishNumber = digits.slice(2); 145 } else if (!isForeignForm && /^\d{9}$/.test(digits)) { 146 polishNumber = digits; 147 } 148 149 if (!polishNumber) return original; 150 151 return '+48 ' + polishNumber.replace(/(\d{3})(?=\d)/g, '$1 ').trim(); 152 } 153 154 function formPayload(form) { 155 var payload = {}; 156 var data = new FormData(form); 157 var marketingConsent = form.querySelector('[name="zgoda_marketingowa"]'); 158 var submittedAt = new Date().toISOString(); 159 160 data.forEach(function (value, key) { 161 payload[key] = typeof value === 'string' ? value.trim() : value; 162 }); 163 164 payload.formularz = form.getAttribute('data-form-name') || 'Formularz eSIGN'; 165 payload.strona = window.location.pathname; 166 payload.strona_zrodlowa = window.location.pathname; 167 payload.data_wyslania = submittedAt; 168 payload.zgoda_marketingowa = marketingConsent ? (marketingConsent.checked ? 'tak' : 'nie') : 'nie_dotyczy'; 169 payload.data_zgody_marketingowej = marketingConsent && marketingConsent.checked ? submittedAt : 'nie_dotyczy'; 170 payload.wersja_tresci_zgody = 'marketing-v1-2026-08-12';
171 payload.telefon_powiadomienie = formatNotificationPhone(payload.telefon, form); 172 payload._email = getEmailNotificationSettings(form); 173 return payload; 174 } 175 176 function trackSavedLead(form) { 177 var detail = { 178 formName: form.getAttribute('data-form-name') || 'Formularz eSIGN', 179 sourcePath: window.location.pathname 180 }; 181 182 window.dispatchEvent(new CustomEvent('esign:lead-saved', { detail: detail })); 183 } 184 185 function loadTurnstile() { 186 if (turnstileLoader) return turnstileLoader; 187 188 turnstileLoader = new Promise(function (resolve, reject) { 189 var existing = document.getElementById('esign-turnstile-script'); 190 if (existing) { 191 existing.addEventListener('load', function () { resolve(window.turnstile); }, { once: true }); 192 existing.addEventListener('error', reject, { once: true }); 193 return; 194 } 195 196 var script = document.createElement('script'); 197 script.id = 'esign-turnstile-script'; 198 script.src = TURNSTILE_SCRIPT_URL; 199 script.async = true; 200 script.onload = function () { resolve(window.turnstile); }; 201 script.onerror = reject; 202 document.head.appendChild(script); 203 }); 204 205 return turnstileLoader; 206 } 207 208 function prepareTurnstile(form) { 209 var siteKey = (form.getAttribute('data-turnstile-site-key') || '').trim(); 210 var mount = form.querySelector('[data-turnstile-widget]'); 211 var responseField = form.querySelector('[name="cf-turnstile-response"]'); 212 213 if (!siteKey || !mount || !responseField) return; 214 215 mount.hidden = false; 216 loadTurnstile().then(function (turnstile) { 217 if (!turnstile || mount.dataset.rendered === 'true') return; 218 219 mount.dataset.rendered = 'true'; 220 mount.dataset.widgetId = turnstile.render(mount, { 221 sitekey: siteKey, 222 callback: function (token) { responseField.value = token; }, 223 'expired-callback': function () { responseField.value = ''; }, 224 'error-callback': function () { responseField.value = ''; } 225 }); 226 }).catch(function () { 227 showStatus(form, 'error', getCopy(form, 'turnstile-error')); 228 }); 229 } 230 231 function resetTurnstile(form) { 232 var mount = form.querySelector('[data-turnstile-widget]'); 233 if (mount && mount.dataset.widgetId && window.turnstile) { 234 window.turnstile.reset(mount.dataset.widgetId); 235 } 236 } 237 238 async function handleSubmit(event) { 239 event.preventDefault(); 240 241 var form = event.currentTarget; 242 if (form.dataset.submitting === 'true') return; 243 244 if (!validateForm(form)) { 245 form.reportValidity(); 246 return; 247 } 248 249 var endpoint = getEndpoint(form); 250 if (!endpoint) { 251 showStatus(form, 'error', getCopy(form, 'config-error')); 252 return; 253 } 254 255 var honeypot = form.querySelector('[name="_honeypot"]'); 256 if (honeypot && honeypot.value.trim() !== '') { 257 showStatus(form, 'error', getCopy(form, 'generic-error')); 258 return; 259 } 260 261 var turnstileField = form.querySelector('[name="cf-turnstile-response"]'); 262 if ((form.getAttribute('data-turnstile-site-key') || '').trim() && (!turnstileField || !turnstileField.value)) { 263 showStatus(form, 'error', getCopy(form, 'turnstile-required')); 264 return; 265 } 266 267 form.dataset.submitting = 'true'; 268 clearStatus(form); 269 setSubmitting(form, true); 270 271 try { 272 var response = await fetch(endpoint, { 273 method: 'POST', 274 headers: { 275 'Content-Type': 'application/json', 276 'Accept': 'application/json' 277 }, 278 body: JSON.stringify(formPayload(form)), 279 credentials: 'omit' 280 }); 281 282 if (!response.ok) { 283 throw new Error('HTTP ' + response.status); 284 } 285 286 await response.json(); 287 form.reset(); 288 resetTurnstile(form); 289 showStatus(form, 'success', getCopy(form, 'success')); 290 trackSavedLead(form); 291 } catch (error) { 292 showStatus(form, 'error', getCopy(form, 'generic-error')); 293 } finally { 294 delete form.dataset.submitting; 295 setSubmitting(form, false); 296 } 297 } 298 299 function prepareForm(form) { 300 form.addEventListener('submit', handleSubmit); 301 form.addEventListener('input', function () { clearStatus(form); }); 302 form.addEventListener('change', function (event) { 303 if (event.target && event.target.name === 'zgoda_marketingowa') {
304 syncMarketingEmailRequirement(form); 305 clearStatus(form); 306 } 307 }); 308 syncMarketingEmailRequirement(form); 309 prepareTurnstile(form); 310 } 311 312 function initialize() { 313 document.querySelectorAll(FORM_SELECTOR).forEach(prepareForm); 314 } 315 316 if (document.readyState === 'loading') { 317 document.addEventListener('DOMContentLoaded', initialize); 318 } else { 319 initialize(); 320 } 321}());
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.