1const isPreview = (typeof isPreviewMode === 'function') ? isPreviewMode() : false; 2const currentOrg = getCurrentOrg(); 3 4document.addEventListener("alpine:init", () => { 5 Alpine.data("webinarListData", () => ({ 6 webinars: [], 7 page: 0, 8 limit: 6, 9 total: 0, 10 loading: false, 11 hasMore: true, 12 observer: null, 13 activeTab: "upcoming", 14 upcomingCount: 0, 15 pastCount: 0, 16 allCount: 0, 17 orgId: "", 18 19 async init() { 20 await this.fetchTotals(); 21 22 // Set up observer FIRST 23 this.observer = new IntersectionObserver( 24 (entries) => { 25 entries.forEach((entry) => { 26 if (entry.isIntersecting && this.hasMore && !this.loading) { 27 this.fetchWebinars(); 28 } 29 }); 30 }, 31 { threshold: 0.1 } 32 ); 33 34 // Then fetch initial data 35 await this.fetchWebinars(); 36 37 // Observe after DOM is ready 38 this.$nextTick(() => { 39 if (this.$refs.loadMore) { 40 this.observer.observe(this.$refs.loadMore); 41 } 42 }); 43 44 window.webinarListData = this; 45 }, 46 47 // Fetch counts 48 async fetchTotals() { 49 try { 50 const authToken = localStorage.getItem("microserviceAuthToken"); 51 const headers = {}; 52 if (authToken) { 53 headers["Authorization"] = authToken; 54 } 55 56 const res = await fetch( 57 `/t/api/content/public/livesession/totals?orgId=${currentOrg._id}`, 58 { headers } 59 ); 60 const json = await res.json(); 61 this.upcomingCount = json?.upcoming || 0; 62 this.pastCount = json?.past || 0; 63 this.allCount = json?.all || 0; 64 } catch (e) { 65 console.error(e); 66 } 67 }, 68 69 // Switch tab 70 switchTab(tab) { 71 if (this.activeTab === tab) { 72 return; 73 } 74 75 if (this.observer && this.$refs.loadMore) { 76 this.observer.unobserve(this.$refs.loadMore); 77 } 78 79 this.activeTab = tab; 80 this.page = 0; 81 this.webinars = []; 82 this.hasMore = true; 83 this.total = 0; 84 this.fetchWebinars(); 85 this.$nextTick(() => { 86 if (this.$refs.loadMore && this.observer) { 87 this.observer.observe(this.$refs.loadMore); 88 } 89 }); 90 }, 91 92 // Fetch webinars 93 async fetchWebinars() { 94 if (this.loading || !this.hasMore) { 95 return; 96 } 97 98 this.loading = true; 99 100 try { 101 let filters = {}; 102 if (this.activeTab === "upcoming") { 103 filters = { pastSessions: [{ operator: "EQUALS", value: false }] }; 104 } else if (this.activeTab === "past") { 105 filters = { pastSessions: [{ operator: "EQUALS", value: true }] }; 106 } 107 108 const sortOrder = this.activeTab === "upcoming" ? "ascend" : "descend"; 109 110 const body = { 111 orgId: this.orgId, 112 scroll: true, 113 p: this.page, 114 s: this.limit, 115 query: "", 116 filters, 117 sort: { 118 field: "startTime", 119 order: sortOrder 120 }, 121 timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone 122 }; 123 124 // Add auth token if available 125 const authToken = localStorage.getItem("microserviceAuthToken"); 126 const headers = { 127 "Content-Type": "application/json" 128 }; 129 if (authToken) { 130 headers["Authorization"] = authToken; 131 } 132 133 const res = await fetch( 134 `/t/api/content/public/livesession/get/all?orgId=${currentOrg._id}`, 135 { 136 method: "POST", 137 headers, 138 body: JSON.stringify(body) 139 } 140 ); 141 142 const json = await res.json(); 143 144 const items = json?.data?.length 145 ? json.data 146 : (this.page === 0 && isPreview) 147 ? getMockProducts()?.webinars ?? [] 148 : []; 149 150 this.total = json?.total || items.length; 151 152 const mapped = items.map((w, index) => { 153 const badge = getWebinarStatusBadge(w); 154 const v = createVariableResolver(isVariableMode(), 'webinar'); 155 return { 156 id: w.id, 157 uniqueKey: `${w.id}-${this.activeTab}-${this.page}-${index}`, 158 title: v('title', w.title), 159 coverUrl: isPreview && w.image_url ? w.image_url : getCoverImageUrl(w.coverPath), 160 dateTime: v('dateTime', webinarUtils.getFormattedDateTime(w.startTime, w.endTime)), 161 price: v('displayPrice', formatPrice(w.isPaid, w.price, w.currency)), 162 actionText: v('actionText', getWebinarActionButtonText(w)), 163 statusBadgeText: badge.statusBadgeText, 164 statusBadgeBg: badge.statusBadgeBg, 165 isLive: badge.isLive, 166 sessionUrl: isPreview ? 'javascript:void(0)' : `/sessions/${w.sessionUrl}`, 167 raw: w 168 }; 169 }); 170 171 this.webinars.push(...mapped); 172 173 // FIX: Better hasMore logic with fallback 174 this.hasMore = typeof json?.hasMore === 'boolean' 175 ? json.hasMore 176 : (items.length >= this.limit && this.webinars.length < (this.total || Infinity)); 177 178 this.page++; 179 180 } catch (e) { 181 console.error(e); 182 this.hasMore = false;
183 } finally { 184 this.loading = false; 185 } 186 } 187 })); 188});
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.