feat(app): Stitch paywall — single plan card with free-trial toggle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, ActivityIndicator, Alert, Linking, BackHandler, ImageBackground, Platform, useWindowDimensions } from 'react-native';
|
||||
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, ActivityIndicator, Alert, Linking, BackHandler, ImageBackground, Platform, Switch } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||
@@ -175,6 +175,20 @@ const getBillingCopy = (language: Language) => {
|
||||
autoRenewMonthly: 'Verlängert sich monatlich automatisch. Jederzeit über iOS-Einstellungen kündbar.',
|
||||
autoRenewYearly: 'Verlängert sich jährlich automatisch. Jederzeit über iOS-Einstellungen kündbar.',
|
||||
manageInSettings: 'In iOS-Einstellungen verwalten',
|
||||
paywallEyebrow: 'GreenLens Pro',
|
||||
paywallHeadline: 'Unbegrenzter Zugriff',
|
||||
paywallSub: 'Unbegrenzte Scans, Health-Checks und dein persönlicher Pflegeplan.',
|
||||
planCardTitle: 'GreenLens Pro',
|
||||
planCardBody: 'Unbegrenzte KI-Scans, Gesundheitsdiagnose, 7-Tage-Rettungspläne, 100 Credits/Monat',
|
||||
planCardPriceTrial: (price: string) => `7 Tage gratis, dann ${price}/Jahr`,
|
||||
planCardPriceMonthly: (price: string) => `${price}/Monat`,
|
||||
trialToggleLabel: 'Gratis-Test aktiviert',
|
||||
dueTodayTrial: 'Fällig heute — 7 Tage gratis',
|
||||
dueTodayAmount: '0,00 €',
|
||||
dueLater: (date: string) => `Fällig am ${date}`,
|
||||
ctaTrial: 'Gratis testen',
|
||||
ctaMonthly: 'Jetzt starten',
|
||||
cancelAnytime: 'Jederzeit kündbar',
|
||||
};
|
||||
} else if (language === 'es') {
|
||||
return {
|
||||
@@ -249,6 +263,20 @@ const getBillingCopy = (language: Language) => {
|
||||
autoRenewMonthly: 'Se renueva mensualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.',
|
||||
autoRenewYearly: 'Se renueva anualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.',
|
||||
manageInSettings: 'Administrar en Ajustes de iOS',
|
||||
paywallEyebrow: 'GreenLens Pro',
|
||||
paywallHeadline: 'Acceso ilimitado',
|
||||
paywallSub: 'Escaneos ilimitados, chequeos de salud y tu plan de cuidados personal.',
|
||||
planCardTitle: 'GreenLens Pro',
|
||||
planCardBody: 'Escaneos IA ilimitados, diagnóstico de salud, planes de rescate de 7 días, 100 créditos/mes',
|
||||
planCardPriceTrial: (price: string) => `7 días gratis, luego ${price}/año`,
|
||||
planCardPriceMonthly: (price: string) => `${price}/mes`,
|
||||
trialToggleLabel: 'Prueba gratis activada',
|
||||
dueTodayTrial: 'Hoy — 7 días gratis',
|
||||
dueTodayAmount: '0,00 €',
|
||||
dueLater: (date: string) => `El ${date}`,
|
||||
ctaTrial: 'Probar gratis',
|
||||
ctaMonthly: 'Empezar ahora',
|
||||
cancelAnytime: 'Cancela cuando quieras',
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -323,6 +351,20 @@ const getBillingCopy = (language: Language) => {
|
||||
autoRenewMonthly: 'Auto-renews monthly. Cancel anytime in iOS Settings.',
|
||||
autoRenewYearly: 'Auto-renews annually. Cancel anytime in iOS Settings.',
|
||||
manageInSettings: 'Manage in iOS Settings',
|
||||
paywallEyebrow: 'GreenLens Pro',
|
||||
paywallHeadline: 'Get Unlimited Access',
|
||||
paywallSub: 'Unlimited scans, health checks and your personal care plan.',
|
||||
planCardTitle: 'GreenLens Pro',
|
||||
planCardBody: 'Unlimited AI scans, health diagnosis, 7-day rescue plans, 100 credits/month',
|
||||
planCardPriceTrial: (price: string) => `Free for 7 days, then ${price}/year`,
|
||||
planCardPriceMonthly: (price: string) => `${price}/month`,
|
||||
trialToggleLabel: 'Free Trial Enabled',
|
||||
dueTodayTrial: 'Due today — 7 days free',
|
||||
dueTodayAmount: '€0.00',
|
||||
dueLater: (date: string) => `Due ${date}`,
|
||||
ctaTrial: 'Try Free',
|
||||
ctaMonthly: 'Start Now',
|
||||
cancelAnytime: 'Cancel Anytime',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -338,8 +380,6 @@ export default function BillingScreen() {
|
||||
const posthog = useSafeAnalytics();
|
||||
const copy = getBillingCopy(language);
|
||||
const isExpoGo = Constants.appOwnership === 'expo';
|
||||
const { height: windowHeight } = useWindowDimensions();
|
||||
const compactPaywall = windowHeight < 760;
|
||||
|
||||
const [subModalVisible, setSubModalVisible] = useState(false);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
@@ -407,8 +447,16 @@ export default function BillingScreen() {
|
||||
};
|
||||
}, [isExpoGo]);
|
||||
|
||||
const trialEnabled = selectedPaywallPlan === 'yearly';
|
||||
|
||||
useEffect(() => {
|
||||
try { posthog.capture('paywall_viewed', { plan_id: planId }); } catch {}
|
||||
try {
|
||||
posthog.capture('paywall_viewed', {
|
||||
plan_id: planId,
|
||||
context: onboardingContext ? 'onboarding' : 'in_app',
|
||||
trial_enabled: trialEnabled,
|
||||
});
|
||||
} catch {}
|
||||
if (showPaywallPlans) {
|
||||
try {
|
||||
posthog.capture('hard_paywall_viewed', {
|
||||
@@ -417,18 +465,19 @@ export default function BillingScreen() {
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
}, [posthog, planId, session?.serverUserId, showPaywallPlans]);
|
||||
}, [posthog, planId, session?.serverUserId, showPaywallPlans, onboardingContext, trialEnabled]);
|
||||
|
||||
const monthlyPackage = subscriptionPackages.monthly_pro;
|
||||
const yearlyPackage = subscriptionPackages.yearly_pro;
|
||||
|
||||
const monthlyPrice = monthlyPackage?.product.priceString ?? copy.proPlanPrice;
|
||||
const yearlyPrice = yearlyPackage?.product.priceString ?? copy.proYearlyPlanPrice;
|
||||
const weeklyDisplayPrice = copy.weeklyPriceFallback;
|
||||
const yearlyDisplayPrice = copy.yearlyPriceFallback;
|
||||
const selectedProductId: SubscriptionProductId = selectedPaywallPlan === 'yearly' ? 'yearly_pro' : 'monthly_pro';
|
||||
const paywallCtaLabel = selectedPaywallPlan === 'yearly' ? copy.startTrial : copy.monthlyCta;
|
||||
const paywallFooterLabel = selectedPaywallPlan === 'yearly' ? copy.noPaymentToday : copy.monthlyFooter;
|
||||
const trialEndDate = useMemo(() => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 7);
|
||||
const locale = language === 'de' ? 'de-DE' : language === 'es' ? 'es-ES' : 'en-US';
|
||||
return date.toLocaleDateString(locale, { day: 'numeric', month: 'long' });
|
||||
}, [language]);
|
||||
|
||||
const topupLabels = useMemo(() => ({
|
||||
topup_small: topupProducts.topup_small ? `${TOPUP_CREDITS_BY_PRODUCT.topup_small} Credits - ${topupProducts.topup_small.priceString}` : copy.topupSmall,
|
||||
@@ -638,125 +687,89 @@ export default function BillingScreen() {
|
||||
imageStyle={styles.hardPaywallHeroImage}
|
||||
resizeMode="cover"
|
||||
>
|
||||
<SafeAreaView
|
||||
style={[styles.hardPaywallSafe, compactPaywall && styles.hardPaywallSafeCompact]}
|
||||
edges={['top']}
|
||||
>
|
||||
<SafeAreaView style={styles.hardPaywallSafe} edges={['top']}>
|
||||
<View style={styles.heroTopBar}>
|
||||
<TouchableOpacity onPress={handleBack} style={styles.heroIconButton}>
|
||||
<Ionicons name="close" size={24} color="#FFFFFF" />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={handleRestore} disabled={isUpdating}>
|
||||
<Text style={styles.heroRestoreText}>{copy.restorePurchases}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View style={[styles.scanFrameOverlay, compactPaywall && styles.scanFrameOverlayCompact]}>
|
||||
<View style={[styles.scanCorner, styles.scanCornerTopLeft]} />
|
||||
<View style={[styles.scanCorner, styles.scanCornerTopRight]} />
|
||||
<View style={[styles.scanCorner, styles.scanCornerBottomLeft]} />
|
||||
<View style={[styles.scanCorner, styles.scanCornerBottomRight]} />
|
||||
</View>
|
||||
|
||||
<View style={[styles.identifiedChip, compactPaywall && styles.identifiedChipCompact]}>
|
||||
<Ionicons name="leaf-outline" size={17} color="#8FD19E" />
|
||||
<Text style={styles.identifiedChipText}>{copy.identifiedChip}</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.hardPaywallSheet, compactPaywall && styles.hardPaywallSheetCompact]}>
|
||||
<View style={[styles.sheetHandle, compactPaywall && styles.sheetHandleCompact]} />
|
||||
<Text style={[styles.hardPaywallTitle, compactPaywall && styles.hardPaywallTitleCompact]}>
|
||||
{copy.hardPaywallTitle}
|
||||
</Text>
|
||||
<Text style={[styles.hardPaywallHint, compactPaywall && styles.hardPaywallHintCompact]}>
|
||||
{copy.hardPaywallHint}
|
||||
</Text>
|
||||
|
||||
<View style={[styles.hardBenefits, compactPaywall && styles.hardBenefitsCompact]}>
|
||||
{[
|
||||
{ icon: 'infinite-outline' as const, title: copy.benefitUnlimitedScans },
|
||||
{ icon: 'calendar-outline' as const, title: copy.benefitCarePlan },
|
||||
].map((benefit) => (
|
||||
<View key={benefit.title} style={[styles.hardBenefitRow, compactPaywall && styles.hardBenefitRowCompact]}>
|
||||
<View style={[styles.hardBenefitIcon, compactPaywall && styles.hardBenefitIconCompact]}>
|
||||
<Ionicons name={benefit.icon} size={compactPaywall ? 18 : 20} color="#1F5B34" />
|
||||
</View>
|
||||
<Text style={[styles.hardBenefitText, compactPaywall && styles.hardBenefitTextCompact]}>
|
||||
{benefit.title}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={styles.hardPlanRow}>
|
||||
{[
|
||||
{ id: 'weekly' as PaywallPlanId, name: copy.weeklyPlanName, price: weeklyDisplayPrice },
|
||||
{ id: 'yearly' as PaywallPlanId, name: copy.yearlyPlanName, price: yearlyDisplayPrice, badge: copy.bestOffer },
|
||||
].map((plan) => {
|
||||
const selected = selectedPaywallPlan === plan.id;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={plan.id}
|
||||
style={[
|
||||
styles.hardPlanCard,
|
||||
compactPaywall && styles.hardPlanCardCompact,
|
||||
selected && styles.hardPlanCardSelected,
|
||||
]}
|
||||
onPress={() => setSelectedPaywallPlan(plan.id)}
|
||||
activeOpacity={0.88}
|
||||
>
|
||||
<View style={[styles.planRadio, selected && styles.planRadioSelected]}>
|
||||
{selected ? <Ionicons name="checkmark" size={15} color="#FFFFFF" /> : null}
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[styles.hardPlanName, compactPaywall && styles.hardPlanNameCompact]}>{plan.name}</Text>
|
||||
<Text style={[styles.hardPlanPrice, compactPaywall && styles.hardPlanPriceCompact]}>{plan.price}</Text>
|
||||
{plan.badge ? (
|
||||
<View style={styles.bestOfferBadge}>
|
||||
<Text style={styles.bestOfferText}>{plan.badge}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.hardPaywallCta,
|
||||
compactPaywall && styles.hardPaywallCtaCompact,
|
||||
(!storeReady || isUpdating || Boolean(storeError)) && styles.disabledPlanCard,
|
||||
]}
|
||||
onPress={() => handlePurchase(selectedProductId)}
|
||||
disabled={isUpdating || !storeReady || Boolean(storeError)}
|
||||
activeOpacity={0.9}
|
||||
<View style={[styles.paywallSheet, { backgroundColor: colors.background }]}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.paywallBody}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{isUpdating || !storeReady ? (
|
||||
<ActivityIndicator color="#FFFFFF" />
|
||||
) : (
|
||||
<>
|
||||
<Text style={[styles.hardPaywallCtaText, compactPaywall && styles.hardPaywallCtaTextCompact]}>
|
||||
{paywallCtaLabel}
|
||||
</Text>
|
||||
<Ionicons name="arrow-forward" size={23} color="#FFFFFF" />
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<View style={[styles.sheetHandle, { backgroundColor: colors.border }]} />
|
||||
<Text style={[styles.paywallEyebrow, { color: colors.primary }]}>{copy.paywallEyebrow.toUpperCase()}</Text>
|
||||
<Text style={[styles.paywallHeadline, { color: colors.text }]}>{copy.paywallHeadline}</Text>
|
||||
<Text style={[styles.paywallSub, { color: colors.textSecondary }]}>{copy.paywallSub}</Text>
|
||||
|
||||
{storeError ? (
|
||||
<Text style={styles.hardPaywallFooter}>{storeError}</Text>
|
||||
) : null}
|
||||
<Text style={styles.hardPaywallFooter}>{paywallFooterLabel}</Text>
|
||||
<View style={styles.legalLinksRow}>
|
||||
<TouchableOpacity onPress={() => Linking.openURL('https://greenlenspro.com/privacy')}>
|
||||
<Text style={[styles.legalLink, { color: '#1F5B34' }]}>Privacy Policy</Text>
|
||||
<View style={[styles.planCard, { backgroundColor: colors.surfaceMuted }]}>
|
||||
<Text style={[styles.planCardTitle, { color: colors.text }]}>{copy.planCardTitle}</Text>
|
||||
<Text style={[styles.planCardBody, { color: colors.textSecondary }]}>{copy.planCardBody}</Text>
|
||||
<View style={[styles.planCardDivider, { backgroundColor: colors.border }]} />
|
||||
<Text style={[styles.planCardPrice, { color: colors.text }]}>
|
||||
{trialEnabled ? copy.planCardPriceTrial(yearlyPrice) : copy.planCardPriceMonthly(monthlyPrice)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.trialToggleRow, { backgroundColor: colors.surface, borderColor: colors.border }]}>
|
||||
<Text style={[styles.trialToggleLabel, { color: colors.text }]}>{copy.trialToggleLabel}</Text>
|
||||
<Switch
|
||||
value={trialEnabled}
|
||||
onValueChange={(next) => setSelectedPaywallPlan(next ? 'yearly' : 'weekly')}
|
||||
trackColor={{ true: colors.primary, false: colors.border }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{trialEnabled ? (
|
||||
<View style={styles.dueTimeline}>
|
||||
<View style={styles.dueRow}>
|
||||
<View style={[styles.dueDot, { backgroundColor: colors.primary }]} />
|
||||
<Text style={[styles.dueLabel, { color: colors.primary }]}>{copy.dueTodayTrial}</Text>
|
||||
<Text style={[styles.dueAmount, { color: colors.text }]}>{copy.dueTodayAmount}</Text>
|
||||
</View>
|
||||
<View style={[styles.dueLine, { backgroundColor: colors.border }]} />
|
||||
<View style={styles.dueRow}>
|
||||
<View style={[styles.dueDot, { backgroundColor: colors.border }]} />
|
||||
<Text style={[styles.dueLabel, { color: colors.textSecondary }]}>{copy.dueLater(trialEndDate)}</Text>
|
||||
<Text style={[styles.dueAmount, { color: colors.text }]}>{yearlyPrice}</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{storeError ? (
|
||||
<Text style={[styles.paywallFooterText, { color: colors.danger, marginBottom: 8 }]}>{storeError}</Text>
|
||||
) : null}
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.paywallCta,
|
||||
{ backgroundColor: colors.primary },
|
||||
(!storeReady || isUpdating || Boolean(storeError)) && styles.disabledPlanCard,
|
||||
]}
|
||||
onPress={() => handlePurchase(trialEnabled ? 'yearly_pro' : 'monthly_pro')}
|
||||
disabled={isUpdating || !storeReady || Boolean(storeError)}
|
||||
activeOpacity={0.86}
|
||||
>
|
||||
{isUpdating || !storeReady ? (
|
||||
<ActivityIndicator color={colors.onPrimary} />
|
||||
) : (
|
||||
<Text style={[styles.paywallCtaText, { color: colors.onPrimary }]}>
|
||||
{trialEnabled ? copy.ctaTrial : copy.ctaMonthly}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.legalSep, { color: '#7A8079' }]}> · </Text>
|
||||
<TouchableOpacity onPress={() => Linking.openURL('https://greenlenspro.com/terms')}>
|
||||
<Text style={[styles.legalLink, { color: '#1F5B34' }]}>Terms of Use</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity style={styles.hardRestoreBtn} onPress={handleRestore} disabled={isUpdating}>
|
||||
<Text style={styles.hardRestoreText}>{copy.restorePurchases}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.paywallFooter}>
|
||||
<Text style={[styles.paywallFooterText, { color: colors.textMuted }]}>Privacy | Terms</Text>
|
||||
<Text style={[styles.paywallFooterText, { color: colors.textMuted }]}>{copy.cancelAnytime}</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ImageBackground>
|
||||
@@ -1142,10 +1155,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
hardPaywallSafe: {
|
||||
flex: 1,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
hardPaywallSafeCompact: {
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
heroTopBar: {
|
||||
flexDirection: 'row',
|
||||
@@ -1170,256 +1179,42 @@ const styles = StyleSheet.create({
|
||||
textShadowOffset: { width: 0, height: 1 },
|
||||
textShadowRadius: 3,
|
||||
},
|
||||
scanFrameOverlay: {
|
||||
position: 'absolute',
|
||||
top: '14%',
|
||||
left: 54,
|
||||
right: 54,
|
||||
height: '28%',
|
||||
},
|
||||
scanFrameOverlayCompact: {
|
||||
display: 'none',
|
||||
},
|
||||
scanCorner: {
|
||||
position: 'absolute',
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderColor: '#FFFFFF',
|
||||
},
|
||||
scanCornerTopLeft: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
borderTopWidth: 4,
|
||||
borderLeftWidth: 4,
|
||||
borderTopLeftRadius: 18,
|
||||
},
|
||||
scanCornerTopRight: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
borderTopWidth: 4,
|
||||
borderRightWidth: 4,
|
||||
borderTopRightRadius: 18,
|
||||
},
|
||||
scanCornerBottomLeft: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
borderBottomWidth: 4,
|
||||
borderLeftWidth: 4,
|
||||
borderBottomLeftRadius: 18,
|
||||
},
|
||||
scanCornerBottomRight: {
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
borderBottomWidth: 4,
|
||||
borderRightWidth: 4,
|
||||
borderBottomRightRadius: 18,
|
||||
},
|
||||
identifiedChip: {
|
||||
position: 'absolute',
|
||||
alignSelf: 'center',
|
||||
top: '43%',
|
||||
zIndex: 4,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
borderRadius: 18,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
backgroundColor: '#16351FCC',
|
||||
},
|
||||
identifiedChipCompact: {
|
||||
display: 'none',
|
||||
},
|
||||
identifiedChipText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 15,
|
||||
fontWeight: '800',
|
||||
},
|
||||
hardPaywallSheet: {
|
||||
backgroundColor: '#FAFAF5',
|
||||
paywallSheet: {
|
||||
flex: 1,
|
||||
borderTopLeftRadius: 30,
|
||||
borderTopRightRadius: 30,
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 9,
|
||||
paddingBottom: 18,
|
||||
overflow: 'hidden',
|
||||
zIndex: 5,
|
||||
},
|
||||
hardPaywallSheetCompact: {
|
||||
paddingHorizontal: 18,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 16,
|
||||
},
|
||||
sheetHandle: {
|
||||
alignSelf: 'center',
|
||||
width: 42,
|
||||
height: 5,
|
||||
borderRadius: 999,
|
||||
backgroundColor: '#C7C8C3',
|
||||
marginTop: 10,
|
||||
marginBottom: 8,
|
||||
},
|
||||
sheetHandleCompact: {
|
||||
marginBottom: 8,
|
||||
},
|
||||
hardPaywallTitle: {
|
||||
color: '#101411',
|
||||
fontSize: 23,
|
||||
lineHeight: 28,
|
||||
fontWeight: '900',
|
||||
textAlign: 'center',
|
||||
marginBottom: 5,
|
||||
},
|
||||
hardPaywallTitleCompact: {
|
||||
fontSize: 22,
|
||||
lineHeight: 26,
|
||||
},
|
||||
hardPaywallHint: {
|
||||
color: '#676C66',
|
||||
fontSize: 12,
|
||||
lineHeight: 17,
|
||||
textAlign: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
hardPaywallHintCompact: {
|
||||
display: 'none',
|
||||
},
|
||||
hardBenefits: {
|
||||
gap: 5,
|
||||
marginBottom: 7,
|
||||
},
|
||||
hardBenefitsCompact: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
hardBenefitRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
hardBenefitRowCompact: {
|
||||
gap: 9,
|
||||
},
|
||||
hardBenefitIcon: {
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: 17,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#E3EFD9',
|
||||
},
|
||||
hardBenefitIconCompact: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
},
|
||||
hardBenefitText: {
|
||||
color: '#101411',
|
||||
fontSize: 13,
|
||||
fontWeight: '800',
|
||||
},
|
||||
hardBenefitTextCompact: {
|
||||
fontSize: 13,
|
||||
},
|
||||
hardPlanRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
marginBottom: 7,
|
||||
},
|
||||
hardPlanCard: {
|
||||
flex: 1,
|
||||
minHeight: 76,
|
||||
borderRadius: 14,
|
||||
borderWidth: 1.5,
|
||||
borderColor: '#D8DAD3',
|
||||
padding: 9,
|
||||
flexDirection: 'row',
|
||||
gap: 10,
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
hardPlanCardCompact: {
|
||||
minHeight: 76,
|
||||
padding: 9,
|
||||
gap: 8,
|
||||
},
|
||||
hardPlanCardSelected: {
|
||||
borderColor: '#1F5B34',
|
||||
borderWidth: 2,
|
||||
backgroundColor: '#FBFFF8',
|
||||
},
|
||||
planRadio: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 11,
|
||||
borderWidth: 2,
|
||||
borderColor: '#8C918A',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 2,
|
||||
},
|
||||
planRadioSelected: {
|
||||
backgroundColor: '#1F5B34',
|
||||
borderColor: '#1F5B34',
|
||||
},
|
||||
hardPlanName: {
|
||||
color: '#101411',
|
||||
fontSize: 14,
|
||||
fontWeight: '900',
|
||||
marginBottom: 4,
|
||||
},
|
||||
hardPlanNameCompact: {
|
||||
fontSize: 13,
|
||||
marginBottom: 2,
|
||||
},
|
||||
hardPlanPrice: {
|
||||
color: '#626862',
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
lineHeight: 18,
|
||||
},
|
||||
hardPlanPriceCompact: {
|
||||
fontSize: 11,
|
||||
lineHeight: 15,
|
||||
},
|
||||
bestOfferBadge: {
|
||||
alignSelf: 'flex-start',
|
||||
backgroundColor: '#DDEBCF',
|
||||
borderRadius: 999,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 3,
|
||||
marginTop: 5,
|
||||
},
|
||||
bestOfferText: {
|
||||
color: '#1F5B34',
|
||||
fontSize: 10,
|
||||
fontWeight: '900',
|
||||
},
|
||||
hardPaywallCta: {
|
||||
minHeight: 50,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#1F5B34',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
marginBottom: 5,
|
||||
},
|
||||
hardPaywallCtaCompact: {
|
||||
minHeight: 48,
|
||||
marginBottom: 8,
|
||||
},
|
||||
hardPaywallCtaText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 15,
|
||||
fontWeight: '900',
|
||||
},
|
||||
hardPaywallCtaTextCompact: {
|
||||
fontSize: 14,
|
||||
},
|
||||
hardPaywallFooter: {
|
||||
color: '#777C75',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
marginBottom: 5,
|
||||
},
|
||||
paywallBody: { paddingHorizontal: 22, paddingTop: 10, paddingBottom: 24 },
|
||||
paywallEyebrow: { fontSize: 12, fontWeight: '900', letterSpacing: 1.4, textAlign: 'center', marginBottom: 6 },
|
||||
paywallHeadline: { fontSize: 32, fontWeight: '900', textAlign: 'center', marginBottom: 6 },
|
||||
paywallSub: { fontSize: 15, lineHeight: 21, textAlign: 'center', marginBottom: 18 },
|
||||
planCard: { borderRadius: 16, padding: 18, marginBottom: 14 },
|
||||
planCardTitle: { fontSize: 19, fontWeight: '800', marginBottom: 6 },
|
||||
planCardBody: { fontSize: 14, lineHeight: 20 },
|
||||
planCardDivider: { height: StyleSheet.hairlineWidth, marginVertical: 12 },
|
||||
planCardPrice: { fontSize: 15, fontWeight: '800' },
|
||||
trialToggleRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderRadius: 14, borderWidth: 1, paddingHorizontal: 16, paddingVertical: 12, marginBottom: 16 },
|
||||
trialToggleLabel: { fontSize: 15, fontWeight: '800' },
|
||||
dueTimeline: { marginBottom: 18, paddingHorizontal: 4 },
|
||||
dueRow: { flexDirection: 'row', alignItems: 'center', gap: 10 },
|
||||
dueDot: { width: 10, height: 10, borderRadius: 5 },
|
||||
dueLine: { width: 2, height: 18, marginLeft: 4, marginVertical: 2 },
|
||||
dueLabel: { flex: 1, fontSize: 14, fontWeight: '700' },
|
||||
dueAmount: { fontSize: 14, fontWeight: '800' },
|
||||
paywallCta: { height: 58, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginBottom: 12 },
|
||||
paywallCtaText: { fontSize: 18, fontWeight: '800' },
|
||||
paywallFooter: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||
paywallFooterText: { fontSize: 12, fontWeight: '600' },
|
||||
safeArea: { flex: 1 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', padding: 16 },
|
||||
backButton: { width: 40, height: 40, justifyContent: 'center' },
|
||||
@@ -1768,17 +1563,6 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
paddingVertical: 8,
|
||||
},
|
||||
hardRestoreBtn: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 6,
|
||||
marginTop: 4,
|
||||
},
|
||||
hardRestoreText: {
|
||||
color: '#7A8079',
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
textDecorationLine: 'underline',
|
||||
},
|
||||
autoRenewText: {
|
||||
fontSize: 11,
|
||||
marginTop: 2,
|
||||
|
||||
Reference in New Issue
Block a user