Download admin.html from Attendr/Attender: direct link, hf CLI and curl.
- Browser
- Download file 7.96 kB
-
https://huggingface.co/spaces/Attendr/Attender/resolve/main/admin.html
- Command line
-
hf download hf://spaces/Attendr/Attender/admin.html
-
curl -L -o admin.html https://huggingface.co/spaces/Attendr/Attender/resolve/main/admin.html
7.96 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Admin Dashboard - Attendr</title> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@700;800&display=swap" | |
| rel="stylesheet"> | |
| <style> | |
| .admin-grid { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| gap: 2rem; | |
| } | |
| .user-card { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 1.5rem; | |
| margin-bottom: 1rem; | |
| transition: transform 0.2s; | |
| } | |
| .user-card:hover { | |
| transform: translateY(-2px); | |
| } | |
| .user-info h3 { | |
| margin: 0; | |
| font-size: 1.25rem; | |
| } | |
| .user-meta { | |
| font-size: 0.875rem; | |
| color: var(--color-text-muted); | |
| margin-top: 0.25rem; | |
| } | |
| .action-btns { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .tab-container { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| border-bottom: 1px solid var(--color-border); | |
| padding-bottom: 0.5rem; | |
| } | |
| .tab { | |
| padding: 0.5rem 1rem; | |
| cursor: pointer; | |
| border-radius: 8px; | |
| font-weight: 500; | |
| } | |
| .tab.active { | |
| background: var(--color-primary); | |
| color: white; | |
| } | |
| .badge { | |
| padding: 0.25rem 0.6rem; | |
| border-radius: 999px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .badge-student { background: #e0f2fe; color: #0369a1; } | |
| .badge-lecturer { background: #fef3c7; color: #92400e; } | |
| .badge-pending { background: #f3f4f6; color: #374151; } | |
| .badge-approved { background: #dcfce7; color: #166534; } | |
| .badge-rejected { background: #fee2e2; color: #991b1b; } | |
| </style> | |
| </head> | |
| <body> | |
| {% include '_navbar.html' %} | |
| <main class="container section"> | |
| <div class="header-section mb-3"> | |
| <h1>Admin Dashboard</h1> | |
| <p class="text-muted">Manage user registration applications and verify information.</p> | |
| </div> | |
| <div class="tab-container"> | |
| <div class="tab active" onclick="loadUsers('pending', this)">Pending</div> | |
| <div class="tab" onclick="loadUsers('approved', this)">Approved</div> | |
| <div class="tab" onclick="loadUsers('rejected', this)">Rejected</div> | |
| <div class="tab" onclick="loadUsers('all', this)">All Users</div> | |
| </div> | |
| <div id="usersList" class="admin-grid"> | |
| <!-- Users will be loaded here --> | |
| <div class="text-center py-5"> | |
| <div class="spinner"></div> | |
| <p class="mt-2">Loading applications...</p> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Loading Overlay --> | |
| <div id="loadingOverlay" class="loading-overlay hidden"> | |
| <div class="text-center"> | |
| <div class="spinner"></div> | |
| <p class="mt-2" id="loadingText">Processing...</p> | |
| </div> | |
| </div> | |
| <script> | |
| let currentStatus = 'pending'; | |
| document.addEventListener('DOMContentLoaded', () => { | |
| loadUsers('pending'); | |
| }); | |
| async function loadUsers(status, el) { | |
| if (el) { | |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
| el.classList.add('active'); | |
| } | |
| currentStatus = status; | |
| const listDiv = document.getElementById('usersList'); | |
| listDiv.innerHTML = '<div class="text-center py-5"><div class="spinner"></div><p class="mt-2">Loading...</p></div>'; | |
| try { | |
| const response = await fetch(`/api/admin/users?status=${status}`); | |
| const data = await response.json(); | |
| if (data.success) { | |
| renderUsers(data.data); | |
| } else { | |
| listDiv.innerHTML = `<div class="alert alert-error">${data.error}</div>`; | |
| } | |
| } catch (error) { | |
| listDiv.innerHTML = '<div class="alert alert-error">Failed to load users.</div>'; | |
| } | |
| } | |
| function renderUsers(users) { | |
| const listDiv = document.getElementById('usersList'); | |
| if (users.length === 0) { | |
| listDiv.innerHTML = `<div class="glass-card text-center py-5"><p class="text-muted">No ${currentStatus} applications found.</p></div>`; | |
| return; | |
| } | |
| listDiv.innerHTML = users.map(user => ` | |
| <div class="glass-card user-card"> | |
| <div class="user-info"> | |
| <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;"> | |
| <h3>${user.name}</h3> | |
| <span class="badge badge-${user.role}">${user.role}</span> | |
| <span class="badge badge-${user.status}">${user.status}</span> | |
| </div> | |
| <div class="user-meta"> | |
| <strong>Matric/ID:</strong> ${user.matric_no} | | |
| <strong>Email:</strong> ${user.email || 'N/A'}<br> | |
| <strong>Applied on:</strong> ${new Date(user.created_at).toLocaleDateString()} ${new Date(user.created_at).toLocaleTimeString()} | |
| </div> | |
| </div> | |
| <div class="action-btns"> | |
| ${user.status === 'pending' || user.status === 'rejected' ? ` | |
| <button class="btn btn-primary btn-sm" onclick="handleAction(${user.id}, 'approve')">✅ Approve</button> | |
| ` : ''} | |
| ${user.status === 'pending' || user.status === 'approved' ? ` | |
| <button class="btn btn-error btn-sm" style="background: #ef4444; color: white;" onclick="handleAction(${user.id}, 'reject')">❌ Reject</button> | |
| ` : ''} | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| async function handleAction(userId, action) { | |
| if (!confirm(`Are you sure you want to ${action} this user?`)) return; | |
| showLoading(`${action === 'approve' ? 'Approving' : 'Rejecting'} user...`); | |
| try { | |
| const response = await fetch(`/api/admin/users/${userId}/${action}`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| const data = await response.json(); | |
| hideLoading(); | |
| if (data.success) { | |
| alert(data.message); | |
| loadUsers(currentStatus); | |
| } else { | |
| alert(data.error); | |
| } | |
| } catch (error) { | |
| hideLoading(); | |
| alert('Operation failed.'); | |
| } | |
| } | |
| function showLoading(text) { | |
| document.getElementById('loadingText').textContent = text; | |
| document.getElementById('loadingOverlay').classList.remove('hidden'); | |
| } | |
| function hideLoading() { | |
| document.getElementById('loadingOverlay').classList.add('hidden'); | |
| } | |
| </script> | |
| </body> | |
| </html> | |