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

@@ -1585,7 +1585,7 @@ async applyFilters() {
// marketplace.js
return {
...data(),
currentPage: 'marketplace', // Highlights "Marketplace Import" in sidebar
currentPage: 'marketplace', // Highlights "Marketplace" in sidebar
// ...
};
@@ -1597,6 +1597,28 @@ return {
};
```
### Collapsible Sections
Sidebar sections are collapsible with state persisted to localStorage:
```javascript
// Section keys used in openSections state
{
platformAdmin: true, // Platform Administration (default open)
contentMgmt: false, // Content Management
devTools: false, // Developer Tools
monitoring: false // Platform Monitoring
}
// Toggle a section
toggleSection('devTools');
// Check if section is open
if (openSections.devTools) { ... }
```
See [Sidebar Navigation](../shared/sidebar.md) for full documentation.
---
## 🎨 UI Patterns

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/)

View File

@@ -1,6 +1,6 @@
# Tailwind CSS Build Guide
**Version:** 1.0
**Version:** 2.0
**Last Updated:** December 2024
**Audience:** Frontend Developers
@@ -8,17 +8,7 @@
## Overview
The platform uses [Tailwind CSS](https://tailwindcss.com/) for styling with a **dual-layer architecture**:
1. **Base Layer (CDN):** Tailwind CSS v2.2.19 loaded via CDN with local fallback
2. **Override Layer (npm build):** Custom Windmill Dashboard theme built from Tailwind v1.4.6
This layered approach allows:
- Fast loading via CDN for base utilities
- Custom theme extensions (colors, dark mode, forms) via npm build
- Offline support via local fallback
> **Note:** A migration to Tailwind v3.4 is planned. See [Migration Plan](tailwind-migration-plan.md).
The platform uses [Tailwind CSS v4](https://tailwindcss.com/) with the **Standalone CLI** - no Node.js required. Each frontend (admin, vendor, shop, platform) has its own dedicated CSS configuration.
---
@@ -27,180 +17,230 @@ This layered approach allows:
### How It Works
```
Browser loads:
1. CDN Tailwind 2.2.19 (base utilities)
└── Fallback: static/shared/css/tailwind.min.css
2. Custom tailwind.output.css (overrides/extensions)
└── Built from: static/admin/css/tailwind.css
└── Contains: Windmill Dashboard theme, custom colors, dark mode
Tailwind Standalone CLI (single binary, no npm)
├── static/admin/css/tailwind.css → tailwind.output.css (Admin)
├── static/vendor/css/tailwind.css → tailwind.output.css (Vendor)
├── static/shop/css/tailwind.css → tailwind.output.css (Shop)
└── static/platform/css/tailwind.css → tailwind.output.css (Platform)
```
### Key Files
| File | Version | Purpose |
|------|---------|---------|
| CDN `tailwindcss@2.2.19` | 2.2.19 | Base Tailwind utilities |
| `static/shared/css/tailwind.min.css` | 2.2.19 | Local fallback for CDN |
| `tailwind.config.js` | 1.4.6 | Custom build configuration |
| `static/admin/css/tailwind.css` | - | Source file with directives |
| `static/admin/css/tailwind.output.css` | 1.4.6 | Compiled custom styles |
| `static/vendor/css/tailwind.output.css` | 1.4.6 | Compiled custom styles |
| File | Purpose |
|------|---------|
| `~/.local/bin/tailwindcss` | Standalone CLI binary (v4.x) |
| `static/*/css/tailwind.css` | CSS-first source configuration |
| `static/*/css/tailwind.output.css` | Compiled output (do not edit) |
### Template Loading Order
### Template Loading
Each frontend loads its own CSS:
```html
<!-- 1. Base Tailwind from CDN (with fallback) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='/static/shared/css/tailwind.min.css';">
<!-- Admin -->
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
<!-- 2. Custom overrides (built via npm) -->
<link rel="stylesheet" href="/static/admin/css/tailwind.output.css" />
<!-- Vendor -->
<link rel="stylesheet" href="{{ url_for('static', path='vendor/css/tailwind.output.css') }}" />
<!-- Shop -->
<link rel="stylesheet" href="{{ url_for('static', path='shop/css/tailwind.output.css') }}" />
<!-- Platform -->
<link rel="stylesheet" href="{{ url_for('static', path='platform/css/tailwind.output.css') }}" />
```
See [CDN Fallback Strategy](cdn-fallback-strategy.md) for details on offline support.
---
## How Tailwind Purging Works
## CSS-First Configuration (Tailwind v4)
Tailwind generates thousands of utility classes. To keep the CSS file small, it "purges" (removes) unused classes by scanning your source files.
Tailwind v4 uses **CSS-first configuration** instead of `tailwind.config.js`. All customization happens directly in CSS files.
### Content Paths
### Source File Structure
The `purge.content` array in `tailwind.config.js` tells Tailwind where to look for class usage:
```css
/* static/admin/css/tailwind.css */
```javascript
purge: {
content: [
'public/**/*.html',
'app/templates/**/*.html', // Jinja2 templates
'static/**/*.js', // Alpine.js components
],
// ...
/* Import Tailwind */
@import "tailwindcss";
/* Content sources for tree-shaking */
@source "../../../app/templates/admin/**/*.html";
@source "../../js/**/*.js";
/* Custom theme (colors, fonts, spacing) */
@theme {
--color-gray-50: #f9fafb;
--color-gray-900: #121317;
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
}
/* Dark mode variant */
@variant dark (&:where(.dark, .dark *));
/* Custom utilities */
@layer utilities {
.shadow-outline-purple {
box-shadow: 0 0 0 3px hsla(262, 97%, 81%, 0.45);
}
}
/* Custom components */
@layer components {
.form-input { ... }
.btn-primary { ... }
}
```
### Safelist
### Key Directives
Some classes are used dynamically in Alpine.js expressions and can't be detected by scanning. These must be added to the `safelist`:
| Directive | Purpose | Example |
|-----------|---------|---------|
| `@import "tailwindcss"` | Import Tailwind base | Required at top |
| `@source` | Content paths for purging | `@source "../../../app/templates/**/*.html"` |
| `@theme` | Custom design tokens | `--color-purple-600: #7e3af2;` |
| `@variant` | Custom variants | `@variant dark (&:where(.dark, .dark *))` |
| `@layer utilities` | Custom utility classes | `.shadow-outline-*` |
| `@layer components` | Custom components | `.form-input`, `.btn-*` |
```javascript
purge: {
content: [...],
safelist: [
'bg-orange-600',
'bg-green-600',
'bg-red-600',
'hover:bg-orange-700',
'hover:bg-green-700',
'hover:bg-red-700',
],
---
## Custom Color Palette
All frontends share the same Windmill Dashboard color palette:
```css
@theme {
/* Gray (custom darker palette) */
--color-gray-50: #f9fafb;
--color-gray-100: #f4f5f7;
--color-gray-200: #e5e7eb;
--color-gray-300: #d5d6d7;
--color-gray-400: #9e9e9e;
--color-gray-500: #707275;
--color-gray-600: #4c4f52;
--color-gray-700: #24262d;
--color-gray-800: #1a1c23;
--color-gray-900: #121317;
/* Purple (primary) */
--color-purple-600: #7e3af2;
/* Plus: red, orange, yellow, green, teal, blue, indigo, pink, cool-gray */
}
```
**When to add to safelist:**
- Classes used in Alpine.js `:class` bindings with dynamic conditions
- Classes constructed from variables (e.g., `bg-${color}-500`)
- Classes used only in JavaScript, not in HTML templates
---
## Building Tailwind CSS
### Prerequisites
Install Node.js dependencies (one-time setup):
Install the standalone CLI (one-time setup):
```bash
npm install
make tailwind-install
```
Or using Make:
Or manually:
```bash
make npm-install
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
chmod +x tailwindcss-linux-x64
mv tailwindcss-linux-x64 ~/.local/bin/tailwindcss
```
### Development Build
For development, build without purging (includes all classes, larger file):
Build all frontends (includes all classes, larger files):
```bash
# Build admin CSS
npm run tailwind:admin
# Build vendor CSS
npm run tailwind:vendor
# Or using Make
make tailwind-dev
```
### Production Build
For production, build with purging and minification (smaller file):
Build with minification (smaller files):
```bash
npm run build
# Or using Make
make tailwind-build
```
---
### Watch Mode
## Available npm Scripts
Watch for changes during development:
| Script | Command | Description |
|--------|---------|-------------|
| `npm run tailwind:admin` | Build admin CSS (dev) | Fast, includes all classes |
| `npm run tailwind:vendor` | Build vendor CSS (dev) | Fast, includes all classes |
| `npm run build:admin` | Build admin CSS (prod) | Purged and minified |
| `npm run build:vendor` | Build vendor CSS (prod) | Purged and minified |
| `npm run build` | Build all (prod) | Runs both production builds |
---
## Adding New Utility Classes
If you need a class that doesn't exist in the compiled CSS:
### Option 1: Check if it's being purged
The class might exist but is being removed. Add it to the `safelist` in `tailwind.config.js`:
```javascript
safelist: [
'your-new-class',
// ...
]
```bash
make tailwind-watch
```
### Option 2: Extend the theme
---
Add custom values to `tailwind.config.js`:
## Makefile Targets
| Target | Description |
|--------|-------------|
| `make tailwind-install` | Install Tailwind standalone CLI |
| `make tailwind-dev` | Build all CSS (development) |
| `make tailwind-build` | Build all CSS (production, minified) |
| `make tailwind-watch` | Watch for changes |
---
## Dark Mode
The platform uses class-based dark mode with the `.dark` class on the `<html>` element:
```html
<html :class="{ 'dark': dark }">
<body class="bg-white dark:bg-gray-900">
```
Toggle dark mode with JavaScript:
```javascript
theme: {
extend: {
colors: {
'brand': '#123456',
},
spacing: {
'128': '32rem',
},
},
document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', dark);
```
---
## Adding Custom Utilities
Add custom utilities in the source CSS file:
```css
@layer utilities {
.shadow-outline-purple {
box-shadow: 0 0 0 3px hsla(262, 97%, 81%, 0.45);
}
.text-balance {
text-wrap: balance;
}
}
```
### Option 3: Rebuild CSS
---
After any config change, rebuild the CSS:
## Adding Custom Components
```bash
make tailwind-dev
Add reusable component classes:
```css
@layer components {
.form-input {
@apply block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm;
@apply focus:border-purple-500 focus:ring-1 focus:ring-purple-500;
@apply dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200;
}
.btn-primary {
@apply px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg;
@apply hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500;
}
}
```
---
@@ -209,105 +249,51 @@ make tailwind-dev
### Classes not appearing
1. **Check purge paths** - Ensure your file is in a scanned directory
2. **Check safelist** - Dynamic classes need to be safelisted
1. **Check @source paths** - Ensure your templates are in a scanned directory
2. **Check class exists** - Tailwind v4 may have renamed some classes
3. **Rebuild CSS** - Run `make tailwind-dev` after config changes
### Dynamic classes in Alpine.js
**Problem:** Classes in `:class` bindings may be purged.
Tailwind v4 uses JIT compilation and scans for complete class names. Dynamic classes work automatically if the full class name appears in your templates:
```html
<!-- This class might be purged -->
<!-- This works - full class names visible -->
<div :class="isActive ? 'bg-green-600' : 'bg-red-600'">
```
**Solution:** Add to safelist:
### Class name changes (v2 → v4)
```javascript
safelist: ['bg-green-600', 'bg-red-600']
```
### Large CSS file in development
Development builds include all Tailwind classes (~1.2MB). This is normal.
Production builds purge unused classes, resulting in much smaller files (~50-100KB typically).
---
## Configuration Reference
### tailwind.config.js Structure
```javascript
module.exports = {
// Where to scan for class usage
purge: {
content: [...],
safelist: [...],
},
// Theme customization
theme: {
// Override defaults
colors: {...},
// Extend defaults
extend: {
fontFamily: {...},
maxHeight: {...},
},
},
// Variant configuration (hover, focus, dark mode, etc.)
variants: {
backgroundColor: ['hover', 'focus', 'dark', 'dark:hover'],
textColor: ['hover', 'dark'],
// ...
},
// Plugins
plugins: [
require('tailwindcss-multi-theme'),
require('@tailwindcss/custom-forms'),
],
}
```
### Dark Mode
The platform uses `tailwindcss-multi-theme` for dark mode. Dark mode classes use the `.theme-dark` parent selector:
```css
/* Light mode */
.bg-white { ... }
/* Dark mode */
.theme-dark .dark\:bg-gray-800 { ... }
```
| v2.x | v4.x |
|------|------|
| `overflow-ellipsis` | `text-ellipsis` |
| `flex-grow-0` | `grow-0` |
| `flex-shrink-0` | `shrink-0` |
---
## Best Practices
1. **Always rebuild after config changes**
1. **Always rebuild after CSS changes**
```bash
make tailwind-dev
```
2. **Add dynamic classes to safelist** to prevent purging
2. **Use production builds for deployment**
```bash
make tailwind-build
```
3. **Use production builds for deployment** to minimize file size
3. **Keep each frontend's CSS isolated** - Don't cross-import between frontends
4. **Check existing classes first** before adding custom ones - Tailwind likely has what you need
4. **Use @apply sparingly** - Prefer utility classes in templates when possible
5. **Use consistent color scales** (e.g., `purple-600`, `purple-700`) for hover states
5. **Test in both light and dark modes**
---
## Related Documentation
- [Frontend Overview](overview.md)
- [UI Components](shared/ui-components.md)
- [Tailwind CSS Official Docs](https://tailwindcss.com/docs)
- [Tailwind CSS v4 Blog Post](https://tailwindcss.com/blog/tailwindcss-v4)

View File

@@ -1,361 +0,0 @@
# Tailwind CSS Migration Plan: v1.4/v2.2 → v3.4
**Created:** December 2024
**Status:** Planned
**Estimated Time:** 2-3 hours
---
## Current State
### Two Tailwind Setups
| Component | Version | Source | Purpose |
|-----------|---------|--------|---------|
| Base styles | 2.2.19 | CDN + local fallback | Core Tailwind utilities for all frontends |
| Custom overrides | 1.4.6 | npm build | Windmill Dashboard theme (admin/vendor) |
### Current Files
```
package.json # tailwindcss: 1.4.6
tailwind.config.js # v1.4 format config
static/shared/css/tailwind.min.css # CDN fallback (v2.2.19)
static/admin/css/tailwind.output.css # Built overrides
static/vendor/css/tailwind.output.css # Built overrides
```
### Current Plugins
```json
{
"@tailwindcss/custom-forms": "0.2.1", // DEPRECATED in v3
"tailwindcss-multi-theme": "1.0.3" // May not work with v3
}
```
---
## Migration Goals
1. Upgrade npm Tailwind to v3.4.x (latest stable)
2. Upgrade CDN Tailwind to v3.4.x
3. Update local fallback file
4. Replace deprecated plugins
5. Update config to v3 format
6. Verify all frontends work correctly
---
## Step-by-Step Migration
### Phase 1: Backup Current State
```bash
# Create backup branch
git checkout -b backup/tailwind-v1.4
git add -A
git commit -m "Backup before Tailwind v3 migration"
git checkout master
# Create migration branch
git checkout -b feat/tailwind-v3-upgrade
```
### Phase 2: Update npm Dependencies
```bash
# Remove old packages
npm uninstall tailwindcss tailwindcss-multi-theme @tailwindcss/custom-forms
# Install new packages
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npm install -D @tailwindcss/forms @tailwindcss/typography
# Verify versions
npm list tailwindcss
```
**Expected versions:**
- tailwindcss: ^3.4.x
- postcss: ^8.4.x
- autoprefixer: ^10.4.x
### Phase 3: Update tailwind.config.js
Replace the entire config file:
```javascript
// tailwind.config.js - Tailwind CSS v3.4
/** @type {import('tailwindcss').Config} */
module.exports = {
// v3: 'content' replaces 'purge'
content: [
'app/templates/**/*.html',
'static/**/*.js',
],
// v3: safelist for dynamic classes
safelist: [
'bg-orange-600',
'bg-green-600',
'bg-red-600',
'hover:bg-orange-700',
'hover:bg-green-700',
'hover:bg-red-700',
// Add any other dynamic classes used in Alpine.js
],
// v3: darkMode replaces tailwindcss-multi-theme
darkMode: 'class', // or 'media' for OS preference
theme: {
extend: {
// Custom colors from Windmill Dashboard
colors: {
gray: {
50: '#f9fafb',
100: '#f4f5f7',
200: '#e5e7eb',
300: '#d5d6d7',
400: '#9e9e9e',
500: '#707275',
600: '#4c4f52',
700: '#24262d',
800: '#1a1c23',
900: '#121317',
},
purple: {
50: '#f6f5ff',
100: '#edebfe',
200: '#dcd7fe',
300: '#cabffd',
400: '#ac94fa',
500: '#9061f9',
600: '#7e3af2',
700: '#6c2bd9',
800: '#5521b5',
900: '#4a1d96',
},
// Add other custom colors as needed
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
},
maxHeight: {
'xl': '36rem',
},
},
},
plugins: [
require('@tailwindcss/forms'), // Replaces @tailwindcss/custom-forms
require('@tailwindcss/typography'), // Optional: for prose content
],
}
```
### Phase 4: Update CSS Source File
Update `static/admin/css/tailwind.css`:
```css
/* Tailwind CSS v3 directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom component classes if needed */
@layer components {
.form-input {
@apply block w-full rounded-md border-gray-300 shadow-sm
focus:border-purple-500 focus:ring-purple-500;
}
.form-select {
@apply block w-full rounded-md border-gray-300 shadow-sm
focus:border-purple-500 focus:ring-purple-500;
}
.form-checkbox {
@apply rounded border-gray-300 text-purple-600
focus:ring-purple-500;
}
}
```
### Phase 5: Update postcss.config.js
```javascript
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
}
}
```
### Phase 6: Update package.json Scripts
```json
{
"scripts": {
"tailwind:admin": "npx tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css",
"tailwind:vendor": "npx tailwindcss -i static/admin/css/tailwind.css -o static/vendor/css/tailwind.output.css",
"tailwind:watch": "npx tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css --watch",
"build:admin": "npx tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css --minify",
"build:vendor": "npx tailwindcss -i static/admin/css/tailwind.css -o static/vendor/css/tailwind.output.css --minify",
"build": "npm run build:admin && npm run build:vendor"
}
}
```
### Phase 7: Update Dark Mode Implementation
**Old approach (tailwindcss-multi-theme):**
```html
<body class="theme-dark">
<div class="bg-white dark:bg-gray-800">
```
**New approach (Tailwind v3 native):**
```html
<body class="dark">
<div class="bg-white dark:bg-gray-800">
```
**Files to update:**
1. `app/templates/admin/base.html` - Change `theme-dark` to `dark`
2. `app/templates/vendor/base.html` - Change `theme-dark` to `dark`
3. `static/admin/js/init-alpine.js` - Update dark mode toggle logic
4. `static/vendor/js/init-alpine.js` - Update dark mode toggle logic
**JavaScript update example:**
```javascript
// Old
document.body.classList.toggle('theme-dark');
// New
document.documentElement.classList.toggle('dark');
```
### Phase 8: Update CDN and Local Fallback
**Option A: Use Tailwind Play CDN (development only)**
```html
<script src="https://cdn.tailwindcss.com"></script>
```
**Option B: Build and serve locally (recommended for production)**
Since Tailwind v3 doesn't have a pre-built CDN CSS file (it's JIT-only), the recommended approach is:
1. Remove CDN loading from templates
2. Load only the built `tailwind.output.css`
3. Include all needed utilities in your build
**Update base templates:**
```html
<!-- OLD: CDN with fallback -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
<!-- NEW: Local only (v3) -->
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
```
### Phase 9: Build and Test
```bash
# Install dependencies
npm install
# Build CSS
npm run tailwind:admin
npm run tailwind:vendor
# Start development server
make dev
# Test all frontends:
# - http://localhost:8000/admin/dashboard
# - http://localhost:8000/vendor/{code}/dashboard
# - http://localhost:8000/ (shop)
```
### Phase 10: Verify Checklist
- [ ] Admin dashboard loads correctly
- [ ] Vendor dashboard loads correctly
- [ ] Shop frontend loads correctly
- [ ] Dark mode toggle works
- [ ] All buttons have correct colors
- [ ] Forms display correctly (inputs, selects, checkboxes)
- [ ] Responsive design works (mobile/tablet/desktop)
- [ ] No console errors
- [ ] Production build works (`npm run build`)
---
## Breaking Changes Reference
### Class Name Changes (v2 → v3)
| v2.x | v3.x | Notes |
|------|------|-------|
| `overflow-ellipsis` | `text-ellipsis` | Renamed |
| `flex-grow-0` | `grow-0` | Shortened |
| `flex-shrink-0` | `shrink-0` | Shortened |
| `decoration-clone` | `box-decoration-clone` | Renamed |
### Plugin Changes
| Old Plugin | New Plugin | Notes |
|------------|------------|-------|
| `@tailwindcss/custom-forms` | `@tailwindcss/forms` | Complete rewrite |
| `tailwindcss-multi-theme` | Built-in `darkMode: 'class'` | Native support |
### Config Changes
| v1.x/v2.x | v3.x |
|-----------|------|
| `purge: [...]` | `content: [...]` |
| `variants: {...}` | Removed (JIT generates all) |
| `mode: 'jit'` | Default (not needed) |
---
## Rollback Plan
If issues arise:
```bash
# Switch back to backup branch
git checkout backup/tailwind-v1.4
# Or reset changes
git checkout master
git reset --hard HEAD~1
```
---
## Post-Migration Tasks
1. Update `docs/frontend/tailwind-css.md` with v3 information
2. Update `docs/frontend/cdn-fallback-strategy.md` (or remove CDN references)
3. Remove `static/shared/css/tailwind.min.css` if no longer needed
4. Update any documentation referencing old class names
5. Consider adding Tailwind IntelliSense VS Code extension config
---
## Resources
- [Tailwind CSS v3 Upgrade Guide](https://tailwindcss.com/docs/upgrade-guide)
- [Tailwind CSS v3 Documentation](https://tailwindcss.com/docs)
- [@tailwindcss/forms Plugin](https://github.com/tailwindlabs/tailwindcss-forms)
- [Dark Mode in Tailwind v3](https://tailwindcss.com/docs/dark-mode)

View File

@@ -1,387 +0,0 @@
# UI Components
This document describes the reusable UI components and patterns used in the Wizamart admin panel.
## Page Layout Structure
All admin list pages follow a consistent structure:
```
┌─────────────────────────────────────────────────────┐
│ Page Header (Title + Action Button) │
├─────────────────────────────────────────────────────┤
│ Stats Cards (4 columns on desktop) │
├─────────────────────────────────────────────────────┤
│ Search & Filters Bar │
├─────────────────────────────────────────────────────┤
│ Data Table │
│ ├── Table Header │
│ ├── Table Rows │
│ └── Pagination │
└─────────────────────────────────────────────────────┘
```
## Page Header
The page header contains the page title and primary action button.
```html
<div class="flex items-center justify-between my-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Page Title
</h2>
<a href="/admin/resource/create"
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 focus:shadow-outline-purple">
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Create Resource
</a>
</div>
```
## Stats Cards
Stats cards display key metrics in a 4-column grid layout.
### Structure
```html
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<!-- Card Template -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-{color}-500 bg-{color}-100 rounded-full dark:text-{color}-100 dark:bg-{color}-500">
<span x-html="$icon('icon-name', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Label
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="value">
0
</p>
</div>
</div>
</div>
```
### Color Options
| Color | Use Case | Example |
|-------|----------|---------|
| `blue` | Total counts | Total Users, Total Companies |
| `green` | Positive status | Active, Verified |
| `red` | Negative status | Inactive, Errors |
| `orange` | Special/Admin | Admin users, Warnings |
| `purple` | Primary/Vendors | Active vendors, Main metrics |
### Icon Style
- Icons should be inside a **circular** container (`rounded-full`)
- Icon size: `w-5 h-5`
- Container padding: `p-3`
## Search & Filters Bar
The search and filters bar provides filtering capabilities for list pages.
### Structure
```html
<div class="mb-6 px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<!-- Search Input -->
<div class="flex-1 max-w-md">
<div class="relative">
<input
type="text"
x-model="filters.search"
@input="debouncedSearch()"
placeholder="Search..."
class="w-full pl-10 pr-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:bg-gray-700 dark:text-gray-300"
>
<div class="absolute inset-y-0 left-0 flex items-center pl-3">
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
</div>
</div>
</div>
<!-- Filter Dropdowns -->
<div class="flex gap-3">
<select
x-model="filters.filterName"
@change="loadData()"
class="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300"
>
<option value="">All Options</option>
<option value="value1">Option 1</option>
<option value="value2">Option 2</option>
</select>
</div>
</div>
</div>
```
### JavaScript Implementation
```javascript
// State
filters: {
search: '',
status: '',
type: ''
},
// Debounced search function
debouncedSearch() {
if (this._searchTimeout) {
clearTimeout(this._searchTimeout);
}
this._searchTimeout = setTimeout(() => {
this.pagination.page = 1; // Reset to first page
this.loadData();
}, 300);
},
// Load data with filters
async loadData() {
const params = new URLSearchParams();
params.append('page', this.pagination.page);
params.append('per_page', this.pagination.per_page);
if (this.filters.search) {
params.append('search', this.filters.search);
}
if (this.filters.status) {
params.append('status', this.filters.status);
}
const response = await apiClient.get(`/admin/resource?${params}`);
// Handle response...
}
```
### Backend API Support
The API endpoint should support these query parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| `page` | int | Page number (1-based) |
| `per_page` | int | Items per page |
| `search` | string | Search term (searches multiple fields) |
| `{filter}` | string | Filter by specific field |
Example API implementation:
```python
@router.get("", response_model=ListResponse)
def get_all(
page: int = Query(1, ge=1),
per_page: int = Query(10, ge=1, le=100),
search: str = Query("", description="Search term"),
status: str = Query("", description="Filter by status"),
db: Session = Depends(get_db),
):
query = db.query(Model)
# Apply search
if search:
search_term = f"%{search.lower()}%"
query = query.filter(
(Model.name.ilike(search_term)) |
(Model.email.ilike(search_term))
)
# Apply filters
if status:
query = query.filter(Model.status == status)
# Pagination
total = query.count()
items = query.offset((page - 1) * per_page).limit(per_page).all()
return ListResponse(items=items, total=total, page=page, ...)
```
## Data Table
### Structure
```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">Column</th>
<!-- More columns -->
<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">
<!-- Columns -->
</tr>
</template>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
<!-- Pagination controls -->
</div>
</div>
```
### Action Buttons
Standard action buttons for table rows:
```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>
<!-- 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>
<!-- 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>
```
### Status Badges
```html
<!-- Active/Inactive -->
<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>
<!-- Role Badge -->
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs capitalize"
:class="{
'text-orange-700 bg-orange-100 dark:bg-orange-700 dark:text-orange-100': item.role === 'admin',
'text-purple-700 bg-purple-100 dark:bg-purple-700 dark:text-purple-100': item.role === 'vendor'
}"
x-text="item.role">
</span>
```
## Loading & Error States
### Loading State
```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>
```
### Error State
```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>
```
### Empty State
```html
<template x-if="items.length === 0">
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
<div class="flex flex-col items-center">
<span x-html="$icon('inbox', 'w-12 h-12 text-gray-400 mb-4')"></span>
<p class="text-lg font-medium">No items found</p>
<p class="text-sm" x-text="filters.search ? 'Try adjusting your search' : 'Create your first item'"></p>
</div>
</td>
</tr>
</template>
```
## Pages Using These Components
| Page | Stats | Search | Filters |
|------|-------|--------|---------|
| `/admin/users` | Total, Active, Inactive, Admins | Name, Email, Username | Role, Status |
| `/admin/companies` | Total, Verified, Active, Vendors | Name, Email, Owner | Status |
| `/admin/vendors` | Total, Verified, Active, Products | Name, Code, Subdomain | Status, Company |
## JavaScript Module Structure
Each list page follows this pattern:
```javascript
function adminResourceList() {
return {
// Inherit base layout
...data(),
// Page identifier (for sidebar highlighting)
currentPage: 'resource-name',
// State
items: [],
loading: false,
error: null,
filters: { search: '', status: '' },
stats: {},
pagination: { page: 1, per_page: 10, total: 0, pages: 0 },
// Initialize
async init() {
await this.loadItems();
await this.loadStats();
},
// Format helpers
formatDate(dateString) {
return dateString ? Utils.formatDate(dateString) : '-';
},
// Data loading
async loadItems() { /* ... */ },
async loadStats() { /* ... */ },
// Search & filters
debouncedSearch() { /* ... */ },
// Pagination
nextPage() { /* ... */ },
previousPage() { /* ... */ },
// Actions
async deleteItem(item) { /* ... */ }
};
}
```
## Related Documentation
- [Icons Reference](./icons.md)
- [Alpine.js Integration](./alpine-integration.md)
- [Tailwind CSS](./tailwind-css.md)