import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Alert, ActivityIndicator, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { router, useLocalSearchParams } from 'expo-router'; import { usePaymentSheet } from '@stripe/stripe-react-native'; import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows } from '@/constants/theme'; import { useBeneficiary } from '@/contexts/BeneficiaryContext'; import { useAuth } from '@/contexts/AuthContext'; import { api } from '@/services/api'; import type { Beneficiary, BeneficiarySubscription } from '@/types'; const STRIPE_API_URL = 'https://wellnuo.smartlaunchhub.com/api/stripe'; const SUBSCRIPTION_PRICE = 49; // $49/month interface PlanFeatureProps { text: string; included: boolean; } function PlanFeature({ text, included }: PlanFeatureProps) { return ( {text} ); } export default function SubscriptionScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const [isProcessing, setIsProcessing] = useState(false); const [beneficiary, setBeneficiary] = useState(null); const [isLoading, setIsLoading] = useState(true); const { initPaymentSheet, presentPaymentSheet } = usePaymentSheet(); const { user } = useAuth(); const { updateLocalBeneficiary } = useBeneficiary(); useEffect(() => { loadBeneficiary(); }, [id]); const loadBeneficiary = async () => { if (!id) return; try { const response = await api.getWellNuoBeneficiary(parseInt(id, 10)); if (response.ok && response.data) { setBeneficiary(response.data); } else { console.error('Failed to load beneficiary:', response.error); } } catch (error) { console.error('Failed to load beneficiary:', error); } finally { setIsLoading(false); } }; const subscription = beneficiary?.subscription; const isActive = subscription?.status === 'active' && subscription?.endDate && new Date(subscription.endDate) > new Date(); const daysRemaining = subscription?.endDate ? Math.max(0, Math.ceil((new Date(subscription.endDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24))) : 0; const handleSubscribe = async () => { if (!beneficiary) { Alert.alert('Error', 'Beneficiary data not loaded.'); return; } setIsProcessing(true); try { // 1. Create Payment Sheet on our server const response = await fetch(`${STRIPE_API_URL}/create-payment-sheet`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: user?.email || 'guest@wellnuo.com', amount: SUBSCRIPTION_PRICE * 100, // Convert to cents ($49.00) metadata: { type: 'subscription', planType: 'monthly', userId: user?.user_id || 'guest', beneficiaryId: beneficiary.id, beneficiaryName: beneficiary.name, }, }), }); const data = await response.json(); if (!data.paymentIntent) { throw new Error(data.error || 'Failed to create payment sheet'); } // 2. Initialize the Payment Sheet const { error: initError } = await initPaymentSheet({ merchantDisplayName: 'WellNuo', paymentIntentClientSecret: data.paymentIntent, customerId: data.customer, customerEphemeralKeySecret: data.ephemeralKey, defaultBillingDetails: { email: user?.email || '', }, returnURL: 'wellnuo://stripe-redirect', applePay: { merchantCountryCode: 'US', }, googlePay: { merchantCountryCode: 'US', testEnv: true, }, }); if (initError) { throw new Error(initError.message); } // 3. Present the Payment Sheet const { error: presentError } = await presentPaymentSheet(); if (presentError) { if (presentError.code === 'Canceled') { setIsProcessing(false); return; } throw new Error(presentError.message); } // 4. Payment successful! Save subscription to beneficiary const now = new Date(); const endDate = new Date(now); endDate.setMonth(endDate.getMonth() + 1); // 1 month subscription const newSubscription: BeneficiarySubscription = { status: 'active', startDate: now.toISOString(), endDate: endDate.toISOString(), planType: 'monthly', price: SUBSCRIPTION_PRICE, }; await updateLocalBeneficiary(beneficiary.id, { subscription: newSubscription, }); // Reload beneficiary to get updated subscription await loadBeneficiary(); Alert.alert( 'Subscription Activated!', `Subscription for ${beneficiary.name} is now active until ${formatDate(endDate)}.`, [{ text: 'Great!' }] ); } catch (error) { console.error('Payment error:', error); Alert.alert( 'Payment Failed', error instanceof Error ? error.message : 'Something went wrong. Please try again.' ); } finally { setIsProcessing(false); } }; const handleRestorePurchases = () => { Alert.alert( 'Restoring Purchases', 'Looking for previous purchases...', [{ text: 'OK' }] ); setTimeout(() => { Alert.alert('No Purchases Found', 'We couldn\'t find any previous purchases associated with your account.'); }, 1500); }; const formatDate = (date: Date) => { return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); }; if (isLoading) { return ( router.back()}> Subscription ); } if (!beneficiary) { return ( router.back()}> Subscription Beneficiary Not Found Unable to load beneficiary information. ); } return ( {/* Header */} router.back()}> Subscription {/* Beneficiary Info */} {beneficiary.name.charAt(0).toUpperCase()} Subscription for {beneficiary.name} {/* Current Status */} {isActive ? ( <> ACTIVE Subscription is active Valid until {subscription?.endDate ? formatDate(new Date(subscription.endDate)) : 'N/A'} {daysRemaining} days remaining ) : ( <> {subscription?.status === 'expired' ? 'EXPIRED' : 'NO SUBSCRIPTION'} {subscription?.status === 'expired' ? 'Subscription has expired' : `Subscribe for ${beneficiary.name}`} Get full access to monitoring features )} {/* Subscription Card */} WellNuo ${SUBSCRIPTION_PRICE} /month {!isActive && ( {isProcessing ? ( ) : ( <> Subscribe — ${SUBSCRIPTION_PRICE}/month )} )} {/* Secure Payment Badge */} Secure payment powered by Stripe {/* Restore Purchases */} Restore Purchases {/* Terms */} Payment will be charged to your account at the confirmation of purchase. Subscription can be cancelled at any time from your account settings. ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: AppColors.surface, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: Spacing.md, paddingVertical: Spacing.sm, borderBottomWidth: 1, borderBottomColor: AppColors.border, }, backButton: { padding: Spacing.xs, }, headerTitle: { fontSize: FontSizes.lg, fontWeight: FontWeights.semibold, color: AppColors.textPrimary, }, placeholder: { width: 32, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, // No beneficiary state noBeneficiaryContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: Spacing.xl, }, noBeneficiaryIcon: { width: 96, height: 96, borderRadius: 48, backgroundColor: AppColors.surfaceSecondary, justifyContent: 'center', alignItems: 'center', marginBottom: Spacing.lg, }, noBeneficiaryTitle: { fontSize: FontSizes.xl, fontWeight: FontWeights.bold, color: AppColors.textPrimary, marginBottom: Spacing.sm, }, noBeneficiaryText: { fontSize: FontSizes.base, color: AppColors.textSecondary, textAlign: 'center', lineHeight: 24, }, // Beneficiary banner beneficiaryBanner: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.primaryLighter, marginHorizontal: Spacing.lg, marginTop: Spacing.md, padding: Spacing.md, borderRadius: BorderRadius.lg, }, beneficiaryAvatar: { width: 48, height: 48, borderRadius: 24, backgroundColor: AppColors.primary, justifyContent: 'center', alignItems: 'center', }, beneficiaryAvatarText: { fontSize: FontSizes.xl, fontWeight: FontWeights.bold, color: AppColors.white, }, beneficiaryInfo: { marginLeft: Spacing.md, }, beneficiaryLabel: { fontSize: FontSizes.sm, color: AppColors.textSecondary, }, beneficiaryName: { fontSize: FontSizes.lg, fontWeight: FontWeights.bold, color: AppColors.textPrimary, }, // Status banner statusBanner: { backgroundColor: AppColors.background, padding: Spacing.xl, alignItems: 'center', }, activeBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#D1FAE5', paddingHorizontal: Spacing.md, paddingVertical: Spacing.xs, borderRadius: BorderRadius.full, gap: Spacing.xs, }, activeBadgeText: { fontSize: FontSizes.sm, fontWeight: FontWeights.bold, color: AppColors.success, }, inactiveBadge: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#FEE2E2', paddingHorizontal: Spacing.md, paddingVertical: Spacing.xs, borderRadius: BorderRadius.full, gap: Spacing.xs, }, inactiveBadgeText: { fontSize: FontSizes.sm, fontWeight: FontWeights.bold, color: AppColors.error, }, statusTitle: { fontSize: FontSizes.xl, fontWeight: FontWeights.bold, color: AppColors.textPrimary, marginTop: Spacing.md, textAlign: 'center', }, statusDescription: { fontSize: FontSizes.base, color: AppColors.textSecondary, marginTop: Spacing.xs, textAlign: 'center', }, daysRemaining: { marginTop: Spacing.lg, alignItems: 'center', }, daysRemainingNumber: { fontSize: 48, fontWeight: FontWeights.bold, color: AppColors.primary, }, daysRemainingLabel: { fontSize: FontSizes.sm, color: AppColors.textSecondary, }, section: { marginTop: Spacing.md, }, subscriptionCard: { backgroundColor: AppColors.background, marginHorizontal: Spacing.lg, borderRadius: BorderRadius.lg, overflow: 'hidden', borderWidth: 2, borderColor: AppColors.primary, }, cardHeader: { backgroundColor: `${AppColors.primary}10`, padding: Spacing.lg, alignItems: 'center', }, proBadge: { flexDirection: 'row', alignItems: 'center', gap: Spacing.xs, }, proBadgeText: { fontSize: FontSizes.lg, fontWeight: FontWeights.bold, color: AppColors.primary, }, priceContainer: { flexDirection: 'row', alignItems: 'baseline', marginTop: Spacing.md, }, priceAmount: { fontSize: 48, fontWeight: FontWeights.bold, color: AppColors.textPrimary, }, pricePeriod: { fontSize: FontSizes.lg, color: AppColors.textSecondary, marginLeft: Spacing.xs, }, featuresContainer: { padding: Spacing.lg, }, featureRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: Spacing.xs, }, featureText: { fontSize: FontSizes.sm, color: AppColors.textPrimary, marginLeft: Spacing.sm, }, featureTextDisabled: { color: AppColors.textMuted, }, subscribeButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: Spacing.sm, backgroundColor: AppColors.primary, marginHorizontal: Spacing.lg, marginBottom: Spacing.lg, paddingVertical: Spacing.lg, borderRadius: BorderRadius.lg, }, buttonDisabled: { opacity: 0.7, }, subscribeButtonText: { fontSize: FontSizes.base, fontWeight: FontWeights.semibold, color: AppColors.white, }, securityBadge: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: Spacing.xs, marginTop: Spacing.lg, paddingVertical: Spacing.md, }, securityText: { fontSize: FontSizes.sm, color: AppColors.success, }, restoreButton: { alignItems: 'center', paddingVertical: Spacing.md, }, restoreButtonText: { fontSize: FontSizes.sm, color: AppColors.primary, }, termsText: { fontSize: FontSizes.xs, color: AppColors.textMuted, textAlign: 'center', paddingHorizontal: Spacing.xl, paddingBottom: Spacing.xl, lineHeight: 16, }, });