feat: implement vendor landing pages with multi-template support and fix shop routing

Major improvements to shop URL routing and vendor landing page system:

## Landing Page System
- Add template field to ContentPage model for flexible landing page designs
- Create 4 landing page templates: default, minimal, modern, and full
- Implement smart root handler to serve landing pages or redirect to shop
- Add create_landing_page.py script for easy landing page management
- Support both domain/subdomain and path-based vendor access
- Add comprehensive landing page documentation

## Route Fixes
- Fix duplicate /shop prefix in shop_pages.py routes
- Correct product detail page routing (was /shop/shop/products/{id})
- Update all shop routes to work with router prefix mounting
- Remove unused public vendor endpoints (/api/v1/public/vendors)

## Template Link Corrections
- Fix all shop template links to include /shop/ prefix
- Update breadcrumb 'Home' links to point to vendor root (landing page)
- Update header navigation 'Home' link to point to vendor root
- Correct CMS page links in footer navigation
- Fix account, cart, and error page navigation links

## Navigation Architecture
- Establish two-tier navigation: landing page (/) and shop (/shop/)
- Document complete navigation flow and URL hierarchy
- Support for vendors with or without landing pages (auto-redirect fallback)
- Consistent breadcrumb and header navigation behavior

## Documentation
- Add vendor-landing-pages.md feature documentation
- Add navigation-flow.md with complete URL hierarchy
- Update shop architecture docs with error handling section
- Add orphaned docs to mkdocs.yml navigation
- Document multi-access routing patterns

## Database
- Migration f68d8da5315a: add template field to content_pages table
- Support template values: default, minimal, modern, full

This establishes a complete landing page system allowing vendors to have
custom marketing homepages separate from their e-commerce shop, with
flexible template options and proper navigation hierarchy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-23 00:10:45 +01:00
parent d85a68f175
commit b7bf505a61
26 changed files with 1967 additions and 242 deletions

View File

@@ -4,12 +4,12 @@ API router configuration for multi-tenant ecommerce platform.
This module provides:
- API version 1 route aggregation
- Route organization by user type (admin, vendor, public)
- Route organization by user type (admin, vendor, shop)
- Proper route prefixing and tagging
"""
from fastapi import APIRouter
from app.api.v1 import admin, vendor, public, shop
from app.api.v1 import admin, vendor, shop
api_router = APIRouter()
@@ -35,17 +35,6 @@ api_router.include_router(
tags=["vendor"]
)
# ============================================================================
# PUBLIC/CUSTOMER ROUTES (Customer-facing)
# Prefix: /api/v1/public
# ============================================================================
api_router.include_router(
public.router,
prefix="/v1/public",
tags=["public"]
)
# ============================================================================
# SHOP ROUTES (Public shop frontend API)
# Prefix: /api/v1/shop

View File

@@ -3,6 +3,6 @@
API Version 1 - All endpoints
"""
from . import admin, vendor, public, shop
from . import admin, vendor, shop
__all__ = ["admin", "vendor", "public", "shop"]
__all__ = ["admin", "vendor", "shop"]

View File

@@ -1,19 +0,0 @@
# app/api/v1/public/__init__.py
"""
Public API endpoints (non-shop, non-authenticated).
Note: Shop-related endpoints have been migrated to /api/v1/shop/*
This module now only contains truly public endpoints:
- Vendor lookup (by code, subdomain, ID)
"""
from fastapi import APIRouter
from .vendors import vendors
# Create public router
router = APIRouter()
# Include vendor lookup endpoints (not shop-specific)
router.include_router(vendors.router, prefix="/vendors", tags=["public-vendors"])
__all__ = ["router"]

View File

@@ -1,2 +0,0 @@
# app/api/v1/public/vendors/__init__.py
"""Vendor-specific public API endpoints"""

View File

@@ -1,128 +0,0 @@
# app/api/v1/public/vendors/vendors.py
"""
Public vendor information endpoints.
Provides public-facing vendor lookup and information retrieval.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Path
from sqlalchemy.orm import Session
from app.core.database import get_db
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/by-code/{vendor_code}")
def get_vendor_by_code(
vendor_code: str = Path(..., description="Vendor code (e.g., TECHSTORE233)"),
db: Session = Depends(get_db)
):
"""
Get public vendor information by vendor code.
This endpoint is used by:
- Frontend vendor login page to validate vendor existence
- Customer shop to display vendor information
Returns basic vendor information (no sensitive data).
"""
vendor = db.query(Vendor).filter(
Vendor.vendor_code == vendor_code.upper(),
Vendor.is_active == True
).first()
if not vendor:
logger.warning(f"Vendor lookup failed for code: {vendor_code}")
raise HTTPException(
status_code=404,
detail=f"Vendor '{vendor_code}' not found or inactive"
)
logger.info(f"Vendor lookup successful: {vendor.vendor_code}")
# Return public vendor information (no sensitive data)
return {
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"description": vendor.description,
"website": vendor.website,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
}
@router.get("/by-subdomain/{subdomain}")
def get_vendor_by_subdomain(
subdomain: str = Path(..., description="Vendor subdomain (e.g., techstore233)"),
db: Session = Depends(get_db)
):
"""
Get public vendor information by subdomain.
Used for subdomain-based vendor detection in production environments.
Example: techstore233.platform.com -> subdomain is "techstore233"
"""
vendor = db.query(Vendor).filter(
Vendor.subdomain == subdomain.lower(),
Vendor.is_active == True
).first()
if not vendor:
logger.warning(f"Vendor lookup failed for subdomain: {subdomain}")
raise HTTPException(
status_code=404,
detail=f"Vendor with subdomain '{subdomain}' not found or inactive"
)
logger.info(f"Vendor lookup by subdomain successful: {vendor.vendor_code}")
return {
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"description": vendor.description,
"website": vendor.website,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
}
@router.get("/{vendor_id}/info")
def get_vendor_info(
vendor_id: int = Path(..., description="Vendor ID"),
db: Session = Depends(get_db)
):
"""
Get public vendor information by ID.
Used when vendor_id is already known (e.g., from URL parameters).
"""
vendor = db.query(Vendor).filter(
Vendor.id == vendor_id,
Vendor.is_active == True
).first()
if not vendor:
logger.warning(f"Vendor lookup failed for ID: {vendor_id}")
raise HTTPException(
status_code=404,
detail=f"Vendor with ID {vendor_id} not found or inactive"
)
return {
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"description": vendor.description,
"website": vendor.website,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
}