Sửa lỗi UX trong dashboard và privacy của người dùng ở chế độ private

This commit is contained in:
2026-06-08 20:46:01 +07:00
parent 37f1984e8b
commit 6dfc811292
4 changed files with 93 additions and 40 deletions
+2 -9
View File
@@ -207,21 +207,14 @@ router.get('/scenes', optionalAuth, async (req, res) => {
query = {
$or: [
{ privacy: 'public' },
{ privacy: 'member' },
{ privacy: 'shared' }, // shareToken will be required to fetch panorama, but coordinates show on map
{ createdBy: req.user._id },
{ sharedWith: req.user._id },
{ sharedEmails: req.user.email }
]
};
} else {
// Guests: See only public or shared scenes
query = {
$or: [
{ privacy: 'public' },
{ privacy: 'shared' }
]
};
// Guests: See only public scenes
query = { privacy: 'public' };
}
const scenes = await Scene.find(query)
+2 -1
View File
@@ -596,7 +596,8 @@ html, body {
#logout-confirm-modal,
#delete-asset-confirm-modal,
#success-modal {
#success-modal,
#error-modal {
z-index: 5500;
}
+11 -1
View File
@@ -314,12 +314,22 @@
<!-- Success Message Modal -->
<div id="success-modal" class="modal-overlay" onclick="closeSuccessModal(event)">
<div class="modal-content action-modal-content logout-modal-dark" style="border-top: 4px solid #28a745; max-width: 350px;">
<div style="font-size: 40px; color: #28a745; margin-bottom: 10px;"></div>
<div id="success-modal-icon" style="font-size: 40px; color: #28a745; margin-bottom: 10px;"></div>
<h2 style="color: #fff; margin-bottom: 5px;">Thành công</h2>
<p style="color: #ccc;" id="success-modal-message">Dữ liệu đã được cập nhật.</p>
</div>
</div>
<!-- Error/Warning Message Modal -->
<div id="error-modal" class="modal-overlay" onclick="closeErrorModal(event)">
<div class="modal-content action-modal-content logout-modal-dark" style="border-top: 4px solid #dc3545; max-width: 380px;">
<div id="error-modal-icon" style="font-size: 40px; color: #dc3545; margin-bottom: 10px;">⚠️</div>
<h2 style="color: #fff; margin-bottom: 5px;" id="error-modal-title">Thông báo</h2>
<p style="color: #ccc;" id="error-modal-message">Bạn không có quyền thực hiện thao tác này.</p>
<button onclick="closeErrorModal()" class="edit-btn-large" style="background: #444; width: 100%; margin-top: 20px; font-size: 14px;">Đóng</button>
</div>
</div>
<!-- Modal for Action Choice (Edit/Delete) -->
<div id="action-choice-modal" class="modal">
<div class="modal-content action-modal-content">
+78 -29
View File
@@ -366,9 +366,9 @@ async function handleRegister() {
const data = await response.json();
if (!response.ok) throw new Error(data.message || 'Registration failed');
alert('Registration successful! You can now log in.');
showNotification('Registration successful! You can now log in.', 'success');
} catch (error) {
alert(error.message);
showNotification(error.message, 'error');
}
}
@@ -377,7 +377,10 @@ async function handleRegister() {
*/
function showLogoutConfirm() {
const modal = document.getElementById('logout-confirm-modal');
if (modal) modal.style.display = 'flex';
if (modal) {
closeDashboard(); // Đảm bảo đóng dashboard nếu đang mở
modal.style.display = 'flex';
}
}
/**
@@ -385,7 +388,11 @@ function showLogoutConfirm() {
*/
function closeLogoutConfirm() {
const modal = document.getElementById('logout-confirm-modal');
if (modal) modal.style.display = 'none';
if (modal) {
modal.style.display = 'none';
openDashboard(); // Mở lại dashboard sau khi đóng confirm
}
}
/**
@@ -421,7 +428,7 @@ function openCreateSceneModal(lat, lng) {
returnToDashboardAfterEdit = false;
const token = localStorage.getItem('jwt');
if (!token) {
alert('Please log in first to create a 3D scene.');
showNotification('Please log in first to create a 3D scene.', 'error');
return;
}
@@ -486,7 +493,7 @@ async function submitScene(e) {
const method = sceneId ? 'PUT' : 'POST';
uploadWithProgress(url, method, formData, token, 'create', () => {
alert(sceneId ? 'Scene đang được cập nhật ngầm!' : 'Scene đã được tạo! Ảnh đang được xử lý 8K...');
showNotification(sceneId ? 'Scene đang được cập nhật ngầm!' : 'Scene đã được tạo! Ảnh đang được xử lý 8K...', 'success');
closeModal();
loadScenes();
});
@@ -524,13 +531,13 @@ function uploadWithProgress(url, method, formData, token, prefix, callback) {
const err = JSON.parse(xhr.responseText);
errorMsg = err.message || errorMsg;
} catch (e) {}
alert('Lỗi: ' + errorMsg);
showNotification('Lỗi: ' + errorMsg, 'error');
}
});
xhr.addEventListener('error', () => {
if (container) container.style.display = 'none';
alert('Lỗi kết nối mạng.');
showNotification('Lỗi kết nối mạng.', 'error');
});
xhr.open(method, url);
@@ -651,7 +658,7 @@ async function loadScenes() {
if (isAdmin || (currentUserId && ownerId && ownerId.toString() === currentUserId.toString())) {
handleEditDeleteScene(scene);
} else {
alert("Bạn không có quyền chỉnh sửa scene này.");
showNotification("Bạn không có quyền chỉnh sửa scene này.", 'warning');
}
});
@@ -771,7 +778,7 @@ window.confirmDeleteScene = async function() {
loadMyScenes();
}
} catch (error) {
alert("Lỗi khi xóa: " + error.message);
showNotification("Lỗi khi xóa: " + error.message, 'error');
}
};
@@ -858,14 +865,14 @@ async function openScene(sceneId, privacy, shareToken, force = false, initialPit
// Kiểm tra nếu đang truy cập qua link trực tiếp (URL có sceneId) mà gặp lỗi (do xóa token hoặc token không hợp lệ)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('sceneId')) {
alert("Bạn không có quyền truy cập hoặc liên kết chia sẻ đã hết hạn. Quay về bản đồ công cộng.");
showNotification("Bạn không có quyền truy cập hoặc liên kết chia sẻ đã hết hạn. Quay về bản đồ công cộng.", 'error');
// Xóa toàn bộ tham số URL và tải lại trang để làm mới trạng thái (về trang chủ dành cho khách)
window.history.replaceState({}, document.title, window.location.pathname);
location.reload();
return;
}
alert(error.message);
showNotification(error.message, 'error');
}
}
@@ -892,7 +899,7 @@ function restoreActiveScene() {
window.handleHotspotCreation = async function(pitch, yaw, existingHotspot = null) {
const token = localStorage.getItem('jwt');
if (!token) {
alert('Vui lòng đăng nhập để thực hiện thao tác này.');
showNotification('Vui lòng đăng nhập để thực hiện thao tác này.', 'warning');
return;
}
@@ -946,7 +953,7 @@ window.handleHotspotCreation = async function(pitch, yaw, existingHotspot = null
if (linkType === 'upload') {
const file = document.getElementById('hs-panorama-file').files[0];
if (!file) {
alert('Vui lòng chọn ảnh panorama.');
showNotification('Vui lòng chọn ảnh panorama.', 'warning');
return;
}
@@ -961,7 +968,7 @@ window.handleHotspotCreation = async function(pitch, yaw, existingHotspot = null
lat = Number(document.getElementById('hs-lat').value);
lng = Number(document.getElementById('hs-lng').value);
if (!lat || !lng) {
alert('Vui lòng chọn vị trí GPS.');
showNotification('Vui lòng chọn vị trí GPS.', 'warning');
return;
}
}
@@ -982,7 +989,7 @@ window.handleHotspotCreation = async function(pitch, yaw, existingHotspot = null
const finalTargetId = formData.get('targetSceneId');
if (!finalTargetId) {
alert('Vui lòng chọn cảnh để liên kết.');
showNotification('Vui lòng chọn cảnh để liên kết.', 'warning');
return;
}
await saveHotspotToDB(pitch, yaw, formData.get('title'), formData.get('description'), finalTargetId, existingHotspot?._id);
@@ -1105,13 +1112,13 @@ async function saveHotspotToDB(pitch, yaw, title, description, targetSceneId, ho
const data = await response.json();
if (!response.ok) throw new Error(data.message || 'Lỗi khi lưu hotspot');
alert('Lưu điểm điều hướng thành công!');
showNotification('Lưu điểm điều hướng thành công!', 'success');
// Buộc nạp lại để cập nhật danh sách hotspot mới
openScene(currentSceneId, localStorage.getItem('activeScenePrivacy'), localStorage.getItem('activeSceneToken'), true);
} catch (error) {
console.error(error);
alert(error.message);
showNotification(error.message, 'error');
}
}
@@ -1132,13 +1139,13 @@ window.systemReset = async function() {
if (response.ok) {
localStorage.clear(); // Xóa sạch token, vị trí map, active scene
alert(data.message);
showNotification(data.message, 'success');
location.reload();
} else {
throw new Error(data.message);
}
} catch (e) {
alert("Lỗi reset: " + e.message);
showNotification("Lỗi reset: " + e.message, 'error');
}
};
@@ -1191,11 +1198,11 @@ async function deleteHotspot(hotspotId) {
throw new Error(err.message || 'Lỗi xóa hotspot');
}
alert('Đã xóa điểm điều hướng.');
showNotification('Đã xóa điểm điều hướng.', 'success');
// Refresh lại scene hiện tại để cập nhật viewer
openScene(currentSceneId, null, null, true);
} catch (e) {
alert(e.message);
showNotification(e.message, 'error');
}
}
@@ -1415,22 +1422,24 @@ window.confirmDeleteAsset = async function() {
}
closeDeleteAssetModal();
showSuccessModal(data.message || "Đã xóa thành công");
showNotification(data.message || "Đã xóa thành công", 'success');
loadMyAssets(); // Nạp lại kho ảnh
loadScenes(); // Nạp lại bản đồ nếu có scene bị xóa kèm theo
} catch (e) {
alert("Lỗi khi xóa: " + e.message);
showNotification("Lỗi khi xóa: " + e.message, 'error');
}
};
/**
* Hiển thị Modal thông báo thành công
*/
window.showSuccessModal = function(message) {
window.showSuccessModal = function(message, icon = '✓') {
const modal = document.getElementById('success-modal');
const msgElem = document.getElementById('success-modal-message');
if (modal && msgElem) {
const iconElem = document.getElementById('success-modal-icon');
if (modal && msgElem && iconElem) {
msgElem.innerText = message;
iconElem.innerText = icon;
modal.style.display = 'flex';
// Tự động ẩn sau 3 giây
setTimeout(() => closeSuccessModal(), 3000);
@@ -1448,9 +1457,49 @@ window.closeSuccessModal = function(e) {
modal.style.display = 'none';
};
/**
* Hiển thị Modal thông báo lỗi hoặc cảnh báo
*/
window.showErrorModal = function(message, title = "Thông báo", icon = '⚠️') {
const modal = document.getElementById('error-modal');
const msgElem = document.getElementById('error-modal-message');
const titleElem = document.getElementById('error-modal-title');
const iconElem = document.getElementById('error-modal-icon');
if (modal && msgElem && iconElem) {
msgElem.innerText = message;
iconElem.innerText = icon;
if (titleElem) titleElem.innerText = title;
modal.style.display = 'flex';
}
};
/**
* Đóng Modal lỗi (hỗ trợ click ra ngoài)
*/
window.closeErrorModal = function(e) {
const modal = document.getElementById('error-modal');
if (!modal) return;
// Nếu nhấn từ code (không có e) hoặc click trúng overlay thì đóng
if (e && e.target !== modal) return;
modal.style.display = 'none';
};
/**
* Hàm thông báo dùng chung thay thế alert()
*/
window.showNotification = function(message, type = 'success') {
if (type === 'success') {
showSuccessModal(message, '✓');
} else if (type === 'warning') {
showErrorModal(message, 'Cảnh báo', '⚠️');
} else {
showErrorModal(message, 'Lỗi', '❌');
}
};
window.openEditFromMedia = function(scene, isChild = false) {
if (!scene || !scene._id) {
alert("Không thể chỉnh sửa: Ảnh này không được gắn với một Scene hợp lệ.");
showNotification("Không thể chỉnh sửa: Ảnh này không được gắn với một Scene hợp lệ.", 'error');
return;
}
dashboardReturnTab = 'media-library';
@@ -1679,11 +1728,11 @@ async function submitEditScene(e) {
const data = await res.json();
if (!res.ok) throw new Error(data.message);
showSuccessModal("Đã cập nhật thông tin cảnh thành công!");
showNotification("Đã cập nhật thông tin cảnh thành công!", 'success');
closeEditMetadataModal();
loadScenes();
} catch (err) {
alert("Lỗi cập nhật: " + err.message);
showNotification("Lỗi cập nhật: " + err.message, 'error');
}
}