Files
orion/docs/frontend/storefront/architecture.md
Samir Boulahtit a6e6d9be8e
Some checks failed
CI / ruff (push) Successful in 11s
CI / pytest (push) Failing after 46m49s
CI / validate (push) Successful in 23s
CI / dependency-scanning (push) Successful in 30s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
refactor: rename shopLayoutData to storefrontLayoutData
Align Alpine.js base component naming with storefront terminology.
Updated across all storefront JS, templates, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:06:45 +01:00

47 KiB

╔══════════════════════════════════════════════════════════════════╗ ║ 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 store. Built with: Jinja2 Templates (server-side rendering) Alpine.js (client-side reactivity) Tailwind CSS (utility-first styling) Multi-Theme System (store branding) FastAPI (backend routes)

🎯 KEY PRINCIPLES ═════════════════════════════════════════════════════════════════

  1. Theme-First Design • Each store has unique colors, fonts, logos • CSS variables for dynamic theming • Custom CSS support per store • Dark mode with store 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 + 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 │ ├── account/ ← Customer account pages │ │ ├── login.html ← Customer login (IMPLEMENTED) │ │ ├── register.html ← Customer registration (IMPLEMENTED) │ │ ├── forgot-password.html ← Password reset (IMPLEMENTED) │ │ ├── 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 (IMPLEMENTED) │ ├── js/ │ │ └── shop-layout.js ← Base shop functionality (IMPLEMENTED) │ └── img/ │ └── (placeholder images) │ ├── 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 │ └── css/ │ └── (shared styles if needed) │ └── routes/ └── shop_pages.py ← Route handlers (IMPLEMENTED)

🏗️ ARCHITECTURE LAYERS ═════════════════════════════════════════════════════════════════

Layer 1: Routes (FastAPI) ↓ Layer 2: Middleware (Store + Theme Detection) ↓ Layer 3: Templates (Jinja2) ↓ Layer 4: JavaScript (Alpine.js) ↓ Layer 5: API (REST endpoints) ↓ Layer 6: Database

Layer 1: ROUTES (FastAPI) ────────────────────────────────────────────────────────────────── Purpose: Store Detection + Template Rendering Location: app/routes/shop_pages.py

⚠️ ROUTE REGISTRATION (main.py): The shop router is mounted at TWO prefixes to support both access methods:

main.py

app.include_router(shop_pages.router, prefix="/shop", ...) # Domain/subdomain app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop", ...) # Path-based

This means routes defined WITHOUT /shop prefix in shop_pages.py: @router.get("/products") → /shop/products OR /stores/{code}/shop/products

COMMON MISTAKE: Don't add /shop prefix in route definitions! @router.get("/shop/products") WRONG - creates /shop/shop/products @router.get("/products") CORRECT - creates /shop/products

Example Route Handler: @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. Store and theme are auto-injected by middleware. """ return templates.TemplateResponse( "shop/products.html", get_shop_context(request) # Helper function )

Helper Function: def get_shop_context(request: Request, **extra_context) -> dict: """Build template context with store/theme from middleware""" store = getattr(request.state, 'store', None) theme = getattr(request.state, 'theme', None) clean_path = getattr(request.state, 'clean_path', request.url.path) store_context = getattr(request.state, 'store_context', None)

  # Get detection method (domain, subdomain, or path)
  access_method = store_context.get('detection_method', 'unknown') if store_context else 'unknown'

  # Calculate base URL for links
  # - Domain/subdomain: base_url = "/"
  # - Path-based: base_url = "/store/{store_code}/"
  base_url = "/"
  if access_method == "path" and store:
      full_prefix = store_context.get('full_prefix', '/store/')
      base_url = f"{full_prefix}{store.subdomain}/"

  return {
      "request": request,
      "store": store,
      "theme": theme,
      "clean_path": clean_path,
      "access_method": access_method,
      "base_url": base_url,  # ⭐ Used for all links in templates
      **extra_context
  }

Responsibilities: Access store from middleware (request.state.store) 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/shop/products

    • Store has their own domain
    • base_url = "/"
    • Links: /shop/products, /shop/about, /shop/contact
  2. Subdomain (Production) URL: https://orion.letzshop.com/shop/products

    • Store uses platform subdomain
    • base_url = "/"
    • Links: /shop/products, /shop/about, /shop/contact
  3. Path-Based (Development/Testing) URL: http://localhost:8000/stores/orion/shop/products

    • Store accessed via path prefix
    • base_url = "/stores/orion/"
    • Links: /stores/orion/shop/products, /stores/orion/shop/about

⚠️ CRITICAL: All template links MUST use {{ base_url }}shop/ prefix

Example: BAD: Products BAD: Products GOOD: Products

Note: The router is mounted at /shop prefix in main.py, so all links need shop/ after base_url

How It Works:

  1. StoreContextMiddleware detects access method
  2. Sets request.state.store_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 ────────────────────────────────────────────────────────────────── Purpose: Store & Theme Identification

Two middleware components work together:

  1. Store Context Middleware (middleware/store_context.py) • Detects store from domain/subdomain/path • Sets request.state.store • Sets request.state.store_context (includes detection_method) • Sets request.state.clean_path (path without store prefix) • Returns 404 if store not found

  2. Theme Context Middleware (middleware/theme_context.py) • Loads theme for detected store • Sets request.state.theme • Falls back to default theme

Order matters: store_context_middleware → theme_context_middleware

Detection Methods:

  • custom_domain: Store has custom domain
  • subdomain: Store uses platform subdomain
  • path: Store accessed via /store/{code}/ or /stores/{code}/

Layer 3: TEMPLATES (Jinja2) ────────────────────────────────────────────────────────────────── Purpose: HTML Structure + Store 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 %}{{ store.name }}{% endblock %} {% block alpine_data %}shopHome(){% endblock %} {% block content %}

Loading products...
{% endblock %}

Key Features: Theme CSS variables injection Store 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/

⚠️ 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 storefrontLayoutData() 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:

<script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script> <script src="{{ url_for('static', path='shared/js/icons.js') }}"></script> <script src="{{ url_for('static', path='shop/js/shop-layout.js') }}"></script> <script src="{{ url_for('static', path='shared/js/utils.js') }}"></script> <script src="{{ url_for('static', path='shared/js/api-client.js') }}"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

Alpine.js Component Architecture: ──────────────────────────────────────────────────────────────────

BASE COMPONENT (shop-layout.js):

Provides shared functionality for all shop pages:

function storefrontLayoutData() { return { // 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();
          window.addEventListener('cart-updated', () => {
              this.loadCart();
          });
          shopLog.info('Shop layout initialized');
      },

      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',
              this.dark ? 'dark' : 'light');
          shopLog.debug('Theme toggled:', this.dark ? 'dark' : 'light');
      },

      showToast(message, type = 'info') {
          // Toast notification implementation
      }
  };

}

// Make globally available window.storefrontLayoutData = storefrontLayoutData;

PAGE-SPECIFIC COMPONENTS:

Each page extends storefrontLayoutData() for page-specific functionality:

// Example: products.html document.addEventListener('alpine:init', () => { Alpine.data('shopProducts', () => ({ ...storefrontLayoutData(), // Extend base component

      // Page-specific state
      products: [],
      loading: true,
      filters: { search: '', category: '' },

      // Override init to add page-specific initialization
      async init() {
          shopLog.info('Products page initializing...');
          this.loadCart();  // From storefrontLayoutData
          await this.loadProducts();  // Page-specific
      },

      // Page-specific methods
      async loadProducts() {
          const response = await fetch('/api/v1/shop/products');
          const data = await response.json();
          this.products = data.products;
          this.loading = false;
      }
  }));

});

Template Usage: ──────────────────────────────────────────────────────────────────

{# In base.html - uses block to allow override #}

<html x-data="{% block alpine_data %}storefrontLayoutData(){% endblock %}" x-bind:class="{ 'dark': dark }">

{# In products.html - overrides to use page-specific component #} {% block alpine_data %}shopProducts(){% endblock %}

{# In home.html - uses default base component #} {# No block override needed, inherits storefrontLayoutData() #}

COMPONENT HIERARCHY:

storefrontLayoutData() ← Base component (shared state & methods) ↓ shopProducts() ← Products page (extends base + products state) shopCart() ← Cart page (extends base + cart state) shopCheckout() ← Checkout page (extends base + order state) shopAccount() ← Account page (extends base + user state)

Benefits: Shared functionality (theme, cart, toasts) available on all pages Each page has its own state and methods DRY - base functionality defined once Flexible - pages can override init() or add new methods

Tradeoffs: ⚠️ One component per page (not multiple components) ⚠️ All page state is at root level ⚠️ Can't easily split page into independent sub-components

Best Practices:

  1. Always extend storefrontLayoutData() in page components
  2. Override init() if you need page-specific initialization
  3. Call parent methods when needed (this.loadCart(), this.showToast())
  4. Keep page-specific state in the page component
  5. Keep shared state in storefrontLayoutData()

Responsibilities: Load products from API Manage cart in localStorage Handle search and filters Update DOM reactively Theme toggling (light/dark) Mobile menu management Toast notifications

Layer 5: API (REST) ────────────────────────────────────────────────────────────────── Purpose: Product Data + Cart + Orders Location: app/api/v1/shop/*.py

NEW API STRUCTURE (as of 2025-11-22): All shop endpoints use middleware-based store context. NO store_id or store_code in URLs!

Example Endpoints: GET /api/v1/shop/products ← Product catalog GET /api/v1/shop/products/{id} ← Product details GET /api/v1/shop/products?search=... ← Search products GET /api/v1/shop/cart/{session_id} ← Get cart POST /api/v1/shop/cart/{session_id}/items ← Add to cart PUT /api/v1/shop/cart/{session_id}/items/{product_id} ← Update item DELETE /api/v1/shop/cart/{session_id}/items/{product_id} ← Remove item POST /api/v1/shop/orders ← Place order (auth required) GET /api/v1/shop/orders ← Order history (auth required) POST /api/v1/shop/auth/login ← Customer login POST /api/v1/shop/auth/register ← Customer registration GET /api/v1/shop/content-pages/navigation ← CMS navigation GET /api/v1/shop/content-pages/{slug} ← CMS page content

How Store Context Works:

  1. Browser makes API call from shop page (e.g., /stores/orion/shop/products)
  2. Browser automatically sends Referer header: http://localhost:8000/stores/orion/shop/products
  3. StoreContextMiddleware extracts store from Referer header
  4. Middleware sets request.state.store = <Store: orion>
  5. API endpoint accesses store: store = request.state.store
  6. No store_id needed in URL!

🔄 DATA FLOW ═════════════════════════════════════════════════════════════════

Page Load Flow: ──────────────────────────────────────────────────────────────────

  1. Customer → visits acme-shop.com (or /stores/acme/shop/products)
  2. Store Middleware → Identifies "ACME" store from domain/path
  3. Theme Middleware → Loads ACME's theme config
  4. FastAPI → Renders shop/products.html
  5. Browser → Receives HTML with theme CSS variables
  6. Alpine.js → init() executes
  7. JavaScript → GET /api/v1/shop/products (with Referer header)
  8. Middleware → Extracts store from Referer, injects into request.state
  9. API → Returns product list JSON for ACME store
  10. Alpine.js → Updates products array
  11. 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/orders (with Referer header)
  5. Middleware → Extracts store from Referer
  6. API → Creates order + payment intent for store
  7. Alpine.js → Redirects to payment
  8. Payment → Completes
  9. Redirect → /order/{order_id}/confirmation

🎨 MULTI-THEME SYSTEM ═════════════════════════════════════════════════════════════════

How Themes Work:

  1. Database Storage • Each store has a theme record • Stores colors, fonts, logos, layout prefs • Custom CSS per store

  2. CSS Variables Injection • base.html injects variables in <style> tag • Variables available throughout page • Example: :root { --color-primary: #6366f1; --color-secondary: #8b5cf6; --color-accent: #ec4899; --color-background: #ffffff; --color-text: #1f2937; --font-heading: Inter, sans-serif; --font-body: Inter, sans-serif; }

  3. Usage in Templates Buy Now

  4. Dark Mode • Store colors adjust for dark mode • Saved in localStorage • Applied via :class="{ 'dark': dark }" • Uses dark: variants in Tailwind

Theme Configuration Example: ────────────────────────────────────────────────────────────────── { "theme_name": "modern", "colors": { "primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899", "background": "#ffffff", "text": "#1f2937" }, "fonts": { "heading": "Inter, sans-serif", "body": "Inter, sans-serif" }, "branding": { "logo": "/media/stores/acme/logo.png", "logo_dark": "/media/stores/acme/logo-dark.png", "favicon": "/media/stores/acme/favicon.ico" }, "layout": { "header": "fixed", "style": "grid" }, "social_links": { "facebook": "https://facebook.com/acme", "instagram": "https://instagram.com/acme" }, "custom_css": ".product-card { border-radius: 12px; }" }

🛒 CART SYSTEM ═════════════════════════════════════════════════════════════════

Client-Side Cart Management:

Storage: localStorage Format: JSON array of cart items

Cart Item Structure: { "id": "product-123", "name": "Product Name", "price": 29.99, "quantity": 2, "image": "/media/products/image.jpg", "store_code": "ACME" }

Key Functions: • loadCart(): Load from localStorage • saveCart(): Persist to localStorage • addToCart(product, quantity): Add item • updateCartItem(id, quantity): Update quantity • removeFromCart(id): Remove item • clearCart(): Empty cart • cartTotal: Computed total price

Cart Persistence: • Survives page refresh • Shared across shop pages • Cleared on checkout completion • Synced across tabs (optional)

🔍 SEARCH & FILTERS ═════════════════════════════════════════════════════════════════

Search System:

  1. Search Modal • Overlay with input • Live search as you type • Keyboard shortcuts (Cmd+K)

  2. Search API POST /api/v1/shop/{store_code}/search { "query": "laptop", "category": "electronics", "min_price": 100, "max_price": 1000, "sort": "price:asc" }

  3. Client-Side Filtering • Filter products array • No API call needed for basic filters • Use for in-memory datasets

Filter Components: • Category dropdown • Price range slider • Sort options • Availability toggle • Brand checkboxes

📱 RESPONSIVE DESIGN ═════════════════════════════════════════════════════════════════

Breakpoints (Tailwind): • sm: 640px (mobile landscape) • md: 768px (tablet) • lg: 1024px (desktop) • xl: 1280px (large desktop)

Product Grid Responsive: • Mobile: 1 column • Tablet: 2 columns • Desktop: 4 columns

Example:

Touch Optimization: • Larger touch targets (min 44x44px) • Swipeable carousels • Touch-friendly buttons • Tap to zoom images

🌙 DARK MODE ═════════════════════════════════════════════════════════════════

Implementation:

  1. Alpine.js state: dark: boolean
  2. HTML class binding: :class="{ 'dark': dark }"
  3. Tailwind variants: dark:bg-gray-800
  4. LocalStorage persistence
  5. Store colors adapt to dark mode

Toggle Button: <button @click="toggleTheme()">

Dark Mode Colors: • Background: dark:bg-gray-900 • Text: dark:text-gray-100 • Borders: dark:border-gray-700 • Cards: dark:bg-gray-800

Store Colors: • Primary color adjusts brightness • Maintains brand identity • Readable in both modes

🔐 CUSTOMER AUTHENTICATION ═════════════════════════════════════════════════════════════════

Optional Auth System:

Guest Checkout: No account required Email for order updates Quick checkout

Account Features: Order history Saved addresses Wishlist Profile management

Auth Flow:

  1. Login/Register → POST /api/v1/shop/auth/login (with Referer header)
  2. Middleware → Extracts store from Referer
  3. API → Validates credentials for store's customers
  4. API → Returns JWT token + sets cookie (path=/shop)
  5. JavaScript → Store token in localStorage
  6. API Client → Add token to authenticated requests
  7. Optional → Use account features (orders, profile, etc.)

Authentication Pages

Added: 2025-11-24

All authentication pages use Tailwind CSS, Alpine.js, and theme integration for a consistent, branded experience across all stores.

Login Page (app/templates/shop/account/login.html) ────────────────────────────────────────────────────────────────── Route: /shop/account/login

Features: • Two-column layout (branding + form) • Email and password fields with validation • Password visibility toggle • "Remember me" checkbox • Error and success message alerts • Loading states with animated spinner • Links to register and forgot password • Theme-aware colors from CSS variables • Dark mode support • Mobile responsive design

Alpine.js Component: function customerLogin() { return { credentials: { email: '', password: '' }, rememberMe: false, showPassword: false, loading: false, errors: {}, alert: { show: false, type: 'error', message: '' }, dark: false,

      async handleLogin() {
          // POST /api/v1/shop/auth/login
          // Store token in localStorage
          // Redirect to account or return URL
      }
  }

}

API Endpoint: POST /api/v1/shop/auth/login Body: { email_or_username, password } Returns: { access_token, user }

Register Page (app/templates/shop/account/register.html) ────────────────────────────────────────────────────────────────── Route: /shop/account/register

Features: • Two-column layout with store branding • First name, last name, email fields • Phone number (optional) • Password with strength requirements • Confirm password field • Marketing consent checkbox • Real-time client-side validation • Password visibility toggle • Theme-aware styling • Loading states • Redirects to login after success

Validation Rules: • First name: required • Last name: required • Email: required, valid format • Password: min 8 chars, 1 letter, 1 number • Confirm password: must match password

Alpine.js Component: function customerRegistration() { return { formData: { first_name: '', last_name: '', email: '', phone: '', password: '', marketing_consent: false }, confirmPassword: '',

      validateForm() {
          // Validates all fields
          // Returns true if valid
      },

      async handleRegister() {
          // POST /api/v1/shop/auth/register
          // Redirect to login?registered=true
      }
  }

}

API Endpoint: POST /api/v1/shop/auth/register Body: { first_name, last_name, email, phone?, password, marketing_consent } Returns: { message }

Forgot Password Page (app/templates/shop/account/forgot-password.html) ────────────────────────────────────────────────────────────────── Route: /shop/account/forgot-password

Features: • Two-column layout with store branding • Email input field • Two-state interface: 1. Form submission state 2. Success confirmation state • Success state with checkmark icon • Option to retry if email not received • Theme-aware styling • Links back to login and shop • Dark mode support

Alpine.js Component: function forgotPassword() { return { email: '', emailSent: false, loading: false,

      async handleSubmit() {
          // POST /api/v1/shop/auth/forgot-password
          // Show success message
          // emailSent = true
      }
  }

}

API Endpoint: POST /api/v1/shop/auth/forgot-password Body: { email } Returns: { message }

🎨 THEME INTEGRATION ──────────────────────────────────────────────────────────────────

All authentication pages inject store theme CSS variables:

<style id="store-theme-variables"> :root { {% for key, value in theme.css_variables.items() %} {{ key }}: {{ value }}; {% endfor %} } /* Theme-aware button and focus colors */ .btn-primary-theme { background-color: var(--color-primary); } .btn-primary-theme:hover:not(:disabled) { background-color: var(--color-primary-dark, var(--color-primary)); filter: brightness(0.9); } .focus-primary:focus { border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb, 124, 58, 237), 0.1); } </style>

Key Theme Elements: • Left panel background: var(--color-primary) • Submit buttons: var(--color-primary) • Links: var(--color-primary) • Checkboxes: var(--color-primary) • Focus states: var(--color-primary) with transparency • Store logo from theme.branding.logo

Benefits: Each store's auth pages match their brand Consistent with main shop design Dark mode adapts to store colors Professional, polished appearance

📱 RESPONSIVE DESIGN ──────────────────────────────────────────────────────────────────

Mobile (<640px): • Vertical layout (image on top, form below) • Smaller padding and spacing • Full-width buttons • Touch-friendly input fields

Tablet (640px-1024px): • Side-by-side layout begins • Balanced column widths • Comfortable spacing

Desktop (>1024px): • Full two-column layout • Max width container (max-w-4xl) • Centered on page • Larger brand imagery

🔒 SECURITY FEATURES ──────────────────────────────────────────────────────────────────

Client-Side: • Input validation before submission • Password visibility toggle • HTTPS required • No sensitive data in URLs • Token stored in localStorage (not cookies)

Server-Side (API handles): • Password hashing (bcrypt) • Email verification • Rate limiting • CSRF protection • SQL injection prevention

📡 API CLIENT ═════════════════════════════════════════════════════════════════

Location: app/static/shared/js/api-client.js

NEW USAGE (as of 2025-11-22): No store_code needed! Store extracted from Referer header automatically.

Usage: // Product catalog const products = await fetch('/api/v1/shop/products');

// Add to cart const response = await fetch('/api/v1/shop/cart/session123/items', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ product_id: 1, quantity: 2 }) });

// Place order const order = await fetch('/api/v1/shop/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) });

Features: Automatic error handling JSON parsing Loading states Auth token injection (if logged in)

🐛 LOGGING ═════════════════════════════════════════════════════════════════

Location: app/static/shared/js/log-config.js

Shop-Specific Logging: shopLog.info('Product added to cart', product); shopLog.error('Checkout failed', error); shopLog.debug('Search query', { query, results });

Levels: • INFO: User actions • ERROR: Failures and exceptions • DEBUG: Development debugging • WARN: Warnings

🎭 ICONS ═════════════════════════════════════════════════════════════════

Location: app/static/shared/js/icons.js

Usage:

Shop Icons: • shopping-cart, shopping-bag • heart (wishlist) • search, filter • eye (view) • star (rating) • truck (shipping) • check (success) • x (close) • chevron-left, chevron-right • spinner (loading)

🚀 PERFORMANCE ═════════════════════════════════════════════════════════════════

Optimization Techniques:

  1. Image Optimization • WebP format • Lazy loading • Responsive images (srcset) • CDN delivery

  2. Code Splitting • Base layout loads first • Page-specific JS loads after • Deferred non-critical CSS

  3. Caching • Browser cache for assets • LocalStorage for cart • Service worker (optional)

  4. Lazy Loading • Products load as you scroll • Images load when visible • Infinite scroll for large catalogs

  5. CDN Assets 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

📊 PAGE-BY-PAGE BREAKDOWN ═════════════════════════════════════════════════════════════════

/ (Homepage) ────────────────────────────────────────────────────────────────── Purpose: Display featured products, hero section Components: • Hero banner with CTA • Featured products grid • Category cards • About store section Data Sources: • GET /api/v1/shop/products?is_featured=true

/products ────────────────────────────────────────────────────────────────── Purpose: Browse all products with filters Components: • Product grid • Filter sidebar • Sort dropdown • Pagination Data Sources: • GET /api/v1/shop/products?skip=0&limit=20 • GET /api/v1/shop/products?search=query • Filters applied client-side or server-side

/products/{product_id} ────────────────────────────────────────────────────────────────── Purpose: Single product detail page Components: • Image gallery • Product info • Add to cart form • Related products • Reviews (optional) Data Sources: • GET /api/v1/shop/products/{id} • GET /api/v1/shop/products?limit=4 (related products)

/cart ────────────────────────────────────────────────────────────────── Purpose: Review cart contents before checkout Components: • Cart items list • Quantity adjusters • Remove buttons • Cart total • Checkout button Data Sources: • LocalStorage cart • No API call needed

/checkout ────────────────────────────────────────────────────────────────── Purpose: Complete purchase Components: • Shipping form • Payment form • Order summary • Submit button Data Sources: • POST /api/v1/shop/orders • Stripe/PayPal integration

/search ────────────────────────────────────────────────────────────────── Purpose: Search results page Components: • Search input • Results grid • Filter options • Sort options Data Sources: • GET /api/v1/shop/products?search=query

/category/{category_slug} ────────────────────────────────────────────────────────────────── Purpose: Browse products by category Components: • Breadcrumbs • Product grid • Subcategories • Filters Data Sources: • GET /api/v1/shop/products?category={slug}

/about ────────────────────────────────────────────────────────────────── Purpose: About the store Components: • Store story • Team photos • Values/mission • Contact info Data Sources: • Store info from middleware • Static content

/contact ────────────────────────────────────────────────────────────────── Purpose: Contact form Components: • Contact form • Map (optional) • Business hours • Social links Data Sources: • CMS content page (GET /api/v1/shop/content-pages/contact) • Form submission to store email

🎓 LEARNING PATH ═════════════════════════════════════════════════════════════════

For New Developers:

  1. Understand Architecture (1 hour) → Read this document → Review file structure → Examine base template → Understand theme system

  2. Study Existing Page (2 hours) → Open home.html → Open shop-layout.js → Trace product loading flow → Examine cart management

  3. Create Simple Page (4 hours) → Copy templates → Modify for new feature → Test with different store themes → Verify responsive design

  4. Add Complex Feature (1 day) → Product filters → Cart operations → API integration → Search functionality

  5. Master Patterns (1 week) → All common patterns → Theme customization → Performance optimization → Mobile responsiveness

🔄 DEPLOYMENT CHECKLIST ═════════════════════════════════════════════════════════════════

Before Deploying: □ Build Tailwind CSS □ Minify JavaScript □ Test all routes □ Test with multiple store themes □ Verify cart persistence □ Check mobile responsive □ Test dark mode □ Validate product display □ Test checkout flow □ Check image optimization □ Test search functionality □ Verify social links □ Test across browsers □ Check console for errors □ Test in production mode

🚨 ERROR HANDLING & ERROR PAGES ═════════════════════════════════════════════════════════════════

Multi-Access Aware Error Pages:

All shop error pages (404, 500, etc.) are store-context aware and display correct links based on the access method (domain, subdomain, or path-based).

Error Page Templates: • app/templates/shop/errors/404.html - Not Found • app/templates/shop/errors/400.html - Bad Request • app/templates/shop/errors/401.html - Unauthorized • app/templates/shop/errors/403.html - Forbidden • app/templates/shop/errors/422.html - Validation Error • app/templates/shop/errors/429.html - Rate Limited • app/templates/shop/errors/500.html - Server Error • app/templates/shop/errors/502.html - Bad Gateway • app/templates/shop/errors/base.html - Base error template • app/templates/shop/errors/generic.html - Generic error

Error Renderer (app/exceptions/error_renderer.py):

Calculates base_url dynamically based on store access method:

def _get_context_data(self, request: Request, ...): store = getattr(request.state, 'store', None) access_method = getattr(request.state, "access_method", None) store_context = getattr(request.state, "store_context", None)

  # Calculate base_url for shop links
  base_url = "/"
  if access_method == "path" and store:
      full_prefix = store_context.get('full_prefix', '/store/')
      base_url = f"{full_prefix}{store.subdomain}/"

  return {
      "request": request,
      "store": store,
      "base_url": base_url,  # ⭐ Used in error templates
      ...
  }

Error Template Usage:

All error page links use {{ base_url }} prefix for correct routing:

{# BAD - Hardcoded links #} Continue Shopping View Products

{# GOOD - Context-aware links #} Continue Shopping View Products

How It Works:

  1. Error occurs (404, 500, etc.)
  2. Exception handler detects shop context
  3. error_renderer.py calculates base_url from store_context
  4. Error template renders with correct base_url
  5. Links work for all access methods:
    • Domain: customshop.com → base_url = "/"
    • Subdomain: orion.platform.com → base_url = "/"
    • Path: localhost/stores/orion/ → base_url = "/stores/orion/"

Benefits: Error pages work correctly regardless of access method No broken links in error states Consistent user experience Store branding maintained in errors

🔒 SECURITY ═════════════════════════════════════════════════════════════════

Best Practices:

  1. Input Validation Validate all form inputs Sanitize user content XSS prevention

  2. Cart Security Validate cart on server Check stock availability Verify prices server-side Never trust client cart

  3. Payment Security Use trusted payment providers Never store card details HTTPS only PCI compliance

  4. Rate Limiting Search endpoint throttling Contact form limits Checkout attempt limits

📚 REFERENCE LINKS ═════════════════════════════════════════════════════════════════

Documentation: • Alpine.js: https://alpinejs.dev/ • Tailwind CSS: https://tailwindcss.com/ • Jinja2: https://jinja.palletsprojects.com/ • FastAPI: https://fastapi.tiangolo.com/

Internal Docs: • Page Template Guide: FRONTEND_SHOP_ALPINE_PAGE_TEMPLATE.md • Multi-Theme Guide: MULTI_THEME_SHOP_GUIDE.md • API Documentation: API_REFERENCE.md • Database Schema: DATABASE_SCHEMA.md

══════════════════════════════════════════════════════════════════ SHOP FRONTEND ARCHITECTURE Theme-Driven, Customer-Focused, Brand-Consistent ══════════════════════════════════════════════════════════════════