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.