@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
1,171 lines (1,147 loc) • 58.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = require("react");
var _reactNative = require("react-native");
var _OxyContext = require("../context/OxyContext");
var _fonts = require("../styles/fonts");
var _sonner = require("../../lib/sonner");
var _confirmAction = require("../utils/confirmAction");
var _vectorIcons = require("@expo/vector-icons");
var _useI18n = require("../hooks/useI18n");
var _jsxRuntime = require("react/jsx-runtime");
const PremiumSubscriptionScreen = ({
onClose,
theme,
navigate,
goBack
}) => {
const {
user,
oxyServices
} = (0, _OxyContext.useOxy)();
const [loading, setLoading] = (0, _react.useState)(true);
const [subscription, setSubscription] = (0, _react.useState)(null);
const [plans, setPlans] = (0, _react.useState)([]);
const [individualFeatures, setIndividualFeatures] = (0, _react.useState)([]);
const [selectedPlan, setSelectedPlan] = (0, _react.useState)(null);
const [processingPayment, setProcessingPayment] = (0, _react.useState)(false);
const [billingInterval, setBillingInterval] = (0, _react.useState)('month');
const [activeTab, setActiveTab] = (0, _react.useState)('plans');
const [currentAppPackage, setCurrentAppPackage] = (0, _react.useState)('mention'); // Default to mention for demo
const isDarkTheme = theme === 'dark';
const {
t
} = (0, _useI18n.useI18n)();
const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#F5F5F5';
const borderColor = isDarkTheme ? '#444444' : '#E0E0E0';
const primaryColor = '#007AFF';
const successColor = '#30D158';
const warningColor = '#FF9500';
const dangerColor = '#FF3B30';
// Oxy+ subscription plans
const mockPlans = [{
id: 'mention-plus',
name: 'Mention+',
description: 'Enhanced features for better social experience',
price: 4.99,
currency: 'USD',
interval: 'month',
appScope: 'specific',
applicableApps: ['mention'],
// Only available in mention app
features: ['Undo posts option', 'Improved reading mode', 'Organize bookmarks into folders', 'Early access to select features', 'Edit posts capability', 'Enhanced customization options'],
includedFeatures: ['reading-mode-plus', 'custom-themes']
}, {
id: 'oxy-insider',
name: 'Oxy+ Insider',
description: 'Exclusive access to behind-the-scenes content',
price: 9.99,
currency: 'USD',
interval: 'month',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator'],
features: ['Everything in Mention+', 'Behind-the-scenes updates from creators', 'Early access to new features', 'Dedicated support team', 'Exclusive content access', 'Beta feature testing'],
includedFeatures: ['reading-mode-plus', 'custom-themes', 'analytics-basic'],
isPopular: true
}, {
id: 'oxy-connect',
name: 'Oxy+ Connect',
description: 'Advanced networking and community features',
price: 14.99,
currency: 'USD',
interval: 'month',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator'],
features: ['Everything in Oxy+ Insider', 'Create and join private groups', 'Advanced search and filtering tools', 'Customizable profile highlighting', 'Enhanced connection features', 'Priority in community events'],
includedFeatures: ['reading-mode-plus', 'custom-themes', 'analytics-basic', 'group-management']
}, {
id: 'oxy-premium',
name: 'Oxy+ Premium',
description: 'Complete premium experience with all perks',
price: 24.99,
currency: 'USD',
interval: 'month',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator', 'oxy-analytics'],
features: ['Everything in Oxy+ Connect', 'Priority customer support', 'Access to premium content and events', 'Advanced analytics dashboard', 'VIP community status', 'Exclusive premium events'],
includedFeatures: ['reading-mode-plus', 'custom-themes', 'analytics-basic', 'analytics-advanced', 'group-management']
}, {
id: 'oxy-creator',
name: 'Oxy+ Creator',
description: 'Professional tools for content creators',
price: 39.99,
currency: 'USD',
interval: 'month',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator', 'oxy-analytics', 'oxy-studio'],
features: ['Everything in Oxy+ Premium', 'Advanced analytics and insights', 'Promotional tools and resources', 'Content monetization features', 'Creator support program', 'Revenue sharing opportunities'],
includedFeatures: ['reading-mode-plus', 'custom-themes', 'analytics-basic', 'analytics-advanced', 'group-management', 'creator-tools', 'monetization-features']
}];
// Individual feature subscriptions
const mockIndividualFeatures = [{
id: 'analytics-basic',
name: 'Basic Analytics',
description: 'View post performance and engagement metrics',
price: 2.99,
currency: 'USD',
interval: 'month',
category: 'analytics',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace'],
canBePurchasedSeparately: true,
includedInPlans: ['oxy-insider', 'oxy-connect', 'oxy-premium', 'oxy-creator']
}, {
id: 'analytics-advanced',
name: 'Advanced Analytics',
description: 'Detailed insights, trends, and audience demographics',
price: 7.99,
currency: 'USD',
interval: 'month',
category: 'analytics',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator', 'oxy-analytics'],
canBePurchasedSeparately: true,
includedInPlans: ['oxy-premium', 'oxy-creator']
}, {
id: 'custom-themes',
name: 'Custom Themes',
description: 'Personalize your app with custom colors and layouts',
price: 1.99,
currency: 'USD',
interval: 'month',
category: 'customization',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator'],
canBePurchasedSeparately: false,
// Included in all plans
includedInPlans: ['mention-plus', 'oxy-insider', 'oxy-connect', 'oxy-premium', 'oxy-creator']
}, {
id: 'reading-mode-plus',
name: 'Reading Mode Plus',
description: 'Enhanced reading experience with focus modes',
price: 1.99,
currency: 'USD',
interval: 'month',
category: 'content',
appScope: 'specific',
applicableApps: ['mention', 'oxy-social'],
canBePurchasedSeparately: false,
// Included in all plans
includedInPlans: ['mention-plus', 'oxy-insider', 'oxy-connect', 'oxy-premium', 'oxy-creator']
}, {
id: 'group-management',
name: 'Group Management',
description: 'Create and manage private groups and communities',
price: 4.99,
currency: 'USD',
interval: 'month',
category: 'networking',
appScope: 'ecosystem',
applicableApps: ['mention', 'oxy-social', 'oxy-workspace'],
canBePurchasedSeparately: true,
includedInPlans: ['oxy-connect', 'oxy-premium', 'oxy-creator']
}, {
id: 'creator-tools',
name: 'Creator Tools Suite',
description: 'Professional content creation and editing tools',
price: 9.99,
currency: 'USD',
interval: 'month',
category: 'productivity',
appScope: 'specific',
applicableApps: ['oxy-creator', 'oxy-studio'],
canBePurchasedSeparately: true,
includedInPlans: ['oxy-creator']
}, {
id: 'monetization-features',
name: 'Monetization Features',
description: 'Revenue sharing, sponsorship tools, and creator fund access',
price: 12.99,
currency: 'USD',
interval: 'month',
category: 'productivity',
appScope: 'specific',
applicableApps: ['oxy-creator'],
canBePurchasedSeparately: true,
includedInPlans: ['oxy-creator']
}, {
id: 'workspace-collaboration',
name: 'Workspace Collaboration',
description: 'Advanced team features and project management tools',
price: 6.99,
currency: 'USD',
interval: 'month',
category: 'productivity',
appScope: 'specific',
applicableApps: ['oxy-workspace'],
canBePurchasedSeparately: true,
includedInPlans: ['oxy-premium', 'oxy-creator']
}];
(0, _react.useEffect)(() => {
detectCurrentApp();
}, []);
(0, _react.useEffect)(() => {
if (currentAppPackage) {
loadSubscriptionData();
}
}, [currentAppPackage, user?.isPremium]);
const detectCurrentApp = () => {
// In a real implementation, this would detect the actual app package name
// For now, we'll use a mock detection based on available methods
// Real app detection methods you could use:
// 1. Check bundle identifier in React Native:
// import DeviceInfo from 'react-native-device-info';
// const bundleId = DeviceInfo.getBundleId();
// Example: com.oxy.mention -> 'mention'
// 2. Environment variables or build configuration
// const appPackage = __DEV__ ? process.env.APP_PACKAGE : 'mention';
// 3. Check specific app capabilities or modules
// if (typeof MentionModule !== 'undefined') return 'mention';
// if (typeof OxyWorkspaceModule !== 'undefined') return 'oxy-workspace';
// 4. Use build-time configuration with Metro or similar
// const appPackage = require('../config/app.json').packageName;
// For demo purposes, we'll simulate different apps
// You would replace this with actual app detection logic
// IMPORTANT: This ensures subscription restrictions work properly:
// - Mention+ plan can only be subscribed to when app package == 'mention'
// - Other app-specific plans follow the same pattern
// - Ecosystem plans work across all apps
const detectedApp = 'mention'; // This would be dynamic in real implementation
setCurrentAppPackage(detectedApp);
// Log for debugging
console.log('Detected app package:', detectedApp);
console.log('Available plans for this app will be filtered accordingly');
};
const loadSubscriptionData = async () => {
try {
setLoading(true);
// Filter plans available for current app
const availablePlans = mockPlans.filter(plan => plan.applicableApps.includes(currentAppPackage));
setPlans(availablePlans);
// Mock current subscription
let currentSubscription = null;
if (user?.isPremium) {
currentSubscription = {
id: 'sub_12345',
planId: 'oxy-insider',
status: 'active',
currentPeriodStart: new Date().toISOString(),
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
cancelAtPeriodEnd: false
};
setSubscription(currentSubscription);
}
// Filter features available for current app and update based on current subscription
const availableFeatures = mockIndividualFeatures.filter(feature => feature.applicableApps.includes(currentAppPackage));
const updatedFeatures = availableFeatures.map(feature => {
const isIncludedInCurrentPlan = !!(currentSubscription && feature.includedInPlans.includes(currentSubscription.planId));
return {
...feature,
isIncludedInCurrentPlan,
isSubscribed: isIncludedInCurrentPlan ? true : false // Mock some individual subscriptions
};
});
setIndividualFeatures(updatedFeatures);
} catch (error) {
console.error('Failed to load subscription data:', error);
_sonner.toast.error('Failed to load subscription information');
} finally {
setLoading(false);
}
};
const handlePlanSelection = planId => {
setSelectedPlan(planId);
};
const handleSubscribe = async planId => {
try {
// Check if plan is available for current app
const selectedPlan = mockPlans.find(plan => plan.id === planId);
if (!selectedPlan?.applicableApps.includes(currentAppPackage)) {
console.log(`❌ Subscription blocked: Plan "${selectedPlan?.name}" not available for app "${currentAppPackage}"`);
_sonner.toast.error(t('premium.toasts.planUnavailable', {
app: currentAppPackage
}) || `This plan is not available for the current app (${currentAppPackage})`);
return;
}
// Special restriction for Mention+ plan - only available in mention app
if (planId === 'mention-plus' && currentAppPackage !== 'mention') {
console.log(`❌ Subscription blocked: Mention+ plan requires app to be "mention", current app is "${currentAppPackage}"`);
_sonner.toast.error(t('premium.toasts.mentionOnly') || 'Mention+ is only available in the Mention app');
return;
}
console.log(`✅ Subscription allowed: Plan "${selectedPlan.name}" is available for app "${currentAppPackage}"`);
setProcessingPayment(true);
// Mock payment processing
await new Promise(resolve => setTimeout(resolve, 2000));
_sonner.toast.success(t('premium.toasts.activated') || 'Subscription activated successfully!');
// Mock subscription update
setSubscription({
id: 'sub_' + Date.now(),
planId,
status: 'active',
currentPeriodStart: new Date().toISOString(),
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
cancelAtPeriodEnd: false
});
// Reload data to update feature states
loadSubscriptionData();
} catch (error) {
console.error('Payment failed:', error);
_sonner.toast.error(t('premium.toasts.paymentFailed') || 'Payment failed. Please try again.');
} finally {
setProcessingPayment(false);
}
};
const handleCancelSubscription = () => {
(0, _confirmAction.confirmAction)(t('premium.confirms.cancelSub') || 'Are you sure you want to cancel your subscription? You will lose access to premium features at the end of your current billing period.', async () => {
try {
// Mock cancellation
setSubscription(prev => prev ? {
...prev,
cancelAtPeriodEnd: true
} : null);
_sonner.toast.success(t('premium.toasts.willCancel') || 'Subscription will be canceled at the end of the billing period');
} catch (error) {
_sonner.toast.error(t('premium.toasts.cancelFailed') || 'Failed to cancel subscription');
}
});
};
const handleReactivateSubscription = async () => {
try {
setSubscription(prev => prev ? {
...prev,
cancelAtPeriodEnd: false
} : null);
_sonner.toast.success(t('premium.toasts.reactivated') || 'Subscription reactivated successfully');
} catch (error) {
_sonner.toast.error(t('premium.toasts.reactivateFailed') || 'Failed to reactivate subscription');
}
};
const formatPrice = (price, currency, interval) => {
const yearlyPrice = interval === 'year' ? price : price * 12 * 0.8; // 20% discount for yearly
const displayPrice = billingInterval === 'year' ? yearlyPrice : price;
return {
price: displayPrice,
formatted: `$${displayPrice.toFixed(2)}`,
interval: billingInterval === 'year' ? 'year' : 'month'
};
};
const getCurrentPlan = () => {
if (!subscription) return null;
return plans.find(plan => plan.id === subscription.planId);
};
const handleFeatureSubscribe = async featureId => {
try {
// Check if feature is available for current app
const selectedFeature = mockIndividualFeatures.find(feature => feature.id === featureId);
if (!selectedFeature?.applicableApps.includes(currentAppPackage)) {
_sonner.toast.error(`This feature is not available for the current app (${currentAppPackage})`);
return;
}
// Special restrictions for app-specific features
if (selectedFeature.appScope === 'specific') {
// For features that are only available in specific apps, enforce strict matching
const hasExactMatch = selectedFeature.applicableApps.length === 1 && selectedFeature.applicableApps[0] === currentAppPackage;
if (!hasExactMatch && selectedFeature.applicableApps.length === 1) {
const requiredApp = selectedFeature.applicableApps[0];
_sonner.toast.error(`${selectedFeature.name} is only available in the ${requiredApp} app`);
return;
}
}
setProcessingPayment(true);
// Mock feature subscription
await new Promise(resolve => setTimeout(resolve, 1500));
setIndividualFeatures(prev => prev.map(feature => feature.id === featureId ? {
...feature,
isSubscribed: true
} : feature));
const feature = individualFeatures.find(f => f.id === featureId);
_sonner.toast.success(t('premium.toasts.featureSubscribed', {
name: feature?.name ?? ''
}) ?? `Subscribed to ${feature?.name} successfully!`);
} catch (error) {
console.error('Feature subscription failed:', error);
_sonner.toast.error(t('premium.toasts.featureSubscribeFailed') || 'Feature subscription failed. Please try again.');
} finally {
setProcessingPayment(false);
}
};
const handleFeatureUnsubscribe = async featureId => {
const feature = individualFeatures.find(f => f.id === featureId);
(0, _confirmAction.confirmAction)(t('premium.confirms.unsubscribeFeature', {
name: feature?.name ?? ''
}) ?? `Are you sure you want to unsubscribe from ${feature?.name}?`, async () => {
try {
setIndividualFeatures(prev => prev.map(f => f.id === featureId ? {
...f,
isSubscribed: false
} : f));
_sonner.toast.success(t('premium.toasts.featureUnsubscribed', {
name: feature?.name ?? ''
}) ?? `Unsubscribed from ${feature?.name}`);
} catch (error) {
_sonner.toast.error(t('premium.toasts.featureUnsubscribeFailed') || 'Failed to unsubscribe from feature');
}
});
};
const renderHeader = () => {
const getAppDisplayName = packageName => {
const appNames = {
'mention': 'Mention',
'oxy-social': 'Oxy Social',
'oxy-workspace': 'Oxy Workspace',
'oxy-creator': 'Oxy Creator',
'oxy-analytics': 'Oxy Analytics',
'oxy-studio': 'Oxy Studio'
};
return appNames[packageName] || packageName;
};
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.header, {
borderBottomColor: borderColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: styles.backButton,
onPress: goBack,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "arrow-back",
size: 24,
color: textColor
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.headerTitleContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.headerTitle, {
color: textColor
}],
children: t('premium.title') || 'Oxy+ Subscriptions'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.currentAppText, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.forApp', {
app: getAppDisplayName(currentAppPackage)
}) || `for ${getAppDisplayName(currentAppPackage)}`
})]
}), onClose && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: styles.closeButton,
onPress: onClose,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "close",
size: 24,
color: textColor
})
})]
});
};
const renderCurrentSubscription = () => {
if (!subscription) return null;
const currentPlan = getCurrentPlan();
if (!currentPlan) return null;
const statusColor = subscription.status === 'active' ? successColor : subscription.status === 'trialing' ? warningColor : dangerColor;
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.section,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionTitle, {
color: textColor
}],
children: t('premium.current.title') || 'Current Subscription'
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.currentSubscriptionCard, {
backgroundColor: secondaryBackgroundColor,
borderColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.subscriptionHeader,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planName, {
color: textColor
}],
children: currentPlan.name
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
style: [styles.planPrice, {
color: primaryColor
}],
children: ["$", currentPlan.price, "/month"]
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.statusBadge, {
backgroundColor: statusColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.statusText,
children: subscription.status.toUpperCase()
})
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.subscriptionDetail, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.current.renewsOn', {
date: new Date(subscription.currentPeriodEnd).toLocaleDateString()
}) || `Renews on ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`
}), subscription.cancelAtPeriodEnd && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.cancelNotice,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "warning",
size: 16,
color: warningColor
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.cancelText, {
color: warningColor
}],
children: t('premium.current.willCancelOn', {
date: new Date(subscription.currentPeriodEnd).toLocaleDateString()
}) || `Subscription will cancel on ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.subscriptionActions,
children: [subscription.cancelAtPeriodEnd ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.actionButton, {
backgroundColor: successColor
}],
onPress: handleReactivateSubscription,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.actionButtonText,
children: t('premium.actions.reactivate') || 'Reactivate'
})
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.actionButton, {
backgroundColor: dangerColor
}],
onPress: handleCancelSubscription,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.actionButtonText,
children: t('premium.actions.cancelSubBtn') || 'Cancel Subscription'
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.actionButton, styles.secondaryButton, {
borderColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.actionButtonText, {
color: textColor
}],
children: t('premium.actions.manageBilling') || 'Manage Billing'
})
})]
})]
})]
});
};
const renderBillingToggle = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.section,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.billingToggle,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.billingOption, billingInterval === 'month' && {
backgroundColor: primaryColor
}],
onPress: () => setBillingInterval('month'),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.billingOptionText, {
color: billingInterval === 'month' ? '#FFFFFF' : textColor
}],
children: t('premium.billing.monthly') || 'Monthly'
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.billingOption, billingInterval === 'year' && {
backgroundColor: primaryColor
}],
onPress: () => setBillingInterval('year'),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.billingOptionText, {
color: billingInterval === 'year' ? '#FFFFFF' : textColor
}],
children: t('premium.billing.yearly') || 'Yearly'
})
})]
}), billingInterval === 'year' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.savingsText, {
color: successColor
}],
children: t('premium.billing.saveYearly') || '💰 Save 20% with yearly billing'
})]
});
const renderPlanCard = plan => {
const pricing = formatPrice(plan.price, plan.currency, plan.interval);
const isSelected = selectedPlan === plan.id;
const isCurrentPlan = subscription?.planId === plan.id;
const isAppSpecific = plan.appScope === 'specific' && plan.applicableApps.length === 1;
const isAvailableForCurrentApp = plan.applicableApps.includes(currentAppPackage);
const getAppScopeText = () => {
if (plan.appScope === 'ecosystem') {
return t('premium.plan.scope.allApps') || 'Works across all Oxy apps';
} else if (isAppSpecific) {
const appName = plan.applicableApps[0];
return t('premium.plan.scope.exclusive', {
app: appName
}) || `Exclusive to ${appName} app`;
} else {
return t('premium.plan.scope.availableIn', {
apps: plan.applicableApps.join(', ')
}) || `Available in: ${plan.applicableApps.join(', ')}`;
}
};
const getAvailabilityStatus = () => {
if (isAppSpecific && !isAvailableForCurrentApp) {
const requiredApp = plan.applicableApps[0];
return {
available: false,
reason: t('premium.plan.scope.exclusive', {
app: requiredApp
}) || `Only available in ${requiredApp} app`
};
}
return {
available: true,
reason: null
};
};
const availability = getAvailabilityStatus();
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.planCard, {
backgroundColor: secondaryBackgroundColor,
borderColor
}, isSelected && {
borderColor: primaryColor,
borderWidth: 2
}, plan.isPopular && styles.popularPlan, !availability.available && {
opacity: 0.6
}],
children: [plan.isPopular && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.popularBadge, {
backgroundColor: primaryColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.popularText,
children: "MOST POPULAR"
})
}), isAppSpecific && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.appSpecificBadge, {
backgroundColor: isAvailableForCurrentApp ? successColor : warningColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.appSpecificText,
children: isAvailableForCurrentApp ? t('premium.plan.badge.appExclusive') || 'App Exclusive' : t('premium.plan.badge.notAvailable') || 'Not Available'
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.planHeader,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planName, {
color: textColor
}],
children: plan.name
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planDescription, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: plan.description
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planAppScope, {
color: isDarkTheme ? '#888888' : '#999999'
}],
children: getAppScopeText()
}), !availability.available && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planRestrictionText, {
color: dangerColor
}],
children: availability.reason
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.planPricing,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planPrice, {
color: textColor
}],
children: pricing.formatted
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.planInterval, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.plan.perInterval', {
interval: pricing.interval
}) || `per ${pricing.interval}`
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.planFeatures,
children: plan.features.map((feature, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.featureItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "checkmark",
size: 16,
color: successColor
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.featureText, {
color: textColor
}],
children: feature
})]
}, index))
}), isCurrentPlan ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.currentPlanButton, {
backgroundColor: successColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.currentPlanText,
children: t('premium.plan.current') || 'Current Plan'
})
}) : !availability.available ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.unavailablePlanButton, {
backgroundColor: isDarkTheme ? '#444444' : '#E0E0E0'
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.unavailablePlanText, {
color: isDarkTheme ? '#888888' : '#999999'
}],
children: t('premium.plan.notAvailableInApp') || 'Not Available in Current App'
})
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.selectPlanButton, {
backgroundColor: plan.isPopular ? primaryColor : borderColor
}],
onPress: () => handleSubscribe(plan.id),
disabled: processingPayment,
children: processingPayment ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
color: "#FFFFFF",
size: "small"
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.selectPlanText, {
color: plan.isPopular ? '#FFFFFF' : textColor
}],
children: t('premium.actions.subscribeTo', {
name: plan.name
}) || `Subscribe to ${plan.name}`
})
})]
}, plan.id);
};
const renderTabNavigation = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.section,
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.tabContainer, {
borderBottomColor: borderColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.tab, activeTab === 'plans' && {
borderBottomColor: primaryColor,
borderBottomWidth: 2
}],
onPress: () => setActiveTab('plans'),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.tabText, {
color: activeTab === 'plans' ? primaryColor : textColor
}],
children: t('premium.tabs.plans') || 'Full Plans'
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.tab, activeTab === 'features' && {
borderBottomColor: primaryColor,
borderBottomWidth: 2
}],
onPress: () => setActiveTab('features'),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.tabText, {
color: activeTab === 'features' ? primaryColor : textColor
}],
children: t('premium.tabs.features') || 'Individual Features'
})
})]
})
});
const renderFeatureCard = feature => {
const pricing = formatPrice(feature.price, feature.currency, feature.interval);
const isSubscribed = feature.isSubscribed;
const isIncludedInCurrentPlan = feature.isIncludedInCurrentPlan;
const canPurchase = feature.canBePurchasedSeparately && !isIncludedInCurrentPlan;
const getCategoryColor = category => {
switch (category) {
case 'analytics':
return '#FF9500';
case 'customization':
return '#5856D6';
case 'content':
return '#30D158';
case 'networking':
return '#007AFF';
case 'productivity':
return '#FF3B30';
default:
return primaryColor;
}
};
const getCategoryIcon = category => {
switch (category) {
case 'analytics':
return 'analytics';
case 'customization':
return 'color-palette';
case 'content':
return 'document-text';
case 'networking':
return 'people';
case 'productivity':
return 'briefcase';
default:
return 'star';
}
};
const getAppScopeText = () => {
if (feature.appScope === 'ecosystem') {
return t('premium.feature.scope.allApps');
} else {
return t('premium.feature.scope.availableIn', {
apps: feature.applicableApps.join(', ')
}) || `Available in: ${feature.applicableApps.join(', ')}`;
}
};
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.featureCard, {
backgroundColor: secondaryBackgroundColor,
borderColor
}, isSubscribed && {
borderColor: successColor,
borderWidth: 2
}, isIncludedInCurrentPlan && {
borderColor: primaryColor,
borderWidth: 2
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.featureHeader,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.featureIconContainer,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: getCategoryIcon(feature.category),
size: 24,
color: getCategoryColor(feature.category)
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.featureInfo,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.featureNameRow,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.featureName, {
color: textColor
}],
children: feature.name
}), isIncludedInCurrentPlan && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.includedBadge, {
backgroundColor: primaryColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.includedBadgeText,
children: "Included"
})
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.featureDescription, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: feature.description
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.appScopeText, {
color: isDarkTheme ? '#888888' : '#999999'
}],
children: getAppScopeText()
})]
})]
}), !isIncludedInCurrentPlan && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.featurePricing,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.featurePrice, {
color: textColor
}],
children: pricing.formatted
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.featureInterval, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.plan.perInterval', {
interval: pricing.interval
}) || `per ${pricing.interval}`
})]
}), isIncludedInCurrentPlan ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.includedInPlanButton, {
backgroundColor: primaryColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "checkmark-circle",
size: 16,
color: "#FFFFFF"
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.includedInPlanText,
children: t('premium.feature.includedInPlan') || 'Included in your plan'
})]
}) : isSubscribed ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.featureActions,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.subscribedButton, {
backgroundColor: successColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "checkmark",
size: 16,
color: "#FFFFFF"
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.subscribedText,
children: t('premium.feature.subscribed') || 'Subscribed'
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.unsubscribeButton, {
borderColor: dangerColor
}],
onPress: () => handleFeatureUnsubscribe(feature.id),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.unsubscribeText, {
color: dangerColor
}],
children: t('premium.actions.unsubscribe') || 'Unsubscribe'
})
})]
}) : canPurchase ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.subscribeFeatureButton, {
backgroundColor: primaryColor
}],
onPress: () => handleFeatureSubscribe(feature.id),
disabled: processingPayment,
children: processingPayment ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
color: "#FFFFFF",
size: "small"
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.subscribeFeatureText,
children: t('premium.actions.subscribe') || 'Subscribe'
})
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.unavailableButton, {
backgroundColor: isDarkTheme ? '#444444' : '#E0E0E0'
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.unavailableText, {
color: isDarkTheme ? '#888888' : '#999999'
}],
children: "Only available in subscription plans"
})
})]
}, feature.id);
};
const renderIndividualFeatures = () => {
const categories = ['analytics', 'customization', 'content', 'networking', 'productivity'];
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.section,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionTitle, {
color: textColor
}],
children: "Individual Features"
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionSubtitle, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: "Subscribe to specific features you need. Some features are included in subscription plans."
}), categories.map(category => {
const categoryFeatures = individualFeatures.filter(f => f.category === category);
if (categoryFeatures.length === 0) return null;
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.categorySection,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.categoryTitle, {
color: textColor
}],
children: category.charAt(0).toUpperCase() + category.slice(1)
}), categoryFeatures.map(renderFeatureCard)]
}, category);
})]
});
};
// Add this for testing different app contexts (remove in production)
const [showAppSwitcher, setShowAppSwitcher] = (0, _react.useState)(__DEV__); // Only show in development
const renderAppSwitcher = () => {
if (!showAppSwitcher) return null;
const testApps = ['mention', 'oxy-social', 'oxy-workspace', 'oxy-creator'];
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.appSwitcher, {
backgroundColor: isDarkTheme ? '#333333' : '#F0F0F0',
borderColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.appSwitcherTitle, {
color: textColor
}],
children: "\uD83E\uDDEA Test App Context (Dev Only)"
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
horizontal: true,
showsHorizontalScrollIndicator: false,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.appSwitcherButtons,
children: testApps.map(app => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: [styles.appSwitcherButton, {
backgroundColor: currentAppPackage === app ? primaryColor : 'transparent',
borderColor: primaryColor
}],
onPress: () => {
setCurrentAppPackage(app);
},
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.appSwitcherButtonText, {
color: currentAppPackage === app ? '#FFFFFF' : textColor
}],
children: app
})
}, app))
})
})]
});
};
if (loading) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.container, {
backgroundColor,
justifyContent: 'center'
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
size: "large",
color: primaryColor
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.loadingText, {
color: textColor
}],
children: t('premium.loading') || 'Loading subscription plans...'
})]
});
}
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.container, {
backgroundColor
}],
children: [renderHeader(), renderAppSwitcher(), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
style: styles.content,
showsVerticalScrollIndicator: false,
children: [subscription && renderCurrentSubscription(), !subscription && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.section,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionTitle, {
color: textColor
}],
children: t('premium.choosePlan') || 'Choose Your Plan'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionSubtitle, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.choosePlanSubtitle') || 'Unlock premium features and take your experience to the next level'
})]
}), !subscription && renderTabNavigation(), !subscription && activeTab === 'plans' && renderBillingToggle(), activeTab === 'plans' ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.section,
children: [!subscription && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionTitle, {
color: textColor
}],
children: t('premium.availablePlans') || 'Available Plans'
}), plans.map(renderPlanCard)]
}) : renderIndividualFeatures(), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.section,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionTitle, {
color: textColor
}],
children: t('premium.why') || 'Why Go Premium?'
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.benefitsCard, {
backgroundColor: secondaryBackgroundColor,
borderColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.benefitItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "flash",
size: 24,
color: primaryColor
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.benefitContent,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.benefitTitle, {
color: textColor
}],
children: t('premium.benefits.performance.title') || 'Enhanced Performance'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.benefitDescription, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.benefits.performance.desc') || 'Faster processing and priority access to our servers'
})]
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.benefitItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "shield-checkmark",
size: 24,
color: successColor
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.benefitContent,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.benefitTitle, {
color: textColor
}],
children: t('premium.benefits.security.title') || 'Advanced Security'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.benefitDescription, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.benefits.security.desc') || 'Enhanced encryption and security features'
})]
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.benefitItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "headset",
size: 24,
color: warningColor
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.benefitContent,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.benefitTitle, {
color: textColor
}],
children: t('premium.benefits.support.title') || 'Priority Support'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.benefitDescription, {
color: isDarkTheme ? '#BBBBBB' : '#666666'
}],
children: t('premium.benefits.support.desc') || 'Get help faster with our premium support team'
})]
})]
})]
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.bottomSpacing
})]
})]
});
};
const styles = _reactNative.StyleSheet.create({
container: {