From e492e5f71cb112f8ec41c135db7ccc440ea3c462 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Thu, 2 Apr 2026 20:15:11 +0200 Subject: [PATCH] fix(hosting): render POC preview directly instead of iframe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The POC viewer was loading the storefront in an iframe, which hit the StorefrontAccessMiddleware subscription check (POC sites don't have subscriptions yet). Fixed by rendering CMS sections directly in the preview template: - Load ContentPages and StoreTheme from DB - Render hero, features, testimonials, CTA sections inline - Apply template colors/fonts via Tailwind CSS config - HostWizard preview banner with nav links - Footer with contact info - No iframe, no subscription check needed Also fixed Jinja2 dict.items collision (dict.items is the method, not the 'items' key — use dict.get('items') instead). Co-Authored-By: Claude Opus 4.6 (1M context) --- app/modules/hosting/routes/pages/public.py | 62 ++++- .../templates/hosting/public/poc-viewer.html | 220 +++++++++++++----- 2 files changed, 227 insertions(+), 55 deletions(-) diff --git a/app/modules/hosting/routes/pages/public.py b/app/modules/hosting/routes/pages/public.py index fb7104ef..673f429d 100644 --- a/app/modules/hosting/routes/pages/public.py +++ b/app/modules/hosting/routes/pages/public.py @@ -26,19 +26,75 @@ async def poc_site_viewer( site_id: int = Path(..., description="Hosted Site ID"), db: Session = Depends(get_db), ): - """Render POC site viewer with HostWizard preview banner.""" + """Render POC site viewer with HostWizard preview banner. + + Renders CMS content directly (not via iframe to storefront) to + bypass the subscription access gate for pre-launch POC sites. + """ + from app.modules.cms.models.content_page import ContentPage + from app.modules.cms.models.store_theme import StoreTheme from app.modules.hosting.models import HostedSite, HostedSiteStatus + from app.modules.tenancy.models import StorePlatform site = db.query(HostedSite).filter(HostedSite.id == site_id).first() # Only allow viewing for poc_ready or proposal_sent sites - if not site or site.status not in (HostedSiteStatus.POC_READY, HostedSiteStatus.PROPOSAL_SENT): + if not site or site.status not in ( + HostedSiteStatus.POC_READY, + HostedSiteStatus.PROPOSAL_SENT, + HostedSiteStatus.ACCEPTED, + ): return HTMLResponse(content="

Site not available for preview

", status_code=404) + store = site.store + + # Get platform_id for CMS query + store_platform = ( + db.query(StorePlatform) + .filter(StorePlatform.store_id == store.id) + .first() + ) if store else None + platform_id = store_platform.platform_id if store_platform else None + + # Load homepage (slug='homepage' from POC builder) + page = None + if platform_id: + page = ( + db.query(ContentPage) + .filter( + ContentPage.platform_id == platform_id, + ContentPage.store_id == store.id, + ContentPage.slug == "homepage", + ContentPage.is_published.is_(True), + ) + .first() + ) + + # Load all nav pages for this store + nav_pages = [] + if platform_id: + nav_pages = ( + db.query(ContentPage) + .filter( + ContentPage.platform_id == platform_id, + ContentPage.store_id == store.id, + ContentPage.is_published.is_(True), + ContentPage.show_in_header.is_(True), + ) + .order_by(ContentPage.display_order) + .all() + ) + + # Load theme + theme = db.query(StoreTheme).filter(StoreTheme.store_id == store.id).first() if store else None + context = { "request": request, "site": site, - "store_url": f"/stores/{site.store.subdomain}" if site.store else "#", + "store": store, + "page": page, + "nav_pages": nav_pages, + "theme": theme, } return templates.TemplateResponse( "hosting/public/poc-viewer.html", diff --git a/app/modules/hosting/templates/hosting/public/poc-viewer.html b/app/modules/hosting/templates/hosting/public/poc-viewer.html index fcb0e608..352a94e9 100644 --- a/app/modules/hosting/templates/hosting/public/poc-viewer.html +++ b/app/modules/hosting/templates/hosting/public/poc-viewer.html @@ -4,64 +4,180 @@ {{ site.business_name }} - Preview by HostWizard + {% if page and page.meta_description %} + + {% endif %} + + - -
-
- - Preview for {{ site.business_name }} + + + {# HostWizard Preview Banner #} +
+
+ HostWizard + Preview for {{ site.business_name }}
-
- hostwizard.lu +
+ {% for np in nav_pages %} + {{ np.title }} + {% endfor %} + hostwizard.lu
-
- + + {# Main Content — rendered directly, no iframe #} +
+ {% if page and page.sections %} + {# Section-based rendering (from POC builder templates) #} + {% set lang = 'fr' %} + {% set default_lang = 'fr' %} + + {% if page.sections.hero %} + {% set hero = page.sections.hero %} +
+
+ {% set t = hero.get('title', {}) %} + {% set tr = t.get('translations', {}) if t is mapping else {} %} +

{{ tr.get(lang) or tr.get(default_lang) or site.business_name }}

+ {% set st = hero.get('subtitle', {}) %} + {% set str_ = st.get('translations', {}) if st is mapping else {} %} + {% set subtitle_text = str_.get(lang) or str_.get(default_lang, '') %} + {% if subtitle_text %} +

{{ subtitle_text }}

+ {% endif %} + {% set hero_buttons = hero.get('buttons', []) %} + {% if hero_buttons %} +
+ {% for btn in hero_buttons %} + {% set bl = btn.get('label', {}) %} + {% set bltr = bl.get('translations', {}) if bl is mapping else {} %} + {% set btn_label = bltr.get(lang) or bltr.get(default_lang, 'Learn More') %} + + {{ btn_label }} + + {% endfor %} +
+ {% endif %} +
+
+ {% endif %} + + {% if page.sections.features %} + {% set features = page.sections.features %} +
+
+ {% set ft = features.get('title', {}) %} + {% set ftr = ft.get('translations', {}) if ft is mapping else {} %} + {% set title = ftr.get(lang) or ftr.get(default_lang, '') %} + {% if title %} +

{{ title }}

+ {% endif %} + {% set feature_items = features.get('items', []) %} + {% if feature_items %} +
+ {% for item in feature_items %} + {% set it = item.get('title', {}) %} + {% set itr = it.get('translations', {}) if it is mapping else {} %} + {% set item_title = itr.get(lang) or itr.get(default_lang, '') %} + {% set id_ = item.get('description', {}) %} + {% set idr = id_.get('translations', {}) if id_ is mapping else {} %} + {% set item_desc = idr.get(lang) or idr.get(default_lang, '') %} +
+
+ {{ item_title[0]|upper if item_title else '?' }} +
+

{{ item_title }}

+

{{ item_desc }}

+
+ {% endfor %} +
+ {% endif %} +
+
+ {% endif %} + + {% if page.sections.testimonials %} + {% set testimonials = page.sections.testimonials %} +
+
+ {% set tt = testimonials.get('title', {}) %} + {% set ttr = tt.get('translations', {}) if tt is mapping else {} %} + {% set title = ttr.get(lang) or ttr.get(default_lang, '') %} + {% if title %} +

{{ title }}

+ {% endif %} +

Coming soon

+
+
+ {% endif %} + + {% if page.sections.cta %} + {% set cta = page.sections.cta %} +
+
+ {% set ct = cta.get('title', {}) %} + {% set ctr = ct.get('translations', {}) if ct is mapping else {} %} +

{{ ctr.get(lang) or ctr.get(default_lang, '') }}

+ {% set cta_buttons = cta.get('buttons', []) %} + {% if cta_buttons %} +
+ {% for btn in cta_buttons %} + {% set bl = btn.get('label', {}) %} + {% set bltr = bl.get('translations', {}) if bl is mapping else {} %} + + {{ bltr.get(lang) or bltr.get(default_lang, 'Contact') }} + + {% endfor %} +
+ {% endif %} +
+
+ {% endif %} + + {% elif page and page.content %} + {# Plain HTML content #} +
+ {{ page.content | safe }} +
+ {% else %} +
+

No content yet. Build a POC to populate this site.

+
+ {% endif %}
+ + {# Footer #} + +