From adbecd360b409e5b4181b5092987bcb58eb6aed3 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 1 Mar 2026 12:12:20 +0100 Subject: [PATCH] feat(cms): CMS-driven homepages, products section, placeholder resolution - Add ProductCard/ProductsSection schema and _products.html section macro - Rewrite seed script with 3-platform homepage sections (wizard, OMS, loyalty), platform marketing pages, and store defaults with {{store_name}} placeholders - Add resolve_placeholders() to ContentPageService for store default pages - Fix SQLAlchemy filter bugs: replace Python `is None` with `.is_(None)` across all ContentPageService query methods (was silently breaking all platform page lookups) - Remove hardcoded orion fallback and delete homepage-orion.html - Add placeholder hint box with click-to-copy in admin content page editor - Export ProductCard/ProductsSection from cms schemas __init__ Co-Authored-By: Claude Opus 4.6 --- app/modules/cms/routes/pages/platform.py | 39 +- app/modules/cms/routes/pages/storefront.py | 10 +- app/modules/cms/schemas/__init__.py | 4 + app/modules/cms/schemas/homepage_sections.py | 26 +- .../cms/services/content_page_service.py | 66 +- .../cms/admin/content-page-edit.html | 29 + .../cms/platform/homepage-default.html | 6 + .../cms/platform/homepage-orion.html | 427 ------ .../cms/platform/sections/_products.html | 91 ++ .../cms/storefront/content-page.html | 9 +- scripts/seed/create_default_content_pages.py | 1341 +++++++++++------ 11 files changed, 1132 insertions(+), 916 deletions(-) delete mode 100644 app/modules/cms/templates/cms/platform/homepage-orion.html create mode 100644 app/modules/cms/templates/cms/platform/sections/_products.html diff --git a/app/modules/cms/routes/pages/platform.py b/app/modules/cms/routes/pages/platform.py index d9929a1a..50939617 100644 --- a/app/modules/cms/routes/pages/platform.py +++ b/app/modules/cms/routes/pages/platform.py @@ -164,46 +164,13 @@ async def homepage( logger.info(f"[HOMEPAGE] Rendering CMS homepage with template: {template_path}") return templates.TemplateResponse(template_path, context) - # Fallback: Default orion homepage (no CMS content) - logger.info("[HOMEPAGE] No CMS homepage found, using default orion template") + # Fallback: Default homepage template with placeholder content + logger.info("[HOMEPAGE] No CMS homepage found, using default template with placeholders") context = get_platform_context(request, db) context["tiers"] = _get_tiers_data(db) - # Add-ons (hardcoded for now, will come from DB) - context["addons"] = [ - { - "code": "domain", - "name": "Custom Domain", - "description": "Use your own domain (mydomain.com)", - "price": 15, - "billing_period": "year", - "icon": "globe", - }, - { - "code": "ssl_premium", - "name": "Premium SSL", - "description": "EV certificate for trust badges", - "price": 49, - "billing_period": "year", - "icon": "shield-check", - }, - { - "code": "email", - "name": "Email Package", - "description": "Professional email addresses", - "price": 5, - "billing_period": "month", - "icon": "mail", - "options": [ - {"quantity": 5, "price": 5}, - {"quantity": 10, "price": 9}, - {"quantity": 25, "price": 19}, - ], - }, - ] - return templates.TemplateResponse( - "cms/platform/homepage-orion.html", + "cms/platform/homepage-default.html", context, ) diff --git a/app/modules/cms/routes/pages/storefront.py b/app/modules/cms/routes/pages/storefront.py index a9acb254..92dbd1cf 100644 --- a/app/modules/cms/routes/pages/storefront.py +++ b/app/modules/cms/routes/pages/storefront.py @@ -101,9 +101,17 @@ async def generic_content_page( }, ) + # Resolve placeholders in store default pages ({{store_name}}, etc.) + page_content = page.content + if page.is_store_default and store: + page_content = content_page_service.resolve_placeholders(page.content, store) + + context = get_storefront_context(request, db=db, page=page) + context["page_content"] = page_content + return templates.TemplateResponse( "cms/storefront/content-page.html", - get_storefront_context(request, db=db, page=page), + context, ) diff --git a/app/modules/cms/schemas/__init__.py b/app/modules/cms/schemas/__init__.py index de04a9ec..3cb28100 100644 --- a/app/modules/cms/schemas/__init__.py +++ b/app/modules/cms/schemas/__init__.py @@ -31,6 +31,8 @@ from app.modules.cms.schemas.homepage_sections import ( HomepageSections, HomepageSectionsResponse, PricingSection, + ProductCard, + ProductsSection, # API schemas SectionUpdateRequest, # Translatable text @@ -92,6 +94,8 @@ __all__ = [ "HeroSection", "FeatureCard", "FeaturesSection", + "ProductCard", + "ProductsSection", "PricingSection", "CTASection", "HomepageSections", diff --git a/app/modules/cms/schemas/homepage_sections.py b/app/modules/cms/schemas/homepage_sections.py index 4f9b9e14..ff81f0c6 100644 --- a/app/modules/cms/schemas/homepage_sections.py +++ b/app/modules/cms/schemas/homepage_sections.py @@ -77,6 +77,25 @@ class FeatureCard(BaseModel): description: TranslatableText = Field(default_factory=TranslatableText) +class ProductCard(BaseModel): + """Single product/offering card in products section.""" + + icon: str = "" + title: TranslatableText = Field(default_factory=TranslatableText) + description: TranslatableText = Field(default_factory=TranslatableText) + url: str = "" + badge: TranslatableText | None = None + + +class ProductsSection(BaseModel): + """Product/offering showcase section (e.g. wizard.lu multi-product landing).""" + + enabled: bool = True + title: TranslatableText = Field(default_factory=TranslatableText) + subtitle: TranslatableText | None = None + products: list[ProductCard] = Field(default_factory=list) + + class FeaturesSection(BaseModel): """Features section configuration.""" @@ -114,6 +133,7 @@ class HomepageSections(BaseModel): """Complete homepage sections structure.""" hero: HeroSection | None = None + products: ProductsSection | None = None features: FeaturesSection | None = None pricing: PricingSection | None = None cta: CTASection | None = None @@ -139,6 +159,10 @@ class HomepageSections(BaseModel): subtitle=make_translatable(languages), buttons=[], ), + products=ProductsSection( + title=make_translatable(languages), + products=[], + ), features=FeaturesSection( title=make_translatable(languages), features=[], @@ -162,7 +186,7 @@ class HomepageSections(BaseModel): class SectionUpdateRequest(BaseModel): """Request to update a single section.""" - section_name: str = Field(..., description="hero, features, pricing, or cta") + section_name: str = Field(..., description="hero, products, features, pricing, or cta") section_data: dict = Field(..., description="Section configuration") diff --git a/app/modules/cms/services/content_page_service.py b/app/modules/cms/services/content_page_service.py index 8631ecd2..e42274f7 100644 --- a/app/modules/cms/services/content_page_service.py +++ b/app/modules/cms/services/content_page_service.py @@ -142,8 +142,8 @@ class ContentPageService: db.query(ContentPage) .filter( and_( - ContentPage.store_id is None, - ContentPage.is_platform_page == False, + ContentPage.store_id.is_(None), + ContentPage.is_platform_page.is_(False), *base_filters, ) ) @@ -182,12 +182,12 @@ class ContentPageService: filters = [ ContentPage.platform_id == platform_id, ContentPage.slug == slug, - ContentPage.store_id is None, - ContentPage.is_platform_page == True, + ContentPage.store_id.is_(None), + ContentPage.is_platform_page.is_(True), ] if not include_unpublished: - filters.append(ContentPage.is_published == True) + filters.append(ContentPage.is_published.is_(True)) page = db.query(ContentPage).filter(and_(*filters)).first() @@ -255,8 +255,8 @@ class ContentPageService: db.query(ContentPage) .filter( and_( - ContentPage.store_id is None, - ContentPage.is_platform_page == False, + ContentPage.store_id.is_(None), + ContentPage.is_platform_page.is_(False), *base_filters, ) ) @@ -298,12 +298,12 @@ class ContentPageService: """ filters = [ ContentPage.platform_id == platform_id, - ContentPage.store_id is None, - ContentPage.is_platform_page == True, + ContentPage.store_id.is_(None), + ContentPage.is_platform_page.is_(True), ] if not include_unpublished: - filters.append(ContentPage.is_published == True) + filters.append(ContentPage.is_published.is_(True)) if footer_only: filters.append(ContentPage.show_in_footer == True) @@ -377,12 +377,12 @@ class ContentPageService: """ filters = [ ContentPage.platform_id == platform_id, - ContentPage.store_id is None, - ContentPage.is_platform_page == False, + ContentPage.store_id.is_(None), + ContentPage.is_platform_page.is_(False), ] if not include_unpublished: - filters.append(ContentPage.is_published == True) + filters.append(ContentPage.is_published.is_(True)) return ( db.query(ContentPage) @@ -845,13 +845,13 @@ class ContentPageService: filters.append(ContentPage.is_published == True) if page_tier == "platform": - filters.append(ContentPage.is_platform_page == True) - filters.append(ContentPage.store_id is None) + filters.append(ContentPage.is_platform_page.is_(True)) + filters.append(ContentPage.store_id.is_(None)) elif page_tier == "store_default": - filters.append(ContentPage.is_platform_page == False) - filters.append(ContentPage.store_id is None) + filters.append(ContentPage.is_platform_page.is_(False)) + filters.append(ContentPage.store_id.is_(None)) elif page_tier == "store_override": - filters.append(ContentPage.store_id is not None) + filters.append(ContentPage.store_id.isnot(None)) return ( db.query(ContentPage) @@ -958,6 +958,34 @@ class ContentPageService: if not success: raise ContentPageNotFoundException(identifier=page_id) + # ========================================================================= + # Placeholder Resolution (for store default pages) + # ========================================================================= + + @staticmethod + def resolve_placeholders(content: str, store) -> str: + """ + Replace {{store_name}}, {{store_email}}, {{store_phone}} placeholders + in store default page content with actual store values. + + Args: + content: HTML content with placeholders + store: Store object with name, contact_email, phone attributes + + Returns: + Content with placeholders replaced + """ + if not content or not store: + return content or "" + replacements = { + "{{store_name}}": store.name or "Our Store", + "{{store_email}}": getattr(store, "contact_email", "") or "", + "{{store_phone}}": getattr(store, "phone", "") or "", + } + for placeholder, value in replacements.items(): + content = content.replace(placeholder, value) + return content + # ========================================================================= # Homepage Sections Management # ========================================================================= @@ -1032,10 +1060,12 @@ class ContentPageService: FeaturesSection, HeroSection, PricingSection, + ProductsSection, ) SECTION_SCHEMAS = { "hero": HeroSection, + "products": ProductsSection, "features": FeaturesSection, "pricing": PricingSection, "cta": CTASection, diff --git a/app/modules/cms/templates/cms/admin/content-page-edit.html b/app/modules/cms/templates/cms/admin/content-page-edit.html index 35580152..dabe531c 100644 --- a/app/modules/cms/templates/cms/admin/content-page-edit.html +++ b/app/modules/cms/templates/cms/admin/content-page-edit.html @@ -187,6 +187,35 @@

+ + {# Available Placeholders (for store default pages) #} + {% set placeholders = [ + ('store_name', "The store's display name"), + ('store_email', "The store's contact email"), + ('store_phone', "The store's phone number"), + ] %} +
+

+ + Available Placeholders +

+

+ Use these placeholders in store default pages. They will be automatically replaced with the store's actual information when displayed. +

+
+ {% for name, description in placeholders %} + + {% raw %}{{{% endraw %}{{ name }}{% raw %}}}{% endraw %} + + {% endfor %} +
+

+ Click a placeholder to copy it to your clipboard. +

+
diff --git a/app/modules/cms/templates/cms/platform/homepage-default.html b/app/modules/cms/templates/cms/platform/homepage-default.html index feca7f36..df5dc22d 100644 --- a/app/modules/cms/templates/cms/platform/homepage-default.html +++ b/app/modules/cms/templates/cms/platform/homepage-default.html @@ -4,6 +4,7 @@ {# Import section partials #} {% from 'cms/platform/sections/_hero.html' import render_hero %} +{% from 'cms/platform/sections/_products.html' import render_products %} {% from 'cms/platform/sections/_features.html' import render_features %} {% from 'cms/platform/sections/_pricing.html' import render_pricing %} {% from 'cms/platform/sections/_cta.html' import render_cta %} @@ -35,6 +36,11 @@ {{ render_hero(page.sections.hero, lang, default_lang) }} {% endif %} + {# Products Section #} + {% if page.sections.products %} + {{ render_products(page.sections.products, lang, default_lang) }} + {% endif %} + {# Features Section #} {% if page.sections.features %} {{ render_features(page.sections.features, lang, default_lang) }} diff --git a/app/modules/cms/templates/cms/platform/homepage-orion.html b/app/modules/cms/templates/cms/platform/homepage-orion.html deleted file mode 100644 index bb815265..00000000 --- a/app/modules/cms/templates/cms/platform/homepage-orion.html +++ /dev/null @@ -1,427 +0,0 @@ -{# app/templates/platform/homepage-orion.html #} -{# Orion Marketing Homepage - Letzshop OMS Platform #} -{% extends "platform/base.html" %} -{% from 'shared/macros/inputs.html' import toggle_switch %} - -{% block title %}Orion - Order Management for Letzshop Sellers{% endblock %} -{% block meta_description %}Lightweight OMS for Letzshop stores. Manage orders, inventory, and invoicing. Start your 30-day free trial today.{% endblock %} - -{% block content %} -
- - {# ========================================================================= - HERO SECTION - ========================================================================= #} -
-
-
- {# Badge #} -
- - - - {{ _("cms.platform.hero.badge", trial_days=trial_days) }} -
- - {# Headline #} -

- {{ _("cms.platform.hero.title") }} -

- - {# Subheadline #} -

- {{ _("cms.platform.hero.subtitle") }} -

- - {# CTA Buttons #} - -
-
- - {# Background Decoration #} -
-
-
-
-
- - {# ========================================================================= - PRICING SECTION - ========================================================================= #} -
-
- {# Section Header #} -
-

- {{ _("cms.platform.pricing.title") }} -

-

- {{ _("cms.platform.pricing.subtitle", trial_days=trial_days) }} -

- - {# Billing Toggle #} -
- {{ toggle_switch( - model='annual', - left_label=_("cms.platform.pricing.monthly"), - right_label=_("cms.platform.pricing.annual"), - right_badge=_("cms.platform.pricing.save_months") - ) }} -
-
- - {# Pricing Cards Grid #} -
- {% for tier in tiers %} - - {% endfor %} -
-
-
- - {# ========================================================================= - ADD-ONS SECTION - ========================================================================= #} -
-
- {# Section Header #} -
-

- {{ _("cms.platform.addons.title") }} -

-

- {{ _("cms.platform.addons.subtitle") }} -

-
- - {# Add-ons Grid #} -
- {% for addon in addons %} -
- {# Icon #} -
- {% if addon.icon == 'globe' %} - - - - {% elif addon.icon == 'shield-check' %} - - - - {% elif addon.icon == 'mail' %} - - - - {% endif %} -
- - {# Name & Description #} -

{{ addon.name }}

-

{{ addon.description }}

- - {# Price #} -
- {{ addon.price }}€ - /{{ addon.billing_period }} -
- - {# Options for email packages #} - {% if addon.options %} -
- {% for opt in addon.options %} -
- {{ opt.quantity }} addresses: {{ opt.price }}€/month -
- {% endfor %} -
- {% endif %} -
- {% endfor %} -
-
-
- - {# ========================================================================= - LETZSHOP STORE FINDER - ========================================================================= #} -
-
- {# Section Header #} -
-

- {{ _("cms.platform.find_shop.title") }} -

-

- {{ _("cms.platform.find_shop.subtitle") }} -

-
- - {# Search Form #} -
-
- - -
- - {# Result #} - - - {# Help Text #} -

- {{ _("cms.platform.find_shop.no_account") }} {{ _("cms.platform.find_shop.signup_letzshop") }}{{ _("cms.platform.find_shop.then_connect") }} -

-
-
-
- - {# ========================================================================= - FINAL CTA SECTION - ========================================================================= #} -
-
-

- {{ _("cms.platform.cta.title") }} -

-

- {{ _("cms.platform.cta.subtitle", trial_days=trial_days) }} -

- - {{ _("cms.platform.cta.button") }} - - - - -
-
-
-{% endblock %} - -{% block extra_scripts %} - -{% endblock %} diff --git a/app/modules/cms/templates/cms/platform/sections/_products.html b/app/modules/cms/templates/cms/platform/sections/_products.html new file mode 100644 index 00000000..266e8f04 --- /dev/null +++ b/app/modules/cms/templates/cms/platform/sections/_products.html @@ -0,0 +1,91 @@ +{# app/templates/platform/sections/_products.html #} +{# Products/offerings section for multi-product platforms (e.g. wizard.lu) #} +{# + Parameters: + - products: ProductsSection object (or dict) + - lang: Current language code + - default_lang: Fallback language +#} + +{% macro render_products(products, lang, default_lang) %} +{% if products and products.enabled %} +
+
+ {# Section header #} +
+ {% set title = products.title.translations.get(lang) or products.title.translations.get(default_lang) or '' %} + {% if title %} +

+ {{ title }} +

+ {% endif %} + + {% if products.subtitle and products.subtitle.translations %} + {% set subtitle = products.subtitle.translations.get(lang) or products.subtitle.translations.get(default_lang) %} + {% if subtitle %} +

+ {{ subtitle }} +

+ {% endif %} + {% endif %} +
+ + {# Product cards #} + {% if products.products %} +
+ {% for product in products.products %} +
+ {# Badge #} + {% if product.badge and product.badge.translations %} + {% set badge_text = product.badge.translations.get(lang) or product.badge.translations.get(default_lang) %} + {% if badge_text %} +
+ + {{ badge_text }} + +
+ {% endif %} + {% endif %} + + {# Icon #} + {% if product.icon %} +
+ +
+ {% endif %} + + {# Title #} + {% set product_title = product.title.translations.get(lang) or product.title.translations.get(default_lang) or '' %} + {% if product_title %} +

+ {{ product_title }} +

+ {% endif %} + + {# Description #} + {% set product_desc = product.description.translations.get(lang) or product.description.translations.get(default_lang) or '' %} + {% if product_desc %} +

+ {{ product_desc }} +

+ {% endif %} + + {# CTA Link #} + {% if product.url %} + + {% set link_text = product_title or 'Learn More' %} + Learn More + + + + + {% endif %} +
+ {% endfor %} +
+ {% endif %} +
+
+{% endif %} +{% endmacro %} diff --git a/app/modules/cms/templates/cms/storefront/content-page.html b/app/modules/cms/templates/cms/storefront/content-page.html index 1df5795e..45a99664 100644 --- a/app/modules/cms/templates/cms/storefront/content-page.html +++ b/app/modules/cms/templates/cms/storefront/content-page.html @@ -42,17 +42,16 @@ {% endif %} - {# Content #} + {# Content — use page_content (with resolved placeholders) when available #} + {% set content = page_content if page_content is defined and page_content else page.content %}
{% if page.content_format == 'markdown' %} - {# Markdown content - future enhancement: render with markdown library #}
- {{ page.content | safe }}{# sanitized: CMS content #} + {{ content | safe }}{# sanitized: CMS content #}
{% else %} - {# HTML content (default) #} - {{ page.content | safe }}{# sanitized: CMS content #} + {{ content | safe }}{# sanitized: CMS content #} {% endif %}
diff --git a/scripts/seed/create_default_content_pages.py b/scripts/seed/create_default_content_pages.py index b2353b05..a5ae86f5 100755 --- a/scripts/seed/create_default_content_pages.py +++ b/scripts/seed/create_default_content_pages.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 """ -Create Default Platform Content Pages (CMS) +Create Default Content Pages for All Platforms (CMS) -This script creates platform-level default content pages for ALL platforms. -Pages include: -- Platform Homepage (platform marketing page) -- About Us -- Contact -- FAQ -- Shipping Policy -- Return Policy -- Privacy Policy -- Terms of Service +This script creates: +1. Platform Homepages (slug="home", is_platform_page=True, sections JSON) + - wizard.lu: multi-product landing (hero + products + cta) + - omsflow.lu: OMS-focused (hero + features + pricing + cta) + - rewardflow.lu: loyalty-focused (hero + features + pricing + cta) -Pages are created per-platform (unique constraint: platform_id + store_id + slug). -Stores can override any of these pages with their own custom content. +2. Platform Marketing Pages (is_platform_page=True) + - about, contact, faq, privacy, terms per platform + +3. Store Default Pages (is_platform_page=False, store_id=NULL) + - Generic templates with {{store_name}} placeholders + - OMS: about, contact, faq, shipping, returns, privacy, terms + - Loyalty: about, contact, faq, privacy, terms Prerequisites: - Database migrations must be applied @@ -23,9 +23,6 @@ Prerequisites: Usage: python scripts/seed/create_default_content_pages.py - - # Or with make: - make create-cms-defaults """ import sys @@ -61,66 +58,761 @@ from app.core.database import SessionLocal from app.modules.cms.models import ContentPage from app.modules.tenancy.models import Platform + # ============================================================================ -# PLATFORM HOMEPAGE (is_platform_page=True) +# HELPER: TranslatableText dict builder # ============================================================================ -PLATFORM_HOMEPAGE = { - "slug": "platform_homepage", - "title": "Welcome to Our Multi-Store Marketplace", - "content": """ -

- Connect stores with customers worldwide. Build your online store and reach millions of shoppers. -

-

- Our platform empowers entrepreneurs to launch their own branded e-commerce stores - with minimal effort and maximum impact. -

- """, - "meta_description": "Leading multi-store marketplace platform. Connect with thousands of stores and discover millions of products.", - "meta_keywords": "marketplace, multi-store, e-commerce, online shopping, platform", - "show_in_footer": False, - "show_in_header": False, - "is_platform_page": True, - "template": "modern", - "display_order": 0, + +def t(fr: str, en: str, de: str) -> dict: + """Build a TranslatableText-compatible dict.""" + return {"translations": {"fr": fr, "en": en, "de": de}} + + +# ============================================================================ +# PLATFORM HOMEPAGE SECTIONS (slug="home", is_platform_page=True) +# ============================================================================ + + +def _wizard_homepage_sections() -> dict: + """Wizard.lu (main) — multi-product landing page.""" + return { + "hero": { + "enabled": True, + "title": t( + "Votre boîte à outils numérique pour votre entreprise", + "Your Business Digital Toolkit", + "Ihr digitales Business-Toolkit", + ), + "subtitle": t( + "Gestion des commandes, programmes de fidélité et création de sites web — tout ce dont votre entreprise luxembourgeoise a besoin.", + "Order management, loyalty programs, and website building — everything your Luxembourg business needs.", + "Bestellverwaltung, Treueprogramme und Website-Erstellung — alles, was Ihr luxemburgisches Unternehmen braucht.", + ), + "background_type": "gradient", + "buttons": [ + { + "text": t( + "Découvrir nos solutions", + "Discover Our Solutions", + "Unsere Lösungen entdecken", + ), + "url": "#products", + "style": "primary", + }, + ], + }, + "products": { + "enabled": True, + "title": t( + "Nos solutions", + "Our Solutions", + "Unsere Lösungen", + ), + "subtitle": t( + "Des outils conçus pour le commerce luxembourgeois", + "Tools built for Luxembourg commerce", + "Werkzeuge für den luxemburgischen Handel", + ), + "products": [ + { + "icon": "clipboard-list", + "title": t( + "Gestion des Commandes (OMS)", + "Order Management (OMS)", + "Bestellverwaltung (OMS)", + ), + "description": t( + "Synchronisez vos commandes Letzshop, gérez les stocks et générez des factures conformes à la TVA.", + "Sync your Letzshop orders, manage inventory, and generate VAT-compliant invoices.", + "Synchronisieren Sie Ihre Letzshop-Bestellungen, verwalten Sie Lagerbestände und erstellen Sie MwSt-konforme Rechnungen.", + ), + "url": "/platforms/oms/", + }, + { + "icon": "heart", + "title": t( + "Programme de Fidélité", + "Loyalty Program", + "Treueprogramm", + ), + "description": t( + "Créez des programmes de fidélité avec points, récompenses et niveaux pour fidéliser vos clients.", + "Create loyalty programs with points, rewards, and tiers to retain your customers.", + "Erstellen Sie Treueprogramme mit Punkten, Prämien und Stufen, um Ihre Kunden zu binden.", + ), + "url": "/platforms/loyalty/", + }, + { + "icon": "globe-alt", + "title": t( + "Création de Sites Web", + "Website Builder", + "Website-Erstellung", + ), + "description": t( + "Créez votre présence en ligne avec notre outil de création de sites web intuitif.", + "Build your online presence with our intuitive website builder.", + "Erstellen Sie Ihre Online-Präsenz mit unserem intuitiven Website-Baukasten.", + ), + "url": "", + "badge": t("Bientôt", "Coming Soon", "Demnächst"), + }, + ], + }, + "cta": { + "enabled": True, + "title": t( + "Prêt à digitaliser votre entreprise ?", + "Ready to Digitalize Your Business?", + "Bereit, Ihr Unternehmen zu digitalisieren?", + ), + "subtitle": t( + "Rejoignez les entreprises luxembourgeoises qui font confiance à nos solutions.", + "Join Luxembourg businesses that trust our solutions.", + "Schließen Sie sich luxemburgischen Unternehmen an, die unseren Lösungen vertrauen.", + ), + "background_type": "gradient", + "buttons": [ + { + "text": t( + "Commencer maintenant", + "Get Started Now", + "Jetzt starten", + ), + "url": "/signup", + "style": "primary", + }, + { + "text": t( + "Nous contacter", + "Contact Us", + "Kontaktieren Sie uns", + ), + "url": "/contact", + "style": "secondary", + }, + ], + }, + } + + +def _oms_homepage_sections() -> dict: + """OMS (omsflow.lu) — order management focused homepage.""" + return { + "hero": { + "enabled": True, + "badge_text": t( + "Essai gratuit — Aucune carte de crédit requise", + "Free Trial — No Credit Card Required", + "Kostenlose Testversion — Keine Kreditkarte erforderlich", + ), + "title": t( + "OMS léger pour les vendeurs Letzshop", + "Lightweight OMS for Letzshop Sellers", + "Leichtes OMS für Letzshop-Verkäufer", + ), + "subtitle": t( + "Gestion des commandes, stocks et facturation conçue pour le e-commerce luxembourgeois. Arrêtez de jongler avec les tableurs. Gérez votre entreprise.", + "Order management, inventory, and invoicing built for Luxembourg e-commerce. Stop juggling spreadsheets. Start running your business.", + "Bestellverwaltung, Lager und Rechnungsstellung für den luxemburgischen E-Commerce. Schluss mit Tabellenkalkulationen. Führen Sie Ihr Geschäft.", + ), + "background_type": "gradient", + "buttons": [ + { + "text": t( + "Essai gratuit", + "Start Free Trial", + "Kostenlos testen", + ), + "url": "/signup", + "style": "primary", + }, + { + "text": t( + "Trouvez votre boutique Letzshop", + "Find Your Letzshop Shop", + "Finden Sie Ihren Letzshop", + ), + "url": "#find-shop", + "style": "secondary", + }, + ], + }, + "features": { + "enabled": True, + "title": t( + "Tout ce dont un vendeur Letzshop a besoin", + "Everything a Letzshop Seller Needs", + "Alles, was ein Letzshop-Verkäufer braucht", + ), + "subtitle": t( + "Les outils opérationnels que Letzshop ne fournit pas", + "The operational tools Letzshop doesn't provide", + "Die operativen Tools, die Letzshop nicht bietet", + ), + "layout": "grid", + "features": [ + { + "icon": "refresh", + "title": t( + "Synchronisation Letzshop", + "Letzshop Order Sync", + "Letzshop-Synchronisierung", + ), + "description": t( + "Les commandes se synchronisent automatiquement. Confirmez et ajoutez le suivi depuis Orion.", + "Orders sync automatically. Confirm and add tracking directly from Orion.", + "Bestellungen werden automatisch synchronisiert. Bestätigen und Tracking direkt von Orion hinzufügen.", + ), + }, + { + "icon": "cube", + "title": t( + "Gestion des stocks", + "Inventory Management", + "Lagerverwaltung", + ), + "description": t( + "Suivez vos stocks en temps réel avec emplacements d'entrepôt et bons de commande.", + "Track inventory in real-time with warehouse locations and purchase orders.", + "Verfolgen Sie Lagerbestände in Echtzeit mit Lagerstandorten und Bestellungen.", + ), + }, + { + "icon": "document-text", + "title": t( + "Facturation TVA UE", + "EU VAT Invoicing", + "EU-MwSt-Rechnungen", + ), + "description": t( + "Générez des factures PDF conformes avec la TVA correcte pour tout pays UE.", + "Generate compliant PDF invoices with correct VAT for any EU country.", + "Erstellen Sie konforme PDF-Rechnungen mit korrekter MwSt für jedes EU-Land.", + ), + }, + { + "icon": "users", + "title": t( + "Données clients", + "Customer Data", + "Kundendaten", + ), + "description": t( + "Possédez vos données clients. Exportez pour le marketing et la fidélisation.", + "Own your customer data. Export for marketing and loyalty building.", + "Besitzen Sie Ihre Kundendaten. Exportieren Sie für Marketing und Kundenbindung.", + ), + }, + ], + }, + "pricing": { + "enabled": True, + "title": t( + "Tarification simple et transparente", + "Simple, Transparent Pricing", + "Einfache, transparente Preise", + ), + "subtitle": t( + "Choisissez le plan adapté à votre entreprise. Tous les plans incluent un essai gratuit.", + "Choose the plan that fits your business. All plans include a free trial.", + "Wählen Sie den Plan, der zu Ihrem Unternehmen passt. Alle Pläne beinhalten eine kostenlose Testversion.", + ), + "use_subscription_tiers": True, + }, + "cta": { + "enabled": True, + "title": t( + "Prêt à optimiser vos commandes ?", + "Ready to Streamline Your Orders?", + "Bereit, Ihre Bestellungen zu optimieren?", + ), + "subtitle": t( + "Rejoignez les vendeurs Letzshop qui font confiance à Orion pour leur gestion de commandes.", + "Join Letzshop stores who trust Orion for their order management.", + "Schließen Sie sich Letzshop-Händlern an, die Orion für ihre Bestellverwaltung vertrauen.", + ), + "background_type": "gradient", + "buttons": [ + { + "text": t( + "Essai gratuit", + "Start Free Trial", + "Kostenlos testen", + ), + "url": "/signup", + "style": "primary", + }, + ], + }, + } + + +def _loyalty_homepage_sections() -> dict: + """Loyalty (rewardflow.lu) — loyalty program focused homepage.""" + return { + "hero": { + "enabled": True, + "badge_text": t( + "Essai gratuit — Aucune carte de crédit requise", + "Free Trial — No Credit Card Required", + "Kostenlose Testversion — Keine Kreditkarte erforderlich", + ), + "title": t( + "Fidélisation client simplifiée", + "Customer Loyalty Made Simple", + "Kundentreue leicht gemacht", + ), + "subtitle": t( + "Créez des programmes de fidélité engageants avec points, récompenses et niveaux. Conçu pour les commerces luxembourgeois.", + "Create engaging loyalty programs with points, rewards, and tiers. Built for Luxembourg businesses.", + "Erstellen Sie ansprechende Treueprogramme mit Punkten, Prämien und Stufen. Für luxemburgische Unternehmen.", + ), + "background_type": "gradient", + "buttons": [ + { + "text": t( + "Essai gratuit", + "Start Free Trial", + "Kostenlos testen", + ), + "url": "/signup", + "style": "primary", + }, + { + "text": t( + "Voir les fonctionnalités", + "See Features", + "Funktionen ansehen", + ), + "url": "#features", + "style": "secondary", + }, + ], + }, + "features": { + "enabled": True, + "title": t( + "Tout pour fidéliser vos clients", + "Everything to Build Customer Loyalty", + "Alles für die Kundenbindung", + ), + "subtitle": t( + "Des outils puissants pour créer et gérer vos programmes de fidélité", + "Powerful tools to create and manage your loyalty programs", + "Leistungsstarke Tools zum Erstellen und Verwalten Ihrer Treueprogramme", + ), + "layout": "grid", + "features": [ + { + "icon": "star", + "title": t( + "Système de points", + "Points System", + "Punktesystem", + ), + "description": t( + "Attribuez des points pour chaque achat. Règles flexibles et multiplicateurs personnalisables.", + "Award points for every purchase. Flexible rules and customizable multipliers.", + "Vergeben Sie Punkte für jeden Einkauf. Flexible Regeln und anpassbare Multiplikatoren.", + ), + }, + { + "icon": "gift", + "title": t( + "Catalogue de récompenses", + "Rewards Catalog", + "Prämienkatalog", + ), + "description": t( + "Créez un catalogue de récompenses attrayant. Produits, remises ou expériences exclusives.", + "Create an attractive rewards catalog. Products, discounts, or exclusive experiences.", + "Erstellen Sie einen attraktiven Prämienkatalog. Produkte, Rabatte oder exklusive Erlebnisse.", + ), + }, + { + "icon": "trending-up", + "title": t( + "Gestion des niveaux", + "Tier Management", + "Stufenverwaltung", + ), + "description": t( + "Bronze, Argent, Or — motivez vos clients avec des niveaux de fidélité progressifs.", + "Bronze, Silver, Gold — motivate customers with progressive loyalty tiers.", + "Bronze, Silber, Gold — motivieren Sie Kunden mit progressiven Treuestufen.", + ), + }, + { + "icon": "chart-bar", + "title": t( + "Analytique avancée", + "Advanced Analytics", + "Erweiterte Analytik", + ), + "description": t( + "Suivez l'engagement, les taux de rétention et le ROI de vos programmes.", + "Track engagement, retention rates, and program ROI.", + "Verfolgen Sie Engagement, Bindungsraten und Programm-ROI.", + ), + }, + ], + }, + "pricing": { + "enabled": True, + "title": t( + "Tarification simple et transparente", + "Simple, Transparent Pricing", + "Einfache, transparente Preise", + ), + "subtitle": t( + "Choisissez le plan adapté à votre programme de fidélité.", + "Choose the plan that fits your loyalty program.", + "Wählen Sie den Plan, der zu Ihrem Treueprogramm passt.", + ), + "use_subscription_tiers": True, + }, + "cta": { + "enabled": True, + "title": t( + "Prêt à fidéliser vos clients ?", + "Ready to Build Customer Loyalty?", + "Bereit, Kundentreue aufzubauen?", + ), + "subtitle": t( + "Commencez votre programme de fidélité dès aujourd'hui. Essai gratuit, sans engagement.", + "Start your loyalty program today. Free trial, no commitment.", + "Starten Sie Ihr Treueprogramm noch heute. Kostenlose Testversion, ohne Verpflichtung.", + ), + "background_type": "gradient", + "buttons": [ + { + "text": t( + "Essai gratuit", + "Start Free Trial", + "Kostenlos testen", + ), + "url": "/signup", + "style": "primary", + }, + ], + }, + } + + +HOMEPAGE_SECTIONS = { + "main": _wizard_homepage_sections, + "oms": _oms_homepage_sections, + "loyalty": _loyalty_homepage_sections, } + # ============================================================================ -# DEFAULT STORE CONTENT PAGES (is_platform_page=False) +# PLATFORM MARKETING PAGES (is_platform_page=True) # ============================================================================ -DEFAULT_PAGES = [ + +def _get_platform_pages(platform_code: str) -> list[dict]: + """Get platform marketing pages for a given platform code.""" + + if platform_code == "main": + return [ + { + "slug": "about", + "title": "About Wizard", + "content": """
+

About Wizard

+

Wizard is the digital toolkit for Luxembourg businesses. We provide integrated solutions for order management, customer loyalty, and online presence.

+

Our Mission

+

To empower Luxembourg businesses with modern, easy-to-use digital tools that help them grow and thrive in the digital age.

+

Our Solutions

+
    +
  • OMS (omsflow.lu): Order management, inventory, and invoicing for Letzshop sellers
  • +
  • Loyalty (rewardflow.lu): Customer loyalty programs with points, rewards, and tiers
  • +
  • Website Builder: Coming soon — build your online presence
  • +
+

Built in Luxembourg

+

We understand the unique needs of Luxembourg commerce — from multilingual support (FR/DE/EN) to EU VAT compliance.

+
""", + "meta_description": "Wizard — the digital toolkit for Luxembourg businesses. Order management, loyalty programs, and more.", + "show_in_footer": True, + "show_in_header": True, + "display_order": 1, + }, + { + "slug": "contact", + "title": "Contact Us", + "content": """
+

Contact Wizard

+

We'd love to hear from you. Get in touch with our team.

+

General Inquiries

+
    +
  • Email: info@wizard.lu
  • +
+

Sales

+

Interested in our solutions for your business?

+
    +
  • Email: sales@wizard.lu
  • +
+

Support

+

Already a customer? Our support team is here to help.

+
    +
  • Email: support@wizard.lu
  • +
+

Office

+

Luxembourg

+
""", + "meta_description": "Contact the Wizard team for inquiries about our business solutions.", + "show_in_footer": True, + "show_in_header": True, + "display_order": 2, + }, + { + "slug": "faq", + "title": "FAQ", + "content": """
+

Frequently Asked Questions

+

General

+

What is Wizard?

+

Wizard is a suite of digital tools for Luxembourg businesses, including order management (OMS), customer loyalty programs, and website building.

+

Who is Wizard for?

+

Wizard is designed for Luxembourg businesses of all sizes — from individual Letzshop sellers to multi-store retailers.

+

Pricing & Billing

+

Is there a free trial?

+

Yes! All our solutions offer a free trial period. No credit card required to start.

+

Can I switch plans?

+

Yes, you can upgrade or downgrade your plan at any time.

+

Technical

+

What languages are supported?

+

Our platform supports French, German, and English — the three official languages of Luxembourg.

+

Is my data secure?

+

Yes. We use industry-standard encryption and follow GDPR regulations for data protection.

+
""", + "meta_description": "Frequently asked questions about Wizard solutions for Luxembourg businesses.", + "show_in_footer": True, + "show_in_header": False, + "display_order": 3, + }, + ] + + if platform_code == "oms": + return [ + { + "slug": "about", + "title": "About OMS", + "content": """
+

About OMS by Wizard

+

OMS (omsflow.lu) is a lightweight order management system built specifically for Letzshop sellers in Luxembourg.

+

Why OMS?

+

Letzshop is a great marketplace, but it doesn't give sellers the back-office tools they need. OMS fills that gap with:

+
    +
  • Automatic order sync from Letzshop
  • +
  • Inventory management with warehouse locations
  • +
  • EU VAT invoicing with correct rates per country
  • +
  • Customer data ownership for marketing and retention
  • +
+

Built for Luxembourg

+

From multilingual support to Luxembourg VAT compliance, OMS is designed for the local market.

+
""", + "meta_description": "OMS — lightweight order management for Letzshop sellers. Manage orders, inventory, and invoicing.", + "show_in_footer": True, + "show_in_header": True, + "display_order": 1, + }, + { + "slug": "contact", + "title": "Contact OMS Support", + "content": """
+

Contact OMS Support

+

Need help with your order management? We're here for you.

+

Support

+
    +
  • Email: support@omsflow.lu
  • +
+

Sales

+

Interested in OMS for your Letzshop store?

+
    +
  • Email: sales@omsflow.lu
  • +
+
""", + "meta_description": "Contact the OMS support team for help with order management.", + "show_in_footer": True, + "show_in_header": True, + "display_order": 2, + }, + { + "slug": "faq", + "title": "OMS FAQ", + "content": """
+

Frequently Asked Questions

+

Do I need a Letzshop account?

+

While OMS works best with Letzshop integration, you can also use it as a standalone order management tool.

+

How does Letzshop sync work?

+

Enter your Letzshop API credentials and orders sync automatically. Setup takes about 2 minutes.

+

Is EU VAT invoicing included?

+

Yes, EU VAT invoicing is available on Professional and Enterprise plans. Luxembourg VAT is available on all plans.

+

Can I export my data?

+

Yes, you can export customers, orders, and invoices at any time.

+

Is there a free trial?

+

Yes! Start with a free trial, no credit card required.

+
""", + "meta_description": "Frequently asked questions about OMS order management for Letzshop sellers.", + "show_in_footer": True, + "show_in_header": False, + "display_order": 3, + }, + ] + + if platform_code == "loyalty": + return [ + { + "slug": "about", + "title": "About Loyalty", + "content": """
+

About Loyalty by Wizard

+

Loyalty (rewardflow.lu) helps Luxembourg businesses create engaging customer loyalty programs.

+

What We Offer

+
    +
  • Points system: Award points for purchases with flexible rules
  • +
  • Rewards catalog: Products, discounts, or exclusive experiences
  • +
  • Tier management: Bronze, Silver, Gold — progressive loyalty levels
  • +
  • Analytics: Track engagement, retention, and program ROI
  • +
+

Built for Luxembourg

+

Multilingual support (FR/DE/EN) and local business features out of the box.

+
""", + "meta_description": "Loyalty — customer loyalty programs for Luxembourg businesses. Points, rewards, and tiers.", + "show_in_footer": True, + "show_in_header": True, + "display_order": 1, + }, + { + "slug": "contact", + "title": "Contact Loyalty Support", + "content": """
+

Contact Loyalty Support

+

Need help with your loyalty program? We're here for you.

+

Support

+
    +
  • Email: support@rewardflow.lu
  • +
+

Sales

+

Interested in a loyalty program for your business?

+
    +
  • Email: sales@rewardflow.lu
  • +
+
""", + "meta_description": "Contact the Loyalty support team for help with your loyalty program.", + "show_in_footer": True, + "show_in_header": True, + "display_order": 2, + }, + { + "slug": "faq", + "title": "Loyalty FAQ", + "content": """
+

Frequently Asked Questions

+

How do loyalty points work?

+

You define the rules — for example, 1 point per euro spent. Points can be redeemed for rewards from your catalog.

+

Can I customize the loyalty tiers?

+

Yes! You can create custom tiers with different names, thresholds, and benefits.

+

How do customers check their points?

+

Customers can check their balance through a personalized loyalty page or at your point of sale.

+

Is there a free trial?

+

Yes! Start with a free trial, no credit card required.

+
""", + "meta_description": "Frequently asked questions about Loyalty customer loyalty programs.", + "show_in_footer": True, + "show_in_header": False, + "display_order": 3, + }, + ] + + return [] + + +# Shared legal pages (same content for all platforms) +SHARED_PLATFORM_PAGES = [ + { + "slug": "privacy", + "title": "Privacy Policy", + "content": """
+

Privacy Policy

+

Last Updated: February 2026

+

Information We Collect

+

We collect information you provide directly:

+
    +
  • Name, email address, and contact information
  • +
  • Business information (for merchants)
  • +
  • Payment information (processed securely by our payment processor)
  • +
+

How We Use Your Information

+

We use your information to provide and improve our services, process payments, communicate with you, and comply with legal obligations.

+

Data Protection (GDPR)

+

We comply with the EU General Data Protection Regulation. You have the right to access, correct, delete, or export your personal data at any time.

+

Contact

+

For privacy-related questions, contact privacy@wizard.lu

+
""", + "meta_description": "Privacy policy — how we collect, use, and protect your personal information.", + "show_in_footer": False, + "show_in_header": False, + "show_in_legal": True, + "display_order": 10, + }, + { + "slug": "terms", + "title": "Terms of Service", + "content": """
+

Terms of Service

+

Last Updated: February 2026

+

1. Acceptance of Terms

+

By accessing and using this platform, you accept and agree to be bound by these Terms of Service.

+

2. Services

+

We provide digital business tools including order management, loyalty programs, and website building services.

+

3. Account

+

You must provide accurate information and maintain the security of your account.

+

4. Payments

+

Subscription fees are billed monthly or annually. You may cancel at any time.

+

5. Data & Privacy

+

Your use of our services is also governed by our Privacy Policy.

+

6. Governing Law

+

These terms are governed by the laws of Luxembourg.

+

Contact

+

For questions about these terms, contact legal@wizard.lu

+
""", + "meta_description": "Terms of service governing the use of our platform.", + "show_in_footer": False, + "show_in_header": False, + "show_in_legal": True, + "display_order": 11, + }, +] + + +# ============================================================================ +# STORE DEFAULT PAGES (is_platform_page=False, store_id=NULL) +# Uses {{store_name}}, {{store_email}}, {{store_phone}} placeholders +# ============================================================================ + + +STORE_DEFAULTS_COMMON = [ { "slug": "about", "title": "About Us", - "content": """ -
-

Welcome to Our Platform

-

We are a multi-store e-commerce platform connecting quality sellers with customers worldwide.

- -

Our Mission

-

To empower independent businesses and artisans by providing them with the tools and platform they need to reach customers globally.

- -

What We Offer

+ "content": """
+

About {{store_name}}

+

Welcome to {{store_name}}. We are committed to providing you with quality products and excellent service.

+

Our Story

+

{{store_name}} was founded with a simple mission: to deliver exceptional value to our customers.

+

Contact Us

+

Have questions? We'd love to hear from you.

    -
  • Curated selection of quality products from verified stores
  • -
  • Secure payment processing and buyer protection
  • -
  • Fast and reliable shipping options
  • -
  • Dedicated customer support
  • +
  • Email: {{store_email}}
  • +
  • Phone: {{store_phone}}
- -

Our Values

-
    -
  • Quality: We work only with stores who meet our quality standards
  • -
  • Transparency: Clear pricing, policies, and communication
  • -
  • Customer First: Your satisfaction is our priority
  • -
  • Innovation: Continuously improving our platform and services
  • -
-
- """, - "meta_description": "Learn about our mission to connect quality stores with customers worldwide", - "meta_keywords": "about us, mission, values, platform", +
""", + "meta_description": "Learn about {{store_name}} — our story, values, and commitment to quality.", "show_in_footer": True, "show_in_header": True, "display_order": 1, @@ -128,140 +820,95 @@ DEFAULT_PAGES = [ { "slug": "contact", "title": "Contact Us", - "content": """ -
-

Get in Touch

-

We're here to help! Reach out to us through any of the following channels:

- -

Customer Support

-

For questions about orders, products, or general inquiries:

+ "content": """
+

Contact {{store_name}}

+

We're here to help! Reach out to us.

+

Get in Touch

    -
  • Email: support@example.com
  • -
  • Phone: 1-800-EXAMPLE (Mon-Fri, 9am-6pm EST)
  • -
  • Live Chat: Available on our website during business hours
  • +
  • Email: {{store_email}}
  • +
  • Phone: {{store_phone}}
- -

Business Inquiries

-

Interested in becoming a store or partnering with us?

-
    -
  • Email: stores@example.com
  • -
- -

Office Address

-

- 123 Commerce Street
- Suite 456
- City, State 12345
- United States -

- -

Response Time

-

We typically respond to all inquiries within 24 hours during business days.

-
- """, - "meta_description": "Contact our customer support team for assistance with orders, products, or inquiries", - "meta_keywords": "contact, support, customer service, help", +

We typically respond within 24 hours during business days.

+
""", + "meta_description": "Contact {{store_name}} for questions, support, or inquiries.", "show_in_footer": True, "show_in_header": True, "display_order": 2, }, { "slug": "faq", - "title": "Frequently Asked Questions", - "content": """ -
+ "title": "FAQ", + "content": """

Frequently Asked Questions

- -

Orders & Shipping

- -

How do I track my order?

-

Once your order ships, you'll receive a tracking number via email. You can also view your order status in your account dashboard.

- -

How long does shipping take?

-

Shipping times vary by store and destination. Most orders arrive within 3-7 business days. See our Shipping Policy for details.

- -

Do you ship internationally?

-

Yes! We ship to most countries worldwide. International shipping times and costs vary by destination.

- -

Returns & Refunds

- -

What is your return policy?

-

Most items can be returned within 30 days of delivery. See our Return Policy for complete details.

- -

How do I request a refund?

-

Contact the store directly through your order page or reach out to our support team for assistance.

- -

Payments

- -

What payment methods do you accept?

-

We accept all major credit cards, PayPal, and other secure payment methods.

- -

Is my payment information secure?

-

Yes! We use industry-standard encryption and never store your full payment details.

- -

Account

- -

Do I need an account to make a purchase?

-

No, you can checkout as a guest. However, creating an account lets you track orders and save your preferences.

- -

How do I reset my password?

-

Click "Forgot Password" on the login page and follow the instructions sent to your email.

-
- """, - "meta_description": "Find answers to common questions about orders, shipping, returns, and more", - "meta_keywords": "faq, questions, help, support", +

How can I contact you?

+

You can reach us at {{store_email}} or call {{store_phone}}.

+

What are your business hours?

+

We are available during regular business hours, Monday through Friday.

+

Do you have a physical location?

+

Please contact us for information about our location and visiting hours.

+
""", + "meta_description": "Frequently asked questions about {{store_name}}.", "show_in_footer": True, "show_in_header": False, "display_order": 3, }, + { + "slug": "privacy", + "title": "Privacy Policy", + "content": """
+

Privacy Policy

+

{{store_name}} is committed to protecting your personal information.

+

Information We Collect

+

We collect information necessary to process your orders and provide customer service.

+

Your Rights

+

Under GDPR, you have the right to access, correct, or delete your personal data. Contact us at {{store_email}}.

+
""", + "meta_description": "Privacy policy for {{store_name}}.", + "show_in_footer": False, + "show_in_header": False, + "show_in_legal": True, + "display_order": 10, + }, + { + "slug": "terms", + "title": "Terms of Service", + "content": """
+

Terms of Service

+

By using the services provided by {{store_name}}, you agree to these terms.

+

Orders

+

All orders are subject to availability and confirmation.

+

Contact

+

For questions about these terms, contact us at {{store_email}}.

+
""", + "meta_description": "Terms of service for {{store_name}}.", + "show_in_footer": False, + "show_in_header": False, + "show_in_legal": True, + "display_order": 11, + }, +] + +# OMS-specific store defaults (shipping/returns) +STORE_DEFAULTS_OMS_EXTRA = [ { "slug": "shipping", "title": "Shipping Policy", - "content": """ -
+ "content": """

Shipping Policy

-

Shipping Methods

-

We offer multiple shipping options to meet your needs:

+

{{store_name}} offers multiple shipping options:

    -
  • Standard Shipping: 5-7 business days
  • -
  • Express Shipping: 2-3 business days
  • -
  • Overnight Shipping: Next business day
  • +
  • Standard Shipping: 3-7 business days
  • +
  • Express Shipping: 1-3 business days
-

Shipping Costs

-

Shipping costs are calculated based on:

-
    -
  • Package weight and dimensions
  • -
  • Destination address
  • -
  • Selected shipping method
  • -
-

Free standard shipping on orders over $50!

- -

Processing Time

-

Orders are typically processed within 1-2 business days. You'll receive a confirmation email when your order ships with tracking information.

- -

International Shipping

-

We ship to most countries worldwide. International shipping times vary by destination (typically 7-21 business days).

-
    -
  • Customs fees and import duties are the responsibility of the recipient
  • -
  • International orders may be subject to customs inspection
  • -
- +

Shipping costs are calculated based on weight and destination at checkout.

Tracking

-

All orders include tracking. You can monitor your package status through:

-
    -
  • Your account dashboard
  • -
  • Email notifications
  • -
  • Carrier tracking website
  • -
- -

Delivery Issues

-

If you experience any delivery issues, please contact us within 7 days of the expected delivery date.

-
- """, - "meta_description": "Learn about our shipping methods, costs, and delivery times", - "meta_keywords": "shipping, delivery, tracking, international", +

You will receive a tracking number by email once your order ships.

+

Questions?

+

Contact us at {{store_email}} for shipping inquiries.

+
""", + "meta_description": "Shipping policy for {{store_name}} — methods, costs, and delivery times.", "show_in_footer": True, "show_in_header": False, "display_order": 4, @@ -269,236 +916,26 @@ DEFAULT_PAGES = [ { "slug": "returns", "title": "Return & Refund Policy", - "content": """ -
+ "content": """

Return & Refund Policy

- -

30-Day Return Window

-

Most items can be returned within 30 days of delivery for a full refund or exchange.

- -

Return Requirements

-

To be eligible for a return, items must:

-
    -
  • Be in original condition with tags attached
  • -
  • Include original packaging and accessories
  • -
  • Not be used or damaged
  • -
  • Include proof of purchase
  • -
- -

Non-Returnable Items

-

The following items cannot be returned:

-
    -
  • Personalized or custom-made items
  • -
  • Perishable goods
  • -
  • Health and personal care items
  • -
  • Digital downloads
  • -
  • Sale or clearance items (marked as final sale)
  • -
- +

Returns

+

{{store_name}} accepts returns within 14 days of delivery, in accordance with Luxembourg consumer protection law.

How to Return

    -
  1. Log into your account and go to "Order History"
  2. -
  3. Select the order and click "Request Return"
  4. -
  5. Choose items and provide reason for return
  6. -
  7. Print the prepaid return label
  8. -
  9. Pack items securely and attach label
  10. -
  11. Drop off at any authorized carrier location
  12. +
  13. Contact us at {{store_email}} to initiate a return
  14. +
  15. Pack the item in its original condition
  16. +
  17. Ship using the provided return instructions
- -

Refund Process

-

Once we receive your return:

-
    -
  • We'll inspect the items (2-3 business days)
  • -
  • Approved refunds are processed within 5-7 business days
  • -
  • Refunds go to original payment method
  • -
  • You'll receive email confirmation
  • -
- -

Return Shipping

-

Return shipping is free for defective or incorrect items. For other returns, a $5.99 return shipping fee will be deducted from your refund.

- -

Exchanges

-

We offer free exchanges for different sizes or colors of the same item. Contact support to arrange an exchange.

- -

Damaged or Defective Items

-

If you receive a damaged or defective item, please contact us within 7 days of delivery with photos. We'll send a replacement or full refund immediately.

-
- """, - "meta_description": "Our 30-day return policy ensures your satisfaction with every purchase", - "meta_keywords": "returns, refunds, exchange, policy", +

Refunds

+

Refunds are processed within 14 days of receiving the returned item, back to the original payment method.

+

Damaged Items

+

If you receive a damaged item, contact us immediately at {{store_email}} with photos.

+
""", + "meta_description": "Return and refund policy for {{store_name}}.", "show_in_footer": True, "show_in_header": False, "display_order": 5, }, - { - "slug": "privacy", - "title": "Privacy Policy", - "content": """ -
-

Privacy Policy

-

Last Updated: [Date]

- -

Information We Collect

-

We collect information you provide directly:

-
    -
  • Name, email address, and contact information
  • -
  • Shipping and billing addresses
  • -
  • Payment information (processed securely by our payment processor)
  • -
  • Order history and preferences
  • -
- -

We automatically collect:

-
    -
  • Browser type and device information
  • -
  • IP address and location data
  • -
  • Cookies and similar tracking technologies
  • -
  • Usage data and analytics
  • -
- -

How We Use Your Information

-

We use your information to:

-
    -
  • Process and fulfill your orders
  • -
  • Communicate about orders and account
  • -
  • Provide customer support
  • -
  • Send marketing communications (with your consent)
  • -
  • Improve our platform and services
  • -
  • Prevent fraud and enhance security
  • -
- -

Information Sharing

-

We share your information only when necessary:

-
    -
  • Stores: To fulfill your orders
  • -
  • Service Providers: Payment processors, shipping carriers, analytics
  • -
  • Legal Requirements: When required by law
  • -
-

We never sell your personal information to third parties.

- -

Your Rights

-

You have the right to:

-
    -
  • Access your personal data
  • -
  • Correct inaccurate information
  • -
  • Request deletion of your data
  • -
  • Opt-out of marketing communications
  • -
  • Export your data
  • -
- -

Data Security

-

We implement industry-standard security measures including:

-
    -
  • SSL/TLS encryption for data transmission
  • -
  • Secure password hashing
  • -
  • Regular security audits
  • -
  • Limited employee access to personal data
  • -
- -

Cookies

-

We use cookies to enhance your experience. You can control cookies through your browser settings.

- -

Children's Privacy

-

Our platform is not intended for children under 13. We do not knowingly collect information from children.

- -

Changes to This Policy

-

We may update this policy periodically. Significant changes will be communicated via email.

- -

Contact Us

-

For privacy-related questions, contact us at privacy@example.com

-
- """, - "meta_description": "Learn how we collect, use, and protect your personal information", - "meta_keywords": "privacy, data protection, security, policy", - "show_in_footer": False, - "show_in_header": False, - "show_in_legal": True, - "display_order": 6, - }, - { - "slug": "terms", - "title": "Terms of Service", - "content": """ -
-

Terms of Service

-

Last Updated: [Date]

- -

1. Acceptance of Terms

-

By accessing and using this platform, you accept and agree to be bound by these Terms of Service.

- -

2. Account Registration

-

You must:

-
    -
  • Be at least 18 years old
  • -
  • Provide accurate and complete information
  • -
  • Maintain the security of your account
  • -
  • Notify us of unauthorized access
  • -
- -

3. User Conduct

-

You agree not to:

-
    -
  • Violate any laws or regulations
  • -
  • Infringe on intellectual property rights
  • -
  • Transmit harmful code or viruses
  • -
  • Harass or harm other users
  • -
  • Engage in fraudulent activities
  • -
  • Scrape or data mine our platform
  • -
- -

4. Product Listings

-

Product information is provided by stores. While we strive for accuracy:

-
    -
  • Descriptions may contain errors
  • -
  • Prices are subject to change
  • -
  • Availability is not guaranteed
  • -
  • Images are representative
  • -
- -

5. Orders and Payment

-
    -
  • All orders are subject to acceptance
  • -
  • We reserve the right to refuse or cancel orders
  • -
  • Prices include applicable taxes unless stated otherwise
  • -
  • Payment must be received before order processing
  • -
- -

6. Intellectual Property

-

All content on this platform is protected by copyright, trademark, and other laws. You may not:

-
    -
  • Reproduce or distribute our content
  • -
  • Create derivative works
  • -
  • Use our trademarks without permission
  • -
- -

7. Disclaimer of Warranties

-

This platform is provided "as is" without warranties of any kind, express or implied.

- -

8. Limitation of Liability

-

We are not liable for indirect, incidental, or consequential damages arising from your use of the platform.

- -

9. Indemnification

-

You agree to indemnify and hold us harmless from claims arising from your use of the platform or violation of these terms.

- -

10. Dispute Resolution

-

Disputes will be resolved through binding arbitration in accordance with the rules of the American Arbitration Association.

- -

11. Governing Law

-

These terms are governed by the laws of [State/Country], without regard to conflict of law principles.

- -

12. Changes to Terms

-

We may modify these terms at any time. Continued use constitutes acceptance of modified terms.

- -

13. Contact

-

For questions about these terms, contact legal@example.com

-
- """, - "meta_description": "Read our terms of service governing the use of our platform", - "meta_keywords": "terms, conditions, legal, agreement", - "show_in_footer": False, - "show_in_header": False, - "show_in_legal": True, - "display_order": 7, - }, ] @@ -507,26 +944,32 @@ DEFAULT_PAGES = [ # ============================================================================ -def _page_exists(db: Session, platform_id: int, slug: str) -> bool: - """Check if a page already exists for the given platform and slug.""" +def _page_exists(db: Session, platform_id: int, slug: str, *, is_platform_page: bool) -> bool: + """Check if a page already exists for the given platform, slug, and type.""" existing = db.execute( select(ContentPage).where( ContentPage.platform_id == platform_id, ContentPage.store_id.is_(None), ContentPage.slug == slug, + ContentPage.is_platform_page == is_platform_page, ) ).scalar_one_or_none() return existing is not None def _create_page( - db: Session, platform_id: int, page_data: dict, *, is_platform_page: bool = False + db: Session, + platform_id: int, + page_data: dict, + *, + is_platform_page: bool = False, + sections: dict | None = None, ) -> bool: """Create a single content page. Returns True if created, False if skipped.""" slug = page_data["slug"] title = page_data["title"] - if _page_exists(db, platform_id, slug): + if _page_exists(db, platform_id, slug, is_platform_page=is_platform_page): print(f" Skipped: {title} (/{slug}) - already exists") return False @@ -535,35 +978,37 @@ def _create_page( store_id=None, slug=slug, title=title, - content=page_data["content"], + content=page_data.get("content", ""), content_format="html", template=page_data.get("template", "default"), - meta_description=page_data["meta_description"], - meta_keywords=page_data["meta_keywords"], + sections=sections, + meta_description=page_data.get("meta_description", ""), + meta_keywords=page_data.get("meta_keywords", ""), is_platform_page=is_platform_page, is_published=True, published_at=datetime.now(UTC), - show_in_footer=page_data.get("show_in_footer", True), + show_in_footer=page_data.get("show_in_footer", False), show_in_header=page_data.get("show_in_header", False), show_in_legal=page_data.get("show_in_legal", False), - display_order=page_data["display_order"], + display_order=page_data.get("display_order", 0), created_at=datetime.now(UTC), updated_at=datetime.now(UTC), ) db.add(page) - print(f" Created: {title} (/{slug})") + tier_label = "platform" if is_platform_page else "store-default" + print(f" Created [{tier_label}]: {title} (/{slug})") return True def create_default_pages(db: Session) -> None: """ - Create default platform content pages for ALL platforms. + Create all default content pages for all platforms. - This function is idempotent - it will skip pages that already exist. + This function is idempotent — it skips pages that already exist. """ print("\n" + "=" * 70) - print("Creating Default Platform Content Pages (CMS)") + print("Creating Default Content Pages (CMS)") print("=" * 70 + "\n") # Load all platforms @@ -576,33 +1021,79 @@ def create_default_pages(db: Session) -> None: total_skipped = 0 for platform in platforms: - print(f" Platform: {platform.name} (code={platform.code})") - + print(f"\n Platform: {platform.name} (code={platform.code})") created_count = 0 skipped_count = 0 - # Create platform homepage - if _create_page(db, platform.id, PLATFORM_HOMEPAGE, is_platform_page=True): - created_count += 1 + # ------------------------------------------------------------------ + # 1. Platform Homepage (slug="home", is_platform_page=True, sections) + # ------------------------------------------------------------------ + sections_fn = HOMEPAGE_SECTIONS.get(platform.code) + if sections_fn: + sections_data = sections_fn() + homepage_data = { + "slug": "home", + "title": f"{platform.name} - Home", + "content": "", + "meta_description": f"{platform.name} — digital tools for Luxembourg businesses.", + "show_in_footer": False, + "show_in_header": False, + "display_order": 0, + } + if _create_page( + db, platform.id, homepage_data, + is_platform_page=True, sections=sections_data, + ): + created_count += 1 + else: + skipped_count += 1 else: - skipped_count += 1 + print(f" Warning: No homepage sections defined for platform '{platform.code}'") - # Create default store content pages - for page_data in DEFAULT_PAGES: - if _create_page(db, platform.id, page_data): + # ------------------------------------------------------------------ + # 2. Platform Marketing Pages (is_platform_page=True) + # ------------------------------------------------------------------ + platform_pages = _get_platform_pages(platform.code) + for page_data in platform_pages: + if _create_page(db, platform.id, page_data, is_platform_page=True): created_count += 1 else: skipped_count += 1 + # Shared legal pages (privacy, terms) for all platforms + for page_data in SHARED_PLATFORM_PAGES: + if _create_page(db, platform.id, page_data, is_platform_page=True): + created_count += 1 + else: + skipped_count += 1 + + # ------------------------------------------------------------------ + # 3. Store Default Pages (is_platform_page=False, store_id=NULL) + # Only for platforms that host stores (not wizard.lu main) + # ------------------------------------------------------------------ + if platform.code != "main": + for page_data in STORE_DEFAULTS_COMMON: + if _create_page(db, platform.id, page_data, is_platform_page=False): + created_count += 1 + else: + skipped_count += 1 + + # OMS-specific: shipping + returns + if platform.code == "oms": + for page_data in STORE_DEFAULTS_OMS_EXTRA: + if _create_page(db, platform.id, page_data, is_platform_page=False): + created_count += 1 + else: + skipped_count += 1 + print(f" --- {created_count} created, {skipped_count} skipped") - print() total_created += created_count total_skipped += skipped_count db.commit() - print("=" * 70) + print("\n" + "=" * 70) print("Summary:") print(f" Platforms: {len(platforms)}") print(f" Created: {total_created} pages") @@ -611,13 +1102,7 @@ def create_default_pages(db: Session) -> None: print("=" * 70 + "\n") if total_created > 0: - print("Default platform content pages created successfully!\n") - print("Next steps:") - print( - " 1. View pages at: /about, /contact, /faq, /shipping, /returns, /privacy, /terms" - ) - print(" 2. Stores can override these pages through the store dashboard") - print(" 3. Edit platform defaults through the admin panel\n") + print("Default content pages created successfully!\n") else: print("All default pages already exist. No changes made.\n")