/
varsl
/
HTML-CSS
Обзор
Документация
Войти
/
varsl
/
HTML-CSS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/profile.html
1 509 строк
60 KB
varsl
Добавление файлов фронтеда
21 дек 2025, 23:11
21 дек 2025, 23:11
7b4bf01
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Профиль - Зоопарк Дикий Мир</title> <link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="css/animations.css"> <link rel="stylesheet" href="css/dashboard.css"> <link rel="stylesheet" href="libs/all.min.css"> <style> .profile-section { padding: 2rem 0; } .profile-header { display: flex; align-items: center; gap: 2rem; margin-bottom: 3rem; background: var(--white); border-radius: var(--border-radius); padding: 2rem; box-shadow: var(--shadow); } .profile-avatar { position: relative; } .avatar-image { width: 150px; height: 150px; border-radius: 50%; overflow: hidden; border: 4px solid var(--white); box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .avatar-image img { width: 100%; height: 100%; object-fit: cover; } .avatar-upload { position: absolute; bottom: 10px; right: 10px; width: 40px; height: 40px; border-radius: 50%; background: var(--primary-color); color: var(--white); display: flex; align-items: center; justify-content: center; cursor: pointer; border: 2px solid var(--white); } .profile-info { flex: 1; } .profile-name { font-size: 2rem; margin-bottom: 0.5rem; color: var(--text-color); } .profile-email { color: var(--text-light); margin-bottom: 1rem; font-size: 1.1rem; } .profile-bio { color: var(--text-color); margin-bottom: 1.5rem; font-style: italic; } .profile-meta { display: flex; gap: 2rem; margin-top: 1rem; } .meta-item { display: flex; align-items: center; gap: 0.5rem; } .meta-icon { width: 40px; height: 40px; border-radius: 50%; background: var(--bg-color); display: flex; align-items: center; justify-content: center; color: var(--primary-color); } .meta-content h4 { font-size: 1.5rem; margin-bottom: 0.25rem; } .meta-content p { color: var(--text-light); font-size: 0.9rem; } .profile-tabs { margin-bottom: 2rem; } .tab-buttons { display: flex; border-bottom: 2px solid #eee; overflow-x: auto; } .tab-btn { padding: 1rem 2rem; background: none; border: none; font-size: 1rem; color: var(--text-color); cursor: pointer; position: relative; white-space: nowrap; } .tab-btn.active { color: var(--primary-color); } .tab-btn.active::after { content: ''; position: absolute; bottom: -2px; left: 0; right: 0; height: 2px; background: var(--primary-color); } .tab-content { display: none; animation: fadeIn 0.5s ease; } .tab-content.active { display: block; } .profile-form { background: var(--white); border-radius: var(--border-radius); padding: 2rem; box-shadow: var(--shadow); margin-bottom: 2rem; } .form-section { margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid #eee; } .form-section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } .form-section h3 { font-size: 1.3rem; margin-bottom: 1.5rem; color: var(--text-color); display: flex; align-items: center; gap: 0.5rem; } .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; } .form-group { margin-bottom: 1.5rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; color: var(--text-color); } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 0.75rem 1rem; border: 1px solid #ddd; border-radius: var(--border-radius); font-size: 1rem; transition: var(--transition); } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(46, 125, 50, 0.1); } .form-group textarea { resize: vertical; min-height: 100px; } .form-actions { display: flex; gap: 1rem; margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #eee; } .form-hint { font-size: 0.85rem; color: var(--text-light); margin-top: 0.5rem; } .preferences-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; } .preference-item { background: #f8f9fa; border-radius: var(--border-radius); padding: 1rem; border: 1px solid #eee; } .preference-item label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; margin: 0; } .badge-list { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 1rem; } .badge { display: flex; align-items: center; gap: 0.5rem; background: var(--white); border-radius: 50px; padding: 0.5rem 1rem; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .badge-icon { width: 30px; height: 30px; border-radius: 50%; background: var(--primary-color); color: var(--white); display: flex; align-items: center; justify-content: center; } .badge.locked { opacity: 0.5; } .progress-stats { margin-top: 1.5rem; } .progress-item { margin-bottom: 1.5rem; } .progress-header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.9rem; } .progress-bar { height: 8px; background: #e9ecef; border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, var(--primary-color), var(--primary-dark)); border-radius: 4px; transition: width 0.3s ease; } .danger-zone { background: #fff5f5; border: 1px solid #feb2b2; border-radius: var(--border-radius); padding: 2rem; margin-top: 2rem; } .danger-zone h3 { color: #c53030; margin-bottom: 1rem; } .danger-zone p { color: #718096; margin-bottom: 1.5rem; } .loading-spinner { display: flex; justify-content: center; align-items: center; height: 200px; font-size: 1.2rem; color: var(--text-light); } .loading-spinner i { margin-right: 10px; } @media (max-width: 768px) { .profile-header { flex-direction: column; text-align: center; gap: 1rem; } .profile-meta { flex-direction: column; gap: 1rem; } .tab-buttons { flex-wrap: wrap; } .tab-btn { flex: 1; min-width: 120px; padding: 0.75rem 1rem; } .form-grid { grid-template-columns: 1fr; } } </style> </head> <body> <div class="page-wrapper"> <header class="header"> <nav class="navbar container"> <div class="logo"> <i class="fas fa-paw"></i> <span>Зоопарк Дикий Мир</span> </div> <button class="menu-toggle" id="menuToggle"> <i class="fas fa-bars"></i> </button> <ul class="nav-menu" id="navMenu"> <li><a href="index.html"><i class="fas fa-home"></i> Главная</a></li> <li><a href="about.html"><i class="fas fa-info-circle"></i> О нас</a></li> <li><a href="animals.html"><i class="fas fa-dove"></i> Животные</a></li> <li><a href="catalog.html"><i class="fas fa-th-list"></i> Каталог</a></li> <li><a href="prices.html"><i class="fas fa-tag"></i> Цены</a></li> <li><a href="ticket.html"><i class="fas fa-ticket-alt"></i> Билеты</a></li> <li><a href="excursion.html"><i class="fas fa-calendar-alt"></i> Экскурсии</a></li> <li><a href="contacts.html"><i class="fas fa-address-book"></i> Контакты</a></li> <li><a href="map.html"><i class="fas fa-map-marked-alt"></i> Карта</a></li> <li class="auth-buttons"> <!-- Кнопки входа/выхода будут загружены через JavaScript --> </li> </ul> </nav> </header> <div class="dashboard-container"> <aside class="dashboard-sidebar"> <div class="sidebar-header"> <div class="user-avatar" id="userAvatar"> <i class="fas fa-user"></i> </div> <h3 class="user-name" id="userName">Загрузка...</h3> <p class="user-email" id="userEmail">...</p> </div> <nav class="sidebar-menu"> <a href="dashboard.html" class="menu-item"> <i class="fas fa-chart-line"></i> <span>Дашборд</span> </a> <a href="profile.html" class="menu-item active"> <i class="fas fa-user"></i> <span>Профиль</span> </a> <a href="ticket.html" class="menu-item"> <i class="fas fa-ticket-alt"></i> <span>Мои билеты</span> </a> <a href="excursion.html" class="menu-item"> <i class="fas fa-calendar-alt"></i> <span>Мои экскурсии</span> </a> <a href="animals.html" class="menu-item"> <i class="fas fa-heart"></i> <span>Любимые животные</span> </a> <a href="#" class="menu-item" id="historyBtn"> <i class="fas fa-history"></i> <span>История посещений</span> </a> </nav> </aside> <main class="dashboard-content"> <section class="profile-section"> <div class="profile-header" id="profileHeader"> <div class="profile-avatar"> <div class="avatar-image" id="avatarImage"> <img src="https://via.placeholder.com/150" alt="Аватар" id="avatarImg"> </div> <div class="avatar-upload" id="avatarUploadBtn"> <i class="fas fa-camera"></i> </div> </div> <div class="profile-info"> <h1 class="profile-name" id="profileName">Загрузка...</h1> <p class="profile-email" id="profileEmail">...</p> <p class="profile-bio" id="profileBio"></p> <div class="profile-meta"> <div class="meta-item"> <div class="meta-icon"> <i class="fas fa-ticket-alt"></i> </div> <div class="meta-content"> <h4 id="ticketsCount">0</h4> <p>Билетов куплено</p> </div> </div> <div class="meta-item"> <div class="meta-icon"> <i class="fas fa-calendar-check"></i> </div> <div class="meta-content"> <h4 id="visitsCount">0</h4> <p>Посещений</p> </div> </div> <div class="meta-item"> <div class="meta-icon"> <i class="fas fa-wallet"></i> </div> <div class="meta-content"> <h4 id="spendingAmount">0 ₽</h4> <p>Общие расходы</p> </div> </div> </div> </div> </div> <div class="profile-tabs"> <div class="tab-buttons"> <button class="tab-btn active" data-tab="personal"> <i class="fas fa-user-edit"></i> Личные данные </button> <button class="tab-btn" data-tab="security"> <i class="fas fa-shield-alt"></i> Безопасность </button> <button class="tab-btn" data-tab="preferences"> <i class="fas fa-sliders-h"></i> Настройки </button> <button class="tab-btn" data-tab="badges"> <i class="fas fa-award"></i> Достижения </button> </div> <div class="tab-content active" id="personalTab"> <form class="profile-form" id="personalForm"> <div class="loading-spinner" id="personalLoading"> <i class="fas fa-spinner fa-spin"></i> Загрузка данных... </div> <div class="form-content" id="personalFormContent" style="display: none;"> <div class="form-section"> <h3><i class="fas fa-user-circle"></i> Основная информация</h3> <div class="form-grid"> <div class="form-group"> <label for="firstName">Имя *</label> <input type="text" id="firstName" required> </div> <div class="form-group"> <label for="lastName">Фамилия *</label> <input type="text" id="lastName" required> </div> <div class="form-group"> <label for="email">Email *</label> <input type="email" id="email" required readonly style="background-color: #f5f5f5;"> <div class="form-hint">Email нельзя изменить</div> </div> <div class="form-group"> <label for="phone">Телефон</label> <input type="tel" id="phone" placeholder="+7 (999) 123-45-67"> </div> <div class="form-group"> <label for="birthDate">Дата рождения</label> <input type="date" id="birthDate"> </div> <div class="form-group"> <label for="city">Город</label> <input type="text" id="city" placeholder="Москва"> </div> </div> </div> <div class="form-section"> <h3><i class="fas fa-heart"></i> О себе</h3> <div class="form-group"> <label for="bio">Краткая биография</label> <textarea id="bio" placeholder="Расскажите о себе и своих интересах..."></textarea> </div> <div class="form-group"> <label for="interests">Интересы</label> <input type="text" id="interests" placeholder="Через запятую (например: животные, фотография, природа)"> </div> </div> <div class="form-actions"> <button type="button" class="btn btn-outline" id="cancelChanges"> Отменить </button> <button type="submit" class="btn btn-primary"> <i class="fas fa-save"></i> Сохранить изменения </button> </div> </div> </form> </div> <div class="tab-content" id="securityTab"> <form class="profile-form" id="securityForm"> <div class="form-section"> <h3><i class="fas fa-key"></i> Смена пароля</h3> <div class="form-grid"> <div class="form-group"> <label for="currentPassword">Текущий пароль *</label> <input type="password" id="currentPassword" required> </div> <div class="form-group"> <label for="newPassword">Новый пароль *</label> <input type="password" id="newPassword" required minlength="6"> <div class="form-hint">Пароль должен содержать минимум 6 символов</div> </div> <div class="form-group"> <label for="confirmPassword">Подтверждение пароля *</label> <input type="password" id="confirmPassword" required> </div> </div> </div> <div class="form-section"> <h3><i class="fas fa-shield-alt"></i> Безопасность аккаунта</h3> <div class="preferences-grid"> <div class="preference-item"> <label> <input type="checkbox" id="emailAlerts" checked> <span>Оповещения о входе по email</span> </label> </div> <div class="preference-item"> <label> <input type="checkbox" id="newsletter" checked> <span>Подписка на новости и акции</span> </label> </div> </div> </div> <div class="form-actions"> <button type="submit" class="btn btn-primary"> <i class="fas fa-save"></i> Обновить настройки безопасности </button> </div> </form> </div> <div class="tab-content" id="preferencesTab"> <form class="profile-form" id="preferencesForm"> <div class="form-section"> <h3><i class="fas fa-bell"></i> Уведомления</h3> <div class="preferences-grid"> <div class="preference-item"> <label> <input type="checkbox" id="notifyEmail" checked> <span>Email уведомления</span> </label> </div> <div class="preference-item"> <label> <input type="checkbox" id="notifyNews" checked> <span>Новости и акции</span> </label> </div> <div class="preference-item"> <label> <input type="checkbox" id="notifyEvents"> <span>Мероприятия и экскурсии</span> </label> </div> <div class="preference-item"> <label> <input type="checkbox" id="notifyAnimal" checked> <span>Новые животные</span> </label> </div> <div class="preference-item"> <label> <input type="checkbox" id="notifyDiscount" checked> <span>Специальные предложения</span> </label> </div> </div> </div> <div class="form-section"> <h3><i class="fas fa-globe"></i> Язык и регион</h3> <div class="form-grid"> <div class="form-group"> <label for="language">Язык интерфейса</label> <select id="language"> <option value="ru" selected>Русский</option> <option value="en">English</option> </select> </div> <div class="form-group"> <label for="timezone">Часовой пояс</label> <select id="timezone"> <option value="Europe/Moscow" selected>Москва (GMT+3)</option> <option value="Europe/London">Лондон (GMT+0)</option> <option value="America/New_York">Нью-Йорк (GMT-5)</option> </select> </div> <div class="form-group"> <label for="dateFormat">Формат даты</label> <select id="dateFormat"> <option value="DD.MM.YYYY" selected>ДД.ММ.ГГГГ</option> <option value="YYYY-MM-DD">ГГГГ-ММ-ДД</option> </select> </div> </div> </div> <div class="form-actions"> <button type="submit" class="btn btn-primary"> <i class="fas fa-save"></i> Сохранить настройки </button> </div> </form> </div> <div class="tab-content" id="badgesTab"> <div class="profile-form"> <div class="loading-spinner" id="badgesLoading"> <i class="fas fa-spinner fa-spin"></i> Загрузка достижений... </div> <div class="form-content" id="badgesContent" style="display: none;"> <div class="form-section"> <h3><i class="fas fa-award"></i> Ваши достижения</h3> <div class="badge-list" id="badgesList"> <!-- Достижения будут загружены динамически --> </div> <div class="no-achievements" id="noAchievements" style="display: none;"> <p style="text-align: center; color: var(--text-light); padding: 2rem;"> <i class="fas fa-trophy" style="font-size: 3rem; margin-bottom: 1rem; display: block; color: #e0e0e0;"></i> У вас пока нет достижений.<br> Совершайте действия на сайте, чтобы получить их! </p> </div> </div> <div class="form-section"> <h3><i class="fas fa-chart-line"></i> Прогресс</h3> <div class="progress-stats" id="progressStats"> <!-- Прогресс будет загружен динамически --> </div> </div> </div> </div> </div> </div> <div class="danger-zone"> <h3><i class="fas fa-exclamation-triangle"></i> Опасная зона</h3> <p>Эти действия нельзя отменить. Будьте внимательны.</p> <div class="form-actions"> <button type="button" class="btn btn-outline" id="exportData"> <i class="fas fa-download"></i> Экспорт данных </button> <button type="button" class="btn btn-danger" id="deleteAccount"> <i class="fas fa-trash-alt"></i> Удалить аккаунт </button> </div> </div> </section> </main> </div> <footer class="footer"> <div class="container"> <div class="footer-content"> <div class="footer-section"> <h3>Зоопарк Дикий Мир</h3> <p>г. Москва, ул. Зоологическая, 123</p> <p>Ежедневно с 9:00 до 20:00</p> </div> <div class="footer-section"> <h3>Контакты</h3> <p><i class="fas fa-phone"></i> +7 (495) 123-45-67</p> <p><i class="fas fa-envelope"></i> info@wildzoo.ru</p> </div> <div class="footer-section"> <h3>Мы в соцсетях</h3> <div class="social-links"> <a href="#"><i class="fab fa-vk"></i></a> <a href="#"><i class="fab fa-telegram"></i></a> <a href="#"><i class="fab fa-youtube"></i></a> </div> </div> </div> <div class="footer-bottom"> <p>© 2025 Зоопарк Дикий Мир. Все права защищены.</p> </div> </div> </footer> </div> <!-- Модальные окна --> <div class="modal" id="avatarModal"> <div class="modal-content"> <div class="modal-header"> <h3>Смена аватара</h3> <button class="modal-close"> <i class="fas fa-times"></i> </button> </div> <div class="modal-body"> <div class="avatar-upload-area"> <div class="upload-placeholder"> <i class="fas fa-cloud-upload-alt"></i> <p>Перетащите сюда изображение или нажмите для выбора</p> <small>Рекомендуемый размер: 150×150 пикселей</small> </div> <input type="file" id="avatarFile" accept="image/*" style="display: none;"> </div> </div> <div class="modal-footer"> <button class="btn btn-outline modal-close">Отмена</button> <button class="btn btn-primary" id="saveAvatar">Сохранить</button> </div> </div> </div> <script src="js/main.js"></script> <script src="js/auth.js"></script> <script src="js/auth-manager.js"></script> <script> class ProfileManager { constructor() { this.apiBaseUrl = 'http://localhost:3000/api'; this.authManager = window.authManager; this.init(); } async init() { if (!this.authManager || !this.authManager.isAuthenticated()) { window.location.href = 'login.html'; return; } await this.loadUserData(); this.setupEventListeners(); this.setupNavigation(); this.loadUserStats(); } async checkAuth() { const token = localStorage.getItem('token'); if (!token) { window.location.href = 'login.html'; return false; } try { const response = await fetch(`${this.apiBaseUrl}/auth/me`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { throw new Error('Not authenticated'); } } catch (error) { localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = 'login.html'; return false; } return true; } async loadUserData() { const user = this.authManager.getUser(); if (user) { this.updateProfileInfo(user); } else { // Пробуем загрузить данные const user = await this.authManager.getCurrentUser(); if (user) { this.updateProfileInfo(user); } } } async loadUserStats() { try { const token = localStorage.getItem('token'); if (!token) return; // Загружаем статистику пользователя const response = await fetch(`${this.apiBaseUrl}/auth/dashboard-data`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) return; const data = await response.json(); if (data.success && data.stats) { this.userStats = data.stats; this.updateUserStats(data.stats); } // Загружаем билеты для расчета расходов await this.loadUserTickets(); } catch (error) { console.error('Ошибка загрузки статистики:', error); } } async loadUserTickets() { try { const token = localStorage.getItem('token'); if (!token) return; const response = await fetch(`${this.apiBaseUrl}/tickets/my`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) return; const data = await response.json(); if (data.success && data.tickets) { this.calculateSpending(data.tickets); } } catch (error) { console.error('Ошибка загрузки билетов:', error); } } calculateSpending(tickets) { if (!tickets || tickets.length === 0) { document.getElementById('spendingAmount').textContent = '0 ₽'; return; } const totalSpending = tickets.reduce((sum, ticket) => { return sum + parseFloat(ticket.total_amount || 0); }, 0); document.getElementById('spendingAmount').textContent = `${totalSpending.toLocaleString('ru-RU')} ₽`; } updateProfileInfo(user) { // Обновляем информацию в заголовке профиля document.getElementById('profileName').textContent = user.name || 'Пользователь'; document.getElementById('profileEmail').textContent = user.email || ''; // Обновляем информацию в сайдбаре document.querySelector('.user-name').textContent = user.name || 'Пользователь'; document.querySelector('.user-email').textContent = user.email || ''; // Обновляем аватар const avatarImg = document.getElementById('avatarImg'); if (user.avatar) { avatarImg.src = user.avatar; } else { // Генерируем инициалы для аватара const initials = user.name ? user.name.charAt(0).toUpperCase() : 'П'; const canvas = document.createElement('canvas'); canvas.width = 150; canvas.height = 150; const ctx = canvas.getContext('2d'); // Фон ctx.fillStyle = '#4CAF50'; ctx.fillRect(0, 0, 150, 150); // Текст ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 60px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(initials, 75, 75); avatarImg.src = canvas.toDataURL(); } // Заполняем форму личных данных this.fillPersonalForm(user); // Скрываем спиннер загрузки document.getElementById('personalLoading').style.display = 'none'; document.getElementById('personalFormContent').style.display = 'block'; } fillPersonalForm(user) { // Разделяем имя и фамилию const nameParts = (user.name || '').split(' '); document.getElementById('firstName').value = nameParts[0] || ''; document.getElementById('lastName').value = nameParts.slice(1).join(' ') || ''; document.getElementById('email').value = user.email || ''; document.getElementById('phone').value = user.phone || ''; document.getElementById('birthDate').value = user.birth_date || ''; document.getElementById('city').value = user.city || ''; // Биография и интересы (можно добавить в модель пользователя позже) document.getElementById('bio').value = user.bio || ''; document.getElementById('interests').value = user.interests || ''; // Настройки уведомлений document.getElementById('newsletter').checked = user.newsletter || false; } updateUserStats(stats) { if (!stats) return; document.getElementById('ticketsCount').textContent = stats.tickets || 0; document.getElementById('visitsCount').textContent = stats.visits || 0; // Загружаем достижения this.loadAchievements(stats); } async loadAchievements(stats) { const badgesList = document.getElementById('badgesList'); const noAchievements = document.getElementById('noAchievements'); const progressStats = document.getElementById('progressStats'); if (!badgesList || !noAchievements || !progressStats) return; badgesList.innerHTML = ''; progressStats.innerHTML = ''; const achievements = []; // Проверяем достижения на основе статистики if (stats.tickets >= 1) { achievements.push({ title: 'Первый билет', description: 'Купил первый билет', icon: 'ticket-alt', unlocked: true }); } if (stats.tickets >= 5) { achievements.push({ title: 'Частый посетитель', description: 'Купил 5 билетов', icon: 'calendar-check', unlocked: true }); } if (stats.visits >= 1) { achievements.push({ title: 'Первый визит', description: 'Посетил зоопарк впервые', icon: 'map-marker-alt', unlocked: true }); } if (stats.visits >= 3) { achievements.push({ title: 'Завсегдатай', description: 'Посетил зоопарк 3 раза', icon: 'home', unlocked: true }); } if (stats.favorites >= 1) { achievements.push({ title: 'Любитель животных', description: 'Добавил животное в избранное', icon: 'heart', unlocked: true }); } if (stats.favorites >= 3) { achievements.push({ title: 'Натуралист', description: 'Добавил 3 животных в избранное', icon: 'leaf', unlocked: true }); } // Отображаем достижения if (achievements.length > 0) { achievements.forEach(achievement => { const badge = document.createElement('div'); badge.className = `badge ${achievement.unlocked ? '' : 'locked'}`; badge.innerHTML = ` <div class="badge-icon"> <i class="fas fa-${achievement.icon}"></i> </div> <div class="badge-info"> <strong>${achievement.title}</strong> <small>${achievement.description}</small> </div> `; badgesList.appendChild(badge); }); noAchievements.style.display = 'none'; } else { noAchievements.style.display = 'block'; } // Отображаем прогресс const progressItems = [ { title: 'Частый посетитель', current: stats.tickets || 0, target: 10, label: 'билетов' }, { title: 'Знаток животных', current: stats.favorites || 0, target: 10, label: 'животных' }, { title: 'Активный участник', current: stats.visits || 0, target: 10, label: 'посещений' } ]; progressItems.forEach(item => { const progressItem = document.createElement('div'); progressItem.className = 'progress-item'; const progress = Math.min(100, (item.current / item.target) * 100); progressItem.innerHTML = ` <div class="progress-header"> <span>${item.title}</span> <span>${item.current}/${item.target} ${item.label}</span> </div> <div class="progress-bar"> <div class="progress-fill" style="width: ${progress}%"></div> </div> `; progressStats.appendChild(progressItem); }); // Скрываем спиннер загрузки document.getElementById('badgesLoading').style.display = 'none'; document.getElementById('badgesContent').style.display = 'block'; } setupEventListeners() { // Табы const tabButtons = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabButtons.forEach(button => { button.addEventListener('click', () => { const tabId = button.getAttribute('data-tab'); tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); button.classList.add('active'); document.getElementById(tabId + 'Tab').classList.add('active'); }); }); // Загрузка аватара const avatarUploadBtn = document.getElementById('avatarUploadBtn'); if (avatarUploadBtn) { avatarUploadBtn.addEventListener('click', () => { document.getElementById('avatarModal').classList.add('show'); }); } // Модальные окна const modalClose = document.querySelectorAll('.modal-close'); modalClose.forEach(btn => { btn.addEventListener('click', function() { const modal = this.closest('.modal'); if (modal) { modal.classList.remove('show'); } }); }); window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.classList.remove('show'); } }); // Отмена изменений const cancelBtn = document.getElementById('cancelChanges'); if (cancelBtn) { cancelBtn.addEventListener('click', () => { if (confirm('Отменить все изменения?')) { this.fillPersonalForm(this.currentUser); } }); } // Сохранение личных данных const personalForm = document.getElementById('personalForm'); if (personalForm) { personalForm.addEventListener('submit', async (e) => { e.preventDefault(); await this.savePersonalData(); }); } // Смена пароля const securityForm = document.getElementById('securityForm'); if (securityForm) { securityForm.addEventListener('submit', async (e) => { e.preventDefault(); await this.changePassword(); }); } // Сохранение настроек const preferencesForm = document.getElementById('preferencesForm'); if (preferencesForm) { preferencesForm.addEventListener('submit', async (e) => { e.preventDefault(); await this.savePreferences(); }); } // Экспорт данных const exportBtn = document.getElementById('exportData'); if (exportBtn) { exportBtn.addEventListener('click', () => { this.exportUserData(); }); } // Удаление аккаунта const deleteBtn = document.getElementById('deleteAccount'); if (deleteBtn) { deleteBtn.addEventListener('click', () => { this.deleteAccount(); }); } // Сохранение аватара const saveAvatarBtn = document.getElementById('saveAvatar'); if (saveAvatarBtn) { saveAvatarBtn.addEventListener('click', async () => { await this.saveAvatar(); }); } } setupNavigation() { const historyBtn = document.getElementById('historyBtn'); if (historyBtn) { historyBtn.addEventListener('click', (e) => { e.preventDefault(); alert('История посещений в разработке'); }); } } async savePersonalData() { try { const token = localStorage.getItem('token'); if (!token) { window.location.href = 'login.html'; return; } const firstName = document.getElementById('firstName').value; const lastName = document.getElementById('lastName').value; const phone = document.getElementById('phone').value; const birthDate = document.getElementById('birthDate').value; const city = document.getElementById('city').value; const bio = document.getElementById('bio').value; const interests = document.getElementById('interests').value; const newsletter = document.getElementById('newsletter').checked; const name = `${firstName} ${lastName}`.trim(); const updateData = { name: name || this.currentUser.name, phone: phone || null, birth_date: birthDate || null, city: city || null, bio: bio || null, interests: interests || null, newsletter: newsletter }; const response = await fetch(`${this.apiBaseUrl}/auth/profile`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(updateData) }); if (!response.ok) { throw new Error('Ошибка сохранения данных'); } const data = await response.json(); if (data.success) { alert('Данные успешно сохранены!'); this.currentUser = data.user; localStorage.setItem('user', JSON.stringify(data.user)); this.updateProfileInfo(data.user); } else { throw new Error(data.message || 'Ошибка сохранения'); } } catch (error) { console.error('Ошибка сохранения данных:', error); alert('Ошибка при сохранении данных: ' + error.message); } } async changePassword() { try { const token = localStorage.getItem('token'); if (!token) { window.location.href = 'login.html'; return; } const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; if (newPassword !== confirmPassword) { alert('Пароли не совпадают!'); return; } if (newPassword.length < 6) { alert('Пароль должен содержать минимум 6 символов!'); return; } const response = await fetch(`${this.apiBaseUrl}/auth/change-password`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPassword, newPassword }) }); if (!response.ok) { throw new Error('Ошибка смены пароля'); } const data = await response.json(); if (data.success) { alert('Пароль успешно изменен!'); document.getElementById('securityForm').reset(); } else { throw new Error(data.message || 'Ошибка смены пароля'); } } catch (error) { console.error('Ошибка смены пароля:', error); alert('Ошибка при смене пароля: ' + error.message); } } async savePreferences() { try { const token = localStorage.getItem('token'); if (!token) { window.location.href = 'login.html'; return; } const preferences = { notifyEmail: document.getElementById('notifyEmail').checked, notifyNews: document.getElementById('notifyNews').checked, notifyEvents: document.getElementById('notifyEvents').checked, notifyAnimal: document.getElementById('notifyAnimal').checked, notifyDiscount: document.getElementById('notifyDiscount').checked, language: document.getElementById('language').value, timezone: document.getElementById('timezone').value, dateFormat: document.getElementById('dateFormat').value }; // Здесь можно добавить API endpoint для сохранения настроек // Пока просто показываем сообщение об успехе alert('Настройки успешно сохранены!'); } catch (error) { console.error('Ошибка сохранения настроек:', error); alert('Ошибка при сохранении настроек: ' + error.message); } } exportUserData() { alert('Функция экспорта данных будет доступна в ближайшее время'); } deleteAccount() { const password = prompt('Для удаления аккаунта введите ваш пароль:'); if (password) { if (confirm('ВНИМАНИЕ! Это действие нельзя отменить. Все ваши данные будут удалены без возможности восстановления. Вы уверены?')) { this.performAccountDeletion(password); } } } async performAccountDeletion(password) { try { const token = localStorage.getItem('token'); if (!token) { return; } const response = await fetch(`${this.apiBaseUrl}/auth/account`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) }); if (!response.ok) { throw new Error('Ошибка удаления аккаунта'); } const data = await response.json(); if (data.success) { alert('Аккаунт успешно удален!'); localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = 'index.html'; } else { throw new Error(data.message || 'Ошибка удаления аккаунта'); } } catch (error) { console.error('Ошибка удаления аккаунта:', error); alert('Ошибка при удалении аккаунта: ' + error.message); } } async saveAvatar() { const fileInput = document.getElementById('avatarFile'); if (!fileInput.files.length) { alert('Пожалуйста, выберите изображение'); return; } const file = fileInput.files[0]; const formData = new FormData(); formData.append('avatar', file); try { const token = localStorage.getItem('token'); if (!token) { window.location.href = 'login.html'; return; } const response = await fetch(`${this.apiBaseUrl}/auth/upload-avatar`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); if (!response.ok) { throw new Error('Ошибка загрузки аватара'); } const data = await response.json(); if (data.success) { alert('Аватар успешно обновлен!'); document.getElementById('avatarModal').classList.remove('show'); this.currentUser.avatar = data.avatarUrl; localStorage.setItem('user', JSON.stringify(this.currentUser)); this.updateProfileInfo(this.currentUser); } else { throw new Error(data.message || 'Ошибка загрузки аватара'); } } catch (error) { console.error('Ошибка загрузки аватара:', error); alert('Ошибка при загрузке аватара: ' + error.message); } } } // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { const profileManager = new ProfileManager(); window.profileManager = profileManager; }); </script> <script src="js/auth-manager.js"></script> </body> </html>