Reflex/src/views/RegisterView.vue
Professional 90e26544db фикс
2025-05-25 20:54:05 +07:00

1000 lines
24 KiB
Vue
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.

<template>
<div class="register-container">
<div class="auth-bg">
<div class="brand-logo">
<span class="logo-letter">R</span>
<span class="logo-letter">E</span>
<span class="logo-letter">F</span>
<span class="logo-letter">L</span>
<span class="logo-letter">E</span>
<span class="logo-letter">X</span>
</div>
<div class="register-card">
<div class="register-header">
<h2>Создать аккаунт</h2>
<p>Начните свой путь к новым знакомствам</p>
</div>
<form @submit.prevent="handleRegister" class="register-form">
<div class="form-group">
<label for="name">
<i class="bi-person"></i>
Имя
</label>
<input
type="text"
id="name"
v-model="name"
placeholder="Введите ваше имя"
required
:disabled="loading"
/>
</div>
<div class="form-group">
<label for="email">
<i class="bi-envelope"></i>
Email
</label>
<input
type="email"
id="email"
v-model="email"
placeholder="Введите ваш email"
required
:disabled="loading"
/>
</div>
<div class="form-group">
<label for="birthdate">
<i class="bi-calendar"></i>
Дата рождения
</label>
<div class="date-input-container">
<input
type="date"
id="birthdate"
v-model="birthdate"
required
:disabled="loading"
:max="maxDate"
class="date-input"
ref="birthdateInput"
@click="openDatePicker"
/>
</div>
</div>
<div class="form-group">
<label for="city">
<i class="bi-geo-alt"></i>
Город
</label>
<div class="select-wrapper">
<input
type="text"
class="form-input"
id="city"
v-model="citySearchQuery"
placeholder="Начните вводить название города..."
@focus="showCityList = true"
@input="onCitySearch"
required
:disabled="loading"
/>
<button
type="button"
class="clear-btn"
@click="clearCitySelection"
v-if="city"
:disabled="loading"
>
<i class="bi-x"></i>
</button>
<div class="dropdown" v-if="showCityList && filteredCities.length > 0">
<div
v-for="cityItem in filteredCities"
:key="cityItem"
class="dropdown-option"
@click="selectCity(cityItem)"
>
{{ cityItem }}
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="password">
<i class="bi-shield-lock"></i>
Пароль
</label>
<input
type="password"
id="password"
v-model="password"
placeholder="Создайте надежный пароль"
required
:disabled="loading"
/>
</div>
<div class="form-group">
<label for="confirmPassword">
<i class="bi-shield-check"></i>
Подтверждение пароля
</label>
<input
type="password"
id="confirmPassword"
v-model="confirmPassword"
placeholder="Повторите пароль"
required
:disabled="loading"
/>
</div>
<div v-if="errorMessage" class="error-message">
<i class="bi-exclamation-circle"></i>
{{ errorMessage }}
</div>
<button type="submit" class="action-button" :disabled="loading">
<span v-if="loading" class="spinner"></span>
<span v-else class="btn-text">Зарегистрироваться</span>
</button>
<div class="auth-footer">
<p>
Уже есть аккаунт?
<router-link to="/login" class="auth-link">Войти</router-link>
</p>
</div>
</form>
</div>
<router-link to="/" class="back-link">
<i class="bi-arrow-left"></i>
Вернуться
</router-link>
<div class="wave-container">
<div class="wave"></div>
<div class="wave"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { useAuth } from '@/auth';
import russianCities from '@/assets/russian-cities.json';
const name = ref('');
const email = ref('');
const birthdate = ref('');
const city = ref('');
const password = ref('');
const confirmPassword = ref('');
const errorMessage = ref('');
const loading = ref(false);
const birthdateInput = ref(null);
const cities = ref([]);
const citySearchQuery = ref('');
const showCityList = ref(false);
const filteredCities = ref([]);
// Функция для открытия календаря при клике на поле
const openDatePicker = () => {
if (birthdateInput.value && typeof birthdateInput.value.showPicker === 'function') {
birthdateInput.value.showPicker();
}
};
// Вычисляем максимальную дату (минимальный возраст 14 лет)
const maxDate = computed(() => {
const date = new Date();
date.setFullYear(date.getFullYear() - 14);
return date.toISOString().split('T')[0];
});
const { register } = useAuth();
// Функция для блокировки прокрутки страницы
const preventScroll = () => {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.height = '100%';
document.body.style.width = '100%';
};
// Функция для восстановления прокрутки
const enableScroll = () => {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.height = '';
document.body.style.width = '';
};
onMounted(() => {
preventScroll(); // Блокируем прокрутку при монтировании компонента
// Сортируем города по алфавиту
cities.value = russianCities.sort((a, b) => {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
});
});
onUnmounted(() => {
enableScroll(); // Восстанавливаем прокрутку при размонтировании компонента
});
const handleRegister = async () => {
errorMessage.value = '';
loading.value = true;
// Простая валидация на клиенте
if (!name.value || !email.value || !birthdate.value || !city.value || !password.value || !confirmPassword.value) {
errorMessage.value = 'Пожалуйста, заполните все обязательные поля.';
loading.value = false;
return;
}
if (password.value !== confirmPassword.value) {
errorMessage.value = 'Пароли не совпадают.';
loading.value = false;
return;
}
if (password.value.length < 6) {
errorMessage.value = 'Пароль должен быть не менее 6 символов.';
loading.value = false;
return;
}
// Проверяем возраст (минимум 14 лет)
const birthDate = new Date(birthdate.value);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
if (age < 14) {
errorMessage.value = 'Вам должно быть не менее 14 лет для регистрации.';
loading.value = false;
return;
}
try {
const userData = {
name: name.value,
email: email.value,
dateOfBirth: birthdate.value, // Изменено с birthdate на dateOfBirth для соответствия бэкенду
city: city.value, // Добавлено поле города
password: password.value
};
// Для отладки
console.log('Отправка данных регистрации:', { ...userData, password: '****' });
await register(userData);
} catch (error) {
errorMessage.value = error.message || 'Произошла неизвестная ошибка при регистрации.';
} finally {
loading.value = false;
}
};
const onCitySearch = () => {
if (!citySearchQuery.value) {
filteredCities.value = [];
return;
}
const query = citySearchQuery.value.toLowerCase();
filteredCities.value = cities.value
.map(cityItem => cityItem.name) // Получаем только названия городов
.filter(cityName => cityName.toLowerCase().includes(query)) // Фильтруем по запросу
.slice(0, 10); // Ограничиваем количество результатов
};
const selectCity = (cityName) => {
city.value = cityName;
citySearchQuery.value = cityName; // Устанавливаем значение в поле ввода
showCityList.value = false; // Скрываем список городов
};
const clearCitySelection = () => {
city.value = '';
citySearchQuery.value = '';
filteredCities.value = [];
};
</script>
<style scoped>
.register-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
touch-action: none;
color: white; /* Явно задаем белый цвет текста для всего контейнера */
}
.auth-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0;
overflow: hidden;
}
.brand-logo {
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: 800;
letter-spacing: 1px;
margin-bottom: 1rem;
flex-shrink: 0;
}
.logo-letter {
display: inline-block;
animation: gradientShift 8s infinite;
background: linear-gradient(45deg, #ffffff, #00bfff, #9932cc, #ff4500);
background-size: 300% 300%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow:
-1px -1px 0 rgba(255, 255, 255, 0.3),
1px -1px 0 rgba(255, 255, 255, 0.3),
-1px 1px 0 rgba(255, 255, 255, 0.3),
1px 1px 0 rgba(255, 255, 255, 0.3),
0 0 8px rgba(255, 255, 255, 0.5);
}
.logo-letter:nth-child(1) { animation-delay: 0s; }
.logo-letter:nth-child(2) { animation-delay: 0.25s; }
.logo-letter:nth-child(3) { animation-delay: 0.5s; }
.logo-letter:nth-child(4) { animation-delay: 0.75s; }
.logo-letter:nth-child(5) { animation-delay: 1s; }
.logo-letter:nth-child(6) { animation-delay: 1.25s; }
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.register-card {
width: 90%;
max-width: 400px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
text-align: center;
position: relative;
z-index: 10;
margin: 0 auto;
display: flex;
flex-direction: column;
color: white; /* Явно задаем белый цвет текста */
max-height: 90vh;
overflow-y: auto;
}
.register-header {
margin-bottom: 1rem;
color: white; /* Явно задаем белый цвет текста для заголовка */
}
.register-header h2 {
font-size: 1.8rem;
margin-bottom: 0.3rem;
color: white; /* Явно задаем белый цвет текста для h2 */
font-weight: 600;
}
.register-header p {
font-size: 1rem;
color: rgba(255, 255, 255, 0.9); /* Полупрозрачный белый для подзаголовка */
margin-bottom: 1.5rem;
}
.register-form {
flex: 1;
display: flex;
flex-direction: column;
}
.form-group {
margin-bottom: 0.8rem;
position: relative;
text-align: left; /* Выравниваем текст полей формы по левому краю */
}
.form-group label {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.4rem;
color: white;
font-weight: 500;
font-size: 1rem;
}
.form-group label i {
font-size: 1.1rem;
}
.form-group input {
width: 100%;
padding: 0.9rem 1rem;
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
color: white;
font-size: 1rem;
transition: all 0.3s ease;
}
/* Стили для поля даты */
.date-input {
color-scheme: dark; /* Темная тема для календаря */
}
.date-input::-webkit-calendar-picker-indicator {
filter: invert(1); /* Инвертируем цвет иконки календаря для темной темы */
}
.form-group input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.form-group input:focus {
outline: none;
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.5);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
}
.select-wrapper {
position: relative;
width: 100%;
}
.select-wrapper input {
width: 100%;
padding: 0.9rem 1rem;
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
color: white;
font-size: 1rem;
transition: all 0.3s ease;
}
.clear-btn {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
background: transparent;
border: none;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 1.1rem;
display: flex;
align-items: center;
justify-content: center;
}
.clear-btn:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
max-height: 200px;
overflow-y: auto;
z-index: 100;
margin-top: 0.2rem;
}
.dropdown-option {
padding: 0.8rem 1rem;
cursor: pointer;
transition: background 0.3s ease;
}
.dropdown-option:hover {
background: rgba(255, 255, 255, 0.2);
}
.action-button {
width: 100%;
padding: 1rem;
margin-top: 1rem;
background: #00BFFF;
border: none;
border-radius: 50px; /* Более закругленная кнопка как на скриншоте */
color: white;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
}
.action-button:before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: all 0.5s ease;
}
.action-button:hover:before {
left: 100%;
}
.action-button:hover {
transform: translateY(-3px);
box-shadow: 0 7px 15px rgba(0, 191, 255, 0.3);
}
.action-button:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.spinner {
width: 1.4rem;
height: 1.4rem;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top: 3px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.auth-footer {
margin-top: 1.5rem;
color: white;
font-size: 0.95rem;
}
.auth-link {
color: #20B2AA;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
}
.auth-link:hover {
color: white;
text-decoration: underline;
}
.error-message {
background: rgba(255, 92, 92, 0.2);
color: white;
padding: 1rem;
border-radius: 10px;
margin-top: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.95rem;
}
.error-message i {
font-size: 1.1rem;
color: #ffcccc;
}
.back-link {
position: absolute;
top: 10px;
left: 10px;
color: white;
text-decoration: none;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
transition: all 0.3s ease;
z-index: 10;
font-size: 0.9rem;
padding: 6px 10px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 50px;
backdrop-filter: blur(5px);
}
.back-link:hover {
transform: translateX(-5px);
background-color: rgba(255, 255, 255, 0.2);
}
.wave-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 150px;
overflow: hidden;
}
.wave {
position: absolute;
bottom: 0;
left: 0;
width: 300%; /* Увеличиваем ширину для сглаживания краёв */
height: 150px;
background: url('data:image/svg+xml;utf8,<svg viewBox="0 0 1200 120" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 0v46.29c47.79 22.2 103.59 32.17 158 28 70.36-5.37 136.33-33.31 206.8-37.5 73.84-4.36 147.54 16.88 218.2 35.26 69.27 18 138.3 24.88 209.4 13.08 36.15-6 69.85-17.84 104.45-29.34C989.49 25 1113-14.29 1200 52.47V0z" opacity=".25" fill="white"/><path d="M0 0v15.81c13 21.11 27.64 41.05 47.69 56.24C99.41 111.27 165 111 224.58 91.58c31.15-10.15 60.09-26.07 89.67-39.8 40.92-19 84.73-46 130.83-49.67 36.26-2.85 70.9 9.42 98.6 31.56 31.77 25.39 62.32 62 103.63 73 40.44 10.79 81.35-6.69 119.13-24.28s75.16-39 116.92-43.05c59.73-5.85 113.28 22.88 168.9 38.84 30.2 8.66 59 6.17 87.09-7.5 22.43-10.89 48-26.93 60.65-49.24V0z" opacity=".5" fill="white"/><path d="M0 0v5.63C149.93 59 314.09 71.32 475.83 42.57c43-7.64 84.23-20.12 127.61-26.46 59-8.63 112.48 12.24 165.56 35.4C827.93 77.22 886 95.24 951.2 90c86.53-7 172.46-45.71 248.8-84.81V0z" fill="white"/></svg>') repeat-x;
background-size: 1200px 150px;
transform: translateX(0) rotate(180deg); /* Начинаем с нулевого положения */
animation: register-wave 30s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite; /* Плавная бесконечная анимация */
opacity: 0.6;
}
.wave:nth-child(2) {
bottom: 10px;
animation: register-wave 36s cubic-bezier(0.36, 0.45, 0.63, 0.53) reverse infinite;
opacity: 0.3;
}
@keyframes register-wave {
0% { transform: translateX(0) rotate(180deg); }
100% { transform: translateX(-66.66%) rotate(180deg); } /* Анимируем точно на 2/3 ширины для плавного цикла */
}
/* Адаптивность */
@media (max-width: 768px) {
.register-card {
padding: 2rem 1.5rem;
max-height: 85vh;
}
.register-header h2 {
font-size: 1.8rem;
}
.brand-logo {
font-size: 2rem;
margin-bottom: 2rem;
}
.back-link {
top: 1.5rem;
left: 1.5rem;
font-size: 0.9rem;
}
.wave-container {
height: 120px;
}
.wave {
height: 120px;
background-size: 1200px 120px;
transform: rotate(180deg);
}
}
@media (max-width: 576px) {
.brand-logo {
font-size: 1.8rem;
margin-top: 2rem;
margin-bottom: 1rem;
}
.register-card {
width: 94%;
padding: 1.5rem 1.2rem;
margin: 0;
max-height: 80vh;
}
.register-header h2 {
font-size: 1.6rem;
}
.register-header p {
font-size: 0.9rem;
margin-bottom: 1rem;
}
}
@media (max-width: 480px) {
.brand-logo {
font-size: 1.6rem;
margin-bottom: 0.8rem;
margin-top: 1.8rem;
}
.register-card {
padding: 1.3rem 1.1rem;
width: 95%;
max-height: 75vh;
}
.register-header h2 {
font-size: 1.4rem;
}
.register-header p {
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 0.7rem;
}
.form-group input {
padding: 0.7rem 0.9rem;
font-size: 0.95rem;
}
.action-button {
padding: 0.8rem;
font-size: 0.95rem;
}
}
@media (max-height: 700px) {
.brand-logo {
font-size: 1.6rem;
margin-bottom: 0.7rem;
margin-top: 0.7rem;
}
.register-header {
margin-bottom: 0.7rem;
}
.register-header h2 {
font-size: 1.5rem;
margin-bottom: 0.2rem;
}
.register-header p {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.form-group {
margin-bottom: 0.5rem;
}
.form-group label {
margin-bottom: 0.2rem;
font-size: 0.9rem;
}
.form-group input {
padding: 0.6rem 0.8rem;
font-size: 0.9rem;
}
.action-button {
padding: 0.7rem;
margin-top: 0.7rem;
font-size: 1rem;
}
.auth-footer {
margin-top: 0.7rem;
font-size: 0.9rem;
}
}
@media (max-height: 640px) {
.brand-logo {
font-size: 1.4rem;
margin-bottom: 0.5rem;
margin-top: 0.5rem;
}
.register-card {
padding: 1rem;
max-height: calc(100vh - 60px);
}
.register-header h2 {
font-size: 1.2rem;
margin-bottom: 0.1rem;
}
.register-header p {
font-size: 0.8rem;
margin-bottom: 0.3rem;
}
.form-group {
margin-bottom: 0.4rem;
}
.form-group label {
font-size: 0.8rem;
margin-bottom: 0.1rem;
}
.form-group input {
padding: 0.5rem 0.6rem;
font-size: 0.85rem;
}
.action-button {
padding: 0.5rem;
font-size: 0.85rem;
margin-top: 0.5rem;
}
}
@media (max-height: 570px) {
.brand-logo {
font-size: 1.2rem;
margin-bottom: 0.3rem;
margin-top: 0.3rem;
}
.register-card {
padding: 0.8rem;
max-height: calc(100vh - 40px);
}
.register-header h2 {
font-size: 1.1rem;
margin-bottom: 0;
}
.register-header p {
font-size: 0.75rem;
margin-bottom: 0.2rem;
}
.form-group {
margin-bottom: 0.25rem;
}
.form-group label {
font-size: 0.75rem;
}
.form-group input {
padding: 0.4rem 0.5rem;
font-size: 0.8rem;
}
.action-button {
padding: 0.4rem;
font-size: 0.8rem;
margin-top: 0.3rem;
}
.auth-footer {
font-size: 0.75rem;
margin-top: 0.3rem;
}
.wave-container {
height: 80px; /* Уменьшаем высоту волн */
}
.wave {
height: 80px;
background-size: 1200px 80px;
}
}
@media (max-height: 480px) {
.brand-logo {
font-size: 1rem;
margin-bottom: 0.2rem;
margin-top: 0.2rem;
}
.register-card {
padding: 0.6rem;
max-height: calc(100vh - 30px);
}
.register-header h2 {
font-size: 1rem;
margin-bottom: 0;
}
.register-header p {
font-size: 0.7rem;
margin-bottom: 0.2rem;
}
.form-group {
margin-bottom: 0.2rem;
}
.form-group label {
font-size: 0.7rem;
}
.form-group input {
padding: 0.35rem 0.4rem;
font-size: 0.75rem;
}
.action-button {
padding: 0.35rem;
font-size: 0.75rem;
margin-top: 0.2rem;
}
.auth-footer {
font-size: 0.7rem;
margin-top: 0.2rem;
}
.wave-container {
display: none; /* Скрываем волны на очень маленьких экранах */
}
}
/* Исправления Safari */
@supports (-webkit-touch-callout: none) {
.logo-letter {
background-clip: text;
-webkit-text-fill-color: transparent;
}
.date-input::-webkit-calendar-picker-indicator {
background-color: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
}
</style>