editia-core
Version:
Core services and utilities for Editia applications - Authentication, Monetization, Video Generation Types, and Database Management
289 lines • 11.2 kB
JavaScript
;
/**
* 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