From d803e1c911372b1d811c4c0808326a7e2437b722 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Thu, 1 Jan 2026 12:58:09 +0100 Subject: [PATCH] feat: add feature assignment to admin tier management UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add slide-over panel for assigning features to subscription tiers - Features grouped by category with select all/deselect all - Add puzzle-piece icon button in tier table actions - Add feature management methods to subscription-tiers.js - Fix JS-006 by adding try/catch to init function Documentation: - Update feature-gating-system.md with Admin Tier Management UI section - Update subscription-billing.md with tier management overview - Add new admin user guide: subscription-tier-management.md - Add guide to mkdocs.yml navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/templates/admin/subscription-tiers.html | 140 +++++++++++++++++ docs/features/subscription-billing.md | 20 +++ docs/guides/subscription-tier-management.md | 135 +++++++++++++++++ docs/implementation/feature-gating-system.md | 59 ++++++++ mkdocs.yml | 1 + static/admin/js/subscription-tiers.js | 149 ++++++++++++++++++- 6 files changed, 501 insertions(+), 3 deletions(-) create mode 100644 docs/guides/subscription-tier-management.md diff --git a/app/templates/admin/subscription-tiers.html b/app/templates/admin/subscription-tiers.html index 038ac915..120f018e 100644 --- a/app/templates/admin/subscription-tiers.html +++ b/app/templates/admin/subscription-tiers.html @@ -144,6 +144,9 @@
+ @@ -332,6 +335,143 @@
+ + + {% endblock %} {% block extra_scripts %} diff --git a/docs/features/subscription-billing.md b/docs/features/subscription-billing.md index a291cb68..745e609f 100644 --- a/docs/features/subscription-billing.md +++ b/docs/features/subscription-billing.md @@ -137,6 +137,26 @@ tier.features = [ ] ``` +### Admin Tier Management + +Administrators can manage subscription tiers at `/admin/subscription-tiers`: + +**Capabilities:** +- View all tiers with stats (total, active, public, MRR) +- Create new tiers with custom pricing and limits +- Edit tier properties (name, pricing, limits, Stripe IDs) +- Activate/deactivate tiers +- Assign features to tiers via slide-over panel + +**Feature Assignment:** +1. Click the puzzle-piece icon on any tier row +2. Features are displayed grouped by category +3. Use checkboxes to select/deselect features +4. Use "Select all" / "Deselect all" per category +5. Click "Save Features" to update + +See [Feature Gating System](../implementation/feature-gating-system.md#admin-tier-management-ui) for technical details. + ## Limit Enforcement Limits are enforced at the service layer: diff --git a/docs/guides/subscription-tier-management.md b/docs/guides/subscription-tier-management.md new file mode 100644 index 00000000..a11d967f --- /dev/null +++ b/docs/guides/subscription-tier-management.md @@ -0,0 +1,135 @@ +# Subscription Tier Management + +This guide explains how to manage subscription tiers and assign features to them in the admin panel. + +## Accessing Tier Management + +Navigate to **Admin → Billing & Subscriptions → Subscription Tiers** or go directly to `/admin/subscription-tiers`. + +## Dashboard Overview + +The tier management page displays: + +### Stats Cards +- **Total Tiers**: Number of configured subscription tiers +- **Active Tiers**: Tiers currently available for subscription +- **Public Tiers**: Tiers visible to vendors (excludes enterprise/custom) +- **Est. MRR**: Estimated Monthly Recurring Revenue + +### Tier Table + +Each tier shows: + +| Column | Description | +|--------|-------------| +| # | Display order (affects pricing page order) | +| Code | Unique identifier (e.g., `essential`, `professional`) | +| Name | Display name shown to vendors | +| Monthly | Monthly price in EUR | +| Annual | Annual price in EUR (or `-` if not set) | +| Orders/Mo | Monthly order limit (or `Unlimited`) | +| Products | Product limit (or `Unlimited`) | +| Team | Team member limit (or `Unlimited`) | +| Features | Number of features assigned | +| Status | Active, Private, or Inactive | +| Actions | Edit Features, Edit, Activate/Deactivate | + +## Managing Tiers + +### Creating a New Tier + +1. Click **Create Tier** button +2. Fill in the tier details: + - **Code**: Unique lowercase identifier (cannot be changed after creation) + - **Name**: Display name for the tier + - **Monthly Price**: Price in cents (e.g., 4900 for €49.00) + - **Annual Price**: Optional annual price in cents + - **Order Limit**: Leave empty for unlimited + - **Product Limit**: Leave empty for unlimited + - **Team Members**: Leave empty for unlimited + - **Display Order**: Controls sort order on pricing pages + - **Active**: Whether tier is available + - **Public**: Whether tier is visible to vendors +3. Click **Create** + +### Editing a Tier + +1. Click the **pencil icon** on the tier row +2. Modify the tier properties +3. Click **Update** + +Note: The tier code cannot be changed after creation. + +### Activating/Deactivating Tiers + +- Click the **check-circle icon** to activate an inactive tier +- Click the **x-circle icon** to deactivate an active tier + +Deactivating a tier: +- Does not affect existing subscriptions +- Hides the tier from new subscription selection +- Can be reactivated at any time + +## Managing Features + +### Assigning Features to a Tier + +1. Click the **puzzle-piece icon** on the tier row +2. A slide-over panel opens showing all available features +3. Features are grouped by category: + - Analytics + - Product Management + - Order Management + - Marketing + - Support + - Integration + - Branding + - Team + +4. Check/uncheck features to include in the tier +5. Use **Select all** or **Deselect all** per category for bulk actions +6. The footer shows the total number of selected features +7. Click **Save Features** to apply changes + +### Feature Categories + +| Category | Example Features | +|----------|------------------| +| Analytics | Basic Analytics, Analytics Dashboard, Custom Reports | +| Product Management | Bulk Edit, Variants, Bundles, Inventory Alerts | +| Order Management | Order Automation, Advanced Fulfillment, Multi-Warehouse | +| Marketing | Discount Codes, Abandoned Cart, Email Marketing, Loyalty | +| Support | Email Support, Priority Support, Phone Support, Dedicated Manager | +| Integration | Basic API, Advanced API, Webhooks, Custom Integrations | +| Branding | Theme Customization, Custom Domain, White Label | +| Team | Team Management, Role Permissions, Audit Logs | + +## Best Practices + +### Tier Pricing Strategy + +1. **Essential**: Entry-level with basic features and limits +2. **Professional**: Mid-tier with increased limits and key integrations +3. **Business**: Full-featured for growing businesses +4. **Enterprise**: Custom pricing with unlimited everything + +### Feature Assignment Tips + +- Start with fewer features in lower tiers +- Ensure each upgrade tier adds meaningful value +- Keep support features as upgrade incentives +- API access typically belongs in Business+ tiers + +### Stripe Integration + +For each tier, you can optionally configure: +- **Stripe Product ID**: Link to Stripe product +- **Stripe Monthly Price ID**: Link to monthly price +- **Stripe Annual Price ID**: Link to annual price + +These are required for automated billing via Stripe Checkout. + +## Related Documentation + +- [Subscription & Billing System](../features/subscription-billing.md) - Complete billing documentation +- [Feature Gating System](../implementation/feature-gating-system.md) - Technical feature gating details diff --git a/docs/implementation/feature-gating-system.md b/docs/implementation/feature-gating-system.md index 1737e20b..29259f6f 100644 --- a/docs/implementation/feature-gating-system.md +++ b/docs/implementation/feature-gating-system.md @@ -338,6 +338,65 @@ Located at `/admin/features`: - Search by name/code - View tier requirements +## Admin Tier Management UI + +Located at `/admin/subscription-tiers`: + +### Overview + +The subscription tiers admin page provides full CRUD functionality for managing subscription tiers and their feature assignments. + +### Features + +1. **Stats Cards**: Display total tiers, active tiers, public tiers, and estimated MRR +2. **Tier Table**: Sortable list of all tiers with: + - Display order + - Code (colored badge by tier) + - Name + - Monthly/Annual pricing + - Limits (orders, products, team members) + - Feature count + - Status (Active/Private/Inactive) + - Actions (Edit Features, Edit, Activate/Deactivate) + +3. **Create/Edit Modal**: Form with all tier fields: + - Code and Name + - Monthly and Annual pricing (in cents) + - Order, Product, and Team member limits + - Display order + - Stripe IDs (optional) + - Description + - Active/Public toggles + +4. **Feature Assignment Slide-over Panel**: + - Opens when clicking the puzzle-piece icon + - Shows all features grouped by category + - Checkbox selection with Select all/Deselect all per category + - Feature count in footer + - Save to update tier's feature assignments + +### Files + +| File | Purpose | +|------|---------| +| `app/templates/admin/subscription-tiers.html` | Page template | +| `static/admin/js/subscription-tiers.js` | Alpine.js component | +| `app/routes/admin_pages.py` | Route registration | + +### API Endpoints Used + +| Action | Method | Endpoint | +|--------|--------|----------| +| Load tiers | GET | `/api/v1/admin/subscriptions/tiers` | +| Load stats | GET | `/api/v1/admin/subscriptions/stats` | +| Create tier | POST | `/api/v1/admin/subscriptions/tiers` | +| Update tier | PATCH | `/api/v1/admin/subscriptions/tiers/{code}` | +| Delete tier | DELETE | `/api/v1/admin/subscriptions/tiers/{code}` | +| Load features | GET | `/api/v1/admin/features` | +| Load categories | GET | `/api/v1/admin/features/categories` | +| Get tier features | GET | `/api/v1/admin/features/tiers/{code}/features` | +| Update tier features | PUT | `/api/v1/admin/features/tiers/{code}/features` | + ## Migration The features are seeded via Alembic migration: diff --git a/mkdocs.yml b/mkdocs.yml index 2e84912c..bb483f5e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -211,6 +211,7 @@ nav: - User Management: guides/user-management.md - Product Management: guides/product-management.md - Inventory Management: guides/inventory-management.md + - Subscription Tier Management: guides/subscription-tier-management.md - Shop Setup: guides/shop-setup.md - CSV Import: guides/csv-import.md - Marketplace Integration: guides/marketplace-integration.md diff --git a/static/admin/js/subscription-tiers.js b/static/admin/js/subscription-tiers.js index d4e4ed8a..547237ab 100644 --- a/static/admin/js/subscription-tiers.js +++ b/static/admin/js/subscription-tiers.js @@ -1,7 +1,7 @@ // static/admin/js/subscription-tiers.js // noqa: JS-003 - Uses ...baseData which is data() with safety check -const tiersLog = window.LogConfig?.loggers?.subscriptionTiers || console; +const tiersLog = window.LogConfig?.loggers?.subscriptionTiers || window.LogConfig?.createLogger?.('subscriptionTiers') || console; function adminSubscriptionTiers() { // Get base data with safety check for standalone usage @@ -23,6 +23,16 @@ function adminSubscriptionTiers() { stats: null, includeInactive: false, + // Feature management + features: [], + categories: [], + featuresGrouped: {}, + selectedFeatures: [], + selectedTierForFeatures: null, + showFeaturePanel: false, + loadingFeatures: false, + savingFeatures: false, + // Sorting sortBy: 'display_order', sortOrder: 'asc', @@ -56,8 +66,18 @@ function adminSubscriptionTiers() { window._adminSubscriptionTiersInitialized = true; tiersLog.info('=== SUBSCRIPTION TIERS PAGE INITIALIZING ==='); - await this.loadTiers(); - await this.loadStats(); + try { + await Promise.all([ + this.loadTiers(), + this.loadStats(), + this.loadFeatures(), + this.loadCategories() + ]); + tiersLog.info('=== SUBSCRIPTION TIERS PAGE INITIALIZED ==='); + } catch (error) { + tiersLog.error('Failed to initialize subscription tiers page:', error); + this.error = 'Failed to load page data. Please refresh.'; + } }, async refresh() { @@ -207,6 +227,129 @@ function adminSubscriptionTiers() { style: 'currency', currency: 'EUR' }).format(cents / 100); + }, + + // ==================== FEATURE MANAGEMENT ==================== + + async loadFeatures() { + try { + const data = await apiClient.get('/admin/features'); + this.features = data.features || []; + tiersLog.info(`Loaded ${this.features.length} features`); + } catch (error) { + tiersLog.error('Failed to load features:', error); + } + }, + + async loadCategories() { + try { + const data = await apiClient.get('/admin/features/categories'); + this.categories = data.categories || []; + tiersLog.info(`Loaded ${this.categories.length} categories`); + } catch (error) { + tiersLog.error('Failed to load categories:', error); + } + }, + + groupFeaturesByCategory() { + this.featuresGrouped = {}; + for (const category of this.categories) { + this.featuresGrouped[category] = this.features.filter(f => f.category === category); + } + }, + + async openFeaturePanel(tier) { + tiersLog.info('Opening feature panel for tier:', tier.code); + this.selectedTierForFeatures = tier; + this.loadingFeatures = true; + this.showFeaturePanel = true; + + try { + // Load tier's current features + const data = await apiClient.get(`/admin/features/tiers/${tier.code}/features`); + if (data.features) { + this.selectedFeatures = data.features.map(f => f.code); + } else { + this.selectedFeatures = tier.features || []; + } + } catch (error) { + tiersLog.error('Failed to load tier features:', error); + this.selectedFeatures = tier.features || []; + } finally { + this.groupFeaturesByCategory(); + this.loadingFeatures = false; + } + }, + + closeFeaturePanel() { + this.showFeaturePanel = false; + this.selectedTierForFeatures = null; + this.selectedFeatures = []; + this.featuresGrouped = {}; + }, + + toggleFeature(featureCode) { + const index = this.selectedFeatures.indexOf(featureCode); + if (index === -1) { + this.selectedFeatures.push(featureCode); + } else { + this.selectedFeatures.splice(index, 1); + } + }, + + isFeatureSelected(featureCode) { + return this.selectedFeatures.includes(featureCode); + }, + + async saveFeatures() { + if (!this.selectedTierForFeatures) return; + + tiersLog.info('Saving features for tier:', this.selectedTierForFeatures.code); + this.savingFeatures = true; + + try { + await apiClient.put( + `/admin/features/tiers/${this.selectedTierForFeatures.code}/features`, + { feature_codes: this.selectedFeatures } + ); + + this.successMessage = `Features updated for ${this.selectedTierForFeatures.name}`; + this.closeFeaturePanel(); + await this.loadTiers(); + } catch (error) { + tiersLog.error('Failed to save features:', error); + this.error = error.message || 'Failed to save features'; + } finally { + this.savingFeatures = false; + } + }, + + selectAllInCategory(category) { + const categoryFeatures = this.featuresGrouped[category] || []; + for (const feature of categoryFeatures) { + if (!this.selectedFeatures.includes(feature.code)) { + this.selectedFeatures.push(feature.code); + } + } + }, + + deselectAllInCategory(category) { + const categoryFeatures = this.featuresGrouped[category] || []; + const codes = categoryFeatures.map(f => f.code); + this.selectedFeatures = this.selectedFeatures.filter(c => !codes.includes(c)); + }, + + allSelectedInCategory(category) { + const categoryFeatures = this.featuresGrouped[category] || []; + if (categoryFeatures.length === 0) return false; + return categoryFeatures.every(f => this.selectedFeatures.includes(f.code)); + }, + + formatCategoryName(category) { + return category + .split('_') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); } }; }