astro_avtourist/frontend/src/pages/auth/reset-password.astro

361 lines
No EOL
13 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import Layout from '@layouts/Layout.astro';
import { SITE_URL } from '@constants';
const token = Astro.url.searchParams.get('token');
const userId = Astro.url.searchParams.get('userId');
const error = Astro.url.searchParams.get('error');
---
<Layout
title="Сброс пароля"
description="Создайте новый пароль"
canonicalLink={`${SITE_URL}/auth/reset-password`}
>
<div class="auth-page">
<div class="auth-container">
<div class="auth-card" id="card">
<!-- Форма сброса пароля -->
<div id="reset-form-container">
<div class="auth-header">
<h1>Новый пароль</h1>
<p>Придумайте новый пароль для аккаунта</p>
</div>
<form class="auth-form" id="reset-form">
<input type="hidden" name="userId" id="userId" value={userId} />
<input type="hidden" name="token" id="token" value={token} />
<div class="form-group">
<label for="password">
Новый пароль
<span class="hint">От 8 до 12 символов</span>
</label>
<div class="password-wrapper">
<input
type="password"
id="password"
name="password"
placeholder="••••••••"
required
minlength="8"
maxlength="12"
/>
<button type="button" class="toggle-password" data-target="password">
<svg class="eye-open" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<svg class="eye-closed" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
<line x1="1" y1="1" x2="23" y2="23"></line>
</svg>
</button>
</div>
<span class="error-message" id="password-error"></span>
</div>
<div class="form-group">
<label for="confirmPassword">Подтвердите пароль</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
placeholder="••••••••"
required
minlength="8"
maxlength="12"
/>
<span class="error-message" id="confirm-error"></span>
</div>
<button type="submit" class="btn-submit" id="submit-btn">
Сохранить пароль
</button>
</form>
</div>
<!-- Успех -->
<div id="success-container" class="hidden">
<div class="auth-header">
<div class="success-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</div>
<h1>Пароль изменён!</h1>
<p>Теперь вы можете войти с новым паролем</p>
</div>
<a href="/auth/sign-in" class="btn-submit">Войти</a>
</div>
<!-- Ошибка -->
<div id="error-container" class="hidden">
<div class="auth-header">
<div class="error-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>
</div>
<h1>Ошибка</h1>
<p id="error-text">Ссылка недействительна или истёк срок действия</p>
</div>
<a href="/auth/forgot-password" class="btn-submit">Запросить заново</a>
</div>
</div>
</div>
</div>
</Layout>
<script>
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
const userId = urlParams.get('userId');
const error = urlParams.get('error');
const form = document.getElementById('reset-form') as HTMLFormElement;
const card = document.getElementById('card');
const submitBtn = document.getElementById('submit-btn') as HTMLButtonElement;
// Показ секций
function showSection(id: string) {
const formContainer = document.getElementById('reset-form-container');
const successContainer = document.getElementById('success-container');
const errorContainer = document.getElementById('error-container');
formContainer?.classList.add('hidden');
successContainer?.classList.add('hidden');
errorContainer?.classList.add('hidden');
document.getElementById(id)?.classList.remove('hidden');
}
// Обработка формы
form?.addEventListener('submit', async (e) => {
e.preventDefault();
const password = (document.getElementById('password') as HTMLInputElement).value;
const confirmPassword = (document.getElementById('confirmPassword') as HTMLInputElement).value;
// Валидация
if (password !== confirmPassword) {
const errorEl = document.getElementById('confirm-error');
if (errorEl) errorEl.textContent = 'Пароли не совпадают';
return;
}
if (password.length < 8 || password.length > 12) {
const errorEl = document.getElementById('password-error');
if (errorEl) errorEl.textContent = 'Пароль должен быть от 8 до 12 символов';
return;
}
submitBtn.disabled = true;
submitBtn.textContent = 'Сохранение...';
try {
const response = await fetch('/api/auth/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, userId, password }),
});
const data = await response.json();
if (response.ok && data.success) {
showSection('success-container');
} else {
const errorEl = document.getElementById('password-error');
if (errorEl) errorEl.textContent = data.error || 'Ошибка';
}
} catch (err) {
const errorEl = document.getElementById('password-error');
if (errorEl) errorEl.textContent = 'Ошибка соединения';
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Сохранить пароль';
}
});
// Переключение видимости пароля
document.querySelectorAll('.toggle-password').forEach(btn => {
btn.addEventListener('click', () => {
const target = (btn as HTMLButtonElement).dataset.target;
const input = document.getElementById(target || '') as HTMLInputElement;
if (input) {
input.type = input.type === 'password' ? 'text' : 'password';
}
});
});
// Инициализация
if (error) {
const errorText = document.getElementById('error-text');
if (errorText) errorText.textContent = error;
showSection('error-container');
} else if (!token || !userId) {
showSection('error-container');
}
</script>
<style>
.auth-page {
min-height: calc(100vh - 160px);
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 6rem 2rem 2rem;
}
.auth-container {
width: 100%;
max-width: 440px;
}
.auth-card {
background: #ffffff;
border-radius: 16px;
padding: 2rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
.auth-header {
text-align: center;
margin-bottom: 2rem;
}
.auth-header h1 {
color: #1e3050;
font-size: 1.5rem;
font-weight: 700;
margin: 1rem 0 0.5rem;
}
.auth-header p {
color: #64748b;
font-size: 0.95rem;
margin: 0;
}
.success-icon, .error-icon {
width: 80px;
height: 80px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
}
.success-icon {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
}
.error-icon {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
.success-icon svg, .error-icon svg {
color: white;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
color: #1e3050;
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.form-group .hint {
color: #94a3b8;
font-weight: 400;
margin-left: 0.5rem;
}
.form-group input {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 0.95rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus {
outline: none;
border-color: #eac26e;
box-shadow: 0 0 0 3px rgba(234, 194, 110, 0.2);
}
.password-wrapper {
position: relative;
}
.password-wrapper input {
padding-right: 3rem;
}
.toggle-password {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: #94a3b8;
}
.toggle-password .eye-closed {
display: none;
}
.password-wrapper input[type="text"] + .toggle-password .eye-open,
.password-wrapper input[type="text"] + .toggle-password .eye-closed {
display: none;
}
.password-wrapper input[type="text"] + .toggle-password .eye-closed {
display: block;
}
.error-message {
color: #ef4444;
font-size: 0.8rem;
margin-top: 0.25rem;
display: block;
}
.btn-submit {
width: 100%;
padding: 0.875rem;
background: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
color: #ffffff;
border: none;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
text-decoration: none;
display: inline-block;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn-submit:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(206, 159, 64, 0.4);
}
.hidden {
display: none !important;
}
</style>