1/* v68: offer validation, email verification, and stable submission feedback. */ 2(function () { 3 'use strict'; 4 5 function noticeTarget(form) { 6 return form.querySelector('[data-offer-notice], [data-purchase-notice], .form-notice, .modal-notice'); 7 } 8 9 function standaloneSuccessTarget(form) { 10 if (!form || !form.closest('.standalone-offer')) return null; 11 return form.closest('.standalone-offer').querySelector('[data-offer-success-screen]'); 12 } 13 14 function showStandaloneSuccess(form, message) { 15 var screen = standaloneSuccessTarget(form); 16 if (!screen) return false; 17 var layout = form.closest('.sale-layout'); 18 var copy = screen.querySelector('[data-offer-success-message]'); 19 if (copy) copy.textContent = String(message || 'Thank you! Your offer has been submitted successfully.'); 20 if (layout) layout.hidden = true; 21 screen.hidden = false; 22 screen.classList.add('is-visible'); 23 window.setTimeout(function () { screen.focus(); }, 0); 24 return true; 25 } 26 27 function showNotice(form, message, failed) { 28 if (!failed && showStandaloneSuccess(form, message)) return; 29 var notice = noticeTarget(form); 30 if (!notice) return; 31 if (notice._offerNoticeTimer) window.clearTimeout(notice._offerNoticeTimer); 32 notice.hidden = false; 33 notice.style.display = 'block'; 34 notice.className = notice.matches('.modal-notice') 35 ? 'modal-notice ' + (failed ? 'error' : 'success') + ' is-dismissible' 36 : 'notice ' + (failed ? 'error' : 'success') + ' form-notice is-dismissible'; 37 var text = String(message || '').replace(/^[ââ]\s*/, ''); 38 var messageText = document.createElement('span'); 39 // Errors are already identified by their color and role. Do not add a 40 // leading cross: it makes longer delivery errors harder to scan. 41 messageText.textContent = failed ? text : 'â ' + text; 42 var dismiss = document.createElement('button'); 43 dismiss.type = 'button'; 44 dismiss.className = 'offer-notice-dismiss'; 45 dismiss.setAttribute('data-notice-dismiss', ''); 46 dismiss.setAttribute('aria-label', 'Dismiss this message'); 47 dismiss.textContent = 'Ã'; 48 dismiss.addEventListener('click', function () { clearNotice(form); }); 49 notice.replaceChildren(messageText, dismiss); 50 notice._offerNoticeVisible = true; 51 } 52 53 function clearNotice(form) { 54 var notice = noticeTarget(form); 55 if (!notice) return; 56 notice.hidden = true; 57 notice.style.display = 'none'; 58 notice.textContent = ''; 59 notice._offerNoticeVisible = false; 60 if (notice._offerNoticeTimer) window.clearTimeout(notice._offerNoticeTimer); 61 notice._offerNoticeTimer = null; 62 notice.className = notice.matches('.modal-notice') ? 'modal-notice' : 'notice form-notice'; 63 } 64 65 window.domainOfferShowNotice = showNotice; 66 67 document.addEventListener('click', function (event) { 68 var close = event.target.closest('[data-offer-close]'); 69 if (!close) return; 70 event.preventDefault(); 71 var destination = close.getAttribute('data-offer-close-url') || 'index.php'; 72 try { window.close(); } catch (ignore) {} 73 // Browsers refuse window.close() for tabs that were not script-opened; 74 // return to the domain page in that case so the completed form cannot 75 // be submitted again accidentally. 76 window.setTimeout(function () { window.location.assign(destination); }, 120); 77 }); 78 79 // Some legacy standalone handlers reset the form immediately after 80 // rendering a success message. Preserve that message across the native 81 // reset event so a successful offer is never reported without feedback. 82 document.addEventListener('reset', function (event) { 83 var form = event.target; 84 if (!form || !form.matches || !form.matches('form[data-ajax="offer"], form[data-offer-form]')) return; 85 var notice = noticeTarget(form); 86 if (!notice || !notice._offerNoticeVisible) return; 87 var html = notice.innerHTML;
88 var className = notice.className; 89 window.setTimeout(function () { 90 if (!notice._offerNoticeVisible) return; 91 notice.hidden = false; 92 notice.style.display = 'block'; 93 notice.className = className; 94 if (!notice.innerHTML.trim()) notice.innerHTML = html; 95 }, 0); 96 }, true); 97 98 function decorateVisibleNotice(notice) { 99 if (!notice || notice.hidden || !String(notice.textContent || '').trim() || notice.querySelector('.offer-notice-dismiss')) return; 100 if (window.getComputedStyle && window.getComputedStyle(notice).display === 'none') return; 101 notice.classList.add('is-dismissible'); 102 var dismiss = document.createElement('button'); 103 dismiss.type = 'button'; 104 dismiss.className = 'offer-notice-dismiss'; 105 dismiss.setAttribute('data-notice-dismiss', ''); 106 dismiss.setAttribute('aria-label', 'Dismiss this message'); 107 dismiss.textContent = 'Ã'; 108 notice.appendChild(dismiss); 109 } 110 111 // Also make any server-rendered notice on these public pages dismissible. 112 document.querySelectorAll('.notice').forEach(decorateVisibleNotice); 113 114 // Notices are injected after page load, so keep one delegated close handler 115 // for both the standalone form and the homepage modal. 116 document.addEventListener('click', function (event) { 117 var dismiss = event.target.closest('[data-notice-dismiss], .offer-notice-dismiss'); 118 if (!dismiss) return; 119 var notice = dismiss.closest('.modal-notice, .form-notice, [data-email-code-notice], .notice'); 120 var form = dismiss.closest('form'); 121 if (form && noticeTarget(form) === notice) clearNotice(form); 122 else if (notice) { 123 notice.hidden = true; 124 notice.style.display = 'none'; 125 notice.replaceChildren(); 126 notice.classList.remove('is-dismissible'); 127 } 128 }); 129 130 function entryElements(form) { 131 var grid = form.querySelector('.modal-form-grid'); 132 if (grid) return [grid]; 133 return Array.prototype.slice.call(form.children).filter(function (node) { 134 return node.classList && (node.classList.contains('field') || node.classList.contains('interest-confirmation')); 135 }); 136 } 137 138 function setEntryVisible(form, visible) { 139 entryElements(form).forEach(function (element) { 140 element.classList.toggle('offer-entry-hidden', !visible); 141 element.setAttribute('aria-hidden', visible ? 'false' : 'true'); 142 }); 143 } 144 145 function panelElements(form) { 146 var panel = form.querySelector('[data-offer-email-verification-panel]'); 147 return { 148 panel: panel, 149 input: panel && panel.querySelector('[name="email_verification_code"]'), 150 title: panel && panel.querySelector('[data-offer-verification-title]'), 151 copy: panel && panel.querySelector('[data-offer-verification-copy]'), 152 email: panel && panel.querySelector('[data-offer-verification-email]'), 153 codeField: panel && panel.querySelector('[data-offer-code-field]'), 154 verify: panel && panel.querySelector('[data-offer-verify-submit]'), 155 resend: panel && panel.querySelector('[data-resend-offer-code]'), 156 countdown: panel && panel.querySelector('[data-email-code-notice]'), 157 change: panel && panel.querySelector('[data-change-offer-details]'), 158 another: panel && panel.querySelector('[data-new-offer]'), 159 main: form.querySelector('[data-offer-main-submit]') 160 }; 161 } 162 163 function restoreMainSubmit(parts) { 164 if (!parts || !parts.main) return; 165 parts.main.hidden = false; 166 parts.main.disabled = false; 167 parts.main.textContent = 'Submit Offer'; 168 } 169 170 function clearVerificationCountdown(parts) { 171 if (!parts || !parts.panel) return; 172 if (parts.panel._verificationCountdown) window.clearInterval(parts.panel._verificationCountdown); 173 parts.panel._verificationCountdown = null; 174 delete parts.panel.dataset.verificationExpiresAt; 175 } 176 177 function formatRemaining(seconds) { 178 var total = Math.max(0, Math.ceil(seconds)); 179 return String(Math.floor(total / 60)).padStart(2, '0') + ':' + String(total % 60).padStart(2, '0'); 180 } 181 182 function setVerificationCountdown(parts, seconds) { 183 if (!parts) return; 184 if (seconds > 0) { 185 if (parts.resend) { 186 parts.resend.disabled = true; 187 parts.resend.textContent = 'Resend available in ' + formatRemaining(seconds); 188 } 189 if (parts.countdown) { 190 parts.countdown.hidden = false;
191 parts.countdown.textContent = 'Code expires in ' + formatRemaining(seconds) + '. You can request a new code after 5 minutes.'; 192 } 193 } else { 194 if (parts.resend) { 195 parts.resend.disabled = false; 196 parts.resend.textContent = 'Resend code'; 197 } 198 if (parts.countdown) { 199 parts.countdown.hidden = false; 200 parts.countdown.textContent = 'The code has expired. You can now request a new code.'; 201 } 202 } 203 } 204 205 function startVerificationCountdown(form, expiresAt) { 206 var parts = panelElements(form); 207 if (!parts.panel) return; 208 clearVerificationCountdown(parts); 209 var expiry = Number(expiresAt || 0); 210 if (!Number.isFinite(expiry) || expiry <= Date.now() / 1000) expiry = Math.floor(Date.now() / 1000) + 300; 211 parts.panel.dataset.verificationExpiresAt = String(expiry); 212 var tick = function () { 213 var remaining = expiry - Date.now() / 1000; 214 setVerificationCountdown(parts, remaining); 215 if (remaining <= 0) clearVerificationCountdown(parts); 216 }; 217 tick(); 218 if (expiry > Date.now() / 1000) parts.panel._verificationCountdown = window.setInterval(tick, 1000); 219 } 220 221 function rememberPanelCopy(parts) { 222 if (!parts.panel || parts.panel.dataset.copyRemembered === '1') return; 223 parts.panel.dataset.copyRemembered = '1'; 224 parts.panel.dataset.defaultTitle = parts.title ? parts.title.textContent : ''; 225 parts.panel.dataset.defaultCopy = parts.copy ? parts.copy.textContent : ''; 226 } 227 228 function restoreVerificationEntry(form, resetForm, preserveNotice) { 229 var parts = panelElements(form); 230 if (!parts.panel) return; 231 rememberPanelCopy(parts); 232 clearVerificationCountdown(parts); 233 if (!preserveNotice) clearNotice(form); 234 if (resetForm) form.reset(); 235 form.dataset.offerVerificationStage = ''; 236 setEntryVisible(form, true); 237 parts.panel.hidden = true; 238 parts.panel.classList.remove('is-complete'); 239 if (parts.input) { parts.input.value = ''; parts.input.disabled = true; parts.input.required = false; } 240 if (parts.title) parts.title.textContent = parts.panel.dataset.defaultTitle || 'Enter the code we sent you'; 241 if (parts.copy) parts.copy.textContent = parts.panel.dataset.defaultCopy || 'Enter the verification code from your email.'; 242 if (parts.codeField) parts.codeField.hidden = false; 243 if (parts.verify) { parts.verify.hidden = true; parts.verify.disabled = false; } 244 if (parts.resend) { parts.resend.hidden = false; parts.resend.disabled = false; parts.resend.textContent = 'Resend code'; } 245 if (parts.change) parts.change.hidden = false; 246 if (parts.another) parts.another.hidden = true; 247 if (parts.countdown) { parts.countdown.hidden = true; parts.countdown.textContent = ''; } 248 restoreMainSubmit(parts); 249 } 250 251 window.domainOfferVerificationStart = function (form, response) { 252 if (!form || form.dataset.offerEmailVerification !== '1') return; 253 var parts = panelElements(form); 254 if (!parts.panel) return; 255 rememberPanelCopy(parts); 256 var email = form.querySelector('[data-match-group="email"]'); 257 form.dataset.offerVerificationStage = 'pending'; 258 setEntryVisible(form, true); 259 parts.panel.hidden = false; 260 parts.panel.classList.remove('is-complete'); 261 if (parts.email) parts.email.textContent = email ? email.value.trim() : ''; 262 if (parts.input) { 263 parts.input.value = ''; 264 parts.input.disabled = false; 265 parts.input.required = true; 266 window.setTimeout(function () { parts.input.focus(); }, 0); 267 } 268 if (parts.codeField) parts.codeField.hidden = false; 269 if (parts.verify) { parts.verify.hidden = true; parts.verify.disabled = false; } 270 if (parts.resend) parts.resend.hidden = false; 271 if (parts.change) parts.change.hidden = false;
272 if (parts.another) parts.another.hidden = true; 273 restoreMainSubmit(parts); 274 clearNotice(form); 275 startVerificationCountdown(form, response && response.verification_expires_at); 276 }; 277 278 window.domainOfferVerificationFinish = function (form) { 279 // Keep the success message visible after the optional verification 280 // panel is reset for the next offer. 281 if (form) restoreVerificationEntry(form, false, true); 282 }; 283 284 function offerCompleted(form, response) { 285 var parts = panelElements(form); 286 if (!parts.panel) return; 287 clearVerificationCountdown(parts); 288 form.dataset.offerVerificationStage = 'complete'; 289 /* Keep the PT12 form intact. The result is a compact success notice, 290 not a separate completed-card screen. */ 291 setEntryVisible(form, true); 292 parts.panel.hidden = true; 293 parts.panel.classList.remove('is-complete'); 294 if (parts.input) { parts.input.value = ''; parts.input.disabled = true; parts.input.required = false; } 295 if (parts.title) parts.title.textContent = parts.panel.dataset.defaultTitle || 'Enter the code we sent you'; 296 if (parts.copy) parts.copy.textContent = parts.panel.dataset.defaultCopy || 'Enter the verification code from your email.'; 297 if (parts.codeField) parts.codeField.hidden = false; 298 if (parts.verify) { parts.verify.hidden = true; parts.verify.disabled = false; } 299 if (parts.resend) { parts.resend.hidden = false; parts.resend.disabled = false; parts.resend.textContent = 'Resend code'; } 300 if (parts.change) parts.change.hidden = false; 301 if (parts.another) parts.another.hidden = true; 302 // A successful verification must never leave the submit control stuck 303 // on the transient âVerifyingâ¦â label, even while the completed state 304 // is being rendered or the form is prepared for another submission. 305 if (parts.main) { parts.main.hidden = true; parts.main.disabled = false; parts.main.textContent = 'Submit Offer'; } 306 if (parts.countdown) { parts.countdown.hidden = true; parts.countdown.textContent = ''; } 307 var question = form.querySelector('[data-verification-question]'); 308 if (question && response.verification_question) question.textContent = response.verification_question; 309 showNotice(form, response.message || 'Thank you! Your offer has been submitted successfully.', false); 310 } 311 312 function submitVerification(form) { 313 var parts = panelElements(form); 314 if (!parts.input || !parts.input.checkValidity()) { if (parts.input) parts.input.reportValidity(); return; } 315 var verify = parts.main || parts.verify; 316 if (verify) { verify.disabled = true; verify.textContent = 'Verifyingâ¦'; } 317 fetch(form.action, {method: 'POST', body: new FormData(form), headers: {'Accept': 'application/json'}}) 318 .then(function (response) { return response.json(); }) 319 .then(function (response) { 320 if (!response.ok) { showNotice(form, response.message || 'The verification code is incorrect.', true); return; } 321 offerCompleted(form, response); 322 }) 323 .catch(function () { showNotice(form, 'Unable to verify the code. Please try again.', true); }) 324 .finally(function () { 325 // Always restore the control after the request. On success it 326 // is hidden by offerCompleted(), but resetting the label here 327 // prevents âVerifyingâ¦â from surviving into the next attempt. 328 if (verify) { 329 verify.disabled = false; 330 verify.textContent = 'Submit Offer'; 331 } 332 }); 333 } 334 335 function resendCode(form, button) { 336 var parts = panelElements(form); 337 var expiresAt = parts.panel ? Number(parts.panel.dataset.verificationExpiresAt || 0) : 0; 338 if (expiresAt > Date.now() / 1000) { 339 setVerificationCountdown(parts, expiresAt - Date.now() / 1000); 340 return; 341 } 342 var email = form.querySelector('[data-match-group="email"]'); 343 var csrf = form.querySelector('[name="csrf"]'); 344 var domain = form.querySelector('[name="domain"]'); 345 if (!email || !email.checkValidity()) { if (email) email.focus(); showNotice(form, 'Please enter a valid email address first.', true); return; } 346 var body = new URLSearchParams(); 347 body.set('csrf', csrf ? csrf.value : ''); 348 body.set('email', email.value.trim()); 349 body.set('domain', domain ? domain.value : ''); 350 body.set('verification_flow', 'offer'); 351 button.disabled = true; 352 button.textContent = 'Sendingâ¦'; 353 fetch(form.dataset.emailCodeEndpoint || 'api/send_offer_verification.php', {method: 'POST', headers: {'Content-Type':'application/x-www-form-urlencoded', 'Accept':'application/json'}, body: body.toString()}) 354 .then(function (response) { return response.json(); }) 355 .then(function (response) { 356 if (!response.ok) { 357 showNotice(form, response.message || 'The email service rejected the resend request. Please try again later.', true); 358 if (response.verification_expires_at) startVerificationCountdown(form, response.verification_expires_at); 359 return; 360 } 361 clearNotice(form); 362 startVerificationCountdown(form, response.verification_expires_at); 363 }) 364 .catch(function () { showNotice(form, 'The verification request could not reach the server. Please check your connection and try again.', true); }) 365 .finally(function () { 366 if (form.dataset.offerVerificationStage === 'pending' && (!parts.panel || Number(parts.panel.dataset.verificationExpiresAt || 0) <= Date.now() / 1000)) { 367 button.disabled = false;
368 button.textContent = 'Resend code'; 369 } 370 }); 371 } 372 373 document.addEventListener('submit', function (event) { 374 var form = event.target.closest('form[data-offer-email-verification="1"]'); 375 if (!form || form.dataset.offerVerificationStage !== 'pending') return; 376 event.preventDefault(); 377 event.stopImmediatePropagation(); 378 submitVerification(form); 379 }, true); 380 381 document.addEventListener('click', function (event) { 382 var resend = event.target.closest('[data-resend-offer-code]'); 383 if (resend) { var resendForm = resend.closest('form'); if (resendForm) resendCode(resendForm, resend); return; } 384 var change = event.target.closest('[data-change-offer-details]'); 385 if (change) { var changeForm = change.closest('form'); if (changeForm) restoreVerificationEntry(changeForm, false); return; } 386 var another = event.target.closest('[data-new-offer]'); 387 if (another) { var anotherForm = another.closest('form'); if (anotherForm) restoreVerificationEntry(anotherForm, true); } 388 }); 389 390 function flagUrl(iso) { return 'https://flagcdn.com/24x18/' + String(iso || '').toLowerCase() + '.png'; } 391 function appendFlag(container, record, className) { 392 var holder = document.createElement('span'); 393 holder.className = className || 'phone-country-flag-art'; 394 var image = document.createElement('img'); 395 image.src = flagUrl(record.iso); 396 image.alt = record.country + ' flag'; 397 image.width = 24; image.height = 18; image.loading = 'lazy'; 398 var fallback = document.createElement('span'); 399 fallback.className = 'phone-country-emoji-fallback'; 400 fallback.textContent = record.flag || 'ð'; fallback.hidden = true; 401 image.addEventListener('error', function () { image.hidden = true; fallback.hidden = false; }); 402 holder.appendChild(image); holder.appendChild(fallback); container.appendChild(holder); 403 return holder; 404 } 405 406 function createCountryOption(record, choose) { 407 var option = document.createElement('button'); 408 option.type = 'button'; option.className = 'phone-country-option'; option.dataset.phoneSearch = record.search; 409 appendFlag(option, record, 'phone-country-option-flag'); 410 var code = document.createElement('span'); code.className = 'phone-country-option-code'; code.textContent = record.code; 411 var name = document.createElement('span'); name.className = 'phone-country-option-name'; name.textContent = record.country; 412 option.appendChild(code); option.appendChild(name); 413 option.addEventListener('click', function () { choose(record); }); 414 return option; 415 } 416 417 function enhanceCountryPicker(select) { 418 var composite = select.closest('[data-phone-composite]'); 419 if (!composite || composite.dataset.countryPickerReady === '1') return; 420 composite.dataset.countryPickerReady = '1'; 421 Array.prototype.slice.call(composite.querySelectorAll('[data-phone-flag]')).forEach(function (item) { item.remove(); }); 422 var records = Array.prototype.slice.call(select.options).map(function (option, index) { 423 var code = option.value; var country = option.dataset.country || option.textContent.trim(); var iso = option.dataset.iso || ''; 424 return {index:index, code:code, country:country, iso:iso, flag:option.dataset.flag || 'ð', search:(country + ' ' + code + ' ' + code.replace('+', '') + ' ' + iso).toLowerCase()}; 425 }).sort(function (a, b) { 426 var left = parseInt(a.code.replace('+', ''), 10); var right = parseInt(b.code.replace('+', ''), 10); 427 return left === right ? a.country.localeCompare(b.country) : left - right; 428 }); 429 select.classList.add('phone-country-native'); select.tabIndex = -1; select.setAttribute('aria-hidden', 'true'); 430 var picker = document.createElement('button'); 431 picker.type = 'button'; picker.className = 'phone-country-picker'; picker.setAttribute('aria-haspopup', 'listbox'); picker.setAttribute('aria-expanded', 'false'); 432 var selectedArt = document.createElement('span'); selectedArt.className = 'phone-country-selected-art'; 433 var selectedCode = document.createElement('span'); selectedCode.className = 'phone-country-picker-code'; 434 var arrow = document.createElement('span'); arrow.className = 'phone-country-picker-arrow'; arrow.setAttribute('aria-hidden', 'true'); arrow.textContent = 'â'; 435 picker.appendChild(selectedArt); picker.appendChild(selectedCode); picker.appendChild(arrow); 436 var menu = document.createElement('div'); menu.className = 'phone-country-menu'; menu.hidden = true; 437 var search = document.createElement('input'); search.type = 'search'; search.className = 'phone-country-search'; search.placeholder = 'Search country or code'; search.setAttribute('aria-label', 'Search country or country code'); 438 var optionsBox = document.createElement('div'); optionsBox.className = 'phone-country-options'; optionsBox.setAttribute('role', 'listbox'); 439 menu.appendChild(search); menu.appendChild(optionsBox); 440 function selectedRecord() { return records.filter(function (record) { return record.index === select.selectedIndex; })[0] || records[0]; } 441 function updateSelected() { 442 var record = selectedRecord(); if (!record) return; 443 selectedArt.replaceChildren(); appendFlag(selectedArt, record, 'phone-country-selected-flag'); 444 selectedCode.textContent = record.code; picker.title = record.country + ' ' + record.code;
445 } 446 function closeMenu() { menu.hidden = true; picker.setAttribute('aria-expanded', 'false'); } 447 function choose(record) { select.selectedIndex = record.index; select.dispatchEvent(new Event('change', {bubbles:true})); updateSelected(); closeMenu(); } 448 records.forEach(function (record) { optionsBox.appendChild(createCountryOption(record, choose)); }); 449 picker.addEventListener('click', function (event) { 450 event.preventDefault(); event.stopPropagation(); 451 var opening = menu.hidden; 452 document.querySelectorAll('.phone-country-menu:not([hidden])').forEach(function (other) { other.hidden = true; }); 453 document.querySelectorAll('.phone-country-picker[aria-expanded="true"]').forEach(function (other) { other.setAttribute('aria-expanded', 'false'); }); 454 menu.hidden = !opening; picker.setAttribute('aria-expanded', opening ? 'true' : 'false'); 455 if (opening) { search.value = ''; Array.prototype.forEach.call(optionsBox.children, function (item) { item.hidden = false; }); window.setTimeout(function () { search.focus(); }, 0); } 456 }); 457 search.addEventListener('input', function () { 458 var needle = search.value.trim().toLowerCase(); 459 var firstMatch = null; 460 Array.prototype.forEach.call(optionsBox.children, function (option) { 461 var matched = needle === '' || option.dataset.phoneSearch.indexOf(needle) !== -1; 462 option.hidden = !matched; 463 if (matched && !firstMatch) firstMatch = option; 464 }); 465 if (firstMatch) { 466 optionsBox.scrollTop = Math.max(0, firstMatch.offsetTop - 2); 467 firstMatch.classList.add('is-search-match'); 468 } 469 Array.prototype.forEach.call(optionsBox.children, function (option) { if (option !== firstMatch) option.classList.remove('is-search-match'); }); 470 }); 471 search.addEventListener('keydown', function (event) { if (event.key === 'Escape') { closeMenu(); picker.focus(); } }); 472 select.addEventListener('change', updateSelected); 473 composite.insertBefore(picker, select); composite.appendChild(menu); updateSelected(); 474 } 475 476 document.querySelectorAll('[data-phone-code]').forEach(enhanceCountryPicker); 477 document.addEventListener('click', function (event) { 478 if (!event.target.closest('.whatsapp-composite')) { 479 document.querySelectorAll('.phone-country-menu:not([hidden])').forEach(function (menu) { menu.hidden = true; }); 480 document.querySelectorAll('.phone-country-picker[aria-expanded="true"]').forEach(function (picker) { picker.setAttribute('aria-expanded', 'false'); }); 481 } 482 }); 483 document.addEventListener('keydown', function (event) { 484 if (event.key !== 'Escape') return; 485 document.querySelectorAll('.phone-country-menu:not([hidden])').forEach(function (menu) { menu.hidden = true; }); 486 document.querySelectorAll('.phone-country-picker[aria-expanded="true"]').forEach(function (picker) { picker.setAttribute('aria-expanded', 'false'); }); 487 }); 488})(); 489 490/* v71: fixed-price and offer verification use separate persisted flows while 491 keeping the existing purchase.php continuation response. */ 492(function () { 493 'use strict'; 494 function parts(form) { 495 var panel = form.querySelector('[data-purchase-email-verification-panel]'); 496 return { 497 panel: panel, 498 input: panel && panel.querySelector('[data-purchase-verification-input]'), 499 email: panel && panel.querySelector('[data-purchase-verification-email]'), 500 resend: panel && panel.querySelector('[data-resend-purchase-code]'), 501 countdown: panel && panel.querySelector('[data-purchase-email-code-notice]'), 502 main: form.querySelector('[data-purchase-main-submit]') 503 }; 504 } 505 function purchaseNotice(form, message, failed) { 506 var notice = form.querySelector('[data-purchase-notice], .form-notice'); 507 if (!notice) return; 508 notice.hidden = false; notice.style.display = 'block';
509 notice.className = (notice.matches('.modal-notice') ? 'modal-notice ' : 'notice form-notice ') + (failed ? 'error' : 'success') + ' is-dismissible'; 510 var copy = document.createElement('span'); 511 copy.textContent = String(message || '').replace(/^[ââ]\s*/, ''); 512 var close = document.createElement('button'); 513 close.type = 'button'; close.className = 'offer-notice-dismiss'; close.textContent = 'Ã'; 514 close.setAttribute('data-notice-dismiss', ''); close.setAttribute('aria-label', 'Dismiss this message'); 515 notice.replaceChildren(copy, close); 516 } 517 function remainingLabel(seconds) { 518 var total = Math.max(0, Math.ceil(seconds)); 519 return String(Math.floor(total / 60)).padStart(2, '0') + ':' + String(total % 60).padStart(2, '0'); 520 } 521 function stopCountdown(item) { 522 if (!item || !item.panel) return; 523 if (item.panel._purchaseVerificationCountdown) window.clearInterval(item.panel._purchaseVerificationCountdown); 524 item.panel._purchaseVerificationCountdown = null; 525 } 526 function startCountdown(form, expiresAt) { 527 var item = parts(form); 528 if (!item.panel) return; 529 stopCountdown(item); 530 var expiry = Number(expiresAt || 0); 531 if (!Number.isFinite(expiry) || expiry <= Date.now() / 1000) expiry = Math.floor(Date.now() / 1000) + 300; 532 item.panel.dataset.verificationExpiresAt = String(expiry); 533 var tick = function () { 534 var left = expiry - Date.now() / 1000; 535 if (item.resend) { 536 item.resend.disabled = left > 0; 537 item.resend.textContent = left > 0 ? 'Resend available in ' + remainingLabel(left) : 'Resend code'; 538 } 539 if (item.countdown) { 540 item.countdown.hidden = false; 541 item.countdown.textContent = left > 0 ? 'Code expires in ' + remainingLabel(left) + '.' : 'The code has expired. You can now request a new code.'; 542 } 543 if (left <= 0) stopCountdown(item); 544 }; 545 tick(); 546 if (expiry > Date.now() / 1000) item.panel._purchaseVerificationCountdown = window.setInterval(tick, 1000); 547 } 548 window.domainPurchaseVerificationStart = function (form, response) { 549 if (!form || form.dataset.purchaseEmailVerification !== '1') return; 550 var item = parts(form); 551 if (!item.panel) return; 552 form.dataset.purchaseVerificationStage = 'pending'; 553 item.panel.hidden = false; 554 if (item.email) { 555 var email = form.querySelector('[data-match-group="email"]'); 556 item.email.textContent = email ? email.value.trim() : ''; 557 } 558 if (item.input) { 559 item.input.disabled = false; item.input.required = true; item.input.value = ''; 560 window.setTimeout(function () { item.input.focus(); }, 0); 561 } 562 if (item.main) { item.main.disabled = false; item.main.textContent = 'Verify & Continue â'; } 563 var notice = form.querySelector('[data-purchase-notice], .form-notice'); 564 if (notice) { notice.hidden = true; notice.style.display = 'none'; notice.replaceChildren(); } 565 startCountdown(form, response && response.verification_expires_at); 566 }; 567 function resend(form, button) { 568 var item = parts(form); 569 var expiresAt = item.panel ? Number(item.panel.dataset.verificationExpiresAt || 0) : 0; 570 if (expiresAt > Date.now() / 1000) return; 571 var email = form.querySelector('[data-match-group="email"]'); 572 var csrf = form.querySelector('[name="csrf"]'); 573 var domain = form.querySelector('[name="domain"]'); 574 if (!email || !email.checkValidity()) { if (email) email.focus(); purchaseNotice(form, 'Please enter a valid email address first.', true); return; } 575 var body = new URLSearchParams(); 576 body.set('csrf', csrf ? csrf.value : ''); body.set('email', email.value.trim()); body.set('domain', domain ? domain.value : ''); body.set('verification_flow', 'purchase'); 577 button.disabled = true; button.textContent = 'Sendingâ¦'; 578 fetch(form.dataset.emailCodeEndpoint || 'api/send_offer_verification.php', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded','Accept':'application/json'}, body:body.toString()}) 579 .then(function (response) { return response.json(); }) 580 .then(function (response) { 581 if (!response.ok) { purchaseNotice(form, response.message || 'The email service rejected the resend request. Please try again later.', true); if (response.verification_expires_at) startCountdown(form, response.verification_expires_at); return; } 582 startCountdown(form, response.verification_expires_at); 583 }) 584 .catch(function () { purchaseNotice(form, 'The verification request could not reach the server. Please check your connection and try again.', true); }) 585 .finally(function () { if (!item.panel || Number(item.panel.dataset.verificationExpiresAt || 0) <= Date.now() / 1000) { button.disabled = false; button.textContent = 'Resend code'; } }); 586 } 587 document.addEventListener('click', function (event) { 588 var resendButton = event.target.closest('[data-resend-purchase-code]'); 589 if (resendButton) { var resendForm = resendButton.closest('form'); if (resendForm) resend(resendForm, resendButton); return; } 590 var changeButton = event.target.closest('[data-change-purchase-details]'); 591 if (!changeButton) return; 592 var form = changeButton.closest('form'); if (!form) return; 593 var item = parts(form); form.dataset.purchaseVerificationStage = ''; 594 stopCountdown(item); 595 if (item.panel) item.panel.hidden = true; 596 if (item.input) { item.input.value = ''; item.input.disabled = true; item.input.required = false; } 597 if (item.main) { item.main.disabled = false; item.main.textContent = 'Continue to Platform â'; } 598 }); 599})();
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.