// Admin Dashboard - Smart & Powerful const API_BASE = '/api'; class AdminDashboard { constructor() { this.token = localStorage.getItem('admin_token'); this.currentSection = 'stats'; this.data = { apps: [], articles: [], categories: [], sponsors: [] }; this.editingItem = null; this.init(); } async init() { // Check auth if (!this.token) { this.showLogin(); return; } // Try to load stats to verify token try { await this.loadStats(); this.showDashboard(); this.setupEventListeners(); await this.loadAllData(); } catch (error) { if (error.status === 401) { this.showLogin(); } } } showLogin() { document.getElementById('login-screen').classList.remove('hidden'); document.getElementById('admin-dashboard').classList.add('hidden'); // Set up login button click handler const loginBtn = document.getElementById('login-btn'); if (loginBtn) { loginBtn.onclick = async () => { const password = document.getElementById('password').value; await this.login(password); }; } } async login(password) { try { const response = await fetch(`${API_BASE}/admin/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) }); if (!response.ok) throw new Error('Invalid password'); const data = await response.json(); this.token = data.token; localStorage.setItem('admin_token', this.token); document.getElementById('login-screen').classList.add('hidden'); this.showDashboard(); this.setupEventListeners(); await this.loadAllData(); } catch (error) { document.getElementById('login-error').textContent = 'Invalid password'; document.getElementById('password').value = ''; } } showDashboard() { document.getElementById('login-screen').classList.add('hidden'); document.getElementById('admin-dashboard').classList.remove('hidden'); } setupEventListeners() { // Navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.onclick = () => this.switchSection(btn.dataset.section); }); // Logout document.getElementById('logout-btn').onclick = () => this.logout(); // Export/Backup document.getElementById('export-btn').onclick = () => this.exportData(); document.getElementById('backup-btn').onclick = () => this.backupDatabase(); // Search ['apps', 'articles'].forEach(type => { const searchInput = document.getElementById(`${type}-search`); if (searchInput) { searchInput.oninput = (e) => this.filterTable(type, e.target.value); } }); // Category filter const categoryFilter = document.getElementById('apps-filter'); if (categoryFilter) { categoryFilter.onchange = (e) => this.filterByCategory(e.target.value); } // Save button in modal document.getElementById('save-btn').onclick = () => this.saveItem(); } async loadAllData() { try { await this.loadStats(); } catch (e) { console.error('Failed to load stats:', e); } try { await this.loadApps(); } catch (e) { console.error('Failed to load apps:', e); } try { await this.loadArticles(); } catch (e) { console.error('Failed to load articles:', e); } try { await this.loadCategories(); } catch (e) { console.error('Failed to load categories:', e); } try { await this.loadSponsors(); } catch (e) { console.error('Failed to load sponsors:', e); } this.populateCategoryFilter(); } async apiCall(endpoint, options = {}) { const response = await fetch(`${API_BASE}${endpoint}`, { ...options, headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', ...options.headers } }); if (response.status === 401) { this.logout(); throw { status: 401 }; } if (!response.ok) throw new Error(`API Error: ${response.status}`); return response.json(); } async loadStats() { const stats = await this.apiCall('/admin/stats'); document.getElementById('stat-apps').textContent = stats.apps.total; document.getElementById('stat-featured').textContent = stats.apps.featured; document.getElementById('stat-sponsored').textContent = stats.apps.sponsored; document.getElementById('stat-articles').textContent = stats.articles; document.getElementById('stat-sponsors').textContent = stats.sponsors.active; document.getElementById('stat-views').textContent = this.formatNumber(stats.total_views); } async loadApps() { this.data.apps = await this.apiCall('/apps?limit=100'); this.renderAppsTable(this.data.apps); } async loadArticles() { this.data.articles = await this.apiCall('/articles?limit=100'); this.renderArticlesTable(this.data.articles); } async loadCategories() { this.data.categories = await this.apiCall('/categories'); this.renderCategoriesTable(this.data.categories); } async loadSponsors() { this.data.sponsors = await this.apiCall('/sponsors'); this.renderSponsorsTable(this.data.sponsors); } renderAppsTable(apps) { const table = document.getElementById('apps-table'); table.innerHTML = ` ${apps.map(app => ` `).join('')}
ID Name Category Type Rating Downloads Status Actions
${app.id} ${app.name} ${app.category} ${app.type} ◆ ${app.rating}/5 ${this.formatNumber(app.downloads)} ${app.featured ? 'Featured' : ''} ${app.sponsored ? '' : ''}
`; } renderArticlesTable(articles) { const table = document.getElementById('articles-table'); table.innerHTML = ` ${articles.map(article => ` `).join('')}
ID Title Category Author Published Views Actions
${article.id} ${article.title} ${article.category} ${article.author} ${new Date(article.published_date).toLocaleDateString()} ${this.formatNumber(article.views)}
`; } renderCategoriesTable(categories) { const table = document.getElementById('categories-table'); table.innerHTML = ` ${categories.map(cat => ` `).join('')}
Order Icon Name Description Actions
${cat.order_index} ${cat.icon} ${cat.name} ${cat.description}
`; } renderSponsorsTable(sponsors) { const table = document.getElementById('sponsors-table'); table.innerHTML = ` ${sponsors.map(sponsor => ` `).join('')}
ID Company Tier Start End Status Actions
${sponsor.id} ${sponsor.company_name} ${sponsor.tier} ${new Date(sponsor.start_date).toLocaleDateString()} ${new Date(sponsor.end_date).toLocaleDateString()} ${sponsor.active ? 'Active' : 'Inactive'}
`; } showAddForm(type) { this.editingItem = null; this.showModal(type, null); } async editItem(type, id) { const item = this.data[type].find(i => i.id === id); if (item) { this.editingItem = item; this.showModal(type, item); } } async duplicateItem(type, id) { const item = this.data[type].find(i => i.id === id); if (item) { const newItem = { ...item }; delete newItem.id; newItem.name = `${newItem.name || newItem.title} (Copy)`; if (newItem.slug) newItem.slug = `${newItem.slug}-copy-${Date.now()}`; this.editingItem = null; this.showModal(type, newItem); } } showModal(type, item) { const modal = document.getElementById('form-modal'); const title = document.getElementById('modal-title'); const body = document.getElementById('modal-body'); title.textContent = item ? `Edit ${type.slice(0, -1)}` : `Add New ${type.slice(0, -1)}`; if (type === 'apps') { body.innerHTML = this.getAppForm(item); } else if (type === 'articles') { body.innerHTML = this.getArticleForm(item); } else if (type === 'categories') { body.innerHTML = this.getCategoryForm(item); } else if (type === 'sponsors') { body.innerHTML = this.getSponsorForm(item); } modal.classList.remove('hidden'); modal.dataset.type = type; } getAppForm(app) { return `
`; } getArticleForm(article) { return `
`; } getCategoryForm(category) { return `
`; } getSponsorForm(sponsor) { return `
`; } async saveItem() { const modal = document.getElementById('form-modal'); const type = modal.dataset.type; const data = this.collectFormData(type); try { if (this.editingItem) { await this.apiCall(`/admin/${type}/${this.editingItem.id}`, { method: 'PUT', body: JSON.stringify(data) }); } else { await this.apiCall(`/admin/${type}`, { method: 'POST', body: JSON.stringify(data) }); } this.closeModal(); await this[`load${type.charAt(0).toUpperCase() + type.slice(1)}`](); await this.loadStats(); } catch (error) { alert('Error saving item: ' + error.message); } } collectFormData(type) { const data = {}; if (type === 'apps') { data.name = document.getElementById('form-name').value; data.slug = document.getElementById('form-slug').value || this.generateSlug(data.name); data.description = document.getElementById('form-description').value; data.category = document.getElementById('form-category').value; data.type = document.getElementById('form-type').value; data.rating = parseFloat(document.getElementById('form-rating').value); data.downloads = parseInt(document.getElementById('form-downloads').value); data.image = document.getElementById('form-image').value; data.website_url = document.getElementById('form-website').value; data.github_url = document.getElementById('form-github').value; data.pricing = document.getElementById('form-pricing').value; data.contact_email = document.getElementById('form-email').value; data.featured = document.getElementById('form-featured').checked ? 1 : 0; data.sponsored = document.getElementById('form-sponsored').checked ? 1 : 0; data.integration_guide = document.getElementById('form-integration').value; } else if (type === 'articles') { data.title = document.getElementById('form-title').value; data.slug = this.generateSlug(data.title); data.author = document.getElementById('form-author').value; data.category = document.getElementById('form-category').value; data.featured_image = document.getElementById('form-image').value; data.content = document.getElementById('form-content').value; } else if (type === 'categories') { data.name = document.getElementById('form-name').value; data.slug = this.generateSlug(data.name); data.icon = document.getElementById('form-icon').value; data.description = document.getElementById('form-description').value; data.order_index = parseInt(document.getElementById('form-order').value); } else if (type === 'sponsors') { data.company_name = document.getElementById('form-name').value; data.tier = document.getElementById('form-tier').value; data.landing_url = document.getElementById('form-landing').value; data.banner_url = document.getElementById('form-banner').value; data.start_date = document.getElementById('form-start').value; data.end_date = document.getElementById('form-end').value; data.active = document.getElementById('form-active').checked ? 1 : 0; } return data; } async deleteItem(type, id) { if (!confirm(`Are you sure you want to delete this ${type.slice(0, -1)}?`)) return; try { await this.apiCall(`/admin/${type}/${id}`, { method: 'DELETE' }); await this[`load${type.charAt(0).toUpperCase() + type.slice(1)}`](); await this.loadStats(); } catch (error) { alert('Error deleting item: ' + error.message); } } async deleteCategory(id) { const hasApps = this.data.apps.some(app => app.category === this.data.categories.find(c => c.id === id)?.name ); if (hasApps) { alert('Cannot delete category with existing apps'); return; } await this.deleteItem('categories', id); } closeModal() { document.getElementById('form-modal').classList.add('hidden'); this.editingItem = null; } switchSection(section) { // Update navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.section === section); }); // Show section document.querySelectorAll('.content-section').forEach(sec => { sec.classList.remove('active'); }); document.getElementById(`${section}-section`).classList.add('active'); this.currentSection = section; } filterTable(type, query) { const items = this.data[type].filter(item => { const searchText = Object.values(item).join(' ').toLowerCase(); return searchText.includes(query.toLowerCase()); }); if (type === 'apps') { this.renderAppsTable(items); } else if (type === 'articles') { this.renderArticlesTable(items); } } filterByCategory(category) { const apps = category ? this.data.apps.filter(app => app.category === category) : this.data.apps; this.renderAppsTable(apps); } populateCategoryFilter() { const filter = document.getElementById('apps-filter'); if (!filter) return; filter.innerHTML = ''; this.data.categories.forEach(cat => { filter.innerHTML += ``; }); } async exportData() { const data = { apps: this.data.apps, articles: this.data.articles, categories: this.data.categories, sponsors: this.data.sponsors, exported: new Date().toISOString() }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `marketplace-export-${Date.now()}.json`; a.click(); } async backupDatabase() { // In production, this would download the SQLite file alert('Database backup would be implemented on the server side'); } generateSlug(text) { return text.toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .trim(); } formatNumber(num) { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num.toString(); } logout() { localStorage.removeItem('admin_token'); this.token = null; this.showLogin(); } } // Initialize const admin = new AdminDashboard();