feat(merchant): extract merchant portal as first-class frontend with auth, Tailwind fixes, and Gitea CI
Some checks failed
CI / ruff (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / architecture (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / audit (push) Has been cancelled
CI / docs (push) Has been cancelled

- Extract login/dashboard from billing module into core (matching admin pattern)
- Add merchant auth API with path-isolated cookies (path=/merchants)
- Add merchant base layout with sidebar/header partials and Alpine.js init
- Add frontend detection and login redirect for MERCHANT type
- Wire merchant token in shared api-client.js (get/clear)
- Migrate billing templates to merchant base with dark mode support
- Fix Tailwind: rename shop→storefront in sources and config
- DRY Makefile tailwind targets with TAILWIND_FRONTENDS loop
- Rebuild all Tailwind outputs (production minified)
- Add Gitea Actions CI workflow (ruff, pytest, architecture, docs)
- Add Gitea deployment documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 20:25:29 +01:00
parent ecb5309879
commit 0437af67ec
31 changed files with 1925 additions and 780 deletions

View File

@@ -0,0 +1,93 @@
# app/modules/core/routes/pages/merchant.py
"""
Core Merchant Page Routes (HTML rendering).
Merchant pages for core functionality:
- Login page
- Dashboard
- Root redirect
These are core concerns, not billing-specific, matching the admin pattern
where login/dashboard live in core (app/modules/core/routes/pages/admin.py).
"""
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session
from app.api.deps import get_current_merchant_from_cookie_or_header, get_current_merchant_optional, get_db
from app.modules.core.utils.page_context import get_context_for_frontend
from app.modules.enums import FrontendType
from app.templates_config import templates
from models.schema.auth import UserContext
ROUTE_CONFIG = {
"prefix": "",
}
router = APIRouter()
# ============================================================================
# PUBLIC ROUTES (No Authentication Required)
# ============================================================================
@router.get("/", response_class=RedirectResponse, include_in_schema=False)
async def merchant_root(
current_user: UserContext | None = Depends(get_current_merchant_optional),
):
"""
Redirect /merchants/ based on authentication status.
- Authenticated merchant users -> /merchants/dashboard
- Unauthenticated users -> /merchants/login
"""
if current_user:
return RedirectResponse(url="/merchants/dashboard", status_code=302)
return RedirectResponse(url="/merchants/login", status_code=302)
@router.get("/login", response_class=HTMLResponse, include_in_schema=False)
async def merchant_login_page(
request: Request,
current_user: UserContext | None = Depends(get_current_merchant_optional),
):
"""
Render merchant login page.
If user is already authenticated as merchant, redirect to dashboard.
Otherwise, show login form.
"""
if current_user:
return RedirectResponse(url="/merchants/dashboard", status_code=302)
return templates.TemplateResponse("tenancy/merchant/login.html", {"request": request})
# ============================================================================
# AUTHENTICATED ROUTES (Merchant Only)
# ============================================================================
@router.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
async def merchant_dashboard_page(
request: Request,
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render merchant dashboard page.
Shows merchant overview with stores and subscriptions.
"""
context = get_context_for_frontend(
FrontendType.MERCHANT,
request,
db,
user=current_user,
)
return templates.TemplateResponse(
"core/merchant/dashboard.html",
context,
)