UNPKG

editia-core

Version:

Core services and utilities for Editia applications - Authentication, Monetization, Video Generation Types, and Database Management

289 lines 11.2 kB
"use strict"; /** * Editia Monetization Hook for React Native * * This hook provides a unified interface for monetization features including: * - Subscription plan management * - Usage tracking * - Feature access control * - Paywall presentation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.useEditiaMonetization = void 0; const react_1 = require("react"); const types_1 = require("../types"); // ============================================================================ // HOOK IMPLEMENTATION // ============================================================================ const useEditiaMonetization = (config) => { const [state, setState] = (0, react_1.useState)({ isLoading: true, currentPlan: 'free', userUsage: null, plans: null, showPaywall: false, isPurchasing: false, hasOfferingError: false, isReady: false, }); const { supabaseClient, environment = 'development', userId } = config; // ============================================================================ // INITIALIZATION // ============================================================================ (0, react_1.useEffect)(() => { initializeHook(); }, [userId]); const initializeHook = async () => { try { setState(prev => ({ ...prev, isLoading: true })); // Load subscription plans await loadSubscriptionPlans(); // Load user usage if userId is provided if (userId) { await loadUserUsage(); } setState(prev => ({ ...prev, isLoading: false, isReady: true })); } catch (error) { console.error('Error initializing Editia monetization:', error); setState(prev => ({ ...prev, isLoading: false, isReady: true, hasOfferingError: true })); } }; // ============================================================================ // DATA LOADING // ============================================================================ const loadSubscriptionPlans = async () => { try { const { data, error } = await supabaseClient .from('subscription_plans') .select('*') .eq('is_active', true); if (error) { console.error('Failed to fetch subscription plans:', error); return; } const plansData = data.reduce((acc, plan) => { acc[plan.id] = plan; return acc; }, {}); setState(prev => ({ ...prev, plans: plansData, })); } catch (error) { console.error('Error loading subscription plans:', error); } }; const loadUserUsage = async () => { if (!userId) return; try { const { data: usage, error } = await supabaseClient .from('user_usage') .select('*') .eq('user_id', userId) .single(); if (error) { if (error.code === 'PGRST116') { // No usage record found, create one await createUsageRecord(userId); return; } console.error('Failed to load user usage:', error); return; } setState(prev => ({ ...prev, userUsage: usage, currentPlan: usage.current_plan_id, })); } catch (error) { console.error('Error loading user usage:', error); } }; const createUsageRecord = async (userId) => { try { const { data: planData, error: planError } = await supabaseClient .from('subscription_plans') .select('*') .eq('id', 'free') .single(); if (planError) { console.error('Failed to fetch plan limits:', planError); return; } const { data, error } = await supabaseClient .from('user_usage') .insert([ { user_id: userId, current_plan_id: 'free', videos_generated: 0, videos_generated_limit: planData.videos_generated_limit, source_videos_used: 0, source_videos_limit: planData.source_videos_limit, voice_clones_used: 0, voice_clones_limit: planData.voice_clones_limit, account_analysis_used: 0, account_analysis_limit: planData.account_analysis_limit, next_reset_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), }, ]) .select() .single(); if (error) { console.error('Failed to create usage record:', error); return; } setState(prev => ({ ...prev, userUsage: data, currentPlan: 'free', })); } catch (error) { console.error('Error creating usage record:', error); } }; // ============================================================================ // FEATURE ACCESS CONTROL // ============================================================================ const checkFeatureAccess = (0, react_1.useCallback)((featureId) => { if (!state.userUsage) return false; // Check if feature exists in FEATURE_FLAGS const featureKey = Object.keys(types_1.FEATURE_FLAGS).find(key => types_1.FEATURE_FLAGS[key] === featureId); if (!featureKey) { console.warn(`Feature flag ${featureId} not found`); return false; } // For now, use hardcoded logic based on feature ID // In a real implementation, this would query the feature_flags table const featureRequirements = { 'account_analysis': 'free', 'chat_ai': 'free', 'script_generation': 'creator', 'video_generation': 'creator', 'source_videos': 'creator', 'advanced_subtitles': 'creator', 'voice_clone': 'pro', 'multiple_voices': 'pro', 'niche_analysis': 'pro', 'content_ideas': 'pro', 'scheduling': 'pro', }; const requiredPlan = featureRequirements[featureId]; if (!requiredPlan) return true; // No restriction return (0, types_1.hasPlanAccess)(state.currentPlan, requiredPlan); }, [state.currentPlan, state.userUsage]); const getFeatureAccessInfo = (0, react_1.useCallback)((featureId) => { const hasAccess = checkFeatureAccess(featureId); // For now, return basic info // In a real implementation, this would query the feature_flags table const featureRequirements = { 'account_analysis': 'free', 'chat_ai': 'free', 'script_generation': 'creator', 'video_generation': 'creator', 'source_videos': 'creator', 'advanced_subtitles': 'creator', 'voice_clone': 'pro', 'multiple_voices': 'pro', 'niche_analysis': 'pro', 'content_ideas': 'pro', 'scheduling': 'pro', }; const requiredPlan = featureRequirements[featureId] || null; const remainingUsage = state.userUsage ? (0, types_1.calculateRemainingUsage)(state.userUsage.videos_generated, state.userUsage.videos_generated_limit) : 0; const totalLimit = state.userUsage?.videos_generated_limit || 0; return { hasAccess, requiredPlan, remainingUsage, totalLimit, }; }, [state.userUsage, checkFeatureAccess]); // ============================================================================ // PAYWALL MANAGEMENT // ============================================================================ const presentPaywall = (0, react_1.useCallback)(async () => { try { setState(prev => ({ ...prev, isPurchasing: true })); // In development mode, simulate Pro access for testing if (environment === 'development') { console.log('🔧 Development mode: simulating Pro access'); // Simulate successful purchase await new Promise(resolve => setTimeout(resolve, 1000)); setState(prev => ({ ...prev, currentPlan: 'pro', isPurchasing: false })); return true; } // Show paywall setState(prev => ({ ...prev, showPaywall: true, isPurchasing: false })); return false; } catch (error) { console.error('Paywall error:', error); setState(prev => ({ ...prev, isPurchasing: false, hasOfferingError: true })); return false; } }, [environment]); const setShowPaywall = (0, react_1.useCallback)((show) => { setState(prev => ({ ...prev, showPaywall: show })); }, []); const refreshUsage = (0, react_1.useCallback)(async () => { await loadUserUsage(); }, [userId]); // ============================================================================ // COMPUTED VALUES // ============================================================================ const videosRemaining = state.userUsage ? (0, types_1.calculateRemainingUsage)(state.userUsage.videos_generated, state.userUsage.videos_generated_limit) : 0; const sourceVideosRemaining = state.userUsage ? (0, types_1.calculateRemainingUsage)(state.userUsage.source_videos_used, state.userUsage.source_videos_limit) : 0; const voiceClonesRemaining = state.userUsage ? (0, types_1.calculateRemainingUsage)(state.userUsage.voice_clones_used, state.userUsage.voice_clones_limit) : 0; const accountAnalysisRemaining = state.userUsage ? (0, types_1.calculateRemainingUsage)(state.userUsage.account_analysis_used, state.userUsage.account_analysis_limit) : 0; return { // State ...state, // Actions presentPaywall, setShowPaywall, refreshUsage, checkFeatureAccess, getFeatureAccessInfo, // Computed values videosRemaining, sourceVideosRemaining, voiceClonesRemaining, accountAnalysisRemaining, }; }; exports.useEditiaMonetization = useEditiaMonetization; //# sourceMappingURL=useEditiaMonetization.js.map