fix(hosting): render POC preview directly instead of iframe

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) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 20:15:11 +02:00
parent 9a5b7dd061
commit e492e5f71c
2 changed files with 227 additions and 55 deletions

View File

@@ -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="<h1>Site not available for preview</h1>", 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",