feat: loyalty feature provider, admin data fixes, storefront mobile menu
- Add LoyaltyFeatureProvider with 11 BINARY/MERCHANT features for billing feature gating, wired into loyalty module definition - Fix subscription-tiers admin page showing 0 features by populating feature_codes from tier relationship in all admin tier endpoints - Fix merchants admin page showing 0 stores and N/A owner by adding store_count and owner_email to MerchantResponse and eager-loading owner - Add "no tiers" warning with link in subscription creation modal when platform has no configured tiers - Add missing mobile menu panel to storefront base template so hamburger toggle actually shows navigation links Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,7 @@ def get_all_merchants(
|
||||
name=c.name,
|
||||
description=c.description,
|
||||
owner_user_id=c.owner_user_id,
|
||||
owner_email=c.owner.email if c.owner else None,
|
||||
contact_email=c.contact_email,
|
||||
contact_phone=c.contact_phone,
|
||||
website=c.website,
|
||||
@@ -118,6 +119,7 @@ def get_all_merchants(
|
||||
is_verified=c.is_verified,
|
||||
created_at=c.created_at.isoformat(),
|
||||
updated_at=c.updated_at.isoformat(),
|
||||
store_count=c.store_count,
|
||||
)
|
||||
for c in merchants
|
||||
],
|
||||
|
||||
@@ -90,6 +90,7 @@ class MerchantResponse(BaseModel):
|
||||
|
||||
# Owner information
|
||||
owner_user_id: int
|
||||
owner_email: str | None = Field(None, description="Owner's email address")
|
||||
|
||||
# Contact Information
|
||||
contact_email: str
|
||||
@@ -108,6 +109,9 @@ class MerchantResponse(BaseModel):
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
# Store statistics
|
||||
store_count: int = Field(0, description="Number of stores under this merchant")
|
||||
|
||||
|
||||
class MerchantDetailResponse(MerchantResponse):
|
||||
"""
|
||||
@@ -117,11 +121,9 @@ class MerchantDetailResponse(MerchantResponse):
|
||||
"""
|
||||
|
||||
# Owner details (from related User)
|
||||
owner_email: str | None = Field(None, description="Owner's email address")
|
||||
owner_username: str | None = Field(None, description="Owner's username")
|
||||
|
||||
# Store statistics
|
||||
store_count: int = Field(0, description="Number of stores under this merchant")
|
||||
active_store_count: int = Field(
|
||||
0, description="Number of active stores under this merchant"
|
||||
)
|
||||
|
||||
@@ -148,7 +148,10 @@ class MerchantService:
|
||||
Returns:
|
||||
Tuple of (merchants list, total count)
|
||||
"""
|
||||
query = select(Merchant).options(joinedload(Merchant.stores))
|
||||
query = select(Merchant).options(
|
||||
joinedload(Merchant.stores),
|
||||
joinedload(Merchant.owner),
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
if search:
|
||||
|
||||
@@ -331,12 +331,20 @@
|
||||
<!-- Tier Selector -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Subscription Tier</label>
|
||||
<select x-model="createForm.tier_code"
|
||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500">
|
||||
<template x-for="tier in tiers" :key="tier.code">
|
||||
<option :value="tier.code" x-text="tier.name + ' — ' + formatTierPrice(tier)"></option>
|
||||
</template>
|
||||
</select>
|
||||
<template x-if="tiers.length > 0">
|
||||
<select x-model="createForm.tier_code"
|
||||
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500">
|
||||
<template x-for="tier in tiers" :key="tier.code">
|
||||
<option :value="tier.code" x-text="tier.name + ' — ' + formatTierPrice(tier)"></option>
|
||||
</template>
|
||||
</select>
|
||||
</template>
|
||||
<template x-if="tiers.length === 0">
|
||||
<div class="p-3 text-sm text-amber-700 bg-amber-50 border border-amber-200 rounded-lg dark:text-amber-300 dark:bg-amber-900/20 dark:border-amber-800">
|
||||
No tiers configured for this platform.
|
||||
<a href="/admin/subscription-tiers" class="underline font-medium hover:text-amber-900 dark:hover:text-amber-100">Create a tier first</a>.
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
@@ -372,7 +380,7 @@
|
||||
Cancel
|
||||
</button>
|
||||
<button @click="createSubscription()"
|
||||
:disabled="creatingSubscription"
|
||||
:disabled="creatingSubscription || tiers.length === 0"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!creatingSubscription">Create</span>
|
||||
<span x-show="creatingSubscription">Creating...</span>
|
||||
|
||||
Reference in New Issue
Block a user