Perforated panel Designer

`; // Create blob and download const blob = new Blob([htmlContent], { type: 'text/html' }); const link = document.createElement('a'); const filename = getDownloadFilename(); link.download = `${filename}_shareable.html`; link.href = URL.createObjectURL(blob); link.click(); // Track download if user is logged in if (authManager.currentUser) { authManager.trackDownload('html').catch(err => { console.warn('Failed to track download:', err); }); } } // Helper function to get DXF content as string function getDXFContent() { if (generatedHoles.length === 0) return null; const totalH = parseFloat(inputs.h.value); const totalW = parseFloat(inputs.w.value); // Minimal DXF R12 format - simplest possible for maximum compatibility let dxf = ""; // Header - minimal required dxf += "0\nSECTION\n2\nHEADER\n"; dxf += "9\n$ACADVER\n1\nAC1009\n"; dxf += "0\nENDSEC\n"; // Tables - minimal required dxf += "0\nSECTION\n2\nTABLES\n"; // Layer table - only layer 0 required dxf += "0\nTABLE\n2\nLAYER\n70\n1\n"; dxf += "0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n"; dxf += "0\nENDTAB\n"; dxf += "0\nENDSEC\n"; // Entities section dxf += "0\nSECTION\n2\nENTITIES\n"; // Draw panel boundaries as simple POLYLINE sheetDefinitions.forEach(s => { dxf += "0\nPOLYLINE\n8\n0\n66\n1\n70\n1\n"; dxf += "0\nVERTEX\n8\n0\n10\n" + s.x.toFixed(3) + "\n20\n" + (totalH - s.y).toFixed(3) + "\n"; dxf += "0\nVERTEX\n8\n0\n10\n" + (s.x + s.w).toFixed(3) + "\n20\n" + (totalH - s.y).toFixed(3) + "\n"; dxf += "0\nVERTEX\n8\n0\n10\n" + (s.x + s.w).toFixed(3) + "\n20\n" + (totalH - (s.y + s.h)).toFixed(3) + "\n"; dxf += "0\nVERTEX\n8\n0\n10\n" + s.x.toFixed(3) + "\n20\n" + (totalH - (s.y + s.h)).toFixed(3) + "\n"; dxf += "0\nSEQEND\n8\n0\n"; }); // Draw holes as circles generatedHoles.forEach(h => { const dxfY = totalH - h.y; dxf += "0\nCIRCLE\n8\n0\n"; dxf += "10\n" + h.x.toFixed(3) + "\n"; dxf += "20\n" + dxfY.toFixed(3) + "\n"; dxf += "30\n0.0\n"; dxf += "40\n" + h.r.toFixed(3) + "\n"; }); // Draw percentage text if enabled if (showPercentage && panelStats.length > 0) { panelStats.forEach((stat, idx) => { if (idx < sheetDefinitions.length && stat.panelArea > 0) { const sheet = sheetDefinitions[idx]; const percentage = stat.panelArea > 0 ? ((stat.openArea / stat.panelArea) * 100).toFixed(1) : "0.0"; const textX = sheet.x + sheet.w / 2; const textY = totalH - (sheet.y + sheet.h / 2); dxf += "0\nTEXT\n8\n0\n"; dxf += "10\n" + textX.toFixed(3) + "\n"; dxf += "20\n" + textY.toFixed(3) + "\n"; dxf += "30\n0.0\n"; dxf += "40\n10.0\n"; dxf += "1\n" + percentage + "%\n"; dxf += "50\n0.0\n"; } }); } // Add footer statistics if enabled if (includeFooterStatsInDownload) { const stats = getFooterStatistics(); const footerY = totalH + 20; // Position below the design const fontSize = 8.0; const startX = totalW / 2 - 100; // Center the text dxf += "0\nTEXT\n8\n0\n"; dxf += "10\n" + startX.toFixed(3) + "\n"; dxf += "20\n" + footerY.toFixed(3) + "\n"; dxf += "30\n0.0\n"; dxf += "40\n" + fontSize.toFixed(1) + "\n"; dxf += "1\nPanels: " + stats.panels + " | Holes: " + stats.holes.toLocaleString() + " | Opening: " + stats.opening + " | Min Hole: " + stats.minHole + "mm | Max Hole: " + stats.maxHole + "mm | Spacing: " + stats.spacing + "mm\n"; dxf += "50\n0.0\n"; } dxf += "0\nENDSEC\n"; dxf += "0\nEOF\n"; return dxf; } // Helper function to get SVG content as string function getSVGContent() { if (generatedHoles.length === 0) return null; const totalH = parseFloat(inputs.h.value); const totalW = parseFloat(inputs.w.value); const footerHeight = includeFooterStatsInDownload ? 20 : 0; const svgHeight = totalH + footerHeight; let svg = '\n'; svg += '\n`; svg += '\n'; // Flip Y axis // Draw panel boundaries svg += '\n'; sheetDefinitions.forEach(s => { svg += `\n`; }); svg += '\n'; // Draw holes svg += '\n'; generatedHoles.forEach(h => { svg += `\n`; }); svg += '\n'; // Draw percentage text if enabled if (showPercentage && panelStats.length > 0) { svg += '\n'; panelStats.forEach((stat, idx) => { if (idx < sheetDefinitions.length && stat.panelArea > 0) { const sheet = sheetDefinitions[idx]; const percentage = stat.panelArea > 0 ? ((stat.openArea / stat.panelArea) * 100).toFixed(1) : "0.0"; const textX = sheet.x + sheet.w / 2; const textY = sheet.y + sheet.h / 2; svg += `${percentage}%\n`; } }); svg += '\n'; } svg += '\n'; // Add footer statistics if enabled if (includeFooterStatsInDownload) { const stats = getFooterStatistics(); const footerY = totalH + 20; // Position below the design svg += `Panels: ${stats.panels} | Holes: ${stats.holes.toLocaleString()} | Opening: ${stats.opening} | Min Hole: ${stats.minHole}mm | Max Hole: ${stats.maxHole}mm | Spacing: ${stats.spacing}mm\n`; } svg += ''; return svg; } async function downloadZIP() { if (!sourceImage || generatedHoles.length === 0) { alert('Please generate a pattern first.'); return; } if (typeof JSZip === 'undefined') { alert('ZIP library not loaded. Please refresh the page.'); return; } const filename = getDownloadFilename(); const zip = new JSZip(); try { // Add PNG file const pngDataURL = getPNGDataURL(); if (pngDataURL) { const pngBase64 = pngDataURL.split(',')[1]; zip.file(`${filename}.png`, pngBase64, { base64: true }); } // Add TXT file const txtContent = getTXTContent(); zip.file(`${filename}.txt`, txtContent); // Add DXF file const dxfContent = getDXFContent(); if (dxfContent) { zip.file(`${filename}.dxf`, dxfContent); } // Add SVG file const svgContent = getSVGContent(); if (svgContent) { zip.file(`${filename}.svg`, svgContent); } // Generate zip file const zipBlob = await zip.generateAsync({ type: 'blob' }); // Download const link = document.createElement('a'); link.download = `${filename}.zip`; link.href = URL.createObjectURL(zipBlob); link.click(); // Clean up URL.revokeObjectURL(link.href); } catch (error) { console.error('Error creating ZIP file:', error); alert('Error creating ZIP file. Please try again.'); } } // ========== AUTHENTICATION SYSTEM ========== // Firebase Configuration - REPLACE WITH YOUR FIREBASE CONFIG const firebaseConfig = { apiKey: "AIzaSyDyaAtK5YBWpMedGCICayR-VJq2111SpgU", authDomain: "perforated-panel-designer.firebaseapp.com", projectId: "perforated-panel-designer", storageBucket: "perforated-panel-designer.firebasestorage.app", messagingSenderId: "203709637797", appId: "1:203709637797:web:544190529c7c874aaf994c" }; // Initialize Firebase if (typeof firebase !== 'undefined') { try { firebase.initializeApp(firebaseConfig); console.log('Firebase initialized successfully'); } catch (error) { console.error('Firebase initialization error:', error); } } else { console.error('Firebase SDK not loaded'); } // Auth Manager const authManager = { auth: null, db: null, currentUser: null, isAdmin: false, adminUsername: 'admin', // Will be set from environment adminPassword: 'admin123', // Will be set from environment init() { if (typeof firebase === 'undefined') { console.warn('Firebase not loaded. Auth features disabled.'); return; } this.auth = firebase.auth(); this.db = firebase.firestore(); // Check admin credentials from environment (Cloudflare Pages) // For now, using defaults - will be overridden by env vars in production if (window.ADMIN_USERNAME) this.adminUsername = window.ADMIN_USERNAME; if (window.ADMIN_PASSWORD) this.adminPassword = window.ADMIN_PASSWORD; // Listen for auth state changes this.auth.onAuthStateChanged((user) => { this.currentUser = user; this.checkAdminStatus(); updateAuthUI(); }); // Check admin session this.checkAdminSession(); }, async checkAdminStatus() { if (!this.currentUser) { this.isAdmin = false; return; } try { const userDoc = await this.db.collection('users').doc(this.currentUser.uid).get(); this.isAdmin = userDoc.exists && userDoc.data().isAdmin === true; } catch (e) { this.isAdmin = false; } }, checkAdminSession() { const adminSession = sessionStorage.getItem('adminSession'); if (adminSession === 'true') { this.isAdmin = true; } }, async signInWithEmail(email, password) { try { await this.auth.signInWithEmailAndPassword(email, password); await this.trackLogin(); return { success: true }; } catch (error) { return { success: false, error: error.message }; } }, async signUpWithEmail(email, password) { try { const userCredential = await this.auth.createUserWithEmailAndPassword(email, password); await this.db.collection('users').doc(userCredential.user.uid).set({ email: email, createdAt: firebase.firestore.FieldValue.serverTimestamp(), isAdmin: false, downloadCount: 0 }); await this.trackLogin(); return { success: true }; } catch (error) { return { success: false, error: error.message }; } }, async signInWithGoogle() { try { if (!this.auth) { return { success: false, error: 'Firebase not initialized. Please refresh the page.' }; } const provider = new firebase.auth.GoogleAuthProvider(); // Add scopes if needed provider.addScope('profile'); provider.addScope('email'); // Set custom parameters provider.setCustomParameters({ prompt: 'select_account' }); const userCredential = await this.auth.signInWithPopup(provider); // Try to update Firestore, but don't fail login if it doesn't work try { if (this.db) { // Check if user document exists, create if not const userDoc = await this.db.collection('users').doc(userCredential.user.uid).get(); if (!userDoc.exists) { await this.db.collection('users').doc(userCredential.user.uid).set({ email: userCredential.user.email, displayName: userCredential.user.displayName || '', photoURL: userCredential.user.photoURL || '', createdAt: firebase.firestore.FieldValue.serverTimestamp(), isAdmin: false, downloadCount: 0 }); } else { // Update last login await this.db.collection('users').doc(userCredential.user.uid).update({ lastLogin: firebase.firestore.FieldValue.serverTimestamp() }); } // Track login (non-blocking) this.trackLogin().catch(err => { console.warn('Failed to track login:', err); }); } } catch (dbError) { // Log but don't fail - authentication succeeded console.warn('Firestore operation failed, but login succeeded:', dbError); // Provide helpful error message if it's a permissions issue if (dbError.code === 'permission-denied' || dbError.message.includes('permission')) { console.error('Firestore permission denied. Please check your Firestore security rules.'); // Don't show alert to user - login still succeeded } // Try to track login in background this.trackLogin().catch(err => { console.warn('Failed to track login:', err); }); } return { success: true }; } catch (error) { console.error('Google sign-in error:', error); let errorMessage = error.message; // Provide more helpful error messages if (error.code === 'auth/popup-blocked') { errorMessage = 'Popup was blocked by your browser. Please allow popups for this site and try again.'; } else if (error.code === 'auth/popup-closed-by-user') { errorMessage = 'Sign-in popup was closed. Please try again.'; } else if (error.code === 'auth/unauthorized-domain') { errorMessage = 'This domain is not authorized for Google sign-in. Please contact support.'; } else if (error.code === 'auth/operation-not-allowed') { errorMessage = 'Google sign-in is not enabled. Please enable it in Firebase Console.'; } return { success: false, error: errorMessage }; } }, async resetPassword(email) { try { await this.auth.sendPasswordResetEmail(email); return { success: true }; } catch (error) { return { success: false, error: error.message }; } }, async signOut() { try { await this.auth.signOut(); sessionStorage.removeItem('adminSession'); this.currentUser = null; this.isAdmin = false; updateAuthUI(); return { success: true }; } catch (error) { return { success: false, error: error.message }; } }, async trackLogin() { if (!this.currentUser || !this.db) return; try { await this.db.collection('loginLogs').add({ userId: this.currentUser.uid, email: this.currentUser.email, timestamp: firebase.firestore.FieldValue.serverTimestamp() }); } catch (e) { // Silently fail - don't block login if Firestore is unavailable console.warn('Failed to track login (non-critical):', e.message); } }, async trackDownload(fileType) { if (!this.currentUser || !this.db) return; try { await this.db.collection('downloads').add({ userId: this.currentUser.uid, email: this.currentUser.email, fileType: fileType, timestamp: firebase.firestore.FieldValue.serverTimestamp() }); await this.db.collection('users').doc(this.currentUser.uid).update({ downloadCount: firebase.firestore.FieldValue.increment(1) }); } catch (e) { // Silently fail - don't block downloads if Firestore is unavailable console.warn('Failed to track download (non-critical):', e.message); } }, async adminLogin(username, password) { if (username === this.adminUsername && password === this.adminPassword) { sessionStorage.setItem('adminSession', 'true'); this.isAdmin = true; updateAuthUI(); return { success: true }; } return { success: false, error: 'Invalid admin credentials' }; }, canDownload(fileType) { if (!this.currentUser) { return fileType === 'png' || fileType === 'txt'; } return true; } }; // Get superadmin email from config function getSuperAdminEmail() { // First try environment variable (set by Cloudflare Pages build process) if (window.SUPERADMIN_EMAIL) { return window.SUPERADMIN_EMAIL; } // Then try config file if (window.ADMIN_CONFIG && window.ADMIN_CONFIG.SUPERADMIN_EMAIL) { return window.ADMIN_CONFIG.SUPERADMIN_EMAIL; } return null; } // Auth UI Functions function updateAuthUI() { const userInfo = document.getElementById('userInfo'); const guestInfo = document.getElementById('guestInfo'); const userEmail = document.getElementById('userEmail'); const adminBtn = document.getElementById('adminBtn'); const adminPanelLink = document.getElementById('adminPanelLink'); if (authManager.currentUser) { userInfo.classList.remove('hidden'); userInfo.classList.add('flex'); guestInfo.classList.add('hidden'); guestInfo.classList.remove('flex'); userEmail.textContent = authManager.currentUser.email; // Check if user is superadmin const superAdminEmail = getSuperAdminEmail(); const isSuperAdmin = superAdminEmail && authManager.currentUser.email.toLowerCase() === superAdminEmail.toLowerCase(); // Show admin panel link in user menu if superadmin if (adminPanelLink) { if (isSuperAdmin) { adminPanelLink.classList.remove('hidden'); } else { adminPanelLink.classList.add('hidden'); } } // Show admin button in navbar if admin (legacy support) if (authManager.isAdmin) { adminBtn.classList.remove('hidden'); } else { adminBtn.classList.add('hidden'); } } else { userInfo.classList.add('hidden'); userInfo.classList.remove('flex'); guestInfo.classList.remove('hidden'); guestInfo.classList.add('flex'); adminBtn.classList.add('hidden'); if (adminPanelLink) { adminPanelLink.classList.add('hidden'); } } // Update download button visibility updateDownloadButtons(); } // User Menu Toggle function toggleUserMenu() { const dropdown = document.getElementById('userMenuDropdown'); dropdown.classList.toggle('hidden'); } // Close user menu when clicking outside document.addEventListener('click', (e) => { const menuContainer = document.getElementById('userMenuContainer'); const dropdown = document.getElementById('userMenuDropdown'); if (menuContainer && !menuContainer.contains(e.target)) { dropdown.classList.add('hidden'); } }); // Check if user signed in with Google function isGoogleUser() { if (!authManager.currentUser) return false; const providers = authManager.currentUser.providerData || []; return providers.some(provider => provider.providerId === 'google.com'); } // Change Password Functions function showChangePasswordModal() { document.getElementById('changePasswordModal').classList.add('active'); document.getElementById('userMenuDropdown').classList.add('hidden'); // Check if user is Google-authenticated const isGoogle = isGoogleUser(); const currentPasswordGroup = document.getElementById('currentPasswordGroup'); const currentPasswordLabel = document.getElementById('currentPasswordLabel'); if (isGoogle) { // Hide current password field for Google users if (currentPasswordGroup) { currentPasswordGroup.style.display = 'none'; } if (currentPasswordLabel) { currentPasswordLabel.textContent = 'Set Password (Google users can set a password)'; } } else { // Show current password field for email/password users if (currentPasswordGroup) { currentPasswordGroup.style.display = 'block'; } if (currentPasswordLabel) { currentPasswordLabel.textContent = 'Current Password'; } } // Clear form document.getElementById('currentPassword').value = ''; document.getElementById('newPassword').value = ''; document.getElementById('confirmPassword').value = ''; document.getElementById('changePasswordError').classList.remove('show'); document.getElementById('changePasswordSuccess').classList.remove('show'); } function closeChangePasswordModal() { document.getElementById('changePasswordModal').classList.remove('active'); } async function handleChangePassword() { const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; const errorEl = document.getElementById('changePasswordError'); const successEl = document.getElementById('changePasswordSuccess'); // Clear previous messages errorEl.classList.remove('show'); successEl.classList.remove('show'); // Check if user is Google-authenticated const isGoogle = isGoogleUser(); // Validation - skip current password for Google users if (!isGoogle && !currentPassword) { errorEl.textContent = 'Current password is required'; errorEl.classList.add('show'); return; } if (!newPassword || !confirmPassword) { errorEl.textContent = 'New password fields are required'; errorEl.classList.add('show'); return; } if (newPassword.length < 6) { errorEl.textContent = 'New password must be at least 6 characters'; errorEl.classList.add('show'); return; } if (newPassword !== confirmPassword) { errorEl.textContent = 'New passwords do not match'; errorEl.classList.add('show'); return; } try { const user = authManager.currentUser; if (isGoogle) { // For Google users, we need to link email/password provider first // But Firebase requires re-authentication for security // For Google users setting password for the first time, we'll use updatePassword // which should work if the account has email/password linked, or we'll get an error try { await user.updatePassword(newPassword); successEl.textContent = 'Password set successfully! You can now use email/password to login.'; } catch (updateError) { if (updateError.code === 'auth/requires-recent-login') { errorEl.textContent = 'For security, please sign out and sign in again, then set your password.'; errorEl.classList.add('show'); return; } else { // Try to link email/password provider // This requires the user to have a password already set errorEl.textContent = 'Unable to set password. Please contact support or try signing in with Google again.'; errorEl.classList.add('show'); return; } } } else { // For email/password users, re-authenticate first const credential = firebase.auth.EmailAuthProvider.credential( user.email, currentPassword ); await user.reauthenticateWithCredential(credential); await user.updatePassword(newPassword); successEl.textContent = 'Password changed successfully!'; } successEl.classList.add('show'); // Clear form document.getElementById('currentPassword').value = ''; document.getElementById('newPassword').value = ''; document.getElementById('confirmPassword').value = ''; // Close modal after 2 seconds setTimeout(() => { closeChangePasswordModal(); }, 2000); } catch (error) { errorEl.textContent = error.message || 'Failed to change password'; errorEl.classList.add('show'); } } function updateDownloadButtons() { // Update button styling and lock icons based on user permissions const dxfBtn = document.getElementById('downloadDxfBtn'); const svgBtn = document.getElementById('downloadSvgBtn'); const dxfLockIcon = document.getElementById('dxfLockIcon'); const svgLockIcon = document.getElementById('svgLockIcon'); const zipLockIcon = document.getElementById('zipLockIcon'); // DXF Button - always visible, show lock for guests if (dxfBtn) { dxfBtn.style.display = 'flex'; const canDownload = authManager.canDownload('dxf'); if (dxfLockIcon) { dxfLockIcon.style.display = canDownload ? 'none' : 'block'; } // Update button styling for locked state if (canDownload) { dxfBtn.classList.remove('opacity-60', 'cursor-not-allowed'); dxfBtn.classList.add('cursor-pointer'); } else { dxfBtn.classList.add('opacity-60', 'cursor-not-allowed'); dxfBtn.classList.remove('cursor-pointer'); } } // SVG Button - always visible, show lock for guests if (svgBtn) { svgBtn.style.display = 'flex'; const canDownload = authManager.canDownload('svg'); if (svgLockIcon) { svgLockIcon.style.display = canDownload ? 'none' : 'block'; } // Update button styling for locked state if (canDownload) { svgBtn.classList.remove('opacity-60', 'cursor-not-allowed'); svgBtn.classList.add('cursor-pointer'); } else { svgBtn.classList.add('opacity-60', 'cursor-not-allowed'); svgBtn.classList.remove('cursor-pointer'); } } // ZIP Button - always visible, show lock for guests const zipBtn = document.getElementById('downloadZipBtn'); if (zipBtn) { zipBtn.style.display = 'flex'; const canDownloadZip = authManager.canDownload('zip'); if (zipLockIcon) { zipLockIcon.style.display = canDownloadZip ? 'none' : 'block'; } // Update button styling for locked state if (canDownloadZip) { zipBtn.classList.remove('opacity-60', 'cursor-not-allowed'); zipBtn.classList.add('cursor-pointer'); } else { zipBtn.classList.add('opacity-60', 'cursor-not-allowed'); zipBtn.classList.remove('cursor-pointer'); } } // Shareable HTML Button - only visible for registered users const shareableBtn = document.getElementById('downloadShareableBtn'); if (shareableBtn) { if (authManager.currentUser) { shareableBtn.classList.remove('hidden'); shareableBtn.style.display = 'flex'; } else { shareableBtn.classList.add('hidden'); shareableBtn.style.display = 'none'; } } } // Handle download button clicks with permission check function handleDownloadClick(type) { if (!authManager.canDownload(type)) { alert('You need to register/login to download ' + type.toUpperCase() + ' files. Please sign up or login to access this feature.'); showAuthModal(); toggleDownloadsMenu(); // Close the dropdown return; } // User has permission, proceed with download using window functions for tracking if (type === 'dxf') { window.downloadDXF(); } else if (type === 'svg') { window.downloadSVG(); } else if (type === 'zip') { window.downloadZIP(); } toggleDownloadsMenu(); // Close the dropdown after download } // Downloads Menu Toggle function toggleDownloadsMenu() { const dropdown = document.getElementById('downloadsMenuDropdown'); dropdown.classList.toggle('hidden'); } // Close downloads menu when clicking outside document.addEventListener('click', (e) => { const menuContainer = document.getElementById('downloadsMenuContainer'); const dropdown = document.getElementById('downloadsMenuDropdown'); if (menuContainer && !menuContainer.contains(e.target)) { dropdown.classList.add('hidden'); } }); function showAuthModal() { document.getElementById('authModal').classList.add('active'); switchAuthTab('login'); } function closeAuthModal() { document.getElementById('authModal').classList.remove('active'); } function switchAuthTab(tab) { document.querySelectorAll('.auth-tab').forEach(t => t.classList.remove('active')); document.getElementById('loginForm').style.display = tab === 'login' ? 'block' : 'none'; document.getElementById('registerForm').style.display = tab === 'register' ? 'block' : 'none'; document.getElementById('forgotForm').style.display = tab === 'forgot' ? 'block' : 'none'; if (tab === 'login') document.querySelectorAll('.auth-tab')[0].classList.add('active'); if (tab === 'register') document.querySelectorAll('.auth-tab')[1].classList.add('active'); // Clear errors document.querySelectorAll('.error-message').forEach(e => { e.classList.remove('show'); e.textContent = ''; }); } async function handleLogin() { const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; const errorEl = document.getElementById('loginError'); const result = await authManager.signInWithEmail(email, password); if (result.success) { closeAuthModal(); } else { errorEl.textContent = result.error; errorEl.classList.add('show'); } } async function handleRegister() { const email = document.getElementById('registerEmail').value; const password = document.getElementById('registerPassword').value; const errorEl = document.getElementById('registerError'); if (password.length < 6) { errorEl.textContent = 'Password must be at least 6 characters'; errorEl.classList.add('show'); return; } const result = await authManager.signUpWithEmail(email, password); if (result.success) { closeAuthModal(); } else { errorEl.textContent = result.error; errorEl.classList.add('show'); } } async function handleGoogleLogin() { const result = await authManager.signInWithGoogle(); if (result.success) { closeAuthModal(); } else { alert('Google login failed: ' + result.error); } } async function handleForgotPassword() { const email = document.getElementById('forgotEmail').value; const errorEl = document.getElementById('forgotError'); const successEl = document.getElementById('forgotSuccess'); const result = await authManager.resetPassword(email); if (result.success) { successEl.textContent = 'Password reset email sent!'; successEl.classList.add('show'); errorEl.classList.remove('show'); } else { errorEl.textContent = result.error; errorEl.classList.add('show'); successEl.classList.remove('show'); } } // Admin Panel Functions function showAdminPanel() { document.getElementById('adminModal').classList.add('active'); if (!authManager.isAdmin) { document.getElementById('adminLoginForm').style.display = 'block'; document.getElementById('adminPanelContent').style.display = 'none'; } else { loadAdminData(); } } function closeAdminPanel() { document.getElementById('adminModal').classList.remove('active'); } async function handleAdminLogin() { const username = document.getElementById('adminUsername').value; const password = document.getElementById('adminPassword').value; const errorEl = document.getElementById('adminLoginError'); const result = await authManager.adminLogin(username, password); if (result.success) { document.getElementById('adminLoginForm').style.display = 'none'; document.getElementById('adminPanelContent').style.display = 'block'; loadAdminData(); } else { errorEl.textContent = result.error; errorEl.classList.add('show'); } } async function loadAdminData() { if (!authManager.isAdmin || !authManager.db) return; try { // Get total users const usersSnapshot = await authManager.db.collection('users').get(); document.getElementById('totalUsers').textContent = usersSnapshot.size; // Get total downloads const downloadsSnapshot = await authManager.db.collection('downloads').get(); document.getElementById('totalDownloads').textContent = downloadsSnapshot.size; // Get downloads today const today = new Date(); today.setHours(0, 0, 0, 0); const todayDownloads = downloadsSnapshot.docs.filter(doc => { const timestamp = doc.data().timestamp; if (timestamp && timestamp.toDate) { return timestamp.toDate() >= today; } return false; }); document.getElementById('downloadsToday').textContent = todayDownloads.length; // Populate users table const tbody = document.getElementById('usersTableBody'); tbody.innerHTML = ''; usersSnapshot.forEach(doc => { const user = doc.data(); const row = tbody.insertRow(); row.insertCell(0).textContent = user.email || 'N/A'; row.insertCell(1).textContent = user.downloadCount || 0; row.insertCell(2).textContent = user.lastLogin ? new Date(user.lastLogin.toDate()).toLocaleDateString() : 'Never'; }); } catch (e) { console.error('Failed to load admin data:', e); } } // Wrap download functions to check permissions and track downloads const originalDownloadImage = downloadImage; window.downloadImage = async function() { if (!authManager.canDownload('png')) { alert('Please login to download PNG files'); showAuthModal(); return; } if (authManager.currentUser) { await authManager.trackDownload('png'); } // Check if there's content to download if (typeof originalDownloadImage === 'function') { originalDownloadImage(); } else { alert('No image to download. Please upload an image and generate the pattern first.'); } }; const originalDownloadSpecs = downloadSpecs; window.downloadSpecs = async function() { if (!authManager.canDownload('txt')) { alert('Please login to download settings files'); showAuthModal(); return; } if (authManager.currentUser) { await authManager.trackDownload('txt'); } // Check if there's content to download if (typeof originalDownloadSpecs === 'function') { originalDownloadSpecs(); } else { alert('No settings to download. Please configure and generate a pattern first.'); } }; const originalDownloadDXF = downloadDXF; window.downloadDXF = async function() { if (!authManager.canDownload('dxf')) { alert('Please register/login to download DXF files'); showAuthModal(); return; } await authManager.trackDownload('dxf'); // Check if there's content to download if (typeof originalDownloadDXF === 'function') { originalDownloadDXF(); } else { alert('No pattern to download. Please generate a pattern first.'); } }; const originalDownloadSVG = downloadSVG; window.downloadSVG = async function() { if (!authManager.canDownload('svg')) { alert('Please register/login to download SVG files'); showAuthModal(); return; } await authManager.trackDownload('svg'); // Check if there's content to download if (typeof originalDownloadSVG === 'function') { originalDownloadSVG(); } else { alert('No pattern to download. Please generate a pattern first.'); } }; const originalDownloadZIP = downloadZIP; window.downloadZIP = async function() { if (!authManager.canDownload('zip')) { alert('Please register/login to download ZIP files'); showAuthModal(); return; } await authManager.trackDownload('zip'); // Check if there's content to download if (typeof originalDownloadZIP === 'function') { await originalDownloadZIP(); } else { alert('No pattern to download. Please generate a pattern first.'); } }; // Initialize auth on page load if (typeof firebase !== 'undefined') { authManager.init(); } else { // Wait for Firebase to load window.addEventListener('load', () => { if (typeof firebase !== 'undefined') { authManager.init(); } }); } // Make authManager globally available window.authManager = authManager; window.updateAuthUI = updateAuthUI; window.showAuthModal = showAuthModal; window.closeAuthModal = closeAuthModal; window.switchAuthTab = switchAuthTab; window.handleLogin = handleLogin; window.handleRegister = handleRegister; window.handleGoogleLogin = handleGoogleLogin; window.handleForgotPassword = handleForgotPassword; window.showAdminPanel = showAdminPanel; window.closeAdminPanel = closeAdminPanel; window.handleAdminLogin = handleAdminLogin; window.toggleUserMenu = toggleUserMenu; window.showChangePasswordModal = showChangePasswordModal; window.closeChangePasswordModal = closeChangePasswordModal; window.handleChangePassword = handleChangePassword; window.toggleDownloadsMenu = toggleDownloadsMenu; window.handleDownloadClick = handleDownloadClick;