# app/routes/vendor_pages.py """ Vendor HTML page routes using Jinja2 templates. These routes serve HTML pages for vendor-facing interfaces. Follows the same minimal server-side rendering pattern as admin routes. All routes except /login require vendor authentication. Authentication failures redirect to /vendor/{vendor_code}/login. Routes: - GET /vendor/{vendor_code}/ → Redirect to login or dashboard - GET /vendor/{vendor_code}/login → Vendor login page - GET /vendor/{vendor_code}/dashboard → Vendor dashboard - GET /vendor/{vendor_code}/products → Product management - GET /vendor/{vendor_code}/orders → Order management - GET /vendor/{vendor_code}/customers → Customer management - GET /vendor/{vendor_code}/inventory → Inventory management - GET /vendor/{vendor_code}/marketplace → Marketplace imports - GET /vendor/{vendor_code}/team → Team management - GET /vendor/{vendor_code}/settings → Vendor settings """ from fastapi import APIRouter, Request, Depends, Path, HTTPException from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from typing import Optional import logging from app.api.deps import ( get_current_vendor_from_cookie_or_header, get_current_vendor_optional, get_db ) from app.services.content_page_service import content_page_service from models.database.user import User logger = logging.getLogger(__name__) router = APIRouter() templates = Jinja2Templates(directory="app/templates") # ============================================================================ # PUBLIC ROUTES (No Authentication Required) # ============================================================================ @router.get("/{vendor_code}", response_class=RedirectResponse, include_in_schema=False) async def vendor_root_no_slash(vendor_code: str = Path(..., description="Vendor code")): """ Redirect /vendor/{code} (no trailing slash) to login page. Handles requests without trailing slash. """ return RedirectResponse(url=f"/vendor/{vendor_code}/login", status_code=302) @router.get("/{vendor_code}/", response_class=RedirectResponse, include_in_schema=False) async def vendor_root( vendor_code: str = Path(..., description="Vendor code"), current_user: Optional[User] = Depends(get_current_vendor_optional) ): """ Redirect /vendor/{code}/ based on authentication status. - Authenticated vendor users → /vendor/{code}/dashboard - Unauthenticated users → /vendor/{code}/login """ if current_user: # User is already logged in as vendor, redirect to dashboard return RedirectResponse(url=f"/vendor/{vendor_code}/dashboard", status_code=302) return RedirectResponse(url=f"/vendor/{vendor_code}/login", status_code=302) @router.get("/{vendor_code}/login", response_class=HTMLResponse, include_in_schema=False) async def vendor_login_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: Optional[User] = Depends(get_current_vendor_optional) ): """ Render vendor login page. If user is already authenticated as vendor, redirect to dashboard. Otherwise, show login form. JavaScript will: - Load vendor info via API - Handle login form submission - Redirect to dashboard on success """ if current_user: # User is already logged in as vendor, redirect to dashboard return RedirectResponse(url=f"/vendor/{vendor_code}/dashboard", status_code=302) return templates.TemplateResponse( "vendor/login.html", { "request": request, "vendor_code": vendor_code, } ) # ============================================================================ # AUTHENTICATED ROUTES (Vendor Users Only) # ============================================================================ @router.get("/{vendor_code}/dashboard", response_class=HTMLResponse, include_in_schema=False) async def vendor_dashboard_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render vendor dashboard. JavaScript will: - Load vendor info via API - Load dashboard stats via API - Load recent orders via API - Handle all interactivity """ return templates.TemplateResponse( "vendor/dashboard.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # PRODUCT MANAGEMENT # ============================================================================ @router.get("/{vendor_code}/products", response_class=HTMLResponse, include_in_schema=False) async def vendor_products_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render products management page. JavaScript loads product list via API. """ return templates.TemplateResponse( "vendor/products.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # ORDER MANAGEMENT # ============================================================================ @router.get("/{vendor_code}/orders", response_class=HTMLResponse, include_in_schema=False) async def vendor_orders_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render orders management page. JavaScript loads order list via API. """ return templates.TemplateResponse( "vendor/orders.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # CUSTOMER MANAGEMENT # ============================================================================ @router.get("/{vendor_code}/customers", response_class=HTMLResponse, include_in_schema=False) async def vendor_customers_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render customers management page. JavaScript loads customer list via API. """ return templates.TemplateResponse( "vendor/customers.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # INVENTORY MANAGEMENT # ============================================================================ @router.get("/{vendor_code}/inventory", response_class=HTMLResponse, include_in_schema=False) async def vendor_inventory_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render inventory management page. JavaScript loads inventory data via API. """ return templates.TemplateResponse( "vendor/inventory.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # MARKETPLACE IMPORTS # ============================================================================ @router.get("/{vendor_code}/marketplace", response_class=HTMLResponse, include_in_schema=False) async def vendor_marketplace_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render marketplace import page. JavaScript loads import jobs and products via API. """ return templates.TemplateResponse( "vendor/marketplace.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # TEAM MANAGEMENT # ============================================================================ @router.get("/{vendor_code}/team", response_class=HTMLResponse, include_in_schema=False) async def vendor_team_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render team management page. JavaScript loads team members via API. """ return templates.TemplateResponse( "vendor/team.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # PROFILE & SETTINGS # ============================================================================ @router.get("/{vendor_code}/profile", response_class=HTMLResponse, include_in_schema=False) async def vendor_profile_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render vendor profile page. User can manage their personal profile information. """ return templates.TemplateResponse( "vendor/profile.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) @router.get("/{vendor_code}/settings", response_class=HTMLResponse, include_in_schema=False) async def vendor_settings_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), current_user: User = Depends(get_current_vendor_from_cookie_or_header) ): """ Render vendor settings page. JavaScript loads settings via API. """ return templates.TemplateResponse( "vendor/settings.html", { "request": request, "user": current_user, "vendor_code": vendor_code, } ) # ============================================================================ # DYNAMIC CONTENT PAGES (CMS) # ============================================================================ @router.get("/{vendor_code}/{slug}", response_class=HTMLResponse, include_in_schema=False) async def vendor_content_page( request: Request, vendor_code: str = Path(..., description="Vendor code"), slug: str = Path(..., description="Content page slug"), db: Session = Depends(get_db) ): """ Generic content page handler for vendor shop (CMS). Handles dynamic content pages like: - /vendors/wizamart/about, /vendors/wizamart/faq, /vendors/wizamart/contact, etc. Features: - Two-tier system: Vendor overrides take priority, fallback to platform defaults - Only shows published pages - Returns 404 if page not found or unpublished NOTE: This is a catch-all route and must be registered LAST to avoid shadowing other specific routes. """ logger.debug( f"[VENDOR_HANDLER] vendor_content_page REACHED", extra={ "path": request.url.path, "vendor_code": vendor_code, "slug": slug, "vendor": getattr(request.state, 'vendor', 'NOT SET'), "context": getattr(request.state, 'context_type', 'NOT SET'), } ) vendor = getattr(request.state, 'vendor', None) vendor_id = vendor.id if vendor else None # Load content page from database (vendor override → platform default) page = content_page_service.get_page_for_vendor( db, slug=slug, vendor_id=vendor_id, include_unpublished=False ) if not page: logger.info( f"[VENDOR_HANDLER] Content page not found: {slug}", extra={ "slug": slug, "vendor_code": vendor_code, "vendor_id": vendor_id, } ) raise HTTPException(status_code=404, detail="Page not found") logger.info( f"[VENDOR_HANDLER] Rendering CMS page: {page.title}", extra={ "slug": slug, "page_id": page.id, "is_vendor_override": page.vendor_id is not None, "vendor_id": vendor_id, } ) return templates.TemplateResponse( "shop/content-page.html", { "request": request, "page": page, "vendor_code": vendor_code, } )