/** * Main Application Controller * Handles authentication, data loading, and view rendering */ class MailingListApp { constructor() { this.isAuthenticated = false; this.lists = []; this.members = []; this.subscriptions = new Map(); // list_id -> members[] this.initializeApp(); } /** * Initialize the application */ async initializeApp() { this.setupEventListeners(); // Check for saved token const savedToken = localStorage.getItem('apiToken'); if (savedToken) { await this.login(savedToken, false); } } /** * Setup event listeners */ setupEventListeners() { // Login/logout document.getElementById('loginBtn').addEventListener('click', () => { this.handleLogin(); }); document.getElementById('logoutBtn').addEventListener('click', () => { this.logout(); }); // Enter key in token input document.getElementById('apiToken').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.handleLogin(); } }); } /** * Handle login button click */ async handleLogin() { const tokenInput = document.getElementById('apiToken'); const token = tokenInput.value.trim(); if (!token) { uiManager.showNotification('Please enter an API token', 'error'); return; } await this.login(token, true); } /** * Authenticate with API */ async login(token, saveToken = true) { try { uiManager.setLoading(true); // Set token and test authentication apiClient.setToken(token); await apiClient.testAuth(); // Authentication successful this.isAuthenticated = true; if (saveToken) { localStorage.setItem('apiToken', token); } this.showAuthenticatedUI(); await this.loadData(); uiManager.showNotification('Successfully connected to API', 'success'); } catch (error) { this.isAuthenticated = false; apiClient.clearToken(); if (saveToken) { localStorage.removeItem('apiToken'); } uiManager.handleError(error, 'Authentication failed'); } finally { uiManager.setLoading(false); } } /** * Logout */ logout() { this.isAuthenticated = false; apiClient.clearToken(); localStorage.removeItem('apiToken'); this.showUnauthenticatedUI(); uiManager.showNotification('Logged out successfully', 'info'); } /** * Show authenticated UI */ showAuthenticatedUI() { document.getElementById('authControls').style.display = 'none'; document.getElementById('userInfo').style.display = 'flex'; document.getElementById('mainContent').style.display = 'block'; // Clear token input document.getElementById('apiToken').value = ''; } /** * Show unauthenticated UI */ showUnauthenticatedUI() { document.getElementById('authControls').style.display = 'flex'; document.getElementById('userInfo').style.display = 'none'; document.getElementById('mainContent').style.display = 'none'; } /** * Load all data from API */ async loadData() { if (!this.isAuthenticated) return; try { uiManager.setLoading(true); // Load lists and members in parallel const [lists, members] = await Promise.all([ apiClient.getLists(), apiClient.getMembers() ]); this.lists = lists; this.members = members; // Load subscriptions for each list await this.loadSubscriptions(); // Render all views this.renderLists(); this.renderMembers(); } catch (error) { uiManager.handleError(error, 'Failed to load data'); } finally { uiManager.setLoading(false); } } /** * Load subscription data for all lists */ async loadSubscriptions() { this.subscriptions.clear(); const subscriptionPromises = this.lists.map(async (list) => { try { const members = await apiClient.getListMembers(list.list_id); this.subscriptions.set(list.list_id, members); } catch (error) { console.warn(`Failed to load members for list ${list.list_id}:`, error); this.subscriptions.set(list.list_id, []); } }); await Promise.all(subscriptionPromises); } /** * Render mailing lists table */ renderLists() { const tbody = document.getElementById('listsTableBody'); tbody.innerHTML = ''; if (this.lists.length === 0) { tbody.innerHTML = ` No mailing lists found. Create your first list `; document.getElementById('createFirstList').addEventListener('click', (e) => { e.preventDefault(); uiManager.showListModal(); }); return; } this.lists.forEach(list => { const row = document.createElement('tr'); const memberCount = this.subscriptions.get(list.list_id)?.length || 0; row.innerHTML = `
${uiManager.escapeHtml(list.list_name)}
${uiManager.escapeHtml(list.list_email)}
${list.description ? uiManager.escapeHtml(list.description) : 'No description'}
${memberCount} ${memberCount === 1 ? 'member' : 'members'}
`; // Add status badge const statusCell = row.cells[4]; statusCell.appendChild(uiManager.createStatusBadge(list.active)); // Add action buttons const actionsCell = row.cells[5].querySelector('.action-buttons'); const editBtn = uiManager.createActionButton('Edit', 'edit', 'btn-secondary', () => { uiManager.showListModal(list); }); const deleteBtn = uiManager.createActionButton('Delete', 'trash', 'btn-danger', () => { uiManager.showConfirmation( `Are you sure you want to delete the mailing list "${list.list_name}"? This action cannot be undone.`, async () => { await this.deleteList(list.list_id); } ); }); actionsCell.appendChild(editBtn); actionsCell.appendChild(deleteBtn); tbody.appendChild(row); }); } /** * Render members table */ renderMembers() { const tbody = document.getElementById('membersTableBody'); tbody.innerHTML = ''; if (this.members.length === 0) { tbody.innerHTML = ` No members found. Add your first member `; document.getElementById('createFirstMember').addEventListener('click', (e) => { e.preventDefault(); uiManager.showMemberModal(); }); return; } this.members.forEach(member => { const row = document.createElement('tr'); // Find lists this member belongs to const memberLists = []; this.subscriptions.forEach((members, listId) => { if (members.some(m => m.member_id === member.member_id)) { const list = this.lists.find(l => l.list_id === listId); if (list) { memberLists.push(list.list_name); } } }); row.innerHTML = `
${uiManager.escapeHtml(member.name)}
${uiManager.escapeHtml(member.email)}
${memberLists.length > 0 ? memberLists.map(name => `${uiManager.escapeHtml(name)}`).join(', ') : 'No subscriptions' }
`; // Add status badge const statusCell = row.cells[3]; statusCell.appendChild(uiManager.createStatusBadge(member.active)); // Add action buttons const actionsCell = row.cells[4].querySelector('.action-buttons'); // Create Lists button with proper subscription modal functionality const subscriptionsBtn = document.createElement('button'); subscriptionsBtn.className = 'btn btn-sm btn-primary'; subscriptionsBtn.innerHTML = ' Lists'; subscriptionsBtn.title = 'Manage Subscriptions'; subscriptionsBtn.addEventListener('click', () => { uiManager.showMemberSubscriptionsModal(member); }); const editBtn = uiManager.createActionButton('Edit', 'edit', 'btn-secondary', () => { uiManager.showMemberModal(member); }); const deleteBtn = uiManager.createActionButton('Delete', 'trash', 'btn-danger', () => { uiManager.showConfirmation( `Are you sure you want to delete the member "${member.name}"? This will remove them from all mailing lists.`, async () => { await this.deleteMember(member.member_id); } ); }); // Append all buttons actionsCell.appendChild(subscriptionsBtn); actionsCell.appendChild(editBtn); actionsCell.appendChild(deleteBtn); tbody.appendChild(row); }); } /** * Delete a mailing list */ async deleteList(listId) { try { uiManager.setLoading(true); await apiClient.deleteList(listId); uiManager.showNotification('Mailing list deleted successfully', 'success'); await this.loadData(); } catch (error) { uiManager.handleError(error, 'Failed to delete mailing list'); } finally { uiManager.setLoading(false); } } /** * Delete a member */ async deleteMember(memberId) { try { uiManager.setLoading(true); await apiClient.deleteMember(memberId); uiManager.showNotification('Member deleted successfully', 'success'); await this.loadData(); } catch (error) { uiManager.handleError(error, 'Failed to delete member'); } finally { uiManager.setLoading(false); } } /** * Unsubscribe member from list */ async unsubscribeMember(listEmail, memberEmail) { try { uiManager.setLoading(true); await apiClient.deleteSubscription(listEmail, memberEmail); uiManager.showNotification('Member unsubscribed successfully', 'success'); await this.loadData(); } catch (error) { uiManager.handleError(error, 'Failed to unsubscribe member'); } finally { uiManager.setLoading(false); } } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.app = new MailingListApp(); });