{% extends "base.twig" %}
{% block title %}{{ t('ui.reset_password.title', {}, 'common') }} - {{ parent() }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card">
<div class="card-header">
<h4 class="mb-0">
<i class="fas fa-lock"></i>
{{ t('ui.reset_password.create_new_password', {}, 'common') }}
</h4>
</div>
<div class="card-body">
<div id="validatingToken" class="text-center py-4">
<i class="fas fa-spinner fa-spin fa-2x text-primary mb-3"></i>
<p class="text-muted">{{ t('ui.reset_password.validating_token', {}, 'common') }}</p>
</div>
<div id="invalidToken" class="alert alert-danger" style="display: none;">
<strong>{{ t('ui.reset_password.invalid_or_expired_token', {}, 'common') }}</strong>
<p class="mb-0 mt-2">
{{ t('ui.reset_password.invalid_token_help_line1', {}, 'common') }}
{{ t('ui.reset_password.invalid_token_help_line2', {}, 'common') }}
</p>
</div>
<div id="resetForm" style="display: none;">
<p class="text-muted mb-4">
{{ t('ui.reset_password.enter_new_password', {}, 'common') }}
</p>
<form id="passwordResetForm">
<input type="hidden" id="resetToken" name="token" value="{{ token }}">
<div class="mb-3">
<label for="newPassword" class="form-label">{{ t('ui.reset_password.new_password', {}, 'common') }}</label>
<input type="password" class="form-control" id="newPassword" name="newPassword" required minlength="8">
<div class="form-text">
{{ t('ui.reset_password.password_min_length_help', {}, 'common') }}
</div>
</div>
<div class="mb-3">
<label for="confirmPassword" class="form-label">{{ t('ui.reset_password.confirm_new_password', {}, 'common') }}</label>
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword" required>
<div id="passwordMatchError" class="text-danger small mt-1" style="display: none;">
{{ t('ui.reset_password.passwords_do_not_match', {}, 'common') }}
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary" id="submitBtn">
<i class="fas fa-check"></i>
{{ t('ui.reset_password.title', {}, 'common') }}
</button>
</div>
</form>
<div id="successMessage" class="alert alert-success mt-3" style="display: none;"></div>
<div id="errorMessage" class="alert alert-danger mt-3" style="display: none;"></div>
</div>
</div>
</div>
<div class="text-center mt-3">
<small class="text-muted">
<a href="/login" class="text-decoration-none">{{ t('ui.forgot_password.back_to_login', {}, 'common') }}</a>
{% if not token %}
| <a href="/forgot-password" class="text-decoration-none">{{ t('ui.reset_password.request_new_reset_link', {}, 'common') }}</a>
{% endif %}
</small>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function uiT(key, fallback, params = {}) {
if (window.t) {
return window.t(key, params, fallback);
}
return fallback;
}
// Get token from URL parameter
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
if (!token) {
$('#validatingToken').hide();
$('#invalidToken').show();
} else {
// Set the token in the hidden field
$('#resetToken').val(token);
// Validate the token
$.ajax({
url: '/api/auth/validate-reset-token',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ token: token }),
success: function(response) {
if (response.valid) {
$('#validatingToken').hide();
$('#resetForm').show();
$('#newPassword').focus();
} else {
$('#validatingToken').hide();
$('#invalidToken').show();
}
},
error: function() {
$('#validatingToken').hide();
$('#invalidToken').show();
}
});
}
// Password matching validation
$('#confirmPassword').on('input', function() {
const newPassword = $('#newPassword').val();
const confirmPassword = $(this).val();
if (confirmPassword && newPassword !== confirmPassword) {
$('#passwordMatchError').show();
} else {
$('#passwordMatchError').hide();
}
});
// Form submission
$('#passwordResetForm').on('submit', function(e) {
e.preventDefault();
const newPassword = $('#newPassword').val();
const confirmPassword = $('#confirmPassword').val();
// Validate passwords match
if (newPassword !== confirmPassword) {
$('#errorMessage').html(`<strong>${uiT('ui.common.error', 'Error')}</strong><br>${uiT('ui.reset_password.passwords_do_not_match', 'Passwords do not match.')}`).show();
return;
}
// Validate password length
if (newPassword.length < 8) {
$('#errorMessage').html(`<strong>${uiT('ui.common.error', 'Error')}</strong><br>${uiT('ui.reset_password.password_min_length_help', 'Password must be at least 8 characters long.')}`).show();
return;
}
const formData = {
token: $('#resetToken').val(),
newPassword: newPassword
};
// Hide previous messages
$('#successMessage').hide();
$('#errorMessage').hide();
// Disable submit button
const submitBtn = $('#submitBtn');
const originalText = submitBtn.html();
submitBtn.prop('disabled', true).html(`<i class="fas fa-spinner fa-spin"></i> ${uiT('ui.reset_password.resetting', 'Resetting...')}`);
$.ajax({
url: '/api/auth/reset-password',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function(response) {
if (response.success) {
const successText = window.getApiMessage
? window.getApiMessage(response, uiT('ui.reset_password.success_reset_complete', 'Password has been reset successfully. You can now log in with your new password.'))
: uiT('ui.reset_password.success_reset_complete', 'Password has been reset successfully. You can now log in with your new password.');
$('#successMessage').html(
`<strong>${uiT('ui.common.success', 'Success')}</strong><br>` +
successText +
`<br><br>${uiT('ui.reset_password.redirect_notice', 'You will be redirected to the login page in 3 seconds...')}`
).show();
$('#passwordResetForm').hide();
// Redirect to login after 3 seconds
setTimeout(function() {
window.location.href = '/login';
}, 3000);
}
},
error: function(xhr) {
const error = window.getApiErrorMessage
? window.getApiErrorMessage(xhr.responseJSON, uiT('errors.auth.reset_failed', 'Failed to reset password'))
: uiT('errors.auth.reset_failed', 'Failed to reset password');
$('#errorMessage').html(`<strong>${uiT('ui.common.error', 'Error')}</strong><br>${error}`).show();
submitBtn.prop('disabled', false).html(originalText);
}
});
});
</script>
{% endblock %}
|