Loading gists…
A Text snippet, saved for reference.
| 1 | <script type="module"> |
| 2 | // Import Firebase modules |
| 3 | import { initializeApp } from "https://www.gstatic.com/firebasejs/11.8.1/firebase-app.js"; |
| 4 | import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.8.1/firebase-auth.js"; |
| 5 | import { getFirestore, collection, addDoc, getDocs, deleteDoc, doc, updateDoc, getDoc } from "https://www.gstatic.com/firebasejs/11.8.1/firebase-firestore.js"; |
| 6 | |
| 7 | // Firebase configuration |
| 8 | const firebaseConfig = { |
| 9 | apiKey: "AIzaSyA3gwO6KEBUg0jEaYpd-IYlbEbjtlXNBtg", |
| 10 | authDomain: "live-stream-3df4b.firebaseapp.com", |
| 11 | projectId: "live-stream-3df4b", |
| 12 | storageBucket: "live-stream-3df4b.firebasestorage.app", |
| 13 | messagingSenderId: "41369839856", |
| 14 | appId: "1:41369839856:web:e59112ad3c7352e920ba35", |
| 15 | measurementId: "G-8K3RF8KQD1" |
| 16 | }; |
| 17 | |
| 18 | // Initialize Firebase |
| 19 | const app = initializeApp(firebaseConfig); |
| 20 | const auth = getAuth(app); |
| 21 | const db = getFirestore(app); |
| 22 | |
| 23 | // Global variables |
| 24 | let currentUser = null; |
| 25 | let streams = []; |
| 26 | |
| 27 | // DOM Elements |
| 28 | const homePage = document.getElementById('homePage'); |
| 29 | const authPage = document.getElementById('authPage'); |
| 30 | const adminPanel = document.getElementById('adminPanel'); |
| 31 | const loginForm = document.getElementById('loginForm'); |
| 32 | const signupForm = document.getElementById('signupForm'); |
| 33 | |
| 34 | // Navigation |
| 35 | document.getElementById('goToAuth').addEventListener('click', () => { |
| 36 | homePage.classList.add('d-none'); |
| 37 | authPage.classList.remove('d-none'); |
| 38 | }); |
| 39 | |
| 40 | document.getElementById('showSignup').addEventListener('click', (e) => { |
| 41 | e.preventDefault(); |
| 42 | loginForm.classList.add('d-none'); |
| 43 | signupForm.classList.remove('d-none'); |
| 44 | }); |
| 45 | |
| 46 | document.getElementById('showLogin').addEventListener('click', (e) => { |
| 47 | e.preventDefault(); |
| 48 | signupForm.classList.add('d-none'); |
| 49 | loginForm.classList.remove('d-none'); |
| 50 | }); |
| 51 | |
| 52 | // Authentication |
| 53 | onAuthStateChanged(auth, (user) => { |
| 54 | if (user) { |
| 55 | currentUser = user; |
| 56 | showAdminPanel(); |
| 57 | loadStreams(); |
| 58 | updateStats(); |
| 59 | } else { |
| 60 | currentUser = null; |
| 61 | showAuthPage(); |
| 62 | } |
| 63 | }); |
| 64 | |
| 65 | // Login |
| 66 | document.getElementById('loginFormElement').addEventListener('submit', async (e) => { |
| 67 | e.preventDefault(); |
| 68 | const email = document.getElementById('loginEmail').value; |
| 69 | const password = document.getElementById('loginPassword').value; |
| 70 | |
| 71 | showLoading('login', true); |
| 72 | try { |
| 73 | await signInWithEmailAndPassword(auth, email, password); |
| 74 | showAlert('Login successful!', 'success'); |
| 75 | } catch (error) { |
| 76 | showAlert(error.message, 'danger'); |
| 77 | } |
| 78 | showLoading('login', false); |
| 79 | }); |
| 80 | |
| 81 | // Signup |
| 82 | document.getElementById('signupFormElement').addEventListener('submit', async (e) => { |
| 83 | e.preventDefault(); |
| 84 | const name = document.getElementById('signupName').value; |
| 85 | const email = document.getElementById('signupEmail').value; |
| 86 | const password = document.getElementById('signupPassword').value; |
| 87 | |
| 88 | showLoading('signup', true); |
| 89 | try { |
| 90 | const userCredential = await createUserWithEmailAndPassword(auth, email, password); |
| 91 | // Store user info in Firestore |
| 92 | await addDoc(collection(db, 'users'), { |
| 93 | uid: userCredential.user.uid, |
| 94 | name: name, |
| 95 | email: email, |
| 96 | role: 'admin', |
| 97 | createdAt: new Date() |
| 98 | }); |
| 99 | showAlert('Account created successfully!', 'success'); |
| 100 | } catch (error) { |
| 101 | showAlert(error.message, 'danger'); |
| 102 | } |
| 103 | showLoading('signup', false); |
| 104 | }); |
| 105 | |
| 106 | // Logout |
| 107 | document.getElementById('logoutBtn').addEventListener('click', async () => { |
| 108 | try { |
| 109 | await signOut(auth); |
| 110 | showAlert('Logged out successfully!', 'success'); |
| 111 | } catch (error) { |
| 112 | showAlert(error.message, 'danger'); |
| 113 | } |
| 114 | }); |
| 115 | |
| 116 | // Show/Hide functions |
| 117 | function showAuthPage() { |
| 118 | homePage.classList.add('d-none'); |
| 119 | authPage.classList.remove('d-none'); |
| 120 | adminPanel.classList.add('d-none'); |
| 121 | } |
| 122 | |
| 123 | function showAdminPanel() { |
| 124 | homePage.classList.add('d-none'); |
| 125 | authPage.classList.add('d-none'); |
| 126 | adminPanel.classList.remove('d-none'); |
| 127 | |
| 128 | // Set admin name |
| 129 | if (currentUser) { |
| 130 | const adminName = currentUser.email.split('@')[0]; |
| 131 | document.getElementById('adminName').textContent = adminName; |
| 132 | document.getElementById('adminAvatar').textContent = adminName.charAt(0).toUpperCase(); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | function showLoading(type, show) { |
| 137 | const loader = document.getElementById(`${type}Loader`); |
| 138 | const text = document.getElementById(`${type}Text`); |
| 139 | |
| 140 | if (show) { |
| 141 | loader.classList.remove('d-none'); |
| 142 | text.textContent = 'Please wait...'; |
| 143 | } else { |
| 144 | loader.classList.add('d-none'); |
| 145 | text.textContent = type === 'login' ? 'Sign In' : 'Create Account'; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | function showAlert(message, type) { |
| 150 | const alert = document.getElementById('authAlert'); |
| 151 | alert.className = `alert alert-${type}`; |
| 152 | alert.textContent = message; |
| 153 | alert.classList.remove('d-none'); |
| 154 | |
| 155 | setTimeout(() => { |
| 156 | alert.classList.add('d-none'); |
| 157 | }, 5000); |
| 158 | } |
| 159 | |
| 160 | // Navigation between sections |
| 161 | document.querySelectorAll('.nav-link').forEach(link => { |
| 162 | link.addEventListener('click', (e) => { |
| 163 | e.preventDefault(); |
| 164 | const section = link.getAttribute('data-section'); |
| 165 | |
| 166 | // Update active nav |
| 167 | document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active')); |
| 168 | link.classList.add('active'); |
| 169 | |
| 170 | // Show section |
| 171 | document.querySelectorAll('.content-section').forEach(s => s.classList.add('d-none')); |
| 172 | document.getElementById(`${section}Section`).classList.remove('d-none'); |
| 173 | |
| 174 | // Update page title |
| 175 | const titles = { |
| 176 | dashboard: 'Dashboard', |
| 177 | streams: 'Live Streams', |
| 178 | categories: 'Categories', |
| 179 | analytics: 'Analytics', |
| 180 | settings: 'Settings' |
| 181 | }; |
| 182 | document.getElementById('pageTitle').textContent = titles[section]; |
| 183 | }); |
| 184 | }); |
| 185 | |
| 186 | // Stream Management Functions |
| 187 | async function loadStreams() { |
| 188 | try { |
| 189 | const querySnapshot = await getDocs(collection(db, 'streams')); |
| 190 | streams = []; |
| 191 | querySnapshot.forEach((doc) => { |
| 192 | streams.push({ id: doc.id, ...doc.data() }); |
| 193 | }); |
| 194 | renderStreamsTable(); |
| 195 | updateStats(); |
| 196 | } catch (error) { |
| 197 | console.error('Error loading streams:', error); |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | function renderStreamsTable() { |
| 202 | const tbody = document.getElementById('streamsTableBody'); |
| 203 | tbody.innerHTML = ''; |
| 204 | |
| 205 | streams.forEach(stream => { |
| 206 | const row = document.createElement('tr'); |
| 207 | row.innerHTML = ` |
| 208 | <td class="fw-semibold">${stream.sport}</td> |
| 209 | <td>${stream.tournament}</td> |
| 210 | <td>${stream.match}</td> |
| 211 | <td> |
| 212 | <span class="badge bg-primary">${stream.channels.length} channels</span> |
| 213 | </td> |
| 214 | <td> |
| 215 | <button class="btn btn-sm btn-success me-2" onclick="editStream('${stream.id}')"> |
| 216 | <i class="fas fa-edit"></i> |
| 217 | </button> |
| 218 | <button class="btn btn-sm btn-danger" onclick="deleteStream('${stream.id}')"> |
| 219 | <i class="fas fa-trash"></i> |
| 220 | </button> |
| 221 | </td> |
| 222 | `; |
| 223 | tbody.appendChild(row); |
| 224 | }); |
| 225 | } |
| 226 | |
| 227 | function updateStats() { |
| 228 | document.getElementById('totalStreams').textContent = streams.length; |
| 229 | document.getElementById('activeStreams').textContent = streams.length; |
| 230 | |
| 231 | const categories = [...new Set(streams.map(s => s.sport))]; |
| 232 | document.getElementById('totalCategories').textContent = categories.length; |
| 233 | |
| 234 | const totalChannels = streams.reduce((total, stream) => total + stream.channels.length, 0); |
| 235 | document.getElementById('totalChannels').textContent = totalChannels; |
| 236 | } |
| 237 | |
| 238 | // Add Stream |
| 239 | document.getElementById('saveStreamBtn').addEventListener('click', async () => { |
| 240 | const sport = document.getElementById('streamSport').value; |
| 241 | const tournament = document.getElementById('streamTournament').value; |
| 242 | const match = document.getElementById('streamMatch').value; |
| 243 | const channelInputs = document.querySelectorAll('.channel-input'); |
| 244 | const channels = Array.from(channelInputs).map(input => input.value).filter(url => url.trim() !== ''); |
| 245 | |
| 246 | if (!sport || !tournament || !match || channels.length === 0) { |
| 247 | alert('Please fill all fields and add at least one channel URL'); |
| 248 | return; |
| 249 | } |
| 250 | |
| 251 | try { |
| 252 | if (!currentUser) { |
| 253 | alert('User not authenticated. Please log in again.'); |
| 254 | return; |
| 255 | } |
| 256 | await addDoc(collection(db, 'streams'), { |
| 257 | sport, |
| 258 | tournament, |
| 259 | match, |
| 260 | channels, |
| 261 | createdAt: new Date(), |
| 262 | createdBy: currentUser.uid |
| 263 | }); |
| 264 | |
| 265 | // Close modal and refresh |
| 266 | const modal = bootstrap.Modal.getInstance(document.getElementById('addStreamModal')); |
| 267 | modal.hide(); |
| 268 | |
| 269 | // Reset form |
| 270 | document.getElementById('addStreamForm').reset(); |
| 271 | resetChannelInputs(); |
| 272 | |
| 273 | loadStreams(); |
| 274 | alert('Stream added successfully!'); |
| 275 | } catch (error) { |
| 276 | console.error('Error adding stream:', error); |
| 277 | alert('Error adding stream. Please try again.'); |
| 278 | } |
| 279 | }); |
| 280 | |
| 281 | // Edit Stream |
| 282 | window.editStream = async (streamId) => { |
| 283 | const stream = streams.find(s => s.id === streamId); |
| 284 | if (!stream) return; |
| 285 | |
| 286 | document.getElementById('editStreamId').value = streamId; |
| 287 | document.getElementById('editStreamSport').value = stream.sport; |
| 288 | document.getElementById('editStreamTournament').value = stream.tournament; |
| 289 | document.getElementById('editStreamMatch').value = stream.match; |
| 290 | |
| 291 | // Populate channels |
| 292 | const editChannelInputs = document.getElementById('editChannelInputs'); |
| 293 | editChannelInputs.innerHTML = ''; |
| 294 | |
| 295 | stream.channels.forEach((channel, index) => { |
| 296 | const inputGroup = document.createElement('div'); |
| 297 | inputGroup.className = 'input-group mb-2'; |
| 298 | inputGroup.innerHTML = ` |
| 299 | <input type="url" class="form-control form-control-dark edit-channel-input" value="${channel}" required> |
| 300 | <button type="button" class="btn btn-outline-danger" onclick="removeEditChannelInput(this)"> |
| 301 | <i class="fas fa-times"></i> |
| 302 | </button> |
| 303 | `; |
| 304 | editChannelInputs.appendChild(inputGroup); |
| 305 | }); |
| 306 | |
| 307 | // Add empty input for new channels |
| 308 | const inputGroup = document.createElement('div'); |
| 309 | inputGroup.className = 'input-group mb-2'; |
| 310 | inputGroup.innerHTML = ` |
| 311 | <input type="url" class="form-control form-control-dark edit-channel-input" placeholder="Add new channel URL"> |
| 312 | <button type="button" class="btn btn-outline-success" onclick="addEditChannelInput()"> |
| 313 | <i class="fas fa-plus"></i> |
| 314 | </button> |
| 315 | `; |
| 316 | editChannelInputs.appendChild(inputGroup); |
| 317 | |
| 318 | const modal = new bootstrap.Modal(document.getElementById('editStreamModal')); |
| 319 | modal.show(); |
| 320 | }; |
| 321 | |
| 322 | // Update Stream |
| 323 | document.getElementById('updateStreamBtn').addEventListener('click', async () => { |
| 324 | const streamId = document.getElementById('editStreamId').value; |
| 325 | const sport = document.getElementById('editStreamSport').value; |
| 326 | const tournament = document.getElementById('editStreamTournament').value; |
| 327 | const match = document.getElementById('editStreamMatch').value; |
| 328 | const channelInputs = document.querySelectorAll('.edit-channel-input'); |
| 329 | const channels = Array.from(channelInputs).map(input => input.value).filter(url => url.trim() !== ''); |
| 330 | |
| 331 | if (!sport || !tournament || !match || channels.length === 0) { |
| 332 | alert('Please fill all fields and add at least one channel URL'); |
| 333 | return; |
| 334 | } |
| 335 | |
| 336 | try { |
| 337 | await updateDoc(doc(db, 'streams', streamId), { |
| 338 | sport, |
| 339 | tournament, |
| 340 | match, |
| 341 | channels, |
| 342 | updatedAt: new Date() |
| 343 | }); |
| 344 | |
| 345 | const modal = bootstrap.Modal.getInstance(document.getElementById('editStreamModal')); |
| 346 | modal.hide(); |
| 347 | |
| 348 | loadStreams(); |
| 349 | alert('Stream updated successfully!'); |
| 350 | } catch (error) { |
| 351 | console.error('Error updating stream:', error); |
| 352 | alert('Error updating stream. Please try again.'); |
| 353 | } |
| 354 | }); |
| 355 | |
| 356 | // Delete Stream |
| 357 | window.deleteStream = async (streamId) => { |
| 358 | if (!confirm('Are you sure you want to delete this stream?')) return; |
| 359 | |
| 360 | try { |
| 361 | await deleteDoc(doc(db, 'streams', streamId)); |
| 362 | loadStreams(); |
| 363 | alert('Stream deleted successfully!'); |
| 364 | } catch (error) { |
| 365 | console.error('Error deleting stream:', error); |
| 366 | alert('Error deleting stream. Please try again.'); |
| 367 | } |
| 368 | }; |
| 369 | |
| 370 | // Channel Input Management |
| 371 | window.addChannelInput = () => { |
| 372 | const channelInputs = document.getElementById('channelInputs'); |
| 373 | const inputGroup = document.createElement('div'); |
| 374 | inputGroup.className = 'input-group mb-2'; |
| 375 | inputGroup.innerHTML = ` |
| 376 | <input type="url" class="form-control form-control-dark channel-input" placeholder="https://example.com/channel/"> |
| 377 | <button type="button" class="btn btn-outline-danger" onclick="removeChannelInput(this)"> |
| 378 | <i class="fas fa-times"></i> |
| 379 | </button> |
| 380 | `; |
| 381 | channelInputs.appendChild(inputGroup); |
| 382 | }; |
| 383 | |
| 384 | window.removeChannelInput = (button) => { |
| 385 | button.parentElement.remove(); |
| 386 | }; |
| 387 | |
| 388 | window.addEditChannelInput = () => { |
| 389 | const editChannelInputs = document.getElementById('editChannelInputs'); |
| 390 | const inputGroup = document.createElement('div'); |
| 391 | inputGroup.className = 'input-group mb-2'; |
| 392 | inputGroup.innerHTML = ` |
| 393 | <input type="url" class="form-control form-control-dark edit-channel-input" placeholder="https://example.com/channel/"> |
| 394 | <button type="button" class="btn btn-outline-danger" onclick="removeEditChannelInput(this)"> |
| 395 | <i class="fas fa-times"></i> |
| 396 | </button> |
| 397 | `; |
| 398 | editChannelInputs.appendChild(inputGroup); |
| 399 | }; |
| 400 | |
| 401 | window.removeEditChannelInput = (button) => { |
| 402 | button.parentElement.remove(); |
| 403 | }; |
| 404 | |
| 405 | function resetChannelInputs() { |
| 406 | const channelInputs = document.getElementById('channelInputs'); |
| 407 | channelInputs.innerHTML = ` |
| 408 | <div class="input-group mb-2"> |
| 409 | <input type="url" class="form-control form-control-dark channel-input" placeholder="https://example.com/channel/1" required> |
| 410 | <button type="button" class="btn btn-outline-success" onclick="addChannelInput()"> |
| 411 | <i class="fas fa-plus"></i> |
| 412 | </button> |
| 413 | </div> |
| 414 | `; |
| 415 | } |
| 416 | |
| 417 | // Sidebar toggle for mobile |
| 418 | document.getElementById('toggleSidebar').addEventListener('click', () => { |
| 419 | const sidebar = document.querySelector('.sidebar'); |
| 420 | sidebar.classList.toggle('show'); |
| 421 | }); |
| 422 | |
| 423 | // Initialize app |
| 424 | document.addEventListener('DOMContentLoaded', () => { |
| 425 | // Check authentication state |
| 426 | onAuthStateChanged(auth, (user) => { |
| 427 | if (user) { |
| 428 | currentUser = user; |
| 429 | showAdminPanel(); |
| 430 | loadStreams(); |
| 431 | } else { |
| 432 | currentUser = null; |
| 433 | showAuthPage(); |
| 434 | } |
| 435 | }); |
| 436 | }); |
| 437 | </script> |