# 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
<!-- In templates -->
<button style="background-color: var(--color-primary)">
    Click Me
</button>

<h1 style="font-family: var(--font-heading)">
    Welcome
</h1>
```

```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! 🎨
