1/******/ (() => { // webpackBootstrap 2/******/ // The require scope 3/******/ var __webpack_require__ = {}; 4/******/ 5/************************************************************************/ 6/******/ /* webpack/runtime/make namespace object */ 7/******/ (() => { 8/******/ // define __esModule on exports 9/******/ __webpack_require__.r = (exports) => { 10/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 11/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 12/******/ } 13/******/ Object.defineProperty(exports, '__esModule', { value: true }); 14/******/ }; 15/******/ })(); 16/******/ 17/************************************************************************/ 18var __webpack_exports__ = {}; 19// This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules. 20(() => { 21/*!****************************!*\ 22 !*** ./src/js/frontend.js ***! 23 \****************************/ 24/* global wbnb_params */ 25 26(function ($) { 27 'use strict'; 28 29 /** 30 * Initialize on document ready 31 */ 32 $(document).ready(function () { 33 handleAjaxAddToCart(); 34 handleBuyNowButtons(); 35 WBNB_Popup.init(); 36 $('button.wc-buy-now-btn-single').on('click', function (e) { 37 // Check if a valid variation is selected before proceeding buy now button 38 if (!validateVariation($(this), e)) { 39 return; 40 } 41 42 // To fix other plugins ajax trigger on buy now button 43 if (!wbnb_params.is_popup) { 44 e.stopImmediatePropagation(); 45 } 46 }); 47 }); 48 49 /** 50 * Check if a valid variation is selected before proceeding 51 * @return {boolean} false if validation failed (event stopped), true otherwise 52 */ 53 function validateVariation($button, e) { 54 if ($button.is('.disabled')) { 55 e.preventDefault(); 56 e.stopImmediatePropagation(); 57 if ($button.is('.wc-variation-is-unavailable')) { 58 window.alert(wbnb_params.i18n_unavailable_text); 59 } else if ($button.is('.wc-variation-selection-needed')) { 60 window.alert(wbnb_params.i18n_make_a_selection_text); 61 } 62 return false; 63 } 64 return true; 65 } 66 67 /** 68 * Data collector for form data 69 */ 70 const DataCollector = { 71 /** 72 * Collect variation attributes from form 73 */ 74 collectVariations($form) { 75 const variation = {}; 76 $form.find('select[name^="attribute_"], input[name^="attribute_"]').each(function () { 77 const name = $(this).attr('name'); 78 variation[name] = $(this).val(); 79 }); 80 return variation; 81 }, 82 /** 83 * Collect group product quantities 84 */ 85 collectQuantities($form) { 86 const quantities = {}; 87 $form.find('input[name^="quantity"]').each(function () { 88 const matches = $(this).attr('name').match(/^quantity\[(\d+)]$/); 89 if (matches?.[1]) { 90 quantities[matches[1]] = parseInt($(this).val(), 10) || 0; 91 } 92 }); 93 return quantities; 94 }, 95 /** 96 * Get product ID from form 97 */ 98 getProductId($form, isBuyNow = false, $button = null) { 99 if (isBuyNow && $button) { 100 return $button.val(); 101 } 102 return $form.find('button[name="add-to-cart"], input[name="add-to-cart"], input[name="product_id"]').val(); 103 }, 104 /** 105 * Collect all form data for cart action 106 */ 107 collectFormData($form, isBuyNow = false, $button = null) { 108 const productId = this.getProductId($form, isBuyNow, $button); 109 const quantity = $form.find('input[name="quantity"]').val() || 1; 110 const variationId = $form.find('input[name="variation_id"]').val() || 0; 111 const variation = this.collectVariations($form); 112 const quantities = this.collectQuantities($form); 113 return { 114 productId, 115 quantity, 116 variationId, 117 variation, 118 quantities 119 }; 120 } 121 }; 122 123 /** 124 * Handle AJAX cart operations 125 */ 126 const AjaxCartHandler = { 127 /** 128 * Send ajax add to cart request 129 */ 130 sendRequest(data, $button, isBuyNow, isPopup) { 131 $button.addClass('loading'); 132 return $.post(wbnb_params.ajax_url, data).done(response => this.handleSuccess(response, $button, isBuyNow, isPopup)).fail(response => this.handleError(response)).always(() => $button.removeClass('loading')); 133 }, 134 /** 135 * Handle successful AJAX response 136 */ 137 handleSuccess(response, $button, isBuyNow, isPopup) { 138 if (!response.success) { 139 this.handleError(response, $button); 140 return; 141 } 142 const { 143 fragments = {}, 144 cart_hash = '', 145 redirect_url = '', 146 checkout_template = '', 147 notices = '' 148 } = response.data || {}; 149 150 // Trigger standard WC events (classic theme) 151 $(document.body).trigger('added_to_cart', [fragments, cart_hash, $button]); 152 153 // Trigger WooCommerce Blocks native event for the Mini Cart block 154 document.body.dispatchEvent(new CustomEvent('wc-blocks_added_to_cart', { 155 bubbles: true, 156 cancelable: true, 157 detail: { 158 fragments, 159 cart_hash 160 } 161 })); 162 if (isBuyNow) { 163 this.handleBuyNowRedirect(isPopup, checkout_template, redirect_url); 164 } else { 165 // Standard Add to Cart: Stay on page 166 $(document.body).trigger('wc_fragment_refresh'); 167 168 // Inject WC notices ("Product added to cart [View Cart]"). 169 // Handles FSE themes where the fragment system returns no notices. 170 this.injectWooCommerceNotices(notices); 171 } 172 }, 173 /** 174 * Handle buy now redirect or popup 175 */ 176 handleBuyNowRedirect(isPopup, checkoutTemplate, redirectUrl) { 177 if (isPopup) { 178 const hasPopup = $('#wbnb-popup-overlay').length > 0; 179 if (hasPopup && checkoutTemplate) { 180 WBNB_Popup.open(checkoutTemplate); 181 } else { 182 window.location.href = redirectUrl || wbnb_params.checkout_url; 183 } 184 } else { 185 window.location.href = redirectUrl; 186 } 187 }, 188 /** 189 * Inject WooCommerce notices HTML into the page. 190 * 191 * In classic themes, the "Product added to cart [View Cart]" notice is included 192 * in cart fragments and injected automatically. In FSE themes the fragment system 193 * is empty (no widget areas registered), so we receive the notices HTML from the 194 * PHP response and inject it ourselves. 195 * 196 * Creates .woocommerce-notices-wrapper at the top of <main> (or <body> as fallback) 197 * if it does not already exist, matching WooCommerce's own DOM convention. 198 * Auto-hides success notices after 5 s, mirroring WooCommerce JS behaviour. 199 * 200 * Safe to call with an empty string â returns immediately without DOM changes. 201 */ 202 injectWooCommerceNotices(noticesHtml) { 203 if (!noticesHtml) { 204 return; 205 } 206 207 // Find or create the notices wrapper 208 let $wrapper = $('.woocommerce-notices-wrapper').first(); 209 if (!$wrapper.length) {
210 $wrapper = $('<div class="woocommerce-notices-wrapper"></div>'); 211 // Prefer injecting at the top of <main>, fall back to after <body> open 212 const $main = $('main').first(); 213 if ($main.length) { 214 $main.prepend($wrapper); 215 } else { 216 $('body').prepend($wrapper); 217 } 218 } 219 $wrapper.html(noticesHtml); 220 221 // Auto-dismiss success messages after 5 s (matches WooCommerce default JS) 222 setTimeout(() => { 223 $wrapper.find('.woocommerce-message').fadeOut(400, function () { 224 $(this).remove(); 225 }); 226 }, 5000); 227 }, 228 /** 229 * Handle AJAX errors 230 */ 231 handleError(response) { 232 const message = response.data?.message; 233 if (message) { 234 alert(message); 235 } else { 236 console.log(response); 237 } 238 } 239 }; 240 241 /** 242 * Popup manager 243 */ 244 const WBNB_Popup = { 245 isOpen: false, 246 isLoading: false, 247 /** 248 * Initialize popup event listeners 249 */ 250 init() { 251 $(document).on('click', '.wbnb-popup-close, #wbnb-popup-overlay', e => { 252 if ($(e.target).is('#wbnb-popup-overlay') || $(e.target).hasClass('wbnb-popup-close')) { 253 this.close(); 254 } 255 }); 256 $(document).on('keyup', e => { 257 if (this.isOpen && (e.key === 'Escape' || e.keyCode === 27)) { 258 this.close(); 259 } 260 }); 261 262 // Prevent closing when clicking inside content 263 $(document).on('click', '#wbnb-popup-content', e => { 264 e.stopPropagation(); 265 }); 266 }, 267 /** 268 * Open popup with optional HTML content 269 */ 270 open(html) { 271 if (this.isOpen || this.isLoading) { 272 return; 273 } 274 this.isOpen = true; 275 this.isLoading = true; 276 const $overlay = $('#wbnb-popup-overlay'); 277 const $inner = $overlay.find('.wbnb-popup-inner'); 278 $overlay.addClass('wbnb-show'); 279 if (html) { 280 $inner.html(html); 281 this.initWooCommerceCheckout(); 282 } else { 283 $inner.html('<div class="wbnb-loader">Loading Checkout...</div>'); 284 } 285 this.isLoading = false; 286 }, 287 /** 288 * Initialize WooCommerce checkout in popup 289 */ 290 initWooCommerceCheckout() { 291 if (typeof wc_checkout_params === 'undefined') { 292 return; 293 } 294 const $popupContent = $('#wbnb-popup-content'); 295 const $checkoutForm = $popupContent.find('form.checkout'); 296 if (!$checkoutForm.length) { 297 return; 298 } 299 300 // Trigger standard WC checkout initialization 301 $(document.body).trigger('init_checkout'); 302 $(document.body).trigger('update_checkout'); 303 304 // Intercept form submission to handle errors in popup 305 $checkoutForm.off('submit.wbnb_popup').on('submit.wbnb_popup', function (e) { 306 const $form = $(this); 307 if ($form.is('.processing')) { 308 return false; 309 } 310 311 // Get payment method 312 const paymentMethod = $form.find('input[name="payment_method"]:checked').val(); 313 314 // Let payment gateways do their thing 315 if ($form.triggerHandler('checkout_place_order') !== false && $form.triggerHandler('checkout_place_order_' + paymentMethod) !== false) { 316 $form.addClass('processing'); 317 318 // Block the form 319 $form.block({ 320 message: null, 321 overlayCSS: { 322 background: '#fff', 323 opacity: 0.6 324 } 325 }); 326 327 // Submit via AJAX 328 $.ajax({ 329 type: 'POST', 330 url: wc_checkout_params.checkout_url, 331 data: $form.serialize(), 332 dataType: 'json', 333 success: function (result) { 334 try { 335 if ('success' === result.result) { 336 // Successful order - redirect 337 if (-1 === result.redirect.indexOf('https://') || -1 === result.redirect.indexOf('http://')) { 338 window.location = result.redirect; 339 } else { 340 window.location = decodeURI(result.redirect); 341 } 342 } else if ('failure' === result.result) { 343 throw 'Result failure'; 344 } else { 345 throw 'Invalid response'; 346 } 347 } catch (err) { 348 // Reload page if requested 349 if (true === result.reload) { 350 window.location.reload(); 351 return; 352 } 353
354 // Trigger update in case we need a fresh nonce 355 if (true === result.refresh) { 356 $(document.body).trigger('update_checkout'); 357 } 358 359 // Display errors in popup 360 if (result.messages) { 361 WBNB_Popup.showCheckoutError(result.messages, $form); 362 } else { 363 WBNB_Popup.showCheckoutError('<div class="woocommerce-error">' + wc_checkout_params.i18n_checkout_error + '</div>', $form); 364 } 365 } 366 }, 367 error: function (jqXHR, textStatus, errorThrown) { 368 WBNB_Popup.showCheckoutError('<div class="woocommerce-error">' + errorThrown + '</div>', $form); 369 } 370 }); 371 } 372 return false; 373 }); 374 }, 375 /** 376 * Show checkout error in popup 377 */ 378 showCheckoutError(error_message, $form) { 379 const $popupContent = $('#wbnb-popup-content'); 380 381 // Remove existing errors 382 $popupContent.find('.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message, .is-error, .is-success').remove(); 383 384 // Add error to form 385 $form.prepend('<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout">' + error_message + '</div>'); 386 387 // Unblock and remove processing class 388 $form.removeClass('processing').unblock(); 389 $form.find('.input-text, select, input:checkbox').trigger('validate').trigger('blur'); 390 391 // Scroll to error 392 this.scrollToNotices(); 393 394 // Focus on error 395 $form.find('.woocommerce-error[tabindex="-1"]').focus(); 396 397 // Trigger event 398 $(document.body).trigger('checkout_error', [error_message]); 399 }, 400 /** 401 * Scroll to error notices within popup 402 */ 403 scrollToNotices() { 404 const $popupContent = $('#wbnb-popup-content'); 405 const $scrollElement = $popupContent.find('.woocommerce-NoticeGroup-checkout, .woocommerce-error').first(); 406 if ($scrollElement.length) { 407 const scrollTop = $scrollElement.position().top - 20; 408 $popupContent.animate({ 409 scrollTop: scrollTop 410 }, 500); 411 } 412 }, 413 /** 414 * Close popup 415 */ 416 close() { 417 // Remove form submission handler 418 $('#wbnb-popup-content form.checkout').off('submit.wbnb_popup'); 419 this.isOpen = false; 420 $('#wbnb-popup-overlay').removeClass('wbnb-show'); 421 } 422 }; 423 424 /** 425 * Event handler for standard add to cart 426 */ 427 function handleAjaxAddToCart() { 428 if (!wbnb_params.is_ajax) { 429 return; 430 } 431 $(document).on('click', '.single_add_to_cart_button:not([data-wc-buy-now="true"], .disabled)', function (e) { 432 const $button = $(this); 433 const $form = $button.closest('form.cart'); 434 if ($form.length === 0) { 435 return; 436 } 437 438 // Check if the product type is supported for this specific form/button 439 let productType = $button.data('product_type') || $form.data('product_type'); 440 if (!productType) { 441 // Fallback detection 442 if ($form.hasClass('variations_form')) { 443 productType = 'variable'; 444 } else if ($form.find('.grouped_form').length || $form.find('table.group_table').length) { 445 productType = 'grouped'; 446 } else { 447 // Fallback to body class for standard single product pages 448 const bodyClasses = $('body').attr('class') || ''; 449 const match = bodyClasses.match(/product-type-([^\s]+)/); 450 productType = match ? match[1] : 'simple'; 451 } 452 } 453 if (wbnb_params.product_types && !wbnb_params.product_types.includes(productType)) { 454 return; // Let WooCommerce handle it (standard form submit) 455 } 456 e.preventDefault(); 457 const { 458 productId, 459 quantity, 460 variationId, 461 variation, 462 quantities 463 } = DataCollector.collectFormData($form); 464 465 // Validate group product quantities 466 if ($form.find('input[name^="quantity"]').length > 1 && Object.values(quantities).every(q => q === 0)) {
467 alert('Please choose the quantity of items you wish to add to your cart.'); 468 return; 469 } 470 const data = { 471 action: 'wbnb_add_to_cart', 472 is_buy_now: false, 473 product_id: productId, 474 quantity, 475 quantities, 476 variation_id: variationId, 477 variation, 478 nonce: wbnb_params.nonce 479 }; 480 $(document.body).trigger('adding_to_cart', [$button, data]); 481 AjaxCartHandler.sendRequest(data, $button, false, false); 482 }); 483 } 484 485 /** 486 * Event handler for buy now buttons 487 */ 488 function handleBuyNowButtons() { 489 $(document).on('click', '.wc-buy-now-btn[data-redirect-location="popup-checkout"]', function (e) { 490 const $this = $(this); 491 492 // Variation validation (same as WooCommerce's Add to Cart) 493 if (!validateVariation($this, e)) { 494 return; 495 } 496 497 // Check for product type support 498 const productType = $this.data('product_type') || $this.closest('form').data('product_type') || 'simple'; 499 if (wbnb_params.product_types && !wbnb_params.product_types.includes(productType)) { 500 return; // Let browser handle it naturally 501 } 502 503 // Only handle with JavaScript if it's a buy now button with popup-checkout 504 e.preventDefault(); 505 e.stopImmediatePropagation(); 506 if (!wbnb_params.is_popup) { 507 // Let browser handle it naturally (form submit or link navigation) 508 return; 509 } 510 511 // Handle button submit 512 if ($this.is('button[type="submit"]') || $this.is('input[type="submit"]')) { 513 handleButtonSubmitForPopup($this); 514 } 515 // Handle link click 516 else if ($this.is('a')) { 517 handleLinkClickForPopup($this); 518 } 519 }); 520 } 521 522 /** 523 * Handle button submit for popup checkout 524 */ 525 function handleButtonSubmitForPopup($button) { 526 const $form = $button.closest('form'); 527 if ($form.length === 0) { 528 return; 529 } 530 const { 531 productId, 532 quantity, 533 variationId, 534 variation 535 } = DataCollector.collectFormData($form, true, $button); 536 const data = { 537 action: 'wbnb_add_to_cart', 538 product_id: productId, 539 is_buy_now: true, 540 quantity, 541 variation_id: variationId, 542 variation, 543 nonce: wbnb_params.nonce 544 }; 545 AjaxCartHandler.sendRequest(data, $button, true, true); 546 } 547 548 /** 549 * Handle link click for popup checkout from archive pages 550 */ 551 function handleLinkClickForPopup($link) { 552 const productId = $link.data('product_id'); 553 const quantity = $link.data('quantity') || 1; 554 const data = { 555 action: 'wbnb_add_to_cart', 556 product_id: productId, 557 is_buy_now: true, 558 quantity, 559 nonce: wbnb_params.nonce 560 }; 561 AjaxCartHandler.sendRequest(data, $link, true, true); 562 } 563})(jQuery); 564})(); 565 566// This entry needs to be wrapped in an IIFE because it needs to be in strict mode. 567(() => { 568"use strict"; 569/*!********************************!*\ 570 !*** ./src/scss/frontend.scss ***! 571 \********************************/ 572__webpack_require__.r(__webpack_exports__); 573// extracted by mini-css-extract-plugin 574 575})(); 576 577/******/ })() 578; 579//# sourceMappingURL=frontend.js.map
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.