feat(storefront): section-based homepages, header action partials, fixes

Phase 1 — Section-based store homepages:
- Store defaults use template="full" with per-platform sections JSON
- OMS: shop hero + features + CTA; Loyalty: rewards hero + features + CTA
- Hosting: services hero + features + CTA
- Deep placeholder resolution for {{store_name}} inside sections JSON
- landing-full.html uses resolved page_sections from context

Phase 2 — Module-contributed header actions:
- header_template field on MenuItemDefinition + DiscoveredMenuItem
- Catalog provides header-search.html partial
- Cart provides header-cart.html partial with badge
- Base template iterates storefront_nav.actions with {% include %}
- Generic icon fallback for actions without a template

Fixes:
- Store theme API: get_store_by_code → get_store_by_code_or_subdomain

Docs:
- CMS redesign proposal: menu restructure, page types, translations UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 23:33:06 +02:00
parent adc36246b8
commit 3044490a3e
13 changed files with 641 additions and 73 deletions

View File

@@ -2099,47 +2099,319 @@ SHARED_PLATFORM_PAGES = [
STORE_DEFAULT_HOME = {
"slug": "home",
"title": "Welcome to {{store_name}}",
"title": "{{store_name}}",
"title_translations": tt(
"Welcome to {{store_name}}",
"Bienvenue chez {{store_name}}",
"Willkommen bei {{store_name}}",
"Wëllkomm bei {{store_name}}",
"{{store_name}}",
"{{store_name}}",
"{{store_name}}",
"{{store_name}}",
),
"content": """<div class="prose-content">
<h2>Welcome</h2>
<p>{{store_name}} is here to serve you. Browse our offerings and discover what we have for you.</p>
</div>""",
"content_translations": tt(
# English
"""<div class="prose-content">
<h2>Welcome</h2>
<p>{{store_name}} is here to serve you. Browse our offerings and discover what we have for you.</p>
</div>""",
# French
"""<div class="prose-content">
<h2>Bienvenue</h2>
<p>{{store_name}} est là pour vous servir. Découvrez nos offres et ce que nous avons pour vous.</p>
</div>""",
# German
"""<div class="prose-content">
<h2>Willkommen</h2>
<p>{{store_name}} ist für Sie da. Entdecken Sie unser Angebot.</p>
</div>""",
# Luxembourgish
"""<div class="prose-content">
<h2>Wëllkomm</h2>
<p>{{store_name}} ass fir Iech do. Entdeckt eist Angebot.</p>
</div>""",
),
"template": "default",
"meta_description": "Welcome to {{store_name}}",
"content": "",
"template": "full",
"meta_description": "{{store_name}}",
"show_in_header": False,
"show_in_footer": False,
"display_order": 0,
}
def _store_homepage_sections_oms() -> dict:
"""Store homepage sections for OMS platform stores."""
return {
"hero": {
"enabled": True,
"title": t(
"Bienvenue chez {{store_name}}",
"Welcome to {{store_name}}",
"Willkommen bei {{store_name}}",
),
"subtitle": t(
"Découvrez notre sélection de produits et profitez d'une expérience d'achat exceptionnelle.",
"Discover our product selection and enjoy an exceptional shopping experience.",
"Entdecken Sie unsere Produktauswahl und genießen Sie ein außergewöhnliches Einkaufserlebnis.",
),
"background_type": "gradient",
"buttons": [
{
"text": t("Voir nos produits", "Browse Products", "Produkte ansehen"),
"url": "products",
"style": "primary",
},
{
"text": t("À propos", "About Us", "Über uns"),
"url": "about",
"style": "secondary",
},
],
},
"features": {
"enabled": True,
"title": t(
"Pourquoi nous choisir",
"Why Choose Us",
"Warum uns wählen",
),
"subtitle": t(
"Ce qui nous distingue",
"What sets us apart",
"Was uns auszeichnet",
),
"layout": "grid",
"features": [
{
"icon": "check",
"title": t("Qualité premium", "Premium Quality", "Premium-Qualität"),
"description": t(
"Des produits soigneusement sélectionnés pour vous.",
"Carefully selected products just for you.",
"Sorgfältig ausgewählte Produkte für Sie.",
),
},
{
"icon": "truck",
"title": t("Livraison rapide", "Fast Shipping", "Schnelle Lieferung"),
"description": t(
"Livraison rapide directement chez vous.",
"Quick delivery right to your door.",
"Schnelle Lieferung direkt zu Ihnen.",
),
},
{
"icon": "shield-check",
"title": t("Paiement sécurisé", "Secure Payment", "Sichere Zahlung"),
"description": t(
"Vos transactions sont protégées à 100%.",
"Your transactions are 100% protected.",
"Ihre Transaktionen sind 100% geschützt.",
),
},
{
"icon": "chat-bubble-left",
"title": t("Support client", "Customer Support", "Kundensupport"),
"description": t(
"Une équipe à votre écoute pour vous accompagner.",
"A dedicated team ready to assist you.",
"Ein engagiertes Team, das Ihnen zur Seite steht.",
),
},
],
},
"cta": {
"enabled": True,
"title": t(
"Prêt à découvrir nos produits ?",
"Ready to Explore Our Products?",
"Bereit, unsere Produkte zu entdecken?",
),
"subtitle": t(
"Parcourez notre catalogue et trouvez ce qui vous convient.",
"Browse our catalog and find what suits you.",
"Durchstöbern Sie unseren Katalog und finden Sie, was zu Ihnen passt.",
),
"background_type": "gradient",
"buttons": [
{
"text": t("Voir les produits", "View Products", "Produkte ansehen"),
"url": "products",
"style": "primary",
},
],
},
}
def _store_homepage_sections_loyalty() -> dict:
"""Store homepage sections for Loyalty platform stores."""
return {
"hero": {
"enabled": True,
"title": t(
"Bienvenue chez {{store_name}}",
"Welcome to {{store_name}}",
"Willkommen bei {{store_name}}",
),
"subtitle": t(
"Rejoignez notre programme de fidélité et profitez de récompenses exclusives à chaque visite.",
"Join our loyalty program and enjoy exclusive rewards with every visit.",
"Treten Sie unserem Treueprogramm bei und genießen Sie exklusive Prämien bei jedem Besuch.",
),
"background_type": "gradient",
"buttons": [
{
"text": t("Rejoindre le programme", "Join Rewards", "Programm beitreten"),
"url": "account/loyalty",
"style": "primary",
},
{
"text": t("En savoir plus", "Learn More", "Mehr erfahren"),
"url": "about",
"style": "secondary",
},
],
},
"features": {
"enabled": True,
"title": t(
"Votre fidélité récompensée",
"Your Loyalty Rewarded",
"Ihre Treue wird belohnt",
),
"subtitle": t(
"Découvrez les avantages de notre programme",
"Discover the benefits of our program",
"Entdecken Sie die Vorteile unseres Programms",
),
"layout": "grid",
"features": [
{
"icon": "star",
"title": t("Gagnez des points", "Earn Points", "Punkte sammeln"),
"description": t(
"Cumulez des points à chaque achat et échangez-les contre des récompenses.",
"Accumulate points with every purchase and redeem them for rewards.",
"Sammeln Sie bei jedem Einkauf Punkte und lösen Sie sie gegen Prämien ein.",
),
},
{
"icon": "gift",
"title": t("Récompenses exclusives", "Exclusive Rewards", "Exklusive Prämien"),
"description": t(
"Accédez à des offres et récompenses réservées aux membres.",
"Access offers and rewards reserved for members.",
"Zugang zu Angeboten und Prämien nur für Mitglieder.",
),
},
{
"icon": "heart",
"title": t("Avantages membres", "Member Benefits", "Mitgliedervorteile"),
"description": t(
"Profitez d'avantages exclusifs en tant que membre fidèle.",
"Enjoy exclusive benefits as a loyal member.",
"Genießen Sie exklusive Vorteile als treues Mitglied.",
),
},
],
},
"cta": {
"enabled": True,
"title": t(
"Rejoignez-nous aujourd'hui",
"Join Us Today",
"Treten Sie uns noch heute bei",
),
"subtitle": t(
"Inscrivez-vous à notre programme de fidélité et commencez à gagner des récompenses.",
"Sign up for our loyalty program and start earning rewards.",
"Melden Sie sich für unser Treueprogramm an und beginnen Sie, Prämien zu verdienen.",
),
"background_type": "gradient",
"buttons": [
{
"text": t("S'inscrire", "Sign Up", "Anmelden"),
"url": "account/loyalty",
"style": "primary",
},
],
},
}
def _store_homepage_sections_hosting() -> dict:
"""Store homepage sections for Hosting platform stores (client sites)."""
return {
"hero": {
"enabled": True,
"title": t(
"Bienvenue chez {{store_name}}",
"Welcome to {{store_name}}",
"Willkommen bei {{store_name}}",
),
"subtitle": t(
"Votre partenaire de confiance pour des solutions numériques sur mesure.",
"Your trusted partner for tailored digital solutions.",
"Ihr vertrauenswürdiger Partner für maßgeschneiderte digitale Lösungen.",
),
"background_type": "gradient",
"buttons": [
{
"text": t("Nous contacter", "Contact Us", "Kontaktieren Sie uns"),
"url": "contact",
"style": "primary",
},
{
"text": t("À propos", "About Us", "Über uns"),
"url": "about",
"style": "secondary",
},
],
},
"features": {
"enabled": True,
"title": t(
"Nos services",
"Our Services",
"Unsere Dienstleistungen",
),
"subtitle": t(
"Ce que nous pouvons faire pour vous",
"What we can do for you",
"Was wir für Sie tun können",
),
"layout": "grid",
"features": [
{
"icon": "globe-alt",
"title": t("Site web", "Website", "Webseite"),
"description": t(
"Un site web professionnel qui reflète votre activité.",
"A professional website that reflects your business.",
"Eine professionelle Website, die Ihr Unternehmen widerspiegelt.",
),
},
{
"icon": "shield-check",
"title": t("Hébergement sécurisé", "Secure Hosting", "Sicheres Hosting"),
"description": t(
"Hébergement fiable avec certificat SSL inclus.",
"Reliable hosting with SSL certificate included.",
"Zuverlässiges Hosting mit SSL-Zertifikat inklusive.",
),
},
{
"icon": "mail",
"title": t("Email professionnel", "Professional Email", "Professionelle E-Mail"),
"description": t(
"Adresses email personnalisées pour votre entreprise.",
"Custom email addresses for your business.",
"Individuelle E-Mail-Adressen für Ihr Unternehmen.",
),
},
],
},
"cta": {
"enabled": True,
"title": t(
"Besoin d'aide ?",
"Need Help?",
"Brauchen Sie Hilfe?",
),
"subtitle": t(
"Contactez-nous pour discuter de votre projet.",
"Contact us to discuss your project.",
"Kontaktieren Sie uns, um Ihr Projekt zu besprechen.",
),
"background_type": "gradient",
"buttons": [
{
"text": t("Nous contacter", "Contact Us", "Kontaktieren Sie uns"),
"url": "contact",
"style": "primary",
},
],
},
}
STORE_DEFAULTS_COMMON = [
{
"slug": "about",
@@ -2796,8 +3068,18 @@ def create_default_pages(db: Session) -> None:
# Only for platforms that host stores (not wizard.lu main)
# ------------------------------------------------------------------
if platform.code != "main":
# Store homepage (slug="home")
if _create_page(db, platform.id, STORE_DEFAULT_HOME, is_platform_page=False):
# Store homepage (slug="home") with platform-specific sections
store_sections_map = {
"oms": _store_homepage_sections_oms,
"loyalty": _store_homepage_sections_loyalty,
"hosting": _store_homepage_sections_hosting,
}
store_sections_fn = store_sections_map.get(platform.code)
store_sections = store_sections_fn() if store_sections_fn else None
if _create_page(
db, platform.id, STORE_DEFAULT_HOME,
is_platform_page=False, sections=store_sections,
):
created_count += 1
else:
skipped_count += 1