# Storefront Frontend - Alpine.js/Jinja2 Page Template Guide ## 📋 Overview This guide provides complete templates for creating new customer-facing storefront pages using the established Alpine.js + Jinja2 + Multi-Theme architecture. Follow these patterns to ensure consistency across all store storefronts while maintaining unique branding. --- ## 🔐 Authentication Pages (Available) Three fully-implemented authentication pages are available for reference: - **Login** (`app/templates/storefront/account/login.html`) - Customer sign-in with email/password - **Register** (`app/templates/storefront/account/register.html`) - New customer account creation - **Forgot Password** (`app/templates/storefront/account/forgot-password.html`) - Password reset flow All authentication pages feature: - ✅ Tailwind CSS styling - ✅ Alpine.js interactivity - ✅ Theme integration (store colors, logos, fonts) - ✅ Dark mode support - ✅ Mobile responsive design - ✅ Form validation - ✅ Loading states - ✅ Error handling See the [Storefront Architecture Documentation](./architecture.md) (Authentication Pages section) for complete details. --- ## 🎯 Quick Reference ### File Structure for New Page ``` app/ ├── templates/storefront/ │ └── [page-name].html # Jinja2 template ├── static/storefront/js/ │ └── [page-name].js # Alpine.js component └── api/v1/storefront/ └── pages.py # Route registration ``` ### Checklist for New Page - [ ] Create Jinja2 template extending storefront/base.html - [ ] Create Alpine.js JavaScript component - [ ] Register route in pages.py - [ ] Test with multiple store themes - [ ] Test responsive design (mobile/tablet/desktop) - [ ] Test dark mode - [ ] Test cart integration (if applicable) - [ ] Verify theme CSS variables work - [ ] Check image optimization --- ## 📄 Template Structure ### 1. Jinja2 Template **File:** `app/templates/storefront/[page-name].html` ```jinja2 {# app/templates/storefront/[page-name].html #} {% extends "storefront/base.html" %} {# Page title for browser tab - includes store name #} {% block title %}[Page Name] - {{ store.name }}{% endblock %} {# Meta description for SEO #} {% block meta_description %}[Page description for SEO]{% endblock %} {# Alpine.js component name #} {% block alpine_data %}shop[PageName](){% endblock %} {# Page content #} {% block content %}

[Page Name]

Loading...

Error

No items found

Try adjusting your filters or check back later.

{% endblock %} {# Page-specific JavaScript #} {% block extra_scripts %} {% endblock %} ``` --- ### 2. Alpine.js Component **File:** `app/static/storefront/js/[page-name].js` ```javascript // static/storefront/js/[page-name].js /** * [Page Name] Component * Handles [describe functionality] */ const pageLog = { info: (...args) => console.info('🛍️ [PAGE]', ...args), warn: (...args) => console.warn('⚠️ [PAGE]', ...args), error: (...args) => console.error('❌ [PAGE]', ...args), debug: (...args) => console.log('🔍 [PAGE]', ...args) }; /** * Main Alpine.js component for [page name] */ function shop[PageName]() { return { // ───────────────────────────────────────────────────── // STATE // ───────────────────────────────────────────────────── loading: false, error: '', items: [], // Pagination pagination: { currentPage: 1, totalPages: 1, perPage: 12, total: 0 }, // Filters filters: { search: '', category: '', sortBy: 'created_at:desc' }, // Store info (from template) storeCode: '{{ store.code }}', // ───────────────────────────────────────────────────── // LIFECYCLE // ───────────────────────────────────────────────────── /** * Initialize component */ async init() { pageLog.info('[PageName] initializing...'); await this.loadData(); pageLog.info('[PageName] initialized'); }, // ───────────────────────────────────────────────────── // DATA LOADING // ───────────────────────────────────────────────────── /** * Load main data from API */ async loadData() { this.loading = true; this.error = ''; try { const params = new URLSearchParams({ page: this.pagination.currentPage, per_page: this.pagination.perPage, ...this.filters }); const response = await fetch( `/api/v1/storefront/${this.storeCode}/items?${params}` ); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); // Update state this.items = data.items || []; this.pagination.total = data.total || 0; this.pagination.totalPages = Math.ceil( this.pagination.total / this.pagination.perPage ); pageLog.info('Data loaded:', this.items.length, 'items'); } catch (error) { pageLog.error('Failed to load data:', error); this.error = error.message || 'Failed to load data'; } finally { this.loading = false; } }, /** * Refresh data */ async refresh() { pageLog.info('Refreshing data...'); this.error = ''; await this.loadData(); }, // ───────────────────────────────────────────────────── // FILTERS & SEARCH // ───────────────────────────────────────────────────── /** * Apply filters and reload data */ async applyFilters() { pageLog.debug('Applying filters:', this.filters); this.pagination.currentPage = 1; // Reset to first page await this.loadData(); }, /** * Reset filters to default */ async resetFilters() { this.filters = { search: '', category: '', sortBy: 'created_at:desc' }; await this.applyFilters(); }, // ───────────────────────────────────────────────────── // PAGINATION // ───────────────────────────────────────────────────── /** * Navigate to specific page */ async goToPage(page) { if (page < 1 || page > this.pagination.totalPages) return; this.pagination.currentPage = page; await this.loadData(); // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); }, /** * Get pagination range for display */ get paginationRange() { const current = this.pagination.currentPage; const total = this.pagination.totalPages; const range = []; // Show max 7 page numbers let start = Math.max(1, current - 3); let end = Math.min(total, start + 6); // Adjust start if we're near the end if (end - start < 6) { start = Math.max(1, end - 6); } for (let i = start; i <= end; i++) { range.push(i); } return range; }, // ───────────────────────────────────────────────────── // CART INTEGRATION // ───────────────────────────────────────────────────── /** * Add item to cart */ addToCart(item, quantity = 1) { pageLog.info('Adding to cart:', item.name); // Get cart from storefront layout const shopLayout = Alpine.store('shop') || window.storefrontLayoutData(); if (shopLayout && typeof shopLayout.addToCart === 'function') { shopLayout.addToCart(item, quantity); this.showToast(`${item.name} added to cart`, 'success'); } else { pageLog.error('Storefront layout not available'); } }, // ───────────────────────────────────────────────────── // UI HELPERS // ───────────────────────────────────────────────────── /** * Show toast notification */ showToast(message, type = 'info') { const shopLayout = Alpine.store('shop') || window.storefrontLayoutData(); if (shopLayout && typeof shopLayout.showToast === 'function') { shopLayout.showToast(message, type); } }, /** * Format price as currency */ formatPrice(price) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price); }, /** * Format date */ formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); }, /** * Truncate text */ truncate(text, length = 100) { if (!text) return ''; if (text.length <= length) return text; return text.substring(0, length) + '...'; } }; } // Make available globally window.shop[PageName] = shop[PageName]; pageLog.info('[PageName] module loaded'); ``` --- ### 3. Route Registration **File:** `app/api/v1/storefront/pages.py` ```python from fastapi import APIRouter, Request, Depends from sqlalchemy.orm import Session from app.core.database import get_db router = APIRouter() @router.get("/[page-route]") async def [page_name]_page( request: Request, db: Session = Depends(get_db) ): """ [Page Name] page Displays [description] """ # Store and theme come from middleware store = request.state.store theme = request.state.theme return templates.TemplateResponse( "storefront/[page-name].html", { "request": request, "store": store, "theme": theme, } ) ``` --- ## 🎨 Common Page Patterns ### Pattern 1: Product Grid Page (Homepage, Category) **Use for:** Homepage, category pages, search results ```javascript async init() { await this.loadProducts(); } async loadProducts() { this.loading = true; try { const response = await fetch( `/api/v1/storefront/${this.storeCode}/products?category=${this.category}` ); const data = await response.json(); this.products = data.products || []; } catch (error) { this.error = error.message; } finally { this.loading = false; } } ``` **Template:** ```html
``` --- ### Pattern 2: Product Detail Page **Use for:** Single product pages ```javascript async init() { const productId = this.getProductIdFromUrl(); await this.loadProduct(productId); await this.loadRelatedProducts(productId); } async loadProduct(id) { const product = await fetch( `/api/v1/storefront/${this.storeCode}/products/${id}` ).then(r => r.json()); this.product = product; this.selectedImage = product.images[0]; } addToCartWithQuantity() { const shopLayout = window.storefrontLayoutData(); shopLayout.addToCart(this.product, this.quantity); } ``` **Template:** ```html

``` --- ### Pattern 3: Cart Page **Use for:** Shopping cart ```javascript async init() { this.loadCart(); } loadCart() { const shopLayout = window.storefrontLayoutData(); this.cart = shopLayout.cart; this.calculateTotals(); } updateQuantity(productId, quantity) { const shopLayout = window.storefrontLayoutData(); shopLayout.updateCartItem(productId, quantity); this.loadCart(); } removeItem(productId) { const shopLayout = window.storefrontLayoutData(); shopLayout.removeFromCart(productId); this.loadCart(); } get subtotal() { return this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); } get shipping() { return this.subtotal > 50 ? 0 : 9.99; } get total() { return this.subtotal + this.shipping; } ``` --- ### Pattern 4: Search & Filter Page **Use for:** Search results, filtered product lists ```javascript filters: { search: '', category: '', minPrice: 0, maxPrice: 1000, sortBy: 'relevance', inStock: true }, async performSearch() { this.loading = true; try { const response = await fetch( `/api/v1/storefront/${this.storeCode}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.filters) } ); const data = await response.json(); this.results = data.results || []; } finally { this.loading = false; } } ``` --- ## 🔧 Best Practices ### 1. Theme Integration Always use CSS variables for store colors: ```html ``` ### 2. Cart Integration Always use the storefront layout's cart methods: ```javascript // ✅ GOOD: Uses storefront layout const shopLayout = window.storefrontLayoutData(); shopLayout.addToCart(product, quantity); // ❌ BAD: Direct localStorage manipulation localStorage.setItem('cart', JSON.stringify(cart)); ``` ### 3. Loading States Always show loading indicators: ```javascript this.loading = true; try { // ... async operation } finally { this.loading = false; // Always executes } ``` ### 4. Error Handling Always handle errors gracefully: ```javascript try { await this.loadData(); } catch (error) { console.error('Load failed:', error); this.error = 'Unable to load products. Please try again.'; // Don't throw - let UI handle gracefully } ``` ### 5. Responsive Images Use lazy loading and responsive images: ```html ``` ### 6. Dark Mode Support both light and dark modes: ```html
Content
``` ### 7. Accessibility Add proper ARIA labels and keyboard navigation: ```html ``` --- ## 📱 Responsive Design Checklist - [ ] Mobile (< 640px): Single column layout - [ ] Tablet (640px - 1024px): 2-3 column layout - [ ] Desktop (> 1024px): 4 column layout - [ ] Images scale properly on all devices - [ ] Touch targets are at least 44x44px - [ ] Text is readable without zooming - [ ] Navigation adapts to screen size - [ ] Modals are scrollable on small screens - [ ] Forms are easy to fill on mobile --- ## ✅ Testing Checklist ### Functionality - [ ] Page loads without errors - [ ] Data loads correctly - [ ] Loading state displays - [ ] Error state handles failures - [ ] Empty state shows when no data - [ ] Filters work correctly - [ ] Pagination works - [ ] Cart integration works ### Theme Integration - [ ] Store colors display correctly - [ ] Store logo displays - [ ] Custom fonts load - [ ] Custom CSS applies - [ ] Dark mode works with store colors ### Responsive Design - [ ] Mobile layout works - [ ] Tablet layout works - [ ] Desktop layout works - [ ] Images are responsive - [ ] Touch interactions work ### Performance - [ ] Page loads quickly - [ ] Images load progressively - [ ] No console errors - [ ] No memory leaks ### Accessibility - [ ] Keyboard navigation works - [ ] Screen reader compatible - [ ] Color contrast sufficient - [ ] ARIA labels present --- ## 🎯 Component Library ### Reusable Partials Create reusable components in `templates/storefront/partials/`: **product-card.html:** ```html

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

Filters

$0
``` --- ## 🚀 Quick Start Commands ```bash # Create new page files touch app/templates/storefront/new-page.html touch app/static/storefront/js/new-page.js # Copy templates cp template.html app/templates/storefront/new-page.html cp template.js app/static/storefront/js/new-page.js # Update placeholders: # - Replace [page-name] with actual name # - Replace [PageName] with PascalCase name # - Add route in pages.py # - Test with multiple store themes! ``` --- ## 📚 Additional Resources ### Theme System - **CSS Variables**: All store colors in `var(--color-name)` format - **Fonts**: `var(--font-heading)` and `var(--font-body)` - **Logo**: Available in both light and dark versions - **Custom CSS**: Store-specific styles automatically injected ### Storefront Layout Functions - `addToCart(product, quantity)`: Add item to cart - `showToast(message, type)`: Show notification - `formatPrice(amount)`: Format as currency - `formatDate(date)`: Format date string ### Icons Use the global icon helper: ```html ``` ### API Client Shared API wrapper for authenticated requests: ```javascript const data = await apiClient.get('/endpoint'); await apiClient.post('/endpoint', { data }); ``` --- This template provides a complete, theme-aware pattern for building storefront pages with consistent structure, store branding, cart integration, and excellent user experience across all devices.