# Multi-Theme Shop System - Complete Implementation Guide ## 🎨 Overview This guide explains how to implement store-specific themes in your FastAPI multi-tenant e-commerce platform, allowing each store to have their own unique shop design, colors, branding, and layout. ## What You're Building **Before:** - All store shops look the same - Same colors, fonts, layouts - Only store name changes **After:** - Each store has unique theme - Custom colors, fonts, logos - Different layouts per store - Store-specific branding - CSS customization support ## Architecture Overview ``` Request → Store Middleware → Theme Middleware → Template Rendering ↓ ↓ ↓ Sets store Loads theme Applies styles in request config for and branding state store ``` ### Data Flow ``` 1. Customer visits: customdomain1.com 2. Store middleware: Identifies Store 1 3. Theme middleware: Loads Store 1's theme 4. Template receives: - store: Store 1 object - theme: Store 1 theme config 5. Template renders with: - Store 1 colors - Store 1 logo - Store 1 layout preferences - Store 1 custom CSS ``` ## Implementation Steps ### Step 1: Add Theme Database Table Create the `store_themes` table: ```sql CREATE TABLE store_themes ( id SERIAL PRIMARY KEY, store_id INTEGER UNIQUE NOT NULL REFERENCES stores(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_store_themes_store_id ON store_themes(store_id); CREATE INDEX idx_store_themes_active ON store_themes(store_id, is_active); ``` ### Step 2: Create StoreTheme Model File: `models/database/store_theme.py` See the complete model in `/home/claude/store_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 Store Model Add theme relationship to `models/database/store.py`: ```python from sqlalchemy.orm import relationship class Store(Base): # ... existing fields ... # Add theme relationship theme = relationship( "StoreTheme", back_populates="store", uselist=False, # One-to-one relationship cascade="all, delete-orphan" ) @property def active_theme(self): """Get store'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 store_context_middleware 2. Loads theme for detected store 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 store_context_middleware app.middleware("http")(theme_context_middleware) ``` ### Step 5: Create Shop Base Template File: `app/templates/storefront/base.html` See complete template in `/home/claude/shop_base_template.html` **Key features:** - Injects CSS variables from theme - Store-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 { "store": store_object, # From store middleware "theme": theme_dict, # From theme middleware } ``` ### Step 6: Create Shop Layout JavaScript File: `static/storefront/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)): store = request.state.store theme = get_current_theme(request) # or request.state.theme # Get products for store products = db.query(Product).filter( Product.store_id == store.id, Product.is_active == True ).all() return templates.TemplateResponse("shop/home.html", { "request": request, "store": store, "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/stores/tech-store/logo.png", "logo_dark": "/media/stores/tech-store/logo-dark.png", "favicon": "/media/stores/tech-store/favicon.ico", "banner": "/media/stores/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: StoreTheme, preset_name: str): """Apply a preset to a store 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/store_themes.py @router.get("/stores/{store_id}/theme") def get_store_theme(store_id: int, db: Session = Depends(get_db)): """Get theme configuration for store""" theme = db.query(StoreTheme).filter( StoreTheme.store_id == store_id ).first() if not theme: # Return default theme return get_default_theme() return theme.to_dict() @router.put("/stores/{store_id}/theme") def update_store_theme( store_id: int, theme_data: dict, db: Session = Depends(get_db) ): """Update or create theme for store""" theme = db.query(StoreTheme).filter( StoreTheme.store_id == store_id ).first() if not theme: theme = StoreTheme(store_id=store_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("/stores/{store_id}/theme/preset/{preset_name}") def apply_theme_preset( store_id: int, preset_name: str, db: Session = Depends(get_db) ): """Apply a preset theme to store""" 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(StoreTheme).filter( StoreTheme.store_id == store_id ).first() if not theme: theme = StoreTheme(store_id=store_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 Stores ### Store 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" } } ``` ### Store 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" } } ``` ### Store 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 Store Themes ```bash # Visit Store 1 (Tech store with blue theme) curl http://store1.localhost:8000/ # Visit Store 2 (Fashion with pink theme) curl http://store2.localhost:8000/ # Each should have different: # - Colors in CSS variables # - Logo # - Fonts # - Layout ``` ### Test 2: Theme API ```bash # Get store theme curl http://localhost:8000/api/v1/admin/stores/1/theme # Update colors curl -X PUT http://localhost:8000/api/v1/admin/stores/1/theme \ -H "Content-Type: application/json" \ -d '{ "colors": { "primary": "#ff0000", "secondary": "#00ff00" } }' # Apply preset curl -X POST http://localhost:8000/api/v1/admin/stores/1/theme/preset/modern ``` ## Benefits ### For Platform Owner - ✅ Premium feature for enterprise stores - ✅ Differentiate store packages (basic vs premium themes) - ✅ Additional revenue stream - ✅ Competitive advantage ### For Stores - ✅ 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 stores to preview themes before applying: ```python @router.get("/stores/{store_id}/theme/preview/{preset_name}") def preview_theme(store_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("store_themes.id")) conversion_rate = Column(Numeric(5, 2)) avg_session_duration = Column(Integer) bounce_rate = Column(Numeric(5, 2)) ``` ## Summary **What you've built:** - ✅ Store-specific theme system - ✅ CSS variables for dynamic styling - ✅ Custom branding (logos, colors, fonts) - ✅ Layout customization - ✅ Custom CSS support - ✅ Theme presets - ✅ Admin theme management **Each store 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 store - Admin panel management - Preview and testing **Your architecture supports this perfectly!** The store context + theme middleware pattern works seamlessly with your existing Alpine.js frontend. Start with the default theme, then let stores customize their shops! 🎨