/** * Main App Logic for Intaleq Dashboard (Vanilla Version) */ const app = { state: { tenant: null, keys: [], showKeys: {}, // { id: boolean } activePage: 'home' }, init: async () => { try { console.log('🚀 Intaleq Dashboard Initializing Core UI...'); app.updateHeader(); app.bindEvents(); // Check for payment success/fail status earlier app.checkPaymentStatus(); // Trigger initial routing based on URL hash app.handleRouting(); if (window.lucide) lucide.createIcons(); } catch (e) { console.error('Core UI Init failed', e); } }, checkPaymentStatus: () => { const params = new URLSearchParams(window.location.search); const status = params.get('payment_status'); const txId = params.get('id'); if (status === 'success') { console.log('🏁 Payment Success Detected! Updating UI...'); app.showSuccessModal(txId); // Force refresh stats to show PRO plan app.fetchStats(); // Clean URL without refresh const newUrl = window.location.pathname + window.location.hash; window.history.replaceState({}, document.title, newUrl); } }, showSuccessModal: (id) => { const modal = document.createElement('div'); modal.className = 'fixed inset-0 z-[100] flex items-center justify-center p-6 bg-slate-950/90 backdrop-blur-xl animate-in fade-in duration-500'; modal.innerHTML = `

Upgrade Successful!

Your account has been upgraded to PRO Plan. Enjoy 50,000 monthly requests!

Transaction ID

${id || 'N/A'}

PRO ACTIVE
`; document.body.appendChild(modal); if (window.lucide) lucide.createIcons(); document.getElementById('close-success-modal').onclick = () => { modal.classList.add('animate-out', 'fade-out', 'zoom-out-95'); setTimeout(() => modal.remove(), 400); }; }, onAuthenticated: async () => { console.log('🔑 User Authenticated. Initializing Data Flow...'); await app.init(); // Setup UI first await app.fetchData(); app.updateStats(); }, bindEvents: () => { window.addEventListener('hashchange', app.handleRouting); // Form submission for new key const createKeyForm = document.getElementById('create-key-form'); if (createKeyForm) { createKeyForm.addEventListener('submit', async (e) => { e.preventDefault(); await app.createKey(); }); } }, handleRouting: () => { const hash = window.location.hash.replace('#', '') || 'home'; app.state.activePage = hash; // Update UI document.querySelectorAll('.page-section').forEach(s => s.classList.remove('active')); const activeSection = document.getElementById(hash); if (activeSection) activeSection.classList.add('active'); // Update Nav document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active')); const activeLink = document.getElementById(`nav-${hash}`); if (activeLink) activeLink.classList.add('active'); // Specialized page logic if (hash === 'playground') { playground.init(app.state.keys); } else if (hash === 'analytics') { analytics.init(); } else if (hash === 'billing') { billing.init(); } else if (hash === 'refinement') { refinement.init(); } else if (hash === 'docs') { docs.init(); } }, fetchData: async () => { const headers = auth.getAuthHeader(); try { // Fetch Tenant const tenantRes = await fetch('/api/auth/management/me', { headers }); if (tenantRes.ok) { app.state.tenant = await tenantRes.json(); app.updateHeader(); } // Fetch Keys if (app.state.tenant) { const keysRes = await fetch('/api/auth/management/keys', { headers }); if (keysRes.ok) { app.state.keys = await keysRes.json(); app.renderKeysTable(); } } // Update Quota Widget const summaryRes = await fetch('/api/usage/summary', { headers }); if (summaryRes.ok) { const summary = await summaryRes.json(); app.updateQuotaWidget(summary); } } catch (error) { console.warn('Dashboard data fetch partially failed', error); } }, updateQuotaWidget: (summary) => { const percent = parseFloat(summary.percentage) || 0; const used = summary.monthlyUsage || 0; const total = summary.limit || 8000; const left = Math.max(total - used, 0); // Sidebar Widget const bar = document.getElementById('quota-bar'); const pctText = document.getElementById('quota-percent'); const leftText = document.getElementById('quota-text'); if (bar) { bar.style.width = `${percent}%`; // Color shift based on usage bar.classList.remove('from-blue-600', 'to-blue-400', 'from-orange-500', 'to-orange-400', 'from-red-600', 'to-red-400'); if (percent > 90) { bar.classList.add('from-red-600', 'to-red-400'); } else if (percent > 70) { bar.classList.add('from-orange-500', 'to-orange-400'); } else { bar.classList.add('from-blue-600', 'to-blue-400'); } } if (pctText) pctText.textContent = `${percent}%`; if (leftText) leftText.textContent = `${left.toLocaleString()} requests left`; // Billing Section Widget (if present) const bBar = document.getElementById('billing-quota-bar'); const bPct = document.getElementById('billing-quota-percent'); const bUsed = document.getElementById('billing-quota-used'); const bTotal = document.getElementById('billing-quota-total'); if (bBar) bBar.style.width = `${percent}%`; if (bPct) bPct.textContent = `${percent}%`; if (bUsed) bUsed.textContent = `${used.toLocaleString()} used`; if (bTotal) bTotal.textContent = `${total.toLocaleString()} limit`; }, updateHeader: () => { if (!app.state.tenant) return; document.getElementById('tenant-name').textContent = app.state.tenant.name; document.getElementById('tenant-email').textContent = app.state.tenant.email; document.getElementById('welcome-msg').textContent = `Welcome back, ${app.state.tenant.name.split(' ')[0]}`; // Role-based UI visibility if (app.state.tenant.role === 'ADMIN') { const auditLink = document.getElementById('nav-refinement'); if (auditLink) auditLink.classList.remove('hidden'); } // Update user avatar if photoUrl exists if (app.state.tenant.photoUrl) { const avatarContainer = document.querySelector('.w-10.h-10.rounded-full.bg-slate-900'); if (avatarContainer) { avatarContainer.innerHTML = `Profile`; } } }, updateStats: async () => { try { const headers = auth.getAuthHeader(); const res = await fetch('/api/usage/summary', { headers }); if (res.ok) { const data = await res.json(); // Update KPI Cards document.getElementById('active-keys-count').textContent = app.state.keys.length; // Detailed Stats from getUsageSummary if (document.getElementById('stat-total-req')) { document.getElementById('stat-total-req').textContent = data.monthlyUsage.toLocaleString(); } if (document.getElementById('stat-success-percent')) { document.getElementById('stat-success-percent').textContent = `${data.successRate}%`; document.getElementById('stat-success-rate').textContent = data.successRate >= 95 ? 'Excellent' : 'Stable'; } if (document.getElementById('stat-avg-latency')) { document.getElementById('stat-avg-latency').textContent = `${data.avgLatency}ms`; document.getElementById('stat-latency-val').textContent = `-${Math.round(data.avgLatency * 0.1)}ms`; } // Update Progress bar & Quota card const bar = document.getElementById('usage-progress-bar'); const label = document.getElementById('usage-percentage-label'); const limitText = document.getElementById('usage-limit-text'); if (bar) bar.style.width = `${data.percentage}%`; if (label) label.textContent = `${data.percentage}% USED`; if (limitText) { const remaining = Math.max(0, data.limit - data.monthlyUsage); limitText.textContent = `${remaining.toLocaleString()} requests left`; } } } catch (error) { console.error('Failed to update stats', error); } // Inject sample bars for visual flair if chart not ready const container = document.getElementById('traffic-bars'); if (container) { container.innerHTML = ''; // High-density bars for a tech/premium feel const values = [40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 40, 60, 55, 80, 70, 45, 90, 85, 60, 40, 30, 55, 75, 50, 65, 80, 70, 30]; values.forEach((h, index) => { const bar = document.createElement('div'); bar.className = 'flex-1 bg-gradient-to-t from-blue-600/10 to-blue-400/60 rounded-t-[2px] relative group hover:to-blue-300 transition-all duration-500'; bar.style.height = `${h}%`; bar.style.transitionDelay = `${index * 20}ms`; bar.innerHTML = `
${h}k
`; container.appendChild(bar); }); } }, renderKeysTable: () => { const tbody = document.getElementById('keys-table-body'); const createBtn = document.getElementById('create-key-btn'); if (!tbody) return; if (app.state.keys.length === 0) { tbody.innerHTML = `No API keys found. Create one to get started.`; return; } // Enable create button (removed previous limit restrictiveness) createBtn.disabled = false; createBtn.title = "Create a new API key"; createBtn.innerHTML = ' Create Key'; tbody.innerHTML = app.state.keys.map(key => { const isVisible = app.state.showKeys[key.id]; const maskedKey = isVisible ? key.key : "in_••••••••••••••••••••••••"; const eyeIcon = isVisible ? 'eye-off' : 'eye'; return ` ${key.name}
${maskedKey}
${key.isActive ? 'Active' : 'Inactive'}
${(key.allowedOrigins && key.allowedOrigins.length > 0) ? key.allowedOrigins.map(org => ` ${org}`).join('') : ` Universal` }
`; }).join(''); lucide.createIcons(); }, toggleKeyVisibility: (id) => { app.state.showKeys[id] = !app.state.showKeys[id]; app.renderKeysTable(); }, copyToClipboard: (text) => { navigator.clipboard.writeText(text); // Simple toast or just feedback console.log('Copied to clipboard'); }, toggleModal: (id, show) => { const modal = document.getElementById(id); if (modal) { if (show) modal.classList.remove('hidden'); else modal.classList.add('hidden'); } }, createKey: async () => { const name = document.getElementById('new-key-name').value; const btn = document.getElementById('btn-submit-key'); if (!app.state.tenant) return; try { btn.disabled = true; btn.textContent = 'Creating...'; const res = await fetch('/api/auth/management/keys', { method: 'POST', headers: { 'Content-Type': 'application/json', ...auth.getAuthHeader() }, body: JSON.stringify({ name, rateLimit: 100 }) }); if (res.ok) { app.toggleModal('create-key-modal', false); await app.fetchData(); document.getElementById('new-key-name').value = ''; } else { alert('Failed to create key'); } } catch (error) { console.error(error); } finally { btn.disabled = false; btn.textContent = 'Create Key'; } } }; // Script sequence is now controlled by auth.js onAuthenticated lifecycle // document.addEventListener('DOMContentLoaded', app.init);