PageSourceSearch

https://www.ur.edu.pl/bundles/migoshop/main-shop.js?v=1

js ur.edu.pl collected 2026-09-24 09:27:54 UTC 23,109 bytes, 749 lines download raw bytes

1(function () {
2  'use strict';
3
4  window.migoShopFilters = (function () {
5    const FORM_SELECTOR = '[data-migo-shop-filter-form]';
6    const CHECKBOX_SELECTOR = 'input[type="checkbox"]';
7    const NUMBER_SELECTOR = 'input[type="number"]';
8    const RANGE_NAME_REGEX = /^(.*)\[(min|max)\]$/;
9    const CHECKBOX_DEBOUNCE_MS = 200;
10
11    function parseNumericInputValue(input) {
12      const rawValue = (input.value || '').trim();
13
14      if (rawValue === '') {
15        return null;
16      }
17
18      const normalizedValue = rawValue.replace(',', '.');
19      const numericValue = Number(normalizedValue);
20
21      return Number.isFinite(numericValue) ? numericValue : null;
22    }
23
24    function validateRangeInputs(form) {
25      const inputs = Array.from(form.querySelectorAll(NUMBER_SELECTOR));
26      const groups = new Map();
27
28      inputs.forEach((input) => {
29        input.setCustomValidity('');
30
31        const name = input.getAttribute('name') || '';
32        const match = name.match(RANGE_NAME_REGEX);
33
34        if (!match) {
35          return;
36        }
37
38        const key = match[1];
39        const bound = match[2];
40        const group = groups.get(key) || {};
41
42        group[bound] = input;
43        groups.set(key, group);
44      });
45
46      for (const group of groups.values()) {
47        const minInput = group.min;
48        const maxInput = group.max;
49
50        if (!minInput || !maxInput) {
51          continue;
52        }
53
54        const minValue = parseNumericInputValue(minInput);
55        const maxValue = parseNumericInputValue(maxInput);
56
57        if (null === minValue || null === maxValue) {
58          continue;
59        }
60
61        if (minValue > maxValue) {
62          const message = 'Wartosc "do" nie moze byc mniejsza niz "od".';
63
64          minInput.setCustomValidity(message);
65          maxInput.setCustomValidity(message);
66          maxInput.reportValidity();
67
68          return false;
69        }
70      }
71
72      return true;
73    }
74
75    function bindForm(form) {
76      if (!form || form.dataset.migoShopFilterBound === '1') {
77        return;
78      }
79
80      let timerId = null;
81
82      const scheduleSubmit = (delay) => {
83        window.clearTimeout(timerId);
84        timerId = window.setTimeout(() => {
85          form.submit();
86        }, delay);
87      };
88
89      form.querySelectorAll(CHECKBOX_SELECTOR).forEach((input) => {
90        input.addEventListener('change', () => {
91          scheduleSubmit(CHECKBOX_DEBOUNCE_MS);
92        });
93      });
94
95      form.querySelectorAll(NUMBER_SELECTOR).forEach((input) => {
96        input.addEventListener('keydown', (event) => {
97          if (event.key !== 'Enter') {
98            return;
99          }
100
101          event.preventDefault();
102
103          if (validateRangeInputs(form)) {
104            form.requestSubmit();
105          }
106        });
107      });
108
109      form.addEventListener('submit', (event) => {
110        if (validateRangeInputs(form)) {
111          return;
112        }
113
114        event.preventDefault();
115      });
116
117      form.dataset.migoShopFilterBound = '1';
118    }
119
120    function init() {
121      document.querySelectorAll(FORM_SELECTOR).forEach(bindForm);
122    }
123
124    return {
125      init,
126    };
127  })();
128
129  window.migoShopCart = (function () {
130    const BUTTON_SELECTOR = '.migo-shop-add-to-cart__button';
131    const CART_SELECTOR = '.migo-shop-cart';
132    const QTY_FORM_SELECTOR = '.migo-shop-cart__qty-form';
133    const REMOVE_FORM_SELECTOR = '.migo-shop-cart__remove-form';
134    const DELIVERY_SELECT_SELECTOR = '.migo-shop-cart__delivery-select';
135
136    function getCartMessageContainer() {
137      return (
138        document.querySelector('.migo-shop-cart') ||
139        document.querySelector('.migo-shop-mini-cart')
140      );
141    }
142
143    function getCartMessage(key) {
144      const container = getCartMessageContainer();
145
146      return container ? container.dataset[key] : undefined;
147    }
148
149    function formatMessage(template, value) {
150      return (template || '').replace('%d', value);
151    }
152
153    function notifyIfTranslated(template, value, type) {
154      const message = formatMessage(getCartMessage(template), value);
155
156      if (message) {
157        showNotification(message, type);
158      }
159    }
160
161    function showNotification(message, type) {
162      const container = document.querySelector(
163        '.migo-shop-notification-container',
164      );
165      if (container) {
166        container.remove();
167      }
168
169      const el = document.createElement('div');
170      el.className = 'migo-shop-notification-container';
171      el.innerHTML = `<div class="migo-shop-notification migo-shop-notification--${type}">${message}</div>`;
172      document.body.appendChild(el);
173
174      setTimeout(() => {
175        if (el.parentNode) {
176          el.parentNode.removeChild(el);
177        }
178      }, 4000);
179    }
180
181    function updateMiniCartSummary(totalItems, grandTotalFormatted) {
182      const countEl = document.querySelector('.migo-shop-mini-cart__count');
183
184      if (countEl && totalItems !== undefined) {
185        countEl.textContent = totalItems;
186      }
187
188      const totalEl = document.querySelector('.migo-shop-mini-cart__total');
189
190      if (totalEl && grandTotalFormatted !== undefined) {
191        totalEl.textContent = grandTotalFormatted;
192      }
193
194      updateMiniCartAriaLabel(totalItems, grandTotalFormatted);
195    }
196
197    function updateMiniCartAriaLabel(totalItems, grandTotalFormatted) {
198      if (totalItems === undefined || grandTotalFormatted === undefined) return;
199
200      const container = document.querySelector('.migo-shop-mini-cart');
201      if (!container) return;
202
203      const template = container.dataset.ariaLabelTemplate;
204      if (!template) return;
205
206      const labelEl = container.querySelector('.migo-shop-mini-cart__link') || container;
207
208      labelEl.setAttribute(
209        'aria-label',
210        template.replace('%s', totalItems).replace('%s', grandTotalFormatted),
211      );
212    }
213
214    function updateCartTotals(subtotalFormatted, deliveryFormatted, grandTotalFormatted) {
215      const subtotalEl = document.querySelector('#js-cart-subtotal');
216      if (subtotalEl && subtotalFormatted !== undefined) {
217        subtotalEl.textContent = subtotalFormatted;
218      }
219
220      const deliveryEl = document.querySelector('#js-cart-delivery-price');
221      if (deliveryEl && deliveryFormatted !== undefined) {
222        deliveryEl.textContent = deliveryFormatted;
223      }
224
225      const grandTotalEl = document.querySelector('#js-cart-grand-total');
226      if (grandTotalEl && grandTotalFormatted !== undefined) {
227        grandTotalEl.textContent = grandTotalFormatted;
228      }
229    }
230
231    function updateDeliveryOptions(deliveryMethods, selectedDeliveryId) {
232      const selectEl = document.querySelector(DELIVERY_SELECT_SELECTOR);
233
234      if (!selectEl || !Array.isArray(deliveryMethods) || deliveryMethods.length === 0) {
235        return;
236      }
237
238      while (selectEl.firstChild) {
239        selectEl.removeChild(selectEl.firstChild);
240      }
241
242      deliveryMethods.forEach((method) => {
243        const option = document.createElement('option');
244        option.value = String(method.id);
245        option.dataset.price = String(method.price || 0);
246        option.textContent = method.label || method.title || '';
247
248        if (parseInt(option.value, 10) === (parseInt(selectedDeliveryId, 10) || 0)) {
249          option.selected = true;
250        }
251
252        selectEl.appendChild(option);
253      });
254    }
255
256    function applyCartResponse(data) {
257      updateMiniCartSummary(data.totalItems, data.cartSubtotalFormatted);
258      updateCartTotals(
259        data.cartSubtotalFormatted,
260        data.deliveryPriceFormatted,
261        data.grandTotalFormatted,
262      );
263      const currentDeliverySelect = document.querySelector(DELIVERY_SELECT_SELECTOR);
264      updateDeliveryOptions(
265        data.deliveryMethods,
266        currentDeliverySelect ? currentDeliverySelect.value : null,
267      );
268    }
269
270    function showEmptyCart(cartEl) {
271      cartEl.innerHTML = `<p class="migo-shop-empty">${cartEl.dataset.emptyText || ''}</p>`;
272    }
273
274    async function updateQuantity(form, formData, previousValue) {
275      const quantity = parseInt(formData.get('quantity'), 10);
276      const input = form.querySelector('.migo-shop-cart__quantity-input');
277      if (input) input.disabled = true;
278
279      try {
280        const response = await fetch('/shop-api/cart/update', {
281          method: 'POST',
282          body: formData,
283        });
284        const data = await response.json();
285
286        if (input) input.disabled = false;
287
288        if (!response.ok) {
289          let clampTarget = null;
290
291          if (data.error === 'max_item_quantity_exceeded') {
292            clampTarget = data.maxItemQuantity;
293          } else if (data.error === 'out_of_stock' && typeof data.maxStock === 'number') {
294            clampTarget = data.maxStock;
295          }
296
297          if (input) {
298            input.value = clampTarget !== null ? clampTarget : previousValue;
299          }
300
301          if (data.error === 'max_item_quantity_exceeded') {
302            notifyIfTranslated('maxItemQuantityText', data.maxItemQuantity, 'error');
303          } else if (data.error === 'max_total_quantity_exceeded') {
304            notifyIfTranslated('maxTotalQuantityText', data.maxTotalQuantity, 'error');
305          } else if (data.error === 'out_of_stock') {
306            notifyIfTranslated('outOfStockText', data.maxStock, 'error');
307          }
308
309          if (clampTarget !== null && String(clampTarget) !== String(previousValue)) {
310            const retryCartEl = form.closest(CART_SELECTOR);
311            const retryToken = retryCartEl ? retryCartEl.dataset.requestToken : null;
312            const retryData = new FormData(form);
313            retryData.set('quantity', String(clampTarget));
314            if (retryToken) retryData.set('REQUEST_TOKEN', retryToken);
315            updateQuantity(form, retryData, previousValue);
316          }
317          return;
318        }
319
320        if (input) input.dataset.confirmedQty = String(quantity);
321
322        applyCartResponse(data);
323
324        if (quantity <= 0 || data.removed) {
325          const item = form.closest('.migo-shop-cart__item');
326          if (item) {
327            const cartEl = item.closest(CART_SELECTOR);
328            item.remove();
329            const remaining = cartEl.querySelectorAll('.migo-shop-cart__item').length;
330            if (remaining === 0) {
331              showEmptyCart(cartEl);
332            }
333          }
334          return;
335        }
336
337        const item = form.closest('.migo-shop-cart__item');
338        if (item && data.itemTotalFormatted) {
339          const itemTotal = item.querySelector('.migo-shop-cart__item-total');
340          if (itemTotal) {
341            itemTotal.textContent = data.itemTotalFormatted;
342          }
343        }
344      } catch (error) {
345        if (input) {
346          input.disabled = false;
347          input.value = previousValue;
348        }
349      }
350    }
351
352    async function removeItem(form, formData) {
353      const submitBtn = form.querySelector('.migo-shop-cart__remove-button');
354      if (submitBtn) submitBtn.disabled = true;
355
356      formData.set('quantity', '0');
357
358      try {
359        const response = await fetch('/shop-api/cart/update', {
360          method: 'POST',
361          body: formData,
362        });
363        const data = await response.json();
364        if (submitBtn) submitBtn.disabled = false;
365
366        applyCartResponse(data);
367
368        const item = form.closest('.migo-shop-cart__item');
369        if (item) {
370          const cartEl = item.closest(CART_SELECTOR);
371          item.remove();
372          const remaining = cartEl.querySelectorAll('.migo-shop-cart__item').length;
373          if (remaining === 0) {
374            showEmptyCart(cartEl);
375          }
376        }
377      } catch (error) {
378        if (submitBtn) submitBtn.disabled = false;
379      }
380    }
381
382    async function updateDeliveryMethod(selectEl, token) {
383      const formData = new FormData();
384      formData.append('delivery_method_id', selectEl.value);
385
386      if (token) {
387        formData.append('REQUEST_TOKEN', token);
388      }
389
390      try {
391        const response = await fetch('/shop-api/cart/delivery', {
392          method: 'POST',
393          body: formData,
394        });
395        const data = await response.json();
396
397        if (!response.ok) {
398          return;
399        }
400
401        applyCartResponse(data);
402      } catch (error) {
403        // Network/parse failure: leave the previous cart state in place.
404      }
405    }
406
407    async function addToCart(button) {
408      const productId = button.dataset.productId;
409      const token = button.dataset.requestToken;
410      const formData = new FormData();
411
412      formData.append('product_id', productId);
413      formData.append('quantity', '1');
414      formData.append('REQUEST_TOKEN', token);
415
416      button.disabled = true;
417
418      try {
419        const response = await fetch('/shop-api/cart/add', {
420          method: 'POST',
421          body: formData,
422        });
423        const data = await response.json();
424
425        if (!response.ok) {
426          button.textContent = button.dataset.originalText;
427          button.disabled = false;
428
429          if (data.error === 'max_item_quantity_exceeded') {
430            notifyIfTranslated('maxItemQuantityText', data.maxItemQuantity, 'error');
431          } else if (data.error === 'max_total_quantity_exceeded') {
432            notifyIfTranslated('maxTotalQuantityText', data.maxTotalQuantity, 'error');
433          } else if (data.error === 'out_of_stock') {
434            const message = button.dataset.outOfStockText || getCartMessage('outOfStockText');
435            if (message) showNotification(message, 'error');
436          }
437          return;
438        }
439
440        button.textContent = button.dataset.addedText;
441        button.classList.add('migo-shop-add-to-cart__button--added');
442        setTimeout(() => {
443          button.textContent = button.dataset.originalText;
444          button.disabled = false;
445          button.classList.remove('migo-shop-add-to-cart__button--added');
446        }, 4000);
447
448        updateMiniCartSummary(data.totalItems, data.cartSubtotalFormatted);
449      } catch (error) {
450        button.textContent = button.dataset.originalText;
451        button.disabled = false;
452      }
453    }
454
455    function bindButtons() {
456      document.querySelectorAll(BUTTON_SELECTOR).forEach((btn) => {
457        if (btn.dataset.migoShopCartBound === '1') {
458          return;
459        }
460
461        btn.dataset.originalText = btn.textContent;
462        btn.addEventListener('click', () => {
463          addToCart(btn);
464        });
465        btn.dataset.migoShopCartBound = '1';
466      });
467    }
468
469    function bindCartForms() {
470      const cartEl = document.querySelector(CART_SELECTOR);
471      if (!cartEl) return;
472
473      const token = cartEl.dataset.requestToken;
474
475      cartEl
476        .querySelectorAll('.migo-shop-cart__quantity-input')
477        .forEach((input) => {
478          if (input.dataset.migoShopQtyBound === '1') return;
479          input.dataset.confirmedQty = input.value;
480          input.addEventListener('change', () => {
481            const form = input.closest(QTY_FORM_SELECTOR);
482            if (!form) return;
483            const previousValue = input.dataset.confirmedQty || input.value;
484            const formData = new FormData(form);
485            if (token) formData.set('REQUEST_TOKEN', token);
486            updateQuantity(form, formData, previousValue);
487          });
488          input.dataset.migoShopQtyBound = '1';
489        });
490
491      cartEl
492        .querySelectorAll(REMOVE_FORM_SELECTOR)
493        .forEach((form) => {
494          if (form.dataset.migoShopRemoveBound === '1') return;
495          form.addEventListener('submit', (e) => {
496            e.preventDefault();
497            const formData = new FormData(form);
498            if (token) formData.set('REQUEST_TOKEN', token);
499            removeItem(form, formData);
500          });
501          form.dataset.migoShopRemoveBound = '1';
502        });
503
504      cartEl
505        .querySelectorAll(DELIVERY_SELECT_SELECTOR)
506        .forEach((selectEl) => {
507          if (selectEl.dataset.migoShopDeliveryBound === '1') return;
508
509          selectEl.addEventListener('change', () => {
510            updateDeliveryMethod(selectEl, token);
511          });
512
513          selectEl.dataset.migoShopDeliveryBound = '1';
514        });
515    }
516
517    function init() {
518      bindButtons();
519      bindCartForms();
520    }
521
522    return {
523      init,
524    };
525  })();
526
527  const formatPrice = (cents) => {
528    const value = (parseInt(cents, 10) || 0) / 100;
529    const formatted = new Intl.NumberFormat('pl-PL', {
530      minimumFractionDigits: 2,
531      maximumFractionDigits: 2,
532    }).format(value);
533
534    return `${formatted} zł`;
535  };
536
537  const getRequestToken = () => {
538    const el = document.querySelector('input[name="REQUEST_TOKEN"]');
539
540    return el ? el.value : '';
541  };
542
543  const persistMethods = (deliveryMethodId, paymentMethodId) => {
544    const token = getRequestToken();
545
546    if (!token) {
547      return;
548    }
549
550    const body = new FormData();
551    body.set('REQUEST_TOKEN', token);
552    body.set('persist_methods', '1');
553    body.set('deliveryMethodId', String(deliveryMethodId));
554    body.set('paymentMethodId', String(paymentMethodId));
555
556    fetch(window.location.href, { method: 'POST', body });
557  };
558
559  class CostSummary {
560    constructor(rootSelector, { subtotalEl, deliveryEl, paymentEl, grandEl, deliveryInput, paymentInput }) {
561      this.root = document.querySelector(rootSelector);
562
563      if (!this.root) {
564        return;
565      }
566
567      this.subtotalEl = this.root.querySelector(subtotalEl);
568      this.deliveryEl = this.root.querySelector(deliveryEl);
569      this.paymentEl = this.root.querySelector(paymentEl);
570      this.grandEl = this.root.querySelector(grandEl);
571      this.deliveryInputSelector = deliveryInput;
572      this.paymentInputSelector = paymentInput;
573
574      if (!this.subtotalEl) {
575        return;
576      }
577
578      this.bindEvents();
579      this.recalc();
580    }
581
582    getCheckedPrice(selector) {
583      const checked = this.root.querySelector(`${selector}:checked`);
584
585      if (!checked) {
586        return 0;
587      }
588
589      return parseInt(checked.dataset.price, 10) || 0;
590    }
591
592    bindEvents() {
593      const handler = () => this.recalc();
594
595      this.root.querySelectorAll(this.deliveryInputSelector).forEach((el) => {
596        el.addEventListener('change', () => {
597          persistMethods(parseInt(el.value, 10) || 0, this.getCheckedPaymentId());
598          handler();
599        });
600      });
601
602      this.root.querySelectorAll(this.paymentInputSelector).forEach((el) => {
603        el.addEventListener('change', () => {
604          persistMethods(this.getCheckedDeliveryId(), parseInt(el.value, 10) || 0);
605          handler();
606        });
607      });
608    }
609
610    getCheckedDeliveryId() {
611      const checked = this.root.querySelector(`${this.deliveryInputSelector}:checked`);
612
613      return checked ? parseInt(checked.value, 10) || 0 : 0;
614    }
615
616    getCheckedPaymentId() {
617      const checked = this.root.querySelector(`${this.paymentInputSelector}:checked`);
618
619      return checked ? parseInt(checked.value, 10) || 0 : 0;
620    }
621
622    recalc() {
623      const subtotal = parseInt(this.subtotalEl.dataset.value, 10) || 0;
624      const deliveryPrice = this.getCheckedPrice(this.deliveryInputSelector);
625      const paymentPrice = this.getCheckedPrice(this.paymentInputSelector);
626      const grandTotal = subtotal + deliveryPrice + paymentPrice;
627
628      if (this.deliveryEl) {
629        this.deliveryEl.textContent = formatPrice(deliveryPrice);
630        this.deliveryEl.dataset.value = deliveryPrice;
631      }
632
633      if (this.paymentEl) {
634        this.paymentEl.textContent = formatPrice(paymentPrice);
635        this.paymentEl.dataset.value = paymentPrice;
636      }
637
638      if (this.grandEl) {
639        this.grandEl.textContent = formatPrice(grandTotal);
640        this.grandEl.dataset.value = grandTotal;
641      }
642    }
643  }
644
645  class CartDelivery {
646    constructor(rootSelector, { selectEl, subtotalEl, deliveryEl, grandEl }) {
647      this.root = document.querySelector(rootSelector);
648
649      if (!this.root) {
650        return;
651      }
652
653      this.selectEl = this.root.querySelector(selectEl);
654      this.subtotalEl = this.root.querySelector(subtotalEl);
655      this.deliveryEl = this.root.querySelector(deliveryEl);
656      this.grandEl = this.root.querySelector(grandEl);
657
658      if (!this.selectEl || !this.subtotalEl) {
659        return;
660      }
661
662      this.bindEvents();
663    }
664
665    bindEvents() {
666      this.selectEl.addEventListener('change', () => {
667        const option = this.selectEl.options[this.selectEl.selectedIndex];
668        const deliveryPrice = option ? parseInt(option.dataset.price, 10) || 0 : 0;
669        const subtotal = parseInt(this.subtotalEl.dataset.value, 10) || 0;
670
671        if (this.deliveryEl) {
672          this.deliveryEl.textContent = formatPrice(deliveryPrice);
673          this.deliveryEl.dataset.value = deliveryPrice;
674        }
675
676        if (this.grandEl) {
677          const grandTotal = subtotal + deliveryPrice;
678          this.grandEl.textContent = formatPrice(grandTotal);
679          this.grandEl.dataset.value = grandTotal;
680        }
681
682        if (this.selectEl.form) {
683          this.selectEl.form.submit();
684        }
685      });
686    }
687  }
688
689  window.migoShopCost = {
690    init() {
691      new CostSummary('.migo-shop-checkout', {
692        subtotalEl: '#js-checkout-subtotal',
693        deliveryEl: '#js-checkout-delivery-price',
694        paymentEl: '#js-checkout-payment-price',
695        grandEl: '#js-checkout-grand-total',
696        deliveryInput: 'input[name="deliveryMethodId"]',
697        paymentInput: 'input[name="paymentMethodId"]',
698      });
699
700      new CartDelivery('.migo-shop-cart', {
701        selectEl: '#migo-shop-cart-delivery',
702        subtotalEl: '#js-cart-subtotal',
703        deliveryEl: '#js-cart-delivery-price',
704        grandEl: '#js-cart-grand-total',
705      });
706    },
707  };
708
709  $(document).ready(() => {
710      const $track = $('.partners-slider__track');
711      if ($track.length) {
712          $track.slick({
713              autoplay: true,
714              autoplaySpeed: 4000,
715              slidesToShow: 6,
716              slidesToScroll: 1,
717              arrows: true,
718              dots: false,
719              prevArrow: `<button type="button" class="slick-prev" aria-label="${$track.data('prev-text')}"></button>`,
720              nextArrow: `<button type="button" class="slick-next" aria-label="${$track.data('next-text')}"></button>`,
721              responsive: [
722                  { breakpoint: 1400, settings: { slidesToShow: 5 } },
723                  { breakpoint: 1200, settings: { slidesToShow: 4 } },
724                  { breakpoint: 992,  settings: { slidesToShow: 3 } },
725                  { breakpoint: 600,  settings: { slidesToShow: 2 } },
726              ]
727          });
728      }
729  });
730
731  function bootMigoShop() {
732    if (window.migoShopFilters && typeof window.migoShopFilters.init === 'function') {
733  	window.migoShopFilters.init();
734    }
735    if (window.migoShopCart && typeof window.migoShopCart.init === 'function') {
736  	window.migoShopCart.init();
737    }
738    if (window.migoShopCost && typeof window.migoShopCost.init === 'function') {
739  	window.migoShopCost.init();
740    }
741  }
742
743  if (document.readyState === 'loading') {
744    document.addEventListener('DOMContentLoaded', bootMigoShop);
745  } else {
746    bootMigoShop();
747  }
748
749})();

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.