Add user registration and authentication UI improvements

- Add registration page with form validation
- Add login/register/logout navigation with conditional display
- Add auth-status API endpoint for session checking
- Add auth-ui.js for dynamic nav based on login state
- Update navigation across all pages (front, admin, profile, player)
- Style logout button with subtle red color
- Auto-login after successful registration
This commit is contained in:
glenneth 2025-10-07 06:38:39 +03:00 committed by Brian O'Reilly
parent fa1de1c874
commit 5d31763e85
9 changed files with 228 additions and 7 deletions

View File

@ -642,6 +642,78 @@
:top-artist-5-plays "")))
|#
;; Auth status API endpoint
(define-page api-auth-status #@"/api/auth-status" ()
"Check if user is logged in and their role"
(setf (radiance:header "Content-Type") "application/json")
(handler-case
(let* ((user-id (session:field "user-id"))
(user (when user-id (find-user-by-id user-id))))
(cl-json:encode-json-to-string
`(("loggedIn" . ,(if user t nil))
("isAdmin" . ,(if (and user (user-has-role-p user :admin)) t nil))
("username" . ,(if user
(let ((username (gethash "username" user)))
(if (listp username) (first username) username))
nil)))))
(error (e)
(cl-json:encode-json-to-string
`(("loggedIn" . nil)
("isAdmin" . nil)
("error" . ,(format nil "~a" e)))))))
;; Register page (GET)
(define-page register #@"/register" ()
"User registration page"
(let ((username (radiance:post-var "username"))
(email (radiance:post-var "email"))
(password (radiance:post-var "password"))
(confirm-password (radiance:post-var "confirm-password")))
(if (and username password)
;; Handle registration form submission
(cond
;; Validate passwords match
((not (string= password confirm-password))
(render-template-with-plist "register"
:title "Asteroid Radio - Register"
:display-error "display: block;"
:display-success "display: none;"
:error-message "Passwords do not match"
:success-message ""))
;; Check if username already exists
((find-user-by-username username)
(render-template-with-plist "register"
:title "Asteroid Radio - Register"
:display-error "display: block;"
:display-success "display: none;"
:error-message "Username already exists"
:success-message ""))
;; Create the user
(t
(if (create-user username email password :role :listener :active t)
(progn
;; Auto-login after successful registration
(let ((user (find-user-by-username username)))
(when user
(let ((user-id (gethash "_id" user)))
(setf (session:field "user-id") (if (listp user-id) (first user-id) user-id)))))
(radiance:redirect "/asteroid/"))
(render-template-with-plist "register"
:title "Asteroid Radio - Register"
:display-error "display: block;"
:display-success "display: none;"
:error-message "Registration failed. Please try again."
:success-message ""))))
;; Show registration form (no POST data)
(render-template-with-plist "register"
:title "Asteroid Radio - Register"
:display-error "display: none;"
:display-success "display: none;"
:error-message ""
:success-message ""))))
(define-page player #@"/player" ()
(let ((template-path (merge-pathnames "template/player.chtml"
(asdf:system-source-directory :asteroid))))

View File

@ -64,6 +64,30 @@ body .nav a:hover{
background: #2a3441;
}
body .nav .btn-logout{
background: #2a3441;
border-color: #3a4551;
color: #ff9999;
}
body .nav .btn-logout:hover{
background: #3a4551;
border-color: #4a5561;
color: #ffaaaa;
}
body [data-show-if-logged-in]{
display: none;
}
body [data-show-if-logged-out]{
display: none;
}
body [data-show-if-admin]{
display: none;
}
body .controls{
margin: 20px 0;
}

View File

@ -55,7 +55,28 @@
:margin-left "0")
((:and a :hover)
:background "#2a3441"))
:background "#2a3441")
;; Logout button styling - subtle, not alarming
(.btn-logout
:background "#2a3441"
:border-color "#3a4551"
:color "#ff9999")
((:and .btn-logout :hover)
:background "#3a4551"
:border-color "#4a5561"
:color "#ffaaaa"))
;; Hide conditional auth elements by default (JavaScript will show them)
(|[data-show-if-logged-in]|
:display none)
(|[data-show-if-logged-out]|
:display none)
(|[data-show-if-admin]|
:display none)
(.controls
:margin "20px 0"

38
static/js/auth-ui.js Normal file
View File

@ -0,0 +1,38 @@
// auth-ui.js - Handle authentication UI state across all pages
// Check if user is logged in by calling the API
async function checkAuthStatus() {
try {
const response = await fetch('/asteroid/api/auth-status');
const data = await response.json();
return data;
} catch (error) {
console.error('Error checking auth status:', error);
return { loggedIn: false, isAdmin: false };
}
}
// Update UI based on authentication status
function updateAuthUI(authStatus) {
// Show/hide elements based on login status
document.querySelectorAll('[data-show-if-logged-in]').forEach(el => {
el.style.display = authStatus.loggedIn ? 'inline-block' : 'none';
});
document.querySelectorAll('[data-show-if-logged-out]').forEach(el => {
el.style.display = authStatus.loggedIn ? 'none' : 'inline-block';
});
document.querySelectorAll('[data-show-if-admin]').forEach(el => {
el.style.display = authStatus.isAdmin ? 'inline-block' : 'none';
});
}
// Initialize auth UI on page load
document.addEventListener('DOMContentLoaded', async function() {
console.log('Auth UI initializing...');
const authStatus = await checkAuthStatus();
console.log('Auth status:', authStatus);
updateAuthUI(authStatus);
console.log('Auth UI updated');
});

View File

@ -5,6 +5,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/admin.js"></script>
</head>
<body>
@ -13,7 +14,9 @@
<div class="nav">
<a href="/asteroid/">← Back to Main</a>
<a href="/asteroid/player/">Web Player</a>
<a href="/asteroid/profile">Profile</a>
<a href="/asteroid/admin/users">👥 Users</a>
<a href="/asteroid/logout" class="btn-logout">Logout</a>
</div>
<!-- System Status -->

View File

@ -5,7 +5,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/front-page.js"></script>
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/front-page.js"></script>
</head>
<body>
<div class="container">
@ -14,11 +15,12 @@
<nav class="nav">
<a href="/asteroid/">Home</a>
<a href="/asteroid/player">Player</a>
<a href="/asteroid/profile">Profile</a>
<a href="/asteroid/admin">Admin</a>
<a href="/asteroid/profile" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" data-show-if-admin>Admin</a>
<a href="/asteroid/status">Status</a>
<a href="/asteroid/login">Login</a>
<a href="/asteroid/register">Register</a>
<a href="/asteroid/login" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</nav>
</header>

View File

@ -5,6 +5,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/player.js"></script>
</head>
<body>
@ -12,7 +13,9 @@
<h1>🎵 WEB PLAYER</h1>
<div class="nav">
<a href="/asteroid/">← Back to Main</a>
<a href="/asteroid/admin">Admin Dashboard</a>
<a href="/asteroid/profile" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" data-show-if-admin>Admin Dashboard</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</div>
<!-- Live Stream Section -->

View File

@ -5,6 +5,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/profile.js"></script>
</head>
<body>
@ -14,6 +15,7 @@
<a href="/asteroid/">← Back to Main</a>
<a href="/asteroid/player/">Web Player</a>
<a href="/asteroid/admin/" data-show-if-admin>Admin</a>
<a href="/asteroid/logout" class="btn-logout">Logout</a>
</div>
<!-- User Profile Header -->

56
template/register.chtml Normal file
View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title data-text="title">Asteroid Radio - Register</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
</head>
<body>
<div class="container">
<h1>🎵 ASTEROID RADIO - REGISTER</h1>
<div class="nav">
<a href="/asteroid/">← Back to Main</a>
<a href="/asteroid/login">Login</a>
</div>
<div class="auth-container">
<div class="auth-form">
<h2>Create Account</h2>
<div class="message error" data-attr="style" data-attr-value="display-error">
<span data-text="error-message">Registration failed</span>
</div>
<div class="message success" data-attr="style" data-attr-value="display-success">
<span data-text="success-message">Registration successful!</span>
</div>
<form method="post" action="/asteroid/register">
<div class="form-group">
<label>Username:</label>
<input type="text" name="username" required minlength="3" maxlength="50">
<small style="color: #8892b0;">Minimum 3 characters</small>
</div>
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" name="password" required minlength="6">
<small style="color: #8892b0;">Minimum 6 characters</small>
</div>
<div class="form-group">
<label>Confirm Password:</label>
<input type="password" name="confirm-password" required minlength="6">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" style="width: 100%;">CREATE ACCOUNT</button>
</div>
</form>
<div class="auth-footer">
<p>Already have an account? <a href="/asteroid/login">Login here</a></p>
</div>
</div>
</div>
</div>
</body>
</html>