diff --git a/app/templates/shop/base.html b/app/templates/shop/base.html index 9bb6ee62..008aa32e 100644 --- a/app/templates/shop/base.html +++ b/app/templates/shop/base.html @@ -37,20 +37,13 @@ {% endif %} - {# Tailwind CSS - uses CSS variables #} - + {# Tailwind CSS with local fallback #} + {# Base Shop Styles #} - {# Optional: Theme-specific stylesheet #} - {% if theme.theme_name != 'default' %} - - {% endif %} - - {# Alpine.js for interactivity #} - - {% block extra_head %}{% endblock %} @@ -64,7 +57,7 @@ {# Vendor Logo #}
- + {% if theme.branding.logo %} {# Show light logo in light mode, dark logo in dark mode #} - + Home - + Products - + About - + Contact @@ -113,7 +106,7 @@ {# Cart #} - + @@ -139,7 +132,7 @@ {# Account #} - + @@ -203,40 +196,101 @@ {% endif %}
- {# Quick Links #} -
-

Quick Links

-
-
+ {# Dynamic CMS Pages - Footer Navigation #} + {% if footer_pages %} + {# Split footer pages into two columns if there are many #} + {% set half = (footer_pages | length / 2) | round(0, 'ceil') | int %} + {% set col1_pages = footer_pages[:half] %} + {% set col2_pages = footer_pages[half:] %} - {# Customer Service #} -
-

Customer Service

- -
+ {# Column 1 #} +
+

Quick Links

+ +
+ + {# Column 2 #} + {% if col2_pages %} +
+

Information

+ +
+ {% endif %} + {% else %} + {# Fallback: Static links if no CMS pages configured #} +
+

Quick Links

+ +
+ +
+

Information

+ +
+ {% endif %} {# Copyright #}
-

© {{ now().year }} {{ vendor.name }}. All rights reserved.

+

© {{ vendor.name }}. All rights reserved.

- {# Base Shop JavaScript #} + {# JavaScript Loading Order (CRITICAL - must be in this order) #} + + {# 1. Log Configuration (must load first) #} + + + {# 2. Icon System #} + + + {# 3. Base Shop Layout (Alpine.js component - must load before Alpine) #} - {# Page-specific JavaScript #} + {# 4. Utilities #} + + + {# 5. API Client #} + + + {# 6. Alpine.js with CDN fallback (deferred - loads last) #} + + + {# 7. Page-specific JavaScript #} {% block extra_scripts %}{% endblock %} {# Toast notification container #} diff --git a/app/templates/shop/home.html b/app/templates/shop/home.html index 8e463635..b7041226 100644 --- a/app/templates/shop/home.html +++ b/app/templates/shop/home.html @@ -1,11 +1,153 @@ - - - - - - Shop homepage - - - <-- Shop homepage --> - - +{# app/templates/shop/home.html #} +{% extends "shop/base.html" %} + +{% block title %}Home{% endblock %} + +{# Alpine.js component - uses shopLayoutData() from shop-layout.js #} +{% block alpine_data %}shopLayoutData(){% endblock %} + +{% block content %} +
+ + {# Hero Section #} +
+

+ Welcome to {{ vendor.name }} +

+ {% if vendor.tagline %} +

+ {{ vendor.tagline }} +

+ {% endif %} + {% if vendor.description %} +

+ {{ vendor.description }} +

+ {% endif %} + + Shop Now + +
+ + {# Featured Categories (if you have categories) #} +
+

+ Shop by Category +

+
+ {# Placeholder categories - will be loaded via API in future #} +
+
🏠
+

Home & Living

+
+
+
👔
+

Fashion

+
+
+
📱
+

Electronics

+
+
+
🎨
+

Arts & Crafts

+
+
+
+ + {# Featured Products Section #} +
+
+

+ Featured Products +

+ + View All → + +
+ + {# Loading State #} +
+
+
+ + {# Products Grid #} +
+ {# Coming Soon Notice #} +
+
🛍️
+

+ Products Coming Soon +

+

+ We're setting up our shop. Check back soon for amazing products! +

+

+ For Developers: Products will be loaded dynamically from the API once you add them to the database. +

+
+
+
+ + {# Why Shop With Us Section #} +
+

+ Why Shop With Us +

+
+
+
🚚
+

Fast Shipping

+

+ Quick and reliable delivery to your doorstep +

+
+
+
🔒
+

Secure Payment

+

+ Your transactions are safe and encrypted +

+
+
+
💝
+

Quality Guarantee

+

+ 100% satisfaction guaranteed on all products +

+
+
+
+ +
+{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/app/templates/shop/products.html b/app/templates/shop/products.html index 99a44701..a2ee6474 100644 --- a/app/templates/shop/products.html +++ b/app/templates/shop/products.html @@ -1,459 +1,165 @@ - - - - - - Products - {{ vendor.name }} - - - - - -
- -
-
-

{{ vendor.name }} - Products

-
- -
+{# app/templates/shop/products.html #} +{% extends "shop/base.html" %} -
- -
- +{% block title %}Products{% endblock %} -
- -
+{# Alpine.js component #} +{% block alpine_data %}shopLayoutData(){% endblock %} -
- -
-
-

Loading products...

+ {# Category Filter #} +
+
- -
-
📦
-

No products found

-

Try adjusting your filters

-

Check back soon for new products!

+ {# Sort #} +
+
+
+
- -
- -
- - - - - - - - \ No newline at end of file +{% block extra_scripts %} + +{% endblock %} diff --git a/docs/frontend/shop/architecture.md b/docs/frontend/shop/architecture.md index 4f6ea802..b89156d5 100644 --- a/docs/frontend/shop/architecture.md +++ b/docs/frontend/shop/architecture.md @@ -47,51 +47,44 @@ e-commerce experience unique to each vendor. Built with: app/ ├── templates/shop/ -│ ├── base.html ← Base template (layout) -│ ├── home.html ← Homepage / product grid -│ ├── product-detail.html ← Single product page +│ ├── base.html ← ✅ Base template (layout + theme) +│ ├── home.html ← ✅ Homepage / featured products +│ ├── products.html ← ✅ Product catalog with filters +│ ├── product.html ← Product detail 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 +│ ├── account/ ← Customer account pages +│ │ ├── login.html +│ │ ├── register.html +│ │ ├── dashboard.html +│ │ ├── orders.html +│ │ ├── profile.html +│ │ └── addresses.html +│ └── errors/ ← Error pages +│ ├── 400.html +│ ├── 404.html +│ └── 500.html │ ├── static/shop/ │ ├── css/ -│ │ ├── shop.css ← Shop-specific styles -│ │ └── themes/ ← Optional theme stylesheets -│ │ ├── modern.css -│ │ ├── minimal.css -│ │ └── elegant.css +│ │ └── shop.css ← ✅ Shop-specific styles (IMPLEMENTED) │ ├── 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 +│ │ └── shop-layout.js ← ✅ Base shop functionality (IMPLEMENTED) │ └── img/ -│ ├── placeholder-product.png -│ └── empty-cart.svg +│ └── (placeholder images) │ -├── static/shared/ ← Shared across all areas +├── static/shared/ ← ✅ Shared across all areas (IMPLEMENTED) │ ├── js/ -│ │ ├── log-config.js ← Logging setup -│ │ ├── icons.js ← Icon registry -│ │ ├── utils.js ← Utility functions -│ │ └── api-client.js ← API wrapper +│ │ ├── log-config.js ← ✅ Logging setup +│ │ ├── icons.js ← ✅ Icon registry +│ │ ├── utils.js ← ✅ Utility functions +│ │ └── api-client.js ← ✅ API wrapper │ └── css/ -│ └── base.css ← Global styles +│ └── (shared styles if needed) │ -└── api/v1/shop/ - └── pages.py ← Route handlers +└── routes/ + └── shop_pages.py ← ✅ Route handlers (IMPLEMENTED) 🏗️ ARCHITECTURE LAYERS @@ -113,32 +106,96 @@ Layer 6: Database Layer 1: ROUTES (FastAPI) ────────────────────────────────────────────────────────────────── Purpose: Vendor Detection + Template Rendering -Location: app/api/v1/shop/pages.py +Location: app/routes/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 - + @router.get("/", response_class=HTMLResponse, include_in_schema=False) + @router.get("/products", response_class=HTMLResponse, include_in_schema=False) + async def shop_products_page(request: Request): + """ + Render shop homepage / product catalog. + Vendor and theme are auto-injected by middleware. + """ return templates.TemplateResponse( - "shop/home.html", - { - "request": request, - "vendor": vendor, - "theme": theme, - } + "shop/products.html", + get_shop_context(request) # Helper function ) +Helper Function: + def get_shop_context(request: Request, **extra_context) -> dict: + """Build template context with vendor/theme from middleware""" + vendor = getattr(request.state, 'vendor', None) + theme = getattr(request.state, 'theme', None) + clean_path = getattr(request.state, 'clean_path', request.url.path) + vendor_context = getattr(request.state, 'vendor_context', None) + + # Get detection method (domain, subdomain, or path) + access_method = vendor_context.get('detection_method', 'unknown') if vendor_context else 'unknown' + + # Calculate base URL for links + # - Domain/subdomain: base_url = "/" + # - Path-based: base_url = "/vendor/{vendor_code}/" + base_url = "/" + if access_method == "path" and vendor: + full_prefix = vendor_context.get('full_prefix', '/vendor/') + base_url = f"{full_prefix}{vendor.subdomain}/" + + return { + "request": request, + "vendor": vendor, + "theme": theme, + "clean_path": clean_path, + "access_method": access_method, + "base_url": base_url, # ⭐ Used for all links in templates + **extra_context + } + Responsibilities: - ✅ Access vendor from middleware - ✅ Access theme from middleware - ✅ Render template - ❌ NO database queries (data loaded client-side) - ❌ NO business logic + ✅ Access vendor from middleware (request.state.vendor) + ✅ Access theme from middleware (request.state.theme) + ✅ Calculate base_url for routing-aware links + ✅ Render template with context + ❌ NO database queries (data loaded client-side via API) + ❌ NO business logic (handled by API endpoints) + + +⭐ MULTI-ACCESS ROUTING (Domain, Subdomain, Path-Based) +────────────────────────────────────────────────────────────────── +The shop frontend supports THREE access methods: + +1. **Custom Domain** (Production) + URL: https://customdomain.com/products + - Vendor has their own domain + - base_url = "/" + - Links: /products, /about, /contact + +2. **Subdomain** (Production) + URL: https://wizamart.letzshop.com/products + - Vendor uses platform subdomain + - base_url = "/" + - Links: /products, /about, /contact + +3. **Path-Based** (Development/Testing) + URL: http://localhost:8000/vendor/wizamart/products + - Vendor accessed via path prefix + - base_url = "/vendor/wizamart/" + - Links: /vendor/wizamart/products, /vendor/wizamart/about + +⚠️ CRITICAL: All template links MUST use {{ base_url }} prefix + +Example: + ❌ BAD: Products + ✅ GOOD: Products + + ❌ BAD: Contact + ✅ GOOD: Contact + +How It Works: + 1. VendorContextMiddleware detects access method + 2. Sets request.state.vendor_context with detection_method + 3. get_shop_context() calculates base_url from detection_method + 4. Templates use {{ base_url }} for all internal links + 5. Links work correctly regardless of access method Layer 2: MIDDLEWARE @@ -147,12 +204,14 @@ Purpose: Vendor & Theme Identification Two middleware components work together: -1. Vendor Context Middleware - • Detects vendor from domain/subdomain +1. Vendor Context Middleware (middleware/vendor_context.py) + • Detects vendor from domain/subdomain/path • Sets request.state.vendor + • Sets request.state.vendor_context (includes detection_method) + • Sets request.state.clean_path (path without vendor prefix) • Returns 404 if vendor not found -2. Theme Context Middleware +2. Theme Context Middleware (middleware/theme_context.py) • Loads theme for detected vendor • Sets request.state.theme • Falls back to default theme @@ -160,6 +219,11 @@ Two middleware components work together: Order matters: vendor_context_middleware → theme_context_middleware +Detection Methods: + - custom_domain: Vendor has custom domain + - subdomain: Vendor uses platform subdomain + - path: Vendor accessed via /vendor/{code}/ or /vendors/{code}/ + Layer 3: TEMPLATES (Jinja2) ────────────────────────────────────────────────────────────────── @@ -199,38 +263,105 @@ Layer 4: JAVASCRIPT (Alpine.js) Purpose: Client-Side Interactivity + Cart + Search Location: app/static/shop/js/ -Example (shop-layout.js): +⚠️ CRITICAL: JavaScript Loading Order +────────────────────────────────────────────────────────────────── +Scripts MUST load in this exact order (see base.html): + + 1. log-config.js ← Logging system (loads first) + 2. icons.js ← Icon registry + 3. shop-layout.js ← Alpine component (before Alpine!) + 4. utils.js ← Utility functions + 5. api-client.js ← API wrapper + 6. Alpine.js (deferred) ← Loads last + 7. Page-specific JS ← Optional page scripts + +Why This Order Matters: + • shop-layout.js defines shopLayoutData() BEFORE Alpine initializes + • Alpine.js defers to ensure DOM is ready + • Shared utilities available to all scripts + • Icons and logging available immediately + +Example from base.html: + + + + + + + +Alpine.js Component (shop-layout.js): +────────────────────────────────────────────────────────────────── function shopLayoutData() { return { - dark: false, + // Theme state + dark: localStorage.getItem('shop-theme') === 'dark', + + // UI state + mobileMenuOpen: false, + searchOpen: false, cartCount: 0, + + // Cart state cart: [], - + init() { + shopLog.info('Shop layout initializing...'); this.loadCart(); - this.loadThemePreference(); + window.addEventListener('cart-updated', () => { + this.loadCart(); + }); + shopLog.info('Shop layout initialized'); }, - - addToCart(product, quantity) { - // Add to cart logic - this.cart.push({ ...product, quantity }); + + addToCart(product, quantity = 1) { + const existingIndex = this.cart.findIndex( + item => item.id === product.id + ); + + if (existingIndex !== -1) { + this.cart[existingIndex].quantity += quantity; + } else { + this.cart.push({ + id: product.id, + name: product.name, + price: product.price, + image: product.image, + quantity: quantity + }); + } + this.saveCart(); + this.showToast(`${product.name} added to cart`, 'success'); }, - + toggleTheme() { this.dark = !this.dark; - localStorage.setItem('shop-theme', + localStorage.setItem('shop-theme', this.dark ? 'dark' : 'light'); + shopLog.debug('Theme toggled:', this.dark ? 'dark' : 'light'); } }; } + // Make globally available + window.shopLayoutData = shopLayoutData; + +Template Usage: +────────────────────────────────────────────────────────────────── + {# In base.html #} + + + {# In page templates #} + {% block alpine_data %}shopLayoutData(){% endblock %} + Responsibilities: ✅ Load products from API ✅ Manage cart in localStorage ✅ Handle search and filters ✅ Update DOM reactively - ✅ Theme toggling + ✅ Theme toggling (light/dark) + ✅ Mobile menu management + ✅ Toast notifications Layer 5: API (REST) @@ -584,10 +715,11 @@ Optimization Techniques: • Images load when visible • Infinite scroll for large catalogs -5. CDN Assets - • Tailwind from CDN - • Alpine.js from CDN - • Vendor assets from CDN +5. CDN Assets with Fallback + • Tailwind CSS from CDN (fallback to local) + • Alpine.js from CDN (fallback to local) + • Works offline and in restricted networks + • See: [CDN Fallback Strategy](../cdn-fallback-strategy.md) 📊 PAGE-BY-PAGE BREAKDOWN diff --git a/static/shop/js/shop-layout.js b/static/shop/js/shop-layout.js index a16b5194..54d1ca6f 100644 --- a/static/shop/js/shop-layout.js +++ b/static/shop/js/shop-layout.js @@ -24,6 +24,7 @@ function shopLayoutData() { // UI state mobileMenuOpen: false, searchOpen: false, + loading: false, cartCount: 0, // Cart state