Merge branch 'main' of https://gits.techvanguard.vn/UKSOURCE/cms.hailearning.edu.vn into fea/bao-10022026-AuditLog

This commit is contained in:
nguyenvanbao
2026-02-10 16:46:39 +07:00
9 changed files with 148 additions and 32 deletions

View File

@@ -172,12 +172,39 @@ exports.update = async (req, res) => {
location: parsedData.contactInfo?.location || "", location: parsedData.contactInfo?.location || "",
socialLinks: parsedData.socialLinks || [], socialLinks: parsedData.socialLinks || [],
}; };
console.log("✓ Converted to top object:", top);
} catch (e) { if (logo) {
console.error("✗ Error parsing topbarJson:", e.message); updateData.logo = logoData;
return res.status(400).json({ }
console.log(
"Preparing to update header with data:",
JSON.stringify(updateData, null, 2),
);
const updatedHeader = await Header.findByIdAndUpdate(
headerId,
updateData,
{ new: true, runValidators: true },
);
if (!updatedHeader) {
console.error("✗ Header not found with ID:", headerId);
return res.status(404).json({
success: false, success: false,
message: "Invalid JSON in topbarJson: " + e.message, message: "Header not found",
});
}
res.json({
success: true,
message: "Header updated successfully",
data: updatedHeader,
});
} catch (error) {
console.error("✗ Error updating header:", error);
res.status(400).json({
success: false,
message: error.message,
}); });
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -0,0 +1,47 @@
document.addEventListener('DOMContentLoaded', function() {
const toggleButtons = document.querySelectorAll('.password-toggle-btn');
toggleButtons.forEach(button => {
// Hover effect logic
button.addEventListener('mouseenter', function() {
this.style.color = 'var(--primary-color)';
});
button.addEventListener('mouseleave', function() {
this.style.color = '#666';
});
button.addEventListener('click', function() {
// Find the input element within the same container
// We assume the structure is container > input + button
// or button is absolutely positioned relative to container
// The safest way is to look for the input in the parent container
const container = this.parentElement;
const input = container.querySelector('input');
if (input) {
const type = input.getAttribute('type') === 'password' ? 'text' : 'password';
input.setAttribute('type', type);
const icon = this.querySelector('i');
if (type === 'text') {
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
this.setAttribute('aria-label', 'Hide password');
} else {
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
this.setAttribute('aria-label', 'Show password');
}
}
});
// Add keyboard accessibility
button.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
});
});

View File

@@ -134,22 +134,22 @@ router.post('/register', async (req, res) => {
} }
}); });
// Forgot Password Page // Change Password Page
router.get('/forgot-password', (req, res) => { router.get('/change-password', (req, res) => {
res.render('auth/forgot-password', { res.render('auth/change-password', {
title: 'Forgot Password', title: 'Change Password',
layout: false layout: false
}); });
}); });
// Forgot Password Handle // Change Password Handle
router.post('/forgot-password', async (req, res) => { router.post('/change-password', async (req, res) => {
const { email } = req.body; const { email } = req.body;
try { try {
const user = await User.findOne({ email }); const user = await User.findOne({ email });
if (!user) { if (!user) {
req.flash('error_msg', 'No account with that email address exists.'); req.flash('error_msg', 'No account with that email address exists.');
return res.redirect('/auth/forgot-password'); return res.redirect('/auth/change-password');
} }
const token = crypto.randomBytes(20).toString('hex'); const token = crypto.randomBytes(20).toString('hex');
@@ -166,7 +166,7 @@ router.post('/forgot-password', async (req, res) => {
} catch (err) { } catch (err) {
console.error(err); console.error(err);
req.flash('error_msg', 'Error processing request'); req.flash('error_msg', 'Error processing request');
res.redirect('/auth/forgot-password'); res.redirect('/auth/change-password');
} }
}); });
@@ -180,7 +180,7 @@ router.get('/reset-password/:token', async (req, res) => {
if (!user) { if (!user) {
req.flash('error_msg', 'Password reset token is invalid or has expired.'); req.flash('error_msg', 'Password reset token is invalid or has expired.');
return res.redirect('/auth/forgot-password'); return res.redirect('/auth/change-password');
} }
res.render('auth/reset-password', { res.render('auth/reset-password', {
@@ -190,7 +190,7 @@ router.get('/reset-password/:token', async (req, res) => {
}); });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
res.redirect('/auth/forgot-password'); res.redirect('/auth/change-password');
} }
}); });
@@ -204,7 +204,7 @@ router.post('/reset-password/:token', async (req, res) => {
if (!user) { if (!user) {
req.flash('error_msg', 'Password reset token is invalid or has expired.'); req.flash('error_msg', 'Password reset token is invalid or has expired.');
return res.redirect('/auth/forgot-password'); return res.redirect('/auth/change-password');
} }
if (req.body.password !== req.body.confirm_password) { if (req.body.password !== req.body.confirm_password) {

View File

@@ -170,11 +170,11 @@
</div> </div>
<div style="text-align: center; margin-bottom: 20px;"> <div style="text-align: center; margin-bottom: 20px;">
<h4 style="color: var(--primary-color); font-weight: 600; margin-bottom: 5px;">Forgot Password</h4> <h4 style="color: var(--primary-color); font-weight: 600; margin-bottom: 5px;">Change Password</h4>
<p style="color: var(--text-color); font-size: 13px;">Enter your email to reset password</p> <p style="color: var(--text-color); font-size: 13px;">Enter your email to change password</p>
</div> </div>
<form action="/auth/forgot-password" method="POST" class="login-form"> <form action="/auth/change-password" method="POST" class="login-form">
<div class="form-group" style="margin-bottom: 12px;"> <div class="form-group" style="margin-bottom: 12px;">
<label for="email" class="form-label">Email Address</label> <label for="email" class="form-label">Email Address</label>
<input type="email" class="form-control" id="email" name="email" required autofocus> <input type="email" class="form-control" id="email" name="email" required autofocus>
@@ -182,7 +182,7 @@
<div style="text-align: center; margin-top: 20px;"> <div style="text-align: center; margin-top: 20px;">
<button type="submit" class="btn-shine"> <button type="submit" class="btn-shine">
Reset Password Continue
</button> </button>
</div> </div>

View File

@@ -170,12 +170,18 @@
<div class="form-group" style="margin-bottom: 12px;"> <div class="form-group" style="margin-bottom: 12px;">
<label for="password" class="form-label">Password</label> <label for="password" class="form-label">Password</label>
<div style="position: relative;">
<input type="password" class="form-control" id="password" name="password" required <input type="password" class="form-control" id="password" name="password" required
autocomplete="current-password"> autocomplete="current-password" style="padding-right: 40px;">
<button type="button" class="password-toggle-btn" aria-label="Show password"
style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); border: none; background: none; color: #666; cursor: pointer; padding: 0; z-index: 10;">
<i class="fas fa-eye"></i>
</button>
</div>
<a href="/auth/forgot-password" <a href="/auth/change-password"
style="display: block; text-align: right; margin-top: 8px; color: #2563eb; text-decoration: none; font-size: 13px; font-weight: 600;"> style="display: block; text-align: right; margin-top: 8px; color: #2563eb; text-decoration: none; font-size: 13px; font-weight: 600;">
Forgot Your Password? Change Password?
</a> </a>
</div> </div>
@@ -209,6 +215,9 @@
<!-- Flash Handler JS --> <!-- Flash Handler JS -->
<script src="/js/flash-handler.js"></script> <script src="/js/flash-handler.js"></script>
<!-- Password Toggle JS -->
<script src="/js/password-toggle.js"></script>
</body> </body>
</html> </html>

View File

@@ -186,13 +186,26 @@
<div class="form-group" style="margin-bottom: 12px;"> <div class="form-group" style="margin-bottom: 12px;">
<label for="password" class="form-label">Password</label> <label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required> <div style="position: relative;">
<input type="password" class="form-control" id="password" name="password" required
style="padding-right: 40px;">
<button type="button" class="password-toggle-btn" aria-label="Show password"
style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); border: none; background: none; color: #666; cursor: pointer; padding: 0; z-index: 10;">
<i class="fas fa-eye"></i>
</button>
</div>
</div> </div>
<div class="form-group" style="margin-bottom: 12px;"> <div class="form-group" style="margin-bottom: 12px;">
<label for="confirm_password" class="form-label">Confirm Password</label> <label for="confirm_password" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" <div style="position: relative;">
required> <input type="password" class="form-control" id="confirm_password"
name="confirm_password" required style="padding-right: 40px;">
<button type="button" class="password-toggle-btn" aria-label="Show password"
style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); border: none; background: none; color: #666; cursor: pointer; padding: 0; z-index: 10;">
<i class="fas fa-eye"></i>
</button>
</div>
</div> </div>
<div style="text-align: center; margin-top: 20px;"> <div style="text-align: center; margin-top: 20px;">
@@ -251,6 +264,9 @@
} }
}); });
</script> </script>
<!-- Password Toggle JS -->
<script src="/js/password-toggle.js"></script>
</body> </body>
</html> </html>

View File

@@ -176,12 +176,26 @@
<form action="/auth/reset-password/<%= token %>" method="POST" class="login-form"> <form action="/auth/reset-password/<%= token %>" method="POST" class="login-form">
<div class="form-group" style="margin-bottom: 12px;"> <div class="form-group" style="margin-bottom: 12px;">
<label for="password" class="form-label">New Password</label> <label for="password" class="form-label">New Password</label>
<input type="password" class="form-control" id="password" name="password" required autofocus> <div style="position: relative;">
<input type="password" class="form-control" id="password" name="password" required autofocus
style="padding-right: 40px;">
<button type="button" class="password-toggle-btn" aria-label="Show password"
style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); border: none; background: none; color: #666; cursor: pointer; padding: 0; z-index: 10;">
<i class="fas fa-eye"></i>
</button>
</div>
</div> </div>
<div class="form-group" style="margin-bottom: 12px;"> <div class="form-group" style="margin-bottom: 12px;">
<label for="confirm_password" class="form-label">Confirm New Password</label> <label for="confirm_password" class="form-label">Confirm New Password</label>
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required> <div style="position: relative;">
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required
style="padding-right: 40px;">
<button type="button" class="password-toggle-btn" aria-label="Show password"
style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); border: none; background: none; color: #666; cursor: pointer; padding: 0; z-index: 10;">
<i class="fas fa-eye"></i>
</button>
</div>
</div> </div>
<div style="text-align: center; margin-top: 20px;"> <div style="text-align: center; margin-top: 20px;">
@@ -234,6 +248,9 @@
} }
}); });
</script> </script>
<!-- Password Toggle JS -->
<script src="/js/password-toggle.js"></script>
</body> </body>
</html> </html>

View File

@@ -131,7 +131,7 @@
} }
// Automatically clean up on every hide event // Automatically clean up on every hide event
document.addEventListener('hidden.bs.modal', function() { document.addEventListener('hidden.bs.modal', function () {
// Wait a tiny bit for the animation to finish // Wait a tiny bit for the animation to finish
setTimeout(forceCleanupModals, 100); setTimeout(forceCleanupModals, 100);
}); });