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>
55 lines
2.4 KiB
HTML
55 lines
2.4 KiB
HTML
{# app/templates/platform/sections/_cta.html #}
|
|
{# Call-to-action section partial with multi-language support #}
|
|
{#
|
|
Parameters:
|
|
- cta: CTASection object (or dict)
|
|
- lang: Current language code
|
|
- default_lang: Fallback language
|
|
#}
|
|
|
|
{% macro render_cta(cta, lang, default_lang) %}
|
|
{% if cta and cta.enabled %}
|
|
<section class="py-16 lg:py-24 {% if cta.background_type == 'gradient' %}bg-gradient-to-r from-indigo-600 to-purple-600{% else %}bg-indigo-600{% endif %}">
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
|
{# Title #}
|
|
{% set title = cta.title.translations.get(lang) or cta.title.translations.get(default_lang) or '' %}
|
|
{% if title %}
|
|
<h2 class="text-3xl md:text-4xl font-bold text-white mb-6">
|
|
{{ title }}
|
|
</h2>
|
|
{% endif %}
|
|
|
|
{# Subtitle #}
|
|
{% if cta.subtitle and cta.subtitle.translations %}
|
|
{% set subtitle = cta.subtitle.translations.get(lang) or cta.subtitle.translations.get(default_lang) %}
|
|
{% if subtitle %}
|
|
<p class="text-xl text-indigo-100 mb-10 max-w-2xl mx-auto">
|
|
{{ subtitle }}
|
|
</p>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{# Buttons #}
|
|
{% if cta.buttons %}
|
|
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
|
{% for button in cta.buttons %}
|
|
{% set btn_text = button.text.translations.get(lang) or button.text.translations.get(default_lang) or '' %}
|
|
{% if btn_text and button.url %}
|
|
<a href="{{ button.url }}"
|
|
class="{% if button.style == 'primary' %}bg-white text-indigo-600 hover:bg-gray-100{% elif button.style == 'secondary' %}bg-indigo-500 text-white hover:bg-indigo-400{% else %}border-2 border-white text-white hover:bg-white/10{% endif %} px-10 py-4 rounded-xl font-bold transition inline-flex items-center space-x-2">
|
|
<span>{{ btn_text }}</span>
|
|
{% if button.style == 'primary' %}
|
|
<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>
|
|
{% endif %}
|
|
</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
{% endmacro %}
|