// DOM элементы const avatarImage = document.getElementById("avatarImage"); const avatarPlaceholder = document.getElementById("avatarPlaceholder"); const avatarInput = document.getElementById("avatarInput"); const deleteAvatarBtn = document.getElementById("deleteAvatarBtn"); const usernameInput = document.getElementById("username"); const emailInput = document.getElementById("email"); const updateProfileBtn = document.getElementById("updateProfileBtn"); const currentPasswordInput = document.getElementById("currentPassword"); const newPasswordInput = document.getElementById("newPassword"); const confirmPasswordInput = document.getElementById("confirmPassword"); const changePasswordBtn = document.getElementById("changePasswordBtn"); const messageContainer = document.getElementById("messageContainer"); const accentColorInput = document.getElementById("accentColor"); // Lazy loading для изображений function initLazyLoading() { // Проверяем поддержку Intersection Observer API if ("IntersectionObserver" in window) { const imageObserver = new IntersectionObserver( (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { const img = entry.target; // Если у изображения есть data-src, загружаем его if (img.dataset.src) { img.src = img.dataset.src; img.removeAttribute("data-src"); } img.classList.remove("lazy"); observer.unobserve(img); } }); }, { rootMargin: "50px 0px", threshold: 0.01, } ); // Наблюдаем за всеми изображениями с классом lazy document.querySelectorAll("img.lazy").forEach((img) => { imageObserver.observe(img); }); } else { // Fallback для старых браузеров document.querySelectorAll("img.lazy").forEach((img) => { if (img.dataset.src) { img.src = img.dataset.src; img.removeAttribute("data-src"); } img.classList.remove("lazy"); }); } } // Функция для показа сообщений function showMessage(message, type = "success") { messageContainer.textContent = message; messageContainer.className = `message-container ${type}`; messageContainer.style.display = "block"; setTimeout(() => { messageContainer.style.display = "none"; }, 5000); } // Загрузка данных профиля async function loadProfile() { try { const response = await fetch("/api/user"); if (!response.ok) { throw new Error("Ошибка загрузки профиля"); } const user = await response.json(); // Заполняем поля usernameInput.value = user.username || ""; emailInput.value = user.email || ""; accentColorInput.value = user.accent_color || "#007bff"; // Обновляем выбранный цвет в цветовых опциях updateColorPickerSelection(user.accent_color || "#007bff"); // Применяем цветовой акцент пользователя const accentColor = user.accent_color || "#007bff"; document.documentElement.style.setProperty("--accent-color", accentColor); // Обрабатываем аватарку if (user.avatar) { avatarImage.src = user.avatar; avatarImage.style.display = "block"; avatarPlaceholder.style.display = "none"; deleteAvatarBtn.style.display = "inline-block"; } else { avatarImage.style.display = "none"; avatarPlaceholder.style.display = "inline-flex"; deleteAvatarBtn.style.display = "none"; } } catch (error) { console.error("Ошибка загрузки профиля:", error); showMessage("Ошибка загрузки данных профиля", "error"); } } // Функция для обновления выбора цвета в цветовой палитре function updateColorPickerSelection(selectedColor) { const colorOptions = document.querySelectorAll(".color-option"); colorOptions.forEach((option) => { option.classList.remove("selected"); if (option.dataset.color === selectedColor) { option.classList.add("selected"); } }); } // Обработчик загрузки аватарки avatarInput.addEventListener("change", async function (event) { const file = event.target.files[0]; if (!file) return; // Проверка размера файла (5MB) if (file.size > 5 * 1024 * 1024) { showMessage("Файл слишком большой. Максимальный размер: 5 МБ", "error"); return; } // Проверка типа файла const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif"]; if (!allowedTypes.includes(file.type)) { showMessage( "Недопустимый формат файла. Используйте JPG, PNG или GIF", "error" ); return; } try { const formData = new FormData(); formData.append("avatar", file); const response = await fetch("/api/user/avatar", { method: "POST", body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Ошибка загрузки аватарки"); } const result = await response.json(); // Обновляем отображение аватарки avatarImage.src = result.avatar + "?t=" + Date.now(); // Добавляем timestamp для обновления кэша avatarImage.style.display = "block"; avatarPlaceholder.style.display = "none"; deleteAvatarBtn.style.display = "inline-block"; showMessage("Аватарка успешно загружена", "success"); } catch (error) { console.error("Ошибка загрузки аватарки:", error); showMessage(error.message, "error"); } // Сбрасываем input для возможности повторной загрузки того же файла event.target.value = ""; }); // Обработчик удаления аватарки deleteAvatarBtn.addEventListener("click", async function () { if (!confirm("Вы уверены, что хотите удалить аватарку?")) { return; } try { const response = await fetch("/api/user/avatar", { method: "DELETE", }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Ошибка удаления аватарки"); } // Обновляем отображение avatarImage.style.display = "none"; avatarPlaceholder.style.display = "inline-flex"; deleteAvatarBtn.style.display = "none"; showMessage("Аватарка успешно удалена", "success"); } catch (error) { console.error("Ошибка удаления аватарки:", error); showMessage(error.message, "error"); } }); // Обработчик обновления профиля updateProfileBtn.addEventListener("click", async function () { const username = usernameInput.value.trim(); const email = emailInput.value.trim(); const accentColor = accentColorInput.value; // Валидация if (!username) { showMessage("Логин не может быть пустым", "error"); return; } if (username.length < 3) { showMessage("Логин должен быть не менее 3 символов", "error"); return; } if (email && !isValidEmail(email)) { showMessage("Некорректный email адрес", "error"); return; } try { const response = await fetch("/api/user/profile", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, email: email || null, accent_color: accentColor, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Ошибка обновления профиля"); } const result = await response.json(); // Применяем новый цветовой акцент document.documentElement.style.setProperty("--accent-color", accentColor); showMessage(result.message || "Профиль успешно обновлен", "success"); } catch (error) { console.error("Ошибка обновления профиля:", error); showMessage(error.message, "error"); } }); // Обработчик изменения пароля changePasswordBtn.addEventListener("click", async function () { const currentPassword = currentPasswordInput.value; const newPassword = newPasswordInput.value; const confirmPassword = confirmPasswordInput.value; // Валидация if (!currentPassword) { showMessage("Введите текущий пароль", "error"); return; } if (!newPassword) { showMessage("Введите новый пароль", "error"); return; } if (newPassword.length < 6) { showMessage("Новый пароль должен быть не менее 6 символов", "error"); return; } if (newPassword !== confirmPassword) { showMessage("Новый пароль и подтверждение не совпадают", "error"); return; } try { const response = await fetch("/api/user/profile", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ currentPassword, newPassword, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Ошибка изменения пароля"); } const result = await response.json(); // Очищаем поля паролей currentPasswordInput.value = ""; newPasswordInput.value = ""; confirmPasswordInput.value = ""; showMessage(result.message || "Пароль успешно изменен", "success"); } catch (error) { console.error("Ошибка изменения пароля:", error); showMessage(error.message, "error"); } }); // Функция валидации email function isValidEmail(email) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); } // Функция для проверки аутентификации async function checkAuthentication() { const isAuthenticated = localStorage.getItem("isAuthenticated"); if (isAuthenticated !== "true") { // Если пользователь не аутентифицирован, перенаправляем на страницу входа window.location.href = "/"; return; } // Проверяем, что сессия на сервере еще действительна try { const response = await fetch("/api/auth/status"); if (!response.ok) { // Если сессия недействительна, очищаем localStorage и перенаправляем localStorage.removeItem("isAuthenticated"); localStorage.removeItem("username"); window.location.href = "/"; return; } const authData = await response.json(); if (!authData.authenticated) { // Если сервер говорит, что пользователь не аутентифицирован localStorage.removeItem("isAuthenticated"); localStorage.removeItem("username"); window.location.href = "/"; return; } } catch (error) { console.error("Ошибка проверки аутентификации:", error); // В случае ошибки сети, оставляем пользователя на странице // но показываем предупреждение console.warn("Не удалось проверить статус аутентификации"); } } // Функция для настройки обработчика выхода function setupLogoutHandler() { const logoutForms = document.querySelectorAll('form[action="/logout"]'); logoutForms.forEach((form) => { form.addEventListener("submit", function (e) { // Очищаем localStorage перед выходом localStorage.removeItem("isAuthenticated"); localStorage.removeItem("username"); }); }); } // Обработчики для цветовой палитры function setupColorPicker() { const colorOptions = document.querySelectorAll(".color-option"); colorOptions.forEach((option) => { option.addEventListener("click", function () { const selectedColor = this.dataset.color; accentColorInput.value = selectedColor; updateColorPickerSelection(selectedColor); }); }); // Обработчик для input color accentColorInput.addEventListener("input", function () { updateColorPickerSelection(this.value); }); } // Загружаем профиль при загрузке страницы document.addEventListener("DOMContentLoaded", function () { // Проверяем аутентификацию при загрузке страницы checkAuthentication(); loadProfile(); // Инициализируем lazy loading для изображений initLazyLoading(); // Настраиваем цветовую палитру setupColorPicker(); // Добавляем обработчик для кнопки выхода setupLogoutHandler(); });