проверка активности клиета и изменение своего профиля

This commit is contained in:
2025-12-15 10:54:11 +03:00
parent 1a677f9ef2
commit a484d03894
9 changed files with 298 additions and 44 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ function hideLoader() {
}
export async function apiRequest(url, payload = {}, method = 'POST') {
method = method.toUpperCase();
method = typeof method === 'string' ? method.toUpperCase() : 'POST';
let finalUrl = url;
let options = {
+4
View File
@@ -26,6 +26,9 @@ export async function setCookie(name, value, days = 180) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
const encodedName = encodeURIComponent(name);
document.cookie = `${encodedName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;`;
document.cookie = `${encodedName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; Secure;`;
// ---------- 1. Пытаемся установить безопасную куку ----------
let secureCookie = `${encodedName}=${cookieValue}; expires=${expires}; path=/; Secure; SameSite=Lax`;
document.cookie = secureCookie;
@@ -45,6 +48,7 @@ export async function setCookie(name, value, days = 180) {
return false; // безопасную куку установить не удалось
}
export async function getCookie(name) {
const cookies = document.cookie ? document.cookie.split('; ') : [];
+14
View File
@@ -67,6 +67,19 @@ const predefinedSpecs = {
async function getCookieData() {
accessData = await getCookie('toolbox_access');
userData = await getCookie('toolbox_user');
await checkActiveUser();
}
async function checkActiveUser() {
const activeCookie = loadFromStorage('active');
if (!activeCookie || !activeCookie.active || !activeCookie.datetime || Date.now() - activeCookie.datetime > 12 * 60 * 60 * 1000) {
const checkActive = await apiRequest('/user/check', { userId: userData.id });
if (checkActive.status === 'ok') {
saveToStorage('active', { active: true, datetime: Date.now() });
} else {
window.clientManager?.initLogout();
}
}
}
async function openTab(event, tabId) {
@@ -7209,6 +7222,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!accessData || !userData) {
console.warn('Access data or user data not found');
console.log(accessData, userData);
return;
}
+227 -38
View File
@@ -1,4 +1,5 @@
import { getCookie, deleteCookie } from '/static/js/cookies.js';
import { apiRequest } from './api.js';
import { getCookie, deleteCookie, setCookie } from './cookies.js';
class ClientManager {
constructor() {
@@ -13,11 +14,9 @@ class ClientManager {
this.userData = await getCookie('toolbox_user');
this.accessData = await getCookie('toolbox_access');
if (!this.userData || !this.accessData) {
console.warn('User data or access data not found in cookie');
this.clearUserCookie();
window.location.href = '/user/login';
this.initLogout();
}
// Вставляем данные пользователя в DOM
@@ -35,8 +34,7 @@ class ClientManager {
renderUserInfo() {
if (!this.userData) {
// Если нет данных, показываем заглушку
this.renderFallbackUser();
this.initLogout();
return;
}
@@ -64,37 +62,6 @@ class ClientManager {
}
renderFallbackUser() {
const userCard = document.querySelector('.client-card');
if (userCard) {
userCard.innerHTML = `
<div class="d-flex align-items-center gap-3">
<div class="position-relative">
<img src="images/users/default.png"
alt="Гость"
class="client-avatar">
<span class="position-absolute bottom-0 end-0 bg-warning border border-2 border-white rounded-circle p-1"></span>
</div>
<div class="me-3">
<h6 class="client-name mb-1">Гость</h6>
<div class="d-flex align-items-center">
<span class="badge bg-warning bg-opacity-10 text-warning fs-7 px-2 py-1 me-2">
<i class="bi bi-exclamation-triangle me-1"></i>
Не авторизован
</span>
</div>
</div>
<div>
<a href="/login" class="btn btn-primary btn-sm">
<i class="bi bi-box-arrow-in-right me-1"></i>
Войти
</a>
</div>
</div>
`;
}
}
initLogoutHandler() {
const logoutBtn = document.getElementById('clientLogoutBtn');
if (logoutBtn) {
@@ -105,12 +72,18 @@ class ClientManager {
}
}
initLogout() {
const logoutBtn = document.getElementById('clientLogoutBtn');
if (logoutBtn) {
this.handleLogout(logoutBtn);
}
}
handleLogout(button) {
// Анимация нажатия
button.style.transform = 'scale(0.95)';
// Добавляем иконку загрузки
const originalContent = button.innerHTML;
button.innerHTML = `
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
Выход...
@@ -162,6 +135,220 @@ class ClientManager {
};
}
}
userProfile() {
const avatarBtn = document.getElementById('userProfile');
async function openEditUserModal(user = null) {
const isNew = !user;
const modalId = `profile-edit-user-modal`;
// Удаляем старую модалку, если есть
const existingModal = document.getElementById(modalId);
if (existingModal) {
existingModal.remove();
}
const modalHTML = `
<div class="modal fade" id="${modalId}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${isNew ? 'Добавить пользователя' : 'Редактировать пользователя'}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="${modalId}-form">
<div class="text-center mb-3">
<img src="${user?.photo || 'static/images/users/default.png'}"
alt="Фото пользователя"
class="rounded-circle border object-fit-cover"
width="100" height="100"
id="${modalId}-photo-preview"
onerror="this.src='static/images/users/default.png'">
<div class="mt-2">
<input type="file"
class="form-control form-control-sm"
id="${modalId}-photo-input"
accept="image/*"
style="display: none;">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-primary" id="${modalId}-change-photo-btn">
<i class="bi bi-upload"></i> Изменить
</button>
${user?.photo && !user.photo.includes('default.png') ? `
<button type="button" class="btn btn-outline-danger" id="${modalId}-remove-photo-btn">
<i class="bi bi-trash"></i> Удалить
</button>
` : ''}
</div>
</div>
</div>
<div class="mb-3">
<label for="${modalId}-login" class="form-label">Логин *</label>
<input type="text" class="form-control" id="${modalId}-login"
value="${user?.login || ''}" required>
</div>
<div class="mb-3">
<label for="${modalId}-username" class="form-label">Имя пользователя *</label>
<input type="text" class="form-control" id="${modalId}-username"
value="${user?.username || ''}" required>
</div>
<div class="mb-3">
<label for="${modalId}-password" class="form-label">${isNew ? 'Пароль *' : 'Новый пароль'}</label>
<input type="password" class="form-control" id="${modalId}-password"
${isNew ? 'required' : ''}
placeholder="${isNew ? '' : 'Оставьте пустым, если не нужно менять'}">
${!isNew ? '<div class="form-text">Оставьте пустым, если не нужно менять пароль</div>' : ''}
</div>
</form>
<div id="${modalId}-error-message" class="alert alert-danger mb-3" role="alert" hidden></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" id="${modalId}-save-btn">Сохранить</button>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
const modal = new bootstrap.Modal(document.getElementById(modalId));
let photoFile = null;
let removePhoto = false;
// Обработчики для фото
const changePhotoBtn = document.getElementById(`${modalId}-change-photo-btn`);
const photoInput = document.getElementById(`${modalId}-photo-input`);
const photoPreview = document.getElementById(`${modalId}-photo-preview`);
changePhotoBtn.addEventListener('click', () => {
photoInput.click();
});
photoInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
if (file.type.startsWith('image/')) {
photoFile = file;
const reader = new FileReader();
reader.onload = (e) => {
photoPreview.src = e.target.result;
};
reader.readAsDataURL(file);
removePhoto = false;
} else {
showConfirmationModal('Ошибка', 'Пожалуйста, выберите файл изображения', () => { });
photoInput.value = '';
}
}
});
if (document.getElementById(`${modalId}-remove-photo-btn`)) {
document.getElementById(`${modalId}-remove-photo-btn`).addEventListener('click', () => {
photoPreview.src = 'static/images/users/default.png';
photoFile = null;
removePhoto = true;
});
}
// Обработчик сохранения
document.getElementById(`${modalId}-save-btn`).addEventListener('click', async () => {
const form = document.getElementById(`${modalId}-form`);
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const formUserData = {
login: document.getElementById(`${modalId}-login`).value.trim(),
username: document.getElementById(`${modalId}-username`).value.trim(),
};
// Добавляем пароль, если он указан
const password = document.getElementById(`${modalId}-password`).value;
if (password || isNew) {
formUserData.password = password || '';
}
// Определяем действие
let action = isNew ? 'create' : 'update';
// Обработка фото
if (photoFile) {
if (photoFile.size > 5 * 1024 * 1024) {
showInfo('Фото больше 5 МБ, оно не подходит', 'warning');
return;
}
formUserData.photo = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsDataURL(photoFile);
});
} else if (removePhoto) {
formUserData.photo = '';
}
let changedUserData = {};
if (isNew) {
changedUserData = formUserData;
} else {
changedUserData = Object.keys(formUserData).reduce((acc, key) => {
if (formUserData[key] !== user[key]) {
acc[key] = formUserData[key];
}
return acc;
}, {});
}
if (Object.keys(changedUserData).length === 0) {
showInfo('Нет изменений', 'info');
return;
}
// Обновляем данные
if (!isNew) {
changedUserData.id = user.id;
}
const result = await apiRequest('/user/', { action, userData: changedUserData, userId: user.id });
if (result && result.status === 'ok') {
const newUserData = await apiRequest('/user/', { userId: user.id }, 'get');
await setCookie('toolbox_user', JSON.stringify(newUserData));
const checkData = await getCookie('toolbox_user');
if (window.clientManager) {
window.clientManager.userData = newUserData;
window.clientManager.renderUserInfo();
}
if (modal) { modal.hide(); }
} else {
const errorMessageDiv = document.getElementById(`${modalId}-error-message`);
errorMessageDiv.textContent = result.message;
errorMessageDiv.hidden = false;
}
});
modal.show();
// Удаляем модалку после скрытия
modal._element.addEventListener('hidden.bs.modal', () => {
modal._element.remove();
});
}
avatarBtn.addEventListener('click', async () => {
await openEditUserModal(this.userData);
});
}
}
// Инициализация при загрузке документа
@@ -174,6 +361,8 @@ document.addEventListener('DOMContentLoaded', () => {
elements.forEach((el, index) => {
el.style.animationDelay = `${index * 0.1}s`;
});
window.clientManager.userProfile();
});
// Экспорт для использования в других модулях