# Multi-Theme Shop System - Complete Implementation Guide ## 🎨 Overview This guide explains how to implement vendor-specific themes in your FastAPI multi-tenant e-commerce platform, allowing each vendor to have their own unique shop design, colors, branding, and layout. ## What You're Building **Before:** - All vendor shops look the same - Same colors, fonts, layouts - Only vendor name changes **After:** - Each vendor has unique theme - Custom colors, fonts, logos - Different layouts per vendor - Vendor-specific branding - CSS customization support ## Architecture Overview ``` Request → Vendor Middleware → Theme Middleware → Template Rendering ↓ ↓ ↓ Sets vendor Loads theme Applies styles in request config for and branding state vendor ``` ### Data Flow ``` 1. Customer visits: customdomain1.com 2. Vendor middleware: Identifies Vendor 1 3. Theme middleware: Loads Vendor 1's theme 4. Template receives: - vendor: Vendor 1 object - theme: Vendor 1 theme config 5. Template renders with: - Vendor 1 colors - Vendor 1 logo - Vendor 1 layout preferences - Vendor 1 custom CSS ``` ## Implementation Steps ### Step 1: Add Theme Database Table Create the `vendor_themes` table: ```sql CREATE TABLE vendor_themes ( id SERIAL PRIMARY KEY, vendor_id INTEGER UNIQUE NOT NULL REFERENCES vendors(id) ON DELETE CASCADE, theme_name VARCHAR(100) DEFAULT 'default', is_active BOOLEAN DEFAULT TRUE, -- Colors (JSON) colors JSONB DEFAULT '{ "primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899", "background": "#ffffff", "text": "#1f2937", "border": "#e5e7eb" }'::jsonb, -- Typography font_family_heading VARCHAR(100) DEFAULT 'Inter, sans-serif', font_family_body VARCHAR(100) DEFAULT 'Inter, sans-serif', -- Branding logo_url VARCHAR(500), logo_dark_url VARCHAR(500), favicon_url VARCHAR(500), banner_url VARCHAR(500), -- Layout layout_style VARCHAR(50) DEFAULT 'grid', header_style VARCHAR(50) DEFAULT 'fixed', product_card_style VARCHAR(50) DEFAULT 'modern', -- Customization custom_css TEXT, social_links JSONB DEFAULT '{}'::jsonb, -- Meta meta_title_template VARCHAR(200), meta_description TEXT, -- Timestamps created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_vendor_themes_vendor_id ON vendor_themes(vendor_id); CREATE INDEX idx_vendor_themes_active ON vendor_themes(vendor_id, is_active); ``` ### Step 2: Create VendorTheme Model File: `models/database/vendor_theme.py` See the complete model in `/home/claude/vendor_theme_model.py` **Key features:** - JSON fields for flexible color schemes - Brand asset URLs (logo, favicon, banner) - Layout preferences - Custom CSS support - CSS variables generator - to_dict() for template rendering ### Step 3: Update Vendor Model Add theme relationship to `models/database/vendor.py`: ```python from sqlalchemy.orm import relationship class Vendor(Base): # ... existing fields ... # Add theme relationship theme = relationship( "VendorTheme", back_populates="vendor", uselist=False, # One-to-one relationship cascade="all, delete-orphan" ) @property def active_theme(self): """Get vendor's active theme or return None""" if self.theme and self.theme.is_active: return self.theme return None ``` ### Step 4: Create Theme Context Middleware File: `middleware/theme_context.py` See complete middleware in `/home/claude/theme_context_middleware.py` **What it does:** 1. Runs AFTER vendor_context_middleware 2. Loads theme for detected vendor 3. Injects theme into request.state 4. Falls back to default theme if needed **Add to main.py:** ```python from middleware.theme_context import theme_context_middleware # AFTER vendor_context_middleware app.middleware("http")(theme_context_middleware) ``` ### Step 5: Create Shop Base Template File: `app/templates/shop/base.html` See complete template in `/home/claude/shop_base_template.html` **Key features:** - Injects CSS variables from theme - Vendor-specific logo (light/dark mode) - Theme-aware header/footer - Social links from theme config - Custom CSS injection - Dynamic favicon - SEO meta tags **Template receives:** ```python { "vendor": vendor_object, # From vendor middleware "theme": theme_dict, # From theme middleware } ``` ### Step 6: Create Shop Layout JavaScript File: `static/shop/js/shop-layout.js` See complete code in `/home/claude/shop_layout.js` **Provides:** - Theme toggling (light/dark) - Cart management - Mobile menu - Search overlay - Toast notifications - Price formatting - Date formatting ### Step 7: Update Route Handlers Ensure theme is passed to templates: ```python from middleware.theme_context import get_current_theme @router.get("/") async def shop_home(request: Request, db: Session = Depends(get_db)): vendor = request.state.vendor theme = get_current_theme(request) # or request.state.theme # Get products for vendor products = db.query(Product).filter( Product.vendor_id == vendor.id, Product.is_active == True ).all() return templates.TemplateResponse("shop/home.html", { "request": request, "vendor": vendor, "theme": theme, "products": products }) ``` **Note:** If middleware is set up correctly, theme is already in `request.state.theme`, so you may not need to explicitly pass it! ## How Themes Work ### CSS Variables System Each theme generates CSS custom properties: ```css :root { --color-primary: #6366f1; --color-secondary: #8b5cf6; --color-accent: #ec4899; --color-background: #ffffff; --color-text: #1f2937; --color-border: #e5e7eb; --font-heading: Inter, sans-serif; --font-body: Inter, sans-serif; } ``` **Usage in HTML/CSS:** ```html

Welcome

``` ```css /* In stylesheets */ .btn-primary { background-color: var(--color-primary); color: var(--color-background); } .heading { font-family: var(--font-heading); color: var(--color-text); } ``` ### Theme Configuration Example ```python # Example theme for "Modern Electronics Store" theme = { "theme_name": "tech-modern", "colors": { "primary": "#2563eb", # Blue "secondary": "#0ea5e9", # Sky Blue "accent": "#f59e0b", # Amber "background": "#ffffff", "text": "#111827", "border": "#e5e7eb" }, "fonts": { "heading": "Roboto, sans-serif", "body": "Open Sans, sans-serif" }, "branding": { "logo": "/media/vendors/tech-store/logo.png", "logo_dark": "/media/vendors/tech-store/logo-dark.png", "favicon": "/media/vendors/tech-store/favicon.ico", "banner": "/media/vendors/tech-store/banner.jpg" }, "layout": { "style": "grid", "header": "fixed", "product_card": "modern" }, "social_links": { "facebook": "https://facebook.com/techstore", "instagram": "https://instagram.com/techstore", "twitter": "https://twitter.com/techstore" }, "custom_css": """ .product-card { border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .product-card:hover { transform: translateY(-4px); box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); } """ } ``` ## Creating Theme Presets You can create predefined theme templates: ```python # app/core/theme_presets.py THEME_PRESETS = { "modern": { "colors": { "primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899", }, "fonts": { "heading": "Inter, sans-serif", "body": "Inter, sans-serif" }, "layout": { "style": "grid", "header": "fixed" } }, "classic": { "colors": { "primary": "#1e40af", "secondary": "#7c3aed", "accent": "#dc2626", }, "fonts": { "heading": "Georgia, serif", "body": "Arial, sans-serif" }, "layout": { "style": "list", "header": "static" } }, "minimal": { "colors": { "primary": "#000000", "secondary": "#404040", "accent": "#666666", }, "fonts": { "heading": "Helvetica, sans-serif", "body": "Helvetica, sans-serif" }, "layout": { "style": "grid", "header": "transparent" } }, "vibrant": { "colors": { "primary": "#f59e0b", "secondary": "#ef4444", "accent": "#8b5cf6", }, "fonts": { "heading": "Poppins, sans-serif", "body": "Open Sans, sans-serif" }, "layout": { "style": "masonry", "header": "fixed" } } } def apply_preset(theme: VendorTheme, preset_name: str): """Apply a preset to a vendor theme""" if preset_name not in THEME_PRESETS: raise ValueError(f"Unknown preset: {preset_name}") preset = THEME_PRESETS[preset_name] theme.theme_name = preset_name theme.colors = preset["colors"] theme.font_family_heading = preset["fonts"]["heading"] theme.font_family_body = preset["fonts"]["body"] theme.layout_style = preset["layout"]["style"] theme.header_style = preset["layout"]["header"] return theme ``` ## Admin Interface for Theme Management Create admin endpoints for managing themes: ```python # app/api/v1/admin/vendor_themes.py @router.get("/vendors/{vendor_id}/theme") def get_vendor_theme(vendor_id: int, db: Session = Depends(get_db)): """Get theme configuration for vendor""" theme = db.query(VendorTheme).filter( VendorTheme.vendor_id == vendor_id ).first() if not theme: # Return default theme return get_default_theme() return theme.to_dict() @router.put("/vendors/{vendor_id}/theme") def update_vendor_theme( vendor_id: int, theme_data: dict, db: Session = Depends(get_db) ): """Update or create theme for vendor""" theme = db.query(VendorTheme).filter( VendorTheme.vendor_id == vendor_id ).first() if not theme: theme = VendorTheme(vendor_id=vendor_id) db.add(theme) # Update fields if "colors" in theme_data: theme.colors = theme_data["colors"] if "fonts" in theme_data: theme.font_family_heading = theme_data["fonts"].get("heading") theme.font_family_body = theme_data["fonts"].get("body") if "branding" in theme_data: theme.logo_url = theme_data["branding"].get("logo") theme.logo_dark_url = theme_data["branding"].get("logo_dark") theme.favicon_url = theme_data["branding"].get("favicon") if "layout" in theme_data: theme.layout_style = theme_data["layout"].get("style") theme.header_style = theme_data["layout"].get("header") if "custom_css" in theme_data: theme.custom_css = theme_data["custom_css"] db.commit() db.refresh(theme) return theme.to_dict() @router.post("/vendors/{vendor_id}/theme/preset/{preset_name}") def apply_theme_preset( vendor_id: int, preset_name: str, db: Session = Depends(get_db) ): """Apply a preset theme to vendor""" from app.core.theme_presets import apply_preset, THEME_PRESETS if preset_name not in THEME_PRESETS: raise HTTPException(400, f"Unknown preset: {preset_name}") theme = db.query(VendorTheme).filter( VendorTheme.vendor_id == vendor_id ).first() if not theme: theme = VendorTheme(vendor_id=vendor_id) db.add(theme) apply_preset(theme, preset_name) db.commit() db.refresh(theme) return { "message": f"Applied {preset_name} preset", "theme": theme.to_dict() } ``` ## Example: Different Themes for Different Vendors ### Vendor 1: Tech Electronics Store ```python { "colors": { "primary": "#2563eb", # Blue "secondary": "#0ea5e9", "accent": "#f59e0b" }, "fonts": { "heading": "Roboto, sans-serif", "body": "Open Sans, sans-serif" }, "layout": { "style": "grid", "header": "fixed" } } ``` ### Vendor 2: Fashion Boutique ```python { "colors": { "primary": "#ec4899", # Pink "secondary": "#f472b6", "accent": "#fbbf24" }, "fonts": { "heading": "Playfair Display, serif", "body": "Lato, sans-serif" }, "layout": { "style": "masonry", "header": "transparent" } } ``` ### Vendor 3: Organic Food Store ```python { "colors": { "primary": "#10b981", # Green "secondary": "#059669", "accent": "#f59e0b" }, "fonts": { "heading": "Merriweather, serif", "body": "Source Sans Pro, sans-serif" }, "layout": { "style": "list", "header": "static" } } ``` ## Testing Themes ### Test 1: View Different Vendor Themes ```bash # Visit Vendor 1 (Tech store with blue theme) curl http://vendor1.localhost:8000/ # Visit Vendor 2 (Fashion with pink theme) curl http://vendor2.localhost:8000/ # Each should have different: # - Colors in CSS variables # - Logo # - Fonts # - Layout ``` ### Test 2: Theme API ```bash # Get vendor theme curl http://localhost:8000/api/v1/admin/vendors/1/theme # Update colors curl -X PUT http://localhost:8000/api/v1/admin/vendors/1/theme \ -H "Content-Type: application/json" \ -d '{ "colors": { "primary": "#ff0000", "secondary": "#00ff00" } }' # Apply preset curl -X POST http://localhost:8000/api/v1/admin/vendors/1/theme/preset/modern ``` ## Benefits ### For Platform Owner - ✅ Premium feature for enterprise vendors - ✅ Differentiate vendor packages (basic vs premium themes) - ✅ Additional revenue stream - ✅ Competitive advantage ### For Vendors - ✅ Unique brand identity - ✅ Professional appearance - ✅ Better customer recognition - ✅ Customizable to match brand ### For Customers - ✅ Distinct shopping experiences - ✅ Better brand recognition - ✅ More engaging designs - ✅ Professional appearance ## Advanced Features ### 1. Theme Preview Allow vendors to preview themes before applying: ```python @router.get("/vendors/{vendor_id}/theme/preview/{preset_name}") def preview_theme(vendor_id: int, preset_name: str): """Generate preview URL for theme""" # Return preview HTML with preset applied pass ``` ### 2. Theme Marketplace Create a marketplace of premium themes: ```python class PremiumTheme(Base): __tablename__ = "premium_themes" id = Column(Integer, primary_key=True) name = Column(String(100)) description = Column(Text) price = Column(Numeric(10, 2)) preview_image = Column(String(500)) config = Column(JSON) ``` ### 3. Dark Mode Auto-Detection Respect user's system preferences: ```javascript // Detect system dark mode preference if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { this.dark = true; } ``` ### 4. Theme Analytics Track which themes perform best: ```python class ThemeAnalytics(Base): __tablename__ = "theme_analytics" theme_id = Column(Integer, ForeignKey("vendor_themes.id")) conversion_rate = Column(Numeric(5, 2)) avg_session_duration = Column(Integer) bounce_rate = Column(Numeric(5, 2)) ``` ## Summary **What you've built:** - ✅ Vendor-specific theme system - ✅ CSS variables for dynamic styling - ✅ Custom branding (logos, colors, fonts) - ✅ Layout customization - ✅ Custom CSS support - ✅ Theme presets - ✅ Admin theme management **Each vendor now has:** - Unique colors and fonts - Custom logo and branding - Layout preferences - Social media links - Custom CSS overrides **All controlled by:** - Database configuration - No code changes needed per vendor - Admin panel management - Preview and testing **Your architecture supports this perfectly!** The vendor context + theme middleware pattern works seamlessly with your existing Alpine.js frontend. Start with the default theme, then let vendors customize their shops! 🎨