revamping documentation
This commit is contained in:
828
docs/frontend/admin/architecture.md
Normal file
828
docs/frontend/admin/architecture.md
Normal 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
|
||||
══════════════════════════════════════════════════════════════════
|
||||
1310
docs/frontend/admin/page-templates.md
Normal file
1310
docs/frontend/admin/page-templates.md
Normal file
File diff suppressed because it is too large
Load Diff
501
docs/frontend/overview.md
Normal file
501
docs/frontend/overview.md
Normal 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.
|
||||
410
docs/frontend/shared/logging.md
Normal file
410
docs/frontend/shared/logging.md
Normal 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! 🎉
|
||||
201
docs/frontend/shared/pagination-quick-start.md
Normal file
201
docs/frontend/shared/pagination-quick-start.md
Normal 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! 📖✨
|
||||
══════════════════════════════════════════════════════════════
|
||||
297
docs/frontend/shared/pagination.md
Normal file
297
docs/frontend/shared/pagination.md
Normal 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! 📖
|
||||
444
docs/frontend/shared/sidebar.md
Normal file
444
docs/frontend/shared/sidebar.md
Normal 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! 🚀
|
||||
228
docs/frontend/shared/ui-components-quick-reference.md
Normal file
228
docs/frontend/shared/ui-components-quick-reference.md
Normal 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!
|
||||
305
docs/frontend/shared/ui-components.md
Normal file
305
docs/frontend/shared/ui-components.md
Normal 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! 🎨
|
||||
808
docs/frontend/shop/architecture.md
Normal file
808
docs/frontend/shop/architecture.md
Normal 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
|
||||
══════════════════════════════════════════════════════════════════
|
||||
972
docs/frontend/shop/page-templates.md
Normal file
972
docs/frontend/shop/page-templates.md
Normal 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
695
docs/frontend/vendor/architecture.md
vendored
Normal 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
837
docs/frontend/vendor/page-templates.md
vendored
Normal 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.
|
||||
Reference in New Issue
Block a user