diff --git a/static/js/users.js b/static/js/users.js
new file mode 100644
index 0000000..22b184c
--- /dev/null
+++ b/static/js/users.js
@@ -0,0 +1,220 @@
+// Load user stats on page load
+document.addEventListener('DOMContentLoaded', function() {
+ loadUserStats();
+});
+
+async function loadUserStats() {
+ try {
+ const response = await fetch('/asteroid/api/users/stats');
+ const result = await response.json();
+
+ if (result.status === 'success') {
+ // TODO: move this stats builder to server
+ // const stats = result.stats;
+ const stats = {
+ total: 0,
+ active: 0,
+ admins: 0,
+ djs: 0,
+ };
+ if (result.users) {
+ result.users.forEach((user) => {
+ stats.total += 1;
+ if (user.active) stats.active += 1;
+ if (user.role == "admin") stats.admins += 1;
+ if (user.role == "dj") stats.djs += 1;
+ })
+ }
+ document.getElementById('total-users').textContent = stats.total;
+ document.getElementById('active-users').textContent = stats.active;
+ document.getElementById('admin-users').textContent = stats.admins;
+ document.getElementById('dj-users').textContent = stats.djs;
+ }
+ } catch (error) {
+ console.error('Error loading user stats:', error);
+ }
+}
+
+async function loadUsers() {
+ try {
+ const response = await fetch('/asteroid/api/users');
+ const result = await response.json();
+
+ if (result.status === 'success') {
+ showUsersTable(result.users);
+ document.getElementById('users-list-section').style.display = 'block';
+ }
+ } catch (error) {
+ console.error('Error loading users:', error);
+ alert('Error loading users. Please try again.');
+ }
+}
+
+function showUsersTable(users) {
+ const container = document.getElementById('users-container');
+ container.innerHTML = `
+
+
+
+ | Username |
+ Email |
+ Role |
+ Status |
+ Last Login |
+ Actions |
+
+
+
+ ${users.map(user => `
+
+ | ${user.username} |
+ ${user.email} |
+
+
+ |
+ ${user.active ? '✅ Active' : '❌ Inactive'} |
+ ${user['last-login'] ? new Date(user['last-login'] * 1000).toLocaleString() : 'Never'} |
+
+ ${user.active ?
+ `` :
+ ``
+ }
+ |
+
+ `).join('')}
+
+
+
+ `;
+}
+
+function hideUsersTable() {
+ document.getElementById('users-list-section').style.display = 'none';
+}
+
+async function updateUserRole(userId, newRole) {
+ try {
+ const formData = new FormData();
+ formData.append('role', newRole);
+
+ const response = await fetch(`/asteroid/api/users/${userId}/role`, {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+
+ if (result.status === 'success') {
+ loadUserStats();
+ alert('User role updated successfully');
+ } else {
+ alert('Error updating user role: ' + result.message);
+ }
+ } catch (error) {
+ console.error('Error updating user role:', error);
+ alert('Error updating user role. Please try again.');
+ }
+}
+
+async function deactivateUser(userId) {
+ if (!confirm('Are you sure you want to deactivate this user?')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`/asteroid/api/users/${userId}/deactivate`, {
+ method: 'POST'
+ });
+
+ const result = await response.json();
+
+ if (result.status === 'success') {
+ loadUsers();
+ loadUserStats();
+ alert('User deactivated successfully');
+ } else {
+ alert('Error deactivating user: ' + result.message);
+ }
+ } catch (error) {
+ console.error('Error deactivating user:', error);
+ alert('Error deactivating user. Please try again.');
+ }
+}
+
+async function activateUser(userId) {
+ try {
+ const response = await fetch(`/asteroid/api/users/${userId}/activate`, {
+ method: 'POST'
+ });
+
+ const result = await response.json();
+
+ if (result.status === 'success') {
+ loadUsers();
+ loadUserStats();
+ alert('User activated successfully');
+ } else {
+ alert('Error activating user: ' + result.message);
+ }
+ } catch (error) {
+ console.error('Error activating user:', error);
+ alert('Error activating user. Please try again.');
+ }
+}
+
+function toggleCreateUserForm() {
+ const form = document.getElementById('create-user-form');
+ if (form.style.display === 'none') {
+ form.style.display = 'block';
+ // Clear form
+ document.getElementById('new-username').value = '';
+ document.getElementById('new-email').value = '';
+ document.getElementById('new-password').value = '';
+ document.getElementById('new-role').value = 'listener';
+ } else {
+ form.style.display = 'none';
+ }
+}
+
+async function createNewUser(event) {
+ event.preventDefault();
+
+ const username = document.getElementById('new-username').value;
+ const email = document.getElementById('new-email').value;
+ const password = document.getElementById('new-password').value;
+ const role = document.getElementById('new-role').value;
+
+ try {
+ const formData = new FormData();
+ formData.append('username', username);
+ formData.append('email', email);
+ formData.append('password', password);
+ formData.append('role', role);
+
+ const response = await fetch('/asteroid/api/users/create', {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+
+ if (result.status === 'success') {
+ alert(`User "${username}" created successfully!`);
+ toggleCreateUserForm();
+ loadUserStats();
+ loadUsers();
+ } else {
+ alert('Error creating user: ' + result.message);
+ }
+ } catch (error) {
+ console.error('Error creating user:', error);
+ alert('Error creating user. Please try again.');
+ }
+}
+
+// Update user stats every 30 seconds
+setInterval(loadUserStats, 30000);
diff --git a/template/users.chtml b/template/users.chtml
index 32b5b9f..e96d19f 100644
--- a/template/users.chtml
+++ b/template/users.chtml
@@ -5,6 +5,7 @@
+
@@ -86,220 +87,5 @@
-
-