feat(loyalty): align program view, edit, and analytics pages across all frontends
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

Standardize naming (Program for view/edit, Analytics for stats), create shared
read-only program-view partial, fix admin edit field population bug (14 missing
fields), add store Program menu item, and rename merchant Overview→Program,
Settings→Analytics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 22:51:26 +01:00
parent aefca3115e
commit eee33d6a1b
20 changed files with 674 additions and 665 deletions

View File

@@ -3,7 +3,9 @@
Loyalty Merchant Page Routes (HTML rendering).
Merchant portal pages for:
- Loyalty overview (aggregate stats across all stores)
- Loyalty program (read-only view)
- Loyalty program edit
- Loyalty analytics (aggregate stats across all stores)
Authentication: merchant_token cookie or Authorization header.
@@ -60,23 +62,22 @@ def _get_merchant_context(
# ============================================================================
# LOYALTY OVERVIEW
# LOYALTY PROGRAM (Read-only view)
# ============================================================================
@router.get("/overview", response_class=HTMLResponse, include_in_schema=False)
async def merchant_loyalty_overview(
@router.get("/program", response_class=HTMLResponse, include_in_schema=False)
async def merchant_loyalty_program(
request: Request,
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
merchant: Merchant = Depends(get_merchant_for_current_user_page),
db: Session = Depends(get_db),
):
"""
Render merchant loyalty overview page.
Render merchant loyalty program view page.
Shows aggregate loyalty program stats across all merchant stores.
Shows read-only program configuration with link to edit.
"""
# Get merchant stats server-side
merchant_id = merchant.id
stats = {}
try:
@@ -95,25 +96,25 @@ async def merchant_loyalty_overview(
merchant_id=merchant_id,
)
return templates.TemplateResponse(
"loyalty/merchant/overview.html",
"loyalty/merchant/program.html",
context,
)
# ============================================================================
# LOYALTY SETTINGS
# LOYALTY PROGRAM EDIT
# ============================================================================
@router.get("/settings", response_class=HTMLResponse, include_in_schema=False)
async def merchant_loyalty_settings(
@router.get("/program/edit", response_class=HTMLResponse, include_in_schema=False)
async def merchant_loyalty_program_edit(
request: Request,
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
merchant: Merchant = Depends(get_merchant_for_current_user_page),
db: Session = Depends(get_db),
):
"""
Render merchant loyalty settings page.
Render merchant loyalty program edit page.
Allows merchant to create, configure, and manage their loyalty program.
"""
@@ -124,6 +125,46 @@ async def merchant_loyalty_settings(
merchant_id=merchant.id,
)
return templates.TemplateResponse(
"loyalty/merchant/settings.html",
"loyalty/merchant/program-edit.html",
context,
)
# ============================================================================
# LOYALTY ANALYTICS
# ============================================================================
@router.get("/analytics", response_class=HTMLResponse, include_in_schema=False)
async def merchant_loyalty_analytics(
request: Request,
current_user: UserContext = Depends(get_current_merchant_from_cookie_or_header),
merchant: Merchant = Depends(get_merchant_for_current_user_page),
db: Session = Depends(get_db),
):
"""
Render merchant loyalty analytics page.
Shows aggregate loyalty program stats across all merchant stores.
"""
merchant_id = merchant.id
stats = {}
try:
stats = program_service.get_merchant_stats(db, merchant_id)
except Exception:
logger.warning(
f"Failed to load loyalty stats for merchant {merchant_id}",
exc_info=True,
)
context = _get_merchant_context(
request,
db,
current_user,
loyalty_stats=stats,
merchant_id=merchant_id,
)
return templates.TemplateResponse(
"loyalty/merchant/analytics.html",
context,
)

View File

@@ -5,8 +5,9 @@ Loyalty Store Page Routes (HTML rendering).
Store pages for:
- Loyalty terminal (primary daily interface for staff)
- Loyalty members management
- Program settings
- Stats dashboard
- Program view (read-only)
- Program edit (settings)
- Analytics dashboard
Routes follow the standard store convention: /loyalty/...
so they match the menu URLs in definition.py.
@@ -183,49 +184,49 @@ async def store_loyalty_card_detail(
# ============================================================================
# STATS DASHBOARD
# PROGRAM VIEW (Read-only)
# ============================================================================
@router.get(
"/loyalty/stats",
"/loyalty/program",
response_class=HTMLResponse,
include_in_schema=False,
)
async def store_loyalty_stats(
async def store_loyalty_program(
request: Request,
store_code: str = Depends(get_resolved_store_code),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render loyalty statistics dashboard.
Shows store's loyalty program metrics and trends.
Render loyalty program view page (read-only).
Shows program configuration. Edit button shown to merchant owners only.
"""
return templates.TemplateResponse(
"loyalty/store/stats.html",
"loyalty/store/program.html",
get_store_context(request, db, current_user, store_code),
)
# ============================================================================
# SETTINGS (Merchant Owner)
# PROGRAM EDIT (Merchant Owner)
# ============================================================================
@router.get(
"/loyalty/settings",
"/loyalty/program/edit",
response_class=HTMLResponse,
include_in_schema=False,
)
async def store_loyalty_settings(
async def store_loyalty_program_edit(
request: Request,
store_code: str = Depends(get_resolved_store_code),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render loyalty program settings page.
Render loyalty program edit page.
Allows merchant owners to create or edit their loyalty program.
"""
return templates.TemplateResponse(
@@ -234,6 +235,32 @@ async def store_loyalty_settings(
)
# ============================================================================
# ANALYTICS DASHBOARD
# ============================================================================
@router.get(
"/loyalty/analytics",
response_class=HTMLResponse,
include_in_schema=False,
)
async def store_loyalty_analytics(
request: Request,
store_code: str = Depends(get_resolved_store_code),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render loyalty analytics dashboard.
Shows store's loyalty program metrics and trends.
"""
return templates.TemplateResponse(
"loyalty/store/analytics.html",
get_store_context(request, db, current_user, store_code),
)
# ============================================================================
# ENROLLMENT
# ============================================================================