345 lines
16 KiB
HTML
345 lines
16 KiB
HTML
{% macro section(user) -%}
|
||
<script>
|
||
function toggleMenu() {
|
||
const button = document.getElementById('menu_button');
|
||
const lines = button.querySelectorAll('.line');
|
||
lines.forEach((line) => {
|
||
line.classList.toggle('active');
|
||
});
|
||
}
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const btn = document.getElementById('menu_button');
|
||
const menu = document.getElementById('menu');
|
||
const menu2 = document.getElementById('menu2');
|
||
const iconHamburger = document.getElementById('icon_hamburger');
|
||
const iconClose = document.getElementById('icon_close');
|
||
if (!btn) return;
|
||
btn.addEventListener('click', () => {
|
||
toggleMenu()
|
||
const isOpen = btn.getAttribute('aria-expanded') === 'true';
|
||
const newState = !isOpen;
|
||
btn.setAttribute('aria-expanded', String(newState));
|
||
menu.classList.toggle('open', newState);
|
||
menu2.classList.toggle('open', newState);
|
||
});
|
||
});
|
||
</script>
|
||
<style>
|
||
.nav-icon {display: flex;flex-direction: column;justify-content: space-around;height: 24px;cursor: pointer;position: relative;width: 100%;}
|
||
.line {right: 0;display: block;width: 42px;height: 0;transition: all 0.3s ease;position: absolute;border: 1.5px solid;}
|
||
.line1{top:3px}
|
||
.line2{width: 32px;top: 10px;}
|
||
.line3{top: 18px;}
|
||
.line1.active {max-width: 30px;top: 9px;transform: rotate(135deg);}
|
||
.line2.active {transform: translateX(-500px);}
|
||
.line3.active {max-width: 30px;top: 9px;transform: rotate(-135deg);}
|
||
</style>
|
||
<header class="c_xirl1">
|
||
<div class="c_uh9b6">
|
||
<a class="c_7dj6c" href="/">
|
||
<img src="/static/logo375.jpeg" class="c_vnonn b_cj7sg" crossorigin="anonymous" fetchpriority="high">
|
||
</a>
|
||
<div class="avatar">
|
||
{% if user %}
|
||
<a class="c_n2zau" href="/profile">
|
||
<img src="/a/{{user._id}}" class="b_e59wr" crossorigin="anonymous" fetchpriority="high">
|
||
</a>
|
||
{% endif %}
|
||
<button id="menu_button" class="c_pcz1l b_oxx99">
|
||
<div class="nav-icon">
|
||
<span class="line line1"></span>
|
||
<span class="line line2"></span>
|
||
<span class="line line3"></span>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<nav id="menu" class="c_olmk1">
|
||
<a class="c_qmp95 b_fokm1" href="/">Главная</a>
|
||
<a class="c_qmp95 b_fokm1" href="https://kultmost.ru/post">Маркетплейс</a>
|
||
{% if user %}
|
||
<a class="c_qmp95 b_fokm1" href="/my-paints">Мои картины</a>
|
||
{% endif %}
|
||
{% if user and 'users:read' in user.get('scopes',[]) %}
|
||
<a class="c_qmp95 b_fokm1" href="/artists">Художники</a>
|
||
{% endif %}
|
||
<a class="c_qmp95 b_fokm1" href="/partner">Партнеры</a>
|
||
<a class="c_qmp95 b_fokm1" href="/about">О платформе</a>
|
||
<a class="c_qmp95 b_fokm1" href="/news">Новости</a>
|
||
<a class="c_qmp95 b_fokm1 mob" href="/profile">Личный кабинет</a>
|
||
{% if user and 'admin' in user.get('roles',[]) %}
|
||
<a class="c_qmp95 b_fokm1 btn" href="/admin">Админ-панель</a>
|
||
{% endif %}
|
||
{% if user and 'back' in user.get('scopes',[]) %}
|
||
<span id="backBtn" class="c_qmp95 b_fokm1 btn">Переключиться на Админа</span>
|
||
|
||
<script>
|
||
document.getElementById('backBtn').addEventListener('click', async () => {
|
||
try {
|
||
const resp = await fetch('/api/admin/back', { method: 'POST' });
|
||
if (!resp.ok) {
|
||
const text = await resp.text();
|
||
console.error('Server error:', resp.status, text);
|
||
return;
|
||
}
|
||
window.location.href = '/profile';
|
||
} catch (err) {
|
||
console.error('Network error:', err);
|
||
}
|
||
});
|
||
</script>
|
||
{% endif %}
|
||
<span
|
||
id="shareBtn" data-imagetext="🎨 Рекомендую! 👍"
|
||
data-image="/static/share.png" data-link="/" data-text="Рекомендую!"
|
||
class="c_qmp95 b_fokm1 mob shareService">Поделиться сервисом</span>
|
||
{% if user is none %}
|
||
<div class="mob auth">
|
||
<a href="/registration">Регистрация</a>
|
||
<span>/</span>
|
||
<a href="/login">Вход</a>
|
||
</div>
|
||
{% endif %}
|
||
|
||
|
||
</nav>
|
||
{% if user %}
|
||
<a class="c_n2zau" id="menu2" href="/profile">
|
||
<img src="/a/{{user._id}}" class="c_27foz b_e59wr" crossorigin="anonymous" fetchpriority="high">
|
||
<div class="c_kl46b b_qel18">
|
||
<span class="c_poz0x b_zp47n">{{user.username}}</span>
|
||
<span class="c_poz0x b_zp47n">Личный кабинет</span>
|
||
</div>
|
||
</a>
|
||
{% else %}
|
||
<div class="auth" id="menu2">
|
||
<a href="/registration">Регистрация</a>
|
||
<span>/</span>
|
||
<a href="/login">Вход</a>
|
||
</div>
|
||
{% endif %}
|
||
{% if user %}
|
||
{% if user.notify %}
|
||
<div class="header-right">
|
||
<div class="notifications-wrapper">
|
||
<button class="notifications-btn" id="notificationsBtn">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||
<path d="M15 17H9C9 18.6569 10.3431 20 12 20C13.6569 20 15 18.6569 15 17Z" fill="#333"/>
|
||
<path d="M18 8C18 6.4087 17.3679 4.88258 16.2426 3.75736C15.1174 2.63214 13.5913 2 12 2C10.4087 2 8.88258 2.63214 7.75736 3.75736C6.63214 4.88258 6 6.4087 6 8C6 11.0902 5.22047 13.206 4.34966 14.6054C3.87894 15.4286 3.5 16.5 3.5 17.5H20.5C20.5 16.5 20.1211 15.4286 19.6503 14.6054C18.7795 13.206 18 11.0902 18 8Z" stroke="#333" stroke-width="2"/>
|
||
</svg>
|
||
{% set notify = user.notify | selectattr('read') | list %}
|
||
{% if notify | length > 0%}
|
||
<span class="notification-badge" id="notificationCount">{{ notify | length }}</span>
|
||
{%endif%}
|
||
</button>
|
||
|
||
<div class="notifications-dropdown" id="notificationsDropdown">
|
||
<div class="dropdown-header">
|
||
<h3>Уведомления</h3>
|
||
<button class="c_qmp95 b_fokm1 btn" id="markAsReadBtn">Отметить все как прочитанные</button>
|
||
|
||
<script>
|
||
|
||
|
||
</script>
|
||
</div>
|
||
|
||
<div class="notifications-list" id="notificationsList">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endif %}
|
||
</header>
|
||
|
||
{% if user %}
|
||
{% if user.notify %}
|
||
<script>
|
||
// Function to update notifications after marking them as read
|
||
function updateNotifications(updatedData) {
|
||
// Hide notification count if no unread notifications
|
||
document.querySelectorAll('#notificationCount').forEach(elem=>elem.style.display = 'none');
|
||
|
||
|
||
// Clear existing notifications list
|
||
const notificationsList = document.getElementById('notificationsList');
|
||
notificationsList.innerHTML = ''; // Clear the existing list
|
||
|
||
// Re-add notifications to the list
|
||
notificationsArray.forEach(item => {
|
||
addNotification(
|
||
item.title,
|
||
item.text,
|
||
item.date,
|
||
item.link,
|
||
false
|
||
);
|
||
});
|
||
}
|
||
|
||
document.getElementById('markAsReadBtn').addEventListener('click', function() {
|
||
fetch('/api/read/notify', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
showToast({ message: 'Ошибка', status: 'error' });
|
||
throw new Error('Network response was not ok');
|
||
}
|
||
|
||
return response.json().then(data => {
|
||
updateNotifications();
|
||
});
|
||
})
|
||
.catch(error => {
|
||
const errorText = error.message || 'Произошла ошибка';
|
||
showToast({ message: errorText, status: 'error' });
|
||
});
|
||
});
|
||
|
||
// Handling dropdown for notifications
|
||
const btn = document.getElementById('notificationsBtn');
|
||
const dropdown = document.getElementById('notificationsDropdown');
|
||
|
||
btn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
dropdown.classList.toggle('show');
|
||
});
|
||
|
||
document.addEventListener('click', () => {
|
||
dropdown.classList.remove('show');
|
||
});
|
||
|
||
function addNotification(title, text, time, link = '#', unread = true) {
|
||
const list = document.getElementById('notificationsList');
|
||
const itemLink = document.createElement('a');
|
||
itemLink.href = link + '#main';
|
||
|
||
const item = document.createElement('div');
|
||
item.className = `notification-item ${unread ? 'unread' : ''}`;
|
||
item.innerHTML = `
|
||
<div>
|
||
<strong>${title}</strong>
|
||
<p>${text}</p>
|
||
<small>${time}</small>
|
||
</div>
|
||
`;
|
||
|
||
itemLink.prepend(item);
|
||
list.prepend(itemLink);
|
||
}
|
||
|
||
// Initial load of notifications
|
||
const notificationsArray = [];
|
||
|
||
{% for item in user.notify %}
|
||
notificationsArray.push({
|
||
title: "{{ item.title }}",
|
||
text: "{{ item.text }}",
|
||
dateStr: "{{ (item._id.generation_time.strftime('%d.%m.%Y')) if item._id else '' }}",
|
||
link: "{{ item.link }}",
|
||
read: Boolean({{ 1 if item.read else 0 }})
|
||
});
|
||
{% endfor %}
|
||
|
||
// Parse dd.mm.YYYY -> Date (or null if missing/invalid)
|
||
function parseDate(dateStr) {
|
||
if (!dateStr) return null;
|
||
const [dd, mm, yyyy] = dateStr.split('.');
|
||
const d = new Date(Number(yyyy), Number(mm) - 1, Number(dd));
|
||
return isNaN(d.getTime()) ? null : d;
|
||
}
|
||
|
||
notificationsArray.sort((a, b) => {
|
||
// 1) unread first
|
||
const aRead = a.read ? 1 : 0;
|
||
const bRead = b.read ? 1 : 0;
|
||
if (aRead !== bRead) return aRead - bRead;
|
||
|
||
// 2) newest first (fallback to 0 for missing dates)
|
||
const aTime = parseDate(a.dateStr)?.getTime() ?? 0;
|
||
const bTime = parseDate(b.dateStr)?.getTime() ?? 0;
|
||
return aTime - bTime;
|
||
});
|
||
|
||
notificationsArray.forEach(item => {
|
||
addNotification(item.title, item.text, item.dateStr, item.link, item.read);
|
||
});
|
||
|
||
</script>
|
||
{% endif %}
|
||
{% endif %}
|
||
{%- endmacro %}
|
||
|
||
{% macro style() -%}
|
||
<style type="text/css">/* Base (common) styles */
|
||
.notifications-wrapper {position: relative;}
|
||
.notifications-btn {background: none;border: none;padding: 8px;cursor: pointer;position: relative}
|
||
.notification-badge {position: absolute;top: 0;right: 4px;background: #e74c3c;color: white;font-size: 11px;font-weight: bold;min-width: 16px;height: 16px;border-radius: 50%;display: flex;align-items: center;justify-content: center;padding: 0 4px;}
|
||
.notifications-dropdown {position: absolute;top: 100%;right: 0;width: 380px;background: white;border-radius: 12px;box-shadow: 0 10px 30px rgba(0,0,0,0.15);display: none;z-index: 2000;margin-top: 8px;overflow: hidden;}
|
||
.notifications-dropdown.show {display: block;}
|
||
.dropdown-header {padding: 16px 20px;border-bottom: 1px solid #eee;display: flex;justify-content: space-between;align-items: center;gap: 15px;}
|
||
.notifications-list {max-height: 420px;overflow-y: auto;}
|
||
.notification-item {padding: 14px 20px;border-bottom: 1px solid #f0f0f0;transition: background 0.2s;color: #1F1F1F;}
|
||
.notification-item:hover {background: #f8f9fa;}
|
||
.notification-item.unread {background: #f0f7ff;}
|
||
.b_cj7sg { align-self: stretch; aspect-ratio: 127/84; width: 127px; }
|
||
.b_2jukj { fill: #1F1F1F; height: 12px; width: 18px; }
|
||
.b_dhbqs { flex-shrink: 0; height: 24px; width: 24px; }
|
||
.b_oxx99 { align-items: center; display: flex; height: 48px; justify-content: center; width: 48px; }
|
||
.b_fokm1 { font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; text-decoration-skip-ink: auto; text-decoration-style: solid; text-decoration-thickness: auto; text-underline-offset: auto; text-underline-position: from-font; }
|
||
.b_e59wr { border-radius: 1em; height: 70px; min-width: 70px; max-width: 170px; object-fit: cover; object-position: top; }
|
||
.b_zp47n { align-self: stretch; color: #5F1798; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; max-width: 210px; }
|
||
.b_qel18 { align-items: flex-start; display: flex; flex-direction: column; gap: 5px; justify-content: center; }
|
||
.avatar{display: flex;align-items: center;gap: 22px;}
|
||
nav>a{color: #111;}
|
||
|
||
.auth>a, .auth>span{color: #5f1798; font-size: 20px;}
|
||
.auth{display: flex;gap: 5px;}
|
||
.mob{display: none;}
|
||
/* Mobile */
|
||
@media (max-width: 699px) {
|
||
#menu, #menu2 { display: none; }
|
||
#menu.open{ display: flex; }
|
||
.c_qmp95{font-size: 20px;}
|
||
.mob{display: flex;}
|
||
.c_vnonn { aspect-ratio: 3/2; flex-shrink: 0; height: 70px; width: 105px; }
|
||
.c_7dj6c { align-items: center; display: flex; height: 70px; justify-content: center; width: 105px; }
|
||
.c_uh9b6 { align-items: center; background: #FFF; display: flex; flex: 1 0 0; justify-content: space-between; }
|
||
.shareService{border-radius: 5px;background: #f39318;color: #111;padding: 13px 15px;justify-content: center;align-items: center;display: flex;margin: 5px 0px;font-size: 16px;}
|
||
.c_olmk1 {position: fixed;top: 0;left: 0;width: 300px;flex-direction: column;gap: 5px;background: white;bottom: 0;z-index: 1111;border: 1px solid #5f1798;padding: 50px 20px; }
|
||
.c_poz0x { align-self: stretch; color: #5F1798; font-family: Inter; font-size: 16px; font-style: normal; font-weight: 400; line-height: normal; min-width: 255px; }
|
||
.c_n2zau { align-items: center; display: flex; flex: 1 0 0; gap: 20px; max-width: 345px;}
|
||
.c_xirl1 { align-content: center; align-items: center; align-self: stretch; background: #FFF; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px 15px; row-gap: 20px; }
|
||
}
|
||
|
||
/* Tablet */
|
||
@media (min-width: 700px) and (max-width: 900px) {
|
||
#menu, #menu2 { display: none; }
|
||
#menu.open { display: flex; }
|
||
.c_qmp95{font-size: 20px;}
|
||
.c_7dj6c { align-items: center; display: flex; flex: 1 0 0; height: 84px; }
|
||
.c_uh9b6 { align-items: center; background: #FFF; display: flex; flex: 1 0 0; justify-content: center; }
|
||
.c_olmk1 {position: fixed;top: 0;left: 0;width: 300px;flex-direction: column;gap: 5px;background: white;bottom: 0;z-index: 1111;border: 1px solid #5f1798;padding: 50px 20px; }
|
||
.c_n2zau { align-items: center; display: flex; flex: 1 0 0; gap: 20px; }
|
||
.c_xirl1 { align-content: center; align-items: center; align-self: stretch; background: #FFF; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px; row-gap: 20px; }
|
||
}
|
||
|
||
/* Desktop */
|
||
@media (min-width: 901px) {
|
||
.avatar,#menu_button{display: none;}
|
||
.c_7dj6c { align-items: center; align-self: stretch; display: flex; height: 84px; justify-content: center; }
|
||
.c_8mapk { fill: #1F1F1F; flex-shrink: 0; height: 12px; width: 18px; }
|
||
.c_vb7zi { align-items: center; display: flex; flex-shrink: 0; height: 24px; justify-content: center; padding: 6px 3px; width: 24px; }
|
||
.c_uh9b6 { align-items: flex-start; display: flex; flex-direction: column; width: 139px; }
|
||
.c_olmk1 { align-content: center; align-items: center; display: flex; flex-wrap: wrap; gap: 5px 20px; }
|
||
.c_n2zau { align-items: center; display: flex; gap: 20px; }
|
||
.c_xirl1 { align-content: center; align-items: center; align-self: stretch; background: #FFF; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px 100px; row-gap: 20px; }
|
||
}
|
||
.btn{border-radius: 5px;background: #5F1798;color: white;padding: 13px 15px;justify-content: center;align-items: center;display: flex;margin: 5px 0px;font-size: 16px;}
|
||
</style>
|
||
{%- endmacro %} |