fix: Complete UI fixes for page flow feature

- Fix api-output wrapper handling in all JavaScript files
- Add profile page API endpoints (profile, listening-stats, recent-tracks, top-artists)
- Fix session persistence - auth-ui.js now correctly detects login status
- Fix user stats display - now shows correct counts (3 users, 1 admin)
- Fix View All Users table - properly displays all users
- Handle empty arrays gracefully in profile.js (no errors for missing data)

All UI issues resolved:
✓ User management page fully functional
✓ Session persists across navigation
✓ Profile page loads without errors
✓ Correct nav links shown based on role
✓ Admin sees Admin link, regular users don't
This commit is contained in:
glenneth 2025-10-12 08:11:14 +03:00 committed by Brian O'Reilly
parent 4b8a3a064c
commit 5362c86f9f
4 changed files with 80 additions and 36 deletions

View File

@ -637,6 +637,49 @@
("error" . ,(format nil "~a" e)))
:status 500))))
;; User profile API endpoints
(define-api asteroid/user/profile () ()
"Get current user profile information"
(require-authentication)
(handler-case
(let* ((user-id (session:field "user-id"))
(user (find-user-by-id user-id)))
(if user
(api-output `(("status" . "success")
("user" . (("username" . ,(first (gethash "username" user)))
("email" . ,(first (gethash "email" user)))
("role" . ,(first (gethash "role" user)))
("created_at" . ,(first (gethash "created-date" user)))
("last_active" . ,(first (gethash "last-login" user)))))))
(api-output `(("status" . "error")
("message" . "User not found"))
:status 404)))
(error (e)
(api-output `(("status" . "error")
("message" . ,(format nil "Error loading profile: ~a" e)))
:status 500))))
(define-api asteroid/user/listening-stats () ()
"Get user listening statistics"
(require-authentication)
(api-output `(("status" . "success")
("stats" . (("total_listen_time" . 0)
("tracks_played" . 0)
("session_count" . 0)
("favorite_genre" . "Unknown"))))))
(define-api asteroid/user/recent-tracks (&optional (limit "3")) ()
"Get recently played tracks for user"
(require-authentication)
(api-output `(("status" . "success")
("tracks" . ()))))
(define-api asteroid/user/top-artists (&optional (limit "5")) ()
"Get top artists for user"
(require-authentication)
(api-output `(("status" . "success")
("artists" . ()))))
;; Register page (GET)
(define-page register #@"/register" ()
"User registration page"

View File

@ -4,7 +4,9 @@
async function checkAuthStatus() {
try {
const response = await fetch('/api/asteroid/auth-status');
const data = await response.json();
const result = await response.json();
// api-output wraps response in {status, message, data}
const data = result.data || result;
return data;
} catch (error) {
console.error('Error checking auth status:', error);

View File

@ -11,7 +11,9 @@ function loadProfileData() {
// Load user info
fetch('/api/asteroid/user/profile')
.then(response => response.json())
.then(data => {
.then(result => {
// api-output wraps response in {status, message, data}
const data = result.data || result;
if (data.status === 'success') {
currentUser = data.user;
updateProfileDisplay(data.user);
@ -52,7 +54,8 @@ function updateProfileDisplay(user) {
function loadListeningStats() {
fetch('/api/asteroid/user/listening-stats')
.then(response => response.json())
.then(data => {
.then(result => {
const data = result.data || result;
if (data.status === 'success') {
const stats = data.stats;
updateElement('total-listen-time', formatDuration(stats.total_listen_time || 0));
@ -74,8 +77,9 @@ function loadListeningStats() {
function loadRecentTracks() {
fetch('/api/asteroid/user/recent-tracks?limit=3')
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.tracks.length > 0) {
.then(result => {
const data = result.data || result;
if (data.status === 'success' && data.tracks && data.tracks.length > 0) {
data.tracks.forEach((track, index) => {
const trackNum = index + 1;
updateElement(`recent-track-${trackNum}-title`, track.title || 'Unknown Track');
@ -86,8 +90,8 @@ function loadRecentTracks() {
} else {
// Hide empty track items
for (let i = 1; i <= 3; i++) {
const trackItem = document.querySelector(`[data-text="recent-track-${i}-title"]`).closest('.track-item');
if (trackItem && !data.tracks[i-1]) {
const trackItem = document.querySelector(`[data-text="recent-track-${i}-title"]`)?.closest('.track-item');
if (trackItem && (!data.tracks || !data.tracks[i-1])) {
trackItem.style.display = 'none';
}
}
@ -101,8 +105,9 @@ function loadRecentTracks() {
function loadTopArtists() {
fetch('/api/asteroid/user/top-artists?limit=5')
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.artists.length > 0) {
.then(result => {
const data = result.data || result;
if (data.status === 'success' && data.artists && data.artists.length > 0) {
data.artists.forEach((artist, index) => {
const artistNum = index + 1;
updateElement(`top-artist-${artistNum}`, artist.name || 'Unknown Artist');
@ -111,8 +116,8 @@ function loadTopArtists() {
} else {
// Hide empty artist items
for (let i = 1; i <= 5; i++) {
const artistItem = document.querySelector(`[data-text="top-artist-${i}"]`).closest('.artist-item');
if (artistItem && !data.artists[i-1]) {
const artistItem = document.querySelector(`[data-text="top-artist-${i}"]`)?.closest('.artist-item');
if (artistItem && (!data.artists || !data.artists[i-1])) {
artistItem.style.display = 'none';
}
}

View File

@ -8,27 +8,15 @@ async function loadUserStats() {
const response = await fetch('/api/asteroid/user-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;
// api-output wraps response in {status, message, data}
const data = result.data || result;
if (data.status === 'success' && data.stats) {
const stats = data.stats;
document.getElementById('total-users').textContent = stats['total-users'] || 0;
document.getElementById('active-users').textContent = stats['active-users'] || 0;
document.getElementById('admin-users').textContent = stats['admins'] || 0;
document.getElementById('dj-users').textContent = stats['djs'] || 0;
}
} catch (error) {
console.error('Error loading user stats:', error);
@ -40,8 +28,11 @@ async function loadUsers() {
const response = await fetch('/api/asteroid/users');
const result = await response.json();
if (result.status === 'success') {
showUsersTable(result.users);
// api-output wraps response in {status, message, data}
const data = result.data || result;
if (data.status === 'success') {
showUsersTable(data.users);
document.getElementById('users-list-section').style.display = 'block';
}
} catch (error) {
@ -202,13 +193,16 @@ async function createNewUser(event) {
const result = await response.json();
if (result.status === 'success') {
// api-output wraps response in {status, message, data}
const data = result.data || result;
if (data.status === 'success') {
alert(`User "${username}" created successfully!`);
toggleCreateUserForm();
loadUserStats();
loadUsers();
} else {
alert('Error creating user: ' + result.message);
alert('Error creating user: ' + (data.message || result.message));
}
} catch (error) {
console.error('Error creating user:', error);