# 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 (
    <>
      <button
        disabled={!hasAccess('document.scanMaintenanceSchedule')}
        onClick={() => {
          if (!hasAccess('document.scanMaintenanceSchedule')) {
            setUpgradeDialogOpen(true);
          } else {
            // Perform action
          }
        }}
      >
        Premium Feature
      </button>

      <UpgradeRequiredDialog
        featureKey="document.scanMaintenanceSchedule"
        open={upgradeDialogOpen}
        onClose={() => setUpgradeDialogOpen(false)}
      />
    </>
  );
};
```

### Gating a Checkbox (Example)

```tsx
const { hasAccess } = useTierAccess();
const canScanMaintenance = hasAccess('document.scanMaintenanceSchedule');

<label className={canScanMaintenance ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'}>
  <input
    type="checkbox"
    checked={canScanMaintenance ? scanForMaintenance : false}
    onChange={(e) => canScanMaintenance && setScanForMaintenance(e.target.checked)}
    disabled={!canScanMaintenance}
  />
  <span>Scan for Maintenance Schedule</span>
</label>
{!canScanMaintenance && (
  <button onClick={() => setUpgradeDialogOpen(true)}>
    <LockIcon />
  </button>
)}
```

## 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 <UpgradePrompt featureKey="reports.advancedAnalytics" />;
}
```

## 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
