1(function($) { 2 'use strict'; 3 4 // Configuration 5 var config = { 6 ajaxUrl: custom_mini_cart_params.ajax_url, 7 nonce: custom_mini_cart_params.nonce, 8 isUpdating: false, 9 miniCartOpen: false 10 }; 11 12 // Donation-specific configuration 13 var donationConfig = { 14 isProcessing: false 15 }; 16 17 // Global cart state 18 let miniCartOpen = false; 19 let isClosing = false; 20 21 // Initialize 22 $(document).ready(function() { 23 initMiniCartAJAX(); 24 initDonationAJAX(); 25 initCouponAJAX(); // Add this line 26 initCartOpenClose(); 27 28 // Initialize donation form values 29 initializeDonationForm(); 30 31 // Initialize cart state 32 $('.headerminicart').removeClass('open cartactive fast-open'); 33 $('html').removeClass('cart-open'); 34 }); 35 36 // ========================= 37 // MINI CART AJAX HANDLERS 38 // ========================= 39 40 function initMiniCartAJAX() { 41 // Handle quantity buttons 42 $(document).on('click', '.qib-button', function(e) { 43 e.preventDefault(); 44 e.stopPropagation(); 45 46 // Global protection check 47 if (config.isUpdating) { 48 console.log('Cart is already updating, please wait...'); 49 showMessage('Please wait, cart is updating...', 'info'); 50 return false; 51 } 52 53 var $button = $(this); 54 55 // Check if button is already disabled 56 if ($button.hasClass('disabled') || $button.prop('disabled')) { 57 console.log('Button is disabled, ignoring click'); 58 return false; 59 } 60 61 var $wrapper = $button.closest('.qib-button-wrapper'); 62 var cartItemKey = $wrapper.data('cart-item-key'); 63 var $input = $wrapper.find('.cart-quantity-input'); 64 var action = $button.data('action'); 65 var currentQty = parseInt($input.val()) || 1; 66 67 // Check if input is disabled 68 if ($input.prop('disabled')) { 69 console.log('Quantity input is disabled, ignoring click'); 70 return false; 71 } 72 73 if (action === 'increase') { 74 currentQty++; 75 } else if (action === 'decrease') { 76 currentQty--; 77 if (currentQty < 1) currentQty = 1; 78 } 79 80 $input.val(currentQty); 81 updateCartItem(cartItemKey, currentQty, $wrapper); 82 83 return false; 84 }); 85 86 // Handle quantity input directly 87 $(document).on('change', '.cart-quantity-input', function(e) { 88 e.stopPropagation(); 89 90 // Global protection check 91 if (config.isUpdating) { 92 console.log('Cart is already updating, please wait...'); 93 showMessage('Please wait, cart is updating...', 'info'); 94 return false; 95 } 96 97 var $input = $(this); 98 99 // Check if input is disabled 100 if ($input.prop('disabled')) { 101 console.log('Input is disabled, ignoring change'); 102 return false; 103 } 104 105 var cartItemKey = $input.data('cart-item-key'); 106 var quantity = parseInt($input.val()) || 1; 107 108 // VALIDATION: Minimum quantity is 1 109 if (quantity < 1) { 110 quantity = 1; 111 $input.val(1); // Reset to minimum 112 showMessage('Minimum quantity must be 1', 'error'); 113 updateCartItem(cartItemKey, quantity, $input.closest('.qib-button-wrapper')); 114 //return false; // Don't proceed with AJAX 115 } 116 117 updateCartItem(cartItemKey, quantity, $input.closest('.qib-button-wrapper')); 118 119 return false; 120}); 121 122// Add input event handler for real-time validation 123$(document).on('input', '.cart-quantity-input', function(e) { 124 var $input = $(this); 125 var value = $input.val(); 126 127 // Remove non-numeric characters 128 var numericValue = value.replace(/[^0-9]/g, ''); 129 130 // Don't allow 0 as the first digit 131 if (numericValue === '0') { 132 $input.val('1'); 133 return; 134 } 135 136 // Remove leading zeros 137 if (numericValue.length > 1 && numericValue.charAt(0) === '0') { 138 numericValue = numericValue.replace(/^0+/, ''); 139 } 140 141 $input.val(numericValue); 142}); 143 144// Add blur event handler to validate when user leaves the field
145$(document).on('blur', '.cart-quantity-input', function(e) { 146 var $input = $(this); 147 var quantity = parseInt($input.val()) || 0; 148 149 if (quantity < 1) { 150 $input.val(1); 151 // Optional: Show a subtle message 152 // showMessage('Minimum quantity is 1', 'error'); 153 } 154}); 155 156 // Handle remove items - INCLUDES DONATION ITEMS 157 $(document).on('click', '.remove-cart-item, .remove-donation-btn', function(e) { 158 e.preventDefault(); 159 e.stopPropagation(); 160 161 // Global protection check 162 if (config.isUpdating) { 163 console.log('Cart is already updating, please wait...'); 164 showMessage('Please wait, cart is updating...', 'info'); 165 return false; 166 } 167 168 var $link = $(this); 169 170 // Check if link is already in loading state 171 if ($link.hasClass('loading')) { 172 console.log('Item is already being removed, please wait...'); 173 return false; 174 } 175 176 var cartItemKey = ''; 177 178 // Check if this is a regular remove link or donation remove button 179 if ($link.hasClass('remove-donation-btn')) { 180 cartItemKey = $link.data('cart-item-key'); 181 } else { 182 cartItemKey = $link.data('cart_item_key'); 183 } 184 185 if (!cartItemKey) { 186 console.error('No cart item key found for removal'); 187 return false; 188 } 189 190 $link.text('Removing...').addClass('loading'); 191 removeCartItem(cartItemKey, $link); 192 193 return false; 194 }); 195} 196 197 // ========================= 198 // DONATION AJAX HANDLERS 199 // ========================= 200 201 function initDonationAJAX() { 202 // Handle donation amount selection 203 $(document).on('click', '.donatecart .price-select, .donatecart .donation-select', function(e) { 204 e.preventDefault(); 205 e.stopPropagation(); 206 207 // Check global processing state 208 if (donationConfig.isProcessing || config.isUpdating) { 209 console.log('Donation is processing, please wait...'); 210 return; 211 } 212 213 var $this = $(this); 214 215 // Check if element is disabled 216 if ($this.hasClass('disabled') || $this.prop('disabled')) { 217 console.log('Donation option is disabled, ignoring click'); 218 return; 219 } 220 221 //var amount = $this.data('amount'); 222 var amount = 5; 223 var $container = $this.closest('.donatecart'); 224 225 // Update UI 226 $container.find('.price-select, .donation-select').removeClass('selected').addClass('price-select'); 227 $this.removeClass('price-select').addClass('donation-select selected'); 228 229 // Update hidden field with selected amount 230 // $container.find('#selected-donation-amount').val(amount); 231 232 // Clear custom amount field 233 $container.find('#custom-donation-amount').val(''); 234 235 // Clear error messages 236 $container.find('.msg.error').hide().text(''); 237 }); 238 239 // Handle custom amount input 240 $(document).on('input change', '.donatecart .custom-donation-input-field', function(e) { 241 // Check if donation is processing 242 if (donationConfig.isProcessing || config.isUpdating) { 243 // Prevent input while processing 244 e.preventDefault(); 245 return; 246 } 247 248 var $this = $(this); 249 var customAmount = $this.val(); 250 var $container = $this.closest('.donatecart'); 251 252 if (customAmount && parseFloat(customAmount) > 0) { 253 // Update hidden field with custom amount 254 $container.find('#selected-donation-amount').val(customAmount); 255 256 // Deselect preset amounts 257 $container.find('.price-select, .donation-select').removeClass('selected').addClass('price-select'); 258 $container.find('.msg.error').hide().text(''); 259 } 260 });
261 262 // Handle donation "Add to Cart" button 263 $(document).on('click', '.donatecart .add-donation-btn', function(e) { 264 e.preventDefault(); 265 e.stopPropagation(); 266 267 // Global protection check 268 if (donationConfig.isProcessing || config.isUpdating) { 269 console.log('Donation is already processing, please wait...'); 270 showMessage('Please wait, donation is being processed...', 'info'); 271 return; 272 } 273 274 var $button = $(this); 275 276 // Check if button is already disabled 277 if ($button.hasClass('loading') || $button.prop('disabled')) { 278 console.log('Donation button is already processing, ignoring click'); 279 return; 280 } 281 282 var $container = $button.closest('.donatecart'); 283 var productId = $button.data('product-id'); 284 var donationSku = $button.data('donation-sku'); 285 var donationNonce = $button.data('nonce'); 286 287 // GET THE ACTUAL AMOUNT FROM THE FORM 288 var donationAmount = $container.find('#selected-donation-amount').val(); 289 290 // If no amount selected but custom field has value, use that 291 if (!donationAmount || donationAmount === '' || donationAmount === '10') { 292 var customAmount = $container.find('#custom-donation-amount').val(); 293 if (customAmount && parseFloat(customAmount) > 0) { 294 donationAmount = customAmount; 295 } 296 } 297 298 // Validate amount 299 if (!donationAmount || parseFloat(donationAmount) < 1) { 300 $container.find('.msg.error').text('Please enter a donation amount of at least $1').show(); 301 return; 302 } 303 304 console.log('Adding donation:', { 305 productId: productId, 306 amount: donationAmount, 307 sku: donationSku 308 }); 309 310 // Add donation to cart 311 addDonationToCart(productId, donationAmount, donationSku, donationNonce, $button); 312 }); 313 314 // Handle Enter key in custom donation field 315 $(document).on('keypress', '.donatecart .custom-donation-input-field', function(e) { 316 // Check if donation is processing 317 if (donationConfig.isProcessing || config.isUpdating) { 318 e.preventDefault(); 319 return; 320 } 321 322 if (e.which === 13) { 323 e.preventDefault(); 324 var $container = $(this).closest('.donatecart'); 325 var $button = $container.find('.add-donation-btn'); 326 327 // Check if button is disabled 328 if (!$button.hasClass('loading') && !$button.prop('disabled')) { 329 $button.click(); 330 } 331 } 332 }); 333} 334 335 // ========================= 336 // DONATION FUNCTIONS 337 // ========================= 338 339 function addDonationToCart(productId, amount, sku, nonce, $button) { 340 // Check if already processing 341 if (donationConfig.isProcessing || config.isUpdating) { 342 console.log('Donation already processing, ignoring click'); 343 return; 344 } 345 346 donationConfig.isProcessing = true; 347 config.isUpdating = true; 348 349 console.log('Sending donation to cart:', { 350 productId: productId, 351 amount: amount, 352 sku: sku 353 }); 354 355 // Disable ALL quantity controls in the cart 356 disableAllQuantityControls(); 357 358 // Get the container 359 var $container = $button.closest('.donatecart'); 360 361 // Show loading state on button 362 $button.addClass('loading').html( 363 '<div class="loading-spinner"></div> Adding...' 364 ); 365 366 // Show cart loading if cart is open 367 if ($('.headerminicart').hasClass('open')) { 368 //$('.headerminicart').addClass('updating'); 369 } 370 371 // Prepare form data 372 var formData = new FormData(); 373 formData.append('action', 'add_donation_to_cart'); 374 formData.append('product_id', productId); 375 formData.append('donation_amount', amount); 376 formData.append('donation_sku', sku); 377 formData.append('nonce', nonce); 378 379 // Use fetch API for better error handling 380 fetch(config.ajaxUrl, { 381 method: 'POST', 382 body: formData, 383 credentials: 'same-origin' 384 }) 385 .then(response => { 386 if (!response.ok) { 387 throw new Error('Network response was not ok: ' + response.statusText); 388 } 389 return response.json(); 390 }) 391 .then(data => { 392 console.log('Donation Response:', data); 393 394 if (data.success) { 395 // Clear any error messages 396 $container.find('.msg.error').hide().text(''); 397 398 // Show success message 399 showMessage(data.data.message || 'Donation of $' + parseFloat(amount).toFixed(2) + ' added to cart!', 'success'); 400 401 // Update cart totals IMMEDIATELY 402 if (data.data) { 403 updateCartTotals(data.data); 404 } 405 406 // IMPORTANT: Trigger WooCommerce cart updates 407 if (typeof wc_cart_fragments_params !== 'undefined') { 408 $(document.body).trigger('wc_fragment_refresh'); 409 } 410 411 $(document.body).trigger('updated_wc_div'); 412 $(document.body).trigger('updated_cart_totals'); 413 414 // Refresh mini cart to show updated donation 415 setTimeout(function() { 416 refreshMiniCart(); 417 // Re-enable all controls after refresh
418 setTimeout(enableAllQuantityControls, 100); 419 }, 500); 420 421 // Auto-open cart if closed 422 if (!miniCartOpen) { 423 setTimeout(function() { 424 openMiniCart(true); 425 }, 1000); 426 } 427 428 } else { 429 // Show error 430 var errorMsg = data.data && data.data.message ? 431 data.data.message : 'Error adding donation. Please try again.'; 432 $container.find('.msg.error').text(errorMsg).show(); 433 434 // Re-enable all controls on error 435 enableAllQuantityControls(); 436 resetDonationButton($button); 437 } 438 }) 439 .catch(error => { 440 console.error('Donation AJAX Error:', error); 441 $container.find('.msg.error').text('Processing donation...').show(); 442 443 // Re-enable all controls on network error 444 enableAllQuantityControls(); 445 resetDonationButton($button); 446 447 // Refresh cart to see if donation was added 448 setTimeout(function() { 449 refreshMiniCart(); 450 showMessage('Please check your cart - donation may have been added.', 'success'); 451 }, 1000); 452 }) 453 .finally(() => { 454 donationConfig.isProcessing = false; 455 config.isUpdating = false; 456 457 // Remove cart loading 458 setTimeout(function() { 459 $('.headerminicart').removeClass('updating'); 460 }, 1000); 461 }); 462} 463 464 function resetDonationButton($button) { 465 $button.removeClass('loading').prop('disabled', false).html( 466 'Add to Cart <svg baseProfile="tiny" height="24px" version="1.2" viewBox="0 0 24 24" width="24px"><g><g><path d="M20.756,5.345C20.565,5.126,20.29,5,20,5H6.181L5.986,3.836C5.906,3.354,5.489,3,5,3H2.75c-0.553,0-1,0.447-1,1 s0.447,1,1,1h1.403l1.86,11.164c0.008,0.045,0.031,0.082,0.045,0.124c0.016,0.053,0.029,0.103,0.054,0.151 c0.032,0.066,0.075,0.122,0.12,0.179c0.031,0.039,0.059,0.078,0.095,0.112c0.058,0.054,0.125,0.092,0.193,0.13 c0.038,0.021,0.071,0.049,0.112,0.065C6.748,16.972,6.87,17,6.999,17C7,17,18,17,18,17c0.553,0,1-0.447,1-1s-0.447-1-1-1H7.847 l-0.166-1H19c0.498,0,0.92-0.366,0.99-0.858l1-7C21.031,5.854,20.945,5.563,20.756,5.345z"/><circle cx="8.5" cy="19.5" r="1.5"/><circle cx="17.5" cy="19.5" r="1.5"/></g></g></svg>' 467 ); 468 } 469 470 function initializeDonationForm() { 471 $('.donatecart').each(function() { 472 var $container = $(this); 473 var selectedAmount = $container.find('#selected-donation-amount').val(); 474 var customAmount = $container.find('#custom-donation-amount').val(); 475 476 console.log('Initializing donation form:', { 477 selectedAmount: selectedAmount, 478 customAmount: customAmount 479 }); 480 481 // If custom amount exists and is valid 482 if (customAmount && customAmount !== '' && parseFloat(customAmount) > 0) { 483 // Deselect all preset amounts 484 $container.find('.price-select, .donation-select').removeClass('selected').addClass('price-select'); 485 } else if (selectedAmount) { 486 // Select the preset amount that matches 487 $container.find('[data-amount="' + selectedAmount + '"]') 488 .removeClass('price-select') 489 .addClass('donation-select selected'); 490 } 491 }); 492 } 493 494 // ========================= 495 // CART FUNCTIONS 496 // ========================= 497 498 function updateCartItem_old(cartItemKey, quantity, $element) { 499 500 501 if (!cartItemKey || quantity < 1) return; 502 503 config.isUpdating = true; 504 505 // Disable ALL quantity controls in the cart 506 disableAllQuantityControls(); 507 508 // Get all relevant quantity elements for this item 509 var $wrapper = $element.closest('.qib-button-wrapper'); 510 var $input = $wrapper.find('.cart-quantity-input'); 511 var $buttons = $wrapper.find('.qib-button'); 512 var $row = $wrapper.closest('tr'); 513 514 // Show loading state for this specific item 515 $row.addClass('updating'); 516 $row.find('.product-subtotal-amount').html('<span class="loading">Updating...</span>'); 517 $('.headerminicart').addClass('updating'); 518 519 $.ajax({
520 url: config.ajaxUrl, 521 type: 'POST', 522 data: { 523 action: 'custom_update_cart_item_clean', 524 cart_item_key: cartItemKey, 525 quantity: quantity, 526 security: config.nonce 527 }, 528 success: function(response) { 529 if (response.success) { 530 // Update cart totals 531 updateCartTotals(response.data); 532 533 // Update individual item subtotal 534 if ($row.length && response.data.item_subtotal) { 535 $row.find('.product-subtotal-amount').html(response.data.item_subtotal); 536 } 537 538 showMessage(response.data.message || 'Quantity updated', 'success'); 539 } else { 540 showMessage(response.data || 'Failed to update quantity', 'error'); 541 refreshMiniCart(); 542 } 543 }, 544 error: function(xhr, status, error) { 545 showMessage('Error updating quantity. Please try again.', 'error'); 546 //showMessage('Error updating quantity. Please try again.', 'success'); 547 refreshMiniCart(); 548 }, 549 complete: function() { 550 config.isUpdating = false; 551 552 // Re-enable ALL quantity controls after a brief delay 553 setTimeout(function() { 554 enableAllQuantityControls(); 555 $row.removeClass('updating'); 556 }, 500); 557 558 $('.headerminicart').removeClass('updating'); 559 } 560 }); 561} 562 563function updateCartItem(cartItemKey, quantity, $element) { 564 if (!cartItemKey || quantity < 1) return; 565 566 config.isUpdating = true; 567 568 // Disable ALL quantity controls in the cart 569 disableAllQuantityControls(); 570 571 // Get all relevant quantity elements for this item 572 var $wrapper = $element.closest('.qib-button-wrapper'); 573 var $input = $wrapper.find('.cart-quantity-input'); 574 var $buttons = $wrapper.find('.qib-button'); 575 var $row = $wrapper.closest('tr'); 576 577 // Show loading state for this specific item 578 $row.addClass('updating123'); 579 $row.find('.product-subtotal-amount').html('<span class="loading">Updating...</span>'); 580 $('.headerminicart').addClass('updating234'); 581 582 $.ajax({
583 url: config.ajaxUrl, 584 type: 'POST', 585 data: { 586 action: 'custom_update_cart_item', 587 cart_item_key: cartItemKey, 588 quantity: quantity, 589 security: config.nonce 590 }, 591 dataType: 'text', // Accept text first, then parse 592 success: function(rawResponse, status, xhr) { 593 console.log('Raw response received:', rawResponse.substring(0, 200)); 594 595 try { 596 // Clean the response - remove script tags and any HTML 597 var cleanedResponse = rawResponse.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); 598 cleanedResponse = cleanedResponse.replace(/^\s*[\r\n]/gm, '').trim(); 599 600 // Parse JSON 601 var response = JSON.parse(cleanedResponse); 602 603 console.log('Parsed response:', response); 604 605 if (response && response.success) { 606 // Update cart totals 607 updateCartTotals(response.data); 608 609 // Update individual item subtotal 610 if ($row.length && response.data.item_subtotal) { 611 $row.find('.product-subtotal-amount').html(response.data.item_subtotal); 612 } 613 614 showMessage(response.data.message || 'Quantity updated', 'success'); 615 refreshMiniCart(); 616 } else { 617 showMessage(response.data || 'Failed to update quantity', 'error'); 618 refreshMiniCart(); 619 } 620 } catch (e) { 621 console.error('Failed to parse response:', e); 622 console.log('Cleaned response:', cleanedResponse); 623 showMessage('Server returned invalid response', 'error'); 624 refreshMiniCart(); 625 } 626 }, 627 error: function(xhr, status, error) { 628 console.error('AJAX Error:', error); 629 showMessage('Error updating quantity. Please try again.', 'error'); 630 refreshMiniCart(); 631 }, 632 complete: function() { 633 config.isUpdating = false; 634 635 // Re-enable ALL quantity controls after a brief delay 636 setTimeout(function() { 637 enableAllQuantityControls(); 638 $row.removeClass('updating'); 639 }, 500); 640 641 $('.headerminicart').removeClass('updating'); 642 } 643 }); 644} 645 646 function removeCartItem(cartItemKey, $link) { 647 if (!cartItemKey) return; 648 649 config.isUpdating = true; 650 651 // Disable ALL quantity controls in the cart 652 disableAllQuantityControls(); 653 654 // Find the specific row 655 var $row = $('[data-cart-item-key="' + cartItemKey + '"]'); 656 657 // Update button text 658 $link.text('Removing...').addClass('loading'); 659 $('.headerminicart').addClass('updating999'); 660 661 $.ajax({
662 url: config.ajaxUrl, 663 type: 'POST', 664 data: { 665 action: 'custom_remove_cart_item', 666 cart_item_key: cartItemKey, 667 security: config.nonce 668 }, 669 success: function(response) { 670 if (response.success) { 671 // Update cart totals 672 updateCartTotals(response.data); 673 674 // Show success message 675 showMessage(response.data.message || 'Item removed successfully', 'success'); 676 677 // Refresh cart to get updated state 678 refreshMiniCart(); 679 } else { 680 showMessage(response.data || 'Failed to remove item', 'error'); 681 $link.text('Remove').removeClass('loading'); 682 enableAllQuantityControls(); // Re-enable on error 683 } 684 }, 685 error: function(xhr, status, error) { 686 console.error('Remove error:', error); 687 showMessage('Error removing item', 'error'); 688 $link.text('Remove').removeClass('loading'); 689 enableAllQuantityControls(); // Re-enable on error 690 }, 691 complete: function() { 692 config.isUpdating = false; 693 $('.headerminicart').removeClass('updating'); 694 } 695 }); 696} 697 698 // ========================= 699 // UPDATE CART TOTALS TO INCLUDE COUPONS 700 // ========================= 701 702 function updateCartTotals(data) { 703 // Update item count 704 if (data.item_count !== undefined) { 705 var countText = '(' + data.item_count + ' item' + (data.item_count !== 1 ? 's' : '') + ')'; 706 $('.cart-count').text(countText); 707 } 708 709 // Preferred path: the server sends back the whole totals block already 710 // rendered, so subtotal, coupon rows, shipping, estimated tax and order 711 // total can never disagree with each other. 712 if (data.totals_html) { 713 $('.minicartTable').html(data.totals_html); 714 715 if (typeof wc_cart_fragments_params !== 'undefined') { 716 $(document.body).trigger('wc_fragment_refresh'); 717 } 718 $(document.body).trigger('updated_wc_div'); 719 $(document.body).trigger('updated_cart_totals'); 720 return; 721 } 722 723 // Fallback: patch the rows individually. 724 // Update subtotal 725 if (data.subtotal) { 726 $('.cart-total-amount').html(data.subtotal); 727 } 728 729 // Update discount row if exists 730 if (data.discount_total) { 731 if ($('.cart-discount').length) { 732 // Update existing discount row 733 $('.cart-discount td').html(data.discount_total); 734 } else { 735 // Add discount row 736 $('.cart-subtotal').after( 737 '<tr class="cart-discount">' + 738 '<th>Discount</th>' + 739 '<td class="cart-discount-amount">' + data.discount_total + '</td>' + 740 '</tr>' 741 ); 742 } 743 } else { 744 // Remove discount row if no discount 745 $('.cart-discount').remove(); 746 } 747 748 // Update total if provided 749 if (data.total) { 750 if ($('.cart-order-total').length) { 751 $('.cart-order-total-amount').html(data.total); 752 } else { 753 // Add total row 754 var totalRow = '<tr class="cart-total">' + 755 '<th>Total</th>' + 756 '<td class="cart-final-amount">' + data.total + '</td>' + 757 '</tr>'; 758 759 if ($('.cart-discount').length) { 760 $('.cart-discount').after(totalRow); 761 } else { 762 $('.cart-subtotal').after(totalRow); 763 } 764 } 765 } else { 766 $('.cart-total').remove(); 767 } 768 769 // Update coupon display if data has coupons_html 770 if (data.coupons_html !== undefined) { 771 $('.applied-coupons-container').html(data.coupons_html); 772 } else if (data.coupons) { 773 updateCouponDisplay(data.coupons); 774 } 775 776 // Also update WooCommerce fragments if available 777 if (typeof wc_cart_fragments_params !== 'undefined') { 778 $(document.body).trigger('wc_fragment_refresh'); 779 } 780 781 // Trigger additional WooCommerce events 782 $(document.body).trigger('updated_wc_div'); 783 $(document.body).trigger('updated_cart_totals'); 784}
785 // Update your refreshMiniCart function to also update coupon display 786 // Update the refreshMiniCart function to also update coupons: 787function refreshMiniCart() { 788 $.ajax({ 789 url: config.ajaxUrl, 790 type: 'POST', 791 data: { 792 action: 'custom_get_mini_cart', 793 security: config.nonce 794 }, 795 success: function(response) { 796 if (response.success) { 797 // Replace the entire mini cart 798 $('.minicart').html(response.data.html); 799 $('.cart-quantity').html(response.data.count); 800 801 // Update state 802 config.miniCartOpen = $('.minicart').is(':visible'); 803 804 // Re-initialize donation form 805 setTimeout(initializeDonationForm, 100); 806 807 // Note: The mini cart HTML from custom_get_mini_cart already includes 808 // the coupons section, so no need to update separately 809 } 810 }, 811 error: function(xhr, status, error) { 812 console.error('Refresh error:', error); 813 } 814 }); 815} 816 817 function showMessage(message, type) { 818 // Remove existing messages 819 $('.mini-cart-message').remove(); 820 821 var $message = $('<div class="mini-cart-message mini-cart-' + type + '">' + message + '</div>'); 822 823 // Add to mini cart 824 $('.minicart').prepend($message); 825 826 // Auto remove after 3 seconds 827 setTimeout(function() { 828 $message.fadeOut(300, function() { 829 $(this).remove(); 830 }); 831 }, 3000); 832 } 833 834 // ========================= 835 // CART OPEN/CLOSE FUNCTIONS 836 // ========================= 837 838 function initCartOpenClose() { 839 // Remove existing handlers 840 $(document).off('click.minicart'); 841 $('.closeCart').off('click'); 842 843 // 1. Click on overlay 844 $(document).on('click.minicart', '.headerminicart.cartactive', function(e) { 845 if (e.target === this) { 846 closeMiniCart(); 847 return false; 848 } 849 }); 850 851 // 2. Close button 852 $(document).on('click.minicart', '.closeCart', function(e) { 853 e.preventDefault(); 854 e.stopPropagation(); 855 closeMiniCart(); 856 return false; 857 }); 858 859 // 3. Open cart 860 // $(document).on('click.minicart', '#myCart, .cartBubble, .cartBubble a', function(e) { 861 // e.preventDefault(); 862 // openMiniCart(); 863 // }); 864 865 $(document).on('click.minicart', '#myCart', function(e) { 866 e.preventDefault(); 867 openMiniCart(); 868 }); 869 870 // 4. ESC key 871 $(document).on('keydown.minicart', function(e) { 872 if (e.key === 'Escape' && miniCartOpen && !isClosing) { 873 closeMiniCart(); 874 } 875 }); 876 877 // 5. Click outside 878 $(document).on('click.minicart', function(e) { 879 if (!miniCartOpen || isClosing) return; 880 881 var $target = $(e.target); 882 var isInCart = $target.closest('.minicart').length > 0; 883 var isOpenButton = $target.closest('#myCart, .cartBubble, .cartBubble a').length > 0; 884 885 if (!isInCart && !isOpenButton && !$target.closest('.headerminicart').length) { 886 //closeMiniCart(); 887 } 888 }); 889 890 // Auto open after add to cart 891 $(document.body).on('added_to_cart', function() { 892 openMiniCart(true); 893 }); 894 895 // Non-AJAX fallback 896 $(document).on('click submit', '.add_to_cart_button, form.cart', function() { 897 sessionStorage.setItem('open_cart', '1'); 898 }); 899 900 if (sessionStorage.getItem('open_cart') === '1') { 901 sessionStorage.removeItem('open_cart'); 902 setTimeout(() => openMiniCart(true), 300); 903 } 904 905 // Checkout button 906 $(document).on('click', '.checkout-button', function() { 907 window.location.href = $(this).attr('href'); 908 return false; 909 }); 910 } 911 912 913 // ========================= 914 // SCROLL LOCK 915 // ========================= 916 917 var gscScrollY = 0; 918 var gscScrollLocked = false; 919 920 function gscLockScroll() { 921 if (gscScrollLocked) return; 922 923 gscScrollY = window.pageYOffset || document.documentElement.scrollTop || 0; 924 gscScrollLocked = true; 925 926 // Offsetting by the current scroll keeps the viewport showing the same 927 // content once the body is fixed. 928 document.body.style.top = (-gscScrollY) + 'px'; 929 } 930 931 function gscUnlockScroll() { 932 if (!gscScrollLocked) return; 933 934 gscScrollLocked = false; 935 document.body.style.top = ''; 936 937 // Jump straight back â 'auto' avoids a smooth-scroll animation if the 938 // theme sets scroll-behavior: smooth on html. 939 window.scrollTo({ top: gscScrollY, left: 0, behavior: 'auto' });
940 } 941 942 function openMiniCart(fast = false) { 943 if (miniCartOpen || isClosing) return; 944 isClosing = false; 945 946 const el = document.querySelector('.cartBubble'); 947 const display = window.getComputedStyle(el).display; 948 console.log('cartBubble is hidden (display: none)'); 949 950 if (display === 'none') { 951 console.log('cartBubble is hidden (display: none)'); 952 //return false; 953 } else { 954 console.log('cartBubble is visible, display is:', display); 955 gscLockScroll(); 956 $('html').addClass('open cartactive'); 957 958 setTimeout(() => { 959 miniCartOpen = true; 960 961 if (fast) { 962 $('html').addClass('open cartactive'); 963 } 964 965 $('.headerminicart').addClass('open cartactive'); 966 967 setTimeout(() => { 968 // Cleanup 969 }, fast ? 200 : 400); 970 }, 10); 971 } 972 973 974 } 975 976 function closeMiniCart() { 977 if (!miniCartOpen || isClosing) return; 978 979 isClosing = true; 980 981 $('.headerminicart').removeClass('open cartactive'); 982 983 setTimeout(() => { 984 $('html').removeClass('open cartactive'); 985 gscUnlockScroll(); 986 }, 500); 987 988 setTimeout(() => { 989 $('.headerminicart').removeClass('cartactive open'); 990 991 miniCartOpen = false; 992 isClosing = false; 993 }, 500); 994 } 995 996 // ========================= 997 // COUPON AJAX HANDLERS 998 // ========================= 999 1000 function initCouponAJAX() { 1001 console.log('Initializing coupon AJAX handlers...'); 1002 1003 // Handle coupon apply button 1004 $(document).on('click', '.discount-code-apply-btn', function(e) { 1005 e.preventDefault(); 1006 e.stopPropagation(); 1007 console.log('Apply button clicked!'); 1008 1009 // Global protection check 1010 if (config.isUpdating) { 1011 console.log('Cart is already updating, please wait...'); 1012 showMessage('Please wait, cart is updating...', 'info'); 1013 return false; 1014 } 1015 1016 var $button = $(this); 1017 1018 // Check if button is disabled 1019 if ($button.prop('disabled')) { 1020 console.log('Coupon button is disabled, ignoring click'); 1021 return false; 1022 } 1023 1024 var $input = $button.closest('.discount-code-input-group').find('.discount-code-input'); 1025 var couponCode = $input.val().trim(); 1026 1027 console.log('Coupon code entered:', couponCode); 1028 1029 if (!couponCode) { 1030 console.log('No coupon code entered'); 1031 showCouponMessage('Please enter a discount code', 'error'); 1032 return false; 1033 } 1034 1035 applyCoupon(couponCode, $button); 1036 1037 return false; 1038 }); 1039 1040 // Handle Enter key in coupon input 1041 $(document).on('keypress', '.discount-code-input', function(e) { 1042 if (e.which === 13) { 1043 e.preventDefault(); 1044 console.log('Enter key pressed in coupon input'); 1045 var $button = $(this).closest('.discount-code-input-group').find('.discount-code-apply-btn'); 1046 1047 // Check if button is disabled 1048 if (!$button.prop('disabled')) { 1049 $button.click(); 1050 } 1051 } 1052 }); 1053 1054 // Handle coupon removal 1055 $(document).on('click', '.remove-coupon', function(e) { 1056 e.preventDefault(); 1057 e.stopPropagation(); 1058 console.log('Remove coupon clicked'); 1059 1060 // Global protection check 1061 if (config.isUpdating) { 1062 console.log('Cart is already updating, please wait...'); 1063 showMessage('Please wait, cart is updating...', 'info'); 1064 return false; 1065 } 1066 1067 var $link = $(this); 1068 var couponCode = $link.data('coupon-code'); 1069 1070 if (!couponCode) { 1071 console.log('No coupon code in remove link'); 1072 return false; 1073 } 1074 1075 removeCoupon(couponCode, $link); 1076 1077 return false; 1078 });
1079} 1080 1081 // ========================= 1082 // COUPON FUNCTIONS 1083 // ========================= 1084 1085 // Update the applyCoupon function to handle display: 1086function applyCoupon(couponCode, $button) { 1087 if (config.isUpdating) { 1088 console.log('Cannot apply coupon: already updating'); 1089 return; 1090 } 1091 1092 config.isUpdating = true; 1093 console.log('Applying coupon:', couponCode, 'AJAX URL:', config.ajaxUrl); 1094 1095 // Disable ALL quantity controls 1096 disableAllQuantityControls(); 1097 1098 // Show loading state 1099 $button.prop('disabled', true).text('Applying...'); 1100 showCouponMessage('Applying coupon...', 'info'); 1101 1102 // Simple AJAX call 1103 $.ajax({ 1104 url: config.ajaxUrl, 1105 type: 'POST', 1106 data: { 1107 action: 'custom_apply_coupon', 1108 coupon_code: couponCode, 1109 security: config.nonce 1110 }, 1111 beforeSend: function() { 1112 console.log('AJAX request sending...'); 1113 }, 1114 success: function(response) { 1115 console.log('AJAX success response:', response); 1116 1117 if (response.success) { 1118 console.log('Coupon applied successfully'); 1119 showCouponMessage(response.data.message || 'Coupon applied successfully!', 'success'); 1120 1121 // Clear input field 1122 $button.closest('.discount-code-input-group').find('.discount-code-input').val(''); 1123 1124 // Update cart totals 1125 if (response.data) { 1126 updateCartTotals(response.data); 1127 1128 if (response.data.coupons_html) { 1129 $('.applied-coupons-container').html(response.data.coupons_html); 1130 } else if (response.data.coupons) { 1131 updateCouponDisplay(response.data.coupons); 1132 } 1133 } 1134 1135 } else { 1136 console.log('Coupon failed:', response.data); 1137 showCouponMessage(response.data || 'Failed to apply coupon', 'error'); 1138 } 1139 }, 1140 error: function(xhr, status, error) { 1141 console.error('AJAX error:', { 1142 status: status, 1143 error: error, 1144 responseText: xhr.responseText 1145 }); 1146 showCouponMessage('Error applying coupon. Please try again.', 'error'); 1147 }, 1148 complete: function() { 1149 console.log('AJAX complete'); 1150 config.isUpdating = false; 1151 $button.prop('disabled', false).text('Apply'); 1152 1153 // Re-enable all controls 1154 setTimeout(enableAllQuantityControls, 500); 1155 } 1156 }); 1157} 1158 1159function removeCoupon(couponCode, $link) { 1160 if (config.isUpdating) return; 1161 1162 config.isUpdating = true; 1163 1164 // Disable ALL quantity controls 1165 disableAllQuantityControls(); 1166 1167 // Show loading state 1168 $link.text('Removing...'); 1169 showCouponMessage('Removing coupon...', 'info'); 1170 1171 $.ajax({
1172 url: config.ajaxUrl, 1173 type: 'POST', 1174 data: { 1175 action: 'custom_remove_coupon', 1176 coupon_code: couponCode, 1177 security: config.nonce 1178 }, 1179 success: function(response) { 1180 if (response.success) { 1181 // Show success message 1182 showCouponMessage(response.data.message || 'Coupon removed successfully!', 'success'); 1183 1184 // Update cart totals 1185 updateCartTotals(response.data); 1186 1187 // Update coupon display using HTML from response 1188 if (response.data.coupons_html !== undefined) { 1189 $('.applied-coupons-container').html(response.data.coupons_html); 1190 } else if (response.data.coupons) { 1191 updateCouponDisplay(response.data.coupons); 1192 } 1193 1194 } else { 1195 // Show error message 1196 showCouponMessage(response.data || 'Failed to remove coupon', 'error'); 1197 } 1198 }, 1199 error: function(xhr, status, error) { 1200 showCouponMessage('Error removing coupon. Please try again.', 'error'); 1201 console.error('Coupon remove error:', error); 1202 }, 1203 complete: function() { 1204 config.isUpdating = false; 1205 1206 // Re-enable all controls 1207 setTimeout(enableAllQuantityControls, 500); 1208 } 1209 }); 1210} 1211 1212 1213// ========================= 1214// GLOBAL QUANTITY CONTROL FUNCTIONS 1215// ========================= 1216 1217function disableAllQuantityControls() { 1218 console.log('Disabling all quantity controls...'); 1219 1220 // Disable all quantity inputs 1221 $('.cart-quantity-input').prop('disabled', true).addClass('disabled'); 1222 1223 // Disable all quantity buttons 1224 $('.qib-button').prop('disabled', true).addClass('disabled'); 1225 1226 // Disable all remove buttons 1227 $('.remove-cart-item, .remove-donation-btn').prop('disabled', true).addClass('disabled'); 1228 1229 // Disable coupon apply button 1230 $('.discount-code-apply-btn').prop('disabled', true).addClass('disabled'); 1231 1232 // Disable donation form elements (if visible) 1233 $('.donatecart .price-select, .donatecart .donation-select') 1234 .prop('disabled', true) 1235 .addClass('disabled'); 1236 1237 $('.donatecart .custom-donation-input-field') 1238 .prop('disabled', true); 1239 1240 $('.donatecart .add-donation-btn') 1241 .prop('disabled', true) 1242 .addClass('disabled'); 1243 1244 // Disable checkout buttons 1245 $('.checkout-button').prop('disabled', true).addClass('disabled'); 1246 1247 // Add global updating class to cart 1248 //$('.headerminicart').addClass('global-updating'); 1249} 1250 1251function enableAllQuantityControls() { 1252 console.log('Enabling all quantity controls...'); 1253 1254 // Enable all quantity inputs 1255 $('.cart-quantity-input').prop('disabled', false).removeClass('disabled'); 1256 1257 // Enable all quantity buttons 1258 $('.qib-button').prop('disabled', false).removeClass('disabled'); 1259 1260 // Enable all remove buttons (except those in loading state) 1261 $('.remove-cart-item, .remove-donation-btn').not('.loading') 1262 .prop('disabled', false) 1263 .removeClass('disabled'); 1264 1265 // Enable coupon apply button 1266 $('.discount-code-apply-btn').prop('disabled', false).removeClass('disabled'); 1267 1268 // Enable donation form elements (if donation hasn't been added) 1269 $('.donatecart:visible .price-select, .donatecart:visible .donation-select') 1270 .prop('disabled', false) 1271 .removeClass('disabled'); 1272 1273 $('.donatecart:visible .custom-donation-input-field') 1274 .prop('disabled', false); 1275 1276 $('.donatecart:visible .add-donation-btn') 1277 .prop('disabled', false) 1278 .removeClass('disabled'); 1279 1280 // Enable checkout buttons 1281 $('.checkout-button').prop('disabled', false).removeClass('disabled'); 1282 1283 // Remove global updating class 1284 $('.headerminicart').removeClass('global-updating'); 1285} 1286 1287// Function to reset specific row after removal 1288function resetSpecificRowControls($row) {
1289 $row.find('.cart-quantity-input').prop('disabled', false).removeClass('disabled'); 1290 $row.find('.qib-button').prop('disabled', false).removeClass('disabled'); 1291 $row.find('.remove-cart-item').prop('disabled', false).removeClass('disabled'); 1292} 1293 1294 function showCouponMessage(message, type) { 1295 // Remove existing coupon messages 1296 $('.coupon-message').remove(); 1297 1298 // Create message element 1299 var $message = $('<div class="coupon-message coupon-' + type + '">' + message + '</div>'); 1300 1301 // Add message above the discount code section 1302 $('.minicart-discount-code').before($message); 1303 1304 // Auto remove after 5 seconds 1305 setTimeout(function() { 1306 $message.fadeOut(300, function() { 1307 $(this).remove(); 1308 }); 1309 }, 5000); 1310 } 1311 1312 function updateCouponDisplay(coupons) { 1313 console.log('Updating coupon display:', coupons); 1314 1315 var $couponContainer = $('.applied-coupons-container'); 1316 1317 if (!coupons || coupons.length === 0) { 1318 // Hide applied coupons section if exists 1319 $couponContainer.html(''); 1320 return; 1321 } 1322 1323 // Build coupon HTML 1324 var couponHtml = '<div class="applied-coupons">' + 1325 '<div class="applied-coupons-title">Applied Discounts</div>'; 1326 1327 $.each(coupons, function(index, coupon) { 1328 couponHtml += '<div class="applied-coupon-item" data-coupon-code="' + coupon.code + '">' + 1329 '<span class="coupon-code">' + coupon.code + '</span>' + 1330 '<span class="coupon-amount">-' + coupon.amount + '</span>' + 1331 '<a href="#" class="remove-coupon" data-coupon-code="' + coupon.code + '">Remove</a>' + 1332 '</div>'; 1333 }); 1334 1335 couponHtml += '</div>'; 1336 1337 // Update container 1338 $couponContainer.html(couponHtml); 1339} 1340 1341 // ========================= 1342 // GLOBAL EXPORTS 1343 // ========================= 1344 1345 window.miniCartAJAX = { 1346 isUpdating: function() { 1347 return config.isUpdating; 1348 }, 1349 preventActions: function() { 1350 return config.isUpdating; 1351 }, 1352 openCart: function(fast) { 1353 openMiniCart(fast); 1354 }, 1355 closeCart: function() { 1356 closeMiniCart(); 1357 } 1358 }; 1359 1360 // ========================= 1361 // ADDITIONAL DOM READY FUNCTIONS 1362 // ========================= 1363 1364 $(document).ready(function() { 1365 // Fix for specific product quantity display 1366 $('td.product-quantity.itemcartqty_1323857').each(function() { 1367 var style = document.createElement('style'); 1368 style.innerHTML = '.woocommerce-js .minicartitems table.shop_table_responsive tr td.product-quantity.itemcartqty_1323857::before { content: attr(data-title) !important; }'; 1369 document.head.appendChild(style); 1370 }); 1371 1372 // Add style tag with highest priority 1373 var style = document.createElement('style'); 1374 style.id = 'remove-colon-fix'; 1375 style.innerHTML = ` 1376 td.product-quantity.itemcartqty_1323857::before { 1377 content: attr(data-title) !important; 1378 visibility: visible !important; 1379 } 1380 .woocommerce-js .minicartitems table.shop_table_responsive tr td.product-quantity.itemcartqty_1323857::before { 1381 content: attr(data-title) !important; 1382 } 1383 `; 1384 document.head.appendChild(style); 1385 1386 // Function to check and toggle forms 1387 function toggleFormsBasedOnClass() { 1388 if ($('td').hasClass('itemcartqty_1323857')) { 1389 $('.donatecart').hide(); 1390 } else { 1391 $('.donatecart').show(); 1392 } 1393 } 1394 1395 // Run on page load 1396 toggleFormsBasedOnClass(); 1397 1398 // Check periodically for dynamic changes 1399 setInterval(toggleFormsBasedOnClass, 1000); 1400 });
1401 1402 1403 1404 1405})(jQuery); 1406 1407document.addEventListener("DOMContentLoaded", function () { 1408 const scrollbarWidth = 1409 window.innerWidth - document.documentElement.clientWidth; 1410 1411 document.documentElement.style.setProperty( 1412 "--scrollbar-width", 1413 scrollbarWidth + "px" 1414 ); 1415}); 1416 1417 1418// Nuclear option - completely override everything 1419jQuery(document).ready(function($) { 1420 console.log('=== DONATION CHECKBOX WITH EXISTING POPUP ==='); 1421 1422 // Track optimistic state 1423 var optimisticChecked = false; 1424 var lastActionTime = 0; 1425 1426 // Function to show modal 1427 function showDonationModal() { 1428 $('.donation-modal-overlay').css('display', 'flex'); 1429 // Prevent body scrolling 1430 $('body').css('overflow', 'hidden'); 1431 console.log('Modal shown'); 1432 } 1433 1434 // Function to hide modal 1435 function hideDonationModal() { 1436 $('.donation-modal-overlay').hide(); 1437 // Restore body scrolling 1438 $('body').css('overflow', ''); 1439 console.log('Modal hidden'); 1440 } 1441 1442 // Modal event handlers 1443 $(document).on('click', '.info-icon', function(e) { 1444 e.preventDefault(); 1445 e.stopPropagation(); 1446 console.log('Info icon clicked - showing modal'); 1447 showDonationModal(); 1448 return false; 1449 }); 1450 1451 $(document).on('click', '.donation-modal-close', function(e) { 1452 e.preventDefault(); 1453 e.stopPropagation(); 1454 console.log('Modal close clicked'); 1455 hideDonationModal(); 1456 return false; 1457 }); 1458 1459 $(document).on('click', '.donation-modal-donate', function(e) { 1460 e.preventDefault(); 1461 e.stopPropagation(); 1462 console.log('Modal donate button clicked'); 1463 hideDonationModal(); 1464 1465 // Check the donation checkbox if not already checked 1466 var $checkbox = $('#add-donation-checkbox'); 1467 var hasDonation = $('td.itemcartqty_1323857').length > 0; 1468 1469 if (!hasDonation) { 1470 // Update tracking 1471 lastActionTime = Date.now(); 1472 optimisticChecked = true; 1473 1474 // Update checkbox state 1475 $checkbox.prop('checked', true); 1476 updateCheckboxVisual(true); 1477 1478 // Trigger donation button 1479 $('.add-donation-btn').click(); 1480 } 1481 1482 return false; 1483 }); 1484 1485 // Close modal when clicking outside 1486 $(document).on('click', '.donation-modal-overlay', function(e) { 1487 if ($(e.target).hasClass('donation-modal-overlay')) { 1488 hideDonationModal(); 1489 } 1490 }); 1491 1492 // Close modal with Escape key 1493 $(document).on('keydown', function(e) { 1494 if (e.key === 'Escape' && $('.donation-modal-overlay').is(':visible')) { 1495 hideDonationModal(); 1496 } 1497 }); 1498 1499 // Function to update checkbox based on .itemcartqty_1323857 1500 function syncCheckboxWithCart() { 1501 // If we just clicked (within last 2 seconds), don't sync yet 1502 if (Date.now() - lastActionTime < 2000) { 1503 console.log('Skipping sync - recent action'); 1504 return; 1505 } 1506 1507 var $checkbox = $('#add-donation-checkbox'); 1508 if ($checkbox.length === 0) return; 1509 1510 // Check if donation is in cart 1511 var hasDonation = $('td.itemcartqty_1323857').length > 0; 1512 1513 console.log('Checking cart - hasDonation:', hasDonation); 1514 1515 // Update checkbox based on REAL cart state 1516 $checkbox.prop('checked', hasDonation); 1517 optimisticChecked = hasDonation; 1518 1519 // Update visual checkmark 1520 updateCheckboxVisual(hasDonation); 1521 1522 // Update info icon visibility based on checkbox state 1523 // if (hasDonation) { 1524 // $('.info-icon').show(); 1525 // } else { 1526 // $('.info-icon').hide(); 1527 // } 1528 } 1529 1530 // Function to update visual appearance 1531 function updateCheckboxVisual(isChecked) { 1532 var $label = $('label[for="add-donation-checkbox"], .donation-label'); 1533 if ($label.length === 0) return; 1534 1535 // Remove any existing visual 1536 $label.find('.check-visual').remove(); 1537 1538 // Add new visual 1539 $label.prepend('<span class="check-visual" style="display: inline-block; width: 18px; height: 18px; border: 2px solid ' + (isChecked ? '#4CAF50' : '#666') + '; border-radius: 3px; background: ' + (isChecked ? '#4CAF50' : 'white') + '; margin-right: 8px; vertical-align: middle; position: relative; color: white; font-size: 12px; text-align: center; line-height: 16px;">' + (isChecked ? 'â' : '') + '</span>'); 1540 } 1541 1542 // Click handler 1543 $(document).on('click', 'label[for="add-donation-checkbox"], .donation-label, .check-visual', function(e) { 1544 e.preventDefault(); 1545 e.stopPropagation(); 1546 1547 console.log('=== CHECKBOX LABEL CLICKED ==='); 1548 1549 var $checkbox = $('#add-donation-checkbox'); 1550 if ($checkbox.length === 0) { 1551 console.log('Checkbox not found'); 1552 return false; 1553 } 1554 1555 // Get current REAL state from cart 1556 var hasDonation = $('td.itemcartqty_1323857').length > 0;
1557 var isNowChecked = !hasDonation; 1558 1559 console.log('Current cart state - hasDonation:', hasDonation); 1560 console.log('Setting checkbox to:', isNowChecked); 1561 1562 // Update tracking 1563 lastActionTime = Date.now(); 1564 optimisticChecked = isNowChecked; 1565 1566 // OPTIMISTIC UPDATE: Show checked state immediately 1567 $checkbox.prop('checked', isNowChecked); 1568 updateCheckboxVisual(isNowChecked); 1569 1570 // Update info icon visibility 1571 if (isNowChecked) { 1572 $('.info-icon').show(); 1573 } else { 1574 $('.info-icon').hide(); 1575 } 1576 1577 // Actually perform the action 1578 if (isNowChecked) { 1579 console.log('Adding donation to cart'); 1580 $('.add-donation-btn').click(); 1581 } else { 1582 console.log('Removing donation from cart'); 1583 $('td.itemcartqty_1323857').closest('tr').find('.remove').click(); 1584 } 1585 1586 // Check REAL cart state after longer delay (after AJAX should complete) 1587 setTimeout(function() { 1588 console.log('Checking REAL cart state after 3 seconds...'); 1589 var newHasDonation = $('td.itemcartqty_1323857').length > 0; 1590 console.log('Real cart state:', newHasDonation); 1591 1592 // Only update if optimistic state doesn't match real state 1593 if (optimisticChecked !== newHasDonation) { 1594 console.log('Correcting checkbox to match real cart state'); 1595 $checkbox.prop('checked', newHasDonation); 1596 updateCheckboxVisual(newHasDonation); 1597 optimisticChecked = newHasDonation; 1598 1599 // Update info icon visibility 1600 if (newHasDonation) { 1601 $('.info-icon').show(); 1602 } else { 1603 $('.info-icon').hide(); 1604 } 1605 } 1606 }, 3000); 1607 1608 return false; 1609 }); 1610 1611 // Also handle checkbox clicks directly 1612 $(document).on('click', '#add-donation-checkbox', function(e) { 1613 e.preventDefault(); 1614 e.stopPropagation(); 1615 console.log('Checkbox directly clicked'); 1616 $('label[for="add-donation-checkbox"], .donation-label').click(); 1617 return false; 1618 }); 1619 1620 // Run on page load 1621 console.log('=== INITIALIZING ==='); 1622 syncCheckboxWithCart(); 1623 1624 // Run after delay (cart might load async) 1625 setTimeout(syncCheckboxWithCart, 1000); 1626 1627 // Run after AJAX - but wait a bit 1628 $(document).ajaxComplete(function(event, xhr, settings) { 1629 console.log('AJAX complete - will check cart in 1 second'); 1630 setTimeout(syncCheckboxWithCart, 500); 1631 }); 1632 1633 // Debug: Check if modal exists 1634 console.log('Modal overlay exists:', $('.donation-modal-overlay').length); 1635 console.log('Info icon exists:', $('.info-icon').length); 1636 1637 // Make show/hide functions globally available for debugging 1638 window.showDonationPopup = showDonationModal; 1639 window.hideDonationPopup = hideDonationModal; 1640 1641 console.log('Donation checkbox with popup loaded successfully'); 1642}); 1643 1644 // Add this BEFORE any other scripts in the <head> or right after opening <body> 1645// (function() { 1646// // Create and add style to hide elements immediately 1647// var style = document.createElement('style'); 1648// style.id = 'cart-preloader-style'; 1649// style.textContent = ` 1650// .elementor-element-c02e1ca.toggle-icon--custom.elementor-menu-cart--cart-type-mini-cart, 1651// .linkitem.cartBubble { 1652// visibility: hidden !important; 1653// opacity: 0 !important; 1654// transition: none !important; 1655// } 1656// `; 1657// document.head.appendChild(style); 1658// })(); 1659 1660 1661//window.SlideCart.setState(state); 1662 1663(function () { 1664 if (window.SlideCart) return; 1665 1666 const STORAGE_KEY = 'site_cart_mode'; 1667 1668 function normalize(val) { 1669 return Number(val) === 1 ? 1 : 0; 1670 } 1671 1672 function getStoredState() { 1673 const saved = localStorage.getItem(STORAGE_KEY); 1674 // if never set â default = 1 (popup cart) 1675 if (saved === null) return 1; 1676 return normalize(saved); 1677 } 1678 1679 function broadcast(state) { 1680 window.dispatchEvent( 1681 new CustomEvent('slidecart:stateChange', { detail: { state } }) 1682 ); 1683 } 1684 1685 // Initialize with current state BEFORE setting up API 1686 let currentState = getStoredState(); 1687 1688 window.SlideCart = { 1689 setState: function (state) { 1690 const s = normalize(state); 1691 localStorage.setItem(STORAGE_KEY, s); 1692 currentState = s; // Update in-memory state 1693 broadcast(s); 1694 console.log('[SlideCart] setState â', s, `(${s === 1 ? 'Popup Cart' : 'Slide Cart'})`); 1695 return s; 1696 }, 1697 1698 getState: function () { 1699 return currentState; // Return in-memory state, not localStorage 1700 }, 1701 1702 toggle: function () { 1703 return this.setState(currentState === 1 ? 0 : 1); 1704 }, 1705 1706 // Add a method to force refresh from localStorage if needed 1707 refresh: function() { 1708 const stored = getStoredState(); 1709 if (stored !== currentState) { 1710 console.log('[SlideCart] Refreshing state from localStorage:', stored); 1711 currentState = stored; 1712 broadcast(stored); 1713 } 1714 return currentState; 1715 } 1716 }; 1717 1718 // Broadcast initial state on DOMContentLoaded 1719 document.addEventListener('DOMContentLoaded', function () { 1720 // Don't broadcast if we already have the state set 1721 // Just ensure display updates 1722 setTimeout(() => { 1723 broadcast(currentState); 1724 }, 100); 1725 }); 1726 1727})(); 1728 1729// Cart display controller 1730document.addEventListener('DOMContentLoaded', function () { 1731 console.log('[CartDisplay] Initializing...'); 1732 1733 if (!window.SlideCart || typeof window.SlideCart.getState !== 'function') { 1734 console.warn('SlideCart API not found'); 1735 return; 1736 } 1737 1738 var elementorCart = document.querySelector( 1739 '.elementor-element.elementor-element-c02e1ca.toggle-icon--custom.elementor-menu-cart--cart-type-mini-cart' 1740 ); 1741 var cartBubble = document.querySelector('.linkitem.cartBubble'); 1742 1743 if (!elementorCart) { 1744 console.warn('[CartDisplay] Elementor cart not found'); 1745 } 1746 if (!cartBubble) { 1747 console.warn('[CartDisplay] Cart bubble not found'); 1748 } 1749 if (!elementorCart || !cartBubble) { 1750 // Try again after a short delay in case elements load later 1751 setTimeout(function() { 1752 //location.reload(); 1753 }, 500); 1754 return; 1755 } 1756 1757 // Remove the preloader style 1758 var preloaderStyle = document.getElementById('cart-preloader-style'); 1759 if (preloaderStyle) { 1760 preloaderStyle.remove(); 1761 console.log('[CartDisplay] Removed preloader'); 1762 } 1763 1764 function applyCartMode(state) {
1765 console.log('[CartDisplay] Applying cart mode:', state, `(${state === 1 ? 'Popup Cart' : 'Slide Cart'})`); 1766 1767 // State mapping: 1768 // 0 = slide cart (cartBubble) 1769 // 1 = popup cart (elementorCart) 1770 1771 if (state === 0) { 1772 // Show slide cart (cartBubble), hide popup cart (elementorCart) 1773 elementorCart.style.cssText = ` 1774 display: none !important; 1775 visibility: hidden !important; 1776 opacity: 0 !important; 1777 pointer-events: none !important; 1778 position: absolute !important; 1779 width: 0 !important; 1780 height: 0 !important; 1781 overflow: hidden !important; 1782 `; 1783 1784 cartBubble.style.cssText = ` 1785 display: inline-flex !important; 1786 visibility: visible !important; 1787 opacity: 1 !important; 1788 pointer-events: auto !important; 1789 position: relative !important; 1790 `; 1791 1792 } else { 1793 // Show popup cart (elementorCart), hide slide cart (cartBubble) 1794 elementorCart.style.cssText = ` 1795 display: block !important; 1796 visibility: visible !important; 1797 opacity: 1 !important; 1798 pointer-events: auto !important; 1799 position: relative !important; 1800 `; 1801 1802 cartBubble.style.cssText = ` 1803 display: none !important; 1804 visibility: hidden !important; 1805 opacity: 0 !important; 1806 pointer-events: none !important; 1807 position: absolute !important; 1808 width: 0 !important; 1809 height: 0 !important; 1810 overflow: hidden !important; 1811 `; 1812 } 1813 1814 // Add data attributes for debugging 1815 elementorCart.setAttribute('data-cart-state', state); 1816 cartBubble.setAttribute('data-cart-state', state); 1817 document.body.setAttribute('data-cart-mode', state === 1 ? 'popup' : 'slide'); 1818 } 1819 1820 // Apply on load using global state 1821 var initialState = window.SlideCart.getState(); 1822 console.log('[CartDisplay] Initial state:', initialState); 1823 applyCartMode(initialState); 1824 1825 // Auto-update when state changes 1826 window.addEventListener('slidecart:stateChange', function (e) { 1827 console.log('[CartDisplay] State change detected:', e.detail.state); 1828 applyCartMode(e.detail.state); 1829 }); 1830 1831 // Add a small debug indicator (remove in production) 1832// setTimeout(function() { 1833// var debugIndicator = document.createElement('div'); 1834// debugIndicator.id = 'cart-debug-indicator'; 1835// debugIndicator.style.cssText = ` 1836// position: fixed; 1837// bottom: 10px; 1838// right: 10px; 1839// background: #333; 1840// color: white; 1841// padding: 6px 10px; 1842// font-size: 11px; 1843// font-family: monospace; 1844// border-radius: 3px; 1845// z-index: 99999; 1846// opacity: 0.8; 1847// cursor: help; 1848// border: 1px solid #666; 1849// `; 1850// debugIndicator.title = 'Click to toggle cart state\nRight-click to reset to default'; 1851 1852// function updateDebugIndicator() { 1853// var state = window.SlideCart.getState(); 1854// debugIndicator.textContent = `Cart: ${state === 1 ? 'Popup' : 'Slide'} [${state}]`; 1855// debugIndicator.style.background = state === 1 ? '#4CAF50' : '#2196F3'; 1856// } 1857 1858// debugIndicator.onclick = function(e) { 1859// e.preventDefault(); 1860// window.SlideCart.toggle();
1861// }; 1862 1863// debugIndicator.oncontextmenu = function(e) { 1864// e.preventDefault(); 1865// window.SlideCart.setState(1); // Reset to default (popup) 1866// }; 1867 1868// document.body.appendChild(debugIndicator); 1869// updateDebugIndicator(); 1870 1871// // Update indicator on state changes 1872// window.addEventListener('slidecart:stateChange', updateDebugIndicator); 1873 1874// console.log('[CartDisplay] Debug indicator added'); 1875// }, 1000); 1876}); 1877 1878 1879// Set to popup cart (Elementor) 1880 //window.SlideCart.setState(1); 1881 1882// Set to slide cart (cartBubble) 1883 //window.SlideCart.setState(0); 1884 1885// Toggle between states 1886 //window.SlideCart.toggle(); 1887 1888// Get current state 1889 //window.SlideCart.getState(); // Returns 0 or 1 1890 1891// Force refresh from localStorage (if needed) 1892 //window.SlideCart.refresh(); 1893 1894/* ------------------------------------------------------------------------- 1895 * Move the slide cart out of the header's stacking context. 1896 * 1897 * The panel is rendered inside the header shortcode, so its z-index competes 1898 * only with its header siblings. Any header element with a z-index of its own 1899 * (the mobile hamburger, a sticky bar) therefore paints over the overlay no 1900 * matter how high the panel's z-index goes. Reparenting to <body> puts it in 1901 * the root stacking context, where its z-index actually means something. 1902 * 1903 * Safe because every handler in this file is delegated from document and 1904 * matches on class, and the panel is position:fixed so layout is unaffected. 1905 * #myCart stays in the header. 1906 * ------------------------------------------------------------------------- */ 1907jQuery(function ($) { 1908 function gscReparentCart() { 1909 var $all = $('.headerminicart'); 1910 1911 if (!$all.length) { 1912 return; 1913 } 1914 1915 var $onBody = $all.filter(function () { 1916 return $(this).parent().is('body'); 1917 }).first(); 1918 1919 if (!$onBody.length) { 1920 $onBody = $all.first().appendTo('body'); 1921 } 1922 1923 // Drop any stray copy a re-rendered header may have left behind. 1924 $all.not($onBody).remove(); 1925 } 1926 1927 gscReparentCart(); 1928 1929 $(document.body).on('wc_fragments_refreshed wc_fragments_loaded', gscReparentCart); 1930});
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.