Files
hive/LICENSING_DEVELOPMENT_PLAN.md
anthonyrawlins a880b26951 feat: Implement license-aware UI for revenue optimization (Phase 3A)
Business Objective: Transform WHOOSH from license-unaware to comprehensive
license-integrated experience that drives upgrade conversions and maximizes
customer lifetime value through usage visibility.

Implementation Summary:

1. SECURE BACKEND PROXY INTEGRATION:
   - License API proxy endpoints (/api/license/status, /api/license/quotas)
   - Server-side license ID resolution (no frontend exposure)
   - Mock data support for development and testing
   - Intelligent upgrade suggestion algorithms

2. COMPREHENSIVE FRONTEND LICENSE INTEGRATION:
   - License API Client with caching and error handling
   - Global License Context for state management
   - License Status Header for always-visible tier information
   - Feature Gate Component for conditional rendering
   - License Dashboard with quotas, features, upgrade suggestions
   - Upgrade Prompt Components for revenue optimization

3. APPLICATION-WIDE INTEGRATION:
   - License Provider integrated into App context hierarchy
   - License status header in main navigation
   - License dashboard route at /license
   - Example feature gates in Analytics page
   - Version bump: → 1.2.0

Key Business Benefits:
 Revenue Optimization: Strategic feature gating drives conversions
 User Trust: Transparent license information builds confidence
 Proactive Upgrades: Usage-based suggestions with ROI estimates
 Self-Service: Clear upgrade paths reduce sales friction

Security-First Design:
🔒 All license operations server-side via proxy
🔒 No sensitive license data exposed to frontend
🔒 Feature enforcement at API level prevents bypass
🔒 Graceful degradation for license API failures

Technical Implementation:
- React 18+ with TypeScript and modern hooks
- Context API for license state management
- Tailwind CSS following existing patterns
- Backend proxy pattern for security compliance
- Comprehensive error handling and loading states

Files Created/Modified:
Backend:
- /backend/app/api/license.py - Complete license proxy API
- /backend/app/main.py - Router integration

Frontend:
- /frontend/src/services/licenseApi.ts - API client with caching
- /frontend/src/contexts/LicenseContext.tsx - Global license state
- /frontend/src/hooks/useLicenseFeatures.ts - Feature checking logic
- /frontend/src/components/license/* - Complete license UI components
- /frontend/src/App.tsx - Context integration and routing
- /frontend/package.json - Version bump to 1.2.0

This Phase 3A implementation provides the complete foundation for
license-aware user experiences, driving revenue optimization through
intelligent feature gating and upgrade suggestions while maintaining
excellent UX and security best practices.

Ready for KACHING integration and Phase 3B advanced features.

🤖 Generated with Claude Code (claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 16:20:24 +10:00

21 KiB

WHOOSH Licensing Development Plan

Date: 2025-09-01
Branch: feature/license-gating-integration
Status: Ready for implementation (depends on KACHING Phase 1)
Priority: MEDIUM - User experience and upselling integration

Executive Summary

WHOOSH currently has zero CHORUS licensing integration. The system operates without license validation, feature gating, or upselling workflows. This plan integrates WHOOSH with KACHING license authority to provide license-aware user experiences and revenue optimization.

Current State Analysis

Existing Infrastructure

  • React-based web application with modern UI components
  • Search and indexing functionality
  • User authentication and session management
  • API integration capabilities

Missing License Integration

  • No license status display - Users unaware of their tier/limits
  • No feature gating - All features available regardless of license
  • No upgrade workflows - No upselling or upgrade prompts
  • No usage tracking - No integration with KACHING telemetry
  • No quota visibility - Users can't see usage limits or consumption

Business Impact

  • Zero upselling capability - No way to drive license upgrades
  • No usage awareness - Customers don't know they're approaching limits
  • No tier differentiation - Premium features not monetized
  • Revenue leakage - Advanced features available to basic tier users

Development Phases

Phase 3A: License Status Integration (PRIORITY 1)

Goal: Display license information and status throughout WHOOSH UI

1. License API Client Implementation

// src/services/licenseApi.ts
export interface LicenseStatus {
  license_id: string;
  status: 'active' | 'suspended' | 'expired' | 'cancelled';
  tier: 'evaluation' | 'standard' | 'enterprise';
  features: string[];
  max_nodes: number;
  expires_at: string;
  quotas: {
    search_requests: { used: number; limit: number };
    storage_gb: { used: number; limit: number };
    api_calls: { used: number; limit: number };
  };
  upgrade_suggestions?: UpgradeSuggestion[];
}

export interface UpgradeSuggestion {
  reason: string;
  current_tier: string;
  suggested_tier: string;
  benefits: string[];
  roi_estimate?: string;
  urgency: 'low' | 'medium' | 'high';
}

class LicenseApiClient {
  private baseUrl: string;
  
  constructor(kachingUrl: string) {
    this.baseUrl = kachingUrl;
  }
  
  async getLicenseStatus(licenseId: string): Promise<LicenseStatus> {
    const response = await fetch(`${this.baseUrl}/v1/license/status/${licenseId}`);
    if (!response.ok) {
      throw new Error('Failed to fetch license status');
    }
    return response.json();
  }
  
  async getUsageMetrics(licenseId: string): Promise<UsageMetrics> {
    const response = await fetch(`${this.baseUrl}/v1/usage/metrics/${licenseId}`);
    return response.json();
  }
}

Backend Proxy (required in production)

To avoid exposing licensing endpoints/IDs client-side and to enforce server-side checks, WHOOSH should proxy KACHING via its own backend:

# backend/app/api/license.py (FastAPI example)
@router.get("/api/license/status")
async def get_status(user=Depends(auth)):
    license_id = await resolve_license_id_for_org(user.org_id)
    res = await kaching.get(f"/v1/license/status/{license_id}")
    return res.json()

@router.get("/api/license/quotas")
async def get_quotas(user=Depends(auth)):
    license_id = await resolve_license_id_for_org(user.org_id)
    res = await kaching.get(f"/v1/license/{license_id}/quotas")
    return res.json()

And in the React client call the WHOOSH backend instead of KACHING directly:

// src/services/licenseApi.ts (frontend)
export async function fetchLicenseStatus(): Promise<LicenseStatus> {
  const res = await fetch("/api/license/status")
  if (!res.ok) throw new Error("Failed to fetch license status")
  return res.json()
}

2. License Status Dashboard Component

// src/components/license/LicenseStatusDashboard.tsx
interface LicenseStatusDashboardProps {
  licenseId: string;
}

export const LicenseStatusDashboard: React.FC<LicenseStatusDashboardProps> = ({ licenseId }) => {
  const [licenseStatus, setLicenseStatus] = useState<LicenseStatus | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchLicenseStatus = async () => {
      try {
        // In production, call WHOOSH backend proxy endpoints
        const status = await fetchLicenseStatus();
        setLicenseStatus(status);
      } catch (error) {
        console.error('Failed to fetch license status:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchLicenseStatus();
    // Refresh every 5 minutes
    const interval = setInterval(fetchLicenseStatus, 5 * 60 * 1000);
    return () => clearInterval(interval);
  }, [licenseId]);
  
  if (loading) return <div>Loading license information...</div>;
  if (!licenseStatus) return <div>License information unavailable</div>;
  
  return (
    <div className="license-status-dashboard">
      <LicenseStatusCard status={licenseStatus} />
      <QuotaUsageCard quotas={licenseStatus.quotas} />
      {licenseStatus.upgrade_suggestions?.map((suggestion, idx) => (
        <UpgradeSuggestionCard key={idx} suggestion={suggestion} />
      ))}
    </div>
  );
};

3. License Status Header Component

// src/components/layout/LicenseStatusHeader.tsx
export const LicenseStatusHeader: React.FC = () => {
  const { licenseStatus } = useLicenseContext();
  
  const getStatusColor = (status: string) => {
    switch (status) {
      case 'active': return 'text-green-600';
      case 'suspended': return 'text-red-600';
      case 'expired': return 'text-orange-600';
      default: return 'text-gray-600';
    }
  };
  
  return (
    <div className="flex items-center space-x-4 text-sm">
      <div className={`font-medium ${getStatusColor(licenseStatus?.status || '')}`}>
        {licenseStatus?.tier?.toUpperCase()} License
      </div>
      <div className="text-gray-500">
        {licenseStatus?.max_nodes} nodes max
      </div>
      <div className="text-gray-500">
        Expires: {new Date(licenseStatus?.expires_at || '').toLocaleDateString()}
      </div>
      {licenseStatus?.status !== 'active' && (
        <button className="bg-blue-600 text-white px-3 py-1 rounded text-xs hover:bg-blue-700">
          Renew License
        </button>
      )}
    </div>
  );
};

Phase 3B: Feature Gating Implementation (PRIORITY 2)

Goal: Restrict features based on license tier and show upgrade prompts

1. Feature Gate Hook

// src/hooks/useLicenseFeatures.ts
export const useLicenseFeatures = () => {
  const { licenseStatus } = useLicenseContext();
  
  const hasFeature = (feature: string): boolean => {
    return licenseStatus?.features?.includes(feature) || false;
  };
  
  const canUseAdvancedSearch = (): boolean => {
    return hasFeature('advanced-search');
  };
  
  const canUseAnalytics = (): boolean => {
    return hasFeature('advanced-analytics');
  };
  
  const canUseBulkOperations = (): boolean => {
    return hasFeature('bulk-operations');
  };
  
  const getMaxSearchResults = (): number => {
    if (hasFeature('enterprise-search')) return 10000;
    if (hasFeature('advanced-search')) return 1000;
    return 100; // Basic tier
  };
  
  return {
    hasFeature,
    canUseAdvancedSearch,
    canUseAnalytics,
    canUseBulkOperations,
    getMaxSearchResults,
  };
};

2. Feature Gate Component

// src/components/license/FeatureGate.tsx
interface FeatureGateProps {
  feature: string;
  children: React.ReactNode;
  fallback?: React.ReactNode;
  showUpgradePrompt?: boolean;
}

export const FeatureGate: React.FC<FeatureGateProps> = ({ 
  feature, 
  children, 
  fallback, 
  showUpgradePrompt = true 
}) => {
  const { hasFeature } = useLicenseFeatures();
  const { licenseStatus } = useLicenseContext();
  
  if (hasFeature(feature)) {
    return <>{children}</>;
  }
  
  if (fallback) {
    return <>{fallback}</>;
  }
  
  if (showUpgradePrompt) {
    return (
      <UpgradePrompt 
        feature={feature}
        currentTier={licenseStatus?.tier || 'unknown'}
      />
    );
  }
  
  return null;
};

// Usage throughout WHOOSH:
// <FeatureGate feature="advanced-analytics">
//   <AdvancedAnalyticsPanel />
// </FeatureGate>

3. Feature-Specific Gates

// src/components/search/AdvancedSearchFilters.tsx
export const AdvancedSearchFilters: React.FC = () => {
  const { canUseAdvancedSearch } = useLicenseFeatures();
  
  return (
    <FeatureGate feature="advanced-search">
      <div className="advanced-filters">
        {/* Advanced search filter components */}
      </div>
      <UpgradePrompt 
        feature="advanced-search"
        message="Unlock advanced search filters with Standard tier"
        benefits={[
          "Date range filtering",
          "Content type filters", 
          "Custom field search",
          "Saved search queries"
        ]}
      />
    </FeatureGate>
  );
};

Phase 3C: Quota Monitoring & Alerts (PRIORITY 3)

Goal: Show usage quotas and proactive upgrade suggestions

1. Quota Usage Components

// src/components/license/QuotaUsageCard.tsx
interface QuotaUsageCardProps {
  quotas: LicenseStatus['quotas'];
}

export const QuotaUsageCard: React.FC<QuotaUsageCardProps> = ({ quotas }) => {
  const getUsagePercentage = (used: number, limit: number): number => {
    return Math.round((used / limit) * 100);
  };
  
  const getUsageColor = (percentage: number): string => {
    if (percentage >= 90) return 'bg-red-500';
    if (percentage >= 75) return 'bg-yellow-500';
    return 'bg-green-500';
  };
  
  return (
    <div className="quota-usage-card bg-white rounded-lg shadow p-6">
      <h3 className="text-lg font-semibold mb-4">Usage Overview</h3>
      
      {Object.entries(quotas).map(([key, quota]) => {
        const percentage = getUsagePercentage(quota.used, quota.limit);
        
        return (
          <div key={key} className="mb-4">
            <div className="flex justify-between text-sm font-medium">
              <span>{key.replace('_', ' ').toUpperCase()}</span>
              <span>{quota.used.toLocaleString()} / {quota.limit.toLocaleString()}</span>
            </div>
            <div className="w-full bg-gray-200 rounded-full h-2 mt-1">
              <div 
                className={`h-2 rounded-full ${getUsageColor(percentage)}`}
                style={{ width: `${percentage}%` }}
              />
            </div>
            {percentage >= 80 && (
              <div className="text-xs text-orange-600 mt-1">
                ⚠️ Approaching limit - consider upgrading
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
};

2. Upgrade Suggestion Component

// src/components/license/UpgradeSuggestionCard.tsx
interface UpgradeSuggestionCardProps {
  suggestion: UpgradeSuggestion;
}

export const UpgradeSuggestionCard: React.FC<UpgradeSuggestionCardProps> = ({ suggestion }) => {
  const getUrgencyColor = (urgency: string): string => {
    switch (urgency) {
      case 'high': return 'border-red-500 bg-red-50';
      case 'medium': return 'border-yellow-500 bg-yellow-50';
      default: return 'border-blue-500 bg-blue-50';
    }
  };
  
  return (
    <div className={`upgrade-suggestion border-l-4 p-4 rounded ${getUrgencyColor(suggestion.urgency)}`}>
      <div className="flex items-center justify-between">
        <div>
          <h4 className="font-semibold">{suggestion.reason}</h4>
          <p className="text-sm text-gray-600 mt-1">
            Upgrade from {suggestion.current_tier} to {suggestion.suggested_tier}
          </p>
          {suggestion.roi_estimate && (
            <p className="text-sm font-medium text-green-600 mt-1">
              Estimated ROI: {suggestion.roi_estimate}
            </p>
          )}
        </div>
        <button 
          className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
          onClick={() => handleUpgradeRequest(suggestion)}
        >
          Upgrade Now
        </button>
      </div>
      
      <div className="mt-3">
        <p className="text-sm font-medium">Benefits:</p>
        <ul className="text-sm text-gray-600 mt-1">
          {suggestion.benefits.map((benefit, idx) => (
            <li key={idx} className="flex items-center">
              <span className="text-green-500 mr-2"></span>
              {benefit}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

Phase 3D: Self-Service Upgrade Workflows (PRIORITY 4)

Goal: Enable customers to upgrade licenses directly from WHOOSH

1. Upgrade Request Modal

// src/components/license/UpgradeRequestModal.tsx
export const UpgradeRequestModal: React.FC = () => {
  const [selectedTier, setSelectedTier] = useState<string>('');
  const [justification, setJustification] = useState<string>('');
  
  const handleUpgradeRequest = async () => {
    const request = {
      current_tier: licenseStatus?.tier,
      requested_tier: selectedTier,
      justification,
      usage_evidence: await getUsageEvidence(),
      contact_email: userEmail,
    };
    
    // Send to KACHING upgrade request endpoint
    await licenseApi.requestUpgrade(request);
    
    // Show success message and close modal
    showNotification('Upgrade request submitted successfully!');
  };
  
  return (
    <Modal>
      <div className="upgrade-request-modal">
        <h2>Request License Upgrade</h2>
        
        <TierComparisonTable 
          currentTier={licenseStatus?.tier}
          highlightTier={selectedTier}
          onTierSelect={setSelectedTier}
        />
        
        <textarea 
          placeholder="Tell us about your use case and why you need an upgrade..."
          value={justification}
          onChange={(e) => setJustification(e.target.value)}
          className="w-full p-3 border rounded"
        />
        
        <UsageEvidencePanel licenseId={licenseStatus?.license_id} />
        
        <div className="flex justify-end space-x-3 mt-6">
          <button onClick={onClose}>Cancel</button>
          <button 
            onClick={handleUpgradeRequest}
            className="bg-blue-600 text-white px-6 py-2 rounded"
          >
            Submit Request
          </button>
        </div>
      </div>
    </Modal>
  );
};

2. Contact Sales Integration

// src/components/license/ContactSalesWidget.tsx
export const ContactSalesWidget: React.FC = () => {
  const { licenseStatus } = useLicenseContext();
  
  const generateSalesContext = () => ({
    license_id: licenseStatus?.license_id,
    current_tier: licenseStatus?.tier,
    usage_summary: getUsageSummary(),
    pain_points: identifyPainPoints(),
    upgrade_urgency: calculateUpgradeUrgency(),
  });
  
  return (
    <div className="contact-sales-widget">
      <h3>Need a Custom Solution?</h3>
      <p>Talk to our sales team about enterprise features and pricing.</p>
      
      <button 
        onClick={() => openSalesChat(generateSalesContext())}
        className="bg-green-600 text-white px-4 py-2 rounded"
      >
        Contact Sales
      </button>
      
      <div className="text-xs text-gray-500 mt-2">
        Your usage data will be shared to provide personalized recommendations
      </div>
    </div>
  );
};

Implementation Files Structure

WHOOSH/
├── src/
│   ├── services/
│   │   ├── licenseApi.ts          # KACHING API client
│   │   └── usageTracking.ts       # Usage metrics collection
│   ├── hooks/
│   │   ├── useLicenseContext.ts   # License state management
│   │   └── useLicenseFeatures.ts  # Feature gate logic
│   ├── components/
│   │   ├── license/
│   │   │   ├── LicenseStatusDashboard.tsx
│   │   │   ├── FeatureGate.tsx
│   │   │   ├── QuotaUsageCard.tsx
│   │   │   ├── UpgradeSuggestionCard.tsx
│   │   │   └── UpgradeRequestModal.tsx
│   │   └── layout/
│   │       └── LicenseStatusHeader.tsx
│   ├── contexts/
│   │   └── LicenseContext.tsx     # Global license state
│   └── utils/
│       ├── licenseHelpers.ts      # License utility functions
│       └── usageAnalytics.ts      # Usage calculation helpers
├── public/
│   └── license-tiers.json         # Tier comparison data
└── docs/
    └── license-integration.md     # Integration documentation

Configuration Requirements

Environment Variables

# KACHING integration
REACT_APP_KACHING_URL=https://kaching.chorus.services   # Dev only; in prod, use backend proxy
# Do NOT expose license keys/IDs in client-side configuration

# Feature flags
REACT_APP_ENABLE_LICENSE_GATING=true
REACT_APP_ENABLE_UPGRADE_PROMPTS=true

# Sales integration
REACT_APP_SALES_CHAT_URL=https://sales.chorus.services/chat
REACT_APP_SALES_EMAIL=sales@chorus.services

License Context Configuration

// src/config/licenseConfig.ts
export const LICENSE_CONFIG = {
  tiers: {
    evaluation: {
      display_name: 'Evaluation',
      max_search_results: 50,
      features: ['basic-search'],
      color: 'gray'
    },
    standard: {
      display_name: 'Standard',
      max_search_results: 1000,
      features: ['basic-search', 'advanced-search', 'analytics'],
      color: 'blue'
    },
    enterprise: {
      display_name: 'Enterprise',
      max_search_results: -1, // unlimited
      features: ['basic-search', 'advanced-search', 'analytics', 'bulk-operations', 'enterprise-support'],
      color: 'purple'
    }
  },
  
  upgrade_thresholds: {
    search_requests: 0.8,   // Show upgrade at 80% quota usage
    storage_gb: 0.9,        // Show upgrade at 90% storage usage
    api_calls: 0.85         // Show upgrade at 85% API usage
  }
};

Testing Strategy

Unit Tests Required

  • Feature gate hook functionality
  • License status display components
  • Quota usage calculations
  • Upgrade suggestion logic

Integration Tests Required

  • End-to-end license status fetching
  • Feature gating across different components
  • Upgrade request workflow
  • Usage tracking integration

User Experience Tests

  • License tier upgrade flows
  • Feature restriction user messaging
  • Quota limit notifications
  • Sales contact workflows

Success Criteria

Phase 3A Success

  • License status displayed prominently in UI
  • Real-time quota usage monitoring
  • Tier information clearly communicated to users

Phase 3B Success

  • Features properly gated based on license tier
  • Upgrade prompts appear for restricted features
  • Clear messaging about tier limitations

Phase 3C Success

  • Quota usage alerts trigger at appropriate thresholds
  • Upgrade suggestions appear based on usage patterns
  • Usage trends drive automated upselling

Phase 3D Success

  • Self-service upgrade request workflow functional
  • Sales team integration captures relevant context
  • Customer can understand upgrade benefits clearly

Overall Success

  • Increased license upgrade conversion rate
  • Users aware of their license limitations
  • Proactive upgrade suggestions drive revenue
  • Seamless integration with KACHING license authority

Business Impact Metrics

Revenue Metrics

  • License upgrade conversion rate (target: 15% monthly)
  • Average revenue per user increase (target: 25% annually)
  • Feature adoption rates by tier

User Experience Metrics

  • License status awareness (target: 90% of users know their tier)
  • Time to upgrade after quota warning (target: <7 days)
  • Support tickets related to license confusion (target: <5% of total)

Technical Metrics

  • License API response times (target: <200ms)
  • Feature gate reliability (target: 99.9% uptime)
  • Quota usage accuracy (target: 100% data integrity)

Dependencies

  • KACHING Phase 1 Complete: Requires license server with quota APIs
  • User Authentication: Must identify users to fetch license status
  • Usage Tracking: Requires instrumentation to measure quota consumption

Security Considerations

  1. License ID Protection: Never expose license keys/IDs in client-side code; resolve license_id server-side
  2. API Authentication: Secure backend→KACHING with service credentials; frontend talks only to WHOOSH backend
  3. Feature Bypass Prevention: Enforce entitlements server-side for any sensitive operations
  4. Usage Data Privacy: Comply with data protection regulations for usage tracking

This plan transforms WHOOSH from license-unaware to a comprehensive license-integrated experience that drives revenue optimization and user satisfaction.