diff --git a/app/templates/vendor/dashboard.html b/app/templates/vendor/admin/dashboard.html similarity index 100% rename from app/templates/vendor/dashboard.html rename to app/templates/vendor/admin/dashboard.html diff --git a/app/templates/vendor/base.html b/app/templates/vendor/base.html new file mode 100644 index 00000000..566549bd --- /dev/null +++ b/app/templates/vendor/base.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/templates/vendor/partials/header.html b/app/templates/vendor/partials/header.html new file mode 100644 index 00000000..566549bd --- /dev/null +++ b/app/templates/vendor/partials/header.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/templates/vendor/partials/notifications.html b/app/templates/vendor/partials/notifications.html new file mode 100644 index 00000000..566549bd --- /dev/null +++ b/app/templates/vendor/partials/notifications.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/templates/vendor/partials/sidebar.html b/app/templates/vendor/partials/sidebar.html new file mode 100644 index 00000000..566549bd --- /dev/null +++ b/app/templates/vendor/partials/sidebar.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/templates/vendor/partials/vendor_info.html b/app/templates/vendor/partials/vendor_info.html new file mode 100644 index 00000000..566549bd --- /dev/null +++ b/app/templates/vendor/partials/vendor_info.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md b/docs/__REVAMPING/FRONTEND/FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md similarity index 100% rename from docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md rename to docs/__REVAMPING/FRONTEND/FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md diff --git a/docs/__REVAMPING/FRONTEND/FRONTEND_ARCHITECTURE_OVERVIEW.txt b/docs/__REVAMPING/FRONTEND/FRONTEND_ADMIN_ARCHITECTURE_OVERVIEW.txt similarity index 100% rename from docs/__REVAMPING/FRONTEND/FRONTEND_ARCHITECTURE_OVERVIEW.txt rename to docs/__REVAMPING/FRONTEND/FRONTEND_ADMIN_ARCHITECTURE_OVERVIEW.txt diff --git a/docs/__REVAMPING/FRONTEND/frontend-structure.txt b/docs/__REVAMPING/FRONTEND/frontend-structure.txt index c05a520a..2f592f9d 100644 --- a/docs/__REVAMPING/FRONTEND/frontend-structure.txt +++ b/docs/__REVAMPING/FRONTEND/frontend-structure.txt @@ -89,40 +89,5 @@ E:\FASTAPI-MULTITENANT-ECOMMERCE\STATIC | api-client.js | icons.js | utils.js -| -+---shop -| | cart.html -| | checkout.html -| | home.html -| | product.html -| | products.html -| | search.html -| | -| \---account -| addresses.html -| login.html -| orders.html -| profile.html -| register.html -| -\---vendor - | dashboard.html - | login.html - | - \---admin - | customers.html - | inventory.html - | media.html - | notifications.html - | orders.html - | payments.html - | products.html - | settings.html - | teams.html - | - \---marketplace - browse.html - config.html - imports.html - selected.html + diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/FRONTEND_ARCHITECTURE_OVERVIEW.txt b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/FRONTEND_ARCHITECTURE_OVERVIEW.txt new file mode 100644 index 00000000..3dd6a2a9 --- /dev/null +++ b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/FRONTEND_ARCHITECTURE_OVERVIEW.txt @@ -0,0 +1,1225 @@ +╔══════════════════════════════════════════════════════════════════╗ +║ LETZSHOP FRONTEND ARCHITECTURE ║ +║ Complete Multi-Tenant E-Commerce Platform ║ +║ Architecture Overview & Design Patterns ║ +╚══════════════════════════════════════════════════════════════════╝ + +📦 WHAT IS THIS DOCUMENT? +═════════════════════════════════════════════════════════════════ + +This document provides a comprehensive overview of the LetzShop 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 +═════════════════════════════════════════════════════════════════ + +LetzShop 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 +═════════════════════════════════════════════════════════════════ + +Understanding the complete 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 + + +🎭 DESIGN PATTERNS +═════════════════════════════════════════════════════════════════ + +The platform uses consistent design patterns across all frontends: + + +╔═══════════════════════════════════════════════════════════════╗ +║ 1. BASE LAYOUT INHERITANCE PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Share common UI state across all pages +Location: static/{frontend}/js/init-alpine.js + +Pattern: +──────────────────────────────────────────────────────────────── +// init-alpine.js provides base state +function data() { + return { + // Theme + dark: localStorage.getItem('theme') === 'dark', + toggleTheme() { /* ... */ }, + + // Side menu + isSideMenuOpen: false, + toggleSideMenu() { /* ... */ }, + + // Profile menu + isProfileMenuOpen: false, + toggleProfileMenu() { /* ... */ }, + + // Page identifier + currentPage: '' + }; +} + +// Every page inherits this via spread operator +function adminDashboard() { + return { + ...data(), // ← Inherits ALL base functionality + currentPage: 'dashboard', // ← Override identifier + + // Page-specific state + stats: [], + loading: false + }; +} + +Benefits: + ✅ Dark mode works automatically on all pages + ✅ Menu states consistent across navigation + ✅ No duplicate code + ✅ Easy to add new base functionality + ✅ Sidebar highlighting works automatically + + +╔═══════════════════════════════════════════════════════════════╗ +║ 2. INITIALIZATION GUARD PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Prevent duplicate initialization and API calls +Problem: Alpine.js can sometimes initialize components twice + +Pattern: +──────────────────────────────────────────────────────────────── +async init() { + // Check if already initialized + if (window._dashboardInitialized) { + log.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 + +Benefits: + ✅ Prevents duplicate API calls + ✅ Prevents duplicate event listeners + ✅ Improves performance + ✅ Avoids state conflicts + ✅ Console warnings help debugging + + +╔═══════════════════════════════════════════════════════════════╗ +║ 3. CENTRALIZED LOGGING PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Consistent, configurable logging across all frontends +Location: static/shared/js/log-config.js + +Old Way (❌ BAD): +──────────────────────────────────────────────────────────────── +// Every file had 15+ lines of duplicate code +const DASHBOARD_LOG_LEVEL = 3; +const dashLog = { + error: (...args) => DASHBOARD_LOG_LEVEL >= 1 && + console.error('❌ [DASHBOARD ERROR]', ...args), + warn: (...args) => DASHBOARD_LOG_LEVEL >= 2 && + console.warn('⚠️ [DASHBOARD WARN]', ...args), + info: (...args) => DASHBOARD_LOG_LEVEL >= 3 && + console.info('ℹ️ [DASHBOARD INFO]', ...args), + debug: (...args) => DASHBOARD_LOG_LEVEL >= 4 && + console.log('🔍 [DASHBOARD DEBUG]', ...args) +}; + +New Way (✅ GOOD): +──────────────────────────────────────────────────────────────── +// Just ONE line per file! +const dashLog = window.LogConfig.loggers.dashboard; + +// Use it +dashLog.info('Dashboard loading...'); +dashLog.error('Failed to load', error); +dashLog.debug('Stats data:', statsData); + +Pre-configured Loggers: +──────────────────────────────────────────────────────────────── +Admin Frontend: + 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 + +Vendor Frontend: + window.LogConfig.loggers.dashboard + window.LogConfig.loggers.products + window.LogConfig.loggers.inventory + window.LogConfig.loggers.orders + window.LogConfig.loggers.theme + window.LogConfig.loggers.settings + window.LogConfig.loggers.analytics + +Shop Frontend: + window.LogConfig.loggers.catalog + window.LogConfig.loggers.product + window.LogConfig.loggers.search + window.LogConfig.loggers.cart + window.LogConfig.loggers.checkout + window.LogConfig.loggers.account + +Advanced Features: +──────────────────────────────────────────────────────────────── +// Grouped logging +log.group('Loading Theme Data'); +log.info('Fetching vendor...'); +log.info('Fetching theme...'); +log.groupEnd(); + +// API call logging +window.LogConfig.logApiCall('GET', url, data, 'response'); + +// Performance logging +window.LogConfig.logPerformance('Load Stats', duration); + +// Error logging with context +window.LogConfig.logError(error, 'Save Theme'); + +// Table logging +log.table([ + { id: 1, name: 'Vendor A', status: 'active' }, + { id: 2, name: 'Vendor B', status: 'inactive' } +]); + +Environment-Aware: +──────────────────────────────────────────────────────────────── +• Development: Full logging (level 4) +• Production: Errors only (level 1) +• Automatically detects environment +• Can override with localStorage + +Benefits: + ✅ 1 line instead of 15+ lines per file + ✅ Consistent format across all frontends + ✅ Environment-aware (dev vs prod) + ✅ Frontend-aware (admin/vendor/shop) + ✅ Advanced features (groups, perf, API) + ✅ Easy to update globally + ✅ Reduces code duplication by 90% + + +╔═══════════════════════════════════════════════════════════════╗ +║ 4. API CLIENT PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Consistent API communication with automatic error handling +Location: static/shared/js/api-client.js + +CRITICAL: Always use lowercase 'apiClient' + +Usage: +──────────────────────────────────────────────────────────────── +// ✅ CORRECT +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'); + +// ❌ WRONG - Capital letters +await ApiClient.get('/url'); +await API_CLIENT.get('/url'); + +Features: +──────────────────────────────────────────────────────────────── +✅ Automatic auth token injection (from localStorage) +✅ Automatic JSON parsing +✅ Automatic error handling +✅ Request/response logging integration +✅ Timeout handling +✅ Retry logic (optional) + +Error Handling: +──────────────────────────────────────────────────────────────── +try { + const data = await apiClient.get('/endpoint'); + // Success +} catch (error) { + // Error is already logged by apiClient + // Error is formatted with: + // - error.message (user-friendly) + // - error.details (technical details) + // - error.status_code (HTTP status) + + this.error = error.message; + Utils.showToast('Failed to load', 'error'); +} + +Benefits: + ✅ Consistent error handling + ✅ Automatic auth headers + ✅ Integrated logging + ✅ Reduced boilerplate + ✅ Easy to mock for testing + + +╔═══════════════════════════════════════════════════════════════╗ +║ 5. EXCEPTION HANDLING PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Structured, consistent error handling across backend +Location: app/exceptions/ + +Exception Hierarchy: +──────────────────────────────────────────────────────────────── +LetzShopException (base) +├── ValidationException (422) +├── AuthenticationException (401) +├── AuthorizationException (403) +├── ResourceNotFoundException (404) +├── ConflictException (409) +├── BusinessLogicException (400) +├── ExternalServiceException (502) +├── RateLimitException (429) +└── ServiceUnavailableException (503) + +Domain-Specific Exceptions: +──────────────────────────────────────────────────────────────── +app/exceptions/ +├── base.py ← Base exceptions +├── admin.py ← Admin operations +│ ├── UserNotFoundException +│ ├── UserStatusChangeException +│ ├── ShopVerificationException +│ ├── CannotModifyAdminException +│ └── InvalidAdminActionException +├── shop.py ← Shop operations +│ ├── ShopNotFoundException +│ ├── ShopNotActiveException +│ ├── UnauthorizedShopAccessException +│ └── MaxShopsReachedException +├── product.py ← Product operations +│ ├── ProductNotFoundException +│ ├── InvalidProductDataException +│ └── InvalidGTINException +├── stock.py ← Stock operations +│ ├── StockNotFoundException +│ ├── InsufficientStockException +│ └── NegativeStockException +└── marketplace.py ← Marketplace imports + ├── ImportJobNotFoundException + ├── InvalidImportDataException + └── MarketplaceConnectionException + +Exception Structure: +──────────────────────────────────────────────────────────────── +class ProductNotFoundException(ResourceNotFoundException): + def __init__(self, product_id: str): + super().__init__( + resource_type="Product", + identifier=product_id, + message=f"Product with ID '{product_id}' not found", + error_code="PRODUCT_NOT_FOUND", + ) + +# Automatic JSON response: +{ + "error_code": "PRODUCT_NOT_FOUND", + "message": "Product with ID '123' not found", + "status_code": 404, + "details": { + "resource_type": "Product", + "identifier": "123" + } +} + +Global Exception Handler: +──────────────────────────────────────────────────────────────── +Location: app/exceptions/handler.py + +Handles: + • LetzShopException → Custom JSON response + • HTTPException → Formatted JSON response + • RequestValidationError → Cleaned validation errors + • Exception → Generic 500 error + +Automatic Logging: + • All exceptions logged with context + • Structured logging data + • Exception type tracking + +Benefits: + ✅ Consistent error responses + ✅ Automatic logging + ✅ Client-friendly error messages + ✅ Technical details preserved + ✅ Type-safe error handling + ✅ Easy to test + + +╔═══════════════════════════════════════════════════════════════╗ +║ 6. UTILITY FUNCTIONS PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Shared utility functions across all frontends +Location: static/shared/js/utils.js + +Available Functions: +──────────────────────────────────────────────────────────────── +// Toast notifications +Utils.showToast(message, type, duration) +// Types: 'success', 'error', 'warning', 'info' + +// Date formatting +Utils.formatDate(dateString) +// Returns: "Jan 15, 2024" + +Utils.formatDateTime(dateString) +// Returns: "Jan 15, 2024 3:45 PM" + +Utils.formatRelativeTime(dateString) +// Returns: "2 hours ago", "3 days ago" + +// String utilities +Utils.truncate(text, length) +Utils.capitalize(text) +Utils.slugify(text) + +// Number formatting +Utils.formatCurrency(amount, currency) +Utils.formatNumber(number) +Utils.formatPercentage(value) + +// Validation +Utils.isValidEmail(email) +Utils.isValidUrl(url) + +// Debounce +Utils.debounce(func, wait) + +Usage Example: +──────────────────────────────────────────────────────────────── +// Show success toast +Utils.showToast('Saved successfully', 'success'); + +// Format date for display +const formattedDate = Utils.formatDate(item.created_at); + +// Format currency +const price = Utils.formatCurrency(29.99, 'USD'); + +// Debounce search +const debouncedSearch = Utils.debounce( + () => this.performSearch(), + 300 +); + +Benefits: + ✅ No code duplication + ✅ Consistent formatting + ✅ Easy to extend + ✅ Tested and reliable + + +╔═══════════════════════════════════════════════════════════════╗ +║ 7. ICON REGISTRY PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Centralized SVG icon management +Location: static/shared/js/icons.js + +Usage: +──────────────────────────────────────────────────────────────── + + + + + + + +Available Icons: +──────────────────────────────────────────────────────────────── +Navigation: + home, dashboard, settings, menu + +Users: + user, users, user-group, user-circle + +Commerce: + shopping-cart, shopping-bag, credit-card + +Content: + document, folder, archive, download, upload + +Actions: + plus, minus, x, check, trash, pencil, eye + +Arrows: + chevron-left, chevron-right, chevron-up, chevron-down + arrow-left, arrow-right + +Status: + exclamation, exclamation-circle, check-circle, x-circle + spinner (animated) + +Other: + search, filter, bell, star, heart, cube + +Benefits: + ✅ Consistent icon style (Heroicons) + ✅ Easy to use + ✅ Customizable size and color + ✅ No external dependencies + ✅ Reduced duplication + + +╔═══════════════════════════════════════════════════════════════╗ +║ 8. DARK MODE PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Consistent dark mode across all frontends +Implementation: Tailwind CSS dark mode + Alpine.js state + +Pattern: +──────────────────────────────────────────────────────────────── +// Base layout provides dark mode state +function data() { + return { + dark: localStorage.getItem('theme') === 'dark', + + toggleTheme() { + this.dark = !this.dark; + localStorage.setItem('theme', + this.dark ? 'dark' : 'light' + ); + } + }; +} + +// HTML class binding + + +// Tailwind dark mode classes +
+

Content

+
+ +// Toggle button + + +Shop Frontend Special Case: +──────────────────────────────────────────────────────────────── +• Vendor colors adapt to dark mode +• CSS variables for both modes +• Maintains brand identity in dark mode + +Benefits: + ✅ Automatic on all pages + ✅ Persisted preference + ✅ Smooth transitions + ✅ Vendor branding preserved (shop) + + +╔═══════════════════════════════════════════════════════════════╗ +║ 9. MULTI-THEME PATTERN (Shop Frontend Only) ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Vendor-specific branding for shop frontend +Location: Shop frontend only + +Components: +──────────────────────────────────────────────────────────────── +1. Database: vendor_themes table +2. Model: VendorTheme (colors, fonts, logos, layout) +3. Middleware: theme_context_middleware +4. Template: CSS variables injection +5. JavaScript: Theme-aware utilities + +Flow: +──────────────────────────────────────────────────────────────── +1. Request → customdomain1.com +2. Vendor Middleware → Identifies Vendor 1 +3. Theme Middleware → Loads Vendor 1's theme +4. Template → Injects CSS variables +5. Render → Vendor-branded shop + +CSS Variables: +──────────────────────────────────────────────────────────────── +:root { + --color-primary: #6366f1; /* Vendor's color */ + --color-secondary: #8b5cf6; + --color-accent: #ec4899; + --font-heading: Inter, sans-serif; + --font-body: Inter, sans-serif; +} + +Usage: +──────────────────────────────────────────────────────────────── + + +

+ Welcome +

+ +Benefits: + ✅ Each vendor has unique branding + ✅ No code changes per vendor + ✅ Database-driven configuration + ✅ Custom CSS support + ✅ Theme presets available + + +╔═══════════════════════════════════════════════════════════════╗ +║ 10. PAGINATION PATTERN ║ +╚═══════════════════════════════════════════════════════════════╝ + +Purpose: Consistent pagination across all list pages + +State: +──────────────────────────────────────────────────────────────── +pagination: { + currentPage: 1, + totalPages: 1, + perPage: 10, + total: 0, + from: 0, + to: 0 +} + +Methods: +──────────────────────────────────────────────────────────────── +async goToPage(page) { + if (page < 1 || page > this.pagination.totalPages) return; + this.pagination.currentPage = page; + await this.loadData(); +} + +async previousPage() { + await this.goToPage(this.pagination.currentPage - 1); +} + +async nextPage() { + await this.goToPage(this.pagination.currentPage + 1); +} + +get paginationRange() { + const current = this.pagination.currentPage; + const total = this.pagination.totalPages; + const range = []; + + let start = Math.max(1, current - 2); + let end = Math.min(total, start + 4); + + if (end - start < 4) { + start = Math.max(1, end - 4); + } + + for (let i = start; i <= end; i++) { + range.push(i); + } + + return range; +} + +Benefits: + ✅ Consistent UX + ✅ Smart page range calculation + ✅ Easy to implement + + +🔐 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) + + +📚 DETAILED ARCHITECTURE DOCUMENTS +═════════════════════════════════════════════════════════════════ + +This overview introduces the architecture. For complete details: + +┌─────────────────────────────────────────────────────────────────┐ +│ 1. FRONTEND_ADMIN_ARCHITECTURE_OVERVIEW.txt │ +├─────────────────────────────────────────────────────────────────┤ +│ Comprehensive guide to admin frontend: │ +│ • Complete file structure │ +│ • Layer-by-layer architecture │ +│ • All admin pages detailed │ +│ • Authentication and authorization │ +│ • Performance optimization │ +│ • Learning path for new developers │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 2. FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md │ +├─────────────────────────────────────────────────────────────────┤ +│ Complete templates for creating admin pages: │ +│ • Full Jinja2 template boilerplate │ +│ • Complete Alpine.js component structure │ +│ • 4 common page patterns │ +│ • Best practices with examples │ +│ • Testing checklist │ +│ • Quick start guide │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 3. FRONTEND_VENDOR_ARCHITECTURE_OVERVIEW.txt │ +├─────────────────────────────────────────────────────────────────┤ +│ Similar to admin but vendor-specific: │ +│ • Vendor context and scoping │ +│ • Shop management features │ +│ • Marketplace integration │ +│ • Team management │ +│ • Vendor-specific pages │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 4. FRONTEND_VENDOR_ALPINE_PAGE_TEMPLATE.md │ +├─────────────────────────────────────────────────────────────────┤ +│ Templates for vendor pages: │ +│ • Similar to admin templates │ +│ • Vendor context integration │ +│ • Scoped data patterns │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 5. FRONTEND_SHOP_ARCHITECTURE_OVERVIEW.txt │ +├─────────────────────────────────────────────────────────────────┤ +│ Customer-facing shop frontend: │ +│ • Multi-theme system │ +│ • Vendor branding │ +│ • Cart management │ +│ • Search and filters │ +│ • E-commerce patterns │ +│ • SEO optimization │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 6. FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md │ +├─────────────────────────────────────────────────────────────────┤ +│ Templates for shop pages: │ +│ • Theme-aware components │ +│ • Cart integration patterns │ +│ • Product display patterns │ +│ • Checkout flow patterns │ +└─────────────────────────────────────────────────────────────────┘ + + +🎓 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 + + +══════════════════════════════════════════════════════════════════ + LETZSHOP FRONTEND ARCHITECTURE + Three Frontends, One Cohesive System, Endless Scale +══════════════════════════════════════════════════════════════════ + +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. Copy templates and start building! + +Questions? Check the detailed architecture docs or review existing +implementations in the codebase. + +Happy Coding! 🚀 diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/15.web-architecture-revamping.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/15.web-architecture-revamping.md similarity index 100% rename from docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/15.web-architecture-revamping.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/15.web-architecture-revamping.md diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-2.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/16.jinja2_migration_progress-2.md similarity index 100% rename from docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-2.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/16.jinja2_migration_progress-2.md diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-3.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/16.jinja2_migration_progress-3.md similarity index 100% rename from docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-3.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/16.jinja2_migration_progress-3.md diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/16.jinja2_migration_progress.md similarity index 100% rename from docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/16.jinja2_migration_progress.md diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md new file mode 100644 index 00000000..1c43c413 --- /dev/null +++ b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/FRONTEND_ADMIN_ALPINE_PAGE_TEMPLATE.md @@ -0,0 +1,1310 @@ +# Admin Frontend - Alpine.js/Jinja2 Page Template Guide + +## 📋 Overview + +This guide provides complete templates for creating new admin pages using the established Alpine.js + Jinja2 architecture. Follow these patterns to ensure consistency, proper initialization, and optimal performance across the admin portal. + +--- + +## 🎯 Quick Reference + +### File Structure for New Page +``` +app/ +├── templates/admin/ +│ └── [page-name].html # Jinja2 template +├── static/admin/js/ +│ └── [page-name].js # Alpine.js component +└── api/v1/admin/ + └── pages.py # Route registration +``` + +### Checklist for New Page +- [ ] Create Jinja2 template extending admin/base.html +- [ ] Create Alpine.js JavaScript component +- [ ] Use centralized logger (one line!) +- [ ] Add ...data() for base inheritance +- [ ] Set currentPage identifier +- [ ] Add initialization guard +- [ ] Use lowercase apiClient +- [ ] Register route in pages.py +- [ ] Add sidebar navigation link +- [ ] Test authentication +- [ ] Test dark mode +- [ ] Verify no duplicate initialization + +--- + +## 📄 Template Structure + +### 1. Jinja2 Template + +**File:** `app/templates/admin/[page-name].html` + +```jinja2 +{# app/templates/admin/[page-name].html #} +{% extends "admin/base.html" %} + +{# Page title for browser tab #} +{% block title %}[Page Name]{% endblock %} + +{# ✅ CRITICAL: Alpine.js component name #} +{% block alpine_data %}admin[PageName](){% endblock %} + +{# Page content #} +{% block content %} + + + +
+

+ [Page Name] +

+ + +
+ + + +
+
+ + + + +
+ +

Loading data...

+
+ + + + +
+ +
+

Error

+

+
+
+ + + + +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+ + + + +
+
+ + + + + + + + + + + + + + + + +
NameStatusDateActions
+
+ + +
+ + Showing + + - + + of + + + + + + + +
+
+ + + + +
+ +
+ + +
+

+ +
+ + +
+
+ + + + + + + + + + + +
+ + +
+
+
+
+
+ +{% endblock %} + +{# Page-specific JavaScript #} +{% block extra_scripts %} + +{% endblock %} +``` + +--- + +### 2. Alpine.js Component + +**File:** `app/static/admin/js/[page-name].js` + +```javascript +// static/admin/js/[page-name].js +/** + * [Page Name] Component + * Handles [describe functionality] + */ + +// ✅ CRITICAL: Use centralized logger (ONE LINE!) +const pageLog = window.LogConfig.loggers.[pageName]; +// OR create custom logger if not pre-configured: +// const pageLog = window.LogConfig.createLogger('[PAGE-NAME]', window.LogConfig.logLevel); + +/** + * Main Alpine.js component for [page name] + */ +function admin[PageName]() { + return { + // ───────────────────────────────────────────────────── + // ✅ CRITICAL: INHERIT BASE LAYOUT + // ───────────────────────────────────────────────────── + ...data(), + + // ✅ CRITICAL: SET PAGE IDENTIFIER + currentPage: '[page-name]', + + // ───────────────────────────────────────────────────── + // STATE + // ───────────────────────────────────────────────────── + + // Loading states + loading: false, + saving: false, + + // Error handling + error: null, + errors: {}, + + // Data + items: [], + currentItem: null, + + // Pagination + pagination: { + currentPage: 1, + totalPages: 1, + perPage: 10, + total: 0, + from: 0, + to: 0 + }, + + // Filters + filters: { + search: '', + status: '', + sortBy: 'created_at:desc' + }, + + // Modal + showModal: false, + modalMode: 'create', // 'create' or 'edit' + formData: { + name: '', + description: '', + is_active: true + }, + + // ───────────────────────────────────────────────────── + // LIFECYCLE + // ───────────────────────────────────────────────────── + + /** + * ✅ CRITICAL: Initialize component with guard + */ + async init() { + pageLog.info('=== [PAGE NAME] INITIALIZING ==='); + + // ✅ CRITICAL: Initialization guard + if (window._[pageName]Initialized) { + pageLog.warn('Already initialized, skipping...'); + return; + } + window._[pageName]Initialized = true; + + // Track performance + const startTime = performance.now(); + + try { + // Load initial data + await this.loadData(); + + // Log performance + const duration = performance.now() - startTime; + window.LogConfig.logPerformance('[Page Name] Init', duration); + + pageLog.info('=== [PAGE NAME] INITIALIZATION COMPLETE ==='); + + } catch (error) { + window.LogConfig.logError(error, '[Page Name] Init'); + this.error = 'Failed to initialize page'; + } + }, + + // ───────────────────────────────────────────────────── + // DATA LOADING + // ───────────────────────────────────────────────────── + + /** + * Load main data from API + */ + async loadData() { + pageLog.info('Loading data...'); + this.loading = true; + this.error = null; + + try { + const startTime = performance.now(); + + // Build query params + const params = new URLSearchParams({ + page: this.pagination.currentPage, + per_page: this.pagination.perPage, + search: this.filters.search, + status: this.filters.status, + sort_by: this.filters.sortBy + }); + + const url = `/api/v1/admin/items?${params}`; + + // Log API request + window.LogConfig.logApiCall('GET', url, null, 'request'); + + // ✅ CRITICAL: Use lowercase apiClient + const response = await apiClient.get(url); + + // Log API response + window.LogConfig.logApiCall('GET', url, response, 'response'); + + // Update state + this.items = response.items || []; + this.pagination.total = response.total || 0; + this.pagination.totalPages = Math.ceil( + this.pagination.total / this.pagination.perPage + ); + this.pagination.from = (this.pagination.currentPage - 1) * this.pagination.perPage + 1; + this.pagination.to = Math.min( + this.pagination.currentPage * this.pagination.perPage, + this.pagination.total + ); + + // Log performance + const duration = performance.now() - startTime; + window.LogConfig.logPerformance('Load Data', duration); + + pageLog.info(`Data loaded successfully`, { + count: this.items.length, + duration: `${duration}ms`, + page: this.pagination.currentPage + }); + + } catch (error) { + window.LogConfig.logError(error, 'Load Data'); + this.error = error.message || 'Failed to load data'; + Utils.showToast('Failed to load data', 'error'); + } finally { + this.loading = false; + } + }, + + /** + * Refresh data + */ + async refresh() { + pageLog.info('Refreshing data...'); + this.error = null; + await this.loadData(); + Utils.showToast('Data refreshed', 'success'); + }, + + // ───────────────────────────────────────────────────── + // 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: '', + status: '', + 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(); + }, + + /** + * Go to previous page + */ + async previousPage() { + await this.goToPage(this.pagination.currentPage - 1); + }, + + /** + * Go to next page + */ + async nextPage() { + await this.goToPage(this.pagination.currentPage + 1); + }, + + /** + * Get pagination range for display + */ + get paginationRange() { + const current = this.pagination.currentPage; + const total = this.pagination.totalPages; + const range = []; + + // Show max 5 page numbers + let start = Math.max(1, current - 2); + let end = Math.min(total, start + 4); + + // Adjust start if we're near the end + if (end - start < 4) { + start = Math.max(1, end - 4); + } + + for (let i = start; i <= end; i++) { + range.push(i); + } + + return range; + }, + + // ───────────────────────────────────────────────────── + // CRUD OPERATIONS + // ───────────────────────────────────────────────────── + + /** + * Open create modal + */ + openCreateModal() { + pageLog.info('Opening create modal'); + this.modalMode = 'create'; + this.formData = { + name: '', + description: '', + is_active: true + }; + this.errors = {}; + this.showModal = true; + }, + + /** + * Open edit modal + */ + async editItem(itemId) { + pageLog.info('Opening edit modal for item:', itemId); + + try { + this.modalMode = 'edit'; + + // Load item details + const url = `/api/v1/admin/items/${itemId}`; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + const item = await apiClient.get(url); + + window.LogConfig.logApiCall('GET', url, item, 'response'); + + // Populate form + this.formData = { ...item }; + this.currentItem = item; + this.errors = {}; + this.showModal = true; + + } catch (error) { + window.LogConfig.logError(error, 'Edit Item'); + Utils.showToast('Failed to load item', 'error'); + } + }, + + /** + * View item details + */ + async viewItem(itemId) { + pageLog.info('Viewing item:', itemId); + // Navigate to detail page or open view modal + window.location.href = `/admin/items/${itemId}`; + }, + + /** + * Save item (create or update) + */ + async saveItem() { + pageLog.info('Saving item...'); + + // Validate form + if (!this.validateForm()) { + pageLog.warn('Form validation failed'); + return; + } + + this.saving = true; + this.errors = {}; + + try { + let response; + + if (this.modalMode === 'create') { + // Create new item + const url = '/api/v1/admin/items'; + window.LogConfig.logApiCall('POST', url, this.formData, 'request'); + + response = await apiClient.post(url, this.formData); + + window.LogConfig.logApiCall('POST', url, response, 'response'); + + pageLog.info('Item created successfully'); + Utils.showToast('Item created successfully', 'success'); + + } else { + // Update existing item + const url = `/api/v1/admin/items/${this.currentItem.id}`; + window.LogConfig.logApiCall('PUT', url, this.formData, 'request'); + + response = await apiClient.put(url, this.formData); + + window.LogConfig.logApiCall('PUT', url, response, 'response'); + + pageLog.info('Item updated successfully'); + Utils.showToast('Item updated successfully', 'success'); + } + + // Close modal and reload data + this.closeModal(); + await this.loadData(); + + } catch (error) { + window.LogConfig.logError(error, 'Save Item'); + + // Handle validation errors + if (error.details && error.details.validation_errors) { + this.errors = error.details.validation_errors; + } else { + Utils.showToast('Failed to save item', 'error'); + } + } finally { + this.saving = false; + } + }, + + /** + * Delete item + */ + async deleteItem(itemId) { + pageLog.info('Deleting item:', itemId); + + // Confirm deletion + if (!confirm('Are you sure you want to delete this item?')) { + return; + } + + try { + const url = `/api/v1/admin/items/${itemId}`; + window.LogConfig.logApiCall('DELETE', url, null, 'request'); + + await apiClient.delete(url); + + window.LogConfig.logApiCall('DELETE', url, null, 'response'); + + pageLog.info('Item deleted successfully'); + Utils.showToast('Item deleted successfully', 'success'); + + // Reload data + await this.loadData(); + + } catch (error) { + window.LogConfig.logError(error, 'Delete Item'); + Utils.showToast('Failed to delete item', 'error'); + } + }, + + /** + * Close modal + */ + closeModal() { + this.showModal = false; + this.formData = {}; + this.errors = {}; + this.currentItem = null; + }, + + // ───────────────────────────────────────────────────── + // VALIDATION + // ───────────────────────────────────────────────────── + + /** + * Validate form data + */ + validateForm() { + this.errors = {}; + + if (!this.formData.name || this.formData.name.trim() === '') { + this.errors.name = 'Name is required'; + } + + // Add more validation rules as needed + + return Object.keys(this.errors).length === 0; + }, + + // ───────────────────────────────────────────────────── + // HELPERS + // ───────────────────────────────────────────────────── + + /** + * Format date for display + */ + formatDate(dateString) { + if (!dateString) return '-'; + return Utils.formatDate(dateString); + }, + + /** + * Truncate text + */ + truncate(text, length = 50) { + if (!text) return ''; + if (text.length <= length) return text; + return text.substring(0, length) + '...'; + } + }; +} + +// Make available globally +window.admin[PageName] = admin[PageName]; + +pageLog.info('[Page Name] module loaded'); +``` + +--- + +### 3. Route Registration + +**File:** `app/api/v1/admin/pages.py` + +```python +from fastapi import APIRouter, Request, Depends +from app.core.auth import get_current_admin_user +from app.models.database.user import User + +router = APIRouter() + +@router.get("/admin/[page-route]") +async def [page_name]_page( + request: Request, + current_user: User = Depends(get_current_admin_user) +): + """ + [Page Name] page + Displays [description] + + Requires admin authentication. + """ + return templates.TemplateResponse( + "admin/[page-name].html", + { + "request": request, + "user": current_user, + } + ) +``` + +--- + +### 4. Sidebar Navigation + +**File:** `app/templates/admin/partials/sidebar.html` + +```jinja2 +
  • + + + + [Page Display Name] + +
  • +``` + +--- + +## 🎨 Common Page Patterns + +### Pattern 1: Simple Data List (GET) + +**Use for:** Vendor list, user list, product list + +```javascript +async init() { + if (window._myPageInitialized) return; + window._myPageInitialized = true; + await this.loadData(); +} + +async loadData() { + this.loading = true; + try { + const response = await apiClient.get('/api/v1/admin/items'); + this.items = response.items || []; + } finally { + this.loading = false; + } +} +``` + +--- + +### Pattern 2: Dashboard with Stats + +**Use for:** Dashboard, analytics pages + +```javascript +async init() { + if (window._dashboardInitialized) return; + window._dashboardInitialized = true; + + await Promise.all([ + this.loadStats(), + this.loadRecentActivity() + ]); +} + +async loadStats() { + this.stats = await apiClient.get('/api/v1/admin/stats'); +} + +async loadRecentActivity() { + this.activity = await apiClient.get('/api/v1/admin/recent-activity'); +} +``` + +--- + +### Pattern 3: Single Item Detail/Edit + +**Use for:** Vendor edit, user edit + +```javascript +async init() { + if (window._editPageInitialized) return; + window._editPageInitialized = true; + + const itemId = this.getItemIdFromUrl(); + await this.loadItem(itemId); +} + +async loadItem(id) { + this.item = await apiClient.get(`/api/v1/admin/items/${id}`); + // Populate form with item data + this.formData = { ...this.item }; +} + +async saveItem() { + if (!this.validateForm()) return; + + await apiClient.put( + `/api/v1/admin/items/${this.item.id}`, + this.formData + ); + + Utils.showToast('Saved successfully', 'success'); +} +``` + +--- + +### Pattern 4: With Filters and Pagination + +**Use for:** Large lists with filtering + +```javascript +filters: { + search: '', + status: '', + sortBy: 'created_at:desc' +}, + +pagination: { + currentPage: 1, + totalPages: 1, + perPage: 10 +}, + +async loadData() { + const params = new URLSearchParams({ + page: this.pagination.currentPage, + per_page: this.pagination.perPage, + ...this.filters + }); + + const response = await apiClient.get( + `/api/v1/admin/items?${params}` + ); + + this.items = response.items; + this.pagination.total = response.total; + this.pagination.totalPages = Math.ceil( + response.total / this.pagination.perPage + ); +} + +async applyFilters() { + this.pagination.currentPage = 1; // Reset to page 1 + await this.loadData(); +} +``` + +--- + +## 🔧 Best Practices + +### 1. Base Layout Inheritance + +**✅ ALWAYS include ...data():** +```javascript +function adminMyPage() { + return { + ...data(), // ← Must be first! + currentPage: 'my-page', + // Your state here + }; +} +``` + +### 2. Initialization Guards + +**✅ ALWAYS use initialization guard:** +```javascript +async init() { + if (window._myPageInitialized) { + log.warn('Already initialized'); + return; + } + window._myPageInitialized = true; + + await this.loadData(); +} +``` + +### 3. API Client Usage + +**✅ Use lowercase apiClient:** +```javascript +// ✅ CORRECT +await apiClient.get('/endpoint'); + +// ❌ WRONG +await ApiClient.get('/endpoint'); +await API_CLIENT.get('/endpoint'); +``` + +### 4. Centralized Logging + +**✅ Use pre-configured logger:** +```javascript +// ✅ CORRECT - One line! +const myLog = window.LogConfig.loggers.myPage; + +// ❌ WRONG - 15+ lines +const myLog = { + error: (...args) => console.error(...), + // ... etc +}; +``` + +### 5. Error Handling + +**✅ Handle errors gracefully:** +```javascript +try { + await this.loadData(); +} catch (error) { + window.LogConfig.logError(error, 'Load Data'); + this.error = error.message; + Utils.showToast('Failed to load data', 'error'); + // Don't throw - let UI handle it +} +``` + +### 6. Loading States + +**✅ Always set loading state:** +```javascript +this.loading = true; +try { + // ... operations +} finally { + this.loading = false; // Always executes +} +``` + +### 7. Performance Tracking + +**✅ Track performance for slow operations:** +```javascript +const startTime = performance.now(); +await this.loadData(); +const duration = performance.now() - startTime; +window.LogConfig.logPerformance('Load Data', duration); +``` + +--- + +## 📱 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 +- [ ] Sidebar collapses on mobile +- [ ] Stats cards stack 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 +- [ ] Create operation works +- [ ] Edit operation works +- [ ] Delete operation works +- [ ] Modal opens/closes +- [ ] Form validation works + +### Architecture +- [ ] Initialization guard works (no duplicate init) +- [ ] ...data() properly inherited +- [ ] currentPage set correctly +- [ ] Lowercase apiClient used +- [ ] Centralized logging used +- [ ] No console errors +- [ ] Dark mode works +- [ ] Mobile responsive + +--- + +## 🚀 Quick Start Commands + +```bash +# Create new page files +touch app/templates/admin/new-page.html +touch app/static/admin/js/new-page.js + +# Copy templates (start from dashboard.js as base) +cp app/static/admin/js/dashboard.js app/static/admin/js/new-page.js + +# Update files: +# 1. Change logger: dashLog → newPageLog +# 2. Change function: adminDashboard() → adminNewPage() +# 3. Change init flag: _dashboardInitialized → _newPageInitialized +# 4. Change currentPage: 'dashboard' → 'new-page' +# 5. Replace data loading logic +# 6. Update HTML template +# 7. Add route in pages.py +# 8. Add sidebar link +# 9. Test! +``` + +--- + +## 📚 Additional Resources + +### Pre-configured Loggers +```javascript +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 +``` + +### From Base Data (via ...data()) +- `this.dark` - Dark mode state +- `this.toggleTheme()` - Toggle theme +- `this.isSideMenuOpen` - Side menu state +- `this.toggleSideMenu()` - Toggle side menu +- `this.isProfileMenuOpen` - Profile menu state +- `this.toggleProfileMenu()` - Toggle profile menu + +### Global Utilities +- `Utils.showToast(message, type, duration)` +- `Utils.formatDate(dateString)` +- `apiClient.get(url)` +- `apiClient.post(url, data)` +- `apiClient.put(url, data)` +- `apiClient.delete(url)` + +### Icon Helper +```html + +``` + +--- + +## ⚠️ Common Mistakes to Avoid + +### 1. Missing ...data() +```javascript +// ❌ WRONG - No base inheritance +function myPage() { + return { + items: [] + }; +} + +// ✅ CORRECT +function myPage() { + return { + ...data(), // Must be first! + items: [] + }; +} +``` + +### 2. Wrong API Client Case +```javascript +// ❌ WRONG +await ApiClient.get('/url'); + +// ✅ CORRECT +await apiClient.get('/url'); +``` + +### 3. Missing Init Guard +```javascript +// ❌ WRONG +async init() { + await this.loadData(); +} + +// ✅ CORRECT +async init() { + if (window._myPageInitialized) return; + window._myPageInitialized = true; + await this.loadData(); +} +``` + +### 4. Old Logging Pattern +```javascript +// ❌ WRONG - 15+ lines +const log = { + error: (...args) => console.error(...), + // ... +}; + +// ✅ CORRECT - 1 line +const log = window.LogConfig.loggers.myPage; +``` + +### 5. Missing currentPage +```javascript +// ❌ WRONG +return { + ...data(), + items: [] +}; + +// ✅ CORRECT +return { + ...data(), + currentPage: 'my-page', // Must set! + items: [] +}; +``` + +--- + +This template provides a complete, production-ready pattern for building admin pages with consistent structure, proper initialization, comprehensive logging, and excellent maintainability. diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/FRONTEND_ADMIN_ARCHITECTURE_OVERVIEW.txt b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/FRONTEND_ADMIN_ARCHITECTURE_OVERVIEW.txt new file mode 100644 index 00000000..60ca7939 --- /dev/null +++ b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/admin/FRONTEND_ADMIN_ARCHITECTURE_OVERVIEW.txt @@ -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 %} +
    Loading...
    +
    +
    + +
    +
    + {% 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: +
    + +
    + + +🌙 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: +
    +

    Content

    +
    + + +🔒 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: + + + +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 +══════════════════════════════════════════════════════════════════ diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/shop/FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/shop/FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md new file mode 100644 index 00000000..5c0c364a --- /dev/null +++ b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/shop/FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md @@ -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 Name] +

    + + + +
    + + + + +
    +
    +
    +

    Loading...

    +
    + + + + +
    +
    + + + +
    +

    Error

    +

    +
    +
    +
    + + + + +
    + + +
    + + + +

    + No items found +

    +

    + Try adjusting your filters or check back later. +

    +
    + + +
    + + +
    + + + + +
    + + + + + + + + + +
    +
    +
    +{% endblock %} + +{# Page-specific JavaScript #} +{% block extra_scripts %} + +{% 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 +
    + +
    +``` + +--- + +### 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 +
    + +
    + +
    + +
    +
    + + +
    +

    +

    +

    + + +
    + + +
    + + + +
    +
    +``` + +--- + +### 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 + + + + + +``` + +### 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 + +``` + +### 6. Dark Mode + +Support both light and dark modes: + +```html +
    + Content +
    +``` + +### 7. Accessibility + +Add proper ARIA labels and keyboard navigation: + +```html + +``` + +--- + +## 📱 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 +
    + +
    +

    +

    + +
    +
    +``` + +**filter-sidebar.html:** +```html +
    +

    Filters

    + + +
    + + +
    + + +
    + + +
    + $0 + +
    +
    + + + +
    +``` + +--- + +## 🚀 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 + + +``` + +### 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. diff --git a/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/shop/FRONTEND_SHOP_ARCHITECTURE_OVERVIEW.txt b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/shop/FRONTEND_SHOP_ARCHITECTURE_OVERVIEW.txt new file mode 100644 index 00000000..4f6ea802 --- /dev/null +++ b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/shop/FRONTEND_SHOP_ARCHITECTURE_OVERVIEW.txt @@ -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 %} +
    Loading products...
    +
    + +
    + {% 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 + + {% block extra_head %}{% endblock %} + + +
    + + {% include 'vendor/partials/sidebar.html' %} + +
    + + {% include 'vendor/partials/header.html' %} + + +
    +
    + {% block content %}{% endblock %} +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + {% block extra_scripts %}{% endblock %} + + +``` + +### Login Template Structure + +```jinja2 +{# app/templates/vendor/login.html #} + + + + + + Vendor Login - Multi-Tenant Platform + + + + + +
    + +
    + + + + + + + + + + +``` + +### Dashboard Template Structure + +```jinja2 +{# app/templates/vendor/admin/dashboard.html #} +{% extends "vendor/base.html" %} + +{% block title %}Dashboard{% endblock %} + +{% block alpine_data %}vendorDashboard(){% endblock %} + +{% block content %} + +
    +

    + Dashboard +

    + +
    + + +{% include 'vendor/partials/vendor_info.html' %} + + +
    + +
    + + +
    + +
    +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} +``` + +--- + +## 6. Component Breakdown + +### 6.1 Header Component (partials/header.html) + +```jinja2 +{# app/templates/vendor/partials/header.html #} +
    +
    + + + + +
    +
    +
    + +
    + +
    +
    + + +
    +
    +``` + +### 6.2 Sidebar Component (partials/sidebar.html) + +```jinja2 +{# app/templates/vendor/partials/sidebar.html #} + + + + +
    + + +``` + +### 6.3 Vendor Info Component (partials/vendor_info.html) + +```jinja2 +{# app/templates/vendor/partials/vendor_info.html #} +
    +
    +
    +
    + + {{ vendor.name[0].upper() if vendor and vendor.name else '?' }} + +
    +
    +

    + {{ vendor.name if vendor else 'Loading...' }} +

    +

    + {{ vendor.vendor_code if vendor else '' }} + {% if vendor and vendor.subdomain %} + • {{ vendor.subdomain }}.platform.com + {% endif %} +

    +
    +
    +
    + {% if vendor %} + + {{ 'Verified' if vendor.is_verified else 'Pending Verification' }} + + + {{ 'Active' if vendor.is_active else 'Inactive' }} + + {% endif %} +
    +
    +
    +``` + +--- + +## 7. Static Assets Organization + +### 7.1 JavaScript Files + +#### init-alpine.js +```javascript +// app/static/vendor/js/init-alpine.js +/** + * Alpine.js initialization for vendor pages + * Provides common data and methods for all vendor pages + */ + +function data() { + return { + dark: false, + isSideMenuOpen: false, + isNotificationsMenuOpen: false, + isProfileMenuOpen: false, + currentPage: '', + currentUser: {}, + vendor: null, + vendorCode: null, + + init() { + // Set current page from URL + const path = window.location.pathname; + const segments = path.split('/').filter(Boolean); + this.currentPage = segments[segments.length - 1] || 'dashboard'; + + // Get vendor code from URL + if (segments[0] === 'vendor' && segments[1]) { + this.vendorCode = segments[1]; + } + + // Load user from localStorage + const user = localStorage.getItem('currentUser'); + if (user) { + this.currentUser = JSON.parse(user); + } + + // Load theme preference + const theme = localStorage.getItem('theme'); + if (theme === 'dark') { + this.dark = true; + } + + // Load vendor info + this.loadVendorInfo(); + }, + + async loadVendorInfo() { + if (!this.vendorCode) return; + + try { + const response = await apiClient.get(`/api/v1/vendors/${this.vendorCode}`); + this.vendor = response; + logDebug('Vendor info loaded', this.vendor); + } catch (error) { + logError('Failed to load vendor info', error); + } + }, + + toggleSideMenu() { + this.isSideMenuOpen = !this.isSideMenuOpen; + }, + + closeSideMenu() { + this.isSideMenuOpen = false; + }, + + toggleNotificationsMenu() { + this.isNotificationsMenuOpen = !this.isNotificationsMenuOpen; + if (this.isNotificationsMenuOpen) { + this.isProfileMenuOpen = false; + } + }, + + closeNotificationsMenu() { + this.isNotificationsMenuOpen = false; + }, + + toggleProfileMenu() { + this.isProfileMenuOpen = !this.isProfileMenuOpen; + if (this.isProfileMenuOpen) { + this.isNotificationsMenuOpen = false; + } + }, + + closeProfileMenu() { + this.isProfileMenuOpen = false; + }, + + toggleTheme() { + this.dark = !this.dark; + localStorage.setItem('theme', this.dark ? 'dark' : 'light'); + }, + + async handleLogout() { + try { + // Call logout endpoint + await apiClient.post('/api/v1/vendor/auth/logout'); + } catch (error) { + logError('Logout error', error); + } finally { + // Clear local storage + localStorage.removeItem('accessToken'); + localStorage.removeItem('currentUser'); + + // Redirect to login + window.location.href = `/vendor/${this.vendorCode}/login`; + } + } + }; +} +``` + +#### login.js +```javascript +// app/static/vendor/js/login.js +/** + * Vendor login page logic + */ + +function vendorLogin() { + return { + credentials: { + username: '', + password: '' + }, + vendor: null, + vendorCode: null, + loading: false, + checked: false, + error: '', + success: '', + errors: {}, + + async init() { + // Get vendor code from URL path + const pathSegments = window.location.pathname.split('/').filter(Boolean); + if (pathSegments[0] === 'vendor' && pathSegments[1]) { + this.vendorCode = pathSegments[1]; + await this.loadVendor(); + } else { + // Try to get from query params (backward compatibility) + const urlParams = new URLSearchParams(window.location.search); + this.vendorCode = urlParams.get('vendor') || localStorage.getItem('vendorCode'); + if (this.vendorCode) { + await this.loadVendor(); + } + } + this.checked = true; + }, + + async loadVendor() { + this.loading = true; + try { + const response = await apiClient.get(`/api/v1/vendors/${this.vendorCode}`); + this.vendor = response; + logInfo('Vendor loaded', this.vendor); + } catch (error) { + logError('Failed to load vendor', error); + this.error = 'Failed to load vendor information'; + } finally { + this.loading = false; + } + }, + + async handleLogin() { + this.clearErrors(); + this.loading = true; + + try { + // Validate inputs + if (!this.credentials.username) { + this.errors.username = 'Username is required'; + } + if (!this.credentials.password) { + this.errors.password = 'Password is required'; + } + + if (Object.keys(this.errors).length > 0) { + this.loading = false; + return; + } + + // Call login endpoint + const response = await apiClient.post('/api/v1/vendor/auth/login', { + username: this.credentials.username, + password: this.credentials.password, + vendor_code: this.vendorCode + }); + + logInfo('Login successful', response); + + // Store tokens and user info + localStorage.setItem('accessToken', response.access_token); + localStorage.setItem('currentUser', JSON.stringify(response.user)); + localStorage.setItem('vendorCode', this.vendorCode); + + // Show success message + this.success = 'Login successful! Redirecting...'; + + // Redirect to dashboard + setTimeout(() => { + window.location.href = `/vendor/${this.vendorCode}/dashboard`; + }, 1000); + + } catch (error) { + logError('Login failed', error); + + if (error.status === 401) { + this.error = 'Invalid username or password'; + } else if (error.status === 403) { + this.error = 'Your account does not have access to this vendor'; + } else { + this.error = error.message || 'Login failed. Please try again.'; + } + } finally { + this.loading = false; + } + }, + + clearErrors() { + this.error = ''; + this.errors = {}; + } + }; +} +``` + +#### dashboard.js +```javascript +// app/static/vendor/js/dashboard.js +/** + * Vendor dashboard page logic + */ + +function vendorDashboard() { + return { + loading: false, + error: '', + stats: { + products_count: 0, + orders_count: 0, + customers_count: 0, + revenue: 0 + }, + recentOrders: [], + recentProducts: [], + + async init() { + await this.loadDashboardData(); + }, + + async loadDashboardData() { + this.loading = true; + this.error = ''; + + try { + // Load stats + const statsResponse = await apiClient.get( + `/api/v1/vendors/${this.vendorCode}/stats` + ); + this.stats = statsResponse; + + // Load recent orders + const ordersResponse = await apiClient.get( + `/api/v1/vendors/${this.vendorCode}/orders?limit=5&sort=created_at:desc` + ); + this.recentOrders = ordersResponse.items || []; + + // Load recent products + const productsResponse = await apiClient.get( + `/api/v1/vendors/${this.vendorCode}/products?limit=5&sort=created_at:desc` + ); + this.recentProducts = productsResponse.items || []; + + logInfo('Dashboard data loaded', { + stats: this.stats, + orders: this.recentOrders.length, + products: this.recentProducts.length + }); + + } catch (error) { + logError('Failed to load dashboard data', error); + this.error = 'Failed to load dashboard data. Please try refreshing the page.'; + } finally { + this.loading = false; + } + }, + + async refresh() { + await this.loadDashboardData(); + }, + + formatCurrency(amount) { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'EUR' + }).format(amount || 0); + }, + + formatDate(dateString) { + if (!dateString) return ''; + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + } + }; +} +``` + +### 7.2 CSS Organization + +#### tailwind.config.js +```javascript +// tailwind.config.js for vendor area +module.exports = { + content: [ + './app/templates/vendor/**/*.html', + './app/static/vendor/js/**/*.js' + ], + darkMode: 'class', + theme: { + extend: { + colors: { + purple: { + 50: '#f5f3ff', + 100: '#ede9fe', + 200: '#ddd6fe', + 300: '#c4b5fd', + 400: '#a78bfa', + 500: '#8b5cf6', + 600: '#7c3aed', + 700: '#6d28d9', + 800: '#5b21b6', + 900: '#4c1d95', + } + } + } + }, + plugins: [ + require('@tailwindcss/forms') + ] +}; +``` + +--- + +## 8. Migration Steps + +### Phase 1: Foundation Setup (Week 1) + +#### Step 1.1: Create Base Template Structure +```bash +# Create directory structure +mkdir -p app/templates/vendor/admin +mkdir -p app/templates/vendor/partials +mkdir -p app/static/vendor/js +mkdir -p app/static/vendor/css +mkdir -p app/static/vendor/img + +# Copy base template from admin as starting point +cp app/templates/admin/base.html app/templates/vendor/base.html +``` + +#### Step 1.2: Create Vendor-Specific Partials +```bash +# Create partials +touch app/templates/vendor/partials/header.html +touch app/templates/vendor/partials/sidebar.html +touch app/templates/vendor/partials/vendor_info.html +touch app/templates/vendor/partials/notifications.html +``` + +**Tasks:** +- [ ] Create base.html with vendor-specific styling +- [ ] Create header.html with vendor context +- [ ] Create sidebar.html with vendor navigation +- [ ] Create vendor_info.html component +- [ ] Set up Tailwind CSS for vendor area +- [ ] Create init-alpine.js with vendor data + +#### Step 1.3: Migrate Login Page +```bash +# Create login template +touch app/templates/vendor/login.html +``` + +**Tasks:** +- [ ] Convert login.html to Jinja2 template +- [ ] Create login.js with vendor authentication logic +- [ ] Test login flow with vendor context +- [ ] Verify redirect to dashboard works +- [ ] Test error handling + +**Update pages.py route:** +```python +@router.get("/vendor/{vendor_code}/login", response_class=HTMLResponse) +async def vendor_login_page( + request: Request, + vendor_code: str = Path(...), + db: Session = Depends(get_db) +): + """Render vendor login page with vendor info.""" + # Load vendor from database + vendor = db.query(Vendor).filter(Vendor.vendor_code == vendor_code).first() + + return templates.TemplateResponse( + "vendor/login.html", + { + "request": request, + "vendor_code": vendor_code, + "vendor": vendor + } + ) +``` + +### Phase 2: Core Pages (Week 2) + +#### Step 2.1: Migrate Dashboard +```bash +# Create dashboard template +touch app/templates/vendor/admin/dashboard.html +touch app/static/vendor/js/dashboard.js +``` + +**Tasks:** +- [ ] Create dashboard.html extending base.html +- [ ] Create dashboard.js with data loading logic +- [ ] Implement stats cards +- [ ] Add recent activity section +- [ ] Test with real vendor data +- [ ] Verify navigation works + +**Update pages.py route:** +```python +@router.get("/vendor/{vendor_code}/dashboard", response_class=HTMLResponse) +async def vendor_dashboard_page( + request: Request, + vendor_code: str = Path(...), + current_user: User = Depends(get_current_vendor_user), + db: Session = Depends(get_db) +): + """Render vendor dashboard with initial data.""" + vendor = db.query(Vendor).filter(Vendor.vendor_code == vendor_code).first() + + # Load initial stats + stats = { + 'products_count': db.query(Product).filter(Product.vendor_id == vendor.id).count(), + 'orders_count': db.query(Order).filter(Order.vendor_id == vendor.id).count(), + 'customers_count': db.query(Customer).filter(Customer.vendor_id == vendor.id).count(), + 'revenue': 0 # Calculate from orders + } + + return templates.TemplateResponse( + "vendor/admin/dashboard.html", + { + "request": request, + "vendor_code": vendor_code, + "vendor": vendor, + "current_user": current_user, + "stats": stats + } + ) +``` + +#### Step 2.2: Migrate Marketplace Import +```bash +# Create marketplace template +touch app/templates/vendor/admin/marketplace.html +touch app/static/vendor/js/marketplace.js +``` + +**Tasks:** +- [ ] Create marketplace.html template +- [ ] Create marketplace.js with import logic +- [ ] Implement import job listing +- [ ] Add product selection interface +- [ ] Test import workflow +- [ ] Verify product publishing works + +#### Step 2.3: Migrate Products +```bash +# Create products template +touch app/templates/vendor/admin/products.html +touch app/static/vendor/js/products.js +``` + +**Tasks:** +- [ ] Create products.html template +- [ ] Create products.js with CRUD logic +- [ ] Implement product listing table +- [ ] Add search and filtering +- [ ] Create product detail modal +- [ ] Test product management + +### Phase 3: Management Pages (Week 3) + +#### Step 3.1: Migrate Orders +```bash +# Create orders template +touch app/templates/vendor/admin/orders.html +touch app/static/vendor/js/orders.js +``` + +**Tasks:** +- [ ] Create orders.html template +- [ ] Create orders.js with order management logic +- [ ] Implement order listing with status filters +- [ ] Add order detail view +- [ ] Test order status updates + +#### Step 3.2: Migrate Customers +```bash +# Create customers template +touch app/templates/vendor/admin/customers.html +touch app/static/vendor/js/customers.js +``` + +**Tasks:** +- [ ] Create customers.html template +- [ ] Create customers.js with customer management +- [ ] Implement customer listing +- [ ] Add customer detail view +- [ ] Test customer search + +#### Step 3.3: Migrate Inventory +```bash +# Create inventory template +touch app/templates/vendor/admin/inventory.html +touch app/static/vendor/js/inventory.js +``` + +**Tasks:** +- [ ] Create inventory.html template +- [ ] Create inventory.js with stock management +- [ ] Implement inventory table +- [ ] Add stock adjustment interface +- [ ] Test inventory updates + +### Phase 4: Settings & Team (Week 4) + +#### Step 4.1: Migrate Team Management +```bash +# Create team template +touch app/templates/vendor/admin/team.html +touch app/static/vendor/js/team.js +``` + +**Tasks:** +- [ ] Create team.html template +- [ ] Create team.js with team management logic +- [ ] Implement team member listing +- [ ] Add role assignment interface +- [ ] Test team invitations + +#### Step 4.2: Migrate Settings +```bash +# Create settings template +touch app/templates/vendor/settings.html +touch app/static/vendor/js/settings.js +``` + +**Tasks:** +- [ ] Create settings.html template +- [ ] Create settings.js with settings management +- [ ] Implement settings tabs (General, Payment, Integrations) +- [ ] Add form validation +- [ ] Test settings updates + +#### Step 4.3: Final Testing & Cleanup +**Tasks:** +- [ ] Cross-browser testing (Chrome, Firefox, Safari) +- [ ] Mobile responsiveness testing +- [ ] Dark mode testing +- [ ] Performance optimization +- [ ] Security audit +- [ ] Code cleanup and documentation +- [ ] Remove legacy HTML files +- [ ] Update documentation + +--- + +## 9. Testing Plan + +### 9.1 Unit Testing + +**Template Rendering Tests:** +```python +# tests/test_vendor_templates.py +def test_vendor_login_page_renders(client, test_vendor): + """Test that vendor login page renders correctly.""" + response = client.get(f"/vendor/{test_vendor.vendor_code}/login") + assert response.status_code == 200 + assert test_vendor.name in response.text + assert test_vendor.vendor_code in response.text + +def test_vendor_dashboard_requires_auth(client, test_vendor): + """Test that dashboard requires authentication.""" + response = client.get(f"/vendor/{test_vendor.vendor_code}/dashboard") + assert response.status_code == 302 # Redirect to login + +def test_vendor_dashboard_with_auth(client, test_vendor, auth_token): + """Test that authenticated user can access dashboard.""" + response = client.get( + f"/vendor/{test_vendor.vendor_code}/dashboard", + headers={"Authorization": f"Bearer {auth_token}"} + ) + assert response.status_code == 200 + assert test_vendor.name in response.text +``` + +### 9.2 Integration Testing + +**Full Workflow Tests:** +```python +# tests/test_vendor_workflows.py +def test_vendor_login_to_dashboard_flow(client, test_vendor, test_user): + """Test complete login to dashboard flow.""" + # 1. Access login page + response = client.get(f"/vendor/{test_vendor.vendor_code}/login") + assert response.status_code == 200 + + # 2. Submit login credentials + login_response = client.post( + "/api/v1/vendor/auth/login", + json={ + "username": test_user.username, + "password": "password123", + "vendor_code": test_vendor.vendor_code + } + ) + assert login_response.status_code == 200 + token = login_response.json()["access_token"] + + # 3. Access dashboard + dashboard_response = client.get( + f"/vendor/{test_vendor.vendor_code}/dashboard", + headers={"Authorization": f"Bearer {token}"} + ) + assert dashboard_response.status_code == 200 + assert test_vendor.name in dashboard_response.text +``` + +### 9.3 Frontend Testing + +**Manual Testing Checklist:** + +- [ ] **Login Page** + - [ ] Vendor name and code display correctly + - [ ] Login form validation works + - [ ] Error messages display properly + - [ ] Successful login redirects to dashboard + - [ ] "Vendor not found" state displays correctly + +- [ ] **Dashboard** + - [ ] Stats cards load with correct data + - [ ] Navigation works correctly + - [ ] Dark mode toggle works + - [ ] Responsive design on mobile + - [ ] Profile menu functions properly + +- [ ] **Sidebar Navigation** + - [ ] All links work correctly + - [ ] Active page highlighting works + - [ ] Mobile menu opens/closes properly + - [ ] Vendor branding displays correctly + +- [ ] **Header** + - [ ] Search functionality works + - [ ] Notifications menu opens + - [ ] Profile menu opens + - [ ] Logout works correctly + - [ ] Theme toggle works + +- [ ] **Products Page** + - [ ] Product listing loads + - [ ] Search and filtering work + - [ ] Pagination works + - [ ] Add/Edit/Delete operations work + - [ ] Product images display correctly + +- [ ] **Marketplace Page** + - [ ] Import job creation works + - [ ] Product browsing works + - [ ] Product selection works + - [ ] Publishing to catalog works + +- [ ] **Orders Page** + - [ ] Order listing loads + - [ ] Status filtering works + - [ ] Order details display + - [ ] Status updates work + +- [ ] **Customers Page** + - [ ] Customer listing loads + - [ ] Customer search works + - [ ] Customer details display + +- [ ] **Inventory Page** + - [ ] Inventory listing loads + - [ ] Stock adjustments work + - [ ] Low stock alerts display + +- [ ] **Team Page** + - [ ] Team member listing loads + - [ ] Invitations work + - [ ] Role assignments work + +- [ ] **Settings Page** + - [ ] Settings tabs work + - [ ] Form validation works + - [ ] Updates save correctly + +### 9.4 Performance Testing + +**Metrics to Monitor:** +- [ ] Page load time < 2 seconds +- [ ] Time to interactive < 3 seconds +- [ ] API response time < 500ms +- [ ] Bundle size < 200KB (compressed) +- [ ] Lighthouse score > 90 + +--- + +## 10. Rollback Strategy + +### 10.1 Version Control + +**Git Branching Strategy:** +```bash +# Create feature branch for migration +git checkout -b feature/vendor-jinja2-migration + +# Create checkpoints for each phase +git tag -a vendor-migration-phase1 -m "Phase 1: Foundation complete" +git tag -a vendor-migration-phase2 -m "Phase 2: Core pages complete" +git tag -a vendor-migration-phase3 -m "Phase 3: Management pages complete" +git tag -a vendor-migration-phase4 -m "Phase 4: Settings & team complete" +``` + +### 10.2 Feature Flags + +**Environment Variable Toggle:** +```python +# app/config.py +USE_LEGACY_VENDOR_TEMPLATES = os.getenv("USE_LEGACY_VENDOR_TEMPLATES", "false").lower() == "true" +``` + +**Route Handling:** +```python +# app/api/v1/vendor/pages.py +from app.config import USE_LEGACY_VENDOR_TEMPLATES + +@router.get("/vendor/{vendor_code}/dashboard") +async def vendor_dashboard_page(request: Request, vendor_code: str): + if USE_LEGACY_VENDOR_TEMPLATES: + # Return legacy HTML + return FileResponse("app/static/legacy/vendor/dashboard.html") + else: + # Return Jinja2 template + return templates.TemplateResponse("vendor/admin/dashboard.html", {...}) +``` + +### 10.3 Rollback Procedures + +**Quick Rollback:** +```bash +# Set environment variable to use legacy templates +export USE_LEGACY_VENDOR_TEMPLATES=true + +# Restart application +systemctl restart gunicorn +``` + +**Full Rollback:** +```bash +# Revert to previous tag +git checkout vendor-migration-phase1 + +# Rebuild static assets +npm run build:vendor + +# Restart application +systemctl restart gunicorn +``` + +--- + +## Summary + +This migration plan provides a complete roadmap for converting the vendor frontend from legacy standalone HTML to a modern Jinja2 template-based architecture. The plan follows the exact same patterns and structure as the admin area migration, ensuring consistency across the platform. + +**Key Benefits:** +1. **Reduced Code Duplication**: Shared base templates and partials +2. **Improved Maintainability**: Centralized styling and behavior +3. **Better Performance**: Server-side rendering with progressive enhancement +4. **Consistent UX**: Uniform design across all vendor pages +5. **Easier Testing**: Better separation of concerns + +**Timeline:** +- **Week 1**: Foundation (base templates, partials, login) +- **Week 2**: Core pages (dashboard, marketplace, products) +- **Week 3**: Management pages (orders, customers, inventory) +- **Week 4**: Settings & team, testing, cleanup + +**Success Criteria:** +- All vendor pages migrated to Jinja2 templates +- No legacy HTML files remaining +- All tests passing +- Performance metrics met +- Documentation updated diff --git a/static/shared/css/base.css b/static/shared/css/base.css new file mode 100644 index 00000000..e69de29b diff --git a/static/admin/img/create-account-office-dark.jpeg b/static/shop/img/create-account-office-dark.jpeg similarity index 100% rename from static/admin/img/create-account-office-dark.jpeg rename to static/shop/img/create-account-office-dark.jpeg diff --git a/static/admin/img/create-account-office.jpeg b/static/shop/img/create-account-office.jpeg similarity index 100% rename from static/admin/img/create-account-office.jpeg rename to static/shop/img/create-account-office.jpeg diff --git a/static/shop/img/forgot-password-office-dark.jpeg b/static/shop/img/forgot-password-office-dark.jpeg new file mode 100644 index 00000000..cdd69e31 Binary files /dev/null and b/static/shop/img/forgot-password-office-dark.jpeg differ diff --git a/static/shop/img/forgot-password-office.jpeg b/static/shop/img/forgot-password-office.jpeg new file mode 100644 index 00000000..4dcaf519 Binary files /dev/null and b/static/shop/img/forgot-password-office.jpeg differ diff --git a/static/shop/img/login-office-dark.jpeg b/static/shop/img/login-office-dark.jpeg new file mode 100644 index 00000000..9dda0540 Binary files /dev/null and b/static/shop/img/login-office-dark.jpeg differ diff --git a/static/shop/img/login-office.jpeg b/static/shop/img/login-office.jpeg new file mode 100644 index 00000000..6414d490 Binary files /dev/null and b/static/shop/img/login-office.jpeg differ diff --git a/static/vendor/css/tailwind.output.css b/static/vendor/css/tailwind.output.css new file mode 100644 index 00000000..b174bcd6 --- /dev/null +++ b/static/vendor/css/tailwind.output.css @@ -0,0 +1 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none;padding:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #d5d6d7}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:#a0aec0}input::placeholder,textarea::placeholder{color:#a0aec0}[role=button],button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#e2e8f0;border-width:1px;border-radius:.25rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5}.form-input::-moz-placeholder{color:#9e9e9e;opacity:1}.form-input:-ms-input-placeholder{color:#9e9e9e;opacity:1}.form-input::-ms-input-placeholder{color:#9e9e9e;opacity:1}.form-input::placeholder{color:#9e9e9e;opacity:1}.form-input:focus{outline:none;box-shadow:0 0 0 3px rgba(66,153,225,.5);border-color:#63b3ed}.form-textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#e2e8f0;border-width:1px;border-radius:.25rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5}.form-textarea::-moz-placeholder{color:#9e9e9e;opacity:1}.form-textarea:-ms-input-placeholder{color:#9e9e9e;opacity:1}.form-textarea::-ms-input-placeholder{color:#9e9e9e;opacity:1}.form-textarea::placeholder{color:#9e9e9e;opacity:1}.form-textarea:focus{outline:none;box-shadow:0 0 0 3px rgba(66,153,225,.5);border-color:#63b3ed}.form-multiselect{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#e2e8f0;border-width:1px;border-radius:.25rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5}.form-multiselect:focus{outline:none;box-shadow:0 0 0 3px rgba(66,153,225,.5);border-color:#63b3ed}.form-select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23a0aec0'%3E%3Cpath d='M15.3 9.3a1 1 0 011.4 1.4l-4 4a1 1 0 01-1.4 0l-4-4a1 1 0 011.4-1.4l3.3 3.29 3.3-3.3z'/%3E%3C/svg%3E");-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;background-repeat:no-repeat;background-color:#fff;border-color:#e2e8f0;border-width:1px;border-radius:.25rem;padding:.5rem 2.5rem .5rem .75rem;font-size:1rem;line-height:1.5;background-position:right .5rem center;background-size:1.5em 1.5em}.form-select::-ms-expand{color:#a0aec0;border:none}@media not print{.form-select::-ms-expand{display:none}}@media print and (-ms-high-contrast:active),print and (-ms-high-contrast:none){.form-select{padding-right:.75rem}}.form-select:focus{outline:none;box-shadow:0 0 0 3px rgba(66,153,225,.5);border-color:#63b3ed}.form-checkbox{-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;height:1em;width:1em;color:#4299e1;background-color:#fff;border-color:#e2e8f0;border-width:1px;border-radius:.25rem}.form-checkbox:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5.707 7.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4a1 1 0 00-1.414-1.414L7 8.586 5.707 7.293z'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media not print{.form-checkbox::-ms-check{border-width:1px;color:transparent;background:inherit;border-color:inherit;border-radius:inherit}}.form-checkbox:focus{outline:none;box-shadow:0 0 0 3px rgba(66,153,225,.5);border-color:#63b3ed}.form-radio{-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;flex-shrink:0;border-radius:100%;height:1em;width:1em;color:#4299e1;background-color:#fff;border-color:#e2e8f0;border-width:1px}.form-radio:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media not print{.form-radio::-ms-check{border-width:1px;color:transparent;background:inherit;border-color:inherit;border-radius:inherit}}.form-radio:focus{outline:none;box-shadow:0 0 0 3px rgba(66,153,225,.5);border-color:#63b3ed}.space-y-2>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(0.5rem*(1 - var(--space-y-reverse)));margin-bottom:calc(0.5rem*var(--space-y-reverse))}.space-x-3>:not(template)~:not(template){--space-x-reverse:0;margin-right:calc(0.75rem*var(--space-x-reverse));margin-left:calc(0.75rem*(1 - var(--space-x-reverse)))}.space-y-4>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(1rem*(1 - var(--space-y-reverse)));margin-bottom:calc(1rem*var(--space-y-reverse))}.space-x-4>:not(template)~:not(template){--space-x-reverse:0;margin-right:calc(1rem*var(--space-x-reverse));margin-left:calc(1rem*(1 - var(--space-x-reverse)))}.space-x-6>:not(template)~:not(template){--space-x-reverse:0;margin-right:calc(1.5rem*var(--space-x-reverse));margin-left:calc(1.5rem*(1 - var(--space-x-reverse)))}.divide-y>:not(template)~:not(template){--divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--divide-y-reverse)));border-bottom-width:calc(1px*var(--divide-y-reverse))}.theme-dark .dark\:divide-gray-700>:not(template)~:not(template){--divide-opacity:1;border-color:#24262d;border-color:rgba(36,38,45,var(--divide-opacity))}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-black{--bg-opacity:1;background-color:#000;background-color:rgba(0,0,0,var(--bg-opacity))}.bg-gray-50{--bg-opacity:1;background-color:#f9fafb;background-color:rgba(249,250,251,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f4f5f7;background-color:rgba(244,245,247,var(--bg-opacity))}.bg-red-100{--bg-opacity:1;background-color:#fde8e8;background-color:rgba(253,232,232,var(--bg-opacity))}.bg-red-600{--bg-opacity:1;background-color:#e02424;background-color:rgba(224,36,36,var(--bg-opacity))}.bg-orange-100{--bg-opacity:1;background-color:#feecdc;background-color:rgba(254,236,220,var(--bg-opacity))}.bg-green-100{--bg-opacity:1;background-color:#def7ec;background-color:rgba(222,247,236,var(--bg-opacity))}.bg-teal-100{--bg-opacity:1;background-color:#d5f5f6;background-color:rgba(213,245,246,var(--bg-opacity))}.bg-teal-500{--bg-opacity:1;background-color:#0694a2;background-color:rgba(6,148,162,var(--bg-opacity))}.bg-teal-600{--bg-opacity:1;background-color:#047481;background-color:rgba(4,116,129,var(--bg-opacity))}.bg-blue-100{--bg-opacity:1;background-color:#e1effe;background-color:rgba(225,239,254,var(--bg-opacity))}.bg-blue-500{--bg-opacity:1;background-color:#3f83f8;background-color:rgba(63,131,248,var(--bg-opacity))}.bg-blue-600{--bg-opacity:1;background-color:#1c64f2;background-color:rgba(28,100,242,var(--bg-opacity))}.bg-purple-600{--bg-opacity:1;background-color:#7e3af2;background-color:rgba(126,58,242,var(--bg-opacity))}.hover\:bg-gray-100:hover{--bg-opacity:1;background-color:#f4f5f7;background-color:rgba(244,245,247,var(--bg-opacity))}.hover\:bg-purple-700:hover{--bg-opacity:1;background-color:#6c2bd9;background-color:rgba(108,43,217,var(--bg-opacity))}.focus\:bg-white:focus{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.active\:bg-transparent:active{background-color:transparent}.active\:bg-purple-600:active{--bg-opacity:1;background-color:#7e3af2;background-color:rgba(126,58,242,var(--bg-opacity))}.theme-dark .dark\:bg-gray-700{--bg-opacity:1;background-color:#24262d;background-color:rgba(36,38,45,var(--bg-opacity))}.theme-dark .dark\:bg-gray-800{--bg-opacity:1;background-color:#1a1c23;background-color:rgba(26,28,35,var(--bg-opacity))}.theme-dark .dark\:bg-gray-900{--bg-opacity:1;background-color:#121317;background-color:rgba(18,19,23,var(--bg-opacity))}.theme-dark .dark\:bg-red-600{--bg-opacity:1;background-color:#e02424;background-color:rgba(224,36,36,var(--bg-opacity))}.theme-dark .dark\:bg-red-700{--bg-opacity:1;background-color:#c81e1e;background-color:rgba(200,30,30,var(--bg-opacity))}.theme-dark .dark\:bg-orange-500{--bg-opacity:1;background-color:#ff5a1f;background-color:rgba(255,90,31,var(--bg-opacity))}.theme-dark .dark\:bg-orange-600{--bg-opacity:1;background-color:#d03801;background-color:rgba(208,56,1,var(--bg-opacity))}.theme-dark .dark\:bg-green-500{--bg-opacity:1;background-color:#0e9f6e;background-color:rgba(14,159,110,var(--bg-opacity))}.theme-dark .dark\:bg-green-700{--bg-opacity:1;background-color:#046c4e;background-color:rgba(4,108,78,var(--bg-opacity))}.theme-dark .dark\:bg-teal-500{--bg-opacity:1;background-color:#0694a2;background-color:rgba(6,148,162,var(--bg-opacity))}.theme-dark .dark\:bg-blue-500{--bg-opacity:1;background-color:#3f83f8;background-color:rgba(63,131,248,var(--bg-opacity))}.theme-dark .dark\:hover\:bg-gray-800:hover{--bg-opacity:1;background-color:#1a1c23;background-color:rgba(26,28,35,var(--bg-opacity))}.bg-opacity-50{--bg-opacity:0.5}.border-transparent{border-color:transparent}.border-white{--border-opacity:1;border-color:#fff;border-color:rgba(255,255,255,var(--border-opacity))}.border-gray-100{--border-opacity:1;border-color:#f4f5f7;border-color:rgba(244,245,247,var(--border-opacity))}.border-gray-300{--border-opacity:1;border-color:#d5d6d7;border-color:rgba(213,214,215,var(--border-opacity))}.border-red-600{--border-opacity:1;border-color:#e02424;border-color:rgba(224,36,36,var(--border-opacity))}.border-green-600{--border-opacity:1;border-color:#057a55;border-color:rgba(5,122,85,var(--border-opacity))}.border-purple-600{--border-opacity:1;border-color:#7e3af2;border-color:rgba(126,58,242,var(--border-opacity))}.focus\:border-gray-500:focus{--border-opacity:1;border-color:#707275;border-color:rgba(112,114,117,var(--border-opacity))}.focus\:border-red-400:focus{--border-opacity:1;border-color:#f98080;border-color:rgba(249,128,128,var(--border-opacity))}.focus\:border-green-400:focus{--border-opacity:1;border-color:#31c48d;border-color:rgba(49,196,141,var(--border-opacity))}.focus\:border-purple-300:focus{--border-opacity:1;border-color:#cabffd;border-color:rgba(202,191,253,var(--border-opacity))}.focus\:border-purple-400:focus{--border-opacity:1;border-color:#ac94fa;border-color:rgba(172,148,250,var(--border-opacity))}.hover\:border-gray-500:hover{--border-opacity:1;border-color:#707275;border-color:rgba(112,114,117,var(--border-opacity))}.theme-dark .dark\:border-gray-600{--border-opacity:1;border-color:#4c4f52;border-color:rgba(76,79,82,var(--border-opacity))}.theme-dark .dark\:border-gray-700{--border-opacity:1;border-color:#24262d;border-color:rgba(36,38,45,var(--border-opacity))}.theme-dark .dark\:border-gray-800{--border-opacity:1;border-color:#1a1c23;border-color:rgba(26,28,35,var(--border-opacity))}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}.rounded-full{border-radius:9999px}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.rounded-l-md{border-top-left-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem}.rounded-r-lg,.rounded-t-lg{border-top-right-radius:.5rem}.rounded-r-lg{border-bottom-right-radius:.5rem}.rounded-l-lg{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-tr-lg{border-top-right-radius:.5rem}.rounded-br-lg{border-bottom-right-radius:.5rem}.border-0{border-width:0}.border-2{border-width:2px}.border{border-width:1px}.border-r-0{border-right-width:0}.border-t{border-top-width:1px}.border-b{border-bottom-width:1px}.cursor-not-allowed{cursor:not-allowed}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.theme-dark .dark\:block{display:block}.theme-dark .dark\:hidden{display:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-12{height:3rem}.h-32{height:8rem}.h-full{height:100%}.h-screen{height:100vh}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.text-xl{font-size:1.25rem}.text-2xl{font-size:1.5rem}.text-6xl{font-size:4rem}.leading-5{line-height:1.25rem}.leading-none{line-height:1}.leading-tight{line-height:1.25}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.my-8{margin-top:2rem;margin-bottom:2rem}.mx-auto{margin-left:auto;margin-right:auto}.-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.mt-1{margin-top:.25rem}.mr-1{margin-right:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mr-3{margin-right:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mr-4{margin-right:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mr-5{margin-right:1.25rem}.mt-6{margin-top:1.5rem}.mr-6{margin-right:1.5rem}.mb-6{margin-bottom:1.5rem}.ml-6{margin-left:1.5rem}.mt-8{margin-top:2rem}.mb-8{margin-bottom:2rem}.mt-16{margin-top:4rem}.-mr-1{margin-right:-.25rem}.-ml-1{margin-left:-.25rem}.-mb-4{margin-bottom:-1rem}.max-h-0{max-height:0}.max-h-xl{max-height:36rem}.max-w-xl{max-width:36rem}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.min-h-screen{min-height:100vh}.min-w-0{min-width:0}.object-cover{-o-object-fit:cover;object-fit:cover}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-100{opacity:1}.focus\:outline-none:focus{outline:0}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pl-8{padding-left:2rem}.pr-10{padding-right:2.5rem}.pl-10{padding-left:2.5rem}.pb-16{padding-bottom:4rem}.pr-20{padding-right:5rem}.pl-20{padding-left:5rem}.placeholder-gray-600::-moz-placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.placeholder-gray-600:-ms-input-placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.placeholder-gray-600::-ms-input-placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.placeholder-gray-600::placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.focus\:placeholder-gray-500:focus::-moz-placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.focus\:placeholder-gray-500:focus:-ms-input-placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.focus\:placeholder-gray-500:focus::-ms-input-placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.focus\:placeholder-gray-500:focus::placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.theme-dark .dark\:placeholder-gray-500::-moz-placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.theme-dark .dark\:placeholder-gray-500:-ms-input-placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.theme-dark .dark\:placeholder-gray-500::-ms-input-placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.theme-dark .dark\:placeholder-gray-500::placeholder{--placeholder-opacity:1;color:#707275;color:rgba(112,114,117,var(--placeholder-opacity))}.theme-dark .dark\:focus\:placeholder-gray-600:focus::-moz-placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.theme-dark .dark\:focus\:placeholder-gray-600:focus:-ms-input-placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.theme-dark .dark\:focus\:placeholder-gray-600:focus::-ms-input-placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.theme-dark .dark\:focus\:placeholder-gray-600:focus::placeholder{--placeholder-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--placeholder-opacity))}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{right:0;left:0}.inset-0,.inset-y-0{top:0;bottom:0}.top-0{top:0}.right-0{right:0}.left-0{left:0}.shadow-xs{box-shadow:0 0 0 1px rgba(0,0,0,.05)}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.shadow-md{box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)}.shadow-xl{box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04)}.shadow-inner{box-shadow:inset 0 2px 4px 0 rgba(0,0,0,.06)}.fill-current{fill:currentColor}.text-left{text-align:left}.text-center{text-align:center}.text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.text-black{--text-opacity:1;color:#000;color:rgba(0,0,0,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#9e9e9e;color:rgba(158,158,158,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#707275;color:rgba(112,114,117,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#4c4f52;color:rgba(76,79,82,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#24262d;color:rgba(36,38,45,var(--text-opacity))}.text-gray-800{--text-opacity:1;color:#1a1c23;color:rgba(26,28,35,var(--text-opacity))}.text-red-600{--text-opacity:1;color:#e02424;color:rgba(224,36,36,var(--text-opacity))}.text-red-700{--text-opacity:1;color:#c81e1e;color:rgba(200,30,30,var(--text-opacity))}.text-orange-500{--text-opacity:1;color:#ff5a1f;color:rgba(255,90,31,var(--text-opacity))}.text-orange-700{--text-opacity:1;color:#b43403;color:rgba(180,52,3,var(--text-opacity))}.text-green-500{--text-opacity:1;color:#0e9f6e;color:rgba(14,159,110,var(--text-opacity))}.text-green-600{--text-opacity:1;color:#057a55;color:rgba(5,122,85,var(--text-opacity))}.text-green-700{--text-opacity:1;color:#046c4e;color:rgba(4,108,78,var(--text-opacity))}.text-teal-500{--text-opacity:1;color:#0694a2;color:rgba(6,148,162,var(--text-opacity))}.text-blue-500{--text-opacity:1;color:#3f83f8;color:rgba(63,131,248,var(--text-opacity))}.text-purple-100{--text-opacity:1;color:#edebfe;color:rgba(237,235,254,var(--text-opacity))}.text-purple-200{--text-opacity:1;color:#dcd7fe;color:rgba(220,215,254,var(--text-opacity))}.text-purple-600{--text-opacity:1;color:#7e3af2;color:rgba(126,58,242,var(--text-opacity))}.focus-within\:text-purple-500:focus-within{--text-opacity:1;color:#9061f9;color:rgba(144,97,249,var(--text-opacity))}.focus-within\:text-purple-600:focus-within{--text-opacity:1;color:#7e3af2;color:rgba(126,58,242,var(--text-opacity))}.hover\:text-gray-700:hover{--text-opacity:1;color:#24262d;color:rgba(36,38,45,var(--text-opacity))}.hover\:text-gray-800:hover{--text-opacity:1;color:#1a1c23;color:rgba(26,28,35,var(--text-opacity))}.active\:text-gray-500:active{--text-opacity:1;color:#707275;color:rgba(112,114,117,var(--text-opacity))}.theme-dark .dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.theme-dark .dark\:text-gray-100{--text-opacity:1;color:#f4f5f7;color:rgba(244,245,247,var(--text-opacity))}.theme-dark .dark\:text-gray-200{--text-opacity:1;color:#e5e7eb;color:rgba(229,231,235,var(--text-opacity))}.theme-dark .dark\:text-gray-300{--text-opacity:1;color:#d5d6d7;color:rgba(213,214,215,var(--text-opacity))}.theme-dark .dark\:text-gray-400{--text-opacity:1;color:#9e9e9e;color:rgba(158,158,158,var(--text-opacity))}.theme-dark .dark\:text-red-100{--text-opacity:1;color:#fde8e8;color:rgba(253,232,232,var(--text-opacity))}.theme-dark .dark\:text-red-400{--text-opacity:1;color:#f98080;color:rgba(249,128,128,var(--text-opacity))}.theme-dark .dark\:text-orange-100{--text-opacity:1;color:#feecdc;color:rgba(254,236,220,var(--text-opacity))}.theme-dark .dark\:text-green-100{--text-opacity:1;color:#def7ec;color:rgba(222,247,236,var(--text-opacity))}.theme-dark .dark\:text-green-400{--text-opacity:1;color:#31c48d;color:rgba(49,196,141,var(--text-opacity))}.theme-dark .dark\:text-teal-100{--text-opacity:1;color:#d5f5f6;color:rgba(213,245,246,var(--text-opacity))}.theme-dark .dark\:text-blue-100{--text-opacity:1;color:#e1effe;color:rgba(225,239,254,var(--text-opacity))}.theme-dark .dark\:text-purple-300{--text-opacity:1;color:#cabffd;color:rgba(202,191,253,var(--text-opacity))}.theme-dark .dark\:text-purple-400{--text-opacity:1;color:#ac94fa;color:rgba(172,148,250,var(--text-opacity))}.theme-dark .dark\:focus-within\:text-purple-400:focus-within{--text-opacity:1;color:#ac94fa;color:rgba(172,148,250,var(--text-opacity))}.theme-dark .dark\:hover\:text-gray-200:hover{--text-opacity:1;color:#e5e7eb;color:rgba(229,231,235,var(--text-opacity))}.uppercase{text-transform:uppercase}.hover\:underline:hover,.underline{text-decoration:underline}.tracking-wide{letter-spacing:.025em}.align-middle{vertical-align:middle}.whitespace-no-wrap{white-space:nowrap}.w-1{width:.25rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-12{width:3rem}.w-56{width:14rem}.w-64{width:16rem}.w-full{width:100%}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.gap-6{grid-gap:1.5rem;gap:1.5rem}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-4{grid-column:span 4/span 4}.transform{--transform-translate-x:0;--transform-translate-y:0;--transform-rotate:0;--transform-skew-x:0;--transform-skew-y:0;--transform-scale-x:1;--transform-scale-y:1;transform:translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y))}.translate-x-1{--transform-translate-x:0.25rem}.-translate-x-20{--transform-translate-x:-5rem}.-translate-y-1{--transform-translate-y:-0.25rem}.translate-y-1\/2{--transform-translate-y:50%}.transition-all{transition-property:all}.transition{transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform}.transition-colors{transition-property:background-color,border-color,color,fill,stroke}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.focus\:shadow-outline-gray:focus{box-shadow:0 0 0 3px rgba(213,214,215,.45)}.focus\:shadow-outline-red:focus{box-shadow:0 0 0 3px rgba(248,180,180,.45)}.focus\:shadow-outline-green:focus{box-shadow:0 0 0 3px rgba(132,225,188,.45)}.focus\:shadow-outline-purple:focus{box-shadow:0 0 0 3px rgba(202,191,253,.45)}.theme-dark .dark\:focus\:shadow-outline-gray:focus{box-shadow:0 0 0 3px rgba(213,214,215,.45)}@media (min-width:640px){.sm\:space-y-0>:not(template)~:not(template){--space-y-reverse:0;margin-top:calc(0px*(1 - var(--space-y-reverse)));margin-bottom:calc(0px*var(--space-y-reverse))}.sm\:space-x-6>:not(template)~:not(template){--space-x-reverse:0;margin-right:calc(1.5rem*var(--space-x-reverse));margin-left:calc(1.5rem*(1 - var(--space-x-reverse)))}.sm\:rounded-lg{border-radius:.5rem}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-center{justify-content:center}.sm\:m-4{margin:1rem}.sm\:mt-auto{margin-top:auto}.sm\:max-w-xl{max-width:36rem}.sm\:p-12{padding:3rem}.sm\:py-2{padding-top:.5rem;padding-bottom:.5rem}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:w-auto{width:auto}.sm\:grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}}@media (min-width:768px){.md\:space-x-4>:not(template)~:not(template){--space-x-reverse:0;margin-right:calc(1rem*var(--space-x-reverse));margin-left:calc(1rem*(1 - var(--space-x-reverse)))}.md\:block{display:block}.md\:hidden{display:none}.md\:flex-row{flex-direction:row}.md\:items-end{align-items:flex-end}.md\:h-auto{height:auto}.md\:w-1\/2{width:50%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:mr-32{margin-right:8rem}}@media (min-width:1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}} \ No newline at end of file diff --git a/static/vendor/img/forgot-password-office-dark.jpeg b/static/vendor/img/forgot-password-office-dark.jpeg new file mode 100644 index 00000000..cdd69e31 Binary files /dev/null and b/static/vendor/img/forgot-password-office-dark.jpeg differ diff --git a/static/vendor/img/forgot-password-office.jpeg b/static/vendor/img/forgot-password-office.jpeg new file mode 100644 index 00000000..4dcaf519 Binary files /dev/null and b/static/vendor/img/forgot-password-office.jpeg differ diff --git a/static/vendor/img/login-office-dark.jpeg b/static/vendor/img/login-office-dark.jpeg new file mode 100644 index 00000000..9dda0540 Binary files /dev/null and b/static/vendor/img/login-office-dark.jpeg differ diff --git a/static/vendor/img/login-office.jpeg b/static/vendor/img/login-office.jpeg new file mode 100644 index 00000000..6414d490 Binary files /dev/null and b/static/vendor/img/login-office.jpeg differ