astro_avtourist/frontend/src/pages/auth/sign-up.astro

1143 lines
38 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';
---
<Layout
title="Регистрация"
description="Создайте аккаунт для доступа к личному кабинету"
canonicalLink={`${SITE_URL}/auth/sign-up`}
>
<div class="auth-page">
<div class="auth-container">
<div class="auth-card">
<div class="auth-header">
<h1>Регистрация</h1>
</div>
<form class="auth-form" id="sign-up-form">
<div class="form-group honeypot-field">
<input
type="text"
name="website_url"
id="website_url"
tabindex="-1"
autocomplete="off"
class="honeypot"
/>
</div>
<div class="form-group">
<label for="firstName">Имя</label>
<input
type="text"
id="firstName"
name="firstName"
placeholder="Иван"
required
autocomplete="given-name"
inputmode="text"
/>
<span class="error-message" id="firstName-error"></span>
</div>
<div class="form-group">
<label for="lastName">Фамилия</label>
<input
type="text"
id="lastName"
name="lastName"
placeholder="Иванов"
required
autocomplete="family-name"
inputmode="text"
/>
<span class="error-message" id="lastName-error"></span>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="example@mail.ru"
required
autocomplete="email"
inputmode="email"
/>
<span class="error-message" id="email-error"></span>
</div>
<div class="form-group">
<label for="phone">Телефон</label>
<input
type="tel"
id="phone"
name="phone"
placeholder="+7 (999) 000-00-00"
required
autocomplete="tel"
inputmode="tel"
/>
<span class="error-message" id="phone-error"></span>
</div>
<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
autocomplete="new-password"
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>
<div class="password-wrapper">
<input
type="password"
id="confirmPassword"
name="confirmPassword"
placeholder="••••••••"
required
autocomplete="new-password"
minlength="8"
maxlength="12"
/>
<button type="button" class="toggle-password" data-target="confirmPassword">
<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="confirmPassword-error"></span>
</div>
<div class="form-options">
<label class="checkbox-label">
<input type="checkbox" name="agreement" required />
<span>Я принимаю условия <button type="button" class="privacy-link-btn" id="privacy-modal-trigger">использования</button> и <a href="/privacy" class="privacy-link">политики конфиденциальности</a>, даю согласие на обработку персональных данных</span>
</label>
</div>
<button type="submit" class="btn-submit">
Зарегистрироваться
</button>
</form>
<div class="auth-footer">
<p>Уже есть аккаунт? <a href="/auth/sign-in">Войти</a></p>
</div>
</div>
</div>
</div>
<!-- Модальное окно политики обработки данных -->
<div id="privacy-modal" class="modal-overlay" aria-hidden="true">
<div class="modal-container" role="dialog" aria-modal="true">
<button class="modal-close" aria-label="Закрыть" id="privacy-modal-close">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div class="modal-content">
<h2 class="modal-title">Политика обработки персональных данных</h2>
<div class="privacy-text">
<p>Настоящая политика обработки персональных данных (далее — Политика) определяет порядок обработки персональных данных пользователей сайта avtourist-surgut.ru.</p>
<h3>1. Общие положения</h3>
<p>1.1. Обработка персональных данных осуществляется на основе принципов законности, справедливости и конфиденциальности.</p>
<p>1.2. Оператором персональных данных является ИП (данные компании).</p>
<h3>2. Состав персональных данных</h3>
<p>2.1. Мы обрабатываем следующие персональные данные: ФИО, адрес электронной почты, номер телефона.</p>
<p>2.2. Персональные данные предоставляются пользователем добровольно при регистрации на сайте.</p>
<h3>3. Цели обработки персональных данных</h3>
<p>3.1. Персональные данные обрабатываются для:</p>
<ul>
<li>Регистрации и авторизации пользователя на сайте;</li>
<li>Предоставления юридических услуг;</li>
<li>Связи с пользователем по вопросам оказания услуг;</li>
<li>Информирования о новых услугах и предложениях.</li>
</ul>
<h3>4. Защита персональных данных</h3>
<p>4.1. Мы принимаем необходимые технические и организационные меры для защиты персональных данных от несанкционированного доступа, изменения, раскрытия или уничтожения.</p>
<p>4.2. Доступ к персональным данным имеют только уполномоченные сотрудники.</p>
<h3>5. Срок хранения персональных данных</h3>
<p>5.1. Персональные данные хранятся в течение срока, необходимого для достижения целей обработки, либо до момента отзыва согласия пользователем.</p>
<h3>6. Права пользователя</h3>
<p>6.1. Пользователь имеет право:</p>
<ul>
<li>Получить информацию об обработке своих персональных данных;</li>
<li>Требовать исправления неточных данных;</li>
<li>Требовать удаления данных;</li>
<li>Отозвать согласие на обработку данных.</li>
</ul>
<h3>7. Контактная информация</h3>
<p>7.1. По вопросам, связанным с обработкой персональных данных, обращаться по адресу: (контактные данные компании).</p>
</div>
<button type="button" class="modal-accept-btn" id="privacy-modal-accept">Принять</button>
</div>
</div>
</div>
</Layout>
<style>
/* Основная страница */
.auth-page {
min-height: calc(100vh - 160px);
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
display: flex;
align-items: flex-start;
justify-content: center;
padding: 4rem 2rem 2rem;
}
.auth-container {
width: 100%;
max-width: 440px;
}
/* Карточка авторизации */
.auth-card {
background: #ffffff;
border-radius: 16px;
padding: 1rem 1.25rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
/* Заголовок */
.auth-header {
text-align: center;
margin-bottom: 1rem;
}
.auth-header h1 {
color: #1e3050;
font-size: 1.5rem;
font-weight: 800;
margin: 0 0 0.5rem 0;
letter-spacing: -0.02em;
}
.auth-header p {
color: #64748b;
font-size: 0.9rem;
margin: 0;
}
/* Форма */
.auth-form {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.form-group label {
color: #1e3050;
font-size: 0.85rem;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-group label .hint {
color: #94a3b8;
font-size: 0.75rem;
font-weight: 400;
}
.form-group input {
padding: 0.5rem 0.6rem;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 0.9rem;
transition: all 0.2s ease;
background: #f8fafc;
}
.form-group input:focus {
outline: none;
border-color: #d4af37;
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.1);
background: #ffffff;
}
.password-wrapper {
position: relative;
display: flex;
align-items: center;
}
.password-wrapper input {
width: 100%;
padding-right: 2.5rem;
}
.toggle-password {
position: absolute;
right: 0.75rem;
background: none;
border: none;
cursor: pointer;
color: #94a3b8;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.2s ease;
}
.toggle-password:hover {
color: #d4af37;
}
.toggle-password .eye-closed {
display: none;
}
.toggle-password.active .eye-open {
display: none;
}
.toggle-password.active .eye-closed {
display: block;
}
.form-group input::placeholder {
color: #94a3b8;
}
.form-group .hint {
color: #94a3b8;
font-size: 0.75rem;
}
/* Honeypot поле */
.honeypot-field {
position: absolute;
left: -9999px;
opacity: 0;
pointer-events: none;
}
/* Сообщения об ошибках */
.error-message {
color: #ef4444;
font-size: 0.75rem;
min-height: 0.75rem;
margin-top: 0.15rem;
}
.form-group input.error {
border-color: #ef4444;
}
.form-group input.error:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
/* Опции формы */
.form-options {
font-size: 0.875rem;
}
.checkbox-label {
display: flex;
align-items: flex-start;
gap: 0.5rem;
color: #64748b;
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: #d4af37;
cursor: pointer;
margin-top: 2px;
}
.checkbox-label a {
color: #d4af37;
text-decoration: none;
font-weight: 500;
}
.checkbox-label a:hover {
color: #b8941f;
}
.checkbox-label .privacy-link {
color: #d4af37;
text-decoration: none;
font-size: 0.75rem;
}
.checkbox-label .privacy-link:hover {
color: #b8941f;
text-decoration: underline;
}
.privacy-link-btn {
background: none;
border: none;
color: #d4af37;
text-decoration: none;
font-weight: 500;
cursor: pointer;
font-size: inherit;
padding: 0;
}
.privacy-link-btn:hover {
color: #b8941f;
text-decoration: underline;
}
/* Модальное окно */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
padding: 1rem;
}
.modal-overlay.open {
opacity: 1;
visibility: visible;
}
.modal-container {
background: #ffffff;
border-radius: 16px;
max-width: 600px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
position: relative;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.modal-overlay.open .modal-container {
transform: scale(1);
}
.modal-close {
position: absolute;
top: 1rem;
right: 1rem;
background: none;
border: none;
cursor: pointer;
color: #64748b;
padding: 0.25rem;
transition: color 0.2s ease;
}
.modal-close:hover {
color: #1e3050;
}
.modal-content {
padding: 2rem;
}
.modal-title {
color: #1e3050;
font-size: 1.25rem;
font-weight: 800;
margin: 0 0 1.5rem;
}
.privacy-text {
color: #64748b;
font-size: 0.875rem;
line-height: 1.6;
}
.privacy-text h3 {
color: #1e3050;
font-size: 1rem;
font-weight: 600;
margin: 1.5rem 0 0.75rem;
}
.privacy-text p {
margin: 0.5rem 0;
}
.privacy-text ul {
margin: 0.5rem 0;
padding-left: 1.25rem;
}
.privacy-text li {
margin: 0.25rem 0;
}
.modal-accept-btn {
background: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
color: #ffffff;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 1.5rem;
width: 100%;
}
.modal-accept-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(206, 159, 64, 0.4);
}
/* Success message */
.success-message {
text-align: center;
padding: 2rem 1rem;
}
.success-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
}
.success-icon svg {
color: white;
}
.success-message h3 {
color: #1e3050;
font-size: 1.5rem;
font-weight: 700;
margin: 0 0 1rem;
}
.success-message p {
color: #64748b;
font-size: 1rem;
line-height: 1.6;
margin: 0 0 0.75rem;
}
.success-message .hint {
color: #94a3b8;
font-size: 0.875rem;
}
.success-message + .auth-footer {
display: none;
}
.success-message .btn-submit {
display: block;
margin-top: 1.5rem;
text-decoration: none;
width: 100%;
box-sizing: border-box;
text-align: center;
background: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
color: #ffffff;
border: none;
border-radius: 8px;
padding: 0.75rem;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
}
.success-message .btn-submit:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(206, 159, 64, 0.4);
}
/* Кнопка отправки */
.btn-submit {
background: linear-gradient(135deg, #eac26e 0%, #ce9f40 100%);
color: #ffffff;
border: none;
border-radius: 8px;
padding: 0.75rem;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 0.25rem;
}
.btn-submit:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(206, 159, 64, 0.4);
}
.btn-submit:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.btn-submit.loading {
position: relative;
color: transparent !important;
pointer-events: none;
}
.btn-submit.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
top: 50%;
left: 50%;
margin-left: -10px;
margin-top: -10px;
border: 2px solid #ffffff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.btn-submit:active {
transform: translateY(0);
}
/* Подвал формы */
.auth-footer {
text-align: center;
margin-top: 1.25rem;
padding-top: 1.25rem;
border-top: 1px solid #e2e8f0;
}
.auth-footer p {
color: #64748b;
font-size: 0.9rem;
margin: 0;
}
.auth-footer a {
color: #d4af37;
text-decoration: none;
font-weight: 600;
transition: color 0.2s ease;
}
.auth-footer a:hover {
color: #b8941f;
}
/* Адаптивность */
@media (max-width: 480px) {
.auth-page {
padding: 5rem 1rem 1.5rem;
}
.auth-card {
padding: 1.5rem 1rem;
}
.auth-header h1 {
font-size: 1.35rem;
}
.auth-header p {
font-size: 0.85rem;
}
.auth-form {
gap: 0.4rem;
}
.form-group {
gap: 0.15rem;
}
.form-group label {
font-size: 0.8rem;
}
.form-group label .hint {
font-size: 0.7rem;
}
.form-group input {
padding: 0.5rem 0.65rem;
font-size: 0.85rem;
}
.form-group input::placeholder {
font-size: 0.8rem;
}
.password-wrapper input {
padding-right: 2.25rem;
}
.toggle-password {
right: 0.5rem;
}
.toggle-password svg {
width: 18px;
height: 18px;
}
.error-message {
font-size: 0.7rem;
}
.form-options {
font-size: 0.8rem;
}
.checkbox-label {
gap: 0.4rem;
}
.checkbox-label input[type="checkbox"] {
width: 14px;
height: 14px;
margin-top: 1px;
}
.checkbox-label span {
font-size: 0.75rem;
line-height: 1.4;
}
.privacy-link-btn {
font-size: 0.75rem;
}
.checkbox-label .privacy-link {
font-size: 0.7rem;
}
.btn-submit {
padding: 0.65rem;
font-size: 0.9rem;
margin-top: 0.2rem;
}
}
@media (max-width: 375px) {
.auth-page {
padding: 4.5rem 0.75rem 1rem;
}
.auth-card {
padding: 1.25rem 0.75rem;
border-radius: 12px;
}
.auth-header h1 {
font-size: 1.25rem;
}
.form-group input {
padding: 0.45rem 0.55rem;
font-size: 0.8rem;
border-radius: 6px;
}
.form-group label {
font-size: 0.75rem;
}
.btn-submit {
padding: 0.55rem;
font-size: 0.85rem;
border-radius: 6px;
}
.checkbox-label span {
font-size: 0.7rem;
}
}
@media (max-width: 320px) {
.auth-page {
padding: 4rem 0.5rem 0.75rem;
}
.auth-card {
padding: 1rem 0.5rem;
}
.auth-header h1 {
font-size: 1.15rem;
}
.form-group input {
padding: 0.4rem 0.5rem;
font-size: 0.75rem;
}
.btn-submit {
padding: 0.5rem;
font-size: 0.8rem;
}
}
</style>
<script>
const firstNameInput = document.getElementById('firstName') as HTMLInputElement;
const lastNameInput = document.getElementById('lastName') as HTMLInputElement;
const emailInput = document.getElementById('email') as HTMLInputElement;
const phoneInput = document.getElementById('phone') as HTMLInputElement;
const passwordInput = document.getElementById('password') as HTMLInputElement;
const confirmPasswordInput = document.getElementById('confirmPassword') as HTMLInputElement;
function showError(input: HTMLInputElement, message: string) {
const errorSpan = document.getElementById(`${input.id}-error`);
if (errorSpan) {
errorSpan.textContent = message;
}
input.classList.add('error');
}
function clearError(input: HTMLInputElement) {
const errorSpan = document.getElementById(`${input.id}-error`);
if (errorSpan) {
errorSpan.textContent = '';
}
input.classList.remove('error');
}
function validateName(value: string): boolean {
const regex = /^[а-яА-ЯёЁ\s\-]+$/;
return regex.test(value);
}
function validateFirstName(value: string): boolean {
return /^[а-яА-ЯёЁ\-]+$/.test(value);
}
function validateLastName(value: string): boolean {
return /^[а-яА-ЯёЁ\-]+$/.test(value);
}
function validateEmail(value: string): boolean {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(value);
}
function validatePhone(value: string): boolean {
const cleaned = value.replace(/[\s\-\(\)]/g, '');
const regex = /^\+7\d{10}$/;
return regex.test(cleaned);
}
function validatePassword(value: string): boolean {
return value.length >= 8 && value.length <= 12;
}
document.querySelectorAll('.toggle-password').forEach(button => {
button.addEventListener('click', () => {
const targetId = button.getAttribute('data-target');
if (!targetId) return;
const input = document.getElementById(targetId) as HTMLInputElement;
if (input) {
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
button.classList.toggle('active', isPassword);
}
});
});
firstNameInput?.addEventListener('input', () => {
const value = firstNameInput.value.replace(/[^\w\u0400-\u04FF\-]/gi, '');
firstNameInput.value = value;
const trimmed = value ? value.trim() : '';
if (trimmed.length > 0 && !validateFirstName(trimmed)) {
showError(firstNameInput, 'Используйте только русские буквы');
} else {
clearError(firstNameInput);
}
});
lastNameInput?.addEventListener('input', () => {
const value = lastNameInput.value.replace(/[^\w\u0400-\u04FF\-]/gi, '');
lastNameInput.value = value;
const trimmed = value ? value.trim() : '';
if (trimmed.length > 0 && !validateLastName(trimmed)) {
showError(lastNameInput, 'Используйте только русские буквы');
} else {
clearError(lastNameInput);
}
});
emailInput?.addEventListener('input', () => {
const value = emailInput.value.replace(/[^\w@\.\-]/g, '');
emailInput.value = value;
if (emailInput.value && !validateEmail(emailInput.value)) {
showError(emailInput, 'Введите корректный email');
} else {
clearError(emailInput);
}
});
phoneInput?.addEventListener('focus', () => {
if (!phoneInput.value || !phoneInput.value.startsWith('+7')) {
phoneInput.value = '+7';
}
});
phoneInput?.addEventListener('input', () => {
const digits = phoneInput.value.replace(/[^\d]/g, '');
const limitedDigits = digits.slice(0, 10);
let formatted = '+7';
if (limitedDigits.length > 0) {
formatted += ' ' + limitedDigits.slice(0, 3);
}
if (limitedDigits.length > 3) {
formatted += '-' + limitedDigits.slice(3, 6);
}
if (limitedDigits.length > 6) {
formatted += '-' + limitedDigits.slice(6, 8);
}
if (limitedDigits.length > 8) {
formatted += '-' + limitedDigits.slice(8, 10);
}
phoneInput.value = formatted;
if (phoneInput.value && !validatePhone(phoneInput.value)) {
showError(phoneInput, 'Введите 10 цифр номера');
} else {
clearError(phoneInput);
}
});
passwordInput?.addEventListener('input', () => {
if (passwordInput.value.length > 12) {
passwordInput.value = passwordInput.value.slice(0, 12);
}
if (passwordInput.value && !validatePassword(passwordInput.value)) {
showError(passwordInput, 'Пароль должен быть 8-12 символов');
} else {
clearError(passwordInput);
}
});
confirmPasswordInput?.addEventListener('input', () => {
if (confirmPasswordInput.value && passwordInput.value !== confirmPasswordInput.value) {
showError(confirmPasswordInput, 'Пароли не совпадают');
} else {
clearError(confirmPasswordInput);
}
});
document.getElementById('sign-up-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const formData = new FormData(form);
const honeypot = formData.get('website_url') as string;
if (honeypot && honeypot.trim() !== '') {
alert('Доступ запрещён');
return;
}
const firstName = formData.get('firstName') as string;
const lastName = formData.get('lastName') as string;
const email = (formData.get('email') as string) || '';
const phone = formData.get('phone') as string;
const password = formData.get('password') as string;
const confirmPassword = formData.get('confirmPassword') as string;
let hasErrors = false;
if (!firstName || typeof firstName !== 'string' || !firstName.trim()) {
showError(firstNameInput, 'Введите имя');
hasErrors = true;
} else if (!validateFirstName(firstName)) {
showError(firstNameInput, 'Введите имя (только русские буквы)');
hasErrors = true;
}
if (!lastName || typeof lastName !== 'string' || !lastName.trim()) {
showError(lastNameInput, 'Введите фамилию');
hasErrors = true;
} else if (!validateLastName(lastName)) {
showError(lastNameInput, 'Введите фамилию (только русские буквы)');
hasErrors = true;
}
if (!validateEmail(email)) {
showError(emailInput, 'Введите корректный email');
hasErrors = true;
}
if (!validatePhone(phone)) {
showError(phoneInput, 'Введите корректный номер телефона');
hasErrors = true;
}
if (!validatePassword(password)) {
showError(passwordInput, 'Пароль должен быть от 8 до 12 символов');
hasErrors = true;
}
if (password !== confirmPassword) {
showError(confirmPasswordInput, 'Пароли не совпадают');
hasErrors = true;
}
if (hasErrors) {
return;
}
// Отправка данных на сервер
const submitBtn = form.querySelector('.btn-submit') as HTMLButtonElement;
submitBtn.disabled = true;
submitBtn.classList.add('loading');
try {
const response = await fetch('/api/auth/sign-up', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ firstName, lastName, email, phone, password }),
});
const result = await response.json();
if (result.success) {
// Показываем success message и скрываем footer
form.innerHTML = `
<div class="success-message" style="text-align: center; padding: 1rem 0;">
<div class="success-icon" style="width: 60px; height: 60px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 1.5rem;">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="white" 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>
<h3 style="color: #1e3050; font-size: 1.5rem; font-weight: 700; margin: 0 0 1rem;">Регистрация успешна!</h3>
<p style="color: #64748b; font-size: 1rem; line-height: 1.6; margin: 0 0 0.75rem;">На ваш email <strong>${email || ''}</strong> отправлена ссылка для подтверждения регистрации.</p>
<p style="color: #94a3b8; font-size: 0.875rem;">Проверьте почту и перейдите по ссылке для активации аккаунта.</p>
</div>
`;
// Скрываем footer с ссылкой на вход
const authFooter = document.querySelector('.auth-footer') as HTMLElement;
if (authFooter) {
authFooter.style.display = 'none';
}
} else {
if (result.error && result.error.includes('Пароль')) {
showError(passwordInput, result.error);
} else {
showError(emailInput, result.error || 'Ошибка регистрации');
}
submitBtn.disabled = false;
submitBtn.classList.remove('loading');
}
} catch (err) {
showError(emailInput, 'Ошибка соединения');
submitBtn.disabled = false;
submitBtn.classList.remove('loading');
}
});
// Модальное окно политики
const privacyModal = document.getElementById('privacy-modal');
const privacyTrigger = document.getElementById('privacy-modal-trigger');
const privacyClose = document.getElementById('privacy-modal-close');
const privacyAccept = document.getElementById('privacy-modal-accept');
function openPrivacyModal() {
privacyModal?.classList.add('open');
document.body.style.overflow = 'hidden';
}
function closePrivacyModal() {
privacyModal?.classList.remove('open');
document.body.style.overflow = '';
}
privacyTrigger?.addEventListener('click', (e) => {
e.preventDefault();
openPrivacyModal();
});
privacyClose?.addEventListener('click', closePrivacyModal);
privacyAccept?.addEventListener('click', closePrivacyModal);
privacyModal?.addEventListener('click', (e) => {
if (e.target === privacyModal) {
closePrivacyModal();
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closePrivacyModal();
}
});
</script>