## 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>
339 lines
9.2 KiB
Markdown
339 lines
9.2 KiB
Markdown
# 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`
|
|
|
|
```javascript
|
|
// 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):
|
|
|
|
```html
|
|
<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:
|
|
|
|
```html
|
|
<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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```html
|
|
<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:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```jinja2
|
|
{% 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:
|
|
|
|
```jinja2
|
|
{% 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:
|
|
|
|
```jinja2
|
|
{% 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
|
|
|
|
```jinja2
|
|
{{ 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`:
|
|
|
|
```python
|
|
@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`:
|
|
|
|
```jinja2
|
|
{% 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`:
|
|
|
|
```javascript
|
|
function adminNewPage() {
|
|
return {
|
|
...data(),
|
|
currentPage: 'new-page', // Must match sidebar
|
|
// ...
|
|
};
|
|
}
|
|
```
|
|
|
|
### Step 4: Add to Sidebar
|
|
|
|
Edit `app/templates/admin/partials/sidebar.html`:
|
|
|
|
```jinja2
|
|
{# 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`:
|
|
|
|
```javascript
|
|
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`
|
|
|
|
```javascript
|
|
// 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
|