feat: Implement user tier-based feature gating system (refs #8)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m35s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 27s
Deploy to Staging / Verify Staging (pull_request) Successful in 5s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 5s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

Add subscription tier system to gate features behind Free/Pro/Enterprise tiers.

Backend:
- Create feature-tiers.ts with FEATURE_TIERS config and utilities
- Add /api/config/feature-tiers endpoint for frontend config fetch
- Create requireTier middleware for route-level tier enforcement
- Add subscriptionTier to request.userContext in auth plugin
- Gate scanForMaintenance in documents controller (Pro+ required)
- Add migration to reset scanForMaintenance for free users

Frontend:
- Create useTierAccess hook for tier checking
- Create UpgradeRequiredDialog component (responsive)
- Gate DocumentForm checkbox with lock icon for free users
- Add SubscriptionTier type to profile.types.ts

Documentation:
- Add TIER-GATING.md with usage guide

Tests: 30 passing (feature-tiers, tier-guard, controller)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-04 14:34:47 -06:00
parent 453083b7db
commit f494f77150
18 changed files with 1544 additions and 7 deletions

View File

@@ -0,0 +1,73 @@
/**
* @ai-summary Feature tier configuration and utilities
* @ai-context Defines feature-to-tier mapping for gating premium features
*/
import { SubscriptionTier } from '../../features/user-profile/domain/user-profile.types';
// Tier hierarchy: higher number = higher access level
export const TIER_LEVELS: Record<SubscriptionTier, number> = {
free: 0,
pro: 1,
enterprise: 2,
} as const;
// Feature configuration interface
export interface FeatureConfig {
minTier: SubscriptionTier;
name: string;
upgradePrompt: string;
}
// Feature registry - add new gated features here
export const FEATURE_TIERS: Record<string, FeatureConfig> = {
'document.scanMaintenanceSchedule': {
minTier: 'pro',
name: 'Scan for Maintenance Schedule',
upgradePrompt: 'Upgrade to Pro to automatically extract maintenance schedules from your vehicle manuals.',
},
} as const;
/**
* Get numeric level for a subscription tier
*/
export function getTierLevel(tier: SubscriptionTier): number {
return TIER_LEVELS[tier] ?? 0;
}
/**
* Check if a user tier can access a feature
* Higher tiers inherit access to all lower tier features
*/
export function canAccessFeature(userTier: SubscriptionTier, featureKey: string): boolean {
const feature = FEATURE_TIERS[featureKey];
if (!feature) {
// Unknown features are accessible by all (fail open for unlisted features)
return true;
}
return getTierLevel(userTier) >= getTierLevel(feature.minTier);
}
/**
* Get the minimum required tier for a feature
* Returns null if feature is not gated
*/
export function getRequiredTier(featureKey: string): SubscriptionTier | null {
const feature = FEATURE_TIERS[featureKey];
return feature?.minTier ?? null;
}
/**
* Get full feature configuration
* Returns undefined if feature is not registered
*/
export function getFeatureConfig(featureKey: string): FeatureConfig | undefined {
return FEATURE_TIERS[featureKey];
}
/**
* Get all feature configurations (for API endpoint)
*/
export function getAllFeatureConfigs(): Record<string, FeatureConfig> {
return { ...FEATURE_TIERS };
}