Sửa lỗi quản lí users, chỉnh sửa users

This commit is contained in:
2026-06-08 21:30:03 +07:00
parent 6dfc811292
commit fadfb6ba09
7 changed files with 376 additions and 35 deletions
+80
View File
@@ -197,6 +197,8 @@ html, body {
z-index: 1600; /* Above top-bar */
border: 1px solid rgba(255, 255, 255, 0.1);
min-width: 180px; /* Đảm bảo không quá nhỏ */
max-height: 550px;
overflow-y: auto;
}
#user-dropdown.show {
@@ -210,6 +212,54 @@ html, body {
/* Nếu đây là tiêu đề "Welcome", nó sẽ bị ẩn bởi quy tắc bên dưới */
}
.auth-tabs {
display: flex;
padding: 0 10px 10px;
margin-bottom: 15px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.auth-tab-btn {
flex: 1;
background: none;
border: none;
color: #888;
cursor: pointer;
padding: 8px 0;
font-size: 14px;
transition: all 0.2s;
}
.auth-tab-btn.active {
color: #fff;
border-bottom: 2px solid #007bff;
}
.auth-error {
color: #ff4d4d;
font-size: 12px;
margin: 0 20px 10px 20px;
display: none;
}
.auth-submit-btn {
margin: 0 20px 15px;
width: calc(100% - 40px) !important;
background: #007bff !important;
color: white !important;
padding: 10px !important;
}
.rules-checkbox {
display: flex;
align-items: center;
gap: 8px;
color: #ccc;
font-size: 12px;
margin: 5px 20px 15px;
cursor: pointer;
}
#user-dropdown input {
width: calc(100% - 40px);
margin: 0 20px 10px 20px;
@@ -837,6 +887,36 @@ html, body {
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* --- Admin User Management Table --- */
.admin-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-size: 14px;
}
.admin-table th, .admin-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.admin-table th {
color: #888;
font-weight: 600;
text-transform: uppercase;
font-size: 12px;
}
.admin-table input, .admin-table select {
background: rgba(255, 255, 255, 0.05) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
color: #fff !important;
padding: 5px !important;
border-radius: 4px;
width: 100%;
}
/* --- Privacy Settings Enhancements --- */
.privacy-settings-btn {
background: rgba(255, 255, 255, 0.1);
+25 -7
View File
@@ -30,13 +30,31 @@
</div>
<div id="user-dropdown" class="dropdown-content">
<div id="auth-guest">
<h3>Login / Register</h3>
<input type="text" id="username-input" placeholder="Username" onkeydown="if(event.key === 'Enter') handleLogin()">
<input type="password" id="password-input" placeholder="Password" onkeydown="if(event.key === 'Enter') handleLogin()">
<p id="login-error-msg" style="color: #ff4d4d; font-size: 12px; margin: 0 20px 10px 20px; display: none;"></p>
<div class="btn-group">
<button onclick="handleLogin()">Login</button>
<button onclick="handleRegister()">Register</button>
<div class="auth-tabs">
<button id="tab-login-btn" class="auth-tab-btn active" onclick="switchAuthMode('login')">Đăng nhập</button>
<button id="tab-register-btn" class="auth-tab-btn" onclick="switchAuthMode('register')">Đăng ký</button>
</div>
<!-- Login Form -->
<div id="login-section">
<input type="text" id="username-input" placeholder="Tên đăng nhập" onkeydown="if(event.key === 'Enter') handleLogin()">
<input type="password" id="password-input" placeholder="Mật khẩu" onkeydown="if(event.key === 'Enter') handleLogin()">
<p id="login-error-msg" class="auth-error"></p>
<button onclick="handleLogin()" class="auth-submit-btn">Đăng nhập</button>
</div>
<!-- Register Form -->
<div id="register-section" style="display: none;">
<input type="text" id="reg-fullname" placeholder="Họ và tên đầy đủ">
<input type="email" id="reg-email" placeholder="Địa chỉ Email">
<input type="text" id="reg-username" placeholder="Tên đăng nhập">
<input type="password" id="reg-password" placeholder="Mật khẩu">
<input type="password" id="reg-confirm" placeholder="Nhắc lại mật khẩu">
<label class="rules-checkbox">
<input type="checkbox" id="reg-agree"> Đồng ý với <a href="#" style="color: #007bff;">quy định của trang</a>
</label>
<p id="reg-error-msg" class="auth-error"></p>
<button onclick="handleRegister()" class="auth-submit-btn">Đăng ký thành viên</button>
</div>
</div>
<div id="auth-logged-in" style="display: none;">
+177 -13
View File
@@ -277,13 +277,13 @@ function checkAuthStatus() {
const avatarInitials = document.getElementById('avatar-initials');
if (token && username) {
authGuest.style.display = 'none'; // Hide login form
if (authGuest) authGuest.style.display = 'none'; // Hide login form
authLoggedIn.style.display = 'block'; // Show welcome message and buttons
avatarInitials.innerText = username.charAt(0).toUpperCase();
// Chỉ hiển thị các NÚT BẤM menu admin trong sidebar
// Hiển thị các nút dành cho admin (Chủ sở hữu/Admin tối cao)
const adminButtons = document.querySelectorAll('.dashboard-tabs .admin-only');
if (role === 'Chủ sở hữu' || role === 'admin') {
if (role === 'admin' || role === 'Chủ sở hữu') {
adminButtons.forEach(btn => btn.style.display = 'block');
} else {
adminButtons.forEach(btn => btn.style.display = 'none');
@@ -344,15 +344,66 @@ async function handleLogin() {
}
}
/**
* Chuyển đổi giữa chế độ Đăng nhập và Đăng ký trong dropdown
*/
window.switchAuthMode = function(mode) {
const loginSection = document.getElementById('login-section');
const registerSection = document.getElementById('register-section');
const loginBtn = document.getElementById('tab-login-btn');
const registerBtn = document.getElementById('tab-register-btn');
if (mode === 'login') {
loginSection.style.display = 'block';
registerSection.style.display = 'none';
loginBtn.classList.add('active');
registerBtn.classList.remove('active');
} else {
loginSection.style.display = 'none';
registerSection.style.display = 'block';
loginBtn.classList.remove('active');
registerBtn.classList.add('active');
}
};
/**
* Handles user registration
*/
async function handleRegister() {
const username = document.getElementById('username-input').value.trim();
const password = document.getElementById('password-input').value.trim();
const errorMsg = document.getElementById('reg-error-msg');
if (errorMsg) errorMsg.style.display = 'none';
if (!username || !password) {
alert('Please fill in both fields');
const fullName = document.getElementById('reg-fullname').value.trim();
const email = document.getElementById('reg-email').value.trim();
const username = document.getElementById('reg-username').value.trim();
const password = document.getElementById('reg-password').value;
const confirm = document.getElementById('reg-confirm').value;
const agree = document.getElementById('reg-agree').checked;
// Kiểm tra các trường trống
if (!fullName || !email || !username || !password || !confirm) {
if (errorMsg) {
errorMsg.innerText = 'Vui lòng điền đầy đủ thông tin';
errorMsg.style.display = 'block';
}
return;
}
// Kiểm tra mật khẩu khớp nhau
if (password !== confirm) {
if (errorMsg) {
errorMsg.innerText = 'Mật khẩu xác nhận không khớp';
errorMsg.style.display = 'block';
}
return;
}
// Kiểm tra đồng ý quy định
if (!agree) {
if (errorMsg) {
errorMsg.innerText = 'Bạn phải đồng ý với quy định của trang';
errorMsg.style.display = 'block';
}
return;
}
@@ -360,15 +411,26 @@ async function handleRegister() {
const response = await fetch(`${API_BASE_URL}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, role: 'Thành viên' })
body: JSON.stringify({
fullName,
email,
username,
password,
agreedToRules: agree,
role: 'Thành viên'
})
});
const data = await response.json();
if (!response.ok) throw new Error(data.message || 'Registration failed');
if (!response.ok) throw new Error(data.message || 'Đăng ký thất bại');
showNotification('Registration successful! You can now log in.', 'success');
showNotification('Đăng ký thành công! Bạn có thể đăng nhập ngay bây giờ.', 'success');
switchAuthMode('login'); // Tự động chuyển về tab đăng nhập sau khi thành công
} catch (error) {
showNotification(error.message, 'error');
if (errorMsg) {
errorMsg.innerText = error.message;
errorMsg.style.display = 'block';
}
}
}
@@ -654,7 +716,7 @@ async function loadScenes() {
const ownerId = scene.createdBy?._id || scene.createdBy || scene.owner?._id || scene.owner;
// Phân quyền: Admin hoặc Chủ sở hữu Scene
const isAdmin = userRole === 'Chủ sở hữu' || userRole === 'admin';
const isAdmin = userRole === 'admin' || userRole === 'Chủ sở hữu';
if (isAdmin || (currentUserId && ownerId && ownerId.toString() === currentUserId.toString())) {
handleEditDeleteScene(scene);
} else {
@@ -1312,6 +1374,105 @@ async function loadMyScenes() {
}
}
/**
* Tải danh sách người dùng dành cho Admin tối cao
*/
async function loadAdminUsers() {
const token = localStorage.getItem('jwt');
const container = document.getElementById('admin-users-list');
if (!container) return;
container.innerHTML = '<p>Đang tải danh sách người dùng...</p>';
try {
const res = await fetch(`${API_BASE_URL}/admin/users`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const users = await res.json();
if (!res.ok) throw new Error(users.message);
let html = `
<table class="admin-table">
<thead>
<tr>
<th>Họ tên</th>
<th>Username</th>
<th>Email</th>
<th>Quyền hạn</th>
<th>Reset Password</th>
<th>Thao tác</th>
</tr>
</thead>
<tbody>
`;
users.forEach(user => {
html += `
<tr>
<td><input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}"></td>
<td><strong>${user.username}</strong></td>
<td><input type="email" id="adm-em-${user._id}" value="${user.email || ''}"></td>
<td>
<select id="adm-role-${user._id}">
<option value="user" ${user.role === 'user' ? 'selected' : ''}>User</option>
<option value="editor" ${user.role === 'editor' ? 'selected' : ''}>Editor</option>
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
</select>
</td>
<td><input type="password" id="adm-pw-${user._id}" placeholder="Mật khẩu mới"></td>
<td>
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')">Lưu</button>
<button class="delete-btn-small" onclick="deleteUserByAdmin('${user._id}')">Xóa</button>
</td>
</tr>
`;
});
html += '</tbody></table>';
container.innerHTML = html;
} catch (e) {
container.innerHTML = `<p style="color:red">Lỗi: ${e.message}</p>`;
}
}
window.updateUserByAdmin = async function(userId) {
const token = localStorage.getItem('jwt');
const payload = {
fullName: document.getElementById(`adm-fn-${userId}`).value,
email: document.getElementById(`adm-em-${userId}`).value,
role: document.getElementById(`adm-role-${userId}`).value,
password: document.getElementById(`adm-pw-${userId}`).value
};
try {
const res = await fetch(`${API_BASE_URL}/admin/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
showNotification(data.message, 'success');
loadAdminUsers();
} catch (e) { showNotification(e.message, 'error'); }
};
window.deleteUserByAdmin = async function(userId) {
if (!confirm('Xóa vĩnh viễn người dùng này?')) return;
const token = localStorage.getItem('jwt');
try {
const res = await fetch(`${API_BASE_URL}/admin/users/${userId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
if (!res.ok) throw new Error(data.message);
showNotification(data.message, 'success');
loadAdminUsers();
} catch (e) { showNotification(e.message, 'error'); }
};
/**
* Tải và hiển thị kho ảnh/media của người dùng
*/
@@ -1373,7 +1534,7 @@ async function loadMyAssets() {
}
// Add delete button and its event listener
if (asset.uploadedBy === currentUserId || (isTrash && userRole === 'Chủ sở hữu')) {
if (asset.uploadedBy === currentUserId || (isTrash && (userRole === 'admin' || userRole === 'Chủ sở hữu'))) {
const deleteButton = document.createElement('button');
deleteButton.className = 'delete-btn-small';
deleteButton.innerText = 'Xóa';
@@ -1764,6 +1925,9 @@ function openDashboardTab(tabName) {
if (tabName === 'media-library') {
loadMyAssets();
}
if (tabName === 'user-management') {
loadAdminUsers();
}
}
// Đánh dấu nút tab được chọn là active
+1 -1
View File
@@ -166,7 +166,7 @@ function applyViewerSecurity() {
const currentUserId = localStorage.getItem('userId');
// Phân quyền: Admin (Chủ sở hữu) hoặc Người tạo ra Scene này
const isAdmin = userRole === 'Chủ sở hữu' || userRole === 'admin';
const isAdmin = userRole === 'admin' || userRole === 'Chủ sở hữu';
const isAuthorized = isAdmin ||
(currentUserId && currentSceneOwnerId && currentUserId.toString() === currentSceneOwnerId.toString());