first commit
This commit is contained in:
commit
4a589825c2
297 changed files with 33019 additions and 0 deletions
529
frontend/src/components/auth/PasswordResetForm.astro
Normal file
529
frontend/src/components/auth/PasswordResetForm.astro
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
---
|
||||
import Button from "../base/Button.astro";
|
||||
|
||||
// Иконки
|
||||
const emailIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>`;
|
||||
|
||||
const lockIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>`;
|
||||
|
||||
const checkIcon = `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 13l4 4L19 7"/></svg>`;
|
||||
|
||||
const arrowLeftIcon = `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>`;
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
// Режим: 'request' - запрос сброса, 'reset' - установка нового пароля
|
||||
mode?: "request" | "reset";
|
||||
token?: string; // Токен из URL для режима reset
|
||||
}
|
||||
|
||||
const { mode = "request", token = "" } = Astro.props;
|
||||
|
||||
const isResetMode = mode === "reset";
|
||||
const title = isResetMode ? "Новый пароль" : "Восстановление пароля";
|
||||
const subtitle = isResetMode
|
||||
? "Придумайте новый пароль для вашего аккаунта"
|
||||
: "Введите email, и мы отправим вам ссылку для сброса пароля";
|
||||
---
|
||||
|
||||
<div class="w-full max-w-md mx-auto">
|
||||
<!-- Назад к входу -->
|
||||
<a
|
||||
href="/auth/login"
|
||||
class="inline-flex items-center gap-2 text-sm text-gray-500 hover:text-gold transition-colors mb-8"
|
||||
>
|
||||
<Fragment set:html={arrowLeftIcon} />
|
||||
<span>Вернуться к входу</span>
|
||||
</a>
|
||||
|
||||
<!-- Заголовок -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">{title}</h1>
|
||||
<p class="text-sm text-gray-500">{subtitle}</p>
|
||||
</div>
|
||||
|
||||
<!-- Форма запроса сброса -->
|
||||
{!isResetMode && (
|
||||
<form class="space-y-6" id="forgot-form" novalidate>
|
||||
<!-- Honeypot -->
|
||||
<div class="honeypot-field" aria-hidden="true">
|
||||
<input type="text" name="website" id="website" tabindex="-1" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="relative group">
|
||||
<label for="email" class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Email <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-gray-400">
|
||||
<Fragment set:html={emailIcon} />
|
||||
</div>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="you@example.com"
|
||||
required
|
||||
maxlength="254"
|
||||
pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||||
class="w-full pl-12 pr-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-900 font-medium focus:outline-none focus:border-gold focus:ring-2 focus:ring-gold/20 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<span class="error-message hidden text-red-500 text-xs mt-1">
|
||||
Введите корректный email адрес
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<Button
|
||||
type="submit"
|
||||
text="Отправить ссылку"
|
||||
variant="primary-white-text"
|
||||
size="md"
|
||||
className="w-full!"
|
||||
id="submit-btn"
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
|
||||
<!-- Форма установки нового пароля -->
|
||||
{isResetMode && (
|
||||
<form class="space-y-6" id="reset-form" novalidate data-token={token}>
|
||||
<!-- Honeypot -->
|
||||
<div class="honeypot-field" aria-hidden="true">
|
||||
<input type="text" name="website" id="website" tabindex="-1" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<!-- Новый пароль -->
|
||||
<div class="relative group">
|
||||
<label for="password" class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Новый пароль <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-gray-400">
|
||||
<Fragment set:html={lockIcon} />
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="••••••••"
|
||||
required
|
||||
minlength="8"
|
||||
maxlength="12"
|
||||
class="w-full pl-12 pr-12 py-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-900 font-medium focus:outline-none focus:border-gold focus:ring-2 focus:ring-gold/20 transition-all"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="toggle-password"
|
||||
class="absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 hover:text-gray-600 transition-colors"
|
||||
aria-label="Показать пароль"
|
||||
>
|
||||
<svg id="eye-icon" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
<svg id="eye-off-icon" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="error-message hidden text-red-500 text-xs mt-1">
|
||||
Пароль должен содержать хотя бы одну букву и одну цифру
|
||||
</span>
|
||||
<p class="text-xs text-gray-400 mt-1">От 8 до 12 символов, буква + цифра</p>
|
||||
</div>
|
||||
|
||||
<!-- Подтверждение пароля -->
|
||||
<div class="relative group">
|
||||
<label for="confirm-password" class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Повторите пароль <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-gray-400">
|
||||
<Fragment set:html={checkIcon} />
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm-password"
|
||||
name="confirmPassword"
|
||||
placeholder="••••••••"
|
||||
required
|
||||
minlength="8"
|
||||
maxlength="12"
|
||||
class="w-full pl-12 pr-12 py-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-900 font-medium focus:outline-none focus:border-gold focus:ring-2 focus:ring-gold/20 transition-all"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="toggle-confirm-password"
|
||||
class="absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 hover:text-gray-600 transition-colors"
|
||||
aria-label="Показать пароль"
|
||||
>
|
||||
<svg id="eye-icon-confirm" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
<svg id="eye-off-icon-confirm" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="error-message hidden text-red-500 text-xs mt-1">
|
||||
Пароли не совпадают
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<Button
|
||||
type="submit"
|
||||
text="Сохранить пароль"
|
||||
variant="primary-white-text"
|
||||
size="md"
|
||||
className="w-full!"
|
||||
id="submit-btn"
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.honeypot-field {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Определяем режим по наличию формы
|
||||
const isResetMode = !!document.getElementById('reset-form');
|
||||
const form = document.getElementById(isResetMode ? 'reset-form' : 'forgot-form');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const honeypotField = document.getElementById('website');
|
||||
|
||||
// Логирование
|
||||
function log(message, data) {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[PASSWORD_RESET_FORM][${timestamp}] ${message}`, data || '');
|
||||
}
|
||||
|
||||
function logError(message, data) {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.error(`[PASSWORD_RESET_FORM][${timestamp}] ERROR: ${message}`, data || '');
|
||||
}
|
||||
|
||||
const touchedFields = new Set();
|
||||
|
||||
// Утилита для ошибок
|
||||
function showFieldError(input, show, message = "") {
|
||||
const group = input.closest('.group');
|
||||
const errorEl = group?.querySelector('.error-message');
|
||||
|
||||
if (errorEl) {
|
||||
if (show) {
|
||||
if (message) errorEl.textContent = message;
|
||||
errorEl.classList.remove('hidden');
|
||||
input.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-500/20');
|
||||
input.classList.remove('focus:border-gold', 'focus:ring-gold/20');
|
||||
} else {
|
||||
errorEl.classList.add('hidden');
|
||||
input.classList.remove('border-red-500', 'focus:border-red-500', 'focus:ring-red-500/20');
|
||||
input.classList.add('focus:border-gold', 'focus:ring-gold/20');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Валидация email
|
||||
function validateEmail(showError = false) {
|
||||
const emailInput = document.getElementById('email');
|
||||
if (!emailInput) return true;
|
||||
|
||||
const value = emailInput.value.trim();
|
||||
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
let isValid = true;
|
||||
|
||||
if (!value || !emailPattern.test(value) || value.length > 254) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (showError || touchedFields.has('email')) {
|
||||
showFieldError(emailInput, !isValid && value.length > 0);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Валидация пароля
|
||||
function validatePassword(showError = false) {
|
||||
const passwordInput = document.getElementById('password');
|
||||
if (!passwordInput) return true;
|
||||
|
||||
const value = passwordInput.value;
|
||||
const minLength = 8;
|
||||
const maxLength = 12;
|
||||
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/;
|
||||
let isValid = true;
|
||||
|
||||
if (!value || value.length < minLength || value.length > maxLength) {
|
||||
isValid = false;
|
||||
} else if (!passwordRegex.test(value)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (showError || touchedFields.has('password')) {
|
||||
showFieldError(passwordInput, !isValid && value.length > 0);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Валидация подтверждения пароля
|
||||
function validateConfirmPassword(showError = false) {
|
||||
const confirmInput = document.getElementById('confirm-password');
|
||||
const passwordInput = document.getElementById('password');
|
||||
if (!confirmInput || !passwordInput) return true;
|
||||
|
||||
const value = confirmInput.value;
|
||||
const passwordValue = passwordInput.value;
|
||||
let isValid = true;
|
||||
|
||||
if (!value || value !== passwordValue) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (showError || touchedFields.has('confirmPassword')) {
|
||||
showFieldError(confirmInput, !isValid && value.length > 0,
|
||||
value !== passwordValue ? 'Пароли не совпадают' : '');
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Проверка всей формы
|
||||
function checkFormValidity() {
|
||||
const isHoneypotEmpty = !honeypotField?.value;
|
||||
|
||||
if (isResetMode) {
|
||||
const isPasswordValid = validatePassword(false);
|
||||
const isConfirmValid = validateConfirmPassword(false);
|
||||
submitBtn.disabled = !(isPasswordValid && isConfirmValid && isHoneypotEmpty);
|
||||
return isPasswordValid && isConfirmValid && isHoneypotEmpty;
|
||||
} else {
|
||||
const isEmailValid = validateEmail(false);
|
||||
submitBtn.disabled = !(isEmailValid && isHoneypotEmpty);
|
||||
return isEmailValid && isHoneypotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
// Показать все ошибки
|
||||
function showAllErrors() {
|
||||
if (isResetMode) {
|
||||
touchedFields.add('password');
|
||||
touchedFields.add('confirmPassword');
|
||||
const isPasswordValid = validatePassword(true);
|
||||
const isConfirmValid = validateConfirmPassword(true);
|
||||
return isPasswordValid && isConfirmValid;
|
||||
} else {
|
||||
touchedFields.add('email');
|
||||
return validateEmail(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Настройка переключателя пароля
|
||||
function setupPasswordToggle(btnId, inputId, eyeId, eyeOffId) {
|
||||
const btn = document.getElementById(btnId);
|
||||
const input = document.getElementById(inputId);
|
||||
const eyeIcon = document.getElementById(eyeId);
|
||||
const eyeOffIcon = document.getElementById(eyeOffId);
|
||||
|
||||
btn?.addEventListener('click', () => {
|
||||
const type = input.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
input.setAttribute('type', type);
|
||||
|
||||
if (type === 'text') {
|
||||
eyeIcon?.classList.add('hidden');
|
||||
eyeOffIcon?.classList.remove('hidden');
|
||||
btn.setAttribute('aria-label', 'Скрыть пароль');
|
||||
} else {
|
||||
eyeIcon?.classList.remove('hidden');
|
||||
eyeOffIcon?.classList.add('hidden');
|
||||
btn.setAttribute('aria-label', 'Показать пароль');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Режим запроса сброса
|
||||
if (!isResetMode) {
|
||||
const emailInput = document.getElementById('email');
|
||||
|
||||
// Ограничение ввода email
|
||||
emailInput?.addEventListener('keypress', (e) => {
|
||||
if (!/[a-zA-Z0-9@._%+\-]/.test(e.key) &&
|
||||
!['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
emailInput?.addEventListener('input', (e) => {
|
||||
e.target.value = e.target.value.replace(/\s/g, '');
|
||||
validateEmail();
|
||||
checkFormValidity();
|
||||
});
|
||||
|
||||
emailInput?.addEventListener('focus', () => touchedFields.add('email'));
|
||||
emailInput?.addEventListener('blur', () => {
|
||||
touchedFields.add('email');
|
||||
if (emailInput.value.length > 0) validateEmail(true);
|
||||
});
|
||||
|
||||
// Отправка формы запроса
|
||||
form?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (honeypotField?.value) {
|
||||
console.log('Bot detected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showAllErrors()) return;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
const originalText = submitBtn.textContent;
|
||||
submitBtn.textContent = 'Отправка...';
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const email = formData.get('email');
|
||||
|
||||
log(`Запрос сброса пароля для: ${email}`);
|
||||
|
||||
// Редирект на страницу успеха с email
|
||||
log('Перенаправление на страницу подтверждения отправки');
|
||||
window.location.href = `/auth/forgot-password-sent?email=${encodeURIComponent(email)}`;
|
||||
|
||||
} catch (error) {
|
||||
logError('❌ Ошибка сброса пароля:', error);
|
||||
alert(error.message || 'Ошибка отправки. Попробуйте позже.');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Режим установки нового пароля
|
||||
if (isResetMode) {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const confirmInput = document.getElementById('confirm-password');
|
||||
const token = form?.dataset.token;
|
||||
|
||||
// Переключатели видимости пароля
|
||||
setupPasswordToggle('toggle-password', 'password', 'eye-icon', 'eye-off-icon');
|
||||
setupPasswordToggle('toggle-confirm-password', 'confirm-password', 'eye-icon-confirm', 'eye-off-icon-confirm');
|
||||
|
||||
// Ограничение ввода пароля
|
||||
passwordInput?.addEventListener('keypress', (e) => {
|
||||
if (passwordInput.value.length >= 12 &&
|
||||
!['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
passwordInput?.addEventListener('input', (e) => {
|
||||
if (e.target.value.length > 12) {
|
||||
e.target.value = e.target.value.slice(0, 12);
|
||||
}
|
||||
e.target.value = e.target.value.replace(/\s/g, '');
|
||||
|
||||
validatePassword();
|
||||
if (confirmInput.value) validateConfirmPassword();
|
||||
checkFormValidity();
|
||||
});
|
||||
|
||||
confirmInput?.addEventListener('keypress', (e) => {
|
||||
if (confirmInput.value.length >= 12 &&
|
||||
!['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
confirmInput?.addEventListener('input', (e) => {
|
||||
if (e.target.value.length > 12) {
|
||||
e.target.value = e.target.value.slice(0, 12);
|
||||
}
|
||||
e.target.value = e.target.value.replace(/\s/g, '');
|
||||
|
||||
validateConfirmPassword();
|
||||
checkFormValidity();
|
||||
});
|
||||
|
||||
// Фокус и blur
|
||||
passwordInput?.addEventListener('focus', () => touchedFields.add('password'));
|
||||
confirmInput?.addEventListener('focus', () => touchedFields.add('confirmPassword'));
|
||||
|
||||
passwordInput?.addEventListener('blur', () => {
|
||||
touchedFields.add('password');
|
||||
if (passwordInput.value.length > 0) validatePassword(true);
|
||||
});
|
||||
|
||||
confirmInput?.addEventListener('blur', () => {
|
||||
touchedFields.add('confirmPassword');
|
||||
if (confirmInput.value.length > 0) validateConfirmPassword(true);
|
||||
});
|
||||
|
||||
// Отправка формы сброса
|
||||
form?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (honeypotField?.value) {
|
||||
console.log('Bot detected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showAllErrors()) return;
|
||||
|
||||
if (!token) {
|
||||
alert('Ошибка: отсутствует токен сброса');
|
||||
return;
|
||||
}
|
||||
|
||||
submitBtn.disabled = true;
|
||||
const originalText = submitBtn.textContent;
|
||||
submitBtn.textContent = 'Сохранение...';
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const password = formData.get('password');
|
||||
const passwordConfirm = formData.get('confirmPassword');
|
||||
|
||||
log(`Сброс пароля с токеном: ${token}`);
|
||||
|
||||
// Сохраняем данные в sessionStorage для страницы подтверждения
|
||||
sessionStorage.setItem('passwordResetData', JSON.stringify({
|
||||
password,
|
||||
passwordConfirm
|
||||
}));
|
||||
|
||||
// Редирект на страницу подтверждения с токеном
|
||||
log('Перенаправление на страницу подтверждения');
|
||||
window.location.href = `/auth/password-reset-success?token=${encodeURIComponent(token)}`;
|
||||
|
||||
} catch (error) {
|
||||
logError('❌ Ошибка сброса пароля:', error);
|
||||
// Очищаем sessionStorage при ошибке
|
||||
sessionStorage.removeItem('passwordResetData');
|
||||
alert(error.message || 'Ошибка сброса пароля. Возможно, ссылка устарела.');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Начальная проверка
|
||||
checkFormValidity();
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue