PageSourceSearch

https://www.zonadepadel.es/modules/fw_spbapi/views/js/front.js

js zonadepadel.es collected 2026-09-25 06:59:53 UTC 27,423 bytes, 679 lines download raw bytes

1/**
2 * 2025 4webs FW_SPBAPI
3 *
4 * @author    4webs
5 * @copyright 4webs 2025
6 * @license   4webs
7 */
8
9var fwSpbapiPendingAddToCart = null;
10var fwSpbapiPendingProductData = null;
11var fwSpbapiProcessing = false;
12var fwSpbapiEventBlocked = false;
13var fwSpbapiIsAddToCartFlow = false;
14var fwSpbapiExecutingClick = false;
15// True only when the user explicitly clicks "add with insurance" or "without insurance"
16var fwSpbapiInsuranceDecided = false;
17
18function isBannerCheckboxChecked() {
19    var checkbox = document.getElementById('fw-spbapi-banner-checkbox');
20    return checkbox ? checkbox.checked : false;
21}
22
23function setBannerCheckboxChecked(checked) {
24    var checkbox = document.getElementById('fw-spbapi-banner-checkbox');
25    if (checkbox) {
26        checkbox.checked = !!checked;
27    }
28}
29
30function extractInsurancePrice(text) {
31    var priceMatch = text.match(/([\d]+[,\.][\d]{2})\s*€/) || 
32                     text.match(/€\s*([\d]+[,\.][\d]{2})/) || 
33                     text.match(/([\d]+[,\.][\d]{2})/);
34    
35    if (priceMatch) {
36        return priceMatch[1].replace(',', '.');
37    }
38    
39    var fallbackMatch = text.match(/([\d]+\.[\d]{2})/);
40    return fallbackMatch ? fallbackMatch[1] : '0.00';
41}
42
43function openModalFromInsuranceIcon(infoIcon) {
44    var openModal = function(retryCount) {
45        retryCount = retryCount || 0;
46        var maxRetries = 20;
47        
48        if (typeof $ === 'undefined' || typeof window.fwSpbapiOpenFullModal !== 'function') {
49            if (retryCount < maxRetries) {
50                setTimeout(function() { openModal(retryCount + 1); }, 50);
51            }
52            return;
53        }
54        
55        var modalOverlay = document.getElementById('fw-spbapi-modal-overlay');
56        if (!modalOverlay) {
57            if (retryCount < maxRetries) {
58                setTimeout(function() { openModal(retryCount + 1); }, 50);
59            }
60            return;
61        }
62        
63        var $iconElement = $(infoIcon);
64        var $insuranceProduct = $iconElement.closest('.insurance-product');
65        
66        // Only update price if we're on cart/checkout page (has .insurance-product element)
67        // On product page, the price is already correct from PHP, so we don't need to update it
68        if ($insuranceProduct.length > 0) {
69            // Try to get price from data attribute first (already formatted from cart)
70            var insurancePrice = $insuranceProduct.attr('data-insurance-price');
71            
72            // If not available in data attribute, try to extract from text
73            if (!insurancePrice || insurancePrice === '0' || insurancePrice === '0.00' || insurancePrice === '') {
74                var insuranceText = $insuranceProduct.find('.insurance-text').text();
75                var extractedPrice = extractInsurancePrice(insuranceText);
76                // Format the price if we extracted a numeric value
77                if (extractedPrice && extractedPrice !== '0.00') {
78                    // Use PrestaShop's formatCurrency if available, otherwise format manually
79                    if (typeof formatCurrency !== 'undefined' && typeof currencyFormat !== 'undefined' && typeof currencySign !== 'undefined') {
80                        insurancePrice = formatCurrency(parseFloat(extractedPrice), currencyFormat, currencySign, currencyBlank);
81                    } else {
82                        // Fallback: simple formatting
83                        insurancePrice = extractedPrice.replace('.', ',') + ' €';
84                    }
85                } else {
86                    insurancePrice = null;
87                }
88            }
89            
90            // Update price element only if we have a valid price
91            var $priceElement = $('#fw-spbapi-dynamic-price');
92            if ($priceElement.length && insurancePrice) {
93                $priceElement.text(insurancePrice);
94            }
95        }
96        
97        try {
98            window.fwSpbapiOpenFullModal();
99        } catch (error) {
100            // Silent fail
101        }
102    };
103    
104    setTimeout(function() { openModal(0); }, 0);
105}
106
107document.addEventListener('click', function(e) {
108    var infoIcon = e.target.closest('.insurance-info-icon');
109    
110    if (infoIcon) {
111        e.preventDefault();
112        e.stopPropagation();
113        e.stopImmediatePropagation();
114        openModalFromInsuranceIcon(infoIcon);
115    }
116}, true);
117
118document.addEventListener('click', function(e) {
119    var banner = document.querySelector('.fw-spbapi-banner-container');
120    if (!banner) {
121        return;
122    }
123
124    // Without checkbox selected, allow normal add-to-cart (no insurance, no modal)
125    if (!isBannerCheckboxChecked()) {
126        return;
127    }
128    
129    var target = e.target.closest('.add-to-cart, [data-button-action="add-to-cart"], .btn-add-to-cart, button[name="add"], button[type="submit"][name="submitAddtocart"], .product-add-to-cart button[type="submit"]');
130    
131    if (!target) {
132        return;
133    }
134    
135    // Allow click to proceed if we're executing it programmatically
136    if (fwSpbapiExecutingClick) {
137        return;
138    }
139    
140    if (fwSpbapiProcessing) {
141        fwSpbapiEventBlocked = false;
142        return;
143    }
144    
145    var urlParams = new URLSearchParams(window.location.search);
146    var productId = urlParams.get('id_product') || 
147                   target.getAttribute('data-id-product') || 
148                   document.querySelector('input[name="id_product"]')?.value;
149    
150    e.preventDefault();
151    e.stopPropagation();
152    e.stopImmediatePropagation();
153    fwSpbapiEventBlocked = true;
154    
155    fwSpbapiPendingAddToCart = target;
156    fwSpbapiPendingProductData = {
157        productId: productId,
158        quantity: document.querySelector('input[name="qty"]')?.value || 1
159    };
160    
161    fwSpbapiProcessing = true;
162    
163    document.dispatchEvent(new CustomEvent('fwSpbapiOpenModal', { 
164        detail: { 
165            button: target,
166            productId: productId 
167        } 
168    }));
169    
170}, true);
171
172// Close modal button handler (native JS - works independently of jQuery)
173document.addEventListener('click', function(e) {
174    var closeBtn = e.target.closest('#fw-spbapi-quick-close-modal');
175    if (closeBtn) {
176        e.preventDefault();
177        e.stopPropagation();
178        e.stopImmediatePropagation();
179        
180        if (typeof window.fwSpbapiCancelModal === 'function') {
181            window.fwSpbapiCancelModal();
182        } else if (typeof window.fwSpbapiCloseModal === 'function') {
183            window.fwSpbapiCloseModal();
184        } else {
185            // Fallback if functions don't exist yet
186            var overlay = document.getElementById('fw-spbapi-quick-modal-overlay');
187            if (overlay) {
188                overlay.classList.remove('active');
189                document.documentElement.classList.remove('fw-spbapi-modal-open');
190                document.body.classList.remove('fw-spbapi-modal-open');
191            }
192            fwSpbapiIsAddToCartFlow = false;
193            fwSpbapiPendingAddToCart = null;
194            fwSpbapiPendingProductData = null;
195            fwSpbapiProcessing = false;
196            fwSpbapiEventBlocked = false;
197        }
198    }
199}, true);
200
201document.addEventListener('fwSpbapiOpenModal', function(e) {
202    fwSpbapiIsAddToCartFlow = true;
203    
204    setTimeout(function() {
205        var overlay = document.getElementById('fw-spbapi-quick-modal-overlay');
206        
207        if (overlay) {
208            overlay.classList.add('active');
209            document.documentElement.classList.add('fw-spbapi-modal-open');
210            document.body.classList.add('fw-spbapi-modal-open');
211            
212            if (typeof $ !== 'undefined') {
213                $('#fw-spbapi-quick-modal-overlay').addClass('active');
214                $('html, body').addClass('fw-spbapi-modal-open');
215            }
216        }
217        
218        if (typeof window.fwSpbapiOpenQuickModal === 'function') {
219            window.fwSpbapiOpenQuickModal();
220        }
221    }, 100);
222});
223
224(function() {
225    function waitForJQuery(callback) {
226        if (typeof jQuery !== 'undefined' && typeof $ !== 'undefined') {
227            callback();
228        } else {
229            setTimeout(function() {
230                waitForJQuery(callback);
231            }, 50);
232        }
233    }
234    
235    waitForJQuery(function() {
236        $(document).ready(function() {
237            if (typeof $ === 'undefined') {
238                return;
239            }
240
241            function getProductId($button) {
242                var productId = $button.data('id-product') || 
243                               $button.closest('[data-id-product]').data('id-product') ||
244                               $button.attr('data-id-product');
245                
246                if (!productId) {
247                    var urlParams = new URLSearchParams(window.location.search);
248                    productId = urlParams.get('id_product');
249                }
250                
251                if (!productId) {
252                    var $productInput = $('input[name="id_product"]');
253                    if ($productInput.length) {
254                        productId = $productInput.val();
255                    }
256                }
257                
258                return parseInt(productId);
259            }
260
261            function isProductEligible() {
262                return $('.fw-spbapi-banner-container').length > 0;
263            }
264
265            function interceptAddToCart(e, $button) {
266                // Allow click to proceed if we're executing it programmatically
267                if (fwSpbapiExecutingClick) {
268                    return true;
269                }
270                
271                if (fwSpbapiEventBlocked) {
272                    fwSpbapiEventBlocked = false;
273                }
274                
275                if (fwSpbapiProcessing) {
276                    return true;
277                }
278                
279                if (!isProductEligible()) {
280                    return true;
281                }
282
283                // Without checkbox selected, allow normal add-to-cart (no insurance, no modal)
284                if (!isBannerCheckboxChecked()) {
285                    return true;
286                }
287                
288                var productId = getProductId($button);
289                
290                if (!productId) {
291                    return true;
292                }
293
294                fwSpbapiPendingAddToCart = $button;
295                fwSpbapiPendingProductData = {
296                    productId: productId,
297                    quantity: $('input[name="qty"]').val() || 1
298                };
299                
300                e.preventDefault();
301                e.stopPropagation();
302                e.stopImmediatePropagation();
303                
304                fwSpbapiProcessing = true;
305                window.fwSpbapiOpenQuickModal();
306
307                return false;
308            }
309
310            var addToCartSelectors = [
311                '.add-to-cart',
312                '[data-button-action="add-to-cart"]',
313                '.btn-add-to-cart',
314                'button[name="add"]',
315                'button[type="submit"][name="submitAddtocart"]',
316                '.product-add-to-cart button[type="submit"]'
317            ];
318
319            $(document).on('click', addToCartSelectors.join(', '), function(e) {
320                return interceptAddToCart(e, $(this));
321            });
322
323            $('form#add-to-cart-or-refresh, form[action*="cart"]').on('submit', function(e) {
324                if (!isProductEligible()) {
325                    return true;
326                }
327
328                if (!isBannerCheckboxChecked()) {
329                    return true;
330                }
331                
332                // Allow click to proceed if we're executing it programmatically
333                if (fwSpbapiExecutingClick) {
334                    return true;
335                }
336                
337                if (fwSpbapiProcessing) {
338                    return true;
339                }
340                
341                var $submitBtn = $(this).find('button[type="submit"]');
342                return interceptAddToCart(e, $submitBtn);
343            });
344
345            var observeAddToCartButton = function() {
346                var targetNode = document.querySelector('.product-add-to-cart') || 
347                                document.querySelector('.product-actions');
348                
349                if (!targetNode) return;
350                
351                var observer = new MutationObserver(function(mutations) {
352                    mutations.forEach(function(mutation) {
353                        if (mutation.addedNodes.length) {
354                            $(addToCartSelectors.join(', ')).off('click.fwspbapi').on('click.fwspbapi', function(e) {
355                                return interceptAddToCart(e, $(this));
356                            });
357                        }
358                    });
359                });
360                
361                observer.observe(targetNode, { childList: true, subtree: true });
362            };
363            
364            observeAddToCartButton();
365
366            var quickModalOverlay = $('#fw-spbapi-quick-modal-overlay');
367            var quickModal = $('#fw-spbapi-quick-modal');
368            var quickAddWithInsuranceBtn = $('#fw-spbapi-quick-add-with-insurance');
369            var quickAddWithoutInsuranceBtn = $('#fw-spbapi-quick-add-without-insurance');
370            var viewFullDetailsBtn = $('#fw-spbapi-view-full-details');
371            var fullModalOverlay = $('#fw-spbapi-modal-overlay');
372            var fullModal = $('#fw-spbapi-modal');
373            var closeFullModalBtn = $('#fw-spbapi-close-modal');
374
375            window.fwSpbapiOpenQuickModal = function() {
376                fwSpbapiIsAddToCartFlow = true;
377                fwSpbapiInsuranceDecided = false;
378                
379                var overlay = document.getElementById('fw-spbapi-quick-modal-overlay');
380                if (overlay) {
381                    overlay.classList.add('active');
382                    document.documentElement.classList.add('fw-spbapi-modal-open');
383                    document.body.classList.add('fw-spbapi-modal-open');
384                }
385                
386                quickModalOverlay.addClass('active');
387                $('html, body').addClass('fw-spbapi-modal-open');
388            };
389    
390            window.fwSpbapiOpenFullModal = function() {
391                fwSpbapiIsAddToCartFlow = false;
392                fullModalOverlay.addClass('active');
393                $('html, body').addClass('fw-spbapi-modal-open');
394            };
395
396            window.fwSpbapiCloseModal = function() {
397                // If the quick modal is being closed without an explicit choice
398                // (X button, click outside, Escape, etc.) and there is a pending
399                // add-to-cart, automatically add the product without insurance.
400                if (fwSpbapiIsAddToCartFlow &&
401                    !fwSpbapiInsuranceDecided &&
402                    fwSpbapiPendingAddToCart &&
403                    fwSpbapiPendingProductData &&
404                    !fwSpbapiExecutingClick) {
405                    
406                    fwSpbapiInsuranceDecided = true; // prevent recursive trigger
407                    setBannerCheckboxChecked(false);
408                    proceedWithAddToCart(false);
409                    return; // proceedWithAddToCart will call fwSpbapiCloseModal() again
410                }
411
412                var quickOverlay = document.getElementById('fw-spbapi-quick-modal-overlay');
413                var fullOverlay = document.getElementById('fw-spbapi-modal-overlay');
414                
415                if (quickOverlay) {
416                    quickOverlay.classList.remove('active');
417                }
418                if (fullOverlay) {
419                    fullOverlay.classList.remove('active');
420                }
421                
422                document.documentElement.classList.remove('fw-spbapi-modal-open');
423                document.body.classList.remove('fw-spbapi-modal-open');
424                
425                quickModalOverlay.removeClass('active');
426                fullModalOverlay.removeClass('active');
427                $('html, body').removeClass('fw-spbapi-modal-open');
428                
429                fwSpbapiIsAddToCartFlow = false;
430                fwSpbapiInsuranceDecided = false;
431                // Don't clear pending data here if we're about to execute the click
432                if (!fwSpbapiExecutingClick) {
433                    fwSpbapiPendingAddToCart = null;
434                    fwSpbapiPendingProductData = null;
435                }
436                if (!fwSpbapiExecutingClick) {
437                    fwSpbapiProcessing = false;
438                }
439            };
440    
441            function cancelModal() {
442                window.fwSpbapiCloseModal();
443                fwSpbapiEventBlocked = false;
444            }
445
446            window.fwSpbapiCancelModal = function() {
447                cancelModal();
448            };
449
450            function proceedWithAddToCart(withInsurance) {
451                if (!fwSpbapiPendingAddToCart || !fwSpbapiPendingProductData) {
452                    setBannerCheckboxChecked(!!withInsurance);
453                    window.fwSpbapiCloseModal();
454                    return;
455                }
456
457                setBannerCheckboxChecked(!!withInsurance);
458
459                // Get the form
460                var $form = fwSpbapiPendingAddToCart.jquery ? 
461                           fwSpbapiPendingAddToCart.closest('form') : 
462                           $(fwSpbapiPendingAddToCart).closest('form');
463                
464                if (!$form.length) {
465                    $form = $('#add-to-cart-or-refresh, form[action*="cart"]').first();
466                }
467                
468                // Always remove any existing insurance input first
469                if ($form.length) {
470                    $form.find('input[name="fw_spbapi_add_insurance"]').remove();
471                    
472                    // Only add the input if user wants insurance
473                    if (withInsurance) {
474                        $form.append('<input type="hidden" name="fw_spbapi_add_insurance" value="1">');
475                    }
476                }
477
478                var pendingButton = fwSpbapiPendingAddToCart;
479                
480                // Clear pending data before executing click
481                fwSpbapiPendingAddToCart = null;
482                fwSpbapiPendingProductData = null;
483                
484                window.fwSpbapiCloseModal();
485
486                setTimeout(function() {
487                    if (!pendingButton) {
488                        fwSpbapiProcessing = false;
489                        fwSpbapiExecutingClick = false;
490                        return;
491                    }
492                    
493                    // Set flag to allow click to proceed without interception
494                    fwSpbapiExecutingClick = true;
495                    fwSpbapiProcessing = false;
496                    
497                    // Execute the click
498                    if (pendingButton.jquery) {
499                        pendingButton.trigger('click');
500                    } else {
501                        pendingButton.click();
502                    }
503                    
504                    // Reset flags after a short delay
505                    setTimeout(function() {
506                        fwSpbapiExecutingClick = false;
507                        fwSpbapiProcessing = false;
508                    }, 500);
509                }, 100);
510            }
511
512            quickAddWithInsuranceBtn.on('click', function(e) {
513                e.preventDefault();
514                e.stopPropagation();
515                fwSpbapiInsuranceDecided = true;
516                proceedWithAddToCart(true);
517            });
518
519            quickAddWithoutInsuranceBtn.on('click', function(e) {
520                e.preventDefault();
521                e.stopPropagation();
522                fwSpbapiInsuranceDecided = true;
523                proceedWithAddToCart(false);
524            });
525
526            viewFullDetailsBtn.on('click', function(e) {
527                e.preventDefault();
528                e.stopPropagation();
529                quickModalOverlay.removeClass('active');
530                window.fwSpbapiOpenFullModal();
531            });
532
533            quickModalOverlay.on('click', function(e) {
534                if ($(e.target).is('#fw-spbapi-quick-modal-overlay')) {
535                    cancelModal();
536                }
537            });
538
539            quickModal.on('click', function(e) {
540                e.stopPropagation();
541            });
542
543            $(document).on('click', '#fw-spbapi-open-modal', function(e) {
544                e.preventDefault();
545                e.stopPropagation();
546                window.fwSpbapiOpenFullModal();
547            });
548
549            function handleInsuranceInfoIconClick($iconElement) {
550                var $insuranceProduct = $iconElement.closest('.insurance-product');
551                
552                // Only update price if we're on cart/checkout page (has .insurance-product element)
553                // On product page, the price is already correct from PHP, so we don't need to update it
554                if ($insuranceProduct.length > 0) {
555                    // Try to get price from data attribute first (already formatted from cart)
556                    var insurancePrice = $insuranceProduct.attr('data-insurance-price');
557                    
558                    // If not available in data attribute, try to extract from text
559                    if (!insurancePrice || insurancePrice === '0' || insurancePrice === '0.00' || insurancePrice === '') {
560                        var insuranceText = $insuranceProduct.find('.insurance-text').text();
561                        var extractedPrice = extractInsurancePrice(insuranceText);
562                        // Format the price if we extracted a numeric value
563                        if (extractedPrice && extractedPrice !== '0.00') {
564                            // Use PrestaShop's formatCurrency if available, otherwise format manually
565                            if (typeof formatCurrency !== 'undefined' && typeof currencyFormat !== 'undefined' && typeof currencySign !== 'undefined') {
566                                insurancePrice = formatCurrency(parseFloat(extractedPrice), currencyFormat, currencySign, currencyBlank);
567                            } else {
568                                // Fallback: simple formatting
569                                insurancePrice = extractedPrice.replace('.', ',') + ' €';
570                            }
571                        } else {
572                            insurancePrice = null;
573                        }
574                    }
575                    
576                    // Update price element only if we have a valid price
577                    var $priceElement = $('#fw-spbapi-dynamic-price');
578                    if ($priceElement.length && insurancePrice) {
579                        $priceElement.text(insurancePrice);
580                    }
581                }
582                
583                window.fwSpbapiOpenFullModal();
584            }
585
586            document.addEventListener('fwSpbapiInfoIconClick', function(e) {
587                if (typeof $ !== 'undefined') {
588                    handleInsuranceInfoIconClick($(e.detail.element));
589                }
590            });
591
592            $(document).on('click', '.insurance-info-icon', function(e) {
593                e.preventDefault();
594                e.stopPropagation();
595                e.stopImmediatePropagation();
596                handleInsuranceInfoIconClick($(this));
597                return false;
598            });
599
600            closeFullModalBtn.on('click', function(e) {
601                e.preventDefault();
602                e.stopPropagation();
603                window.fwSpbapiCloseModal();
604            });
605
606            fullModalOverlay.on('click', function(e) {
607                if ($(e.target).is('#fw-spbapi-modal-overlay')) {
608                    window.fwSpbapiCloseModal();
609                }
610            });
611
612            fullModal.on('click', function(e) {
613                e.stopPropagation();
614            });
615
616            $(document).on('keydown', function(e) {
617                if (e.key === 'Escape') {
618                    if (quickModalOverlay.hasClass('active') && fwSpbapiIsAddToCartFlow) {
619                        cancelModal();
620                    } else if (fullModalOverlay.hasClass('active')) {
621                        window.fwSpbapiCloseModal();
622                    }
623                }
624            });
625
626            // Remove insurance from cart/checkout product line checkbox (enable not allowed)
627            $(document).on('change', '.insurance-checkbox', function() {
628                var $checkbox = $(this);
629                var $insuranceProduct = $checkbox.closest('.insurance-product');
630                var idProduct = parseInt($insuranceProduct.data('product-id'), 10);
631                var idCustomization = parseInt($insuranceProduct.data('customization-id'), 10) || 0;
632                var enable = $checkbox.is(':checked') ? 1 : 0;
633
634                if (!idProduct) { return; }
635
636                // Cart checkbox is only for removing insurance; re-adding is done on the product page
637                if (enable) {
638                    $checkbox.prop('checked', false);
639                    return;
640                }
641
642                $checkbox.prop('disabled', true);
643
644                var ajaxUrl = (typeof prestashop !== 'undefined' && prestashop.urls && prestashop.urls.base_url)
645                    ? prestashop.urls.base_url + 'module/fw_spbapi/ajax'
646                    : '/module/fw_spbapi/ajax';
647
648                $.ajax({
649                    url: ajaxUrl,
650                    type: 'POST',
651                    data: {
652                        action: 'toggleInsurance',
653                        id_product: idProduct,
654                        id_customization: idCustomization,
655                        enable: 0
656                    },
657                    dataType: 'json',
658                    success: function(response) {
659                        if (response.success) {
660                            if (typeof prestashop !== 'undefined') {
661                                prestashop.emit('updateCart', { reason: { action: 'insurance-toggle' } });
662                            } else {
663                                window.location.reload();
664                            }
665                        } else {
666                            $checkbox.prop('checked', true);
667                            $checkbox.prop('disabled', false);
668                        }
669                    },
670                    error: function() {
671                        $checkbox.prop('checked', true);
672                        $checkbox.prop('disabled', false);
673                    }
674                });
675            });
676        });
677    });
678})();
679

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.