Some checks failed
- Add Development URL Quick Reference section to url-routing overview with all login URLs, entry points, and full examples - Replace /shop/ path segments with /storefront/ across 50 docs files - Update file references: shop_pages.py → storefront_pages.py, templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/ - Preserve domain references (orion.shop) and /store/ staff dashboard paths - Archive docs left unchanged (historical) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1346 lines
48 KiB
Markdown
1346 lines
48 KiB
Markdown
╔══════════════════════════════════════════════════════════════════╗
|
|
║ STOREFRONT FRONTEND ARCHITECTURE OVERVIEW ║
|
|
║ Alpine.js + Jinja2 + Tailwind CSS + Multi-Theme ║
|
|
╚══════════════════════════════════════════════════════════════════╝
|
|
|
|
📦 WHAT IS THIS?
|
|
═════════════════════════════════════════════════════════════════
|
|
|
|
Customer-facing storefront 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/storefront/
|
|
│ ├── 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/storefront/
|
|
│ ├── css/
|
|
│ │ └── storefront.css ← ✅ Storefront-specific styles (IMPLEMENTED)
|
|
│ ├── js/
|
|
│ │ └── storefront-layout.js ← ✅ Base storefront 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/
|
|
└── storefront_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/storefront_pages.py
|
|
|
|
⚠️ ROUTE REGISTRATION (main.py):
|
|
The storefront router is mounted at TWO prefixes to support both access methods:
|
|
|
|
# main.py
|
|
app.include_router(storefront_pages.router, prefix="/storefront", ...) # Domain/subdomain
|
|
app.include_router(storefront_pages.router, prefix="/storefront/{store_code}", ...) # Path-based
|
|
|
|
This means routes defined WITHOUT /storefront prefix in storefront_pages.py:
|
|
@router.get("/products") → /storefront/products OR /storefront/{code}/products
|
|
|
|
❌ COMMON MISTAKE: Don't add /storefront prefix in route definitions!
|
|
@router.get("/storefront/products") ❌ WRONG - creates /storefront/storefront/products
|
|
@router.get("/products") ✅ CORRECT - creates /storefront/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 storefront_products_page(request: Request):
|
|
"""
|
|
Render storefront homepage / product catalog.
|
|
Store and theme are auto-injected by middleware.
|
|
"""
|
|
return templates.TemplateResponse(
|
|
"storefront/products.html",
|
|
get_storefront_context(request) # Helper function
|
|
)
|
|
|
|
Helper Function:
|
|
def get_storefront_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 storefront frontend supports THREE access methods:
|
|
|
|
1. **Custom Domain** (Production)
|
|
URL: https://customdomain.com/storefront/products
|
|
- Store has their own domain
|
|
- base_url = "/"
|
|
- Links: /storefront/products, /storefront/about, /storefront/contact
|
|
|
|
2. **Subdomain** (Production)
|
|
URL: https://orion.letzshop.com/storefront/products
|
|
- Store uses platform subdomain
|
|
- base_url = "/"
|
|
- Links: /storefront/products, /storefront/about, /storefront/contact
|
|
|
|
3. **Path-Based** (Development/Testing)
|
|
URL: http://localhost:8000/storefront/orion/products
|
|
- Store accessed via path prefix
|
|
- base_url = "/storefront/orion/"
|
|
- Links: /storefront/orion/products, /storefront/orion/about
|
|
|
|
⚠️ CRITICAL: All template links MUST use {{ base_url }}storefront/ prefix
|
|
|
|
Example:
|
|
❌ BAD: <a href="/products">Products</a>
|
|
❌ BAD: <a href="{{ base_url }}products">Products</a>
|
|
✅ GOOD: <a href="{{ base_url }}storefront/products">Products</a>
|
|
|
|
Note: The router is mounted at /storefront prefix in main.py, so all links need storefront/ after base_url
|
|
|
|
How It Works:
|
|
1. StoreContextMiddleware detects access method
|
|
2. Sets request.state.store_context with detection_method
|
|
3. get_storefront_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/storefront/
|
|
|
|
Template Hierarchy:
|
|
base.html (layout + theme injection)
|
|
↓
|
|
home.html (product grid)
|
|
↓
|
|
partials/product-card.html (components)
|
|
|
|
Example:
|
|
{% extends "storefront/base.html" %}
|
|
{% block title %}{{ store.name }}{% endblock %}
|
|
{% block alpine_data %}shopHome(){% endblock %}
|
|
{% block content %}
|
|
<div x-show="loading">Loading products...</div>
|
|
<div x-show="!loading" class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<template x-for="product in products" :key="product.id">
|
|
<!-- Product card -->
|
|
</template>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
Key Features:
|
|
✅ Theme CSS variables injection
|
|
✅ 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/storefront/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. storefront-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:
|
|
• storefront-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='storefront/js/storefront-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 (storefront-layout.js):
|
|
|
|
Provides shared functionality for all storefront pages:
|
|
|
|
function storefrontLayoutData() {
|
|
return {
|
|
// Theme state
|
|
dark: localStorage.getItem('storefront-theme') === 'dark',
|
|
|
|
// UI state
|
|
mobileMenuOpen: false,
|
|
searchOpen: false,
|
|
cartCount: 0,
|
|
|
|
// Cart state
|
|
cart: [],
|
|
|
|
init() {
|
|
shopLog.info('Storefront layout initializing...');
|
|
this.loadCart();
|
|
window.addEventListener('cart-updated', () => {
|
|
this.loadCart();
|
|
});
|
|
shopLog.info('Storefront 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('storefront-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/storefront/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/storefront/*.py
|
|
|
|
⭐ NEW API STRUCTURE (as of 2025-11-22):
|
|
All storefront endpoints use middleware-based store context.
|
|
NO store_id or store_code in URLs!
|
|
|
|
Example Endpoints:
|
|
GET /api/v1/storefront/products ← Product catalog
|
|
GET /api/v1/storefront/products/{id} ← Product details
|
|
GET /api/v1/storefront/products?search=... ← Search products
|
|
GET /api/v1/storefront/cart/{session_id} ← Get cart
|
|
POST /api/v1/storefront/cart/{session_id}/items ← Add to cart
|
|
PUT /api/v1/storefront/cart/{session_id}/items/{product_id} ← Update item
|
|
DELETE /api/v1/storefront/cart/{session_id}/items/{product_id} ← Remove item
|
|
POST /api/v1/storefront/orders ← Place order (auth required)
|
|
GET /api/v1/storefront/orders ← Order history (auth required)
|
|
POST /api/v1/storefront/auth/login ← Customer login
|
|
POST /api/v1/storefront/auth/register ← Customer registration
|
|
GET /api/v1/storefront/content-pages/navigation ← CMS navigation
|
|
GET /api/v1/storefront/content-pages/{slug} ← CMS page content
|
|
|
|
How Store Context Works:
|
|
1. Browser makes API call from storefront page (e.g., /storefront/orion/products)
|
|
2. Browser automatically sends Referer header: http://localhost:8000/storefront/orion/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 /storefront/acme/products)
|
|
2. Store Middleware → Identifies "ACME" store from domain/path
|
|
3. Theme Middleware → Loads ACME's theme config
|
|
4. FastAPI → Renders storefront/products.html
|
|
5. Browser → Receives HTML with theme CSS variables
|
|
6. Alpine.js → init() executes
|
|
7. JavaScript → GET /api/v1/storefront/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/storefront/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
|
|
<button style="background-color: var(--color-primary)">
|
|
Buy Now
|
|
</button>
|
|
|
|
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 storefront 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/storefront/{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:
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<!-- Product cards -->
|
|
</div>
|
|
|
|
Touch Optimization:
|
|
• Larger touch targets (min 44x44px)
|
|
• Swipeable carousels
|
|
• Touch-friendly buttons
|
|
• Tap to zoom images
|
|
|
|
|
|
🌙 DARK MODE
|
|
═════════════════════════════════════════════════════════════════
|
|
|
|
Implementation:
|
|
1. Alpine.js state: dark: boolean
|
|
2. HTML class binding: :class="{ 'dark': dark }"
|
|
3. Tailwind variants: dark:bg-gray-800
|
|
4. LocalStorage persistence
|
|
5. Store colors adapt to dark mode
|
|
|
|
Toggle Button:
|
|
<button @click="toggleTheme()">
|
|
<svg x-show="!dark"><!-- Sun icon --></svg>
|
|
<svg x-show="dark"><!-- Moon icon --></svg>
|
|
</button>
|
|
|
|
Dark Mode Colors:
|
|
• Background: dark:bg-gray-900
|
|
• Text: dark:text-gray-100
|
|
• Borders: dark:border-gray-700
|
|
• Cards: dark:bg-gray-800
|
|
|
|
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/storefront/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=/storefront)
|
|
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/storefront/account/login.html)
|
|
──────────────────────────────────────────────────────────────────
|
|
Route: /storefront/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/storefront/auth/login
|
|
// Store token in localStorage
|
|
// Redirect to account or return URL
|
|
}
|
|
}
|
|
}
|
|
|
|
API Endpoint:
|
|
POST /api/v1/storefront/auth/login
|
|
Body: { email_or_username, password }
|
|
Returns: { access_token, user }
|
|
|
|
✅ Register Page (app/templates/storefront/account/register.html)
|
|
──────────────────────────────────────────────────────────────────
|
|
Route: /storefront/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/storefront/auth/register
|
|
// Redirect to login?registered=true
|
|
}
|
|
}
|
|
}
|
|
|
|
API Endpoint:
|
|
POST /api/v1/storefront/auth/register
|
|
Body: { first_name, last_name, email, phone?, password, marketing_consent }
|
|
Returns: { message }
|
|
|
|
✅ Forgot Password Page (app/templates/storefront/account/forgot-password.html)
|
|
──────────────────────────────────────────────────────────────────
|
|
Route: /storefront/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 storefront
|
|
• Dark mode support
|
|
|
|
Alpine.js Component:
|
|
function forgotPassword() {
|
|
return {
|
|
email: '',
|
|
emailSent: false,
|
|
loading: false,
|
|
|
|
async handleSubmit() {
|
|
// POST /api/v1/storefront/auth/forgot-password
|
|
// Show success message
|
|
// emailSent = true
|
|
}
|
|
}
|
|
}
|
|
|
|
API Endpoint:
|
|
POST /api/v1/storefront/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 storefront 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/storefront/products');
|
|
|
|
// Add to cart
|
|
const response = await fetch('/api/v1/storefront/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/storefront/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
|
|
|
|
Storefront-Specific Logging:
|
|
shopLog.info('Product added to cart', product);
|
|
shopLog.error('Checkout failed', error);
|
|
shopLog.debug('Search query', { query, results });
|
|
|
|
Levels:
|
|
• INFO: User actions
|
|
• ERROR: Failures and exceptions
|
|
• DEBUG: Development debugging
|
|
• WARN: Warnings
|
|
|
|
|
|
🎭 ICONS
|
|
═════════════════════════════════════════════════════════════════
|
|
|
|
Location: app/static/shared/js/icons.js
|
|
|
|
Usage:
|
|
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
|
|
<span x-html="$icon('search', 'w-6 h-6 text-primary')"></span>
|
|
|
|
Storefront 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](../cdn-fallback-strategy.md)
|
|
|
|
|
|
📊 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/storefront/products?is_featured=true
|
|
|
|
/products
|
|
──────────────────────────────────────────────────────────────────
|
|
Purpose: Browse all products with filters
|
|
Components:
|
|
• Product grid
|
|
• Filter sidebar
|
|
• Sort dropdown
|
|
• Pagination
|
|
Data Sources:
|
|
• GET /api/v1/storefront/products?skip=0&limit=20
|
|
• GET /api/v1/storefront/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/storefront/products/{id}
|
|
• GET /api/v1/storefront/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/storefront/orders
|
|
• Stripe/PayPal integration
|
|
|
|
/search
|
|
──────────────────────────────────────────────────────────────────
|
|
Purpose: Search results page
|
|
Components:
|
|
• Search input
|
|
• Results grid
|
|
• Filter options
|
|
• Sort options
|
|
Data Sources:
|
|
• GET /api/v1/storefront/products?search=query
|
|
|
|
/category/{category_slug}
|
|
──────────────────────────────────────────────────────────────────
|
|
Purpose: Browse products by category
|
|
Components:
|
|
• Breadcrumbs
|
|
• Product grid
|
|
• Subcategories
|
|
• Filters
|
|
Data Sources:
|
|
• GET /api/v1/storefront/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/storefront/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 storefront-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 storefront 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/storefront/errors/404.html - Not Found
|
|
• app/templates/storefront/errors/400.html - Bad Request
|
|
• app/templates/storefront/errors/401.html - Unauthorized
|
|
• app/templates/storefront/errors/403.html - Forbidden
|
|
• app/templates/storefront/errors/422.html - Validation Error
|
|
• app/templates/storefront/errors/429.html - Rate Limited
|
|
• app/templates/storefront/errors/500.html - Server Error
|
|
• app/templates/storefront/errors/502.html - Bad Gateway
|
|
• app/templates/storefront/errors/base.html - Base error template
|
|
• app/templates/storefront/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 storefront 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 #}
|
|
<a href="/">Continue Shopping</a>
|
|
<a href="/products">View Products</a>
|
|
|
|
{# ✅ GOOD - Context-aware links #}
|
|
<a href="{{ base_url or '/' }}">Continue Shopping</a>
|
|
<a href="{{ base_url or '/' }}products">View Products</a>
|
|
|
|
How It Works:
|
|
|
|
1. Error occurs (404, 500, etc.)
|
|
2. Exception handler detects storefront 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/storefront/orion/ → base_url = "/storefront/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_STOREFRONT_ALPINE_PAGE_TEMPLATE.md
|
|
• Multi-Theme Guide: MULTI_THEME_STOREFRONT_GUIDE.md
|
|
• API Documentation: API_REFERENCE.md
|
|
• Database Schema: DATABASE_SCHEMA.md
|
|
|
|
|
|
══════════════════════════════════════════════════════════════════
|
|
STOREFRONT FRONTEND ARCHITECTURE
|
|
Theme-Driven, Customer-Focused, Brand-Consistent
|
|
══════════════════════════════════════════════════════════════════
|