revamping documentation

This commit is contained in:
2025-11-17 22:59:42 +01:00
parent bbd64a6f21
commit 807033be16
107 changed files with 11973 additions and 28413 deletions

View File

@@ -0,0 +1,828 @@
╔══════════════════════════════════════════════════════════════════╗
║ ADMIN FRONTEND ARCHITECTURE OVERVIEW ║
║ Alpine.js + Jinja2 + Tailwind CSS ║
╚══════════════════════════════════════════════════════════════════╝
📦 WHAT IS THIS?
═════════════════════════════════════════════════════════════════
Admin frontend provides platform administrators with complete control
over the marketplace. Built with:
✅ Jinja2 Templates (server-side rendering)
✅ Alpine.js (client-side reactivity)
✅ Tailwind CSS (utility-first styling)
✅ Windmill Dashboard UI (dark mode ready)
✅ FastAPI (backend routes)
🎯 KEY PRINCIPLES
═════════════════════════════════════════════════════════════════
1. Minimal Server-Side Rendering
• Routes handle authentication + template rendering
• NO database queries in route handlers
• ALL data loaded client-side via JavaScript
2. Component-Based Architecture
• Base template with shared layout
• Reusable partials (header, sidebar, etc.)
• Page-specific templates extend base
3. Progressive Enhancement
• Works without JavaScript (basic HTML)
• JavaScript adds interactivity
• Graceful degradation
4. API-First Data Loading
• All data from REST APIs
• Client-side state management
• Real-time updates possible
5. Centralized State & Logging
• Base layout provides shared state
• Centralized logging system
• Consistent error handling
📁 FILE STRUCTURE
═════════════════════════════════════════════════════════════════
app/
├── templates/admin/
│ ├── base.html ← Base template (layout)
│ ├── login.html ← Admin login page
│ ├── dashboard.html ← Dashboard overview
│ ├── vendors.html ← Vendor management
│ ├── vendor-edit.html ← Single vendor edit
│ ├── users.html ← User management
│ ├── products.html ← Product management
│ ├── orders.html ← Order management
│ ├── import-jobs.html ← Import job tracking
│ ├── audit-logs.html ← Audit log viewer
│ ├── settings.html ← System settings
│ └── partials/ ← Reusable components
│ ├── header.html ← Top navigation
│ ├── sidebar.html ← Main navigation
│ ├── notifications.html ← Toast notifications
│ ├── modal.html ← Modal template
│ └── table.html ← Data table template
├── static/admin/
│ ├── css/
│ │ ├── tailwind.output.css ← Generated Tailwind
│ │ └── admin.css ← Custom admin styles
│ ├── js/
│ │ ├── init-alpine.js ← Base Alpine.js data
│ │ ├── dashboard.js ← Dashboard logic
│ │ ├── vendors.js ← Vendor management logic
│ │ ├── vendor-edit.js ← Vendor edit logic
│ │ ├── vendor-theme.js ← Theme customization
│ │ ├── users.js ← User management logic
│ │ ├── products.js ← Product management logic
│ │ ├── orders.js ← Order management logic
│ │ ├── import-jobs.js ← Import tracking logic
│ │ ├── audit-logs.js ← Audit log logic
│ │ └── settings.js ← Settings logic
│ └── img/
│ ├── login-office.jpeg
│ └── login-office-dark.jpeg
├── static/shared/ ← Shared across all areas
│ ├── js/
│ │ ├── log-config.js ← Centralized logging
│ │ ├── icons.js ← Icon registry
│ │ ├── utils.js ← Utility functions
│ │ └── api-client.js ← API wrapper
│ └── css/
│ └── base.css ← Global styles
└── api/v1/admin/
├── pages.py ← Route handlers (templates)
├── vendors.py ← Vendor API endpoints
├── users.py ← User API endpoints
├── products.py ← Product API endpoints
├── orders.py ← Order API endpoints
└── settings.py ← Settings API endpoints
🏗️ ARCHITECTURE LAYERS
═════════════════════════════════════════════════════════════════
Layer 1: Routes (FastAPI)
Layer 2: Templates (Jinja2)
Layer 3: JavaScript (Alpine.js)
Layer 4: API (REST endpoints)
Layer 5: Database
Layer 1: ROUTES (FastAPI)
──────────────────────────────────────────────────────────────────
Purpose: Authentication + Template Rendering
Location: app/api/v1/admin/pages.py
Example:
@router.get("/admin/dashboard")
async def admin_dashboard_page(
request: Request,
current_user: User = Depends(get_current_admin_user)
):
return templates.TemplateResponse(
"admin/dashboard.html",
{
"request": request,
"user": current_user,
}
)
Responsibilities:
✅ Verify authentication (admin role required)
✅ Extract route parameters
✅ Render template with minimal context
❌ NO database queries
❌ NO business logic
Layer 2: TEMPLATES (Jinja2)
──────────────────────────────────────────────────────────────────
Purpose: HTML Structure + Server-Side Data
Location: app/templates/admin/
Template Hierarchy:
base.html (layout + shared components)
dashboard.html (page content)
partials/sidebar.html (navigation)
Example:
{% extends "admin/base.html" %}
{% block title %}Dashboard{% endblock %}
{% block alpine_data %}adminDashboard(){% endblock %}
{% block content %}
<div x-show="loading">Loading...</div>
<div x-show="!loading">
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<template x-for="stat in stats" :key="stat.label">
<!-- Stats card -->
</template>
</div>
</div>
{% endblock %}
Key Features:
✅ Template inheritance
✅ Server-side variables (user info)
✅ Include partials
✅ Block overrides
✅ Alpine.js integration
Layer 3: JAVASCRIPT (Alpine.js)
──────────────────────────────────────────────────────────────────
Purpose: Client-Side Interactivity + Data Loading
Location: app/static/admin/js/
Component Structure:
function adminDashboard() {
return {
// ✅ CRITICAL: Inherit base layout state
...data(),
// ✅ CRITICAL: Set page identifier
currentPage: 'dashboard',
// Page-specific state
loading: false,
error: null,
stats: [],
// Initialization
async init() {
// Guard against multiple initialization
if (window._dashboardInitialized) {
dashLog.warn('Already initialized');
return;
}
window._dashboardInitialized = true;
await this.loadStats();
},
// Data loading
async loadStats() {
this.loading = true;
try {
this.stats = await apiClient.get(
'/api/v1/admin/stats'
);
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
};
}
Responsibilities:
✅ Load data from API
✅ Manage UI state
✅ Handle user interactions
✅ Update DOM reactively
✅ Inherit base layout functionality
Layer 4: API (REST)
──────────────────────────────────────────────────────────────────
Purpose: Business Logic + Data Access
Location: app/api/v1/admin/*.py (not pages.py)
Example Endpoints:
GET /api/v1/admin/stats
GET /api/v1/admin/vendors
POST /api/v1/admin/vendors
PUT /api/v1/admin/vendors/{id}
DELETE /api/v1/admin/vendors/{id}
GET /api/v1/admin/users
GET /api/v1/admin/audit-logs
🔄 DATA FLOW
═════════════════════════════════════════════════════════════════
Page Load Flow:
──────────────────────────────────────────────────────────────────
1. Admin → GET /admin/dashboard
2. FastAPI → Check authentication (admin role)
3. FastAPI → Render template with minimal context
4. Browser → Load HTML + CSS + JS
5. Alpine.js → init() executes
6. JavaScript → Check initialization guard
7. JavaScript → API call for dashboard stats
8. API → Return JSON data
9. Alpine.js → Update reactive state
10. Browser → DOM updates automatically
User Interaction Flow:
──────────────────────────────────────────────────────────────────
1. Admin → Click "Delete Vendor"
2. Alpine.js → Show confirmation dialog
3. Admin → Confirms deletion
4. Alpine.js → DELETE to API
5. API → Delete vendor + return success
6. Alpine.js → Update local state (remove from list)
7. Browser → DOM updates automatically
8. Alpine.js → Show success toast notification
💡 BASE LAYOUT INHERITANCE
═════════════════════════════════════════════════════════════════
The ...data() Spread:
──────────────────────────────────────────────────────────────────
Every page component MUST start with ...data() to inherit
base layout functionality:
init-alpine.js provides:
function data() {
return {
// Theme state
dark: localStorage.getItem('theme') === 'dark',
toggleTheme() { /* ... */ },
// Side menu state
isSideMenuOpen: false,
toggleSideMenu() { /* ... */ },
closeSideMenu() { /* ... */ },
// Profile menu state
isProfileMenuOpen: false,
toggleProfileMenu() { /* ... */ },
closeProfileMenu() { /* ... */ },
// Notifications menu state
isNotificationsMenuOpen: false,
toggleNotificationsMenu() { /* ... */ },
closeNotificationsMenu() { /* ... */ },
// Page identifier (override in each page)
currentPage: ''
};
}
Your page inherits ALL of this:
function adminVendors() {
return {
...data(), // ← Spreads all base functionality
currentPage: 'vendors', // ← Override page identifier
// Your page-specific state
vendors: [],
loading: false
};
}
Benefits:
✅ Automatic dark mode support
✅ Menu states work automatically
✅ No duplicate code
✅ Consistent behavior across pages
🎨 STYLING SYSTEM
═════════════════════════════════════════════════════════════════
Tailwind CSS Utility Classes:
• Responsive: sm:, md:, lg:, xl:
• Dark mode: dark:bg-gray-800
• Hover: hover:bg-purple-700
• Focus: focus:outline-none
• Transitions: transition-colors duration-150
Custom CSS Variables (admin/css/admin.css):
--color-primary: #7c3aed (purple-600)
--color-accent: #ec4899 (pink-500)
--color-success: #10b981 (green-500)
--color-warning: #f59e0b (yellow-500)
--color-danger: #ef4444 (red-500)
Windmill Dashboard Theme:
• Professional admin UI
• Dark mode ready
• Consistent components
• Accessible by default
🔐 AUTHENTICATION
═════════════════════════════════════════════════════════════════
Auth Flow:
1. Login → POST /api/v1/admin/auth/login
2. API → Verify credentials + check admin role
3. API → Return JWT token
4. JavaScript → Store in localStorage
5. API Client → Add to all requests
6. Routes → Verify with get_current_admin_user
Protected Routes:
• All /admin/* routes
• Require valid JWT token
• Require admin role (is_admin=True)
• Redirect to login if unauthorized
Public Routes:
• /admin/login
• No authentication required
Role-Based Access:
• Admin: Full platform access
• Vendor: Limited to own shop (vendor portal)
• Customer: No admin access
📱 RESPONSIVE DESIGN
═════════════════════════════════════════════════════════════════
Breakpoints (Tailwind):
• sm: 640px (mobile landscape)
• md: 768px (tablet)
• lg: 1024px (desktop)
• xl: 1280px (large desktop)
Mobile-First Approach:
• Base styles for mobile
• Add complexity for larger screens
• Collapsible sidebar on mobile
• Stack cards vertically on mobile
Example:
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
<!-- 1 column mobile, 2 tablet, 4 desktop -->
</div>
🌙 DARK MODE
═════════════════════════════════════════════════════════════════
Implementation:
1. Alpine.js state: dark: boolean
2. HTML class binding: :class="{ 'dark': dark }"
3. Tailwind variants: dark:bg-gray-800
4. LocalStorage: persist preference
Toggle Flow:
1. User clicks dark mode button
2. toggleTheme() called (from base data)
3. dark state toggled
4. Saved to localStorage
5. HTML class updates
6. Tailwind dark: variants activate
Example Usage:
<div class="bg-white dark:bg-gray-800">
<p class="text-gray-900 dark:text-white">Content</p>
</div>
🔒 INITIALIZATION GUARDS
═════════════════════════════════════════════════════════════════
Purpose: Prevent Multiple Initialization
Alpine.js can sometimes initialize components multiple times.
Use a guard to prevent duplicate API calls and setup:
Pattern:
async init() {
// Check if already initialized
if (window._dashboardInitialized) {
dashLog.warn('Already initialized, skipping...');
return; // Exit early
}
// Set flag BEFORE async operations
window._dashboardInitialized = true;
// Safe to proceed
await this.loadData();
}
Naming Convention:
• Dashboard: window._dashboardInitialized
• Vendors: window._vendorsInitialized
• Products: window._productsInitialized
• etc.
Benefits:
✅ Prevents duplicate API calls
✅ Prevents duplicate event listeners
✅ Improves performance
✅ Avoids state conflicts
🪵 CENTRALIZED LOGGING
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/log-config.js
Pre-configured Loggers:
window.LogConfig.loggers.dashboard
window.LogConfig.loggers.vendors
window.LogConfig.loggers.vendorTheme
window.LogConfig.loggers.users
window.LogConfig.loggers.products
window.LogConfig.loggers.orders
window.LogConfig.loggers.imports
window.LogConfig.loggers.audit
Usage:
// Use pre-configured logger
const dashLog = window.LogConfig.loggers.dashboard;
dashLog.info('Dashboard loading...');
dashLog.error('Failed to load stats', error);
dashLog.debug('Stats data:', statsData);
dashLog.warn('API response slow');
Advanced Features:
// Grouped logs
dashLog.group('Loading Dashboard Data');
dashLog.info('Fetching stats...');
dashLog.info('Fetching activity...');
dashLog.groupEnd();
// API call logging
window.LogConfig.logApiCall('GET', url, data, 'response');
// Performance logging
window.LogConfig.logPerformance('Load Stats', duration);
// Error logging
window.LogConfig.logError(error, 'Load Stats');
Benefits:
✅ One line instead of 15+ lines per file
✅ Consistent logging format
✅ Environment-aware (dev/prod)
✅ Frontend-aware (admin/vendor/shop)
✅ Advanced features (groups, perf, API)
📡 API CLIENT
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/api-client.js
CRITICAL: Always use lowercase 'apiClient'
✅ apiClient.get()
❌ ApiClient.get()
❌ API_CLIENT.get()
Usage:
const data = await apiClient.get('/api/v1/admin/vendors');
await apiClient.post('/api/v1/admin/vendors', {
name: 'New Vendor',
code: 'NEWVENDOR'
});
await apiClient.put('/api/v1/admin/vendors/123', {
name: 'Updated Name'
});
await apiClient.delete('/api/v1/admin/vendors/123');
Features:
✅ Automatic auth headers
✅ Error handling
✅ JSON parsing
✅ Request/response logging
🎭 ICONS
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/icons.js
Usage:
<span x-html="$icon('home', 'w-5 h-5')"></span>
<span x-html="$icon('users', 'w-4 h-4 text-blue-500')"></span>
Available Icons:
• home, dashboard, settings
• user, users, user-group
• shopping-bag, shopping-cart
• cube, download, upload
• plus, minus, x
• pencil, trash, eye
• check, exclamation
• chevron-left, chevron-right
• spinner (for loading)
🚀 PERFORMANCE
═════════════════════════════════════════════════════════════════
Optimization Techniques:
1. Template Caching
• Base template cached by FastAPI
• Reduces rendering time
2. Lazy Loading
• Data loaded after page render
• Progressive content display
3. Debouncing
• Search inputs debounced
• Reduces API calls
4. Pagination
• Server-side pagination
• Load only needed data
5. CDN Assets
• Tailwind CSS from CDN
• Alpine.js from CDN
6. Initialization Guards
• Prevent duplicate setups
• Reduce unnecessary operations
📊 PAGE-BY-PAGE BREAKDOWN
═════════════════════════════════════════════════════════════════
/admin/dashboard
──────────────────────────────────────────────────────────────────
Purpose: Overview of platform operations
Components:
• Stats cards (vendors, users, orders, revenue)
• Recent activity feed
• Quick actions panel
• System health indicators
Data Sources:
• GET /api/v1/admin/stats
• GET /api/v1/admin/recent-activity
/admin/vendors
──────────────────────────────────────────────────────────────────
Purpose: Manage marketplace vendors
Components:
• Vendor list table
• Search and filters
• Create/Edit modal
• Status management
• Verification controls
Data Sources:
• GET /api/v1/admin/vendors
• POST /api/v1/admin/vendors
• PUT /api/v1/admin/vendors/{id}
• DELETE /api/v1/admin/vendors/{id}
/admin/vendors/{code}/edit
──────────────────────────────────────────────────────────────────
Purpose: Edit single vendor details
Components:
• Vendor information form
• Status controls
• Contact information
• Business details
Data Sources:
• GET /api/v1/admin/vendors/{code}
• PUT /api/v1/admin/vendors/{code}
/admin/vendors/{code}/theme
──────────────────────────────────────────────────────────────────
Purpose: Customize vendor's shop theme
Components:
• Color picker
• Font selector
• Logo uploader
• Layout options
• Custom CSS editor
• Theme presets
Data Sources:
• GET /api/v1/admin/vendor-themes/{code}
• PUT /api/v1/admin/vendor-themes/{code}
/admin/users
──────────────────────────────────────────────────────────────────
Purpose: Manage platform users
Components:
• User list table
• Search and filters
• Role management
• Status controls
Data Sources:
• GET /api/v1/admin/users
• PUT /api/v1/admin/users/{id}
• DELETE /api/v1/admin/users/{id}
/admin/products
──────────────────────────────────────────────────────────────────
Purpose: View all marketplace products
Components:
• Product list table
• Search and filters
• Vendor filter
• Bulk actions
Data Sources:
• GET /api/v1/admin/products
/admin/orders
──────────────────────────────────────────────────────────────────
Purpose: View all marketplace orders
Components:
• Order list table
• Status filters
• Vendor filter
• Order detail modal
Data Sources:
• GET /api/v1/admin/orders
• GET /api/v1/admin/orders/{id}
/admin/import-jobs
──────────────────────────────────────────────────────────────────
Purpose: Monitor marketplace import operations
Components:
• Import job list
• Status indicators
• Progress tracking
• Error logs
Data Sources:
• GET /api/v1/admin/import-jobs
• GET /api/v1/admin/import-jobs/{id}
/admin/audit-logs
──────────────────────────────────────────────────────────────────
Purpose: Track all system actions
Components:
• Audit log table
• Filters (user, action, date)
• Export functionality
Data Sources:
• GET /api/v1/admin/audit-logs
/admin/settings
──────────────────────────────────────────────────────────────────
Purpose: Configure platform settings
Components:
• Settings tabs
• Configuration forms
• Feature toggles
Data Sources:
• GET /api/v1/admin/settings
• PUT /api/v1/admin/settings
🎓 LEARNING PATH
═════════════════════════════════════════════════════════════════
For New Developers:
1. Understand Architecture (1 hour)
→ Read this document
→ Review file structure
→ Examine base template
→ Understand ...data() inheritance
2. Study Existing Page (2 hours)
→ Open dashboard.html
→ Open dashboard.js
→ Trace data flow
→ Understand init guard pattern
3. Create Simple Page (4 hours)
→ Copy templates from dashboard
→ Modify for new feature
→ Test initialization guard
→ Verify dark mode works
4. Add Complex Feature (1 day)
→ Forms with validation
→ Modal dialogs
→ API integration
→ Error handling
5. Master Patterns (1 week)
→ All common patterns
→ Centralized logging
→ Performance optimization
→ Best practices
🔄 DEPLOYMENT CHECKLIST
═════════════════════════════════════════════════════════════════
Before Deploying:
□ Build Tailwind CSS
□ Minify JavaScript
□ Test all routes
□ Verify authentication
□ Check role-based access
□ Verify initialization guards work
□ Check mobile responsive
□ Test dark mode
□ Validate API endpoints
□ Review error handling
□ Test logging in production mode
□ Check console for errors
□ Verify no duplicate initializations
🔒 SECURITY
═════════════════════════════════════════════════════════════════
Best Practices:
1. Authentication
✅ JWT tokens
✅ Token expiration
✅ Secure storage (httpOnly cookies option)
2. Authorization
✅ Route-level checks (admin role required)
✅ API-level validation
✅ Role-based permissions
3. Input Validation
✅ Client-side validation
✅ Server-side validation
✅ XSS prevention
4. CSRF Protection
✅ Token-based
✅ SameSite cookies
5. Admin-Specific
✅ Audit logging for all actions
✅ Strong password requirements
✅ Two-factor authentication (optional)
📚 REFERENCE LINKS
═════════════════════════════════════════════════════════════════
Documentation:
• Alpine.js: https://alpinejs.dev/
• Tailwind CSS: https://tailwindcss.com/
• Jinja2: https://jinja.palletsprojects.com/
• FastAPI: https://fastapi.tiangolo.com/
• Windmill Dashboard: https://windmill-dashboard.vercel.app/
Internal Docs:
• Page Template Guide: FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md
• API Documentation: API_REFERENCE.md
• Database Schema: DATABASE_SCHEMA.md
══════════════════════════════════════════════════════════════════
ADMIN FRONTEND ARCHITECTURE
Powerful, Secure, and Maintainable Platform Control
══════════════════════════════════════════════════════════════════

File diff suppressed because it is too large Load Diff

501
docs/frontend/overview.md Normal file
View File

@@ -0,0 +1,501 @@
# Frontend Architecture Overview
**Version:** 1.0
**Last Updated:** November 2025
**Audience:** Frontend Developers
---
## What is This Document?
This document provides a comprehensive overview of the Wizamart frontend architecture, covering all three distinct frontend applications and the shared design patterns that ensure consistency, maintainability, and developer productivity across the entire platform.
This serves as the introduction to three detailed architecture documents:
1. Admin Frontend Architecture
2. Vendor Frontend Architecture
3. Shop Frontend Architecture
---
## Platform Overview
Wizamart is a multi-tenant e-commerce marketplace platform with three distinct frontend applications, each serving different user groups:
```
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ ADMIN │ │ VENDOR │ │ SHOP │ │
│ │ FRONTEND │ │ FRONTEND │ │ FRONTEND │ │
│ └──────────────┘ └──────────────┘ └────────────┘ │
│ │ │ │ │
│ ├────────────────────────┴─────────────────────┤ │
│ │ │ │
│ │ SHARED ARCHITECTURE │ │
│ │ • Alpine.js │ │
│ │ • Jinja2 Templates │ │
│ │ • Tailwind CSS │ │
│ │ • FastAPI Backend │ │
│ │ • Design Patterns │ │
│ │ │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Three Frontends Explained
### 1. Admin Frontend
**Purpose:** Platform administration and control
**Users:** Platform administrators
**Access:** `/admin/*`
**Auth:** Admin role required (`is_admin=True`)
**Key Features:**
- Vendor management (create, verify, suspend)
- User management (roles, permissions)
- Platform-wide analytics and monitoring
- Theme customization for vendors
- Import job monitoring
- Audit log viewing
- System settings and configuration
**UI Theme:** Windmill Dashboard (professional admin UI)
**Colors:** Purple (#7c3aed) primary
**Pages:**
- `/admin/dashboard`
- `/admin/vendors`
- `/admin/vendors/{code}/edit`
- `/admin/vendors/{code}/theme`
- `/admin/users`
- `/admin/products`
- `/admin/orders`
- `/admin/import-jobs`
- `/admin/audit-logs`
- `/admin/settings`
---
### 2. Vendor Frontend
**Purpose:** Vendor shop management and operations
**Users:** Vendor owners and their team members
**Access:** `/vendor/{vendor_code}/*`
**Auth:** Vendor role required + vendor ownership
**Key Features:**
- Product catalog management
- Inventory tracking
- Order management
- Customer management
- Marketplace imports (Amazon, eBay, etc.)
- Shop analytics and reports
- Team member management
- Shop settings
**UI Theme:** Windmill Dashboard (professional admin UI)
**Colors:** Purple (#7c3aed) primary
**Pages:**
- `/vendor/{code}/dashboard`
- `/vendor/{code}/products`
- `/vendor/{code}/inventory`
- `/vendor/{code}/orders`
- `/vendor/{code}/customers`
- `/vendor/{code}/marketplace`
- `/vendor/{code}/analytics`
- `/vendor/{code}/team`
- `/vendor/{code}/settings`
---
### 3. Shop Frontend
**Purpose:** Customer-facing e-commerce storefront
**Users:** Customers and visitors
**Access:** Vendor-specific domains or subdomains
**Auth:** Optional (guest checkout supported)
**Key Features:**
- Product browsing and search
- Shopping cart management
- Checkout and payment
- Order tracking
- Customer account (optional)
- Wishlist (optional)
- Product reviews (optional)
- Multi-theme system (vendor branding)
**UI Theme:** Custom per vendor (multi-theme system)
**Colors:** Vendor-specific via CSS variables
**Special Features:**
- Vendor Context Middleware (domain → vendor detection)
- Theme Context Middleware (loads vendor theme)
- CSS Variables for dynamic theming
- Client-side cart (localStorage)
**Pages:**
- `/` (homepage)
- `/products` (catalog)
- `/products/{id}` (product detail)
- `/category/{slug}` (category browse)
- `/search` (search results)
- `/cart` (shopping cart)
- `/checkout` (checkout flow)
- `/account` (customer account)
- `/orders` (order history)
- `/about` (about vendor)
- `/contact` (contact form)
---
## Shared Technology Stack
All three frontends share the same core technologies:
| Layer | Technology | Purpose |
|-------|-----------|---------|
| Backend | FastAPI | REST API + routing |
| Templates | Jinja2 | Server-side rendering |
| Interactivity | Alpine.js 3.x | Client-side reactivity |
| Styling | Tailwind CSS 2.x | Utility-first CSS |
| Icons | Heroicons | SVG icon system |
| HTTP Client | Fetch API | API requests |
| State Management | Alpine.js reactive | No external state lib |
| Logging | Custom LogConfig | Centralized logging |
| Error Handling | Custom exceptions | Structured errors |
### Why This Stack?
- ✅ Minimal JavaScript complexity (no React/Vue build process)
- ✅ Server-side rendering for SEO
- ✅ Progressive enhancement (works without JS)
- ✅ Fast development iteration
- ✅ Small bundle sizes
- ✅ Easy to learn and maintain
- ✅ Python developers can contribute to frontend
---
## Architecture Philosophy
### 1. API-First Design
- Routes only render templates (no business logic)
- ALL data loaded client-side via REST APIs
- Clear separation: `pages.py` (templates) vs other API files
- Enables future mobile apps or SPA migrations
### 2. Progressive Enhancement
- HTML works without JavaScript (basic functionality)
- JavaScript enhances experience (filters, live updates)
- Graceful degradation for older browsers
- Accessible by default
### 3. Component-Based Templates
- Base templates provide layout
- Pages extend base templates
- Partials for reusable components
- Block overrides for customization
### 4. Centralized Design Patterns
- Shared utilities (logging, API client, utils)
- Consistent error handling across frontends
- Standardized state management patterns
- Common UI components and patterns
### 5. Developer Experience
- Copy-paste templates for new pages
- Consistent patterns reduce cognitive load
- Comprehensive documentation
- Clear file organization
---
## File Organization
The project follows a clear, frontend-specific organization:
```
app/
├── templates/
│ ├── admin/ ← Admin frontend templates
│ │ ├── base.html
│ │ ├── dashboard.html
│ │ └── partials/
│ ├── vendor/ ← Vendor frontend templates
│ │ ├── base.html
│ │ ├── dashboard.html
│ │ └── partials/
│ └── shop/ ← Shop frontend templates
│ ├── base.html
│ ├── home.html
│ └── partials/
├── static/
│ ├── admin/ ← Admin-specific assets
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── vendor/ ← Vendor-specific assets
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── shop/ ← Shop-specific assets
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ └── shared/ ← Shared across all frontends
│ ├── js/
│ │ ├── log-config.js ← Centralized logging
│ │ ├── api-client.js ← HTTP client wrapper
│ │ ├── icons.js ← Icon registry
│ │ └── utils.js ← Utility functions
│ └── css/
├── api/v1/
│ ├── admin/ ← Admin API endpoints
│ │ ├── pages.py ← Routes (templates only)
│ │ ├── vendors.py ← Business logic
│ │ ├── users.py
│ │ └── ...
│ ├── vendor/ ← Vendor API endpoints
│ │ ├── pages.py
│ │ ├── products.py
│ │ └── ...
│ └── shop/ ← Shop API endpoints
│ ├── pages.py
│ ├── products.py
│ └── ...
└── exceptions/ ← Custom exception classes
├── base.py ← Base exception classes
├── admin.py ← Admin-specific exceptions
├── shop.py ← Shop-specific exceptions
├── product.py ← Product exceptions
└── handler.py ← Exception handler setup
```
---
## Request Flow
### Page Load Flow
1. **Browser Request**
2. **FastAPI Route Handler** (`pages.py`)
- Verify authentication
- Extract route parameters
- Render Jinja2 template
3. **Template Rendering**
- Extend base template
- Include partials
- Inject server-side data (user, vendor, theme)
4. **Browser Receives HTML**
- Load CSS (Tailwind)
- Load JavaScript (Alpine.js, page scripts)
5. **Alpine.js Initialization**
- `x-data` component initialized
- `...data()` spreads base state
- `init()` method runs
- Initialization guard checked
6. **Client-Side Data Loading**
- JavaScript calls REST API
- apiClient handles request
- JSON response received
7. **Reactive Updates**
- Alpine.js updates reactive state
- DOM automatically updates
- Page fully interactive
### User Interaction Flow
1. **User Action** (click, input, etc.)
2. **Alpine.js Event Handler**
- `@click`, `@input`, `@change`, etc.
- Calls component method
3. **Business Logic**
- Validate input
- Update local state
- Call API if needed
4. **API Request** (if needed)
- `apiClient.post/put/delete`
- Automatic error handling
- Logging
5. **Update State**
- Modify reactive data
- Alpine.js watches changes
6. **DOM Updates**
- Automatic reactive updates
- No manual DOM manipulation
7. **User Feedback**
- Toast notification
- Loading indicator removed
- Success/error message
---
## Core Design Patterns
For detailed information about the design patterns used across all frontends, see:
- **[Shared UI Components](shared/ui-components.md)** - Reusable UI components
- **[Pagination System](shared/pagination.md)** - Pagination implementation
- **[Sidebar Navigation](shared/sidebar.md)** - Sidebar setup guide
- **[Logging System](shared/logging.md)** - Frontend logging configuration
- **[Icons Guide](../development/icons_guide.md)** - Icon system usage
---
## Security Patterns
### Authentication & Authorization
1. **JWT Tokens**
- Stored in localStorage
- Automatically sent with API requests
- Expiration handling
2. **Role-Based Access**
- Admin: Full platform access
- Vendor: Limited to own shop
- Customer: Public + account access
3. **Route Protection**
- FastAPI dependencies verify auth
- Middleware for vendor context
- Automatic redirects to login
### Input Validation
1. **Client-Side** (Alpine.js)
- Immediate feedback
- Better UX
- Reduces server load
2. **Server-Side** (Pydantic)
- Security boundary
- Type validation
- Cannot be bypassed
3. **Both Required**
- Client-side for UX
- Server-side for security
### XSS Prevention
- Jinja2 auto-escapes by default
- Use `| safe` only when necessary
- Sanitize user content
### CSRF Protection
- Token-based (if using forms)
- SameSite cookies
- API uses Bearer tokens (CSRF-safe)
---
## Learning Path
### For New Developers
**Week 1: Foundation**
- Day 1-2: Read this overview document
- Day 3-4: Study one detailed architecture doc (start with admin)
- Day 5: Review shared design patterns
**Week 2: Hands-On**
- Day 1-2: Examine existing `dashboard.js` (best example)
- Day 3-4: Copy template and create simple page
- Day 5: Test and understand data flow
**Week 3: Patterns**
- Day 1: Practice base layout inheritance
- Day 2: Implement initialization guards
- Day 3: Use centralized logging
- Day 4: Work with API client
- Day 5: Handle errors properly
**Week 4: Advanced**
- Day 1-2: Create complex page with filters/pagination
- Day 3: Implement modal forms
- Day 4: Add dark mode support
- Day 5: Review and refactor
**After 1 Month:**
- ✅ Understand all three frontends
- ✅ Can create new pages independently
- ✅ Follow all design patterns
- ✅ Debug issues effectively
- ✅ Contribute to architecture improvements
---
## Development Checklist
### Before Creating New Page
- □ Read relevant architecture document
- □ Review page template guide
- □ Study similar existing page
- □ Identify which patterns to use
### While Developing
- □ Use `...data()` for base inheritance
- □ Add initialization guard
- □ Set `currentPage` identifier
- □ Use lowercase `apiClient`
- □ Use centralized logger
- □ Handle errors gracefully
- □ Add loading states
- □ Support dark mode
### Before Committing
- □ No console errors
- □ Initialization guard works
- □ Dark mode works
- □ Mobile responsive
- □ API errors handled
- □ No duplicate API calls
- □ Logging consistent
- □ Code matches patterns
---
## Benefits of This Architecture
### For Developers
- ✅ Copy-paste templates reduce development time
- ✅ Consistent patterns reduce cognitive load
- ✅ Centralized utilities eliminate duplication
- ✅ Clear documentation speeds onboarding
- ✅ Shared patterns enable code reuse
### For The Platform
- ✅ Maintainable codebase
- ✅ Consistent user experience
- ✅ Easier debugging
- ✅ Faster feature development
- ✅ Scalable architecture
### For Users
- ✅ Consistent UI/UX
- ✅ Fast page loads
- ✅ Reliable functionality
- ✅ Professional appearance
- ✅ Works on all devices
---
## Next Steps
1. Read the detailed architecture document for the frontend you're working on (Admin, Vendor, or Shop)
2. Study the page template guide for that frontend
3. Review existing code examples (`dashboard.js` is the best)
4. Check the [Shared Components](shared/ui-components.md) documentation
5. Review the [Icons Guide](../development/icons_guide.md)
6. Copy templates and start building!
Questions? Check the detailed architecture docs or review existing implementations in the codebase.

View File

@@ -0,0 +1,410 @@
# Quick Reference: Centralized Logging System
## 🚀 Quick Start
### In Any Page File (Admin/Vendor/Shop):
```javascript
// 1. Use pre-configured logger (RECOMMENDED)
const pageLog = window.LogConfig.loggers.yourPage;
// 2. Or create custom logger
const pageLog = window.LogConfig.createLogger('PAGE-NAME');
// 3. Use it
pageLog.info('Page loaded');
pageLog.debug('Data:', data);
pageLog.warn('Warning message');
pageLog.error('Error occurred', error);
```
---
## 📦 Pre-configured Loggers
### Admin Frontend
```javascript
window.LogConfig.loggers.vendors
window.LogConfig.loggers.vendorTheme
window.LogConfig.loggers.products
window.LogConfig.loggers.orders
window.LogConfig.loggers.users
window.LogConfig.loggers.dashboard
window.LogConfig.loggers.imports
```
### Vendor Frontend
```javascript
window.LogConfig.loggers.dashboard
window.LogConfig.loggers.products
window.LogConfig.loggers.orders
window.LogConfig.loggers.theme
window.LogConfig.loggers.analytics
```
### Shop Frontend
```javascript
window.LogConfig.loggers.catalog
window.LogConfig.loggers.cart
window.LogConfig.loggers.checkout
window.LogConfig.loggers.product
```
---
## 🎨 Basic Logging
```javascript
const log = window.LogConfig.loggers.myPage;
// Simple messages
log.info('Operation started');
log.warn('Slow connection detected');
log.error('Operation failed');
log.debug('Debug data:', { user: 'John', id: 123 });
// With multiple arguments
log.info('User logged in:', username, 'at', timestamp);
```
---
## 🔥 Advanced Features
### API Call Logging
```javascript
const url = '/api/vendors';
// Before request
window.LogConfig.logApiCall('GET', url, null, 'request');
// Make request
const data = await apiClient.get(url);
// After response
window.LogConfig.logApiCall('GET', url, data, 'response');
```
### Performance Logging
```javascript
const start = performance.now();
await expensiveOperation();
const duration = performance.now() - start;
window.LogConfig.logPerformance('Operation Name', duration);
// Output: ⚡ Operation Name took 45ms (fast)
// Output: ⏱️ Operation Name took 250ms (medium)
// Output: 🐌 Operation Name took 750ms (slow)
```
### Error Logging
```javascript
try {
await riskyOperation();
} catch (error) {
window.LogConfig.logError(error, 'Operation Context');
// Automatically logs error message and stack trace
}
```
### Grouped Logging
```javascript
log.group('Loading Data');
log.info('Fetching users...');
log.info('Fetching products...');
log.info('Fetching orders...');
log.groupEnd();
```
### Table Logging
```javascript
const users = [
{ id: 1, name: 'John', status: 'active' },
{ id: 2, name: 'Jane', status: 'inactive' }
];
log.table(users);
```
### Timer Logging
```javascript
log.time('Data Processing');
// ... long operation ...
log.timeEnd('Data Processing');
// Output: ⏱️ [ADMIN:PAGE TIME] Data Processing: 1234.56ms
```
---
## 📋 Complete Page Template
```javascript
// static/admin/js/my-page.js
// 1. Setup logger
const pageLog = window.LogConfig.loggers.myPage;
// 2. Create component
function myPageComponent() {
return {
...data(),
currentPage: 'my-page',
items: [],
loading: false,
async init() {
pageLog.info('Initializing page');
// Prevent double init
if (window._myPageInitialized) return;
window._myPageInitialized = true;
const start = performance.now();
try {
pageLog.group('Loading Data');
await this.loadData();
pageLog.groupEnd();
const duration = performance.now() - start;
window.LogConfig.logPerformance('Page Init', duration);
pageLog.info('Page initialized successfully');
} catch (error) {
window.LogConfig.logError(error, 'Page Init');
}
},
async loadData() {
const url = '/api/my-data';
window.LogConfig.logApiCall('GET', url, null, 'request');
const data = await apiClient.get(url);
window.LogConfig.logApiCall('GET', url, data, 'response');
this.items = data;
}
};
}
pageLog.info('Page module loaded');
```
---
## 🎯 Log Levels
```javascript
LOG_LEVELS = {
ERROR: 1, // Only errors
WARN: 2, // Errors + warnings
INFO: 3, // Errors + warnings + info (default)
DEBUG: 4 // Everything
}
```
**Auto-detected:**
- `localhost` → DEBUG (4)
- Production → Varies by frontend
**Manual override:**
```javascript
const myLog = window.LogConfig.createLogger('MY-PAGE', 4); // Force DEBUG
```
---
## 🔍 Debugging Tips
### Check Current Config
```javascript
console.log(window.LogConfig.frontend); // 'admin' | 'vendor' | 'shop'
console.log(window.LogConfig.environment); // 'development' | 'production'
console.log(window.LogConfig.logLevel); // 1 | 2 | 3 | 4
```
### Test Logger
```javascript
const test = window.LogConfig.createLogger('TEST', 4);
test.info('This is a test');
test.debug('Debug info:', { test: true });
```
### Enable Debug Mode
```javascript
// In browser console
window.LogConfig.loggers.myPage = window.LogConfig.createLogger('MY-PAGE', 4);
// Now reload page to see debug logs
```
---
## ⚠️ Common Mistakes
### ❌ WRONG
```javascript
// Old way (don't do this!)
const LOG_LEVEL = 3;
const log = {
info: (...args) => LOG_LEVEL >= 3 && console.log(...args)
};
// Wrong case
ApiClient.get(url) // Should be apiClient
Logger.info() // Should be window.LogConfig
```
### ✅ CORRECT
```javascript
// New way
const log = window.LogConfig.loggers.myPage;
// Correct case
apiClient.get(url)
window.LogConfig.loggers.myPage.info()
```
---
## 🎨 Console Output Examples
### Development Mode
```
🎛️ Admin Frontend Logging System Initialized
Environment: Development
Log Level: 4 (DEBUG)
[ADMIN:VENDORS INFO] Vendors module loaded
[ADMIN:VENDORS INFO] Initializing vendors page
📤 [ADMIN:API DEBUG] GET /admin/vendors
📥 [ADMIN:API DEBUG] GET /admin/vendors {data...}
⚡ [ADMIN:PERF DEBUG] Load Vendors took 45ms
[ADMIN:VENDORS INFO] Vendors loaded: 25 items
```
### Production Mode (Admin)
```
🎛️ Admin Frontend Logging System Initialized
Environment: Production
Log Level: 2 (WARN)
⚠️ [ADMIN:API WARN] Slow API response: 2.5s
❌ [ADMIN:VENDORS ERROR] Failed to load vendor
```
---
## 📱 Frontend Detection
The system automatically detects which frontend based on URL:
| URL Path | Frontend | Prefix |
|----------|----------|--------|
| `/admin/*` | admin | `ADMIN:` |
| `/vendor/*` | vendor | `VENDOR:` |
| `/shop/*` | shop | `SHOP:` |
---
## 🛠️ Customization
### Add New Pre-configured Logger
Edit `log-config.js`:
```javascript
const adminLoggers = {
// ... existing loggers
myNewPage: createLogger('MY-NEW-PAGE', ACTIVE_LOG_LEVEL)
};
```
### Change Log Level for Frontend
Edit `log-config.js`:
```javascript
const DEFAULT_LOG_LEVELS = {
admin: {
development: LOG_LEVELS.DEBUG,
production: LOG_LEVELS.INFO // Change this
}
};
```
---
## 📚 Full API Reference
```javascript
window.LogConfig = {
// Log levels
LOG_LEVELS: { ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4 },
// Current config
frontend: 'admin' | 'vendor' | 'shop',
environment: 'development' | 'production',
logLevel: 1 | 2 | 3 | 4,
// Default logger
log: { error(), warn(), info(), debug(), group(), groupEnd(), table(), time(), timeEnd() },
// Pre-configured loggers
loggers: { vendors, products, orders, ... },
// Create custom logger
createLogger(prefix, level?),
// Utility functions
logApiCall(method, url, data?, status),
logError(error, context),
logPerformance(operation, duration)
}
```
---
## 💡 Tips & Tricks
1. **Use Pre-configured Loggers**
- Faster to write
- Consistent naming
- Already configured
2. **Log API Calls**
- Easy to track requests
- See request/response data
- Debug API issues
3. **Track Performance**
- Find slow operations
- Optimize bottlenecks
- Monitor page load times
4. **Group Related Logs**
- Cleaner console
- Easier to follow
- Better debugging
5. **Use Debug Level in Development**
- See everything
- Find issues faster
- Learn how code flows
---
## 📖 More Documentation
- [Admin Page Templates](../admin/page-templates.md)
- [Vendor Page Templates](../vendor/page-templates.md)
- [Shop Page Templates](../shop/page-templates.md)
---
**Print this page and keep it handy!** 📌
Quick pattern to remember:
```javascript
const log = window.LogConfig.loggers.myPage;
log.info('Hello from my page!');
```
That's it! 🎉

View File

@@ -0,0 +1,201 @@
╔══════════════════════════════════════════════════════════════════╗
║ PAGINATION FEATURE - QUICK START ║
╚══════════════════════════════════════════════════════════════════╝
✨ WHAT'S NEW
═════════════════════════════════════════════════════════════════
1. ✅ Edit Button - Pencil icon already present in table
2. ✅ Pagination - Smart pagination with 10 items per page
3. ✅ Page Navigation - Previous/Next buttons + page numbers
4. ✅ Smart Display - Shows "..." for large page ranges
📦 INSTALLATION (3 STEPS)
═════════════════════════════════════════════════════════════════
Step 1: Backup current files
$ cp templates/admin/vendors.html templates/admin/vendors.html.backup
$ cp static/admin/js/vendors.js static/admin/js/vendors.js.backup
Step 2: Replace with new versions
$ cp vendors.html templates/admin/vendors.html
$ cp vendors.js static/admin/js/vendors.js
Step 3: Clear cache and test
- Hard refresh: Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac)
- Test with >10 vendors to see pagination
🎨 VISUAL PREVIEW
═════════════════════════════════════════════════════════════════
Before (No Pagination):
┌────────────────────────────────────────────────────────────┐
│ Vendor │ Subdomain │ Status │ Created │ Actions │
├────────┼───────────┼────────┼─────────┼───────────────────┤
│ Vendor1│ vendor1 │ ✓ │ Jan 1 │ 👁 🗑 │ ❌ No edit
│ Vendor2│ vendor2 │ ⏰ │ Jan 2 │ 👁 🗑 │
│ ...50 more vendors... │
│ Vendor52│vendor52 │ ✓ │ Jan 52 │ 👁 🗑 │
└────────────────────────────────────────────────────────────┘
❌ All vendors on one page (hard to scroll through!)
After (With Pagination):
┌────────────────────────────────────────────────────────────┐
│ Vendor │ Subdomain │ Status │ Created │ Actions │
├────────┼───────────┼────────┼─────────┼───────────────────┤
│ Vendor1│ vendor1 │ ✓ │ Jan 1 │ 👁 ✏️ 🗑 │ ✅ Edit added!
│ Vendor2│ vendor2 │ ⏰ │ Jan 2 │ 👁 ✏️ 🗑 │
│ ...8 more vendors... │
│Vendor10│ vendor10 │ ✓ │ Jan 10 │ 👁 ✏️ 🗑 │
├────────────────────────────────────────────────────────────┤
│ Showing 1-10 of 52 ← 1 [2] 3 4 5 ... 9 → │ ✅ Pagination!
└────────────────────────────────────────────────────────────┘
🎯 ACTION BUTTONS (3 BUTTONS PER ROW)
═════════════════════════════════════════════════════════════
1. 👁 View (Blue) ─────────→ View vendor details
2. ✏️ Edit (Purple) ⭐ NEW → Edit vendor form
3. 🗑 Delete (Red) ────────→ Delete with confirmation
🔢 PAGINATION CONTROLS
═════════════════════════════════════════════════════════════
Page 1 of 10:
[←] 1 2 3 4 5 ... 9 10 [→]
^ ^ ^ ^ ^
| | | | └─ Next button
| | | └──── Last page
| | └─────── Ellipsis (...)
| └───────────────────── Page numbers
└──────────────────────── Previous button (disabled)
Page 5 of 10:
[←] 1 ... 4 [5] 6 ... 10 [→]
↑ ↑ ↑
| | └─ Next page
| └───── Current page (purple)
└───────── Previous pages
📊 HOW IT WORKS
═════════════════════════════════════════════════════════════
1. Template displays: paginatedVendors (not all vendors)
2. Alpine.js computes: Which 10 vendors to show
3. User clicks page: currentPage updates
4. Template refreshes: Shows new 10 vendors
Simple and reactive! 🎉
⚙️ CONFIGURATION
═════════════════════════════════════════════════════════════
Change items per page in vendors.js:
// Find this line:
itemsPerPage: 10,
// Change to any number:
itemsPerPage: 25, // Show 25 per page
itemsPerPage: 50, // Show 50 per page
🧪 TESTING CHECKLIST
═════════════════════════════════════════════════════════════
After installation:
□ Vendors page loads
□ If <10 vendors: No pagination shows (correct!)
□ If >10 vendors: Pagination appears
□ Can click page numbers to navigate
□ Can click ← → arrows
□ "Showing X-Y of Z" updates correctly
□ Edit button (pencil icon) appears
□ Edit button navigates to edit page
□ View button still works
□ Delete button still works
□ Dark mode works
□ Pagination styling matches theme
🎨 FEATURES
═════════════════════════════════════════════════════════════
✅ Smart pagination (10 per page)
✅ Edit button added (purple pencil)
✅ Dynamic page numbers with "..."
✅ Previous/Next disabled at boundaries
✅ Shows "X-Y of Total" count
✅ Dark mode compatible
✅ Windmill theme styling
✅ Fully responsive
✅ Client-side pagination (fast!)
✅ Auto-reset to page 1 after data reload
💡 TIPS
═════════════════════════════════════════════════════════════
1. Need more vendors per page?
Change itemsPerPage in vendors.js
2. Want server-side pagination?
For 1000+ vendors, consider API pagination
3. Want to preserve page on refresh?
Add localStorage for currentPage
4. Want to add search/filter?
Filter vendors array first, then paginate
5. Page numbers look weird?
Check you have enough vendors (need >10 to see pagination)
🆘 TROUBLESHOOTING
═════════════════════════════════════════════════════════════
Problem: Pagination not showing
→ Need more than 10 vendors to see it
→ Check itemsPerPage value
Problem: Edit button doesn't work
→ Check backend route exists: /admin/vendors/{code}/edit
→ Check browser console for errors
Problem: Page numbers stuck
→ Clear browser cache (Ctrl+Shift+R)
→ Check Alpine.js loaded correctly
Problem: "Showing 0-0 of 0"
→ Vendors not loading from API
→ Check API endpoint /admin/vendors
📁 FILES INCLUDED
═════════════════════════════════════════════════════════════
vendors.html ................. Updated template with pagination
vendors.js ................... Updated script with pagination logic
PAGINATION_DOCUMENTATION.md .. Full technical documentation
PAGINATION_QUICK_START.txt ... This file (quick reference)
📖 LEARN MORE
═════════════════════════════════════════════════════════════
For detailed technical explanation, see:
→ PAGINATION_DOCUMENTATION.md
══════════════════════════════════════════════════════════════
Pagination made easy! 📖✨
══════════════════════════════════════════════════════════════

View File

@@ -0,0 +1,297 @@
# Vendor Page Pagination Implementation
## 🎯 What's New
Your vendors page now includes:
1.**Edit button** - Already present (pencil icon next to view icon)
2.**Pagination** - New pagination controls at the bottom of the table
---
## 📦 Files Updated
### 1. vendors.html
**Changes:**
- Changed `x-for="vendor in vendors"``x-for="vendor in paginatedVendors"`
- Added pagination footer with controls
- Added "Showing X-Y of Z" results display
### 2. vendors.js
**Changes:**
- Added pagination state: `currentPage`, `itemsPerPage`
- Added computed properties:
- `paginatedVendors` - Returns current page's vendors
- `totalPages` - Calculates total number of pages
- `startIndex` / `endIndex` - For "Showing X-Y" display
- `pageNumbers` - Generates smart page number array with ellipsis
- Added pagination methods:
- `goToPage(page)` - Navigate to specific page
- `nextPage()` - Go to next page
- `previousPage()` - Go to previous page
---
## 🎨 Pagination Features
### Smart Page Number Display
The pagination intelligently shows page numbers:
**Example 1: Few pages (≤7)**
```
← 1 2 3 4 5 6 7 →
```
**Example 2: Many pages, current = 1**
```
← 1 2 3 ... 9 10 →
```
**Example 3: Many pages, current = 5**
```
← 1 ... 4 5 6 ... 10 →
```
**Example 4: Many pages, current = 10**
```
← 1 ... 8 9 10 →
```
### Configurable Items Per Page
Default: 10 vendors per page
To change, edit in `vendors.js`:
```javascript
itemsPerPage: 10, // Change this number
```
---
## 🔧 How It Works
### 1. Computed Properties (Alpine.js)
Alpine.js computes these values reactively:
```javascript
get paginatedVendors() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.vendors.slice(start, end);
}
```
When `currentPage` changes, `paginatedVendors` automatically updates!
### 2. Template Binding
The HTML template uses the computed property:
```html
<template x-for="vendor in paginatedVendors" :key="vendor.vendor_code">
<!-- Vendor row -->
</template>
```
### 3. Pagination Controls
Buttons call the pagination methods:
```html
<button @click="previousPage()" :disabled="currentPage === 1">
<button @click="goToPage(page)">
<button @click="nextPage()" :disabled="currentPage === totalPages">
```
---
## 📊 Visual Layout
```
┌──────────────────────────────────────────────────────────┐
│ Vendor Management [+ Create Vendor] │
├──────────────────────────────────────────────────────────┤
│ [Total] [Verified] [Pending] [Inactive] ← Stats Cards │
├──────────────────────────────────────────────────────────┤
│ Vendor │ Subdomain │ Status │ Created │ Actions │
├────────┼───────────┼────────┼─────────┼─────────────────┤
│ Acme │ acme │ ✓ │ Jan 1 │ 👁 ✏️ 🗑 │
│ Beta │ beta │ ⏰ │ Jan 2 │ 👁 ✏️ 🗑 │
│ ... │ ... │ ... │ ... │ ... │
├──────────────────────────────────────────────────────────┤
│ Showing 1-10 of 45 ← 1 2 [3] 4 ... 9 → │
└──────────────────────────────────────────────────────────┘
Pagination controls
```
---
## 🎭 Action Buttons Explained
Each vendor row has 3 action buttons:
1. **View Button** (👁 Eye icon - Blue)
- Shows vendor details
- Calls: `viewVendor(vendor.vendor_code)`
- Navigates to: `/admin/vendors/{code}`
2. **Edit Button** (✏️ Pencil icon - Purple) ⭐ NEW
- Opens edit form
- Calls: `editVendor(vendor.vendor_code)`
- Navigates to: `/admin/vendors/{code}/edit`
3. **Delete Button** (🗑 Trash icon - Red)
- Deletes vendor with confirmation
- Calls: `deleteVendor(vendor)`
- Shows confirmation dialog first
---
## 🧪 Testing the Pagination
### Test Scenarios
**Test 1: With few vendors (<10)**
- Pagination should not show if only 1 page
- All vendors visible on page 1
**Test 2: With many vendors (>10)**
- Pagination controls appear
- Can navigate between pages
- "Previous" disabled on page 1
- "Next" disabled on last page
**Test 3: Navigation**
- Click page numbers to jump to specific page
- Click "Previous" arrow to go back
- Click "Next" arrow to go forward
- Page numbers update dynamically
**Test 4: After Delete**
- Delete a vendor
- Data reloads
- Returns to page 1
- Pagination updates
---
## 🎨 Styling
The pagination uses the same Windmill theme as your dashboard:
- **Normal buttons**: Gray with hover effect
- **Current page**: Purple background (matches your theme)
- **Disabled buttons**: 50% opacity, cursor not-allowed
- **Hover effects**: Light gray background
- **Dark mode**: Full support
---
## 🔧 Customization Options
### Change Items Per Page
In `vendors.js`:
```javascript
itemsPerPage: 20, // Show 20 vendors per page
```
### Change Page Number Display Logic
In `vendors.js`, modify the `pageNumbers` computed property:
```javascript
get pageNumbers() {
// Modify this logic to change how page numbers appear
}
```
### Change Pagination Position
In `vendors.html`, the pagination footer is at line ~220.
Move this entire `<div class="grid px-4 py-3...">` section.
---
## 📝 Code Comparison
### Before (No Pagination)
```javascript
// vendors.js
vendors: [], // All vendors displayed at once
// vendors.html
<template x-for="vendor in vendors">
```
### After (With Pagination)
```javascript
// vendors.js
vendors: [],
currentPage: 1,
itemsPerPage: 10,
get paginatedVendors() {
return this.vendors.slice(start, end);
}
// vendors.html
<template x-for="vendor in paginatedVendors">
```
---
## ✅ Installation Checklist
Replace these files in your project:
- [ ] `static/admin/js/vendors.js` → Use `vendors-with-pagination.js`
- [ ] `templates/admin/vendors.html` → Use `vendors-with-pagination.html`
- [ ] Clear browser cache (Ctrl+Shift+R)
- [ ] Test with >10 vendors to see pagination
- [ ] Test edit button works
- [ ] Test page navigation
- [ ] Test in dark mode
---
## 🆘 Troubleshooting
### Pagination not showing?
- Make sure you have more than 10 vendors
- Check `itemsPerPage` value in vendors.js
- Verify `paginatedVendors` is used in template
### Edit button not working?
- Check browser console for errors
- Verify `editVendor()` method exists in vendors.js
- Check the route exists on your backend: `/admin/vendors/{code}/edit`
### Page numbers not updating?
- This is a computed property - it should update automatically
- Check Alpine.js is loaded correctly
- Clear browser cache
### "Showing X-Y of Z" incorrect?
- Check `startIndex` and `endIndex` computed properties
- Verify `vendors.length` is correct
---
## 💡 Tips
1. **Performance**: Pagination is client-side. For 1000+ vendors, consider server-side pagination.
2. **State Preservation**: When returning from edit page, user goes back to page 1. To preserve page state, you'd need to:
- Store `currentPage` in localStorage
- Restore it on page load
3. **Search/Filter**: To add search, filter `vendors` array first, then paginate the filtered results.
4. **Sorting**: Add sorting before pagination to maintain consistent page content.
---
## 🎉 Features Summary
✅ Edit button added (pencil icon)
✅ Smart pagination (10 items per page)
✅ Dynamic page numbers with ellipsis
✅ Previous/Next navigation
✅ Disabled states for boundary pages
✅ "Showing X-Y of Z" display
✅ Dark mode support
✅ Fully responsive
✅ Follows Windmill design theme
Happy paginating! 📖

View File

@@ -0,0 +1,444 @@
# Complete Implementation Guide - Testing Hub, Components & Icons
## 🎉 What's Been Created
### ✅ All Files Follow Your Alpine.js Architecture Perfectly!
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
---
## 📦 Files Created
### 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
### 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
---
## 🔧 Installation Steps
### Step 1: Install JavaScript Files
```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
```
### Step 2: Install HTML Templates
```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
```
### Step 3: Fix Sidebar (IMPORTANT!)
```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_user),
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:
```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' : ''"
href="/admin/vendors">
<span x-html="$icon('shopping-bag')"></span>
<span class="ml-4">Vendors</span>
</a>
</li>
```
**2. JavaScript Files** - Each component must set `currentPage`:
```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
// ... rest of component
};
}
```
### Complete Page Mapping
| Page | JavaScript `currentPage` | Sidebar Check | URL |
|------|--------------------------|---------------|-----|
| Dashboard | `'dashboard'` | `x-show="currentPage === 'dashboard'"` | `/admin/dashboard` |
| Vendors | `'vendors'` | `x-show="currentPage === 'vendors'"` | `/admin/vendors` |
| Users | `'users'` | `x-show="currentPage === 'users'"` | `/admin/users` |
| 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 fixed sidebar now includes:
**Main Navigation:**
- Dashboard
- Vendors
- Users
- Import Jobs
**Developer Tools Section:** (NEW!)
- Components
- Icons
- Testing Hub
**Settings Section:**
- Settings
Each section is properly separated with dividers and all menu items have active indicators.
---
## ✨ New Features
### 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
### 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
### 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
---
## 🎯 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' });
}
```
### Icons Browser Search
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
```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;
}
```
### Testing Hub Navigation
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
---
## 🧪 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
<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>
</a>
</li>
```
---
## 🎯 Summary
**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
**Total Files:** 7 (3 JS + 3 HTML + 1 Sidebar)
**Lines of Code:** ~2000+
**Features Added:** 20+
Everything is ready to install! 🚀

View File

@@ -0,0 +1,228 @@
# UI Components Quick Reference
## Most Common Patterns
### 📝 Form Field (Basic)
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">Field Name</span>
<input
type="text"
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"
/>
</label>
```
### 📝 Required Field with Error
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">
Field Name <span class="text-red-600">*</span>
</span>
<input
type="text"
x-model="formData.field"
required
:class="{ 'border-red-600': errors.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 form-input"
/>
<span x-show="errors.field" class="text-xs text-red-600 dark:text-red-400" x-text="errors.field"></span>
</label>
```
### 📝 Read-Only Field
```html
<label class="block mb-4 text-sm">
<span class="text-gray-700 dark:text-gray-400">Field Name</span>
<input
type="text"
x-model="data.field"
disabled
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"
/>
</label>
```
### 🃏 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-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">Label</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">Value</p>
</div>
</div>
```
### 🃏 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">Title</h3>
<div class="space-y-3">
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Label</p>
<p class="text-sm text-gray-700 dark:text-gray-300">Value</p>
</div>
</div>
</div>
```
### 🔘 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">
Click Me
</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">
<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 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
Cancel
</button>
```
### 🏷️ Status Badge (Success)
```html
<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>
```
### 🏷️ Status Badge (Warning)
```html
<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>
```
### 🏷️ Status Badge (Danger)
```html
<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>
```
## Grid Layouts
### 2 Columns (Desktop)
```html
<div class="grid gap-6 md:grid-cols-2">
<!-- Column 1 -->
<div>...</div>
<!-- Column 2 -->
<div>...</div>
</div>
```
### 4 Columns (Responsive)
```html
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<!-- Cards -->
</div>
```
## Color Classes
### Background Colors
- Primary: `bg-purple-600`
- Success: `bg-green-600`
- Warning: `bg-orange-600`
- Danger: `bg-red-600`
- Info: `bg-blue-600`
### Text Colors
- Primary: `text-purple-600`
- Success: `text-green-600`
- Warning: `text-orange-600`
- Danger: `text-red-600`
- Info: `text-blue-600`
### Icon Colors
- Primary: `text-purple-500 bg-purple-100`
- Success: `text-green-500 bg-green-100`
- Warning: `text-orange-500 bg-orange-100`
- Danger: `text-red-500 bg-red-100`
- Info: `text-blue-500 bg-blue-100`
## Common Icons
- `user-group` - Users/Teams
- `badge-check` - Verified
- `check-circle` - Success
- `x-circle` - Error/Inactive
- `clock` - Pending
- `calendar` - Dates
- `refresh` - Update
- `edit` - Edit
- `delete` - Delete
- `plus` - Add
- `arrow-left` - Back
- `exclamation` - Warning
## Spacing
- Small gap: `gap-3`
- Medium gap: `gap-6`
- Large gap: `gap-8`
- Margin bottom: `mb-4`, `mb-6`, `mb-8`
- Padding: `p-3`, `p-4`, `px-4 py-3`
## Quick Copy-Paste: Page Structure
```html
{# app/templates/admin/your-page.html #}
{% extends "admin/base.html" %}
{% block title %}Your Page{% endblock %}
{% block alpine_data %}yourPageData(){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="my-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Page Title
</h2>
</div>
<!-- Loading State -->
<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>
<!-- Content -->
<div x-show="!loading">
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
<!-- Your content here -->
</div>
</div>
{% endblock %}
```
## Remember
1. Always use `dark:` variants for dark mode
2. Add `:disabled="saving"` to buttons during operations
3. Use `x-show` for conditional display
4. Use `x-text` for dynamic text
5. Use `x-html="$icon(...)"` for icons
6. Validation errors: `border-red-600` class
7. Helper text: `text-xs text-gray-600`
8. Error text: `text-xs text-red-600`
## Reference Page
Visit `/admin/components` for full component library with live examples!

View File

@@ -0,0 +1,305 @@
# UI Components Library Implementation Guide
## 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
### 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.
### 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
### 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
## 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>
```
### Step 2: Add Components Page Route
Update your `app/api/v1/admin/pages.py`:
```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_user),
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,
}
)
```
### 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 }"
/>
<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
```html
<input
type="text"
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
```html
<textarea
x-model="formData.description"
rows="3"
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-textarea"
></textarea>
```
### Card Components
#### 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">
<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>
</div>
</div>
```
#### 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">
Card Title
</h3>
<div class="space-y-3">
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Field Label</p>
<p class="text-sm text-gray-700 dark:text-gray-300">Field Value</p>
</div>
</div>
</div>
```
### 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">
Button Text
</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>
```
## Features of Updated Pages
### Vendor Edit Page Improvements
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
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
## Quick Reference: Where to Find Components
When you need a component, visit `/admin/components` and you'll find:
- **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)
## Testing Checklist
- [ ] `/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
## Color Scheme Reference
Your component library uses these color schemes:
- **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`)
## Next Steps
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.)
## Tips
- 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
Enjoy your new component library! 🎨

View File

@@ -0,0 +1,808 @@
╔══════════════════════════════════════════════════════════════════╗
║ SHOP FRONTEND ARCHITECTURE OVERVIEW ║
║ Alpine.js + Jinja2 + Tailwind CSS + Multi-Theme ║
╚══════════════════════════════════════════════════════════════════╝
📦 WHAT IS THIS?
═════════════════════════════════════════════════════════════════
Customer-facing shop frontend provides visitors with a branded
e-commerce experience unique to each vendor. Built with:
✅ Jinja2 Templates (server-side rendering)
✅ Alpine.js (client-side reactivity)
✅ Tailwind CSS (utility-first styling)
✅ Multi-Theme System (vendor branding)
✅ FastAPI (backend routes)
🎯 KEY PRINCIPLES
═════════════════════════════════════════════════════════════════
1. Theme-First Design
• Each vendor has unique colors, fonts, logos
• CSS variables for dynamic theming
• Custom CSS support per vendor
• Dark mode with vendor colors
2. Progressive Enhancement
• Works without JavaScript (basic HTML)
• JavaScript adds cart, search, filters
• Graceful degradation for older browsers
3. API-First Data Loading
• All products from REST APIs
• Client-side cart management
• Real-time stock updates
• Search and filtering
4. Responsive & Mobile-First
• Mobile-first Tailwind approach
• Touch-friendly interactions
• Optimized images
• Fast page loads
📁 FILE STRUCTURE
═════════════════════════════════════════════════════════════════
app/
├── templates/shop/
│ ├── base.html ← Base template (layout)
│ ├── home.html ← Homepage / product grid
│ ├── product-detail.html ← Single product page
│ ├── cart.html ← Shopping cart
│ ├── checkout.html ← Checkout flow
│ ├── search.html ← Search results
│ ├── category.html ← Category browse
│ ├── about.html ← About the shop
│ ├── contact.html ← Contact form
│ └── partials/ ← Reusable components
│ ├── product-card.html ← Product display card
│ ├── cart-item.html ← Cart item row
│ ├── search-modal.html ← Search overlay
│ └── filters.html ← Product filters
├── static/shop/
│ ├── css/
│ │ ├── shop.css ← Shop-specific styles
│ │ └── themes/ ← Optional theme stylesheets
│ │ ├── modern.css
│ │ ├── minimal.css
│ │ └── elegant.css
│ ├── js/
│ │ ├── shop-layout.js ← Base shop functionality
│ │ ├── home.js ← Homepage logic
│ │ ├── product-detail.js ← Product page logic
│ │ ├── cart.js ← Cart management
│ │ ├── checkout.js ← Checkout flow
│ │ ├── search.js ← Search functionality
│ │ └── filters.js ← Product filtering
│ └── img/
│ ├── placeholder-product.png
│ └── empty-cart.svg
├── static/shared/ ← Shared across all areas
│ ├── js/
│ │ ├── log-config.js ← Logging setup
│ │ ├── icons.js ← Icon registry
│ │ ├── utils.js ← Utility functions
│ │ └── api-client.js ← API wrapper
│ └── css/
│ └── base.css ← Global styles
└── api/v1/shop/
└── pages.py ← Route handlers
🏗️ ARCHITECTURE LAYERS
═════════════════════════════════════════════════════════════════
Layer 1: Routes (FastAPI)
Layer 2: Middleware (Vendor + Theme Detection)
Layer 3: Templates (Jinja2)
Layer 4: JavaScript (Alpine.js)
Layer 5: API (REST endpoints)
Layer 6: Database
Layer 1: ROUTES (FastAPI)
──────────────────────────────────────────────────────────────────
Purpose: Vendor Detection + Template Rendering
Location: app/api/v1/shop/pages.py
Example:
@router.get("/")
async def shop_home(
request: Request,
db: Session = Depends(get_db)
):
vendor = request.state.vendor # From middleware
theme = request.state.theme # From middleware
return templates.TemplateResponse(
"shop/home.html",
{
"request": request,
"vendor": vendor,
"theme": theme,
}
)
Responsibilities:
✅ Access vendor from middleware
✅ Access theme from middleware
✅ Render template
❌ NO database queries (data loaded client-side)
❌ NO business logic
Layer 2: MIDDLEWARE
──────────────────────────────────────────────────────────────────
Purpose: Vendor & Theme Identification
Two middleware components work together:
1. Vendor Context Middleware
• Detects vendor from domain/subdomain
• Sets request.state.vendor
• Returns 404 if vendor not found
2. Theme Context Middleware
• Loads theme for detected vendor
• Sets request.state.theme
• Falls back to default theme
Order matters:
vendor_context_middleware → theme_context_middleware
Layer 3: TEMPLATES (Jinja2)
──────────────────────────────────────────────────────────────────
Purpose: HTML Structure + Vendor Branding
Location: app/templates/shop/
Template Hierarchy:
base.html (layout + theme injection)
home.html (product grid)
partials/product-card.html (components)
Example:
{% extends "shop/base.html" %}
{% block title %}{{ vendor.name }}{% endblock %}
{% block alpine_data %}shopHome(){% endblock %}
{% block content %}
<div x-show="loading">Loading products...</div>
<div x-show="!loading" class="grid grid-cols-1 md:grid-cols-4 gap-6">
<template x-for="product in products" :key="product.id">
<!-- Product card -->
</template>
</div>
{% endblock %}
Key Features:
✅ Theme CSS variables injection
✅ Vendor logo (light/dark mode)
✅ Custom CSS from theme
✅ Social links from theme
✅ Dynamic favicon
Layer 4: JAVASCRIPT (Alpine.js)
──────────────────────────────────────────────────────────────────
Purpose: Client-Side Interactivity + Cart + Search
Location: app/static/shop/js/
Example (shop-layout.js):
function shopLayoutData() {
return {
dark: false,
cartCount: 0,
cart: [],
init() {
this.loadCart();
this.loadThemePreference();
},
addToCart(product, quantity) {
// Add to cart logic
this.cart.push({ ...product, quantity });
this.saveCart();
},
toggleTheme() {
this.dark = !this.dark;
localStorage.setItem('shop-theme',
this.dark ? 'dark' : 'light');
}
};
}
Responsibilities:
✅ Load products from API
✅ Manage cart in localStorage
✅ Handle search and filters
✅ Update DOM reactively
✅ Theme toggling
Layer 5: API (REST)
──────────────────────────────────────────────────────────────────
Purpose: Product Data + Cart + Orders
Location: app/api/v1/shop/*.py (not pages.py)
Example Endpoints:
GET /api/v1/shop/{vendor_code}/products
GET /api/v1/shop/{vendor_code}/products/{id}
GET /api/v1/shop/{vendor_code}/categories
POST /api/v1/shop/{vendor_code}/search
POST /api/v1/shop/{vendor_code}/cart/checkout
🔄 DATA FLOW
═════════════════════════════════════════════════════════════════
Page Load Flow:
──────────────────────────────────────────────────────────────────
1. Customer → visits acme-shop.com
2. Vendor Middleware → Identifies "ACME" vendor
3. Theme Middleware → Loads ACME's theme config
4. FastAPI → Renders shop/home.html
5. Browser → Receives HTML with theme CSS variables
6. Alpine.js → init() executes
7. JavaScript → GET /api/v1/shop/ACME/products
8. API → Returns product list JSON
9. Alpine.js → Updates products array
10. Browser → DOM updates with product cards
Add to Cart Flow:
──────────────────────────────────────────────────────────────────
1. Customer → Clicks "Add to Cart"
2. Alpine.js → addToCart(product, quantity)
3. Alpine.js → Updates cart array
4. Alpine.js → Saves to localStorage
5. Alpine.js → Updates cartCount badge
6. Alpine.js → Shows toast notification
7. Browser → Cart icon updates automatically
Checkout Flow:
──────────────────────────────────────────────────────────────────
1. Customer → Goes to /cart
2. Page → Loads cart from localStorage
3. Customer → Fills checkout form
4. Alpine.js → POST /api/v1/shop/ACME/cart/checkout
5. API → Creates order + payment intent
6. Alpine.js → Redirects to payment
7. Payment → Completes
8. Redirect → /order/{order_id}/confirmation
🎨 MULTI-THEME SYSTEM
═════════════════════════════════════════════════════════════════
How Themes Work:
1. Database Storage
• Each vendor has a theme record
• Stores colors, fonts, logos, layout prefs
• Custom CSS per vendor
2. CSS Variables Injection
• base.html injects variables in <style> tag
• Variables available throughout page
• Example:
:root {
--color-primary: #6366f1;
--color-secondary: #8b5cf6;
--color-accent: #ec4899;
--color-background: #ffffff;
--color-text: #1f2937;
--font-heading: Inter, sans-serif;
--font-body: Inter, sans-serif;
}
3. Usage in Templates
<button style="background-color: var(--color-primary)">
Buy Now
</button>
4. Dark Mode
• Vendor colors adjust for dark mode
• Saved in localStorage
• Applied via :class="{ 'dark': dark }"
• Uses dark: variants in Tailwind
Theme Configuration Example:
──────────────────────────────────────────────────────────────────
{
"theme_name": "modern",
"colors": {
"primary": "#6366f1",
"secondary": "#8b5cf6",
"accent": "#ec4899",
"background": "#ffffff",
"text": "#1f2937"
},
"fonts": {
"heading": "Inter, sans-serif",
"body": "Inter, sans-serif"
},
"branding": {
"logo": "/media/vendors/acme/logo.png",
"logo_dark": "/media/vendors/acme/logo-dark.png",
"favicon": "/media/vendors/acme/favicon.ico"
},
"layout": {
"header": "fixed",
"style": "grid"
},
"social_links": {
"facebook": "https://facebook.com/acme",
"instagram": "https://instagram.com/acme"
},
"custom_css": ".product-card { border-radius: 12px; }"
}
🛒 CART SYSTEM
═════════════════════════════════════════════════════════════════
Client-Side Cart Management:
Storage: localStorage
Format: JSON array of cart items
Cart Item Structure:
{
"id": "product-123",
"name": "Product Name",
"price": 29.99,
"quantity": 2,
"image": "/media/products/image.jpg",
"vendor_code": "ACME"
}
Key Functions:
• loadCart(): Load from localStorage
• saveCart(): Persist to localStorage
• addToCart(product, quantity): Add item
• updateCartItem(id, quantity): Update quantity
• removeFromCart(id): Remove item
• clearCart(): Empty cart
• cartTotal: Computed total price
Cart Persistence:
• Survives page refresh
• Shared across shop pages
• Cleared on checkout completion
• Synced across tabs (optional)
🔍 SEARCH & FILTERS
═════════════════════════════════════════════════════════════════
Search System:
1. Search Modal
• Overlay with input
• Live search as you type
• Keyboard shortcuts (Cmd+K)
2. Search API
POST /api/v1/shop/{vendor_code}/search
{
"query": "laptop",
"category": "electronics",
"min_price": 100,
"max_price": 1000,
"sort": "price:asc"
}
3. Client-Side Filtering
• Filter products array
• No API call needed for basic filters
• Use for in-memory datasets
Filter Components:
• Category dropdown
• Price range slider
• Sort options
• Availability toggle
• Brand checkboxes
📱 RESPONSIVE DESIGN
═════════════════════════════════════════════════════════════════
Breakpoints (Tailwind):
• sm: 640px (mobile landscape)
• md: 768px (tablet)
• lg: 1024px (desktop)
• xl: 1280px (large desktop)
Product Grid Responsive:
• Mobile: 1 column
• Tablet: 2 columns
• Desktop: 4 columns
Example:
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Product cards -->
</div>
Touch Optimization:
• Larger touch targets (min 44x44px)
• Swipeable carousels
• Touch-friendly buttons
• Tap to zoom images
🌙 DARK MODE
═════════════════════════════════════════════════════════════════
Implementation:
1. Alpine.js state: dark: boolean
2. HTML class binding: :class="{ 'dark': dark }"
3. Tailwind variants: dark:bg-gray-800
4. LocalStorage persistence
5. Vendor colors adapt to dark mode
Toggle Button:
<button @click="toggleTheme()">
<svg x-show="!dark"><!-- Sun icon --></svg>
<svg x-show="dark"><!-- Moon icon --></svg>
</button>
Dark Mode Colors:
• Background: dark:bg-gray-900
• Text: dark:text-gray-100
• Borders: dark:border-gray-700
• Cards: dark:bg-gray-800
Vendor Colors:
• Primary color adjusts brightness
• Maintains brand identity
• Readable in both modes
🔐 CUSTOMER AUTHENTICATION
═════════════════════════════════════════════════════════════════
Optional Auth System:
Guest Checkout:
✅ No account required
✅ Email for order updates
✅ Quick checkout
Account Features:
✅ Order history
✅ Saved addresses
✅ Wishlist
✅ Profile management
Auth Flow:
1. Login/Register → POST /api/v1/shop/auth/login
2. API → Return JWT token
3. JavaScript → Store in localStorage
4. API Client → Add to authenticated requests
5. Optional → Use account features
📡 API CLIENT
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/api-client.js
Usage:
const products = await apiClient.get(
`/api/v1/shop/${vendorCode}/products`
);
const order = await apiClient.post(
`/api/v1/shop/${vendorCode}/checkout`,
orderData
);
Features:
✅ Automatic error handling
✅ JSON parsing
✅ Loading states
✅ Auth token injection (if logged in)
🐛 LOGGING
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/log-config.js
Shop-Specific Logging:
shopLog.info('Product added to cart', product);
shopLog.error('Checkout failed', error);
shopLog.debug('Search query', { query, results });
Levels:
• INFO: User actions
• ERROR: Failures and exceptions
• DEBUG: Development debugging
• WARN: Warnings
🎭 ICONS
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/icons.js
Usage:
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
<span x-html="$icon('search', 'w-6 h-6 text-primary')"></span>
Shop Icons:
• shopping-cart, shopping-bag
• heart (wishlist)
• search, filter
• eye (view)
• star (rating)
• truck (shipping)
• check (success)
• x (close)
• chevron-left, chevron-right
• spinner (loading)
🚀 PERFORMANCE
═════════════════════════════════════════════════════════════════
Optimization Techniques:
1. Image Optimization
• WebP format
• Lazy loading
• Responsive images (srcset)
• CDN delivery
2. Code Splitting
• Base layout loads first
• Page-specific JS loads after
• Deferred non-critical CSS
3. Caching
• Browser cache for assets
• LocalStorage for cart
• Service worker (optional)
4. Lazy Loading
• Products load as you scroll
• Images load when visible
• Infinite scroll for large catalogs
5. CDN Assets
• Tailwind from CDN
• Alpine.js from CDN
• Vendor assets from CDN
📊 PAGE-BY-PAGE BREAKDOWN
═════════════════════════════════════════════════════════════════
/ (Homepage)
──────────────────────────────────────────────────────────────────
Purpose: Display featured products, hero section
Components:
• Hero banner with CTA
• Featured products grid
• Category cards
• About vendor section
Data Sources:
• GET /api/v1/shop/{code}/products?featured=true
• GET /api/v1/shop/{code}/categories
/products
──────────────────────────────────────────────────────────────────
Purpose: Browse all products with filters
Components:
• Product grid
• Filter sidebar
• Sort dropdown
• Pagination
Data Sources:
• GET /api/v1/shop/{code}/products
• Filters applied client-side or server-side
/products/{product_id}
──────────────────────────────────────────────────────────────────
Purpose: Single product detail page
Components:
• Image gallery
• Product info
• Add to cart form
• Related products
• Reviews (optional)
Data Sources:
• GET /api/v1/shop/{code}/products/{id}
• GET /api/v1/shop/{code}/products/{id}/related
/cart
──────────────────────────────────────────────────────────────────
Purpose: Review cart contents before checkout
Components:
• Cart items list
• Quantity adjusters
• Remove buttons
• Cart total
• Checkout button
Data Sources:
• LocalStorage cart
• No API call needed
/checkout
──────────────────────────────────────────────────────────────────
Purpose: Complete purchase
Components:
• Shipping form
• Payment form
• Order summary
• Submit button
Data Sources:
• POST /api/v1/shop/{code}/checkout
• Stripe/PayPal integration
/search
──────────────────────────────────────────────────────────────────
Purpose: Search results page
Components:
• Search input
• Results grid
• Filter options
• Sort options
Data Sources:
• POST /api/v1/shop/{code}/search
/category/{category_slug}
──────────────────────────────────────────────────────────────────
Purpose: Browse products by category
Components:
• Breadcrumbs
• Product grid
• Subcategories
• Filters
Data Sources:
• GET /api/v1/shop/{code}/categories/{slug}/products
/about
──────────────────────────────────────────────────────────────────
Purpose: About the vendor
Components:
• Vendor story
• Team photos
• Values/mission
• Contact info
Data Sources:
• Vendor info from middleware
• Static content
/contact
──────────────────────────────────────────────────────────────────
Purpose: Contact form
Components:
• Contact form
• Map (optional)
• Business hours
• Social links
Data Sources:
• POST /api/v1/shop/{code}/contact
🎓 LEARNING PATH
═════════════════════════════════════════════════════════════════
For New Developers:
1. Understand Architecture (1 hour)
→ Read this document
→ Review file structure
→ Examine base template
→ Understand theme system
2. Study Existing Page (2 hours)
→ Open home.html
→ Open shop-layout.js
→ Trace product loading flow
→ Examine cart management
3. Create Simple Page (4 hours)
→ Copy templates
→ Modify for new feature
→ Test with different vendor themes
→ Verify responsive design
4. Add Complex Feature (1 day)
→ Product filters
→ Cart operations
→ API integration
→ Search functionality
5. Master Patterns (1 week)
→ All common patterns
→ Theme customization
→ Performance optimization
→ Mobile responsiveness
🔄 DEPLOYMENT CHECKLIST
═════════════════════════════════════════════════════════════════
Before Deploying:
□ Build Tailwind CSS
□ Minify JavaScript
□ Test all routes
□ Test with multiple vendor themes
□ Verify cart persistence
□ Check mobile responsive
□ Test dark mode
□ Validate product display
□ Test checkout flow
□ Check image optimization
□ Test search functionality
□ Verify social links
□ Test across browsers
□ Check console for errors
□ Test in production mode
🔒 SECURITY
═════════════════════════════════════════════════════════════════
Best Practices:
1. Input Validation
✅ Validate all form inputs
✅ Sanitize user content
✅ XSS prevention
2. Cart Security
✅ Validate cart on server
✅ Check stock availability
✅ Verify prices server-side
✅ Never trust client cart
3. Payment Security
✅ Use trusted payment providers
✅ Never store card details
✅ HTTPS only
✅ PCI compliance
4. Rate Limiting
✅ Search endpoint throttling
✅ Contact form limits
✅ Checkout attempt limits
📚 REFERENCE LINKS
═════════════════════════════════════════════════════════════════
Documentation:
• Alpine.js: https://alpinejs.dev/
• Tailwind CSS: https://tailwindcss.com/
• Jinja2: https://jinja.palletsprojects.com/
• FastAPI: https://fastapi.tiangolo.com/
Internal Docs:
• Page Template Guide: FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md
• Multi-Theme Guide: MULTI_THEME_SHOP_GUIDE.md
• API Documentation: API_REFERENCE.md
• Database Schema: DATABASE_SCHEMA.md
══════════════════════════════════════════════════════════════════
SHOP FRONTEND ARCHITECTURE
Theme-Driven, Customer-Focused, Brand-Consistent
══════════════════════════════════════════════════════════════════

View File

@@ -0,0 +1,972 @@
# Shop Frontend - Alpine.js/Jinja2 Page Template Guide
## 📋 Overview
This guide provides complete templates for creating new customer-facing shop pages using the established Alpine.js + Jinja2 + Multi-Theme architecture. Follow these patterns to ensure consistency across all vendor shops while maintaining unique branding.
---
## 🎯 Quick Reference
### File Structure for New Page
```
app/
├── templates/shop/
│ └── [page-name].html # Jinja2 template
├── static/shop/js/
│ └── [page-name].js # Alpine.js component
└── api/v1/shop/
└── pages.py # Route registration
```
### Checklist for New Page
- [ ] Create Jinja2 template extending shop/base.html
- [ ] Create Alpine.js JavaScript component
- [ ] Register route in pages.py
- [ ] Test with multiple vendor themes
- [ ] Test responsive design (mobile/tablet/desktop)
- [ ] Test dark mode
- [ ] Test cart integration (if applicable)
- [ ] Verify theme CSS variables work
- [ ] Check image optimization
---
## 📄 Template Structure
### 1. Jinja2 Template
**File:** `app/templates/shop/[page-name].html`
```jinja2
{# app/templates/shop/[page-name].html #}
{% extends "shop/base.html" %}
{# Page title for browser tab - includes vendor name #}
{% block title %}[Page Name] - {{ vendor.name }}{% endblock %}
{# Meta description for SEO #}
{% block meta_description %}[Page description for SEO]{% endblock %}
{# Alpine.js component name #}
{% block alpine_data %}shop[PageName](){% endblock %}
{# Page content #}
{% block content %}
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PAGE HEADER -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Breadcrumb -->
<nav class="flex mb-6 text-sm" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-3">
<li class="inline-flex items-center">
<a href="/" class="text-gray-700 hover:text-primary dark:text-gray-400">
Home
</a>
</li>
<li>
<div class="flex items-center">
<svg class="w-4 h-4 text-gray-400 mx-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
<span class="text-gray-500 dark:text-gray-400">[Page Name]</span>
</div>
</li>
</ol>
</nav>
<!-- Page Title -->
<div class="flex items-center justify-between mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white"
style="font-family: var(--font-heading)">
[Page Name]
</h1>
<!-- Optional action button -->
<button
@click="someAction()"
class="px-6 py-2 text-white rounded-lg transition-colors"
style="background-color: var(--color-primary)"
:style="{ 'background-color': 'var(--color-primary)' }"
>
Action
</button>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- LOADING STATE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div x-show="loading" class="text-center py-20">
<div class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-gray-200"
:style="{ 'border-top-color': 'var(--color-primary)' }">
</div>
<p class="mt-4 text-gray-600 dark:text-gray-400">Loading...</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- ERROR STATE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div x-show="error && !loading"
class="mb-6 p-6 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<div class="flex items-start">
<svg class="w-6 h-6 text-red-600 dark:text-red-400 mr-3 flex-shrink-0"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<div>
<h3 class="text-red-800 dark:text-red-200 font-semibold">Error</h3>
<p class="text-red-700 dark:text-red-300 text-sm mt-1" x-text="error"></p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MAIN CONTENT -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div x-show="!loading">
<!-- Empty State -->
<div x-show="items.length === 0" class="text-center py-20">
<svg class="w-24 h-24 mx-auto text-gray-300 dark:text-gray-600 mb-4"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path>
</svg>
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">
No items found
</h3>
<p class="text-gray-500 dark:text-gray-400">
Try adjusting your filters or check back later.
</p>
</div>
<!-- Grid Layout (for products, items, etc.) -->
<div x-show="items.length > 0"
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
<template x-for="item in items" :key="item.id">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-lg transition-shadow border border-gray-200 dark:border-gray-700">
<!-- Item Image -->
<div class="aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-t-lg bg-gray-100 dark:bg-gray-700">
<img :src="item.image || '/static/shop/img/placeholder-product.png'"
:alt="item.name"
class="w-full h-full object-cover object-center hover:scale-105 transition-transform"
loading="lazy">
</div>
<!-- Item Info -->
<div class="p-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2"
x-text="item.name"></h3>
<p class="text-gray-600 dark:text-gray-400 text-sm mb-4 line-clamp-2"
x-text="item.description"></p>
<!-- Price -->
<div class="flex items-center justify-between">
<span class="text-2xl font-bold"
:style="{ color: 'var(--color-primary)' }"
x-text="formatPrice(item.price)"></span>
<button @click="addToCart(item)"
class="px-4 py-2 text-white rounded-lg hover:opacity-90 transition-opacity"
:style="{ 'background-color': 'var(--color-primary)' }">
Add
</button>
</div>
</div>
</div>
</template>
</div>
<!-- ═════════════════════════════════════════════════════════════ -->
<!-- PAGINATION -->
<!-- ═════════════════════════════════════════════════════════════ -->
<div x-show="pagination.totalPages > 1"
class="flex justify-center items-center space-x-2 mt-12">
<!-- Previous Button -->
<button
@click="goToPage(pagination.currentPage - 1)"
:disabled="pagination.currentPage === 1"
class="px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
Previous
</button>
<!-- Page Numbers -->
<template x-for="page in paginationRange" :key="page">
<button
@click="goToPage(page)"
:class="page === pagination.currentPage
? 'text-white'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'"
:style="page === pagination.currentPage ? { 'background-color': 'var(--color-primary)' } : {}"
class="px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600"
x-text="page"
></button>
</template>
<!-- Next Button -->
<button
@click="goToPage(pagination.currentPage + 1)"
:disabled="pagination.currentPage === pagination.totalPages"
class="px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
</button>
</div>
</div>
</div>
{% endblock %}
{# Page-specific JavaScript #}
{% block extra_scripts %}
<script src="{{ url_for('static', path='shop/js/[page-name].js') }}"></script>
{% endblock %}
```
---
### 2. Alpine.js Component
**File:** `app/static/shop/js/[page-name].js`
```javascript
// static/shop/js/[page-name].js
/**
* [Page Name] Component
* Handles [describe functionality]
*/
const pageLog = {
info: (...args) => console.info('🛍️ [PAGE]', ...args),
warn: (...args) => console.warn('⚠️ [PAGE]', ...args),
error: (...args) => console.error('❌ [PAGE]', ...args),
debug: (...args) => console.log('🔍 [PAGE]', ...args)
};
/**
* Main Alpine.js component for [page name]
*/
function shop[PageName]() {
return {
// ─────────────────────────────────────────────────────
// STATE
// ─────────────────────────────────────────────────────
loading: false,
error: '',
items: [],
// Pagination
pagination: {
currentPage: 1,
totalPages: 1,
perPage: 12,
total: 0
},
// Filters
filters: {
search: '',
category: '',
sortBy: 'created_at:desc'
},
// Vendor info (from template)
vendorCode: '{{ vendor.code }}',
// ─────────────────────────────────────────────────────
// LIFECYCLE
// ─────────────────────────────────────────────────────
/**
* Initialize component
*/
async init() {
pageLog.info('[PageName] initializing...');
await this.loadData();
pageLog.info('[PageName] initialized');
},
// ─────────────────────────────────────────────────────
// DATA LOADING
// ─────────────────────────────────────────────────────
/**
* Load main data from API
*/
async loadData() {
this.loading = true;
this.error = '';
try {
const params = new URLSearchParams({
page: this.pagination.currentPage,
per_page: this.pagination.perPage,
...this.filters
});
const response = await fetch(
`/api/v1/shop/${this.vendorCode}/items?${params}`
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Update state
this.items = data.items || [];
this.pagination.total = data.total || 0;
this.pagination.totalPages = Math.ceil(
this.pagination.total / this.pagination.perPage
);
pageLog.info('Data loaded:', this.items.length, 'items');
} catch (error) {
pageLog.error('Failed to load data:', error);
this.error = error.message || 'Failed to load data';
} finally {
this.loading = false;
}
},
/**
* Refresh data
*/
async refresh() {
pageLog.info('Refreshing data...');
this.error = '';
await this.loadData();
},
// ─────────────────────────────────────────────────────
// FILTERS & SEARCH
// ─────────────────────────────────────────────────────
/**
* Apply filters and reload data
*/
async applyFilters() {
pageLog.debug('Applying filters:', this.filters);
this.pagination.currentPage = 1; // Reset to first page
await this.loadData();
},
/**
* Reset filters to default
*/
async resetFilters() {
this.filters = {
search: '',
category: '',
sortBy: 'created_at:desc'
};
await this.applyFilters();
},
// ─────────────────────────────────────────────────────
// PAGINATION
// ─────────────────────────────────────────────────────
/**
* Navigate to specific page
*/
async goToPage(page) {
if (page < 1 || page > this.pagination.totalPages) return;
this.pagination.currentPage = page;
await this.loadData();
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
},
/**
* Get pagination range for display
*/
get paginationRange() {
const current = this.pagination.currentPage;
const total = this.pagination.totalPages;
const range = [];
// Show max 7 page numbers
let start = Math.max(1, current - 3);
let end = Math.min(total, start + 6);
// Adjust start if we're near the end
if (end - start < 6) {
start = Math.max(1, end - 6);
}
for (let i = start; i <= end; i++) {
range.push(i);
}
return range;
},
// ─────────────────────────────────────────────────────
// CART INTEGRATION
// ─────────────────────────────────────────────────────
/**
* Add item to cart
*/
addToCart(item, quantity = 1) {
pageLog.info('Adding to cart:', item.name);
// Get cart from shop layout
const shopLayout = Alpine.store('shop') || window.shopLayoutData();
if (shopLayout && typeof shopLayout.addToCart === 'function') {
shopLayout.addToCart(item, quantity);
this.showToast(`${item.name} added to cart`, 'success');
} else {
pageLog.error('Shop layout not available');
}
},
// ─────────────────────────────────────────────────────
// UI HELPERS
// ─────────────────────────────────────────────────────
/**
* Show toast notification
*/
showToast(message, type = 'info') {
const shopLayout = Alpine.store('shop') || window.shopLayoutData();
if (shopLayout && typeof shopLayout.showToast === 'function') {
shopLayout.showToast(message, type);
}
},
/**
* Format price as currency
*/
formatPrice(price) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price);
},
/**
* Format date
*/
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
},
/**
* Truncate text
*/
truncate(text, length = 100) {
if (!text) return '';
if (text.length <= length) return text;
return text.substring(0, length) + '...';
}
};
}
// Make available globally
window.shop[PageName] = shop[PageName];
pageLog.info('[PageName] module loaded');
```
---
### 3. Route Registration
**File:** `app/api/v1/shop/pages.py`
```python
from fastapi import APIRouter, Request, Depends
from sqlalchemy.orm import Session
from app.core.database import get_db
router = APIRouter()
@router.get("/[page-route]")
async def [page_name]_page(
request: Request,
db: Session = Depends(get_db)
):
"""
[Page Name] page
Displays [description]
"""
# Vendor and theme come from middleware
vendor = request.state.vendor
theme = request.state.theme
return templates.TemplateResponse(
"shop/[page-name].html",
{
"request": request,
"vendor": vendor,
"theme": theme,
}
)
```
---
## 🎨 Common Page Patterns
### Pattern 1: Product Grid Page (Homepage, Category)
**Use for:** Homepage, category pages, search results
```javascript
async init() {
await this.loadProducts();
}
async loadProducts() {
this.loading = true;
try {
const response = await fetch(
`/api/v1/shop/${this.vendorCode}/products?category=${this.category}`
);
const data = await response.json();
this.products = data.products || [];
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
```
**Template:**
```html
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<template x-for="product in products" :key="product.id">
{% include 'shop/partials/product-card.html' %}
</template>
</div>
```
---
### Pattern 2: Product Detail Page
**Use for:** Single product pages
```javascript
async init() {
const productId = this.getProductIdFromUrl();
await this.loadProduct(productId);
await this.loadRelatedProducts(productId);
}
async loadProduct(id) {
const product = await fetch(
`/api/v1/shop/${this.vendorCode}/products/${id}`
).then(r => r.json());
this.product = product;
this.selectedImage = product.images[0];
}
addToCartWithQuantity() {
const shopLayout = window.shopLayoutData();
shopLayout.addToCart(this.product, this.quantity);
}
```
**Template:**
```html
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12">
<!-- Image Gallery -->
<div>
<img :src="selectedImage" class="w-full rounded-lg">
<div class="grid grid-cols-4 gap-2 mt-4">
<template x-for="img in product.images">
<img @click="selectedImage = img"
:src="img"
class="cursor-pointer rounded border-2"
:class="selectedImage === img ? 'border-primary' : 'border-gray-200'">
</template>
</div>
</div>
<!-- Product Info -->
<div>
<h1 class="text-3xl font-bold mb-4" x-text="product.name"></h1>
<p class="text-2xl font-bold mb-6"
:style="{ color: 'var(--color-primary)' }"
x-text="formatPrice(product.price)"></p>
<p class="text-gray-600 mb-8" x-text="product.description"></p>
<!-- Quantity -->
<div class="flex items-center space-x-4 mb-6">
<label>Quantity:</label>
<input type="number" x-model="quantity" min="1" class="w-20 px-3 py-2 border rounded">
</div>
<!-- Add to Cart -->
<button @click="addToCartWithQuantity()"
class="w-full py-3 text-white rounded-lg text-lg font-semibold"
:style="{ 'background-color': 'var(--color-primary)' }">
Add to Cart
</button>
</div>
</div>
```
---
### Pattern 3: Cart Page
**Use for:** Shopping cart
```javascript
async init() {
this.loadCart();
}
loadCart() {
const shopLayout = window.shopLayoutData();
this.cart = shopLayout.cart;
this.calculateTotals();
}
updateQuantity(productId, quantity) {
const shopLayout = window.shopLayoutData();
shopLayout.updateCartItem(productId, quantity);
this.loadCart();
}
removeItem(productId) {
const shopLayout = window.shopLayoutData();
shopLayout.removeFromCart(productId);
this.loadCart();
}
get subtotal() {
return this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
get shipping() {
return this.subtotal > 50 ? 0 : 9.99;
}
get total() {
return this.subtotal + this.shipping;
}
```
---
### Pattern 4: Search & Filter Page
**Use for:** Search results, filtered product lists
```javascript
filters: {
search: '',
category: '',
minPrice: 0,
maxPrice: 1000,
sortBy: 'relevance',
inStock: true
},
async performSearch() {
this.loading = true;
try {
const response = await fetch(
`/api/v1/shop/${this.vendorCode}/search`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.filters)
}
);
const data = await response.json();
this.results = data.results || [];
} finally {
this.loading = false;
}
}
```
---
## 🔧 Best Practices
### 1. Theme Integration
Always use CSS variables for vendor colors:
```html
<!-- ✅ GOOD: Uses theme variable -->
<button :style="{ 'background-color': 'var(--color-primary)' }">
Buy Now
</button>
<!-- ❌ BAD: Hardcoded color -->
<button class="bg-blue-500">
Buy Now
</button>
```
### 2. Cart Integration
Always use the shop layout's cart methods:
```javascript
// ✅ GOOD: Uses shop layout
const shopLayout = window.shopLayoutData();
shopLayout.addToCart(product, quantity);
// ❌ BAD: Direct localStorage manipulation
localStorage.setItem('cart', JSON.stringify(cart));
```
### 3. Loading States
Always show loading indicators:
```javascript
this.loading = true;
try {
// ... async operation
} finally {
this.loading = false; // Always executes
}
```
### 4. Error Handling
Always handle errors gracefully:
```javascript
try {
await this.loadData();
} catch (error) {
console.error('Load failed:', error);
this.error = 'Unable to load products. Please try again.';
// Don't throw - let UI handle gracefully
}
```
### 5. Responsive Images
Use lazy loading and responsive images:
```html
<img :src="product.image"
:alt="product.name"
loading="lazy"
class="w-full h-full object-cover">
```
### 6. Dark Mode
Support both light and dark modes:
```html
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
Content
</div>
```
### 7. Accessibility
Add proper ARIA labels and keyboard navigation:
```html
<button @click="addToCart(product)"
aria-label="Add to cart"
role="button">
Add to Cart
</button>
```
---
## 📱 Responsive Design Checklist
- [ ] Mobile (< 640px): Single column layout
- [ ] Tablet (640px - 1024px): 2-3 column layout
- [ ] Desktop (> 1024px): 4 column layout
- [ ] Images scale properly on all devices
- [ ] Touch targets are at least 44x44px
- [ ] Text is readable without zooming
- [ ] Navigation adapts to screen size
- [ ] Modals are scrollable on small screens
- [ ] Forms are easy to fill on mobile
---
## ✅ Testing Checklist
### Functionality
- [ ] Page loads without errors
- [ ] Data loads correctly
- [ ] Loading state displays
- [ ] Error state handles failures
- [ ] Empty state shows when no data
- [ ] Filters work correctly
- [ ] Pagination works
- [ ] Cart integration works
### Theme Integration
- [ ] Vendor colors display correctly
- [ ] Vendor logo displays
- [ ] Custom fonts load
- [ ] Custom CSS applies
- [ ] Dark mode works with vendor colors
### Responsive Design
- [ ] Mobile layout works
- [ ] Tablet layout works
- [ ] Desktop layout works
- [ ] Images are responsive
- [ ] Touch interactions work
### Performance
- [ ] Page loads quickly
- [ ] Images load progressively
- [ ] No console errors
- [ ] No memory leaks
### Accessibility
- [ ] Keyboard navigation works
- [ ] Screen reader compatible
- [ ] Color contrast sufficient
- [ ] ARIA labels present
---
## 🎯 Component Library
### Reusable Partials
Create reusable components in `templates/shop/partials/`:
**product-card.html:**
```html
<div class="product-card bg-white dark:bg-gray-800 rounded-lg shadow hover:shadow-lg">
<img :src="product.image" :alt="product.name" class="w-full h-64 object-cover rounded-t-lg">
<div class="p-4">
<h3 class="font-semibold text-lg" x-text="product.name"></h3>
<p class="text-2xl font-bold"
:style="{ color: 'var(--color-primary)' }"
x-text="formatPrice(product.price)"></p>
<button @click="addToCart(product)"
class="w-full mt-4 py-2 text-white rounded"
:style="{ 'background-color': 'var(--color-primary)' }">
Add to Cart
</button>
</div>
</div>
```
**filter-sidebar.html:**
```html
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h3 class="font-semibold mb-4">Filters</h3>
<!-- Category -->
<div class="mb-6">
<label class="block mb-2 font-medium">Category</label>
<select x-model="filters.category" @change="applyFilters()"
class="w-full px-3 py-2 border rounded">
<option value="">All Categories</option>
<template x-for="cat in categories">
<option :value="cat.id" x-text="cat.name"></option>
</template>
</select>
</div>
<!-- Price Range -->
<div class="mb-6">
<label class="block mb-2 font-medium">Price Range</label>
<input type="range" x-model="filters.maxPrice"
min="0" max="1000" step="10"
class="w-full">
<div class="flex justify-between text-sm">
<span>$0</span>
<span x-text="'$' + filters.maxPrice"></span>
</div>
</div>
<!-- Apply Button -->
<button @click="applyFilters()"
class="w-full py-2 text-white rounded"
:style="{ 'background-color': 'var(--color-primary)' }">
Apply Filters
</button>
</div>
```
---
## 🚀 Quick Start Commands
```bash
# Create new page files
touch app/templates/shop/new-page.html
touch app/static/shop/js/new-page.js
# Copy templates
cp template.html app/templates/shop/new-page.html
cp template.js app/static/shop/js/new-page.js
# Update placeholders:
# - Replace [page-name] with actual name
# - Replace [PageName] with PascalCase name
# - Add route in pages.py
# - Test with multiple vendor themes!
```
---
## 📚 Additional Resources
### Theme System
- **CSS Variables**: All vendor colors in `var(--color-name)` format
- **Fonts**: `var(--font-heading)` and `var(--font-body)`
- **Logo**: Available in both light and dark versions
- **Custom CSS**: Vendor-specific styles automatically injected
### Shop Layout Functions
- `addToCart(product, quantity)`: Add item to cart
- `showToast(message, type)`: Show notification
- `formatPrice(amount)`: Format as currency
- `formatDate(date)`: Format date string
### Icons
Use the global icon helper:
```html
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
<span x-html="$icon('heart', 'w-6 h-6 text-red-500')"></span>
```
### API Client
Shared API wrapper for authenticated requests:
```javascript
const data = await apiClient.get('/endpoint');
await apiClient.post('/endpoint', { data });
```
---
This template provides a complete, theme-aware pattern for building shop pages with consistent structure, vendor branding, cart integration, and excellent user experience across all devices.

695
docs/frontend/vendor/architecture.md vendored Normal file
View File

@@ -0,0 +1,695 @@
╔══════════════════════════════════════════════════════════════════╗
║ VENDOR ADMIN FRONTEND ARCHITECTURE OVERVIEW ║
║ Alpine.js + Jinja2 + Tailwind CSS ║
╚══════════════════════════════════════════════════════════════════╝
📦 WHAT IS THIS?
═════════════════════════════════════════════════════════════════
Vendor admin frontend provides vendors with a complete management
interface for their store. Built with:
✅ Jinja2 Templates (server-side rendering)
✅ Alpine.js (client-side reactivity)
✅ Tailwind CSS (utility-first styling)
✅ FastAPI (backend routes)
🎯 KEY PRINCIPLES
═════════════════════════════════════════════════════════════════
1. Minimal Server-Side Rendering
• Routes handle authentication + template rendering
• NO database queries in route handlers
• ALL data loaded client-side via JavaScript
2. Component-Based Architecture
• Base template with shared layout
• Reusable partials (header, sidebar, etc.)
• Page-specific templates extend base
3. Progressive Enhancement
• Works without JavaScript (basic HTML)
• JavaScript adds interactivity
• Graceful degradation
4. API-First Data Loading
• All data from REST APIs
• Client-side state management
• Real-time updates possible
📁 FILE STRUCTURE
═════════════════════════════════════════════════════════════════
app/
├── templates/vendor/
│ ├── base.html ← Base template (layout)
│ ├── login.html ← Public login page
│ ├── admin/ ← Authenticated pages
│ │ ├── dashboard.html
│ │ ├── products.html
│ │ ├── orders.html
│ │ ├── customers.html
│ │ ├── inventory.html
│ │ ├── marketplace.html
│ │ ├── team.html
│ │ └── settings.html
│ └── partials/ ← Reusable components
│ ├── header.html ← Top navigation
│ ├── sidebar.html ← Main navigation
│ ├── vendor_info.html ← Vendor details card
│ └── notifications.html ← Toast notifications
├── static/vendor/
│ ├── css/
│ │ ├── tailwind.output.css ← Generated Tailwind
│ │ └── vendor.css ← Custom styles
│ ├── js/
│ │ ├── init-alpine.js ← Alpine.js base data
│ │ ├── dashboard.js ← Dashboard logic
│ │ ├── products.js ← Products page logic
│ │ ├── orders.js ← Orders page logic
│ │ ├── customers.js ← Customers page logic
│ │ ├── inventory.js ← Inventory page logic
│ │ ├── marketplace.js ← Marketplace page logic
│ │ ├── team.js ← Team page logic
│ │ └── settings.js ← Settings page logic
│ └── img/
│ ├── login-office.jpeg
│ └── login-office-dark.jpeg
├── static/shared/ ← Shared across all areas
│ ├── js/
│ │ ├── log-config.js ← Logging setup
│ │ ├── icons.js ← Icon registry
│ │ ├── utils.js ← Utility functions
│ │ └── api-client.js ← API wrapper
│ └── css/
│ └── base.css ← Global styles
└── api/v1/vendor/
└── pages.py ← Route handlers
🏗️ ARCHITECTURE LAYERS
═════════════════════════════════════════════════════════════════
Layer 1: Routes (FastAPI)
Layer 2: Templates (Jinja2)
Layer 3: JavaScript (Alpine.js)
Layer 4: API (REST endpoints)
Layer 5: Database
Layer 1: ROUTES (FastAPI)
──────────────────────────────────────────────────────────────────
Purpose: Authentication + Template Rendering
Location: app/api/v1/vendor/pages.py
Example:
@router.get("/vendor/{vendor_code}/dashboard")
async def vendor_dashboard_page(
request: Request,
vendor_code: str,
current_user: User = Depends(get_current_vendor_user)
):
return templates.TemplateResponse(
"vendor/admin/dashboard.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code
}
)
Responsibilities:
✅ Verify authentication
✅ Extract route parameters
✅ Render template
❌ NO database queries
❌ NO business logic
Layer 2: TEMPLATES (Jinja2)
──────────────────────────────────────────────────────────────────
Purpose: HTML Structure + Server-Side Data
Location: app/templates/vendor/
Template Hierarchy:
base.html (layout)
admin/dashboard.html (page)
partials/sidebar.html (components)
Example:
{% extends "vendor/base.html" %}
{% block title %}Dashboard{% endblock %}
{% block alpine_data %}vendorDashboard(){% endblock %}
{% block content %}
<div x-show="loading">Loading...</div>
<div x-show="!loading" x-text="stats.products_count"></div>
{% endblock %}
Key Features:
✅ Template inheritance
✅ Server-side variables (user, vendor_code)
✅ Include partials
✅ Block overrides
Layer 3: JAVASCRIPT (Alpine.js)
──────────────────────────────────────────────────────────────────
Purpose: Client-Side Interactivity + Data Loading
Location: app/static/vendor/js/
Example:
function vendorDashboard() {
return {
loading: false,
stats: {},
async init() {
await this.loadStats();
},
async loadStats() {
this.loading = true;
try {
this.stats = await apiClient.get(
`/api/v1/vendors/${this.vendorCode}/stats`
);
} finally {
this.loading = false;
}
}
};
}
Responsibilities:
✅ Load data from API
✅ Manage UI state
✅ Handle user interactions
✅ Update DOM reactively
Layer 4: API (REST)
──────────────────────────────────────────────────────────────────
Purpose: Business Logic + Data Access
Location: app/api/v1/vendor/*.py (not pages.py)
Example Endpoints:
GET /api/v1/vendors/{code}/stats
GET /api/v1/vendors/{code}/products
POST /api/v1/vendors/{code}/products
PUT /api/v1/vendors/{code}/products/{id}
DELETE /api/v1/vendors/{code}/products/{id}
🔄 DATA FLOW
═════════════════════════════════════════════════════════════════
Page Load Flow:
──────────────────────────────────────────────────────────────────
1. User → GET /vendor/ACME/dashboard
2. FastAPI → Check authentication
3. FastAPI → Render template with minimal context
4. Browser → Load HTML + CSS + JS
5. Alpine.js → init() executes
6. JavaScript → API call for data
7. API → Return JSON data
8. Alpine.js → Update reactive state
9. Browser → DOM updates automatically
User Interaction Flow:
──────────────────────────────────────────────────────────────────
1. User → Click "Add Product"
2. Alpine.js → openCreateModal()
3. Alpine.js → Show modal
4. User → Fill form + submit
5. Alpine.js → POST to API
6. API → Create product + return
7. Alpine.js → Update local state
8. Browser → DOM updates automatically
9. Alpine.js → Close modal
🎨 STYLING SYSTEM
═════════════════════════════════════════════════════════════════
Tailwind CSS Utility Classes:
• Responsive: sm:, md:, lg:, xl:
• Dark mode: dark:bg-gray-800
• Hover: hover:bg-purple-700
• Focus: focus:outline-none
• Transitions: transition-colors duration-150
Custom CSS Variables (vendor/css/vendor.css):
--color-primary: #7c3aed (purple-600)
--color-accent: #ec4899 (pink-500)
--color-success: #10b981 (green-500)
--color-warning: #f59e0b (yellow-500)
--color-danger: #ef4444 (red-500)
🔐 AUTHENTICATION
═════════════════════════════════════════════════════════════════
Auth Flow:
1. Login → POST /api/v1/vendor/auth/login
2. API → Return JWT token
3. JavaScript → Store in localStorage
4. API Client → Add to all requests
5. Routes → Verify with get_current_vendor_user
Protected Routes:
• All /vendor/{code}/admin/* routes
• Require valid JWT token
• Redirect to login if unauthorized
Public Routes:
• /vendor/{code}/login
• No authentication required
📱 RESPONSIVE DESIGN
═════════════════════════════════════════════════════════════════
Breakpoints (Tailwind):
• sm: 640px (mobile landscape)
• md: 768px (tablet)
• lg: 1024px (desktop)
• xl: 1280px (large desktop)
Mobile-First Approach:
• Base styles for mobile
• Add complexity for larger screens
• Hide sidebar on mobile
• Stack cards vertically
Example:
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
<!-- 1 column mobile, 2 tablet, 4 desktop -->
</div>
🌙 DARK MODE
═════════════════════════════════════════════════════════════════
Implementation:
1. Alpine.js state: dark: boolean
2. HTML class binding: :class="{ 'dark': dark }"
3. Tailwind variants: dark:bg-gray-800
4. LocalStorage: persist preference
Toggle:
toggleTheme() {
this.dark = !this.dark;
localStorage.setItem('theme', this.dark ? 'dark' : 'light');
}
🔧 COMPONENT PATTERNS
═════════════════════════════════════════════════════════════════
Pattern 1: DATA TABLE
──────────────────────────────────────────────────────────────────
Use For: Lists (products, orders, customers)
Structure:
• Loading state
• Error state
• Empty state
• Data rows
• Actions column
• Pagination
Key Features:
✅ Server-side pagination
✅ Filtering
✅ Sorting
✅ Responsive
Pattern 2: DASHBOARD
──────────────────────────────────────────────────────────────────
Use For: Overview pages with stats
Structure:
• Stats cards grid
• Recent activity list
• Quick actions
Key Features:
✅ Real-time stats
✅ Charts (optional)
✅ Refresh button
Pattern 3: FORM MODAL
──────────────────────────────────────────────────────────────────
Use For: Create/Edit operations
Structure:
• Modal overlay
• Form fields
• Validation
• Save/Cancel buttons
Key Features:
✅ Client-side validation
✅ Error messages
✅ Loading state
✅ Escape to close
Pattern 4: DETAIL PAGE
──────────────────────────────────────────────────────────────────
Use For: Single item view (product detail, order detail)
Structure:
• Header with actions
• Info cards
• Related data tabs
Key Features:
✅ Edit inline
✅ Related items
✅ Status badges
🔄 STATE MANAGEMENT
═════════════════════════════════════════════════════════════════
Alpine.js Reactive State:
Global State (init-alpine.js):
• dark mode
• current user
• vendor info
• menu state
Page State (e.g., products.js):
• items array
• loading boolean
• error string
• filters object
• pagination object
State Updates:
• Direct assignment: this.items = []
• Alpine auto-updates DOM
• No manual DOM manipulation
📡 API CLIENT
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/api-client.js
Usage:
const data = await apiClient.get('/endpoint');
await apiClient.post('/endpoint', { data });
await apiClient.put('/endpoint/{id}', { data });
await apiClient.delete('/endpoint/{id}');
Features:
✅ Automatic auth headers
✅ Error handling
✅ JSON parsing
✅ Retry logic
🐛 LOGGING
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/log-config.js
Usage:
logInfo('Operation completed', data);
logError('Operation failed', error);
logDebug('Debug info', context);
logWarn('Warning message');
Levels:
• INFO: General information
• ERROR: Errors and failures
• DEBUG: Detailed debugging
• WARN: Warnings
🎭 ICONS
═════════════════════════════════════════════════════════════════
Location: app/static/shared/js/icons.js
Usage:
<span x-html="$icon('home', 'w-5 h-5')"></span>
<span x-html="$icon('user', 'w-4 h-4 text-blue-500')"></span>
Available Icons:
• home, dashboard, settings
• user, users, user-group
• shopping-bag, shopping-cart
• cube, download, upload
• plus, minus, x
• pencil, trash, eye
• check, exclamation
• chevron-left, chevron-right
• spinner (for loading)
🚀 PERFORMANCE
═════════════════════════════════════════════════════════════════
Optimization Techniques:
1. Template Caching
• Base template cached by FastAPI
• Reduces rendering time
2. Lazy Loading
• Data loaded after page render
• Progressive content display
3. Debouncing
• Search inputs debounced
• Reduces API calls
4. Pagination
• Server-side pagination
• Load only needed data
5. CDN Assets
• Tailwind CSS from CDN
• Alpine.js from CDN
🧪 TESTING APPROACH
═════════════════════════════════════════════════════════════════
Unit Tests:
• Route handlers (Python)
• API endpoints (Python)
• Utility functions (JavaScript)
Integration Tests:
• Full page load
• Data fetching
• Form submission
• Authentication flow
Manual Testing:
• Visual regression
• Cross-browser
• Mobile devices
• Dark mode
🔒 SECURITY
═════════════════════════════════════════════════════════════════
Best Practices:
1. Authentication
✅ JWT tokens
✅ HttpOnly cookies option
✅ Token expiration
2. Authorization
✅ Route-level checks
✅ API-level validation
✅ Vendor-scoped data
3. Input Validation
✅ Client-side validation
✅ Server-side validation
✅ XSS prevention
4. CSRF Protection
✅ Token-based
✅ SameSite cookies
📊 PAGE-BY-PAGE BREAKDOWN
═════════════════════════════════════════════════════════════════
/vendor/{code}/dashboard
──────────────────────────────────────────────────────────────────
Purpose: Overview of vendor operations
Components:
• Stats cards (products, orders, revenue)
• Recent orders table
• Quick actions
Data Sources:
• GET /api/v1/vendors/{code}/stats
• GET /api/v1/vendors/{code}/orders?limit=5
/vendor/{code}/products
──────────────────────────────────────────────────────────────────
Purpose: Manage product catalog
Components:
• Product list table
• Search and filters
• Create/Edit modal
Data Sources:
• GET /api/v1/vendors/{code}/products
• POST /api/v1/vendors/{code}/products
• PUT /api/v1/vendors/{code}/products/{id}
/vendor/{code}/orders
──────────────────────────────────────────────────────────────────
Purpose: View and manage orders
Components:
• Orders table
• Status filters
• Order detail modal
Data Sources:
• GET /api/v1/vendors/{code}/orders
• PUT /api/v1/vendors/{code}/orders/{id}
/vendor/{code}/customers
──────────────────────────────────────────────────────────────────
Purpose: Customer management
Components:
• Customer list
• Search functionality
• Customer detail view
Data Sources:
• GET /api/v1/vendors/{code}/customers
/vendor/{code}/inventory
──────────────────────────────────────────────────────────────────
Purpose: Track stock levels
Components:
• Inventory table
• Stock adjustment modal
• Low stock alerts
Data Sources:
• GET /api/v1/vendors/{code}/inventory
• PUT /api/v1/vendors/{code}/inventory/{id}
/vendor/{code}/marketplace
──────────────────────────────────────────────────────────────────
Purpose: Import products from marketplace
Components:
• Import job list
• Product browser
• Import wizard
Data Sources:
• GET /api/v1/vendors/{code}/marketplace/jobs
• POST /api/v1/vendors/{code}/marketplace/import
/vendor/{code}/team
──────────────────────────────────────────────────────────────────
Purpose: Manage team members
Components:
• Team member list
• Role management
• Invitation form
Data Sources:
• GET /api/v1/vendors/{code}/team
• POST /api/v1/vendors/{code}/team/invite
/vendor/{code}/settings
──────────────────────────────────────────────────────────────────
Purpose: Configure vendor settings
Components:
• Settings tabs
• Form sections
• Save buttons
Data Sources:
• GET /api/v1/vendors/{code}/settings
• PUT /api/v1/vendors/{code}/settings
🎓 LEARNING PATH
═════════════════════════════════════════════════════════════════
For New Developers:
1. Understand Architecture (1 hour)
→ Read this document
→ Review file structure
→ Examine base template
2. Study Existing Page (2 hours)
→ Open dashboard.html
→ Open dashboard.js
→ Trace data flow
3. Create Simple Page (4 hours)
→ Copy templates
→ Modify for new feature
→ Test thoroughly
4. Add Complex Feature (1 day)
→ Forms with validation
→ Modal dialogs
→ API integration
5. Master Patterns (1 week)
→ All common patterns
→ Error handling
→ Performance optimization
🔄 DEPLOYMENT CHECKLIST
═════════════════════════════════════════════════════════════════
Before Deploying:
□ Build Tailwind CSS
□ Minify JavaScript
□ Test all routes
□ Verify authentication
□ Check mobile responsive
□ Test dark mode
□ Validate API endpoints
□ Review error handling
□ Check console for errors
□ Test in production mode
📚 REFERENCE LINKS
═════════════════════════════════════════════════════════════════
Documentation:
• Alpine.js: https://alpinejs.dev/
• Tailwind CSS: https://tailwindcss.com/
• Jinja2: https://jinja.palletsprojects.com/
• FastAPI: https://fastapi.tiangolo.com/
Internal Docs:
• Page Template Guide: FRONTEND_VENDOR_ALPINE_PAGE_TEMPLATE.md
• API Documentation: API_REFERENCE.md
• Database Schema: DATABASE_SCHEMA.md
══════════════════════════════════════════════════════════════════
VENDOR ADMIN ARCHITECTURE
Modern, Maintainable, and Developer-Friendly
══════════════════════════════════════════════════════════════════

837
docs/frontend/vendor/page-templates.md vendored Normal file
View File

@@ -0,0 +1,837 @@
# Vendor Admin Frontend - Alpine.js/Jinja2 Page Template Guide
## 📋 Overview
This guide provides complete templates for creating new vendor admin pages using the established Alpine.js + Jinja2 architecture. Follow these patterns to ensure consistency across the vendor portal.
---
## 🎯 Quick Reference
### File Structure for New Page
```
app/
├── templates/vendor/admin/
│ └── [page-name].html # Jinja2 template
├── static/vendor/js/
│ └── [page-name].js # Alpine.js component
└── api/v1/vendor/
└── pages.py # Route registration
```
### Checklist for New Page
- [ ] Create Jinja2 template extending base.html
- [ ] Create Alpine.js JavaScript component
- [ ] Register route in pages.py
- [ ] Add navigation link to sidebar.html
- [ ] Test authentication
- [ ] Test data loading
- [ ] Test responsive design
---
## 📄 Template Structure
### 1. Jinja2 Template
**File:** `app/templates/vendor/admin/[page-name].html`
```jinja2
{# app/templates/vendor/admin/[page-name].html #}
{% extends "vendor/base.html" %}
{# Page title for browser tab #}
{% block title %}[Page Name]{% endblock %}
{# Alpine.js component name #}
{% block alpine_data %}vendor[PageName](){% endblock %}
{# Page content #}
{% block content %}
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PAGE HEADER -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="flex items-center justify-between my-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
[Page Name]
</h2>
<!-- Action Buttons -->
<div class="flex items-center space-x-3">
<button
@click="refresh()"
:disabled="loading"
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 disabled:opacity-50"
>
<span x-show="!loading" x-html="$icon('refresh', 'w-4 h-4 mr-2')"></span>
<span x-show="loading" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
<span x-text="loading ? 'Loading...' : 'Refresh'"></span>
</button>
<button
@click="openCreateModal()"
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>
<span>Add New</span>
</button>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- LOADING STATE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<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 data...</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- ERROR STATE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<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</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- FILTERS & SEARCH -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div x-show="!loading" class="mb-6 bg-white rounded-lg shadow-xs dark:bg-gray-800 p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Search -->
<div>
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400">Search</span>
<input
x-model="filters.search"
@input.debounce.300ms="applyFilters()"
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input"
placeholder="Search..."
/>
</label>
</div>
<!-- Status Filter -->
<div>
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400">Status</span>
<select
x-model="filters.status"
@change="applyFilters()"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 form-select focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray"
>
<option value="">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</label>
</div>
<!-- Sort -->
<div>
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400">Sort By</span>
<select
x-model="filters.sortBy"
@change="applyFilters()"
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 form-select focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray"
>
<option value="created_at:desc">Newest First</option>
<option value="created_at:asc">Oldest First</option>
<option value="name:asc">Name (A-Z)</option>
<option value="name:desc">Name (Z-A)</option>
</select>
</label>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- DATA TABLE -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div x-show="!loading" 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">Date</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">
<!-- Empty State -->
<template x-if="items.length === 0">
<tr>
<td colspan="4" 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 mb-2 text-gray-300')"></span>
<p>No items found.</p>
</div>
</td>
</tr>
</template>
<!-- Data Rows -->
<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 transition-colors">
<td class="px-4 py-3">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold" x-text="item.name"></p>
<p class="text-xs text-gray-600 dark:text-gray-400"
x-text="item.description"></p>
</div>
</div>
</td>
<td class="px-4 py-3 text-xs">
<span class="px-2 py-1 font-semibold leading-tight rounded-full"
:class="item.status === 'active'
? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
: 'text-gray-700 bg-gray-100 dark:text-gray-100 dark:bg-gray-700'"
x-text="item.status"></span>
</td>
<td class="px-4 py-3 text-sm" x-text="formatDate(item.created_at)">
</td>
<td class="px-4 py-3">
<div class="flex items-center space-x-2 text-sm">
<button
@click="viewItem(item.id)"
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-gray-400 dark:hover:bg-gray-700"
title="View"
>
<span x-html="$icon('eye', 'w-5 h-5')"></span>
</button>
<button
@click="editItem(item.id)"
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-gray-400 dark:hover:bg-gray-700"
title="Edit"
>
<span x-html="$icon('pencil', 'w-5 h-5')"></span>
</button>
<button
@click="deleteItem(item.id)"
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-gray-400 dark:hover:bg-gray-700"
title="Delete"
>
<span x-html="$icon('trash', 'w-5 h-5')"></span>
</button>
</div>
</td>
</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 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800">
<span class="flex items-center col-span-3">
Showing <span class="mx-1 font-semibold" x-text="pagination.from"></span>-<span class="mx-1 font-semibold" x-text="pagination.to"></span> of <span class="mx-1 font-semibold" x-text="pagination.total"></span>
</span>
<span class="col-span-2"></span>
<span class="flex col-span-4 mt-2 sm:mt-auto sm:justify-end">
<nav aria-label="Table navigation">
<ul class="inline-flex items-center">
<li>
<button
@click="previousPage()"
:disabled="!pagination.hasPrevious"
class="px-3 py-1 rounded-md rounded-l-lg focus:outline-none focus:shadow-outline-purple"
:class="pagination.hasPrevious ? 'hover:bg-gray-100 dark:hover:bg-gray-700' : 'opacity-50 cursor-not-allowed'"
>
<span x-html="$icon('chevron-left', 'w-4 h-4')"></span>
</button>
</li>
<li>
<span class="px-3 py-1" x-text="`Page ${pagination.currentPage} of ${pagination.totalPages}`"></span>
</li>
<li>
<button
@click="nextPage()"
:disabled="!pagination.hasNext"
class="px-3 py-1 rounded-md rounded-r-lg focus:outline-none focus:shadow-outline-purple"
:class="pagination.hasNext ? 'hover:bg-gray-100 dark:hover:bg-gray-700' : 'opacity-50 cursor-not-allowed'"
>
<span x-html="$icon('chevron-right', 'w-4 h-4')"></span>
</button>
</li>
</ul>
</nav>
</span>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- MODALS (if needed) -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- Create/Edit Modal -->
<div x-show="showModal"
x-cloak
class="fixed inset-0 z-50 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
@click.self="closeModal()">
<div class="relative w-full max-w-lg p-6 mx-auto bg-white rounded-lg shadow-xl dark:bg-gray-800">
<!-- Modal Header -->
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200"
x-text="modalTitle"></h3>
<button @click="closeModal()"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<span x-html="$icon('x', 'w-6 h-6')"></span>
</button>
</div>
<!-- Modal Body -->
<form @submit.prevent="saveItem()">
<div class="space-y-4">
<!-- Form fields here -->
<div>
<label class="block text-sm">
<span class="text-gray-700 dark:text-gray-400">Name</span>
<input
x-model="formData.name"
type="text"
class="block w-full mt-1 text-sm dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:text-gray-300 dark:focus:shadow-outline-gray form-input"
required
/>
</label>
</div>
</div>
<!-- Modal Footer -->
<div class="flex justify-end mt-6 space-x-3">
<button
type="button"
@click="closeModal()"
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:bg-gray-100 focus:outline-none focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600"
>
Cancel
</button>
<button
type="submit"
:disabled="saving"
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 disabled:opacity-50"
>
<span x-show="!saving">Save</span>
<span x-show="saving">Saving...</span>
</button>
</div>
</form>
</div>
</div>
{% endblock %}
{# Page-specific JavaScript #}
{% block extra_scripts %}
<script src="{{ url_for('static', path='vendor/js/[page-name].js') }}"></script>
{% endblock %}
```
---
### 2. Alpine.js Component
**File:** `app/static/vendor/js/[page-name].js`
```javascript
// app/static/vendor/js/[page-name].js
/**
* [Page Name] page logic
* Handles data loading, filtering, CRUD operations
*/
function vendor[PageName]() {
return {
// ═══════════════════════════════════════════════════════════
// STATE
// ═══════════════════════════════════════════════════════════
loading: false,
error: '',
items: [],
// Filters
filters: {
search: '',
status: '',
sortBy: 'created_at:desc'
},
// Pagination
pagination: {
currentPage: 1,
perPage: 10,
total: 0,
totalPages: 0,
from: 0,
to: 0,
hasPrevious: false,
hasNext: false
},
// Modal state
showModal: false,
modalTitle: '',
modalMode: 'create', // 'create' or 'edit'
formData: {},
saving: false,
// ═══════════════════════════════════════════════════════════
// LIFECYCLE
// ═══════════════════════════════════════════════════════════
async init() {
logInfo('[PageName] page initializing...');
await this.loadData();
logInfo('[PageName] page initialized');
},
// ═══════════════════════════════════════════════════════════
// DATA LOADING
// ═══════════════════════════════════════════════════════════
async loadData() {
this.loading = true;
this.error = '';
try {
// Build query params
const params = new URLSearchParams({
page: this.pagination.currentPage,
per_page: this.pagination.perPage,
...this.filters
});
// API call
const response = await apiClient.get(
`/api/v1/vendors/${this.vendorCode}/[endpoint]?${params}`
);
// Update state
this.items = response.items || [];
this.updatePagination(response);
logInfo('[PageName] data loaded', {
items: this.items.length,
total: this.pagination.total
});
} catch (error) {
logError('Failed to load [page] data', error);
this.error = error.message || 'Failed to load data';
} finally {
this.loading = false;
}
},
async refresh() {
await this.loadData();
},
updatePagination(response) {
this.pagination = {
currentPage: response.page || 1,
perPage: response.per_page || 10,
total: response.total || 0,
totalPages: response.pages || 0,
from: ((response.page - 1) * response.per_page) + 1,
to: Math.min(response.page * response.per_page, response.total),
hasPrevious: response.page > 1,
hasNext: response.page < response.pages
};
},
// ═══════════════════════════════════════════════════════════
// FILTERING & PAGINATION
// ═══════════════════════════════════════════════════════════
async applyFilters() {
this.pagination.currentPage = 1; // Reset to first page
await this.loadData();
},
async previousPage() {
if (this.pagination.hasPrevious) {
this.pagination.currentPage--;
await this.loadData();
}
},
async nextPage() {
if (this.pagination.hasNext) {
this.pagination.currentPage++;
await this.loadData();
}
},
// ═══════════════════════════════════════════════════════════
// CRUD OPERATIONS
// ═══════════════════════════════════════════════════════════
openCreateModal() {
this.modalMode = 'create';
this.modalTitle = 'Create New Item';
this.formData = {
name: '',
description: '',
status: 'active'
};
this.showModal = true;
},
async viewItem(id) {
// Navigate to detail page or open view modal
window.location.href = `/vendor/${this.vendorCode}/[endpoint]/${id}`;
},
async editItem(id) {
try {
// Load item data
const item = await apiClient.get(
`/api/v1/vendors/${this.vendorCode}/[endpoint]/${id}`
);
this.modalMode = 'edit';
this.modalTitle = 'Edit Item';
this.formData = { ...item };
this.showModal = true;
} catch (error) {
logError('Failed to load item', error);
alert('Failed to load item details');
}
},
async saveItem() {
this.saving = true;
try {
if (this.modalMode === 'create') {
await apiClient.post(
`/api/v1/vendors/${this.vendorCode}/[endpoint]`,
this.formData
);
logInfo('Item created successfully');
} else {
await apiClient.put(
`/api/v1/vendors/${this.vendorCode}/[endpoint]/${this.formData.id}`,
this.formData
);
logInfo('Item updated successfully');
}
this.closeModal();
await this.loadData();
} catch (error) {
logError('Failed to save item', error);
alert(error.message || 'Failed to save item');
} finally {
this.saving = false;
}
},
async deleteItem(id) {
if (!confirm('Are you sure you want to delete this item?')) {
return;
}
try {
await apiClient.delete(
`/api/v1/vendors/${this.vendorCode}/[endpoint]/${id}`
);
logInfo('Item deleted successfully');
await this.loadData();
} catch (error) {
logError('Failed to delete item', error);
alert(error.message || 'Failed to delete item');
}
},
closeModal() {
this.showModal = false;
this.formData = {};
},
// ═══════════════════════════════════════════════════════════
// UTILITIES
// ═══════════════════════════════════════════════════════════
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
},
formatCurrency(amount) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount || 0);
}
};
}
// Make available globally
window.vendor[PageName] = vendor[PageName];
```
---
### 3. Route Registration
**File:** `app/api/v1/vendor/pages.py`
```python
@router.get("/vendor/{vendor_code}/[page-name]", response_class=HTMLResponse, include_in_schema=False)
async def vendor_[page_name]_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_vendor_user)
):
"""
Render [page name] page.
JavaScript loads data via API.
"""
return templates.TemplateResponse(
"vendor/admin/[page-name].html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
}
)
```
---
### 4. Sidebar Navigation
**File:** `app/templates/vendor/partials/sidebar.html`
```jinja2
<li class="relative px-6 py-3">
<span x-show="currentPage === '[page-name]'"
class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === '[page-name]' ? 'text-gray-800 dark:text-gray-100' : ''"
:href="`/vendor/${vendorCode}/[page-name]`">
<span x-html="$icon('[icon-name]', 'w-5 h-5')"></span>
<span class="ml-4">[Page Display Name]</span>
</a>
</li>
```
---
## 🎨 Common Patterns
### Pattern 1: Simple Data List
Use for: Product list, order list, customer list
```javascript
async init() {
await this.loadData();
}
async loadData() {
this.loading = true;
try {
const response = await apiClient.get(`/api/v1/vendors/${this.vendorCode}/items`);
this.items = response.items || [];
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
```
### Pattern 2: Dashboard with Stats
Use for: Dashboard, analytics pages
```javascript
async init() {
await Promise.all([
this.loadStats(),
this.loadRecentActivity()
]);
}
async loadStats() {
const stats = await apiClient.get(`/api/v1/vendors/${this.vendorCode}/stats`);
this.stats = stats;
}
```
### Pattern 3: Detail Page
Use for: Product detail, order detail
```javascript
async init() {
await this.loadItem();
}
async loadItem() {
const id = this.getItemIdFromUrl();
this.item = await apiClient.get(`/api/v1/vendors/${this.vendorCode}/items/${id}`);
}
```
### Pattern 4: Form with Validation
Use for: Settings, profile edit
```javascript
formData: {
name: '',
email: ''
},
errors: {},
validateForm() {
this.errors = {};
if (!this.formData.name) this.errors.name = 'Name is required';
if (!this.formData.email) this.errors.email = 'Email is required';
return Object.keys(this.errors).length === 0;
},
async saveForm() {
if (!this.validateForm()) return;
await apiClient.put(`/api/v1/vendors/${this.vendorCode}/settings`, this.formData);
}
```
---
## 🔧 Best Practices
### 1. Error Handling
```javascript
try {
await apiClient.get('/endpoint');
} catch (error) {
logError('Operation failed', error);
this.error = error.message || 'An error occurred';
// Don't throw - let UI handle gracefully
}
```
### 2. Loading States
```javascript
// Always set loading at start and end
this.loading = true;
try {
// ... operations
} finally {
this.loading = false; // Always executes
}
```
### 3. Data Refresh
```javascript
async refresh() {
// Clear error before refresh
this.error = '';
await this.loadData();
}
```
### 4. Modal Management
```javascript
openModal() {
this.showModal = true;
// Reset form
this.formData = {};
this.errors = {};
}
closeModal() {
this.showModal = false;
// Clean up
this.formData = {};
}
```
### 5. Debouncing
```html
<!-- Debounce search input -->
<input
x-model="filters.search"
@input.debounce.300ms="applyFilters()"
/>
```
---
## 📱 Responsive Design Checklist
- [ ] Table scrolls horizontally on mobile
- [ ] Modal is scrollable on small screens
- [ ] Filters stack vertically on mobile
- [ ] Action buttons adapt to screen size
- [ ] Text truncates appropriately
- [ ] Icons remain visible
---
## ✅ Testing Checklist
- [ ] Page loads without errors
- [ ] Data loads correctly
- [ ] Loading state displays
- [ ] Error state handles failures
- [ ] Empty state shows when no data
- [ ] Filters work correctly
- [ ] Pagination works
- [ ] Create operation works
- [ ] Edit operation works
- [ ] Delete operation works
- [ ] Modal opens/closes
- [ ] Form validation works
- [ ] Dark mode works
- [ ] Mobile responsive
---
## 🚀 Quick Start Commands
```bash
# Create new page files
touch app/templates/vendor/admin/products.html
touch app/static/vendor/js/products.js
# Copy templates
cp template.html app/templates/vendor/admin/products.html
cp template.js app/static/vendor/js/products.js
# Update files with your page name
# Register route in pages.py
# Add sidebar link
# Test!
```
---
## 📚 Additional Resources
- **Icons**: Use `$icon('icon-name', 'classes')` helper
- **API Client**: Automatically handles auth tokens
- **Logging**: Use logInfo, logError, logDebug
- **Date Formatting**: Use formatDate() helper
- **Currency**: Use formatCurrency() helper
---
This template provides a complete, production-ready pattern for building vendor admin pages with consistent structure, error handling, and user experience.