PageSourceSearch

https://zoloblocks.com/wp-content/plugins/bdthemes-notice-viewer/assets/notice-viewer.js?ver=3.2.2

js zoloblocks.com collected 2026-09-25 20:50:04 UTC 11,864 bytes, 387 lines download raw bytes

1class NoticeBanner {
2  constructor() {
3    this.apiUrl =
4      "https://api.sigmative.io/prod/store/api/biggopti/api-data-records";
5    this.notices = {};
6    this.currentFilter = "all";
7    this.countdownInterval = null;
8    this.currentNoticeIndex = 0;
9    this.rotationInterval = null;
10    this.isVisible = false;
11    this.isDismissed = false;
12
13    // Domain to product mapping
14    this.domainMapping = {
15      "bdthemes.com": "bdthemes-com",
16      "elementpack.pro": "element-pack",
17      "sigmaforms.ai": "sigma-forms",
18      "launchguard.pro": "launch-guard",
19      "oneaccessibility.com": "one-accessibility",
20      "smartadminassistant.com": "smart-admin-assistant",
21      "primeslider.pro": "prime-slider",
22      "postkit.pro": "ultimate-post-kit",
23      "zoloblocks.com": "zoloblocks",
24      "storekit.pro": "ultimate-store-kit",
25      "pixelgallery.pro": "pixel-gallery",
26      "spinwheel.bdthemes.com": "spin-wheel",
27      "swiftcheckout.pro": "swift-checkout",
28      "darkreader.com": "dark-reader",
29    };
30
31    // Check if banner was previously dismissed
32    if (localStorage.getItem("noticeBannerDismissed")) {
33      this.isDismissed = true;
34      // Hide the wrapper immediately to prevent loading state from showing
35      // Use DOMContentLoaded to ensure element exists
36      if (document.readyState === 'loading') {
37        document.addEventListener('DOMContentLoaded', () => {
38          const wrapper = document.getElementById("biggopti-wrapper");
39          if (wrapper) {
40            wrapper.style.display = "none";
41          }
42        });
43      } else {
44        const wrapper = document.getElementById("biggopti-wrapper");
45        if (wrapper) {
46          wrapper.style.display = "none";
47        }
48      }
49      return;
50    }
51
52    // Auto-detect current website and set filter
53    this.autoDetectWebsite();
54
55    this.init();
56  }
57
58  autoDetectWebsite() {
59    // Check for manual override in URL parameters (for testing)
60    const urlParams = new URLSearchParams(window.location.search);
61    const manualProduct = urlParams.get("product");
62
63    if (
64      manualProduct &&
65      Object.values(this.domainMapping).includes(manualProduct)
66    ) {
67      this.currentFilter = manualProduct;
68      return;
69    }
70
71    const currentDomain = window.location.hostname;
72
73    // Check if current domain matches any of our mapped domains
74    if (this.domainMapping[currentDomain]) {
75      this.currentFilter = this.domainMapping[currentDomain];
76    } else {
77      // Check for subdomain patterns (e.g., www.postkit.pro)
78      const mainDomain = currentDomain.replace(/^www\./, "");
79      if (this.domainMapping[mainDomain]) {
80        this.currentFilter = this.domainMapping[mainDomain];
81      } else {
82        // For localhost or unknown domains, show element-pack notices
83        this.currentFilter = "element-pack";
84      }
85    }
86  }
87
88  async init() {
89    if (this.isDismissed) return;
90
91    this.setupEventListeners();
92    const dataFetched = await this.fetchNotices();
93
94    // Only render if data was successfully fetched
95    if (dataFetched) {
96      this.renderBanner();
97    }
98  }
99
100  setupEventListeners() {
101    // Close button event listener
102    document.addEventListener("click", (e) => {
103      if (e.target.closest(".close-button")) {
104        e.preventDefault();
105        e.stopPropagation();
106        this.dismissBanner();
107      }
108    });
109  }
110
111  async fetchNotices() {
112    try {
113      // Try direct fetch first, fallback to CORS proxy if needed
114      let response;
115      try {
116        response = await fetch(this.apiUrl);
117        if (!response.ok) {
118          throw new Error(`HTTP error! status: ${response.status}`);
119        }
120      } catch (corsError) {
121        if (!response.ok) {
122          throw new Error(`Direct fetch failed! status: ${response.status}`);
123        }
124      }
125
126      this.notices = await response.json();
127
128      // Check if notices object is empty or invalid
129      if (!this.notices || Object.keys(this.notices).length === 0) {
130        this.hideBanner();
131        return false;
132      }
133      return true;
134    } catch (error) {
135      console.error("Error fetching notices:", error);
136      this.hideBanner();
137      return false;
138    }
139  }
140
141  getFilteredNotices() {
142    if (this.currentFilter === "all") {
143      const allNotices = [];
144      Object.entries(this.notices).forEach(([product, notices]) => {
145        notices.forEach((notice) => {
146          allNotices.push({ ...notice, product });
147        });
148      });
149      return allNotices;
150    } else {
151      return this.notices[this.currentFilter] || [];
152    }
153  }
154
155  renderBanner() {
156    const filteredNotices = this.getFilteredNotices();
157    const activeNotices = filteredNotices.filter((notice) => {
158      return (
159        notice?.is_enabled &&
160        notice?.is_enabled === true &&
161        this.isNoticeActive(notice)
162      );
163    });
164
165    if (activeNotices.length === 0) {
166      this.hideBanner();
167      return;
168    }
169
170    this.activeNotices = activeNotices;
171    this.currentNoticeIndex = 0;
172    this.renderCurrentNotice();
173    this.showBanner();
174  }
175
176  renderCurrentNotice() {
177    const notice = this.activeNotices[this.currentNoticeIndex];
178
179    // Get all the elements
180    const wrapper = document.getElementById("biggopti-wrapper");
181    const bannerContent = document.getElementById("biggopti-content");
182    const logoContainer = document.querySelector(".biggopti-logo");
183    const logoImg = document.getElementById("biggopti-logo-img");
184    const title = document.getElementById("biggopti-title");
185    const description = document.getElementById("biggopti-description");
186    const countdownLabel = document.getElementById("biggopti-countdown-label");
187    const countdownDisplay = document.getElementById("biggopti-countdown");
188    const countdownContent = document.getElementById("countdown-content");
189    const actionBtn = document.getElementById("biggopti-action-btn");
190    const btnText = document.getElementById("biggopti-btn-text");
191
192    // Set wrapper link from API
193    if (notice.link && wrapper) {
194      wrapper.href = notice.link;
195      wrapper.target = "_blank";
196    }
197
198    // Remove loading state
199    bannerContent.classList.remove("loading");
200
201    // Set background image
202    if (notice.image) {
203      bannerContent.style.backgroundImage = `url('${notice.image}')`;
204    }
205
206    // Handle logo
207    if (notice.logo) {
208      logoImg.src = notice.logo;
209      logoContainer.style.display = "block";
210    } else {
211      logoContainer.style.display = "none";
212    }
213
214    // Set title and description
215    title.textContent = notice.title;
216    description.innerHTML = notice.content;
217
218    // Handle countdown or offer text
219    if (notice.show_countdown) {
220      countdownLabel.style.display = "block";
221      countdownDisplay.style.display = "flex";
222      countdownContent.style.display = "none";
223      countdownDisplay.innerHTML = "Loading...";
224      this.startCountdown(notice.end_date, notice.timezone || 'UTC');
225    } else {
226      countdownLabel.style.display = "none";
227      countdownDisplay.style.display = "none";
228      countdownContent.style.display = "block";
229      countdownContent.textContent = notice.countdown_content || '';
230    }
231
232    // Handle action button
233    if (notice.button_text && notice.link) {
234      actionBtn.href = notice.link;
235      btnText.textContent = notice.button_text;
236      actionBtn.style.display = "flex";
237    } else {
238      actionBtn.style.display = "none";
239    }
240
241    // Show the banner
242    bannerContent.classList.remove("hidden");
243  }
244
245  isNoticeActive(notice) {
246    const timezone = notice.timezone || 'UTC';
247    const now = this.getDateInTimezone(new Date(), timezone);
248    const startDate = this.getDateInTimezone(new Date(notice.start_date + (timezone === 'UTC' ? 'Z' : '')), timezone);
249    const endDate = this.getDateInTimezone(new Date(notice.end_date + (timezone === 'UTC' ? 'Z' : '')), timezone);
250
251    return now >= startDate && now <= endDate;
252  }
253
254  startCountdown(endDate, timezone = 'UTC') {
255    if (this.countdownInterval) {
256      clearInterval(this.countdownInterval);
257    }
258
259    const countdownElement = document.getElementById("biggopti-countdown");
260    if (!countdownElement) return;
261
262    const updateCountdown = () => {
263      const now = this.getDateInTimezone(new Date(), timezone).getTime();
264      const end = this.getDateInTimezone(new Date(endDate + (timezone === 'UTC' ? 'Z' : '')), timezone).getTime();
265      const distance = end - now;
266
267      if (distance < 0) {
268        countdownElement.innerHTML =
269          '<span class="text-red-200">Expired</span>';
270        if (this.countdownInterval) {
271          clearInterval(this.countdownInterval);
272          this.countdownInterval = null;
273        }
274        return;
275      }
276
277      const days = Math.floor(distance / (1000 * 60 * 60 * 24));
278      const hours = Math.floor(
279        (distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
280      );
281      const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
282      const seconds = Math.floor((distance % (1000 * 60)) / 1000);
283
284      // Pad with leading zero if less than 10
285      const pad = (n) => n < 10 ? '0' + n : n;
286
287      let countdownHtml = "";
288
289      // Always show days, if 0 or less, show 00
290      countdownHtml += `
291      <div class="countdown-unit">
292        <div class="countdown-number">${pad(days > 0 ? days : 0)}</div>
293        <div class="countdown-text">days</div>
294      </div>
295      <span class="countdown-separator"></span>
296    `;
297
298      countdownHtml += `
299        <div class="countdown-unit">
300          <div class="countdown-number">${pad(hours)}</div>
301          <div class="countdown-text">hrs</div>
302        </div>
303        <span class="countdown-separator"></span>
304        <div class="countdown-unit">
305          <div class="countdown-number">${pad(minutes)}</div>
306          <div class="countdown-text">mins</div>
307        </div>
308        <span class="countdown-separator"></span>
309        <div class="countdown-unit">
310          <div class="countdown-number">${pad(seconds)}</div>
311          <div class="countdown-text">secs</div>
312        </div>
313      `;
314
315      countdownElement.innerHTML = countdownHtml;
316    };
317
318    updateCountdown();
319    this.countdownInterval = setInterval(updateCountdown, 1000);
320  }
321
322  showBanner() {
323    if (this.isDismissed) return;
324    this.isVisible = true;
325    const wrapper = document.getElementById("biggopti-wrapper");
326    if (wrapper) {
327      wrapper.style.display = "block";
328    }
329  }
330
331  hideBanner() {
332    this.isVisible = false;
333    const wrapper = document.getElementById("biggopti-wrapper");
334    if (wrapper) {
335      wrapper.style.display = "none";
336    }
337    this.cleanup();
338  }
339
340  dismissBanner() {
341    this.isDismissed = true;
342    localStorage.setItem("noticeBannerDismissed", "true");
343    this.hideBanner();
344  }
345
346
347  cleanup() {
348    if (this.countdownInterval) {
349      clearInterval(this.countdownInterval);
350      this.countdownInterval = null;
351    }
352  }
353
354  show() {
355    this.isDismissed = false;
356    localStorage.removeItem("noticeBannerDismissed");
357    this.renderBanner();
358  }
359
360  hide() {
361    this.hideBanner();
362  }
363
364  updateSettings(newSettings) {
365    this.settings = { ...this.settings, ...newSettings };
366  }
367
368  // Helper method to handle timezone conversions
369  getDateInTimezone(date, timezone) {
370    if (timezone === 'UTC') {
371      return new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
372    }
373
374    // For other timezones, you might want to use a library like moment.js or date-fns
375    // For now, we'll handle UTC and assume local timezone for others
376    try {
377      return new Date(date.toLocaleString("en-US", { timeZone: timezone }));
378    } catch (error) {
379      console.warn(`Invalid timezone: ${timezone}, falling back to UTC`);
380      return new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
381    }
382  }
383}
384
385document.addEventListener("DOMContentLoaded", () => {
386  window.noticeBanner = new NoticeBanner();
387});

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.