diff --git a/LICENSING_DEVELOPMENT_PLAN.md b/LICENSING_DEVELOPMENT_PLAN.md new file mode 100644 index 00000000..baa2249e --- /dev/null +++ b/LICENSING_DEVELOPMENT_PLAN.md @@ -0,0 +1,670 @@ +# 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 +```typescript +// 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 { + 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 { + 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: + +```python +# 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: + +```typescript +// src/services/licenseApi.ts (frontend) +export async function fetchLicenseStatus(): Promise { + 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 +```typescript +// src/components/license/LicenseStatusDashboard.tsx +interface LicenseStatusDashboardProps { + licenseId: string; +} + +export const LicenseStatusDashboard: React.FC = ({ licenseId }) => { + const [licenseStatus, setLicenseStatus] = useState(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
Loading license information...
; + if (!licenseStatus) return
License information unavailable
; + + return ( +
+ + + {licenseStatus.upgrade_suggestions?.map((suggestion, idx) => ( + + ))} +
+ ); +}; +``` + +#### 3. License Status Header Component +```typescript +// 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 ( +
+
+ {licenseStatus?.tier?.toUpperCase()} License +
+
+ {licenseStatus?.max_nodes} nodes max +
+
+ Expires: {new Date(licenseStatus?.expires_at || '').toLocaleDateString()} +
+ {licenseStatus?.status !== 'active' && ( + + )} +
+ ); +}; +``` + +### Phase 3B: Feature Gating Implementation (PRIORITY 2) +**Goal**: Restrict features based on license tier and show upgrade prompts + +#### 1. Feature Gate Hook +```typescript +// 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 +```typescript +// src/components/license/FeatureGate.tsx +interface FeatureGateProps { + feature: string; + children: React.ReactNode; + fallback?: React.ReactNode; + showUpgradePrompt?: boolean; +} + +export const FeatureGate: React.FC = ({ + feature, + children, + fallback, + showUpgradePrompt = true +}) => { + const { hasFeature } = useLicenseFeatures(); + const { licenseStatus } = useLicenseContext(); + + if (hasFeature(feature)) { + return <>{children}; + } + + if (fallback) { + return <>{fallback}; + } + + if (showUpgradePrompt) { + return ( + + ); + } + + return null; +}; + +// Usage throughout WHOOSH: +// +// +// +``` + +#### 3. Feature-Specific Gates +```typescript +// src/components/search/AdvancedSearchFilters.tsx +export const AdvancedSearchFilters: React.FC = () => { + const { canUseAdvancedSearch } = useLicenseFeatures(); + + return ( + +
+ {/* Advanced search filter components */} +
+ +
+ ); +}; +``` + +### Phase 3C: Quota Monitoring & Alerts (PRIORITY 3) +**Goal**: Show usage quotas and proactive upgrade suggestions + +#### 1. Quota Usage Components +```typescript +// src/components/license/QuotaUsageCard.tsx +interface QuotaUsageCardProps { + quotas: LicenseStatus['quotas']; +} + +export const QuotaUsageCard: React.FC = ({ 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 ( +
+

Usage Overview

+ + {Object.entries(quotas).map(([key, quota]) => { + const percentage = getUsagePercentage(quota.used, quota.limit); + + return ( +
+
+ {key.replace('_', ' ').toUpperCase()} + {quota.used.toLocaleString()} / {quota.limit.toLocaleString()} +
+
+
+
+ {percentage >= 80 && ( +
+ ⚠️ Approaching limit - consider upgrading +
+ )} +
+ ); + })} +
+ ); +}; +``` + +#### 2. Upgrade Suggestion Component +```typescript +// src/components/license/UpgradeSuggestionCard.tsx +interface UpgradeSuggestionCardProps { + suggestion: UpgradeSuggestion; +} + +export const UpgradeSuggestionCard: React.FC = ({ 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 ( +
+
+
+

{suggestion.reason}

+

+ Upgrade from {suggestion.current_tier} to {suggestion.suggested_tier} +

+ {suggestion.roi_estimate && ( +

+ Estimated ROI: {suggestion.roi_estimate} +

+ )} +
+ +
+ +
+

Benefits:

+
    + {suggestion.benefits.map((benefit, idx) => ( +
  • + + {benefit} +
  • + ))} +
+
+
+ ); +}; +``` + +### Phase 3D: Self-Service Upgrade Workflows (PRIORITY 4) +**Goal**: Enable customers to upgrade licenses directly from WHOOSH + +#### 1. Upgrade Request Modal +```typescript +// src/components/license/UpgradeRequestModal.tsx +export const UpgradeRequestModal: React.FC = () => { + const [selectedTier, setSelectedTier] = useState(''); + const [justification, setJustification] = useState(''); + + 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 ( + +
+

Request License Upgrade

+ + + +