1/** 2 * CKit Guest Menu 3 * 4 * Dropdown toggle, modal open/close, REST form submit, success state swap, 5 * welcome toast on page load, sign out click handler. Vanilla JS, ES5 6 * compatible to play nicely with older theme JS pipelines. 7 */ 8(function () { 9 'use strict'; 10 11 var config = window.hrckGuestMenu || {}; 12 var strings = config.strings || {}; 13 14 function ready(fn) { 15 if (document.readyState !== 'loading') { 16 fn(); 17 } else { 18 document.addEventListener('DOMContentLoaded', fn); 19 } 20 } 21 22 ready(function () { 23 initDropdowns(); 24 initModal(); 25 showWelcomeToastIfFlashed(); 26 showErrorToastIfFlagged(); 27 }); 28 29 // ------------------------------------------------------------------ 30 // Dropdowns 31 // ------------------------------------------------------------------ 32 function initDropdowns() { 33 var menus = document.querySelectorAll('[data-hrck-guest-menu]'); 34 for (var i = 0; i < menus.length; i++) { 35 bindMenu(menus[i]); 36 } 37 38 document.addEventListener('click', function (e) { 39 closeAllMenusExcept(e.target); 40 }); 41 42 document.addEventListener('keydown', function (e) { 43 if (e.key === 'Escape') { 44 closeAllMenusExcept(null); 45 } 46 }); 47 } 48 49 function bindMenu(menu) { 50 var button = menu.querySelector('.hrck-guest-menu-button'); 51 var dropdown = menu.querySelector('.hrck-guest-menu-dropdown'); 52 if (!button || !dropdown) { return; } 53 54 button.addEventListener('click', function (e) { 55 e.stopPropagation(); 56 var isOpen = dropdown.hasAttribute('hidden') === false; 57 closeAllMenusExcept(menu); 58 if (isOpen) { 59 dropdown.setAttribute('hidden', ''); 60 button.setAttribute('aria-expanded', 'false'); 61 } else { 62 dropdown.removeAttribute('hidden'); 63 button.setAttribute('aria-expanded', 'true'); 64 } 65 }); 66 67 // Menu item click handlers (modal trigger and REST trigger) 68 dropdown.addEventListener('click', function (e) { 69 var item = e.target.closest('[data-hrck-item]'); 70 if (!item) { return; } 71 72 if (item.hasAttribute('data-hrck-modal')) { 73 e.preventDefault(); 74 openModal(item.getAttribute('data-hrck-modal')); 75 dropdown.setAttribute('hidden', ''); 76 button.setAttribute('aria-expanded', 'false'); 77 return; 78 } 79 80 if (item.hasAttribute('data-hrck-rest')) { 81 e.preventDefault(); 82 var url = item.getAttribute('data-hrck-rest'); 83 restPost(url, {}).then(function () { 84 window.location.reload(); 85 }); 86 return; 87 } 88 }); 89 } 90 91 function closeAllMenusExcept(insideNode) { 92 var menus = document.querySelectorAll('[data-hrck-guest-menu]'); 93 for (var i = 0; i < menus.length; i++) { 94 if (insideNode && menus[i].contains(insideNode)) { continue; } 95 var dd = menus[i].querySelector('.hrck-guest-menu-dropdown'); 96 var btn = menus[i].querySelector('.hrck-guest-menu-button'); 97 if (dd && !dd.hasAttribute('hidden')) { 98 dd.setAttribute('hidden', ''); 99 if (btn) { btn.setAttribute('aria-expanded', 'false'); } 100 } 101 } 102 } 103 104 // ------------------------------------------------------------------ 105 // Modal 106 // ------------------------------------------------------------------ 107 var resendTimer = null; 108 var lastSubmittedEmail = ''; 109 110 function initModal() { 111 var modal = document.getElementById('hrck-signin-modal'); 112 if (!modal) { return; } 113 114 // Close triggers 115 modal.addEventListener('click', function (e) { 116 var closer = e.target.closest('[data-hrck-close]'); 117 if (closer) { 118 closeModal(modal); 119 } 120 }); 121 122 document.addEventListener('keydown', function (e) { 123 if (e.key === 'Escape' && !modal.hasAttribute('hidden')) { 124 closeModal(modal); 125 } 126 }); 127 128 // Form submit 129 var form = modal.querySelector('[data-hrck-signin-form]'); 130 if (form) { 131 form.addEventListener('submit', function (e) { 132 e.preventDefault(); 133 handleFormSubmit(modal, form); 134 }); 135 } 136 137 // Resend 138 var resendBtn = modal.querySelector('[data-hrck-resend]'); 139 if (resendBtn) { 140 resendBtn.addEventListener('click', function (e) { 141 e.preventDefault(); 142 if (resendBtn.disabled) { return; } 143 handleResend(modal); 144 }); 145 } 146 147 // Change email 148 var changeBtn = modal.querySelector('[data-hrck-change]'); 149 if (changeBtn) { 150 changeBtn.addEventListener('click', function (e) { 151 e.preventDefault(); 152 swapState(modal, 'form'); 153 }); 154 } 155 } 156 157 function openModal(target) { 158 if (target !== 'signin-modal') { return; } 159 var modal = document.getElementById('hrck-signin-modal'); 160 if (!modal) { return; } 161 modal.removeAttribute('hidden'); 162 var input = modal.querySelector('[name="email"]'); 163 if (input) { 164 setTimeout(function () { input.focus(); }, 40); 165 } 166 } 167 168 function closeModal(modal) { 169 modal.setAttribute('hidden', ''); 170 swapState(modal, 'form'); 171 clearError(modal); 172 if (resendTimer) { 173 clearInterval(resendTimer); 174 resendTimer = null; 175 } 176 // Stream 2: clear pending action state 177 if (window.HRCK) { 178 window.HRCK._pendingAction = null; 179 } 180 var labelEl = document.querySelector('[data-hrck-context-label]'); 181 if (labelEl) { 182 labelEl.textContent = ''; 183 labelEl.setAttribute('hidden', ''); 184 } 185 } 186 187 function swapState(modal, state) { 188 var states = modal.querySelectorAll('[data-hrck-state]'); 189 for (var i = 0; i < states.length; i++) { 190 if (states[i].getAttribute('data-hrck-state') === state) { 191 states[i].removeAttribute('hidden'); 192 } else { 193 states[i].setAttribute('hidden', ''); 194 } 195 } 196 } 197 198 function handleFormSubmit(modal, form) { 199 clearError(modal); 200 var emailInput = form.querySelector('[name="email"]'); 201 var consentInput = form.querySelector('[name="consent"]'); 202 var submitBtn = form.querySelector('.hrck-guest-modal-submit'); 203 204 var email = (emailInput.value || '').trim(); 205 if (!isValidEmail(email)) { 206 showError(modal, strings.invalidEmail || 'Please enter a valid email address.'); 207 emailInput.focus(); 208 return; 209 } 210 211 lastSubmittedEmail = email; 212 213 if (submitBtn) { submitBtn.disabled = true; } 214 215 var postBody = { 216 email: email, 217 consent: consentInput ? !!consentInput.checked : false, 218 origin_url: window.location.href 219 }; 220 221 if (window.HRCK && window.HRCK._pendingAction) { 222 postBody.pending_action = window.HRCK._pendingAction; 223 window.HRCK._pendingAction = null; 224 } 225 226 restPost(config.restBase + '/signin', postBody).then(function (res) { 227 if (submitBtn) { submitBtn.disabled = false; } 228 if (res && res.success) { 229 var emailEl = modal.querySelector('[data-hrck-success-email]'); 230 if (emailEl) { emailEl.textContent = email; } 231 swapState(modal, 'success'); 232 startResendTimer(modal); 233 } else {
234 showError(modal, (res && res.message) || strings.sendFailure || 'Could not send sign in link. Please try again.'); 235 } 236 }).catch(function (err) { 237 if (submitBtn) { submitBtn.disabled = false; } 238 if (err && err.status === 429) { 239 showError(modal, strings.rateLimited || 'Too many requests. Please wait a few minutes before trying again.'); 240 } else { 241 showError(modal, strings.sendFailure || 'Could not send sign in link. Please try again.'); 242 } 243 }); 244 } 245 246 function handleResend(modal) { 247 if (!lastSubmittedEmail) { return; } 248 restPost(config.restBase + '/signin', { 249 email: lastSubmittedEmail, 250 consent: true, 251 origin_url: window.location.href 252 }).then(function (res) { 253 if (res && res.success) { 254 startResendTimer(modal); 255 } else { 256 showError(modal, (res && res.message) || strings.sendFailure); 257 swapState(modal, 'form'); 258 } 259 }); 260 } 261 262 function startResendTimer(modal) { 263 var resendBtn = modal.querySelector('[data-hrck-resend]'); 264 if (!resendBtn) { return; } 265 266 var seconds = parseInt(config.resendCooldown, 10) || 30; 267 var baseLabel = (strings.resend || 'Resend link'); 268 resendBtn.disabled = true; 269 resendBtn.textContent = baseLabel + ' (' + seconds + 's)'; 270 271 if (resendTimer) { clearInterval(resendTimer); } 272 resendTimer = setInterval(function () { 273 seconds--; 274 if (seconds <= 0) { 275 clearInterval(resendTimer); 276 resendTimer = null; 277 resendBtn.disabled = false; 278 resendBtn.textContent = baseLabel; 279 } else { 280 resendBtn.textContent = baseLabel + ' (' + seconds + 's)'; 281 } 282 }, 1000); 283 } 284 285 function showError(modal, message) { 286 var errEl = modal.querySelector('[data-hrck-error]'); 287 if (!errEl) { return; } 288 errEl.textContent = message; 289 errEl.removeAttribute('hidden'); 290 } 291 292 function clearError(modal) { 293 var errEl = modal.querySelector('[data-hrck-error]'); 294 if (!errEl) { return; } 295 errEl.textContent = ''; 296 errEl.setAttribute('hidden', ''); 297 } 298 299 function isValidEmail(value) { 300 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); 301 } 302 303 // ------------------------------------------------------------------ 304 // REST helper 305 // ------------------------------------------------------------------ 306 function restPost(url, body) { 307 return fetch(url, { 308 method: 'POST', 309 credentials: 'same-origin', 310 headers: { 311 'Content-Type': 'application/json', 312 'X-WP-Nonce': config.nonce || '' 313 }, 314 body: JSON.stringify(body) 315 }).then(function (response) { 316 if (response.status === 429) { 317 var err = new Error('rate limited'); 318 err.status = 429; 319 throw err; 320 } 321 return response.json().catch(function () { return {}; }); 322 }); 323 } 324 325 // ------------------------------------------------------------------ 326 // Welcome toast 327 // ------------------------------------------------------------------ 328 function showWelcomeToastIfFlashed() { 329 var flash = config.welcomeFlash; 330 if (!flash) { return; } 331 332 // Stream 2: rich toast from pending action dispatch 333 if (flash.type === 'success' || flash.type === 'error') { 334 var message = flash.message || ''; 335 if (flash.detail) { 336 message += ' ' + flash.detail; 337 } 338 if (message) { 339 showToast(message); 340 } 341 cleanupQueryParam('hrck_welcome'); 342 return; 343 } 344 345 // Stream 1: simple welcome back toast 346 var flashEmail = flash.email || ''; 347 if (!flashEmail && /[\?&]hrck_welcome=1/.test(window.location.search)) { 348 flashEmail = 'you'; 349 } 350 if (!flashEmail) { return; } 351
352 showToast((strings.welcomeBack || 'Welcome back, %s').replace('%s', flashEmail)); 353 cleanupQueryParam('hrck_welcome'); 354 } 355 356 function showErrorToastIfFlagged() { 357 if (/[\?&]hrck_magic_error=1/.test(window.location.search)) { 358 showToast(strings.expiredLink || 'Your sign in link expired. Please request a new one.'); 359 cleanupQueryParam('hrck_magic_error'); 360 } 361 } 362 363 function showToast(message) { 364 var toast = document.createElement('div'); 365 toast.className = 'hrck-guest-toast'; 366 toast.textContent = message; 367 toast.setAttribute('role', 'status'); 368 document.body.appendChild(toast); 369 370 // Next frame so the transition plays 371 requestAnimationFrame(function () { 372 toast.classList.add('hrck-guest-toast--visible'); 373 }); 374 375 setTimeout(function () { 376 toast.classList.remove('hrck-guest-toast--visible'); 377 setTimeout(function () { 378 if (toast.parentNode) { toast.parentNode.removeChild(toast); } 379 }, 400); 380 }, 4000); 381 } 382 383 function cleanupQueryParam(name) { 384 if (!window.history || !window.history.replaceState) { return; } 385 var url = new URL(window.location.href); 386 url.searchParams.delete(name); 387 window.history.replaceState({}, document.title, url.toString()); 388 } 389 390 // ------------------------------------------------------------------ 391 // Stream 2: public API for tool handoff to sign-in modal 392 // ------------------------------------------------------------------ 393 window.HRCK = window.HRCK || {}; 394 window.HRCK.isSignedIn = !!config.isSignedIn; 395 396 /** 397 * Open the sign-in modal with a pending action. 398 * 399 * @param {string} actionName Filter hook suffix (e.g. 'create_price_alert') 400 * @param {object} params Tool-specific parameters to carry through 401 * @param {string} contextLabel Optional subtitle shown in the sign-in modal 402 */ 403 window.HRCK.openSigninWithAction = function (actionName, params, contextLabel) { 404 window.HRCK._pendingAction = { 405 action: actionName, 406 params: params || {} 407 }; 408 409 var labelEl = document.querySelector('[data-hrck-context-label]'); 410 if (labelEl) { 411 if (contextLabel) { 412 labelEl.textContent = contextLabel; 413 labelEl.removeAttribute('hidden'); 414 } else { 415 labelEl.textContent = ''; 416 labelEl.setAttribute('hidden', ''); 417 } 418 } 419 420 openModal('signin-modal'); 421 422 // v1.37.1: pre-fill the sign-in email input from the handoff params 423 // when the calling tool captured one. Avoids making the user type the 424 // same email in two consecutive modals (Wishlist Share, Price Alerts, 425 // Booking Reminder all flow through this path). 426 if (params && params.email) { 427 var preFillInput = document.querySelector('#hrck-signin-modal [name="email"]'); 428 if (preFillInput && !preFillInput.value) { 429 preFillInput.value = params.email; 430 } 431 } 432 }; 433})();
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.