# Tier-Based Feature Gating This document describes the user subscription tier system and how to gate features behind specific tiers. ## Overview MotoVaultPro supports three subscription tiers with hierarchical access: | Tier | Level | Description | |------|-------|-------------| | `free` | 0 | Default tier, limited features | | `pro` | 1 | Mid-tier, most features | | `enterprise` | 2 | Full access to all features | Higher tiers automatically have access to all lower-tier features (tier hierarchy). ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Frontend │ │ ┌─────────────────┐ ┌──────────────────────────────┐ │ │ │ useTierAccess │───▶│ UpgradeRequiredDialog │ │ │ │ hook │ │ component │ │ │ └────────┬────────┘ └──────────────────────────────┘ │ │ │ │ │ ▼ fetches │ ├─────────────────────────────────────────────────────────────┤ │ Backend API │ │ ┌─────────────────┐ ┌──────────────────────────────┐ │ │ │ /api/config/ │ │ /api/documents │ │ │ │ feature-tiers │ │ (tier validation) │ │ │ └────────┬────────┘ └────────┬─────────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ feature-tiers.ts (single source of truth) │ │ │ │ - FEATURE_TIERS config │ │ │ │ - canAccessFeature(), getTierLevel(), etc. │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Backend Implementation ### Feature Configuration All gated features are defined in `backend/src/core/config/feature-tiers.ts`: ```typescript export const FEATURE_TIERS = { 'document.scanMaintenanceSchedule': { minTier: 'pro', name: 'Scan for Maintenance Schedule', upgradePrompt: 'Upgrade to Pro to automatically extract maintenance schedules from your manuals.' }, // Add new features here } as const; ``` ### Utility Functions ```typescript import { canAccessFeature, getTierLevel, getRequiredTier, getFeatureConfig } from '../core/config/feature-tiers'; // Check if user can access a feature canAccessFeature('pro', 'document.scanMaintenanceSchedule'); // true canAccessFeature('free', 'document.scanMaintenanceSchedule'); // false // Get numeric tier level for comparison getTierLevel('pro'); // 1 // Get minimum required tier for a feature getRequiredTier('document.scanMaintenanceSchedule'); // 'pro' ``` ### Route-Level Protection (Middleware) Use `requireTier` for routes that require a minimum tier: ```typescript // In routes file fastify.post('/premium-endpoint', { preHandler: [fastify.requireTier({ minTier: 'pro' })], handler: controller.premiumAction }); // Or by feature key fastify.post('/scan-maintenance', { preHandler: [fastify.requireTier({ featureKey: 'document.scanMaintenanceSchedule' })], handler: controller.scanMaintenance }); ``` ### Controller-Level Validation For conditional feature checks within a controller: ```typescript async create(request: FastifyRequest, reply: FastifyReply) { const userTier = request.userContext?.subscriptionTier || 'free'; if (request.body.scanForMaintenance && !canAccessFeature(userTier, 'document.scanMaintenanceSchedule')) { const config = getFeatureConfig('document.scanMaintenanceSchedule'); return reply.code(403).send({ error: 'TIER_REQUIRED', requiredTier: config?.minTier || 'pro', currentTier: userTier, feature: 'document.scanMaintenanceSchedule', featureName: config?.name || null, upgradePrompt: config?.upgradePrompt || 'Upgrade to access this feature.', }); } // ... continue with operation } ``` ### 403 Error Response Format ```json { "error": "TIER_REQUIRED", "requiredTier": "pro", "currentTier": "free", "feature": "document.scanMaintenanceSchedule", "featureName": "Scan for Maintenance Schedule", "upgradePrompt": "Upgrade to Pro to automatically extract maintenance schedules from your manuals." } ``` ## Frontend Implementation ### useTierAccess Hook ```typescript import { useTierAccess } from '@/core/hooks'; const MyComponent = () => { const { tier, loading, hasAccess, checkAccess } = useTierAccess(); // Simple boolean check if (!hasAccess('document.scanMaintenanceSchedule')) { // Show upgrade prompt or disable feature } // Detailed access info const access = checkAccess('document.scanMaintenanceSchedule'); // { // allowed: false, // requiredTier: 'pro', // config: { name: '...', upgradePrompt: '...' } // } }; ``` ### UpgradeRequiredDialog Component ```tsx import { UpgradeRequiredDialog } from '@/shared-minimal/components'; const MyComponent = () => { const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false); const { hasAccess } = useTierAccess(); return ( <> setUpgradeDialogOpen(false)} /> ); }; ``` ### Gating a Checkbox (Example) ```tsx const { hasAccess } = useTierAccess(); const canScanMaintenance = hasAccess('document.scanMaintenanceSchedule'); {!canScanMaintenance && ( )} ``` ## Adding a New Gated Feature ### Step 1: Add to Feature Config (Backend) ```typescript // backend/src/core/config/feature-tiers.ts export const FEATURE_TIERS = { // ... existing features 'reports.advancedAnalytics': { minTier: 'enterprise', name: 'Advanced Analytics', upgradePrompt: 'Upgrade to Enterprise for advanced fleet analytics and reporting.' }, } as const; ``` ### Step 2: Add Backend Validation Either use middleware on the route: ```typescript fastify.get('/reports/analytics', { preHandler: [fastify.requireTier({ featureKey: 'reports.advancedAnalytics' })], handler: controller.getAnalytics }); ``` Or validate in the controller for conditional features. ### Step 3: Add Frontend Check ```tsx const { hasAccess } = useTierAccess(); if (!hasAccess('reports.advancedAnalytics')) { return ; } ``` ## API Endpoint The frontend fetches tier configuration from: ``` GET /api/config/feature-tiers Response: { "tiers": { "free": 0, "pro": 1, "enterprise": 2 }, "features": { "document.scanMaintenanceSchedule": { "minTier": "pro", "name": "Scan for Maintenance Schedule", "upgradePrompt": "..." } } } ``` ## User Tier Management ### Admin UI Admins can change user tiers via the Admin Users page dropdown. ### Database User tiers are stored in `user_profiles.subscription_tier` column (enum: free, pro, enterprise). ### Auth Context The user's tier is included in `request.userContext.subscriptionTier` after authentication. ## Testing ### Backend Tests ```bash # Run tier-related tests npm test -- --testPathPattern="feature-tiers|tier-guard|documents.controller.tier" ``` ### Test Cases to Cover 1. Free user attempting to use Pro feature returns 403 2. Pro user can use Pro features 3. Enterprise user can use all features 4. Unknown features are allowed (fail open) 5. Missing userContext defaults to free tier ## Future Considerations - Stripe billing integration for tier upgrades - Subscription expiration handling - Grace periods for downgraded users - Feature usage analytics