feat(sidebar): add collapsible sections with localStorage persistence
Sidebar sections can now be collapsed/expanded by clicking the header. State is persisted to localStorage so sections remain open/closed across page navigation and browser sessions. Changes: - init-alpine.js: Added openSections state, toggleSection(), expandSectionForCurrentPage(), and localStorage helpers - sidebar.html: Refactored with Jinja2 macros for DRY code, added collapsible sections with CSS transitions and rotating chevron icons Features: - Click section header to toggle expand/collapse - Chevron rotates 180 degrees when expanded - Smooth CSS transitions (no extra Alpine plugins needed) - State persists in localStorage (admin_sidebar_sections key) - Default: Platform Administration open, others closed - Dashboard and Settings always visible (not collapsible) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,233 +1,128 @@
|
||||
{# app/templates/admin/partials/sidebar.html #}
|
||||
<!-- Desktop sidebar -->
|
||||
<aside class="z-20 hidden w-64 overflow-y-auto bg-white dark:bg-gray-800 md:block flex-shrink-0">
|
||||
<div class="py-4 text-gray-500 dark:text-gray-400">
|
||||
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" href="/admin/dashboard">
|
||||
Admin Portal
|
||||
</a>
|
||||
<ul class="mt-6">
|
||||
<!-- Dashboard -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'dashboard'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'dashboard' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/dashboard">
|
||||
<span x-html="$icon('home')"></span>
|
||||
<span class="ml-4">Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{# Collapsible sidebar sections with localStorage persistence #}
|
||||
|
||||
<!-- Platform Administration Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Platform Administration
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Companies -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'companies'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'companies' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/companies">
|
||||
<span x-html="$icon('office-building')"></span>
|
||||
<span class="ml-4">Companies</span>
|
||||
</a>
|
||||
</li>
|
||||
{# ============================================================================
|
||||
REUSABLE MACROS FOR SIDEBAR ITEMS
|
||||
============================================================================ #}
|
||||
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendors'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendors' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendors">
|
||||
<span x-html="$icon('shopping-bag')"></span>
|
||||
<span class="ml-4">Vendors</span>
|
||||
</a>
|
||||
</li>
|
||||
{# Macro for collapsible section header #}
|
||||
{% macro section_header(title, section_key) %}
|
||||
<div class="px-6 my-4">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<button
|
||||
@click="toggleSection('{{ section_key }}')"
|
||||
class="flex items-center justify-between w-full px-6 py-2 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<span
|
||||
x-html="$icon('chevron-down', 'w-4 h-4 transition-transform duration-200')"
|
||||
:class="{ 'rotate-180': openSections.{{ section_key }} }"
|
||||
></span>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
<!-- Users -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'users'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'users' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/users">
|
||||
<span x-html="$icon('users')"></span>
|
||||
<span class="ml-4">Users</span>
|
||||
</a>
|
||||
</li>
|
||||
{# Macro for collapsible section content wrapper #}
|
||||
{% macro section_content(section_key) %}
|
||||
<ul
|
||||
x-show="openSections.{{ section_key }}"
|
||||
x-transition:enter="transition-all duration-200 ease-out"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition-all duration-150 ease-in"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-2"
|
||||
class="mt-1 overflow-hidden"
|
||||
>
|
||||
{{ caller() }}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
|
||||
<!-- Customers -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'customers'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'customers' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/customers">
|
||||
<span x-html="$icon('user-group')"></span>
|
||||
<span class="ml-4">Customers</span>
|
||||
</a>
|
||||
</li>
|
||||
{# Macro for menu item #}
|
||||
{% macro menu_item(page_id, url, icon, label) %}
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === '{{ page_id }}'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === '{{ page_id }}' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="{{ url }}">
|
||||
<span x-html="$icon('{{ icon }}')"></span>
|
||||
<span class="ml-4">{{ label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
<!-- Marketplace -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'marketplace'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'marketplace' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/marketplace">
|
||||
<span x-html="$icon('globe')"></span>
|
||||
<span class="ml-4">Marketplace</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{# ============================================================================
|
||||
SIDEBAR CONTENT (shared between desktop and mobile)
|
||||
============================================================================ #}
|
||||
|
||||
<!-- Content Management Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Content Management
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Platform Homepage -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'platform-homepage'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'platform-homepage' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/platform-homepage">
|
||||
<span x-html="$icon('home')"></span>
|
||||
<span class="ml-4">Platform Homepage</span>
|
||||
</a>
|
||||
</li>
|
||||
{% macro sidebar_content() %}
|
||||
<div class="py-4 text-gray-500 dark:text-gray-400">
|
||||
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" href="/admin/dashboard">
|
||||
Admin Portal
|
||||
</a>
|
||||
|
||||
<!-- Content Pages -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'content-pages'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'content-pages' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/content-pages">
|
||||
<span x-html="$icon('document-text')"></span>
|
||||
<span class="ml-4">Content Pages</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Dashboard (always visible) -->
|
||||
<ul class="mt-6">
|
||||
{{ menu_item('dashboard', '/admin/dashboard', 'home', 'Dashboard') }}
|
||||
</ul>
|
||||
|
||||
<!-- Vendor Themes -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendor-theme'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendor-theme' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendor-themes">
|
||||
<span x-html="$icon('color-swatch')"></span>
|
||||
<span class="ml-4">Vendor Themes</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Platform Administration Section -->
|
||||
{{ section_header('Platform Administration', 'platformAdmin') }}
|
||||
{% call section_content('platformAdmin') %}
|
||||
{{ menu_item('companies', '/admin/companies', 'office-building', 'Companies') }}
|
||||
{{ menu_item('vendors', '/admin/vendors', 'shopping-bag', 'Vendors') }}
|
||||
{{ menu_item('users', '/admin/users', 'users', 'Users') }}
|
||||
{{ menu_item('customers', '/admin/customers', 'user-group', 'Customers') }}
|
||||
{{ menu_item('marketplace', '/admin/marketplace', 'globe', 'Marketplace') }}
|
||||
{% endcall %}
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Developer Tools
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Components -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'components'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'components' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/components">
|
||||
<span x-html="$icon('view-grid')"></span>
|
||||
<span class="ml-4">Components</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Content Management Section -->
|
||||
{{ section_header('Content Management', 'contentMgmt') }}
|
||||
{% call section_content('contentMgmt') %}
|
||||
{{ menu_item('platform-homepage', '/admin/platform-homepage', 'home', 'Platform Homepage') }}
|
||||
{{ menu_item('content-pages', '/admin/content-pages', 'document-text', 'Content Pages') }}
|
||||
{{ menu_item('vendor-theme', '/admin/vendor-themes', 'color-swatch', 'Vendor Themes') }}
|
||||
{% endcall %}
|
||||
|
||||
<!-- Icons -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'icons'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'icons' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/icons">
|
||||
<span x-html="$icon('photograph')"></span>
|
||||
<span class="ml-4">Icons</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Developer Tools Section -->
|
||||
{{ section_header('Developer Tools', 'devTools') }}
|
||||
{% call section_content('devTools') %}
|
||||
{{ menu_item('components', '/admin/components', 'view-grid', 'Components') }}
|
||||
{{ menu_item('icons', '/admin/icons', 'photograph', 'Icons') }}
|
||||
{{ menu_item('testing', '/admin/testing', 'beaker', 'Testing Hub') }}
|
||||
{{ menu_item('code-quality', '/admin/code-quality', 'shield-check', 'Code Quality') }}
|
||||
{% endcall %}
|
||||
|
||||
<!-- Testing Hub -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'testing'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'testing' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/testing">
|
||||
<span x-html="$icon('beaker')"></span>
|
||||
<span class="ml-4">Testing Hub</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Platform Monitoring Section -->
|
||||
{{ section_header('Platform Monitoring', 'monitoring') }}
|
||||
{% call section_content('monitoring') %}
|
||||
{{ menu_item('imports', '/admin/imports', 'cube', 'Import Jobs') }}
|
||||
{{ menu_item('logs', '/admin/logs', 'document-text', 'Application Logs') }}
|
||||
{% endcall %}
|
||||
|
||||
<!-- Code Quality -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'code-quality'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'code-quality' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/code-quality">
|
||||
<span x-html="$icon('shield-check')"></span>
|
||||
<span class="ml-4">Code Quality</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Platform Monitoring Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Platform Monitoring
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Import Jobs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Application Logs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'logs'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'logs' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/logs">
|
||||
<span x-html="$icon('document-text')"></span>
|
||||
<span class="ml-4">Application Logs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<ul>
|
||||
<!-- Settings -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'settings'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'settings' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/settings">
|
||||
<span x-html="$icon('cog')"></span>
|
||||
<span class="ml-4">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Settings (always visible) -->
|
||||
<div class="px-6 my-4">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<ul>
|
||||
{{ menu_item('settings', '/admin/settings', 'cog', 'Settings') }}
|
||||
</ul>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{# ============================================================================
|
||||
DESKTOP SIDEBAR
|
||||
============================================================================ #}
|
||||
|
||||
<aside class="z-20 hidden w-64 overflow-y-auto bg-white dark:bg-gray-800 md:block flex-shrink-0">
|
||||
{{ sidebar_content() }}
|
||||
</aside>
|
||||
|
||||
<!-- Mobile sidebar -->
|
||||
{# ============================================================================
|
||||
MOBILE SIDEBAR
|
||||
============================================================================ #}
|
||||
|
||||
<!-- Mobile sidebar backdrop -->
|
||||
<div x-show="isSideMenuOpen"
|
||||
x-transition:enter="transition ease-in-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
@@ -237,6 +132,7 @@
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 z-10 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"></div>
|
||||
|
||||
<!-- Mobile sidebar panel -->
|
||||
<aside class="fixed inset-y-0 z-20 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 md:hidden"
|
||||
x-show="isSideMenuOpen"
|
||||
x-transition:enter="transition ease-in-out duration-150"
|
||||
@@ -247,228 +143,5 @@
|
||||
x-transition:leave-end="opacity-0 transform -translate-x-20"
|
||||
@click.away="closeSideMenu"
|
||||
@keydown.escape="closeSideMenu">
|
||||
<div class="py-4 text-gray-500 dark:text-gray-400">
|
||||
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" href="/admin/dashboard">
|
||||
Admin Portal
|
||||
</a>
|
||||
<ul class="mt-6">
|
||||
<!-- Dashboard -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'dashboard'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'dashboard' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/dashboard">
|
||||
<span x-html="$icon('home')"></span>
|
||||
<span class="ml-4">Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Platform Administration Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Platform Administration
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Companies -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'companies'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'companies' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/companies">
|
||||
<span x-html="$icon('office-building')"></span>
|
||||
<span class="ml-4">Companies</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendors'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendors' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendors">
|
||||
<span x-html="$icon('shopping-bag')"></span>
|
||||
<span class="ml-4">Vendors</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Users -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'users'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'users' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/users">
|
||||
<span x-html="$icon('users')"></span>
|
||||
<span class="ml-4">Users</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Customers -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'customers'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'customers' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/customers">
|
||||
<span x-html="$icon('user-group')"></span>
|
||||
<span class="ml-4">Customers</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Marketplace -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'marketplace'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'marketplace' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/marketplace">
|
||||
<span x-html="$icon('globe')"></span>
|
||||
<span class="ml-4">Marketplace</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Content Management Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Content Management
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Platform Homepage -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'platform-homepage'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'platform-homepage' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/platform-homepage">
|
||||
<span x-html="$icon('home')"></span>
|
||||
<span class="ml-4">Platform Homepage</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Content Pages -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'content-pages'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'content-pages' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/content-pages">
|
||||
<span x-html="$icon('document-text')"></span>
|
||||
<span class="ml-4">Content Pages</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Vendor Themes -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendor-theme'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendor-theme' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendor-themes">
|
||||
<span x-html="$icon('color-swatch')"></span>
|
||||
<span class="ml-4">Vendor Themes</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Developer Tools
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Components -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'components'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'components' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/components">
|
||||
<span x-html="$icon('view-grid')"></span>
|
||||
<span class="ml-4">Components</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Icons -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'icons'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'icons' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/icons">
|
||||
<span x-html="$icon('photograph')"></span>
|
||||
<span class="ml-4">Icons</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Testing Hub -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'testing'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'testing' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/testing">
|
||||
<span x-html="$icon('beaker')"></span>
|
||||
<span class="ml-4">Testing Hub</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Code Quality -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'code-quality'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'code-quality' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/code-quality">
|
||||
<span x-html="$icon('shield-check')"></span>
|
||||
<span class="ml-4">Code Quality</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Platform Monitoring Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Platform Monitoring
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Import Jobs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Application Logs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'logs'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'logs' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/logs">
|
||||
<span x-html="$icon('document-text')"></span>
|
||||
<span class="ml-4">Application Logs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<ul>
|
||||
<!-- Settings -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'settings'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'settings' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/settings">
|
||||
<span x-html="$icon('cog')"></span>
|
||||
<span class="ml-4">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
{{ sidebar_content() }}
|
||||
</aside>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* Alpine.js v3 global data initialization
|
||||
* Provides theme toggle, menu controls, and page state
|
||||
* Provides theme toggle, menu controls, sidebar sections, and page state
|
||||
*/
|
||||
function data() {
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Theme (dark mode) persistence
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
function getThemeFromLocalStorage() {
|
||||
// if user already changed the theme, use it
|
||||
if (window.localStorage.getItem('dark')) {
|
||||
return JSON.parse(window.localStorage.getItem('dark'))
|
||||
}
|
||||
|
||||
// else return their preferences
|
||||
return (
|
||||
!!window.matchMedia &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
@@ -20,12 +20,74 @@ function data() {
|
||||
window.localStorage.setItem('dark', value)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Sidebar sections persistence
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
const SIDEBAR_STORAGE_KEY = 'admin_sidebar_sections';
|
||||
|
||||
// Default state: Platform Administration open, others closed
|
||||
const defaultSections = {
|
||||
platformAdmin: true,
|
||||
contentMgmt: false,
|
||||
devTools: false,
|
||||
monitoring: false
|
||||
};
|
||||
|
||||
function getSidebarSectionsFromStorage() {
|
||||
try {
|
||||
const stored = window.localStorage.getItem(SIDEBAR_STORAGE_KEY);
|
||||
if (stored) {
|
||||
return { ...defaultSections, ...JSON.parse(stored) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse sidebar sections from localStorage:', e);
|
||||
}
|
||||
return { ...defaultSections };
|
||||
}
|
||||
|
||||
function saveSidebarSectionsToStorage(sections) {
|
||||
try {
|
||||
window.localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(sections));
|
||||
} catch (e) {
|
||||
console.warn('Failed to save sidebar sections to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Map pages to their parent sections
|
||||
const pageSectionMap = {
|
||||
// Platform Administration
|
||||
companies: 'platformAdmin',
|
||||
vendors: 'platformAdmin',
|
||||
users: 'platformAdmin',
|
||||
customers: 'platformAdmin',
|
||||
marketplace: 'platformAdmin',
|
||||
// Content Management
|
||||
'platform-homepage': 'contentMgmt',
|
||||
'content-pages': 'contentMgmt',
|
||||
'vendor-theme': 'contentMgmt',
|
||||
// Developer Tools
|
||||
components: 'devTools',
|
||||
icons: 'devTools',
|
||||
testing: 'devTools',
|
||||
'code-quality': 'devTools',
|
||||
// Platform Monitoring
|
||||
imports: 'monitoring',
|
||||
logs: 'monitoring'
|
||||
};
|
||||
|
||||
return {
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Theme
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
dark: getThemeFromLocalStorage(),
|
||||
toggleTheme() {
|
||||
this.dark = !this.dark
|
||||
setThemeToLocalStorage(this.dark)
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Mobile side menu
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isSideMenuOpen: false,
|
||||
toggleSideMenu() {
|
||||
this.isSideMenuOpen = !this.isSideMenuOpen
|
||||
@@ -33,6 +95,10 @@ function data() {
|
||||
closeSideMenu() {
|
||||
this.isSideMenuOpen = false
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Notifications menu
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isNotificationsMenuOpen: false,
|
||||
toggleNotificationsMenu() {
|
||||
this.isNotificationsMenuOpen = !this.isNotificationsMenuOpen
|
||||
@@ -40,6 +106,10 @@ function data() {
|
||||
closeNotificationsMenu() {
|
||||
this.isNotificationsMenuOpen = false
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Profile menu
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isProfileMenuOpen: false,
|
||||
toggleProfileMenu() {
|
||||
this.isProfileMenuOpen = !this.isProfileMenuOpen
|
||||
@@ -47,11 +117,37 @@ function data() {
|
||||
closeProfileMenu() {
|
||||
this.isProfileMenuOpen = false
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Pages menu (legacy)
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isPagesMenuOpen: false,
|
||||
togglePagesMenu() {
|
||||
this.isPagesMenuOpen = !this.isPagesMenuOpen
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Collapsible sidebar sections
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
openSections: getSidebarSectionsFromStorage(),
|
||||
|
||||
toggleSection(section) {
|
||||
this.openSections[section] = !this.openSections[section];
|
||||
saveSidebarSectionsToStorage(this.openSections);
|
||||
},
|
||||
|
||||
// Auto-expand section containing current page
|
||||
expandSectionForCurrentPage() {
|
||||
const section = pageSectionMap[this.currentPage];
|
||||
if (section && !this.openSections[section]) {
|
||||
this.openSections[section] = true;
|
||||
saveSidebarSectionsToStorage(this.openSections);
|
||||
}
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Page identifier - will be set by individual pages
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
currentPage: ''
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user