feat: implement section-based homepage management system
Add structured JSON sections to ContentPage for multi-language homepage editing:
Database:
- Add `sections` JSON column to content_pages table
- Migration z8i9j0k1l2m3 adds the column
Schema:
- New models/schema/homepage_sections.py with Pydantic schemas
- TranslatableText for language-keyed translations
- HeroSection, FeaturesSection, PricingSection, CTASection
Templates:
- New section partials in app/templates/platform/sections/
- Updated homepage-default.html to render sections dynamically
- Fallback to placeholder content when sections not configured
Service:
- update_homepage_sections() - validate and save all sections
- update_single_section() - update individual section
- get_default_sections() - empty structure for new homepages
API:
- GET /{page_id}/sections - get sections with platform languages
- PUT /{page_id}/sections - update all sections
- PUT /{page_id}/sections/{section_name} - update single section
Admin UI:
- Section editor appears when editing homepage (slug='home')
- Language tabs from platform.supported_languages
- Accordion sections for Hero, Features, Pricing, CTA
- Button/feature card repeaters with add/remove
Also fixes broken line 181 in z4e5f6a7b8c9 migration.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
{# app/templates/platform/homepage-default.html #}
|
||||
{# Default platform homepage template #}
|
||||
{# Default platform homepage template with section-based rendering #}
|
||||
{% extends "platform/base.html" %}
|
||||
|
||||
{# Import section partials #}
|
||||
{% from 'platform/sections/_hero.html' import render_hero %}
|
||||
{% from 'platform/sections/_features.html' import render_features %}
|
||||
{% from 'platform/sections/_pricing.html' import render_pricing %}
|
||||
{% from 'platform/sections/_cta.html' import render_cta %}
|
||||
|
||||
{% block title %}
|
||||
{% if page %}{{ page.title }}{% else %}Home{% endif %} - Multi-Vendor Marketplace
|
||||
{% if page %}{{ page.title }}{% else %}Home{% endif %} - {{ platform.name if platform else 'Multi-Vendor Marketplace' }}
|
||||
{% endblock %}
|
||||
|
||||
{% block meta_description %}
|
||||
@@ -15,214 +21,114 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- HERO SECTION -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
{# Set up language context #}
|
||||
{% set lang = request.state.language or (platform.default_language if platform else 'fr') %}
|
||||
{% set default_lang = platform.default_language if platform else 'fr' %}
|
||||
|
||||
{# ═══════════════════════════════════════════════════════════════════════════ #}
|
||||
{# SECTION-BASED RENDERING (when page.sections is configured) #}
|
||||
{# ═══════════════════════════════════════════════════════════════════════════ #}
|
||||
{% if page and page.sections %}
|
||||
|
||||
{# Hero Section #}
|
||||
{% if page.sections.hero %}
|
||||
{{ render_hero(page.sections.hero, lang, default_lang) }}
|
||||
{% endif %}
|
||||
|
||||
{# Features Section #}
|
||||
{% if page.sections.features %}
|
||||
{{ render_features(page.sections.features, lang, default_lang) }}
|
||||
{% endif %}
|
||||
|
||||
{# Pricing Section #}
|
||||
{% if page.sections.pricing %}
|
||||
{{ render_pricing(page.sections.pricing, lang, default_lang, tiers) }}
|
||||
{% endif %}
|
||||
|
||||
{# CTA Section #}
|
||||
{% if page.sections.cta %}
|
||||
{{ render_cta(page.sections.cta, lang, default_lang) }}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{# ═══════════════════════════════════════════════════════════════════════════ #}
|
||||
{# PLACEHOLDER CONTENT (when sections not configured) #}
|
||||
{# ═══════════════════════════════════════════════════════════════════════════ #}
|
||||
|
||||
<!-- HERO SECTION -->
|
||||
<section class="gradient-primary text-white py-20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
{% if page %}
|
||||
{# CMS-driven content #}
|
||||
<h1 class="text-4xl md:text-6xl font-bold mb-6">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
<div class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
|
||||
{{ page.content | safe }}{# sanitized: CMS content #}
|
||||
</div>
|
||||
{% else %}
|
||||
{# Default fallback content #}
|
||||
<h1 class="text-4xl md:text-6xl font-bold mb-6">
|
||||
Welcome to Our Marketplace
|
||||
</h1>
|
||||
<p class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
|
||||
Connect vendors with customers worldwide. Build your online store and reach millions of shoppers.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="text-4xl md:text-6xl font-bold mb-6">
|
||||
{{ _('homepage.placeholder.title') or 'Configure Your Homepage' }}
|
||||
</h1>
|
||||
<p class="text-xl md:text-2xl mb-8 opacity-90 max-w-3xl mx-auto">
|
||||
{{ _('homepage.placeholder.subtitle') or 'Use the admin panel to configure homepage sections with multi-language content.' }}
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<a href="/vendors"
|
||||
class="btn-primary inline-flex items-center space-x-2">
|
||||
<span>Browse Vendors</span>
|
||||
<a href="/admin/content-pages"
|
||||
class="bg-white text-gray-900 px-8 py-4 rounded-xl font-semibold hover:bg-gray-100 transition inline-flex items-center space-x-2">
|
||||
<span>{{ _('homepage.placeholder.configure_btn') or 'Configure Homepage' }}</span>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/contact"
|
||||
class="bg-white text-gray-900 px-6 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- FEATURES SECTION -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- FEATURES SECTION (Placeholder) -->
|
||||
<section class="py-16 bg-white dark:bg-gray-800">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Why Choose Our Platform?
|
||||
{{ _('homepage.placeholder.features_title') or 'Features Section' }}
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
|
||||
Everything you need to launch and grow your online business
|
||||
{{ _('homepage.placeholder.features_subtitle') or 'Configure feature cards in the admin panel' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- Feature 1 -->
|
||||
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
{% for i in range(3) %}
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-200 dark:bg-gray-600 flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Lightning Fast
|
||||
<h3 class="text-xl font-semibold text-gray-400 mb-3">
|
||||
Feature {{ i + 1 }}
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Optimized for speed and performance. Your store loads in milliseconds.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 2 -->
|
||||
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Secure & Reliable
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Enterprise-grade security with 99.9% uptime guarantee.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 3 -->
|
||||
<div class="card-hover bg-gray-50 dark:bg-gray-700 rounded-xl p-8 text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 rounded-full gradient-primary flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Fully Customizable
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Brand your store with custom themes, colors, and layouts.
|
||||
<p class="text-gray-400">
|
||||
Configure this feature card
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- FEATURED VENDORS SECTION -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<section class="py-16 bg-gray-50 dark:bg-gray-900">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Featured Vendors
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400">
|
||||
Discover amazing shops from around the world
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- Vendor Card 1 - Placeholder -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
||||
<div class="h-48 bg-gradient-to-r from-purple-400 to-pink-400"></div>
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Sample Vendor 1
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||
Premium products and exceptional service
|
||||
</p>
|
||||
<a href="/vendors/vendor1/shop"
|
||||
class="text-primary hover:underline font-medium inline-flex items-center">
|
||||
Visit Store
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vendor Card 2 - Placeholder -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
||||
<div class="h-48 bg-gradient-to-r from-blue-400 to-cyan-400"></div>
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Sample Vendor 2
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||
Quality craftsmanship meets modern design
|
||||
</p>
|
||||
<a href="/vendors/vendor2/shop"
|
||||
class="text-primary hover:underline font-medium inline-flex items-center">
|
||||
Visit Store
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vendor Card 3 - Placeholder -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm hover:shadow-lg transition-shadow overflow-hidden">
|
||||
<div class="h-48 bg-gradient-to-r from-green-400 to-teal-400"></div>
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Sample Vendor 3
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||
Eco-friendly products for sustainable living
|
||||
</p>
|
||||
<a href="/vendors/vendor3/shop"
|
||||
class="text-primary hover:underline font-medium inline-flex items-center">
|
||||
Visit Store
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-12">
|
||||
<a href="/vendors" class="btn-primary inline-block">
|
||||
View All Vendors
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- CTA SECTION -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<section class="py-16 bg-white dark:bg-gray-800">
|
||||
<!-- CTA SECTION (Placeholder) -->
|
||||
<section class="py-16 bg-gray-100 dark:bg-gray-900">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Ready to Get Started?
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-400 mb-4">
|
||||
{{ _('homepage.placeholder.cta_title') or 'Call to Action' }}
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400 mb-8">
|
||||
Join thousands of vendors already selling on our platform
|
||||
<p class="text-lg text-gray-400 mb-8">
|
||||
{{ _('homepage.placeholder.cta_subtitle') or 'Configure CTA section in the admin panel' }}
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a href="/contact" class="btn-primary">
|
||||
Contact Sales
|
||||
</a>
|
||||
<a href="/about"
|
||||
class="bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white px-6 py-3 rounded-lg font-semibold hover:bg-gray-200 dark:hover:bg-gray-600 transition">
|
||||
Learn More
|
||||
</a>
|
||||
<span class="bg-gray-300 text-gray-500 px-6 py-3 rounded-lg font-semibold">
|
||||
Button 1
|
||||
</span>
|
||||
<span class="bg-gray-200 text-gray-500 px-6 py-3 rounded-lg font-semibold">
|
||||
Button 2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user