Files
orion/docs/proposals/TEMP.md
Samir Boulahtit 3d3b8cae22 feat: add platform detail/edit admin UI and service enhancements
- 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>
2026-01-23 14:08:02 +01:00

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 │
└──────────────────────┴─────────────┴──────────────────────────────────────────────────┘

  1. 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

  1. 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

  1. 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


  1. 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 │
└───────────────┴────────┴─────────────────────────────────────────────────────────────┘

  1. 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 │
    └──────────────────────────┴──────────────────────┴───────────────────────────┘

  1. Gaps for Multi-Platform Flexibility

  2. No Platform-Level Theme Defaults
    - VendorTheme is per-vendor only
    - Each platform cannot define default vendor theme

  3. No Menu Model
    - Cannot create custom navigation structures
    - No external links, no icons, no dropdowns

  4. Limited Template Switching
    - Only 4 homepage templates
    - No way to create new templates via admin

  5. Hardcoded Pricing
    - TIER_LIMITS in code, not database
    - Cannot customize per platform


Would you like me to:

  1. Create a Platform Theme system - Allow platform-level theme defaults that vendors inherit
  2. Create a Menu model - Support hierarchical menus with custom items, icons, external links
  3. Move pricing to database - Make tiers configurable per platform
  4. 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:

  1. Hardcoded platform content - Migrations contain OMS/Loyalty/Main-specific HTML
  2. Monolithic content storage - Entire page stored as HTML blob, can't edit sections individually
  3. 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

  1. models/database/content_page.py - Add sections column
  2. models/schema/homepage_sections.py - NEW: Pydantic schemas with i18n
  3. app/services/content_page_service.py - Add section methods
  4. app/api/v1/admin/content_pages.py - Add section endpoints
  5. app/templates/platform/sections/ - NEW: Section partials
  6. app/templates/platform/homepage-default.html - Use section partials
  7. app/routes/platform_pages.py - Pass sections + language to context
  8. alembic/versions/z4e5f6a7b8c9_*.py - Remove hardcoded content
  9. app/templates/admin/content-page-edit.html - Section editor UI with language tabs
  10. 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

  1. Run migration to add sections column
  2. Create a test homepage with sections via API (all languages)
  3. Verify homepage renders correct language based on request
  4. Test admin UI section editor with language tabs
  5. Verify pricing section pulls from subscription_tiers
  6. Test enable/disable toggle for each section
  7. 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