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;
|
||||
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 {
|
||||
display: flex;
|
||||
@@ -112,6 +101,7 @@
|
||||
text-decoration: none;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
position: relative;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.admin-nav-item:hover {
|
||||
background: rgba(255,255,255,0.07);
|
||||
@@ -155,6 +145,12 @@
|
||||
}
|
||||
.admin-nav-badge.unread { background: #D97706; }
|
||||
|
||||
.admin-nav-divider {
|
||||
height: 1px;
|
||||
background: rgba(255,255,255,0.07);
|
||||
margin: 6px 10px;
|
||||
}
|
||||
|
||||
.admin-sidebar-footer {
|
||||
padding: 12px 10px;
|
||||
border-top: 1px solid rgba(255,255,255,0.08);
|
||||
@@ -194,6 +190,28 @@
|
||||
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 {
|
||||
flex: 1;
|
||||
padding: 28px;
|
||||
@@ -219,6 +237,7 @@
|
||||
.admin-main { margin-left: 0; }
|
||||
.admin-mobile-toggle { display: flex; }
|
||||
.admin-content { padding: 16px; }
|
||||
.admin-subnav { padding: 0 16px; }
|
||||
}
|
||||
|
||||
/* ── Overlay ── */
|
||||
@@ -247,6 +266,14 @@
|
||||
{% block admin_head %}{% endblock %}
|
||||
</head>
|
||||
<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">
|
||||
|
||||
<!-- Sidebar -->
|
||||
@@ -260,107 +287,64 @@
|
||||
|
||||
<!-- Navigation -->
|
||||
<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 %}">
|
||||
class="admin-nav-item {% if active_section == 'overview' %}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">
|
||||
<div class="admin-nav-label">Manage</div>
|
||||
<div class="admin-nav-divider"></div>
|
||||
|
||||
<a href="{{ url_for('admin.users') }}"
|
||||
class="admin-nav-item {% if admin_page == 'users' %}active{% endif %}">
|
||||
class="admin-nav-item {% if active_section == 'manage' %}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"/>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
||||
<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
|
||||
Manage
|
||||
{% 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-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">
|
||||
<div class="admin-nav-label">Content</div>
|
||||
<a href="{{ url_for('cms.cms_index') }}"
|
||||
class="admin-nav-item {% if admin_page == 'cms' %}active{% endif %}">
|
||||
class="admin-nav-item {% if active_section == 'content' %}active{% endif %}">
|
||||
<svg class="admin-nav-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
<polyline points="14 2 14 8 20 8"/>
|
||||
<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
|
||||
<polyline points="10 9 9 9 8 9"/>
|
||||
</svg>
|
||||
Articles
|
||||
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('cms.template_list') }}"
|
||||
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">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
|
||||
<rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/>
|
||||
</svg>
|
||||
pSEO Templates
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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 %}">
|
||||
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="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"/>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
Feature Flags
|
||||
System
|
||||
</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>
|
||||
|
||||
<!-- Footer links -->
|
||||
@@ -414,6 +398,39 @@
|
||||
</div>
|
||||
</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 -->
|
||||
{% if session.get('admin_impersonating') %}
|
||||
<div class="impersonate-banner">
|
||||
|
||||
Reference in New Issue
Block a user