Files
orion/docs/frontend/shared/sidebar.md
Samir Boulahtit 8a367077e1 refactor: migrate vendor APIs to token-based context and consolidate architecture
## Vendor-in-Token Architecture (Complete Migration)
- Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id
- Update permission dependencies to extract vendor from JWT token
- Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException,
  InsufficientVendorPermissionsException
- Shop endpoints retain require_vendor_context() for URL-based detection
- Add AUTH-004 architecture rule enforcing vendor context patterns
- Fix marketplace router missing /marketplace prefix

## Exception Pattern Fixes (API-003/API-004)
- Services raise domain exceptions, endpoints let them bubble up
- Add code_quality and content_page exception modules
- Move business logic from endpoints to services (admin, auth, content_page)
- Fix exception handling in admin, shop, and vendor endpoints

## Tailwind CSS Consolidation
- Consolidate CSS to per-area files (admin, vendor, shop, platform)
- Remove shared/cdn-fallback.html and shared/css/tailwind.min.css
- Update all templates to use area-specific Tailwind output files
- Remove Node.js config (package.json, postcss.config.js, tailwind.config.js)

## Documentation & Cleanup
- Update vendor-in-token-architecture.md with completed migration status
- Update architecture-rules.md with new rules
- Move migration docs to docs/development/migration/
- Remove duplicate/obsolete documentation files
- Merge pytest.ini settings into pyproject.toml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 22:24:45 +01:00

9.2 KiB

Admin Sidebar Navigation

Overview

The admin sidebar provides navigation across all admin pages. It features collapsible sections with state persistence, active page indicators, and responsive mobile support.

File Location: app/templates/admin/partials/sidebar.html


Sidebar Structure

The sidebar is organized into the following sections:

Section Collapsible Pages
Dashboard No Dashboard
Platform Administration Yes Companies, Vendors, Users, Customers, Marketplace
Content Management Yes Platform Homepage, Content Pages, Vendor Themes
Developer Tools Yes Components, Icons, Testing Hub, Code Quality
Platform Monitoring Yes Import Jobs, Application Logs
Settings No Settings

Collapsible Sections

How It Works

Sections can be expanded/collapsed by clicking the section header. The state is persisted to localStorage so sections remain open/closed across page navigation and browser sessions.

State Management

File: static/admin/js/init-alpine.js

// Default state: Platform Administration open, others closed
const defaultSections = {
    platformAdmin: true,
    contentMgmt: false,
    devTools: false,
    monitoring: false
};

// State stored in localStorage under this key
const SIDEBAR_STORAGE_KEY = 'admin_sidebar_sections';

Available Methods

Method Description
toggleSection(section) Toggle a section open/closed
expandSectionForCurrentPage() Auto-expand section containing current page
openSections.platformAdmin Check if Platform Administration is open
openSections.contentMgmt Check if Content Management is open
openSections.devTools Check if Developer Tools is open
openSections.monitoring Check if Platform Monitoring is open

CSS Transitions

Sections animate smoothly using CSS transitions (no plugins required):

<ul
    x-show="openSections.platformAdmin"
    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"
>

Chevron Icon Rotation

The chevron icon rotates 180 degrees when a section is expanded:

<span
    x-html="$icon('chevron-down', 'w-4 h-4 transition-transform duration-200')"
    :class="{ 'rotate-180': openSections.platformAdmin }"
></span>

Page-to-Section Mapping

Pages are mapped to their parent sections for auto-expansion:

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'
};

Complete Page Mapping

Page currentPage Value Section URL
Dashboard 'dashboard' (always visible) /admin/dashboard
Companies 'companies' Platform Administration /admin/companies
Vendors 'vendors' Platform Administration /admin/vendors
Users 'users' Platform Administration /admin/users
Customers 'customers' Platform Administration /admin/customers
Marketplace 'marketplace' Platform Administration /admin/marketplace
Platform Homepage 'platform-homepage' Content Management /admin/platform-homepage
Content Pages 'content-pages' Content Management /admin/content-pages
Vendor Themes 'vendor-theme' Content Management /admin/vendor-themes
Components 'components' Developer Tools /admin/components
Icons 'icons' Developer Tools /admin/icons
Testing Hub 'testing' Developer Tools /admin/testing
Code Quality 'code-quality' Developer Tools /admin/code-quality
Import Jobs 'imports' Platform Monitoring /admin/imports
Application Logs 'logs' Platform Monitoring /admin/logs
Settings 'settings' (always visible) /admin/settings

Active Page Indicator

Each menu item shows a purple vertical bar when active:

<li class="relative px-6 py-3">
    <!-- Purple bar indicator (shows when page is active) -->
    <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>

    <!-- Link with active text styling -->
    <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>

Setting currentPage in Components

Each page component must set currentPage to match the sidebar:

function adminVendors() {
    return {
        ...data(),                    // Inherit base (includes openSections)
        currentPage: 'vendors',       // Must match sidebar check
        // ... rest of component
    };
}

Jinja2 Macros

The sidebar uses Jinja2 macros for DRY code:

section_header

Creates a collapsible section header with chevron:

{% macro section_header(title, section_key) %}
<button
    @click="toggleSection('{{ section_key }}')"
    class="flex items-center justify-between w-full px-6 py-2 ..."
>
    <span>{{ title }}</span>
    <span x-html="$icon('chevron-down', '...')"
          :class="{ 'rotate-180': openSections.{{ section_key }} }"></span>
</button>
{% endmacro %}

section_content

Wraps section items with collapse animation:

{% macro section_content(section_key) %}
<ul x-show="openSections.{{ section_key }}" x-transition:...>
    {{ caller() }}
</ul>
{% endmacro %}

menu_item

Creates a menu item with active indicator:

{% macro menu_item(page_id, url, icon, label) %}
<li class="relative px-6 py-3">
    <span x-show="currentPage === '{{ page_id }}'" class="..."></span>
    <a href="{{ url }}">
        <span x-html="$icon('{{ icon }}')"></span>
        <span class="ml-4">{{ label }}</span>
    </a>
</li>
{% endmacro %}

Usage Example

{{ 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') }}
{% endcall %}

Adding a New Page

Step 1: Add Route

Add the route in app/routes/admin_pages.py:

@router.get("/new-page", response_class=HTMLResponse, include_in_schema=False)
async def admin_new_page(
    request: Request,
    current_user: User = Depends(get_current_admin_from_cookie_or_header),
):
    return templates.TemplateResponse(
        "admin/new-page.html",
        {"request": request, "user": current_user},
    )

Step 2: Create Template

Create app/templates/admin/new-page.html:

{% extends "admin/base.html" %}
{% block title %}New Page{% endblock %}
{% block alpine_data %}adminNewPage(){% endblock %}
{% block content %}
    <!-- Your content -->
{% endblock %}

Step 3: Create JavaScript Component

Create static/admin/js/new-page.js:

function adminNewPage() {
    return {
        ...data(),
        currentPage: 'new-page',  // Must match sidebar
        // ...
    };
}

Step 4: Add to Sidebar

Edit app/templates/admin/partials/sidebar.html:

{# Add to appropriate section #}
{{ menu_item('new-page', '/admin/new-page', 'icon-name', 'New Page') }}

Step 5: Update Page-Section Map (if in collapsible section)

Edit static/admin/js/init-alpine.js:

const pageSectionMap = {
    // ... existing mappings
    'new-page': 'platformAdmin',  // Add mapping
};

Mobile Sidebar

The sidebar has a mobile version that slides in from the left:

  • Toggle: Click hamburger menu in header
  • Close: Click outside, press Escape, or navigate
  • State: Controlled by isSideMenuOpen
// In base data()
isSideMenuOpen: false,
toggleSideMenu() {
    this.isSideMenuOpen = !this.isSideMenuOpen
},
closeSideMenu() {
    this.isSideMenuOpen = false
}

Testing Checklist

  • Sections expand/collapse on header click
  • Chevron rotates when section opens/closes
  • Section state persists after page reload
  • Section state persists across different pages
  • Active page shows purple bar indicator
  • Active page text is highlighted
  • Mobile sidebar opens/closes correctly
  • Collapsible sections work on mobile
  • All navigation links work correctly