/** * TabataFit Paywall Screen * Premium subscription purchase flow */ import React, { useMemo } from 'react' import { View, StyleSheet, ScrollView, Pressable, } from 'react-native' import { useRouter } from 'expo-router' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { LinearGradient } from 'expo-linear-gradient' import { Icon, type IconName } from '@/src/shared/components/Icon' import { useTranslation } from 'react-i18next' import { useHaptics, usePurchases } from '@/src/shared/hooks' import { StyledText } from '@/src/shared/components/StyledText' import { useThemeColors, BRAND, GRADIENTS } from '@/src/shared/theme' import type { ThemeColors } from '@/src/shared/theme/types' import { SPACING } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' // ═══════════════════════════════════════════════════════════════════════════ // FEATURES LIST // ═══════════════════════════════════════════════════════════════════════════ const PREMIUM_FEATURES: { icon: IconName; key: string }[] = [ { icon: 'music.note.list', key: 'music' }, { icon: 'infinity', key: 'workouts' }, { icon: 'chart.bar.fill', key: 'stats' }, { icon: 'flame.fill', key: 'calories' }, { icon: 'bell.fill', key: 'reminders' }, { icon: 'xmark.circle.fill', key: 'ads' }, ] // ═══════════════════════════════════════════════════════════════════════════ // COMPONENTS // ═══════════════════════════════════════════════════════════════════════════ interface PlanCardStyles { planCard: object planCardPressed: object savingsBadge: object savingsText: object planInfo: object planTitle: object planPeriod: object planPrice: object checkmark: object } function PlanCard({ title, price, period, savings, isSelected, onPress, colors, styles, }: { title: string price: string period: string savings?: string isSelected: boolean onPress: () => void colors: ThemeColors styles: PlanCardStyles }) { const haptics = useHaptics() const handlePress = () => { haptics.selection() onPress() } return ( [ styles.planCard, isSelected && { borderColor: BRAND.PRIMARY }, pressed && styles.planCardPressed, { backgroundColor: colors.bg.surface, borderColor: isSelected ? BRAND.PRIMARY : colors.border.glass, }, ]} > {savings && ( {savings} )} {title} {period} {price} {isSelected && ( )} ) } // ═══════════════════════════════════════════════════════════════════════════ // MAIN SCREEN // ═══════════════════════════════════════════════════════════════════════════ export default function PaywallScreen() { const { t } = useTranslation('screens') const router = useRouter() const insets = useSafeAreaInsets() const haptics = useHaptics() const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) // Extract plan card styles for the child component const planCardStyles = useMemo( () => ({ planCard: styles.planCard, planCardPressed: styles.planCardPressed, savingsBadge: styles.savingsBadge, savingsText: styles.savingsText, planInfo: styles.planInfo, planTitle: styles.planTitle, planPeriod: styles.planPeriod, planPrice: styles.planPrice, checkmark: styles.checkmark, }), [styles], ) const { monthlyPackage, annualPackage, purchasePackage, restorePurchases, isLoading, } = usePurchases() const [selectedPlan, setSelectedPlan] = React.useState<'monthly' | 'annual'>('annual') // Get prices from RevenueCat packages const monthlyPrice = monthlyPackage?.product.priceString ?? '$4.99' const annualPrice = annualPackage?.product.priceString ?? '$29.99' const annualMonthlyEquivalent = annualPackage ? (annualPackage.product.price / 12).toFixed(2) : '2.49' const handlePurchase = async () => { haptics.buttonTap() const pkg = selectedPlan === 'monthly' ? monthlyPackage : annualPackage if (!pkg) { console.log('[Paywall] No package available for purchase') return } const result = await purchasePackage(pkg) if (result.success) { haptics.workoutComplete() router.back() } else if (!result.cancelled) { console.log('[Paywall] Purchase error:', result.error) } } const handleRestore = async () => { haptics.selection() const restored = await restorePurchases() if (restored) { haptics.workoutComplete() router.back() } } const handleClose = () => { haptics.selection() router.back() } return ( {/* Close Button */} {/* Header */} TabataFit+ {t('paywall.subtitle')} {/* Features Grid */} {PREMIUM_FEATURES.map((feature) => ( {t(`paywall.features.${feature.key}`)} ))} {/* Plan Selection */} setSelectedPlan('annual')} colors={colors} styles={planCardStyles} /> setSelectedPlan('monthly')} colors={colors} styles={planCardStyles} /> {/* Price Note */} {selectedPlan === 'annual' && ( {t('paywall.equivalent', { price: annualMonthlyEquivalent })} )} {/* CTA Button */} {isLoading ? t('paywall.processing') : t('paywall.trialCta')} {/* Restore & Terms */} {t('paywall.restore')} {t('paywall.terms')} ) } // ═══════════════════════════════════════════════════════════════════════════ // STYLES // ═══════════════════════════════════════════════════════════════════════════ function createStyles(colors: ThemeColors) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg.base, }, closeButton: { position: 'absolute', top: SPACING[4], right: SPACING[4], width: 44, height: 44, alignItems: 'center', justifyContent: 'center', zIndex: 10, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: SPACING[5], paddingTop: SPACING[8], }, header: { alignItems: 'center', }, title: { fontSize: 32, fontWeight: '700', color: colors.text.primary, textAlign: 'center', }, subtitle: { fontSize: 16, color: colors.text.secondary, textAlign: 'center', marginTop: SPACING[2], }, featuresGrid: { flexDirection: 'row', flexWrap: 'wrap', marginTop: SPACING[6], marginHorizontal: -SPACING[2], }, featureItem: { width: '33%', alignItems: 'center', paddingVertical: SPACING[3], }, featureIcon: { width: 48, height: 48, borderRadius: 24, alignItems: 'center', justifyContent: 'center', marginBottom: SPACING[2], }, featureText: { fontSize: 13, textAlign: 'center', }, plansContainer: { marginTop: SPACING[6], gap: SPACING[3], }, planCard: { flexDirection: 'row', alignItems: 'center', borderRadius: RADIUS.LG, padding: SPACING[4], borderWidth: 2, }, planCardPressed: { opacity: 0.8, }, savingsBadge: { position: 'absolute', top: -8, right: SPACING[3], backgroundColor: BRAND.PRIMARY, paddingHorizontal: SPACING[2], paddingVertical: 2, borderRadius: RADIUS.SM, }, savingsText: { fontSize: 10, fontWeight: '700', color: colors.text.primary, }, planInfo: { flex: 1, }, planTitle: { fontSize: 16, fontWeight: '600', }, planPeriod: { fontSize: 13, marginTop: 2, }, planPrice: { fontSize: 20, fontWeight: '700', }, checkmark: { marginLeft: SPACING[2], }, priceNote: { fontSize: 13, textAlign: 'center', marginTop: SPACING[3], }, ctaButton: { borderRadius: RADIUS.LG, overflow: 'hidden', marginTop: SPACING[6], }, ctaButtonDisabled: { opacity: 0.6, }, ctaGradient: { paddingVertical: SPACING[4], alignItems: 'center', }, ctaText: { fontSize: 17, fontWeight: '600', }, footer: { marginTop: SPACING[5], alignItems: 'center', gap: SPACING[4], }, restoreText: { fontSize: 14, }, termsText: { fontSize: 11, textAlign: 'center', lineHeight: 18, paddingHorizontal: SPACING[4], }, }) }