feat(admin): flat sidebar + horizontal subnav navigation
Replace grouped section labels + 9 individual links with 5 flat section-level items (Dashboard, Manage, Content, Engagement, System) and a horizontal tab strip for multi-page sections. Active state derived via _section_map dict — no JS required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -88,17 +88,6 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12px 10px;
|
padding: 12px 10px;
|
||||||
}
|
}
|
||||||
.admin-nav-section {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.admin-nav-label {
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: rgba(255,255,255,0.3);
|
|
||||||
padding: 10px 10px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-nav-item {
|
.admin-nav-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -112,6 +101,7 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: background 0.12s, color 0.12s;
|
transition: background 0.12s, color 0.12s;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-bottom: 1px;
|
||||||
}
|
}
|
||||||
.admin-nav-item:hover {
|
.admin-nav-item:hover {
|
||||||
background: rgba(255,255,255,0.07);
|
background: rgba(255,255,255,0.07);
|
||||||
@@ -155,6 +145,12 @@
|
|||||||
}
|
}
|
||||||
.admin-nav-badge.unread { background: #D97706; }
|
.admin-nav-badge.unread { background: #D97706; }
|
||||||
|
|
||||||
|
.admin-nav-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255,255,255,0.07);
|
||||||
|
margin: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.admin-sidebar-footer {
|
.admin-sidebar-footer {
|
||||||
padding: 12px 10px;
|
padding: 12px 10px;
|
||||||
border-top: 1px solid rgba(255,255,255,0.08);
|
border-top: 1px solid rgba(255,255,255,0.08);
|
||||||
@@ -194,6 +190,28 @@
|
|||||||
color: #78716C;
|
color: #78716C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Horizontal subnav ── */
|
||||||
|
.admin-subnav {
|
||||||
|
display: flex; align-items: stretch; padding: 0 28px;
|
||||||
|
background: #fff; border-bottom: 1px solid #E8DFD5;
|
||||||
|
flex-shrink: 0; overflow-x: auto; gap: 0;
|
||||||
|
}
|
||||||
|
.admin-subnav a {
|
||||||
|
display: flex; align-items: center; gap: 5px;
|
||||||
|
padding: 0 1px; margin: 0 14px 0 0; height: 40px;
|
||||||
|
font-size: 13px; font-weight: 500; color: #78716C;
|
||||||
|
text-decoration: none; white-space: nowrap;
|
||||||
|
border-bottom: 2px solid transparent; margin-bottom: -1px;
|
||||||
|
transition: color 0.12s, border-color 0.12s;
|
||||||
|
}
|
||||||
|
.admin-subnav a:hover { color: #B45309; text-decoration: none; }
|
||||||
|
.admin-subnav a.active { color: #B45309; border-bottom-color: #B45309; font-weight: 600; }
|
||||||
|
.admin-subnav__badge {
|
||||||
|
font-size: 9px; padding: 1px 5px; border-radius: 9999px; font-weight: 700;
|
||||||
|
}
|
||||||
|
.admin-subnav__badge.error { background: #EF4444; color: white; }
|
||||||
|
.admin-subnav__badge.unread { background: #D97706; color: white; }
|
||||||
|
|
||||||
.admin-content {
|
.admin-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 28px;
|
padding: 28px;
|
||||||
@@ -219,6 +237,7 @@
|
|||||||
.admin-main { margin-left: 0; }
|
.admin-main { margin-left: 0; }
|
||||||
.admin-mobile-toggle { display: flex; }
|
.admin-mobile-toggle { display: flex; }
|
||||||
.admin-content { padding: 16px; }
|
.admin-content { padding: 16px; }
|
||||||
|
.admin-subnav { padding: 0 16px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Overlay ── */
|
/* ── Overlay ── */
|
||||||
@@ -247,6 +266,14 @@
|
|||||||
{% block admin_head %}{% endblock %}
|
{% block admin_head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{%- set _section_map = {
|
||||||
|
'dashboard': 'overview',
|
||||||
|
'users': 'manage', 'tasks': 'manage',
|
||||||
|
'cms': 'content', 'cms_templates': 'content',
|
||||||
|
'feedback': 'engagement', 'waitlist': 'engagement',
|
||||||
|
'flags': 'system', 'emails': 'system',
|
||||||
|
} -%}
|
||||||
|
{%- set active_section = _section_map.get(admin_page|default(''), 'overview') -%}
|
||||||
<div class="admin-shell">
|
<div class="admin-shell">
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
@@ -260,107 +287,64 @@
|
|||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="admin-nav">
|
<nav class="admin-nav">
|
||||||
<div class="admin-nav-section">
|
|
||||||
<div class="admin-nav-label">Overview</div>
|
|
||||||
<a href="{{ url_for('admin.index') }}"
|
|
||||||
class="admin-nav-item {% if admin_page == 'dashboard' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
||||||
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
||||||
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
||||||
</svg>
|
|
||||||
Dashboard
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-nav-section">
|
<a href="{{ url_for('admin.index') }}"
|
||||||
<div class="admin-nav-label">Manage</div>
|
class="admin-nav-item {% if active_section == 'overview' %}active{% endif %}">
|
||||||
<a href="{{ url_for('admin.users') }}"
|
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
class="admin-nav-item {% if admin_page == 'users' %}active{% endif %}">
|
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
</svg>
|
||||||
<circle cx="9" cy="7" r="4"/>
|
Dashboard
|
||||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
</a>
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
|
||||||
</svg>
|
|
||||||
Users
|
|
||||||
</a>
|
|
||||||
<a href="{{ url_for('admin.tasks') }}"
|
|
||||||
class="admin-nav-item {% if admin_page == 'tasks' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
||||||
<polyline points="9 11 12 14 22 4"/>
|
|
||||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
||||||
</svg>
|
|
||||||
Tasks
|
|
||||||
{% if stats is defined and stats.tasks_failed > 0 %}
|
|
||||||
<span class="admin-nav-badge">{{ stats.tasks_failed }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-nav-section">
|
<div class="admin-nav-divider"></div>
|
||||||
<div class="admin-nav-label">Engagement</div>
|
|
||||||
<a href="{{ url_for('admin.feedback') }}"
|
|
||||||
class="admin-nav-item {% if admin_page == 'feedback' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
||||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
||||||
</svg>
|
|
||||||
Feedback
|
|
||||||
{% if feedback_unread is defined and feedback_unread > 0 %}
|
|
||||||
<span class="admin-nav-badge unread">{{ feedback_unread }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
<a href="{{ url_for('admin.waitlist') }}"
|
|
||||||
class="admin-nav-item {% if admin_page == 'waitlist' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
|
||||||
<circle cx="9" cy="7" r="4"/>
|
|
||||||
<line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/>
|
|
||||||
</svg>
|
|
||||||
Waitlist
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-nav-section">
|
<a href="{{ url_for('admin.users') }}"
|
||||||
<div class="admin-nav-label">Content</div>
|
class="admin-nav-item {% if active_section == 'manage' %}active{% endif %}">
|
||||||
<a href="{{ url_for('cms.cms_index') }}"
|
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
class="admin-nav-item {% if admin_page == 'cms' %}active{% endif %}">
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<circle cx="9" cy="7" r="4"/>
|
||||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
||||||
<polyline points="14 2 14 8 20 8"/>
|
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
||||||
<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
|
</svg>
|
||||||
<polyline points="10 9 9 9 8 9"/>
|
Manage
|
||||||
</svg>
|
{% if stats is defined and stats.tasks_failed > 0 %}
|
||||||
Articles
|
<span class="admin-nav-badge">{{ stats.tasks_failed }}</span>
|
||||||
</a>
|
{% endif %}
|
||||||
<a href="{{ url_for('cms.template_list') }}"
|
</a>
|
||||||
class="admin-nav-item {% if admin_page == 'cms_templates' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<a href="{{ url_for('cms.cms_index') }}"
|
||||||
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
class="admin-nav-item {% if active_section == 'content' %}active{% endif %}">
|
||||||
<rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/>
|
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
</svg>
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||||
pSEO Templates
|
<polyline points="14 2 14 8 20 8"/>
|
||||||
</a>
|
<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
|
||||||
</div>
|
<polyline points="10 9 9 9 8 9"/>
|
||||||
|
</svg>
|
||||||
|
Content
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="admin-nav-divider"></div>
|
||||||
|
|
||||||
|
<a href="{{ url_for('admin.feedback') }}"
|
||||||
|
class="admin-nav-item {% if active_section == 'engagement' %}active{% endif %}">
|
||||||
|
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||||
|
</svg>
|
||||||
|
Engagement
|
||||||
|
{% if feedback_unread is defined and feedback_unread > 0 %}
|
||||||
|
<span class="admin-nav-badge unread">{{ feedback_unread }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ url_for('admin.flags') }}"
|
||||||
|
class="admin-nav-item {% if active_section == 'system' %}active{% endif %}">
|
||||||
|
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||||
|
</svg>
|
||||||
|
System
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="admin-nav-section">
|
|
||||||
<div class="admin-nav-label">System</div>
|
|
||||||
<a href="{{ url_for('admin.flags') }}"
|
|
||||||
class="admin-nav-item {% if admin_page == 'flags' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
||||||
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
|
|
||||||
<line x1="4" y1="22" x2="4" y2="15"/>
|
|
||||||
</svg>
|
|
||||||
Feature Flags
|
|
||||||
</a>
|
|
||||||
<a href="{{ url_for('admin.emails') }}"
|
|
||||||
class="admin-nav-item {% if admin_page == 'emails' %}active{% endif %}">
|
|
||||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
||||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
|
||||||
<polyline points="22,6 12,13 2,6"/>
|
|
||||||
</svg>
|
|
||||||
Email Log
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Footer links -->
|
<!-- Footer links -->
|
||||||
@@ -414,6 +398,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{# ── Horizontal subnav — rendered only for multi-page sections ── #}
|
||||||
|
{% if active_section == 'manage' %}
|
||||||
|
<nav class="admin-subnav">
|
||||||
|
<a href="{{ url_for('admin.users') }}" class="{% if admin_page == 'users' %}active{% endif %}">Users</a>
|
||||||
|
<a href="{{ url_for('admin.tasks') }}" class="{% if admin_page == 'tasks' %}active{% endif %}">
|
||||||
|
Tasks
|
||||||
|
{% if stats is defined and stats.tasks_failed > 0 %}
|
||||||
|
<span class="admin-subnav__badge error">{{ stats.tasks_failed }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
{% elif active_section == 'content' %}
|
||||||
|
<nav class="admin-subnav">
|
||||||
|
<a href="{{ url_for('cms.cms_index') }}" class="{% if admin_page == 'cms' %}active{% endif %}">Articles</a>
|
||||||
|
<a href="{{ url_for('cms.template_list') }}" class="{% if admin_page == 'cms_templates' %}active{% endif %}">pSEO Templates</a>
|
||||||
|
</nav>
|
||||||
|
{% elif active_section == 'engagement' %}
|
||||||
|
<nav class="admin-subnav">
|
||||||
|
<a href="{{ url_for('admin.feedback') }}" class="{% if admin_page == 'feedback' %}active{% endif %}">
|
||||||
|
Feedback
|
||||||
|
{% if feedback_unread is defined and feedback_unread > 0 %}
|
||||||
|
<span class="admin-subnav__badge unread">{{ feedback_unread }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('admin.waitlist') }}" class="{% if admin_page == 'waitlist' %}active{% endif %}">Waitlist</a>
|
||||||
|
</nav>
|
||||||
|
{% elif active_section == 'system' %}
|
||||||
|
<nav class="admin-subnav">
|
||||||
|
<a href="{{ url_for('admin.flags') }}" class="{% if admin_page == 'flags' %}active{% endif %}">Feature Flags</a>
|
||||||
|
<a href="{{ url_for('admin.emails') }}" class="{% if admin_page == 'emails' %}active{% endif %}">Email Log</a>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Impersonation banner -->
|
<!-- Impersonation banner -->
|
||||||
{% if session.get('admin_impersonating') %}
|
{% if session.get('admin_impersonating') %}
|
||||||
<div class="impersonate-banner">
|
<div class="impersonate-banner">
|
||||||
|
|||||||
Reference in New Issue
Block a user