# app/routes/platform_pages.py """ Platform public page routes. These routes serve the marketing homepage, pricing page, Letzshop vendor finder, and signup wizard. """ import logging from pathlib import Path from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import HTMLResponse from app.templates_config import templates from sqlalchemy.orm import Session from app.core.config import settings from app.core.database import get_db from app.modules.cms.services import content_page_service from app.utils.i18n import get_jinja2_globals router = APIRouter() logger = logging.getLogger(__name__) # Get the templates directory BASE_DIR = Path(__file__).resolve().parent.parent.parent # TEMPLATES_DIR moved to app.templates_config # templates imported from app.templates_config def get_platform_context(request: Request, db: Session) -> dict: """Build context for platform pages.""" # Get language from request state (set by middleware) language = getattr(request.state, "language", "fr") # Get platform from middleware (default to OMS platform_id=1) platform = getattr(request.state, "platform", None) platform_id = platform.id if platform else 1 # Get translation function i18n_globals = get_jinja2_globals(language) context = { "request": request, "platform_name": "Wizamart", "platform_domain": settings.platform_domain, "stripe_publishable_key": settings.stripe_publishable_key, "trial_days": settings.stripe_trial_days, } # Add i18n globals (_, t, current_language, SUPPORTED_LANGUAGES, etc.) context.update(i18n_globals) # Load CMS pages for header, footer, and legal navigation header_pages = [] footer_pages = [] legal_pages = [] try: # Platform marketing pages (is_platform_page=True) header_pages = content_page_service.list_platform_pages( db, platform_id=platform_id, header_only=True, include_unpublished=False ) footer_pages = content_page_service.list_platform_pages( db, platform_id=platform_id, footer_only=True, include_unpublished=False ) # For legal pages, we need to add footer support or use a different approach # For now, legal pages come from footer pages with show_in_legal flag legal_pages = [] # Will be handled separately if needed logger.debug( f"Loaded CMS pages: {len(header_pages)} header, {len(footer_pages)} footer, {len(legal_pages)} legal" ) except Exception as e: logger.error(f"Failed to load CMS navigation pages: {e}") context["header_pages"] = header_pages context["footer_pages"] = footer_pages context["legal_pages"] = legal_pages return context # ============================================================================= # Homepage # ============================================================================= @router.get("/", response_class=HTMLResponse, name="platform_homepage") async def homepage( request: Request, db: Session = Depends(get_db), ): """ Homepage handler. Handles two scenarios: 1. Vendor on custom domain (vendor.com) → Show vendor landing page or redirect to shop 2. Platform marketing site → Show platform homepage from CMS or default template URL routing: - localhost:9999/ → Main marketing site ('main' platform) - localhost:9999/platforms/oms/ → OMS platform (middleware rewrites to /) - oms.lu/ → OMS platform (domain-based) - shop.mycompany.com/ → Vendor landing page (custom domain) """ from fastapi.responses import RedirectResponse # Get platform and vendor from middleware platform = getattr(request.state, "platform", None) vendor = getattr(request.state, "vendor", None) # Scenario 1: Vendor detected (custom domain like vendor.com) if vendor: logger.debug(f"[HOMEPAGE] Vendor detected: {vendor.subdomain}") # Get platform_id (use platform from context or default to 1 for OMS) platform_id = platform.id if platform else 1 # Try to find vendor landing page (slug='landing' or 'home') landing_page = content_page_service.get_page_for_vendor( db, platform_id=platform_id, slug="landing", vendor_id=vendor.id, include_unpublished=False ) if not landing_page: landing_page = content_page_service.get_page_for_vendor( db, platform_id=platform_id, slug="home", vendor_id=vendor.id, include_unpublished=False ) if landing_page: # Render landing page with selected template from app.routes.storefront_pages import get_storefront_context template_name = landing_page.template or "default" template_path = f"vendor/landing-{template_name}.html" logger.info(f"[HOMEPAGE] Rendering vendor landing page: {template_path}") return templates.TemplateResponse( template_path, get_storefront_context(request, db=db, page=landing_page) ) # No landing page - redirect to shop vendor_context = getattr(request.state, "vendor_context", None) access_method = ( vendor_context.get("detection_method", "unknown") if vendor_context else "unknown" ) if access_method == "path": full_prefix = ( vendor_context.get("full_prefix", "/vendor/") if vendor_context else "/vendor/" ) return RedirectResponse( url=f"{full_prefix}{vendor.subdomain}/shop/", status_code=302 ) # Domain/subdomain - redirect to /shop/ return RedirectResponse(url="/shop/", status_code=302) # Scenario 2: Platform marketing site (no vendor) # Load platform homepage from CMS (slug='home') platform_id = platform.id if platform else 1 cms_homepage = content_page_service.get_platform_page( db, platform_id=platform_id, slug="home", include_unpublished=False ) if cms_homepage: # Use CMS-based homepage with template selection context = get_platform_context(request, db) context["page"] = cms_homepage context["platform"] = platform # Include subscription tiers for pricing section from models.database.subscription import TIER_LIMITS, TierCode tiers = [] for tier_code, limits in TIER_LIMITS.items(): tiers.append({ "code": tier_code.value, "name": limits["name"], "price_monthly": limits["price_monthly_cents"] / 100, "price_annual": (limits["price_annual_cents"] / 100) if limits.get("price_annual_cents") else None, "orders_per_month": limits.get("orders_per_month"), "products_limit": limits.get("products_limit"), "team_members": limits.get("team_members"), "features": limits.get("features", []), "is_popular": tier_code == TierCode.PROFESSIONAL, "is_enterprise": tier_code == TierCode.ENTERPRISE, }) context["tiers"] = tiers template_name = cms_homepage.template or "default" template_path = f"platform/homepage-{template_name}.html" logger.info(f"[HOMEPAGE] Rendering CMS homepage with template: {template_path}") return templates.TemplateResponse(template_path, context) # Fallback: Default wizamart homepage (no CMS content) logger.info("[HOMEPAGE] No CMS homepage found, using default wizamart template") context = get_platform_context(request, db) context["platform"] = platform # Fetch tiers for display (use API service internally) from models.database.subscription import TIER_LIMITS, TierCode tiers = [] for tier_code, limits in TIER_LIMITS.items(): tiers.append({ "code": tier_code.value, "name": limits["name"], "price_monthly": limits["price_monthly_cents"] / 100, "price_annual": (limits["price_annual_cents"] / 100) if limits.get("price_annual_cents") else None, "orders_per_month": limits.get("orders_per_month"), "products_limit": limits.get("products_limit"), "team_members": limits.get("team_members"), "features": limits.get("features", []), "is_popular": tier_code == TierCode.PROFESSIONAL, "is_enterprise": tier_code == TierCode.ENTERPRISE, }) context["tiers"] = tiers # Add-ons (hardcoded for now, will come from DB) context["addons"] = [ { "code": "domain", "name": "Custom Domain", "description": "Use your own domain (mydomain.com)", "price": 15, "billing_period": "year", "icon": "globe", }, { "code": "ssl_premium", "name": "Premium SSL", "description": "EV certificate for trust badges", "price": 49, "billing_period": "year", "icon": "shield-check", }, { "code": "email", "name": "Email Package", "description": "Professional email addresses", "price": 5, "billing_period": "month", "icon": "mail", "options": [ {"quantity": 5, "price": 5}, {"quantity": 10, "price": 9}, {"quantity": 25, "price": 19}, ], }, ] return templates.TemplateResponse( "platform/homepage-wizamart.html", context, ) # ============================================================================= # Pricing Page # ============================================================================= @router.get("/pricing", response_class=HTMLResponse, name="platform_pricing") async def pricing_page( request: Request, db: Session = Depends(get_db), ): """ Standalone pricing page with detailed tier comparison. """ context = get_platform_context(request, db) # Reuse tier data from homepage from models.database.subscription import TIER_LIMITS, TierCode tiers = [] for tier_code, limits in TIER_LIMITS.items(): tiers.append({ "code": tier_code.value, "name": limits["name"], "price_monthly": limits["price_monthly_cents"] / 100, "price_annual": (limits["price_annual_cents"] / 100) if limits.get("price_annual_cents") else None, "orders_per_month": limits.get("orders_per_month"), "products_limit": limits.get("products_limit"), "team_members": limits.get("team_members"), "order_history_months": limits.get("order_history_months"), "features": limits.get("features", []), "is_popular": tier_code == TierCode.PROFESSIONAL, "is_enterprise": tier_code == TierCode.ENTERPRISE, }) context["tiers"] = tiers context["page_title"] = "Pricing" return templates.TemplateResponse( "platform/pricing.html", context, ) # ============================================================================= # Find Your Shop (Letzshop Vendor Browser) # ============================================================================= @router.get("/find-shop", response_class=HTMLResponse, name="platform_find_shop") async def find_shop_page( request: Request, db: Session = Depends(get_db), ): """ Letzshop vendor browser page. Allows vendors to search for and claim their Letzshop shop. """ context = get_platform_context(request, db) context["page_title"] = "Find Your Letzshop Shop" return templates.TemplateResponse( "platform/find-shop.html", context, ) # ============================================================================= # Signup Wizard # ============================================================================= @router.get("/signup", response_class=HTMLResponse, name="platform_signup") async def signup_page( request: Request, tier: str | None = None, annual: bool = False, db: Session = Depends(get_db), ): """ Multi-step signup wizard. Query params: - tier: Pre-selected tier code - annual: Pre-select annual billing """ context = get_platform_context(request, db) context["page_title"] = "Start Your Free Trial" context["selected_tier"] = tier context["is_annual"] = annual # Get tiers for tier selection step from models.database.subscription import TIER_LIMITS, TierCode tiers = [] for tier_code, limits in TIER_LIMITS.items(): tiers.append({ "code": tier_code.value, "name": limits["name"], "price_monthly": limits["price_monthly_cents"] / 100, "price_annual": (limits["price_annual_cents"] / 100) if limits.get("price_annual_cents") else None, "orders_per_month": limits.get("orders_per_month"), "team_members": limits.get("team_members"), "is_enterprise": tier_code == TierCode.ENTERPRISE, }) context["tiers"] = tiers return templates.TemplateResponse( "platform/signup.html", context, ) @router.get("/signup/success", response_class=HTMLResponse, name="platform_signup_success") async def signup_success_page( request: Request, vendor_code: str | None = None, db: Session = Depends(get_db), ): """ Signup success page. Shown after successful account creation. """ context = get_platform_context(request, db) context["page_title"] = "Welcome to Wizamart!" context["vendor_code"] = vendor_code return templates.TemplateResponse( "platform/signup-success.html", context, ) # ============================================================================= # Generic Content Pages (CMS) # ============================================================================= # IMPORTANT: This route must be LAST as it catches all /{slug} URLs @router.get("/{slug}", response_class=HTMLResponse, name="platform_content_page") async def content_page( request: Request, slug: str, db: Session = Depends(get_db), ): """ Serve CMS content pages (about, contact, faq, privacy, terms, etc.). This is a catch-all route for dynamic content pages managed via the admin CMS. Platform pages have vendor_id=None and is_platform_page=True. """ # Get platform from middleware (default to OMS platform_id=1) platform = getattr(request.state, "platform", None) platform_id = platform.id if platform else 1 # Load platform marketing page from database page = content_page_service.get_platform_page( db, platform_id=platform_id, slug=slug, include_unpublished=False ) if not page: raise HTTPException(status_code=404, detail=f"Page not found: {slug}") context = get_platform_context(request, db) context["page"] = page context["page_title"] = page.title return templates.TemplateResponse( "platform/content-page.html", context, )