forked from UKSOURCE/cms.hailearning.edu.vn
feat: add password toggle visibility and rename forgot-password to change-password
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 135 KiB |
47
public/js/password-toggle.js
Normal file
47
public/js/password-toggle.js
Normal 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user