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>
This commit is contained in:
2025-12-04 22:24:45 +01:00
parent 76f8a59954
commit 8a367077e1
85 changed files with 21787 additions and 134978 deletions

View File

@@ -1,131 +1,158 @@
# Complete Implementation Guide - Testing Hub, Components & Icons
# Admin Sidebar Navigation
## 🎉 What's Been Created
## Overview
### ✅ All Files Follow Your Alpine.js Architecture Perfectly!
The admin sidebar provides navigation across all admin pages. It features collapsible sections with state persistence, active page indicators, and responsive mobile support.
1. **Testing Hub** - Manual QA tools
2. **Components Library** - UI component reference with navigation
3. **Icons Browser** - Searchable icon library with copy-to-clipboard
4. **Sidebar Fix** - Active menu indicator for all pages
**File Location:** `app/templates/admin/partials/sidebar.html`
---
## 📦 Files Created
## Sidebar Structure
### JavaScript Files (Alpine.js Components)
1. **[testing-hub.js](computer:///mnt/user-data/outputs/testing-hub.js)** - Testing hub component
2. **[components.js](computer:///mnt/user-data/outputs/components.js)** - Components library component
3. **[icons-page.js](computer:///mnt/user-data/outputs/icons-page.js)** - Icons browser component
The sidebar is organized into the following sections:
### HTML Templates
1. **[testing-hub.html](computer:///mnt/user-data/outputs/testing-hub.html)** - Testing hub page
2. **[components.html](computer:///mnt/user-data/outputs/components.html)** - Components library page
3. **[icons.html](computer:///mnt/user-data/outputs/icons.html)** - Icons browser page
### Sidebar Update
1. **[sidebar-fixed.html](computer:///mnt/user-data/outputs/sidebar-fixed.html)** - Fixed sidebar with active indicators
### Documentation
1. **[ARCHITECTURE_CONFIRMATION.md](computer:///mnt/user-data/outputs/ARCHITECTURE_CONFIRMATION.md)** - Architecture confirmation
| 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 |
---
## 🔧 Installation Steps
## Collapsible Sections
### Step 1: Install JavaScript Files
### How It Works
```bash
# Copy to your static directory
cp outputs/testing-hub.js static/admin/js/testing-hub.js
cp outputs/components.js static/admin/js/components.js
cp outputs/icons-page.js static/admin/js/icons-page.js
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';
```
### Step 2: Install HTML Templates
### Available Methods
```bash
# Copy to your templates directory
cp outputs/testing-hub.html app/templates/admin/testing-hub.html
cp outputs/components.html app/templates/admin/components.html
cp outputs/icons.html app/templates/admin/icons.html
```
| 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 |
### Step 3: Fix Sidebar (IMPORTANT!)
### CSS Transitions
```bash
# Replace your current sidebar
cp outputs/sidebar-fixed.html app/templates/partials/sidebar.html
```
### Step 4: Add Icons Route
Update `app/api/v1/admin/pages.py` - add this route:
```python
@router.get("/icons", response_class=HTMLResponse, include_in_schema=False)
async def admin_icons_page(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render icons browser page.
Browse and search all available icons.
"""
return templates.TemplateResponse(
"admin/icons.html",
{
"request": request,
"user": current_user,
}
)
```
### Step 5: Verify Icons Are Updated
Make sure you're using the updated `icons-updated.js` from earlier:
```bash
cp outputs/icons-updated.js static/shared/js/icons.js
```
### Step 6: Restart Server
```bash
# Stop current server (Ctrl+C)
# Start again
uvicorn app.main:app --reload
```
---
## 🐛 Sidebar Active Indicator Fix
### The Problem
You noticed that only the Dashboard menu item showed the vertical purple bar on the left when active. Other menu items didn't show this indicator.
### The Root Cause
Each page's JavaScript component needs to set `currentPage` correctly, and the sidebar HTML needs to check for that value.
**Before (Only Dashboard worked):**
```html
<!-- Only dashboard had the x-show condition -->
<span x-show="currentPage === 'dashboard'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"></span>
```
### The Fix
**1. Sidebar HTML** - Add the indicator `<span>` to EVERY menu item:
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
<!-- Vendors -->
<li class="relative px-6 py-3">
<!-- ✅ Add this span for the purple bar -->
<span x-show="currentPage === 'vendors'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"></span>
<a :class="currentPage === 'vendors' ? 'text-gray-800 dark:text-gray-100' : ''"
<!-- 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>
@@ -133,329 +160,179 @@ Each page's JavaScript component needs to set `currentPage` correctly, and the s
</li>
```
**2. JavaScript Files** - Each component must set `currentPage`:
### Setting currentPage in Components
Each page component must set `currentPage` to match the sidebar:
```javascript
// vendors.js
function adminVendors() {
return {
...data(),
currentPage: 'vendors', // Must match sidebar check
// ... rest of component
};
}
// users.js
function adminUsers() {
return {
...data(),
currentPage: 'users', // ✅ Must match sidebar check
...data(), // Inherit base (includes openSections)
currentPage: 'vendors', // Must match sidebar check
// ... rest of component
};
}
```
### Complete Page Mapping
| Page | JavaScript `currentPage` | Sidebar Check | URL |
|------|--------------------------|---------------|-----|
| Dashboard | `'dashboard'` | `x-show="currentPage === 'dashboard'"` | `/admin/dashboard` |
| Companies | `'companies'` | `x-show="currentPage === 'companies'"` | `/admin/companies` |
| Vendors | `'vendors'` | `x-show="currentPage === 'vendors'"` | `/admin/vendors` |
| Users | `'users'` | `x-show="currentPage === 'users'"` | `/admin/users` |
| Customers | `'customers'` | `x-show="currentPage === 'customers'"` | `/admin/customers` |
| Marketplace | `'marketplace'` | `x-show="currentPage === 'marketplace'"` | `/admin/marketplace` |
| Imports | `'imports'` | `x-show="currentPage === 'imports'"` | `/admin/imports` |
| Components | `'components'` | `x-show="currentPage === 'components'"` | `/admin/components` |
| Icons | `'icons'` | `x-show="currentPage === 'icons'"` | `/admin/icons` |
| Testing | `'testing'` | `x-show="currentPage === 'testing'"` | `/admin/testing` |
| Settings | `'settings'` | `x-show="currentPage === 'settings'"` | `/admin/settings` |
### Updated Sidebar Structure
The sidebar is organized into the following sections:
**Dashboard:**
- Dashboard
**Platform Administration:**
- Companies
- Vendors
- Users
- Customers
- Marketplace
**Content Management:**
- Platform Homepage
- Content Pages
- Vendor Themes
**Developer Tools:**
- Components
- Icons
- Testing Hub
- Code Quality
**Platform Monitoring:**
- Import Jobs
- Application Logs
**Settings:**
- Settings
Each section is properly separated with dividers and all menu items have active indicators.
---
## ✨ New Features
## Jinja2 Macros
### Testing Hub
- **2 Test Suites**: Auth Flow and Data Migration
- **Stats Cards**: Quick metrics overview
- **Interactive Cards**: Click to run tests
- **Best Practices**: Testing guidelines
- **Resource Links**: To Components and Icons pages
The sidebar uses Jinja2 macros for DRY code:
### Components Library
- **Sticky Section Navigation**: Jump to Forms, Buttons, Cards, etc.
- **Hash-based URLs**: Bookmarkable sections (#forms, #buttons)
- **Copy to Clipboard**: Click to copy component code
- **Live Examples**: All components with real Alpine.js
- **Dark Mode**: All examples support dark mode
### section_header
### Icons Browser
- **Search Functionality**: Filter icons by name
- **Category Navigation**: Browse by category
- **Live Preview**: See icons in multiple sizes
- **Copy Icon Name**: Quick copy to clipboard
- **Copy Usage Code**: Copy Alpine.js usage code
- **Selected Icon Details**: Full preview and size examples
- **Auto-categorization**: Icons organized automatically
Creates a collapsible section header with chevron:
---
## 🎯 How Each Feature Works
### Components Library Navigation
1. **Click a section** in the left sidebar
2. **Page scrolls** to that section smoothly
3. **URL updates** with hash (#forms)
4. **Active section** is highlighted in purple
5. **Bookmarkable**: Share URL with #section
```javascript
// How it works
goToSection(sectionId) {
this.activeSection = sectionId;
window.location.hash = sectionId;
// Smooth scroll
document.getElementById(sectionId).scrollIntoView({ behavior: 'smooth' });
}
```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 %}
```
### Icons Browser Search
### section_content
1. **Type in search box** - filters as you type
2. **Click category pill** - filters by category
3. **Click any icon** - shows details panel
4. **Hover icon** - shows copy buttons
5. **Click copy** - copies to clipboard
Wraps section items with collapse animation:
```javascript
// How it works
filterIcons() {
let icons = this.allIcons;
// Filter by category
if (this.activeCategory !== 'all') {
icons = icons.filter(icon => icon.category === this.activeCategory);
}
// Filter by search
if (this.searchQuery.trim()) {
const query = this.searchQuery.toLowerCase();
icons = icons.filter(icon => icon.name.toLowerCase().includes(query));
}
this.filteredIcons = icons;
}
```jinja2
{% macro section_content(section_key) %}
<ul x-show="openSections.{{ section_key }}" x-transition:...>
{{ caller() }}
</ul>
{% endmacro %}
```
### Testing Hub Navigation
### menu_item
1. **View stats** at the top
2. **Read test suite cards** with features
3. **Click "Run Tests"** to go to test page
4. **Read best practices** before testing
Creates a menu item with active indicator:
---
## 🧪 Testing Checklist
After installation, verify:
### General
- [ ] Server starts without errors
- [ ] All routes load successfully
- [ ] Icons display correctly everywhere
- [ ] Dark mode works on all pages
### Sidebar
- [ ] Dashboard shows purple bar when active
- [ ] Vendors shows purple bar when active
- [ ] Users shows purple bar when active
- [ ] Components shows purple bar when active
- [ ] Icons shows purple bar when active
- [ ] Testing shows purple bar when active
- [ ] Text is bold/highlighted on active page
### Testing Hub
- [ ] Page loads at `/admin/testing`
- [ ] Stats cards display correctly
- [ ] Test suite cards are clickable
- [ ] Icons render properly
- [ ] Links to other pages work
### Components Library
- [ ] Page loads at `/admin/components`
- [ ] Section navigation works
- [ ] Clicking section scrolls to it
- [ ] URL hash updates (#forms, etc.)
- [ ] Copy buttons work
- [ ] All form examples render
- [ ] Toast examples work
### Icons Browser
- [ ] Page loads at `/admin/icons`
- [ ] Shows correct icon count
- [ ] Search filters icons
- [ ] Category pills filter icons
- [ ] Clicking icon shows details
- [ ] Copy name button works
- [ ] Copy usage button works
- [ ] Preview shows multiple sizes
---
## 🎨 Customization
### Adding More Test Suites
Edit `testing-hub.js`:
```javascript
testSuites: [
// ... existing suites
{
id: 'new-suite',
name: 'New Test Suite',
description: 'Description here',
url: '/admin/test/new-suite',
icon: 'icon-name',
color: 'blue', // blue, orange, green, purple
testCount: 5,
features: [
'Feature 1',
'Feature 2'
]
}
]
```
### Adding More Component Sections
Edit `components.js`:
```javascript
sections: [
// ... existing sections
{ id: 'new-section', name: 'New Section', icon: 'icon-name' }
]
```
Then add the section HTML in `components.html`:
```html
<section id="new-section">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h2>New Section</h2>
<!-- Your components here -->
</div>
</section>
```
### Adding More Icon Categories
Edit `icons-page.js`:
```javascript
categories: [
// ... existing categories
{ id: 'new-category', name: 'New Category', icon: 'icon-name' }
]
```
And update the `categorizeIcon()` function:
```javascript
categoryMap: {
// ... existing mappings
'new-category': ['keyword1', 'keyword2']
}
```
---
## 📚 Quick Reference
### Alpine.js Component Pattern
```javascript
function yourPageComponent() {
return {
...data(), // ✅ Inherit base
currentPage: 'name', // ✅ Set page ID
async init() {
if (window._yourPageInitialized) return;
window._yourPageInitialized = true;
// Your init code
}
};
}
```
### Sidebar Menu Item Pattern
```html
```jinja2
{% macro menu_item(page_id, url, icon, label) %}
<li class="relative px-6 py-3">
<!-- Active indicator -->
<span x-show="currentPage === 'page-name'"
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg">
</span>
<!-- Link -->
<a :class="currentPage === 'page-name' ? 'text-gray-800 dark:text-gray-100' : ''"
href="/admin/page-name">
<span x-html="$icon('icon-name')"></span>
<span class="ml-4">Page Name</span>
<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 %}
```
---
## 🎯 Summary
## Adding a New Page
**Architecture:** ✅ All files follow your Alpine.js patterns perfectly
**Sidebar:** ✅ Fixed - all menu items now show active indicator
**Testing Hub:** ✅ Complete with test suites and navigation
**Components:** ✅ Complete with section navigation and copy feature
**Icons:** ✅ Complete with search, categories, and copy features
### Step 1: Add Route
**Total Files:** 7 (3 JS + 3 HTML + 1 Sidebar)
**Lines of Code:** ~2000+
**Features Added:** 20+
Add the route in `app/routes/admin_pages.py`:
Everything is ready to install! 🚀
```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

View File

@@ -1,144 +1,86 @@
# UI Components Library Implementation Guide
# UI Components Library
**Version:** 2.0
**Last Updated:** December 2024
**Live Reference:** `/admin/components`
---
## Overview
This guide covers the implementation of:
1. **Components reference page** - A library showcasing all your UI components
2. **Updated vendor edit page** - Using proper form components
3. **Updated vendor detail page** - Using proper card components
4. **Sidebar navigation** - Adding "Components" menu item
## Files Created
The admin panel uses a consistent set of UI components built with Tailwind CSS and Alpine.js. All components support dark mode and are fully accessible.
### 1. Components Library Page
**File:** `app/templates/admin/components.html`
- Complete reference for all UI components
- Quick navigation to sections (Forms, Buttons, Cards, etc.)
- Copy-paste ready examples
- Shows validation states, disabled states, helper text, etc.
**Live Component Library:** Visit `/admin/components` in the admin panel to see all components with copy-paste ready code.
### 2. Updated Vendor Edit Page
**File:** `app/templates/admin/vendor-edit-updated.html`
- Uses proper form components from your library
- Improved visual hierarchy with card sections
- Better validation state displays (red borders for errors)
- Quick actions section at the top
- Status badges showing current state
- Clean, consistent styling throughout
---
### 3. Vendor Detail Page
**File:** `app/templates/admin/vendor-detail.html`
- NEW file (didn't exist before)
- Uses card components to display vendor information
- Status cards showing verification, active status, dates
- Information organized in clear sections
- All vendor data displayed in readable format
- Delete action button
## Page Layout Structure
### 4. JavaScript for Detail Page
**File:** `static/admin/js/vendor-detail.js`
- Loads vendor data
- Handles delete action with double confirmation
- Logging for debugging
- Error handling
All admin list pages follow a consistent structure:
## Implementation Steps
### Step 1: Add Components Menu to Sidebar
Update your `app/templates/admin/sidebar.html` (or wherever your sidebar is defined):
```html
<!-- Add this menu item after "Settings" or wherever appropriate -->
<li class="relative px-6 py-3">
<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="{ 'text-gray-800 dark:text-gray-100': currentPage === 'components' }"
href="/admin/components"
>
<span x-html="$icon('collection', 'w-5 h-5')"></span>
<span class="ml-4">Components</span>
</a>
</li>
```
┌─────────────────────────────────────────────────────┐
│ Page Header (Title + Action Button) │
├─────────────────────────────────────────────────────┤
│ Stats Cards (4 columns on desktop) │
├─────────────────────────────────────────────────────┤
│ Search & Filters Bar │
├─────────────────────────────────────────────────────┤
│ Data Table │
├── Table Header │
├── Table Rows │
└── Pagination │
└─────────────────────────────────────────────────────┘
```
### Step 2: Add Components Page Route
---
Update your `app/api/v1/admin/pages.py`:
## Form Components
```python
@router.get("/components", response_class=HTMLResponse, include_in_schema=False)
async def admin_components_page(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
"""
Render UI components reference page.
Shows all available UI components for easy reference.
"""
return templates.TemplateResponse(
"admin/components.html",
{
"request": request,
"user": current_user,
}
)
```
### Basic Input
### Step 3: Replace Vendor Edit Template
1. Backup your current: `app/templates/admin/vendor-edit.html`
2. Replace it with: `vendor-edit-updated.html`
3. Keep your existing `vendor-edit.js` (no changes needed)
### Step 4: Add Vendor Detail Template & JavaScript
1. Copy `vendor-detail.html` to `app/templates/admin/vendor-detail.html`
2. Copy `vendor-detail.js` to `static/admin/js/vendor-detail.js`
## Component Usage Guide
### Form Components
#### Basic Input
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">
Label Text <span class="text-red-600">*</span>
</span>
<input
type="text"
x-model="formData.fieldName"
required
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
/>
</label>
```
#### Input with Validation Error
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">Field Label</span>
<input
type="text"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.fieldName }"
x-model="formData.field"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
/>
<span x-show="errors.fieldName" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.fieldName"></span>
</label>
```
#### Disabled Input
### Required Field with Validation
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">
Email Address <span class="text-red-600">*</span>
</span>
<input
type="email"
x-model="formData.email"
required
:class="{ 'border-red-600 focus:border-red-400 focus:shadow-outline-red': errors.email }"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple form-input"
/>
<span x-show="errors.email" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.email"></span>
</label>
```
### Disabled/Read-Only Input
```html
<input
type="text"
x-model="data.field"
disabled
value="Read-only value"
class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
/>
```
#### Textarea
### Textarea
```html
<textarea
x-model="formData.description"
@@ -147,26 +89,90 @@ async def admin_components_page(
></textarea>
```
### Card Components
### Select
```html
<select
x-model="formData.option"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-select"
>
<option value="">Select an option</option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
```
---
## Button Components
### Primary Button
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
Primary Button
</button>
```
### Button with Icon
```html
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Add Item
</button>
```
### Secondary Button
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
Cancel
</button>
```
### Danger Button
```html
<button class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700">
Delete
</button>
```
---
## Card Components
### Stats Card
#### Stats Card
```html
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
<span x-html="$icon('user-group', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Total Users
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
1,234
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total">
0
</p>
</div>
</div>
```
#### Info Card
### Stats Card Colors
| Color | Use Case | Classes |
|-------|----------|---------|
| Blue | Total counts | `text-blue-500 bg-blue-100` |
| Green | Active/Success | `text-green-500 bg-green-100` |
| Red | Inactive/Errors | `text-red-500 bg-red-100` |
| Orange | Warnings/Admin | `text-orange-500 bg-orange-100` |
| Purple | Primary metrics | `text-purple-500 bg-purple-100` |
### Info Card
```html
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
@@ -181,125 +187,281 @@ async def admin_components_page(
</div>
```
### Button Components
---
## Status Badges
### Success Badge
#### Primary Button
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
Button Text
</button>
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-green-700 bg-green-100 rounded-full dark:bg-green-700 dark:text-green-100">
<span x-html="$icon('check-circle', 'w-3 h-3 mr-1')"></span>
Active
</span>
```
#### Button with Icon
### Warning Badge
```html
<button class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Add Item
</button>
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-orange-700 bg-orange-100 rounded-full dark:bg-orange-700 dark:text-orange-100">
<span x-html="$icon('clock', 'w-3 h-3 mr-1')"></span>
Pending
</span>
```
#### Secondary Button
### Danger Badge
```html
<button class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg hover:border-gray-400 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
Cancel
</button>
<span class="inline-flex items-center px-3 py-1 text-xs font-semibold leading-tight text-red-700 bg-red-100 rounded-full dark:bg-red-700 dark:text-red-100">
<span x-html="$icon('x-circle', 'w-3 h-3 mr-1')"></span>
Inactive
</span>
```
## Features of Updated Pages
### Dynamic Badge
### Vendor Edit Page Improvements
```html
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs"
:class="item.is_active
? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
: 'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100'"
x-text="item.is_active ? 'Active' : 'Inactive'">
</span>
```
1. **Quick Actions Section**
- Verify/Unverify button
- Activate/Deactivate button
- Status badges showing current state
2. **Better Form Organization**
- Clear sections with headers
- Two-column layout on desktop
- Helper text for all fields
- Proper validation states
3. **Visual Consistency**
- Uses standard form components
- Consistent spacing and sizing
- Dark mode support
4. **User Experience**
- Disabled states for read-only fields
- Clear indication of required fields
- Loading states
- Error messages inline with fields
---
### Vendor Detail Page Features
## Data Tables
1. **Status Overview**
- 4 stats cards at top showing key metrics
- Visual status indicators (colors, icons)
2. **Information Organization**
- Basic info card
- Contact info card
- Business details section
- Owner information section
- Marketplace URLs (if available)
3. **Actions**
- Edit button (goes to edit page)
- Delete button (with double confirmation)
- Back to list button
### Basic Table Structure
## Quick Reference: Where to Find Components
```html
<div class="w-full overflow-hidden rounded-lg shadow-xs">
<div class="w-full overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
<template x-for="item in items" :key="item.id">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-4 py-3" x-text="item.name"></td>
<td class="px-4 py-3"><!-- Status badge --></td>
<td class="px-4 py-3"><!-- Action buttons --></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
```
When you need a component, visit `/admin/components` and you'll find:
### Action Buttons
- **Forms Section**: All input types, validation states, helper text
- **Buttons Section**: All button styles and states
- **Cards Section**: Stats cards, info cards
- **Tables Section**: (from your tables.html)
- **Modals Section**: (from your modals.html)
- **Charts Section**: (from your charts.html)
```html
<div class="flex items-center space-x-2 text-sm">
<!-- View -->
<a :href="'/admin/resource/' + item.id"
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700"
title="View">
<span x-html="$icon('eye', 'w-5 h-5')"></span>
</a>
## Testing Checklist
<!-- Edit -->
<a :href="'/admin/resource/' + item.id + '/edit'"
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700"
title="Edit">
<span x-html="$icon('edit', 'w-5 h-5')"></span>
</a>
- [ ] `/admin/components` page loads and displays all components
- [ ] Components menu item appears in sidebar
- [ ] `/admin/vendors/{vendor_code}/edit` displays correctly
- [ ] Form validation shows errors in red
- [ ] Quick actions (verify/activate) work
- [ ] `/admin/vendors/{vendor_code}` displays all vendor data
- [ ] Status cards show correct information
- [ ] Edit button navigates to edit page
- [ ] Delete button shows double confirmation
- [ ] All pages work in dark mode
- [ ] All pages are responsive on mobile
<!-- Delete -->
<button @click="deleteItem(item)"
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700"
title="Delete">
<span x-html="$icon('delete', 'w-5 h-5')"></span>
</button>
</div>
```
## Color Scheme Reference
---
Your component library uses these color schemes:
## Loading & Error States
- **Primary**: Purple (`bg-purple-600`, `text-purple-600`)
- **Success**: Green (`bg-green-600`, `text-green-600`)
- **Warning**: Orange (`bg-orange-600`, `text-orange-600`)
- **Danger**: Red (`bg-red-600`, `text-red-600`)
- **Info**: Blue (`bg-blue-600`, `text-blue-600`)
### Loading State
## Next Steps
```html
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading...</p>
</div>
```
1. Implement the components page route
2. Add menu item to sidebar
3. Replace vendor-edit.html
4. Add vendor-detail.html and .js
5. Test all pages
6. Apply same patterns to other admin pages (users, imports, etc.)
### Error Alert
## Tips
```html
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold">Error loading data</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
```
- Always reference `/admin/components` when building new pages
- Copy component HTML directly from the components page
- Maintain consistent spacing and styling
- Use Alpine.js x-model for form bindings
- Use your icon system with `x-html="$icon('icon-name', 'w-5 h-5')"`
- Test in both light and dark modes
### Empty State
Enjoy your new component library! 🎨
```html
<div class="flex flex-col items-center py-12">
<span x-html="$icon('inbox', 'w-12 h-12 text-gray-400 mb-4')"></span>
<p class="text-lg font-medium text-gray-600 dark:text-gray-400">No items found</p>
<p class="text-sm text-gray-500" x-text="filters.search ? 'Try adjusting your search' : 'Create your first item'"></p>
</div>
```
---
## Modal Components
### Confirmation Modal
```html
<div
x-show="showModal"
x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
@click.self="showModal = false">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 m-4 max-w-md w-full">
<div class="flex items-start justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
Confirm Action
</h3>
<button @click="showModal = false" class="text-gray-400 hover:text-gray-600">
<span x-html="$icon('x', 'w-5 h-5')"></span>
</button>
</div>
<p class="mb-6 text-sm text-gray-600 dark:text-gray-400">
Are you sure you want to perform this action?
</p>
<div class="flex justify-end gap-3">
<button @click="showModal = false"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600">
Cancel
</button>
<button @click="confirmAction()"
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
Confirm
</button>
</div>
</div>
</div>
```
---
## Toast Notifications
Use the global Utils helper:
```javascript
Utils.showToast('Operation successful!', 'success');
Utils.showToast('Something went wrong', 'error');
Utils.showToast('Please check your input', 'warning');
Utils.showToast('Here is some information', 'info');
```
---
## Grid Layouts
### 2 Columns (Desktop)
```html
<div class="grid gap-6 md:grid-cols-2">
<div>Column 1</div>
<div>Column 2</div>
</div>
```
### 4 Columns (Stats Cards)
```html
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<!-- Stats cards here -->
</div>
```
---
## Color Reference
| Type | Primary | Success | Warning | Danger | Info |
|------|---------|---------|---------|--------|------|
| Background | `bg-purple-600` | `bg-green-600` | `bg-orange-600` | `bg-red-600` | `bg-blue-600` |
| Text | `text-purple-600` | `text-green-600` | `text-orange-600` | `text-red-600` | `text-blue-600` |
| Light BG | `bg-purple-100` | `bg-green-100` | `bg-orange-100` | `bg-red-100` | `bg-blue-100` |
---
## Common Icons
| Icon | Use Case |
|------|----------|
| `user-group` | Users/Teams |
| `badge-check` | Verified |
| `check-circle` | Success |
| `x-circle` | Error/Inactive |
| `clock` | Pending |
| `calendar` | Dates |
| `edit` | Edit |
| `delete` | Delete |
| `plus` | Add |
| `arrow-left` | Back |
| `exclamation` | Warning |
| `spinner` | Loading |
---
## JavaScript Patterns
### List Page Structure
```javascript
function adminResourceList() {
return {
...data(), // Inherit base layout
currentPage: 'resource-name',
// State
items: [],
loading: false,
error: null,
filters: { search: '', status: '' },
stats: {},
pagination: { page: 1, per_page: 10, total: 0 },
async init() {
await this.loadItems();
await this.loadStats();
},
async loadItems() { /* ... */ },
debouncedSearch() { /* ... */ },
async deleteItem(item) { /* ... */ }
};
}
```
---
## Related Documentation
- [Tailwind CSS](../tailwind-css.md)
- [Tailwind CSS Official Docs](https://tailwindcss.com/docs)
- [Alpine.js Official Docs](https://alpinejs.dev/)