Spaces:
Running
Running
| // Sentinel Hawk - Accounts Management Script | |
| // Mock data for demonstration | |
| const mockAccounts = [ | |
| { | |
| id: 1, | |
| username: '@elonmusk', | |
| userId: '44196397', | |
| platform: 'twitter', | |
| createdAt: '2024-01-15T10:30:00Z', | |
| status: 'ACTIVE', | |
| avatar: 'https://static.photos/people/200x200/1' | |
| }, | |
| { | |
| id: 2, | |
| username: '@sama', | |
| userId: '50393960', | |
| platform: 'twitter', | |
| createdAt: '2024-02-20T14:15:00Z', | |
| status: 'ACTIVE', | |
| avatar: 'https://static.photos/people/200x200/2' | |
| }, | |
| { | |
| id: 3, | |
| username: '+1-555-0123', | |
| userId: 'whatsapp_12345', | |
| platform: 'whatsapp', | |
| createdAt: '2024-03-01T09:00:00Z', | |
| status: 'INACTIVE', | |
| avatar: 'https://static.photos/people/200x200/3' | |
| }, | |
| { | |
| id: 4, | |
| username: '@naval', | |
| userId: '745273', | |
| platform: 'twitter', | |
| createdAt: '2024-03-10T16:45:00Z', | |
| status: 'ACTIVE', | |
| avatar: 'https://static.photos/people/200x200/4' | |
| }, | |
| { | |
| id: 5, | |
| username: '+1-555-0456', | |
| userId: 'whatsapp_67890', | |
| platform: 'whatsapp', | |
| createdAt: '2024-03-15T11:20:00Z', | |
| status: 'INACTIVE', | |
| avatar: 'https://static.photos/people/200x200/5' | |
| }, | |
| { | |
| id: 6, | |
| username: '@paulg', | |
| userId: '183749319', | |
| platform: 'twitter', | |
| createdAt: '2024-03-20T08:30:00Z', | |
| status: 'ACTIVE', | |
| avatar: 'https://static.photos/people/200x200/6' | |
| } | |
| ]; | |
| // State management | |
| let accounts = []; | |
| let isLoading = false; | |
| let error = null; | |
| // DOM Elements | |
| const elements = { | |
| loadingState: document.getElementById('loading-state'), | |
| errorState: document.getElementById('error-state'), | |
| emptyState: document.getElementById('empty-state'), | |
| successState: document.getElementById('success-state'), | |
| tableBody: document.getElementById('accounts-table-body'), | |
| errorMessage: document.getElementById('error-message'), | |
| statTotal: document.getElementById('stat-total'), | |
| statActive: document.getElementById('stat-active'), | |
| statInactive: document.getElementById('stat-inactive'), | |
| lastUpdated: document.getElementById('last-updated'), | |
| showingCount: document.getElementById('showing-count'), | |
| toast: document.getElementById('toast'), | |
| toastTitle: document.getElementById('toast-title'), | |
| toastMessage: document.getElementById('toast-message'), | |
| toastIcon: document.getElementById('toast-icon') | |
| }; | |
| // Simulated API calls | |
| const mockApi = { | |
| async fetchAccounts() { | |
| // Simulate network delay | |
| await new Promise(resolve => setTimeout(resolve, 1500)); | |
| // Random error simulation (10% chance) | |
| if (Math.random() < 0.1) { | |
| throw new Error('Network error: Failed to fetch accounts from server'); | |
| } | |
| return [...mockAccounts]; | |
| }, | |
| async toggleAccountStatus(accountId, newStatus) { | |
| await new Promise(resolve => setTimeout(resolve, 800)); | |
| // Random error simulation (5% chance) | |
| if (Math.random() < 0.05) { | |
| throw new Error('Failed to update account status'); | |
| } | |
| return { success: true, newStatus }; | |
| } | |
| }; | |
| // Core functions | |
| async function fetchData() { | |
| isLoading = true; | |
| error = null; | |
| updateUI(); | |
| try { | |
| accounts = await mockApi.fetchAccounts(); | |
| updateStats(); | |
| updateLastUpdated(); | |
| } catch (err) { | |
| error = err.message || 'An unexpected error occurred'; | |
| console.error('Fetch error:', err); | |
| } finally { | |
| isLoading = false; | |
| updateUI(); | |
| } | |
| } | |
| function refreshData() { | |
| fetchData(); | |
| } | |
| function updateStats() { | |
| const total = accounts.length; | |
| const active = accounts.filter(a => a.status === 'ACTIVE').length; | |
| const inactive = accounts.filter(a => a.status === 'INACTIVE').length; | |
| animateCounter(elements.statTotal, parseInt(elements.statTotal.textContent), total); | |
| animateCounter(elements.statActive, parseInt(elements.statActive.textContent), active); | |
| animateCounter(elements.statInactive, parseInt(elements.statInactive.textContent), inactive); | |
| } | |
| function animateCounter(element, start, end) { | |
| const duration = 600; | |
| const startTime = performance.now(); | |
| function update(currentTime) { | |
| const elapsed = currentTime - startTime; | |
| const progress = Math.min(elapsed / duration, 1); | |
| // Ease out cubic | |
| const easeProgress = 1 - Math.pow(1 - progress, 3); | |
| const current = Math.round(start + (end - start) * easeProgress); | |
| element.textContent = current; | |
| if (progress < 1) { | |
| requestAnimationFrame(update); | |
| } | |
| } | |
| requestAnimationFrame(update); | |
| } | |
| function updateLastUpdated() { | |
| const now = new Date(); | |
| elements.lastUpdated.textContent = `Last updated: ${now.toLocaleTimeString()}`; | |
| } | |
| function updateUI() { | |
| // Hide all states | |
| elements.loadingState.classList.add('hidden'); | |
| elements.errorState.classList.add('hidden'); | |
| elements.emptyState.classList.add('hidden'); | |
| elements.successState.classList.add('hidden'); | |
| if (isLoading) { | |
| elements.loadingState.classList.remove('hidden'); | |
| } else if (error) { | |
| elements.errorState.classList.remove('hidden'); | |
| elements.errorMessage.textContent = error; | |
| } else if (accounts.length === 0) { | |
| elements.emptyState.classList.remove('hidden'); | |
| } else { | |
| elements.successState.classList.remove('hidden'); | |
| renderTable(); | |
| } | |
| } | |
| function renderTable() { | |
| elements.tableBody.innerHTML = ''; | |
| elements.showingCount.textContent = accounts.length; | |
| accounts.forEach((account, index) => { | |
| const row = document.createElement('tr'); | |
| row.className = 'hover:bg-gray-50/80 transition-colors duration-200 table-row-hover group'; | |
| row.style.animationDelay = `${index * 0.05}s`; | |
| const platformIcon = account.platform === 'twitter' | |
| ? '<i data-feather="twitter" class="w-4 h-4 text-[#1da1f2]"></i>' | |
| : '<i data-feather="message-circle" class="w-4 h-4 text-[#25d366]"></i>'; | |
| const platformName = account.platform === 'twitter' ? 'Twitter' : 'WhatsApp'; | |
| const statusBadge = account.status === 'ACTIVE' | |
| ? `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 status-active"><span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-1.5 animate-pulse"></span>Active</span>` | |
| : `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800"><span class="w-1.5 h-1.5 bg-yellow-500 rounded-full mr-1.5"></span>Inactive</span>`; | |
| const actionButton = account.status === 'ACTIVE' | |
| ? `<button onclick="toggleStatus(${account.id})" class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-all duration-200 btn-press"><i data-feather="power" class="w-3 h-3"></i>Deactivate</button>` | |
| : `<button onclick="toggleStatus(${account.id})" class="inline-flex items-center gap-1.5 px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200 btn-press"><i data-feather="play" class="w-3 h-3"></i>Activate</button>`; | |
| row.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center"> | |
| <div class="flex-shrink-0 h-10 w-10"> | |
| <img class="h-10 w-10 rounded-full object-cover ring-2 ring-gray-100" src="${account.avatar}" alt=""> | |
| </div> | |
| <div class="ml-4"> | |
| <div class="text-sm font-medium text-amber-600 hover:text-amber-700 cursor-pointer">${account.username}</div> | |
| <div class="text-xs text-gray-500">ID: ${account.userId}</div> | |
| </div> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex items-center gap-2"> | |
| ${platformIcon} | |
| <span class="text-sm text-gray-700">${platformName}</span> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm text-gray-700">${formatDate(account.createdAt)}</div> | |
| <div class="text-xs text-gray-500">${timeAgo(account.createdAt)}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| ${statusBadge} | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-right"> | |
| ${actionButton} | |
| </td> | |
| `; | |
| elements.tableBody.appendChild(row); | |
| }); | |
| // Re-initialize feather icons for new content | |
| feather.replace(); | |
| } | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('en-US', { | |
| year: 'numeric', | |
| month: 'short', | |
| day: 'numeric' | |
| }); | |
| } | |
| function timeAgo(dateString) { | |
| const date = new Date(dateString); | |
| const now = new Date(); | |
| const seconds = Math.floor((now - date) / 1000); | |
| let interval = Math.floor(seconds / 31536000); | |
| if (interval > 1) return `${interval} years ago`; | |
| if (interval === 1) return '1 year ago'; | |
| interval = Math.floor(seconds / 2592000); | |
| if (interval > 1) return `${interval} months ago`; | |
| if (interval === 1) return '1 month ago'; | |
| interval = Math.floor(seconds / 86400); | |
| if (interval > 1) return `${interval} days ago`; | |
| if (interval === 1) return '1 day ago'; | |
| interval = Math.floor(seconds / 3600); | |
| if (interval > 1) return `${interval} hours ago`; | |
| if (interval === 1) return '1 hour ago'; | |
| interval = Math.floor(seconds / 60); | |
| if (interval > 1) return `${interval} minutes ago`; | |
| return 'Just now'; | |
| } | |
| async function toggleStatus(accountId) { | |
| const account = accounts.find(a => a.id === accountId); | |
| if (!account) return; | |
| const newStatus = account.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'; | |
| const actionText = newStatus === 'ACTIVE' ? 'activated' : 'deactivated'; | |
| try { | |
| // Optimistic update | |
| account.status = newStatus; | |
| renderTable(); | |
| // API call | |
| await mockApi.toggleAccountStatus(accountId, newStatus); | |
| // Show success toast | |
| showToast('success', 'Status Updated', `Account ${account.username} has been ${actionText}`); | |
| updateStats(); | |
| } catch (err) { | |
| // Revert on error | |
| account.status = account.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'; | |
| renderTable(); | |
| showToast('error', 'Update Failed', err.message || 'Failed to update account status'); | |
| } | |
| } | |
| function showToast(type, title, message) { | |
| const toast = elements.toast; | |
| const toastTitle = elements.toastTitle; | |
| const toastMessage = elements.toastMessage; | |
| const toastIcon = elements.toastIcon; | |
| // Set content | |
| toastTitle.textContent = title; | |
| toastMessage.textContent = message; | |
| // Set icon and colors | |
| if (type === 'success') { | |
| toastIcon.className = 'w-10 h-10 rounded-full bg-green-100 flex items-center justify-center'; | |
| toastIcon.innerHTML = '<i data-feather="check" class="w-5 h-5 text-green-600"></i>'; | |
| } else { | |
| toastIcon.className = 'w-10 h-10 rounded-full bg-red-100 flex items-center justify-center'; | |
| toastIcon.innerHTML = '<i data-feather="alert-circle" class="w-5 h-5 text-red-600"></i>'; | |
| } | |
| // Show toast | |
| toast.classList.remove('translate-y-20', 'opacity-0'); | |
| feather.replace(); | |
| // Auto hide after 5 seconds | |
| setTimeout(() => { | |
| hideToast(); | |
| }, 5000); | |
| } | |
| function hideToast() { | |
| elements.toast.classList.add('translate-y-20', 'opacity-0'); | |
| } | |
| // Initialize on page load | |
| document.addEventListener('DOMContentLoaded', () => { | |
| fetchData(); | |
| }); |