- Add platform detail and edit admin pages with templates and JS - Add ContentPageService methods: list_all_platform_pages, list_all_vendor_defaults - Deprecate /admin/platform-homepage route (redirects to /admin/platforms) - Add migration to fix content_page nullable columns - Refine platform and vendor context middleware - Add platform context middleware unit tests - Update platforms.js with improved functionality - Add section-based homepage plan documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
86 KiB
● Platform Homepage Configuration Analysis
Summary: Current Flexibility
┌──────────────────────┬─────────────┬──────────────────────────────────────────────────┐
│ Feature │ Flexibility │ Notes │
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
│ Homepage Content │ ✅ High │ Fully CMS-driven via ContentPage model │
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
│ Header/Footer Menus │ ⚠️ Medium │ Driven by ContentPage flags, flat structure only │
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
│ Platform Branding │ ✅ High │ Logo, favicon, colors via Platform model │
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
│ Vendor Themes │ ✅ High │ 7 presets + custom CSS + full color control │
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
│ Pricing/Features │ ❌ Low │ Hardcoded in TIER_LIMITS │
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
│ Navigation Structure │ ❌ Low │ No nested menus, no icons │
└──────────────────────┴─────────────┴──────────────────────────────────────────────────┘
- Homepage Content Configuration
Model: ContentPage with three-tier hierarchy
Platform Marketing Pages (is_platform_page=True, vendor_id=NULL)
↓ e.g., /pricing, /about, /features for oms.lu
Vendor Default Pages (is_platform_page=False, vendor_id=NULL)
↓ Fallback for all vendors (About, Shipping Policy, etc.)
Vendor Override Pages (is_platform_page=False, vendor_id=set)
↓ Vendor-specific customizations
Configurable per page:
- title, content (HTML/Markdown), slug
- template (default, minimal, modern, full)
- meta_description, meta_keywords (SEO)
- show_in_header, show_in_footer, show_in_legal
- display_order, is_published
- Menu Configuration
Current approach: Content-driven (no separate Menu model)
┌───────────────┬─────────────┬─────────────────────┐
│ Menu Location │ Source │ Filter │
├───────────────┼─────────────┼─────────────────────┤
│ Header │ ContentPage │ show_in_header=True │
├───────────────┼─────────────┼─────────────────────┤
│ Footer │ ContentPage │ show_in_footer=True │
├───────────────┼─────────────┼─────────────────────┤
│ Legal bar │ ContentPage │ show_in_legal=True │
└───────────────┴─────────────┴─────────────────────┘
Limitations:
- Flat structure only (no dropdowns/submenus)
- No custom menu items (only links to content pages)
- No menu icons or special styling
- No external URLs
- Platform Model
File: models/database/platform.py
Platform:
code # 'main', 'oms', 'loyalty'
name # Display name
domain # Production: 'oms.lu'
path_prefix # Dev: '/oms/'
logo # Light mode logo URL
logo_dark # Dark mode logo URL
favicon # Favicon URL
theme_config # JSON: colors, fonts, etc.
default_language # 'fr', 'en', 'de'
supported_languages # ['fr', 'de', 'en']
settings # JSON: feature flags
- Theme System
Vendor-level only (not platform-level defaults)
┌───────────────┬────────┬─────────────────────────────────────────────────────────────┐
│ Property │ Type │ Options │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Colors │ JSON │ primary, secondary, accent, background, text, border │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Fonts │ String │ font_family_heading, font_family_body │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Layout │ String │ grid, list, masonry │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Header │ String │ fixed, static, transparent │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Product cards │ String │ modern, classic, minimal │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Custom CSS │ Text │ Injected into <style> │
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ Presets │ 7 │ default, modern, classic, minimal, vibrant, elegant, nature │
└───────────────┴────────┴─────────────────────────────────────────────────────────────┘
- What's Hardcoded
┌──────────────────────────┬──────────────────────┬───────────────────────────┐
│ Item │ Location │ Issue │
├──────────────────────────┼──────────────────────┼───────────────────────────┤
│ Pricing tiers │ TIER_LIMITS constant │ Cannot edit via admin │
├──────────────────────────┼──────────────────────┼───────────────────────────┤
│ Add-on options │ Route handlers │ Hardcoded prices/features │
├──────────────────────────┼──────────────────────┼───────────────────────────┤
│ Platform name "Wizamart" │ Templates │ Not from database │
├──────────────────────────┼──────────────────────┼───────────────────────────┤
│ Hero section layout │ Tailwind classes │ Fixed structure │
├──────────────────────────┼──────────────────────┼───────────────────────────┤
│ Menu structure │ Code │ No nesting support │
└──────────────────────────┴──────────────────────┴───────────────────────────┘
-
Gaps for Multi-Platform Flexibility
-
No Platform-Level Theme Defaults
- VendorTheme is per-vendor only
- Each platform cannot define default vendor theme -
No Menu Model
- Cannot create custom navigation structures
- No external links, no icons, no dropdowns -
Limited Template Switching
- Only 4 homepage templates
- No way to create new templates via admin -
Hardcoded Pricing
- TIER_LIMITS in code, not database
- Cannot customize per platform
Would you like me to:
- Create a Platform Theme system - Allow platform-level theme defaults that vendors inherit
- Create a Menu model - Support hierarchical menus with custom items, icons, external links
- Move pricing to database - Make tiers configurable per platform
- Add homepage block system - Reorderable sections (hero, features, CTA, testimonials)
Which enhancement would be most valuable for your use case?
Section-Based Homepage Management System
Problem Statement
Current homepage implementation has critical issues:
- Hardcoded platform content - Migrations contain OMS/Loyalty/Main-specific HTML
- Monolithic content storage - Entire page stored as HTML blob, can't edit sections individually
- No admin control - Hero, features, pricing sections are hardcoded in templates
Solution: JSON-Based Section Architecture
Approach: Add sections JSON field to ContentPage
Why JSON field vs separate PageSection model:
- Simpler - no new tables, no joins, no N+1 queries
- Flexible - schema can evolve without migrations
- Atomic - save entire homepage in one transaction
- Follows existing pattern - VendorTheme already uses JSON for colors
Multi-Language Support
Option A: Language-Keyed Sections (Recommended)
Store all translations in one JSON structure:
{
"hero": {
"enabled": true,
"title": {
"en": "Welcome to Our Platform",
"fr": "Bienvenue sur notre plateforme",
"de": "Willkommen auf unserer Plattform"
},
"subtitle": {
"en": "Your success starts here",
"fr": "Votre succès commence ici",
"de": "Ihr Erfolg beginnt hier"
},
"buttons": [
{
"text": {"en": "Get Started", "fr": "Commencer", "de": "Loslegen"},
"url": "/signup",
"style": "primary"
}
]
}
}
Pros:
- Single page entry per platform (not 3 separate pages)
- Easy to see which translations are missing
- Atomic save of all language variants
- Admin can edit all languages in one form
Cons:
- Larger JSON payload
- Need helper function to extract current language
Option B: Separate Page Per Language
Create one ContentPage per language with same slug but different content:
- slug="home", language="en"
- slug="home", language="fr"
- slug="home", language="de"
Pros:
- Simpler JSON structure per page
- Can have different sections per language
Cons:
- More database entries
- Harder to keep in sync
- Need to add language column to ContentPage
Recommendation: Option A (Language-Keyed)
This keeps all translations together and matches how the platform already handles supported_languages on the Platform model.
Dynamic Language Support
Languages are NOT hardcoded. The system uses the platform's supported_languages setting:
Platform model already has:
supported_languages = Column(JSON) # e.g., ["fr", "de", "en"]
default_language = Column(String) # e.g., "fr"
Schema with Dynamic i18n
class TranslatableText(BaseModel):
"""
Text field with translations stored as dict.
Keys are language codes from platform.supported_languages.
"""
translations: dict[str, str] = {} # {"fr": "...", "de": "...", "en": "..."}
def get(self, lang: str, default_lang: str = "fr") -> str:
"""Get translation with fallback to default language."""
return self.translations.get(lang) or self.translations.get(default_lang) or ""
class HeroButton(BaseModel):
text: TranslatableText
url: str
style: str = "primary"
class HeroSection(BaseModel):
enabled: bool = True
badge_text: Optional[TranslatableText] = None
title: TranslatableText
subtitle: TranslatableText
background_type: str = "gradient"
buttons: list[HeroButton] = []
Template Usage with Platform Languages
{# Language comes from platform settings #}
{% set lang = request.state.language or platform.default_language %}
{% set default_lang = platform.default_language %}
{{ hero.title.get(lang, default_lang) }}
{{ hero.subtitle.get(lang, default_lang) }}
Admin UI Language Tabs
The admin editor dynamically generates language tabs from platform.supported_languages:
// Fetch platform languages
const platform = await apiClient.get(/admin/platforms/${platformCode});
const languages = platform.supported_languages; // ["fr", "de", "en"]
// Render language tabs dynamically
languages.forEach(lang => {
addLanguageTab(lang);
});
Implementation Plan
Phase 1: Database Changes
1.1 Add sections column to ContentPage
File: models/database/content_page.py
sections = Column(JSON, nullable=True, default=None)
1.2 Create migration
File: alembic/versions/xxx_add_sections_to_content_pages.py
- Add sections JSON column (nullable)
Phase 2: Schema Validation
2.1 Create Pydantic schemas with dynamic i18n
File: models/schema/homepage_sections.py (NEW)
from pydantic import BaseModel
from typing import Optional
class TranslatableText(BaseModel):
"""
Stores translations as dict with language codes as keys.
Language codes come from platform.supported_languages.
"""
translations: dict[str, str] = {}
def get(self, lang: str, default_lang: str = "fr") -> str:
"""Get text for language with fallback."""
return self.translations.get(lang) or self.translations.get(default_lang) or ""
class HeroButton(BaseModel):
text: TranslatableText
url: str
style: str = "primary" # primary, secondary, outline
class HeroSection(BaseModel):
enabled: bool = True
badge_text: Optional[TranslatableText] = None
title: TranslatableText = TranslatableText()
subtitle: TranslatableText = TranslatableText()
background_type: str = "gradient"
buttons: list[HeroButton] = []
class FeatureCard(BaseModel):
icon: str
title: TranslatableText
description: TranslatableText
class FeaturesSection(BaseModel):
enabled: bool = True
title: TranslatableText = TranslatableText()
subtitle: Optional[TranslatableText] = None
features: list[FeatureCard] = []
layout: str = "grid"
class PricingSection(BaseModel):
enabled: bool = True
title: TranslatableText = TranslatableText()
subtitle: Optional[TranslatableText] = None
use_subscription_tiers: bool = True # Pull from DB dynamically
class CTASection(BaseModel):
enabled: bool = True
title: TranslatableText = TranslatableText()
subtitle: Optional[TranslatableText] = None
buttons: list[HeroButton] = []
class HomepageSections(BaseModel):
hero: Optional[HeroSection] = None
features: Optional[FeaturesSection] = None
pricing: Optional[PricingSection] = None
cta: Optional[CTASection] = None
Phase 3: Template Changes
3.1 Create section partials
Directory: app/templates/platform/sections/ (NEW)
- _hero.html - Renders hero with language support
- _features.html - Renders features grid
- _pricing.html - Renders pricing (uses subscription_tiers from DB)
- _cta.html - Renders CTA section
3.2 Update homepage templates
File: app/templates/platform/homepage-default.html
{% set lang = request.state.language or platform.default_language or 'fr' %}
{% if page and page.sections %}
{{ render_hero(page.sections.hero, lang) }}
{{ render_features(page.sections.features, lang) }}
{{ render_pricing(page.sections.pricing, lang, tiers) }}
{{ render_cta(page.sections.cta, lang) }}
{% else %}
{# Placeholder for unconfigured homepage #}
{% endif %}
Phase 4: Service Layer
4.1 Add section methods to ContentPageService
File: app/services/content_page_service.py
- update_homepage_sections(db, page_id, sections, updated_by) - Validates and saves
- get_default_sections() - Returns empty section structure
Phase 5: Admin API
5.1 Add section endpoints
File: app/api/v1/admin/content_pages.py
- GET /{page_id}/sections - Get structured sections
- PUT /{page_id}/sections - Update all sections
- PUT /{page_id}/sections/{section_name} - Update single section
Phase 6: Remove Hardcoded Content from Migrations
6.1 Update OMS migration
File: alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py
- Remove oms_homepage_content variable
- Create homepage with empty sections structure instead
- Set is_published=False (admin configures before publishing)
6.2 Migration creates structure only
- Migrations should ONLY create empty structure
- Content is entered via admin UI in each language
Phase 7: Admin UI
7.1 Add section editor to content-page-edit
File: app/templates/admin/content-page-edit.html
- Add "Sections" tab for homepage pages
- Language tabs within each section (EN | FR | DE | LB)
- Form fields for each section type
- Enable/disable toggle per section
File: static/admin/js/content-page-edit.js
- Section editor logic
- Language tab switching
- Save sections via API
Critical Files to Modify
- models/database/content_page.py - Add sections column
- models/schema/homepage_sections.py - NEW: Pydantic schemas with i18n
- app/services/content_page_service.py - Add section methods
- app/api/v1/admin/content_pages.py - Add section endpoints
- app/templates/platform/sections/ - NEW: Section partials
- app/templates/platform/homepage-default.html - Use section partials
- app/routes/platform_pages.py - Pass sections + language to context
- alembic/versions/z4e5f6a7b8c9_*.py - Remove hardcoded content
- app/templates/admin/content-page-edit.html - Section editor UI with language tabs
- static/admin/js/content-page-edit.js - Section editor JS
Section JSON Schema Example (with dynamic i18n)
Languages in translations dict come from platform.supported_languages.
{
"hero": {
"enabled": true,
"badge_text": {
"translations": {
"fr": "Essai gratuit de 30 jours",
"de": "30 Tage kostenlos testen",
"en": "30-Day Free Trial"
}
},
"title": {
"translations": {
"fr": "Votre titre de plateforme ici",
"de": "Ihr Plattform-Titel hier",
"en": "Your Platform Headline Here"
}
},
"subtitle": {
"translations": {
"fr": "Une description convaincante de votre plateforme.",
"de": "Eine überzeugende Beschreibung Ihrer Plattform.",
"en": "A compelling description of your platform."
}
},
"background_type": "gradient",
"buttons": [
{
"text": {
"translations": {"fr": "Commencer", "de": "Loslegen", "en": "Get Started"}
},
"url": "/signup",
"style": "primary"
}
]
},
"features": {
"enabled": true,
"title": {
"translations": {
"fr": "Pourquoi nous choisir",
"de": "Warum uns wählen",
"en": "Why Choose Us"
}
},
"features": [
{
"icon": "lightning-bolt",
"title": {"translations": {"fr": "Rapide", "de": "Schnell", "en": "Fast"}},
"description": {"translations": {"fr": "Rapide et efficace.", "de": "Schnell und effizient.", "en": "Quick and efficient."}}
}
]
},
"pricing": {
"enabled": true,
"title": {
"translations": {
"fr": "Tarification simple",
"de": "Einfache Preise",
"en": "Simple Pricing"
}
},
"use_subscription_tiers": true
},
"cta": {
"enabled": true,
"title": {
"translations": {
"fr": "Prêt à commencer?",
"de": "Bereit anzufangen?",
"en": "Ready to Start?"
}
},
"buttons": [
{
"text": {
"translations": {"fr": "S'inscrire gratuitement", "de": "Kostenlos registrieren", "en": "Sign Up Free"}
},
"url": "/signup",
"style": "primary"
}
]
}
}
Migration Strategy (No Hardcoded Content)
When creating a platform homepage:
homepage = ContentPage(
platform_id=platform_id,
slug="home",
title="Homepage", # Generic
content="", # Empty - sections used instead
sections=get_default_sections(), # Empty structure with all languages
is_published=False, # Admin configures first
)
Verification Steps
- Run migration to add sections column
- Create a test homepage with sections via API (all languages)
- Verify homepage renders correct language based on request
- Test admin UI section editor with language tabs
- Verify pricing section pulls from subscription_tiers
- Test enable/disable toggle for each section
- Test language fallback when translation is missing
Notes
- Languages are dynamic from platform.supported_languages (not hardcoded)
- Fallback uses platform.default_language
- Admin UI should allow partial translations (show warning indicator for missing)
- Plan saved for resumption tomorrow