merge: flat sidebar + horizontal subnav nav redesign

This commit is contained in:
Deeman
2026-02-26 20:21:28 +01:00

View File

@@ -2,65 +2,63 @@
{% block head %} {% block head %}
<style> <style>
/* ── Layout ── */
.admin-layout { display: flex; min-height: calc(100vh - 64px); } .admin-layout { display: flex; min-height: calc(100vh - 64px); }
/* ── Sidebar ── */
.admin-sidebar { .admin-sidebar {
width: 220px; flex-shrink: 0; background: #F8FAFC; border-right: 1px solid #E2E8F0; width: 196px; flex-shrink: 0;
padding: 1.25rem 0; display: flex; flex-direction: column; overflow-y: auto; background: #F8FAFC; border-right: 1px solid #E2E8F0;
padding: 1.25rem 0; overflow-y: auto;
} }
.admin-sidebar__title { .admin-sidebar__title {
padding: 0 1rem 1rem; font-size: 0.8125rem; font-weight: 700; color: #0F172A; padding: 0 1.125rem 1rem; font-size: 0.8125rem; font-weight: 700; color: #0F172A;
border-bottom: 1px solid #E2E8F0; margin-bottom: 0.25rem; border-bottom: 1px solid #E2E8F0; margin-bottom: 0.375rem; letter-spacing: 0.01em;
} }
.admin-sidebar__divider {
/* ── Direct links (single-item sections) ── */ height: 1px; background: #F1F5F9; margin: 0.375rem 1.125rem;
.sidebar-direct {
display: flex; align-items: center; gap: 8px;
padding: 8px 1rem; font-size: 0.8125rem; color: #64748B;
text-decoration: none; transition: all 0.1s;
} }
.sidebar-direct:hover { background: #EFF6FF; color: #1D4ED8; }
.sidebar-direct.active { background: #EFF6FF; color: #1D4ED8; font-weight: 600; border-right: 3px solid #1D4ED8; }
.sidebar-direct svg { width: 16px; height: 16px; flex-shrink: 0; }
/* ── Collapsible group header ── */
.sidebar-group__header {
display: flex; align-items: center; gap: 7px; width: 100%;
padding: 8px 1rem; font-size: 0.5625rem; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.06em; color: #94A3B8;
background: none; border: none; cursor: pointer; transition: color 0.12s;
margin-top: 0.375rem;
}
.sidebar-group__header:hover { color: #1D4ED8; }
.sidebar-group__header .header-icon { width: 13px; height: 13px; flex-shrink: 0; }
.sidebar-group.active-section .sidebar-group__header { color: #1D4ED8; }
.sidebar-group__chevron {
width: 11px; height: 11px; margin-left: auto; flex-shrink: 0; color: #CBD5E1;
transition: transform 0.2s ease;
}
.sidebar-group.collapsed .sidebar-group__chevron { transform: rotate(-90deg); }
/* Badge on group header (shows unread count even when collapsed) */
.sidebar-header-badge {
font-size: 9px; padding: 1px 5px; border-radius: 9999px;
background: #EF4444; color: white; font-weight: 700; margin-left: 1px;
}
/* ── Collapsible items container ── */
.sidebar-group__items {
overflow: hidden; max-height: 400px; transition: max-height 0.25s ease;
}
.sidebar-group.collapsed .sidebar-group__items { max-height: 0; }
/* ── Links inside groups ── */
.admin-nav a { .admin-nav a {
display: flex; align-items: center; gap: 8px; display: flex; align-items: center; gap: 9px;
padding: 7px 1rem 7px 2.125rem; font-size: 0.8125rem; color: #64748B; padding: 8px 1.125rem; font-size: 0.8125rem; color: #64748B;
text-decoration: none; transition: all 0.1s; text-decoration: none; transition: color 0.1s, background 0.1s;
} }
.admin-nav a:hover { background: #EFF6FF; color: #1D4ED8; } .admin-nav a:hover { background: #EFF6FF; color: #1D4ED8; }
.admin-nav a.active { background: #EFF6FF; color: #1D4ED8; font-weight: 600; border-right: 3px solid #1D4ED8; } .admin-nav a.active {
.admin-nav a svg { width: 16px; height: 16px; flex-shrink: 0; } background: #EFF6FF; color: #1D4ED8; font-weight: 600;
box-shadow: inset 3px 0 0 #1D4ED8;
}
.admin-nav a svg { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.85; }
.admin-nav a.active svg { opacity: 1; }
/* ── Content column (subnav + main) ── */
.admin-content-area {
flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0;
}
/* ── Horizontal subnav ── */
.admin-subnav {
display: flex; align-items: stretch; padding: 0 2rem;
background: #fff; border-bottom: 1px solid #E2E8F0;
flex-shrink: 0; overflow-x: auto; gap: 0;
}
.admin-subnav a {
display: flex; align-items: center; gap: 5px;
padding: 0 1px; margin: 0 13px 0 0; height: 42px;
font-size: 0.8rem; font-weight: 500; color: #64748B;
text-decoration: none; white-space: nowrap;
border-bottom: 2px solid transparent; margin-bottom: -1px;
transition: color 0.1s, border-color 0.1s;
}
.admin-subnav a:first-child { margin-left: 0; }
.admin-subnav a:hover { color: #1D4ED8; }
.admin-subnav a.active { color: #1D4ED8; border-bottom-color: #1D4ED8; font-weight: 600; }
.admin-subnav__badge {
font-size: 9px; padding: 1px 5px; border-radius: 9999px;
background: #EF4444; color: white; font-weight: 700;
}
/* ── Main content ── */
.admin-main { flex: 1; padding: 2rem; overflow-y: auto; } .admin-main { flex: 1; padding: 2rem; overflow-y: auto; }
/* ── Confirm dialog ── */ /* ── Confirm dialog ── */
@@ -77,20 +75,17 @@
@media (max-width: 768px) { @media (max-width: 768px) {
.admin-layout { flex-direction: column; } .admin-layout { flex-direction: column; }
.admin-sidebar { .admin-sidebar {
width: 100%; flex-direction: row; align-items: center; padding: 0.5rem; width: 100%; padding: 0.5rem; overflow-x: auto; overflow-y: hidden;
overflow-x: auto; border-right: none; border-bottom: 1px solid #E2E8F0; border-right: none; border-bottom: 1px solid #E2E8F0;
} }
.admin-sidebar__title { display: none; } .admin-sidebar__title { display: none; }
.sidebar-group__header { display: none; } .admin-sidebar__divider { display: none; }
.sidebar-group { display: contents; } .admin-nav { display: flex; gap: 2px; }
.sidebar-group__items, .admin-nav a {
.sidebar-group.collapsed .sidebar-group__items { padding: 7px 11px; white-space: nowrap;
max-height: none !important; overflow: visible; box-shadow: none !important; border-radius: 6px;
display: flex; flex: none; gap: 2px;
} }
.admin-nav { display: flex; flex: none; padding: 0; gap: 2px; align-items: center; } .admin-subnav { padding: 0 1rem; }
.admin-nav a { padding: 8px 12px; white-space: nowrap; border-right: none !important; border-radius: 6px; }
.sidebar-direct { padding: 8px 12px; white-space: nowrap; border-right: none !important; border-radius: 6px; }
.admin-main { padding: 1rem; } .admin-main { padding: 1rem; }
} }
</style> </style>
@@ -112,155 +107,109 @@
{%- set active_section = _section_map.get(admin_page|default(''), 'overview') -%} {%- set active_section = _section_map.get(admin_page|default(''), 'overview') -%}
<div class="admin-layout"> <div class="admin-layout">
{# ── Sidebar ── #}
<aside class="admin-sidebar"> <aside class="admin-sidebar">
<div class="admin-sidebar__title">Admin</div> <div class="admin-sidebar__title">Admin</div>
<nav class="admin-nav"> <nav class="admin-nav">
{# ── OVERVIEW (direct) ── #} <a href="{{ url_for('admin.index') }}" class="{% if active_section == 'overview' %}active{% endif %}">
<a href="{{ url_for('admin.index') }}" class="sidebar-direct{% if admin_page == 'dashboard' %} active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25a2.25 2.25 0 0 1-2.25-2.25v-2.25Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25a2.25 2.25 0 0 1-2.25-2.25v-2.25Z"/></svg>
Dashboard Dashboard
</a> </a>
{# ── MARKETPLACE (collapsible) ── #} <div class="admin-sidebar__divider"></div>
<div class="sidebar-group{% if active_section == 'marketplace' %} active-section{% endif %}" data-section="marketplace">
<button class="sidebar-group__header" type="button">
<svg class="header-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22m0 0-5.94-2.281m5.94 2.28-2.28 5.941"/></svg>
<span>Marketplace</span>
<svg class="sidebar-group__chevron" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/></svg>
</button>
<div class="sidebar-group__items">
<a href="{{ url_for('admin.marketplace_dashboard') }}" class="{% if admin_page == 'marketplace' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22m0 0-5.94-2.281m5.94 2.28-2.28 5.941"/></svg>
Dashboard
</a>
<a href="{{ url_for('admin.leads') }}" class="{% if admin_page == 'leads' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 0 1 1.037-.443 48.282 48.282 0 0 0 5.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"/></svg>
Leads
</a>
</div>
</div>
{# ── SUPPLIERS (direct) ── #} <a href="{{ url_for('admin.marketplace_dashboard') }}" class="{% if active_section == 'marketplace' %}active{% endif %}">
<a href="{{ url_for('admin.suppliers') }}" class="sidebar-direct{% if admin_page == 'suppliers' %} active{% endif %}"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22m0 0-5.94-2.281m5.94 2.28-2.28 5.941"/></svg>
Marketplace
</a>
<a href="{{ url_for('admin.suppliers') }}" class="{% if active_section == 'suppliers' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5M3.75 3v18m4.5-18v18M12 3v18m4.5-18v18m4.5-18v18M6 6.75h.008v.008H6V6.75Zm0 3h.008v.008H6V9.75Zm0 3h.008v.008H6v-.008Zm4.5-6h.008v.008H10.5V6.75Zm0 3h.008v.008H10.5V9.75Zm0 3h.008v.008H10.5v-.008Zm4.5-6h.008v.008H15V6.75Zm0 3h.008v.008H15V9.75Zm0 3h.008v.008H15v-.008Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5M3.75 3v18m4.5-18v18M12 3v18m4.5-18v18m4.5-18v18M6 6.75h.008v.008H6V6.75Zm0 3h.008v.008H6V9.75Zm0 3h.008v.008H6v-.008Zm4.5-6h.008v.008H10.5V6.75Zm0 3h.008v.008H10.5V9.75Zm0 3h.008v.008H10.5v-.008Zm4.5-6h.008v.008H15V6.75Zm0 3h.008v.008H15V9.75Zm0 3h.008v.008H15v-.008Z"/></svg>
Suppliers Suppliers
</a> </a>
{# ── CONTENT (collapsible: Articles, Scenarios, Templates, pSEO) ── #} <div class="admin-sidebar__divider"></div>
<div class="sidebar-group{% if active_section == 'content' %} active-section{% endif %}" data-section="content">
<button class="sidebar-group__header" type="button"> <a href="{{ url_for('admin.articles') }}" class="{% if active_section == 'content' %}active{% endif %}">
<svg class="header-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
<span>Content</span>
<svg class="sidebar-group__chevron" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/></svg>
</button>
<div class="sidebar-group__items">
<a href="{{ url_for('admin.articles') }}" class="{% if admin_page == 'articles' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
Articles Content
</a> </a>
<a href="{{ url_for('admin.scenarios') }}" class="{% if admin_page == 'scenarios' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"/></svg>
Scenarios
</a>
<a href="{{ url_for('admin.templates') }}" class="{% if admin_page == 'templates' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6Z"/></svg>
Templates
</a>
<a href="{{ url_for('pseo.pseo_dashboard') }}" class="{% if admin_page == 'pseo' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 1-6.23-.693L5 14.5m14.8.8 1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0 1 12 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"/></svg>
pSEO Engine
</a>
</div>
</div>
{# ── EMAIL (collapsible) ── #} <a href="{{ url_for('admin.emails') }}" class="{% if active_section == 'email' %}active{% endif %}">
<div class="sidebar-group{% if active_section == 'email' %} active-section{% endif %}" data-section="email">
<button class="sidebar-group__header" type="button">
<svg class="header-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
<span>Email</span>
{% if unread_count %}<span class="sidebar-header-badge">{{ unread_count }}</span>{% endif %}
<svg class="sidebar-group__chevron" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/></svg>
</button>
<div class="sidebar-group__items">
<a href="{{ url_for('admin.emails') }}" class="{% if admin_page == 'emails' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
Sent Log Email{% if unread_count %} <span class="admin-subnav__badge" style="margin-left:auto">{{ unread_count }}</span>{% endif %}
</a> </a>
<a href="{{ url_for('admin.inbox') }}" class="{% if admin_page == 'inbox' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h3.218a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859m-19.5.338V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H6.911a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661Z"/></svg>
Inbox{% if unread_count %} <span class="badge-danger" style="font-size:10px;padding:1px 6px;margin-left:auto;">{{ unread_count }}</span>{% endif %}
</a>
<a href="{{ url_for('admin.email_compose') }}" class="{% if admin_page == 'compose' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
Compose
</a>
<a href="{{ url_for('admin.email_gallery') }}" class="{% if admin_page == 'gallery' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 0 1-1.125-1.125v-3.75ZM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-8.25ZM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-2.25Z"/></svg>
Gallery
</a>
<a href="{{ url_for('admin.audiences') }}" class="{% if admin_page == 'audiences' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"/></svg>
Audiences
</a>
<a href="{{ url_for('admin.outreach') }}" class="{% if admin_page == 'outreach' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"/></svg>
Outreach
</a>
</div>
</div>
{# ── BILLING (direct) ── #} <div class="admin-sidebar__divider"></div>
<a href="{{ url_for('admin.billing_products') }}" class="sidebar-direct{% if admin_page == 'billing' %} active{% endif %}">
<a href="{{ url_for('admin.billing_products') }}" class="{% if active_section == 'billing' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>
Billing Billing
</a> </a>
{# ── ANALYTICS (direct) ── #} <a href="{{ url_for('admin.seo') }}" class="{% if active_section == 'analytics' %}active{% endif %}">
<a href="{{ url_for('admin.seo') }}" class="sidebar-direct{% if admin_page == 'seo' %} active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22m0 0-5.94-2.281m5.94 2.28-2.28 5.941"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18 9 11.25l4.306 4.306a11.95 11.95 0 0 1 5.814-5.518l2.74-1.22m0 0-5.94-2.281m5.94 2.28-2.28 5.941"/></svg>
Analytics Analytics
</a> </a>
{# ── PIPELINE (direct) ── #} <a href="{{ url_for('pipeline.pipeline_dashboard') }}" class="{% if active_section == 'pipeline' %}active{% endif %}">
<a href="{{ url_for('pipeline.pipeline_dashboard') }}" class="sidebar-direct{% if admin_page == 'pipeline' %} active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"/></svg>
Pipeline Pipeline
</a> </a>
{# ── SYSTEM (collapsible: Users, Flags, Tasks, Feedback) ── #} <div class="admin-sidebar__divider"></div>
<div class="sidebar-group{% if active_section == 'system' %} active-section{% endif %}" data-section="system">
<button class="sidebar-group__header" type="button"> <a href="{{ url_for('admin.users') }}" class="{% if active_section == 'system' %}active{% endif %}">
<svg class="header-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>
<span>System</span> System
<svg class="sidebar-group__chevron" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/></svg>
</button>
<div class="sidebar-group__items">
<a href="{{ url_for('admin.users') }}" class="{% if admin_page == 'users' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"/></svg>
Users
</a> </a>
<a href="{{ url_for('admin.flags') }}" class="{% if admin_page == 'flags' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 3v1.5M3 21v-6m0 0 2.77-.693a9 9 0 0 1 6.208.682l.108.054a9 9 0 0 0 6.086.71l3.114-.732a48.524 48.524 0 0 1-.005-10.499l-3.11.732a9 9 0 0 1-6.085-.711l-.108-.054a9 9 0 0 0-6.208-.682L3 4.5M3 15V4.5"/></svg>
Flags
</a>
<a href="{{ url_for('admin.tasks') }}" class="{% if admin_page == 'tasks' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75"/></svg>
Tasks
</a>
<a href="{{ url_for('admin.feedback') }}" class="{% if admin_page == 'feedback' %}active{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"/></svg>
Feedback
</a>
</div>
</div>
</nav> </nav>
</aside> </aside>
{# ── Content column ── #}
<div class="admin-content-area">
{# ── Horizontal subnav — rendered only for multi-page sections ── #}
{% if active_section == 'marketplace' %}
<nav class="admin-subnav">
<a href="{{ url_for('admin.marketplace_dashboard') }}" class="{% if admin_page == 'marketplace' %}active{% endif %}">Overview</a>
<a href="{{ url_for('admin.leads') }}" class="{% if admin_page == 'leads' %}active{% endif %}">Leads</a>
</nav>
{% elif active_section == 'content' %}
<nav class="admin-subnav">
<a href="{{ url_for('admin.articles') }}" class="{% if admin_page == 'articles' %}active{% endif %}">Articles</a>
<a href="{{ url_for('admin.templates') }}" class="{% if admin_page == 'templates' %}active{% endif %}">Templates</a>
<a href="{{ url_for('admin.scenarios') }}" class="{% if admin_page == 'scenarios' %}active{% endif %}">Scenarios</a>
<a href="{{ url_for('pseo.pseo_dashboard') }}" class="{% if admin_page == 'pseo' %}active{% endif %}">pSEO Engine</a>
</nav>
{% elif active_section == 'email' %}
<nav class="admin-subnav">
<a href="{{ url_for('admin.emails') }}" class="{% if admin_page == 'emails' %}active{% endif %}">Sent Log</a>
<a href="{{ url_for('admin.inbox') }}" class="{% if admin_page == 'inbox' %}active{% endif %}">
Inbox{% if unread_count %} <span class="admin-subnav__badge">{{ unread_count }}</span>{% endif %}
</a>
<a href="{{ url_for('admin.email_compose') }}" class="{% if admin_page == 'compose' %}active{% endif %}">Compose</a>
<a href="{{ url_for('admin.email_gallery') }}" class="{% if admin_page == 'gallery' %}active{% endif %}">Gallery</a>
<a href="{{ url_for('admin.audiences') }}" class="{% if admin_page == 'audiences' %}active{% endif %}">Audiences</a>
<a href="{{ url_for('admin.outreach') }}" class="{% if admin_page == 'outreach' %}active{% endif %}">Outreach</a>
</nav>
{% elif active_section == 'system' %}
<nav class="admin-subnav">
<a href="{{ url_for('admin.users') }}" class="{% if admin_page == 'users' %}active{% endif %}">Users</a>
<a href="{{ url_for('admin.flags') }}" class="{% if admin_page == 'flags' %}active{% endif %}">Flags</a>
<a href="{{ url_for('admin.tasks') }}" class="{% if admin_page == 'tasks' %}active{% endif %}">Tasks</a>
<a href="{{ url_for('admin.feedback') }}" class="{% if admin_page == 'feedback' %}active{% endif %}">Feedback</a>
</nav>
{% endif %}
<main class="admin-main"> <main class="admin-main">
{% block admin_content %}{% endblock %} {% block admin_content %}{% endblock %}
</main> </main>
</div>
</div> </div>
<dialog id="confirm-dialog"> <dialog id="confirm-dialog">
@@ -281,30 +230,5 @@ function confirmAction(message, form) {
document.getElementById('confirm-cancel').addEventListener('click', function() { dialog.close(); }, { once: true }); document.getElementById('confirm-cancel').addEventListener('click', function() { dialog.close(); }, { once: true });
dialog.showModal(); dialog.showModal();
} }
// Collapsible sidebar groups
(function() {
var STORAGE_KEY = 'admin_sidebar_v1';
var activeSection = '{{ active_section }}';
var saved = {};
try { saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch(e) {}
document.querySelectorAll('.sidebar-group').forEach(function(group) {
var section = group.dataset.section;
var isActive = section === activeSection;
if (isActive) {
group.classList.remove('collapsed');
} else if (saved[section] === 'open') {
group.classList.remove('collapsed');
} else {
group.classList.add('collapsed');
}
group.querySelector('.sidebar-group__header').addEventListener('click', function() {
group.classList.toggle('collapsed');
saved[section] = group.classList.contains('collapsed') ? 'closed' : 'open';
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(saved)); } catch(e) {}
});
});
})();
</script> </script>
{% endblock %} {% endblock %}