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:
parent
fa1de1c874
commit
5d31763e85
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue