feat(app): paywall is param-driven and dismissible; onboarding context routes X to sign-up
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ 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, useWindowDimensions } from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
import { useFocusEffect } from '@react-navigation/native';
|
||||||
import Constants from 'expo-constants';
|
import Constants from 'expo-constants';
|
||||||
import Purchases, {
|
import Purchases, {
|
||||||
@@ -330,6 +330,9 @@ const getBillingCopy = (language: Language) => {
|
|||||||
|
|
||||||
export default function BillingScreen() {
|
export default function BillingScreen() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const params = useLocalSearchParams<{ view?: string; context?: string }>();
|
||||||
|
const paywallRequested = params.view === 'paywall';
|
||||||
|
const onboardingContext = params.context === 'onboarding';
|
||||||
const { isDarkMode, language, billingSummary, isLoadingBilling, simulatePurchase, simulateWebhookEvent, syncRevenueCatState, colorPalette, session } = useApp();
|
const { isDarkMode, language, billingSummary, isLoadingBilling, simulatePurchase, simulateWebhookEvent, syncRevenueCatState, colorPalette, session } = useApp();
|
||||||
const colors = useColors(isDarkMode, colorPalette);
|
const colors = useColors(isDarkMode, colorPalette);
|
||||||
const posthog = useSafeAnalytics();
|
const posthog = useSafeAnalytics();
|
||||||
@@ -351,7 +354,7 @@ export default function BillingScreen() {
|
|||||||
|
|
||||||
const planId = billingSummary?.entitlement?.plan || 'free';
|
const planId = billingSummary?.entitlement?.plan || 'free';
|
||||||
const credits = isLoadingBilling && !billingSummary ? '...' : (billingSummary?.credits?.available ?? 0);
|
const credits = isLoadingBilling && !billingSummary ? '...' : (billingSummary?.credits?.available ?? 0);
|
||||||
const showPaywallPlans = !session || (!isLoadingBilling && planId !== 'pro');
|
const showPaywallPlans = (!session || paywallRequested) && planId !== 'pro';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -439,6 +442,16 @@ export default function BillingScreen() {
|
|||||||
|
|
||||||
const handleBack = useCallback(() => {
|
const handleBack = useCallback(() => {
|
||||||
if (showPaywallPlans) {
|
if (showPaywallPlans) {
|
||||||
|
posthog.capture('paywall_dismissed', { context: onboardingContext ? 'onboarding' : 'in_app' });
|
||||||
|
if (onboardingContext) {
|
||||||
|
router.replace('/auth/signup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (session) {
|
||||||
|
if (router.canGoBack()) router.back();
|
||||||
|
else router.replace('/(tabs)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
router.replace('/onboarding');
|
router.replace('/onboarding');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -447,7 +460,9 @@ export default function BillingScreen() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.replace('/(tabs)');
|
router.replace('/(tabs)');
|
||||||
}, [router, showPaywallPlans]);
|
}, [router, showPaywallPlans, onboardingContext, session, posthog]);
|
||||||
|
|
||||||
|
const postPurchaseRoute = onboardingContext ? '/auth/signup' : '/(tabs)';
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
@@ -455,12 +470,12 @@ export default function BillingScreen() {
|
|||||||
if (!showPaywallPlans) {
|
if (!showPaywallPlans) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
router.replace('/onboarding');
|
handleBack();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => subscription.remove();
|
return () => subscription.remove();
|
||||||
}, [router, showPaywallPlans]),
|
}, [showPaywallPlans, handleBack]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const completeExpoGoSimulation = async (productId: PurchaseProductId) => {
|
const completeExpoGoSimulation = async (productId: PurchaseProductId) => {
|
||||||
@@ -471,7 +486,7 @@ export default function BillingScreen() {
|
|||||||
posthog.capture('subscription_started', { product_id: productId, simulated: true });
|
posthog.capture('subscription_started', { product_id: productId, simulated: true });
|
||||||
posthog.capture('trial_started', { product_id: productId, simulated: true });
|
posthog.capture('trial_started', { product_id: productId, simulated: true });
|
||||||
setSubModalVisible(false);
|
setSubModalVisible(false);
|
||||||
router.replace('/(tabs)');
|
router.replace(postPurchaseRoute);
|
||||||
} else {
|
} else {
|
||||||
posthog.capture('topup_purchased', { product_id: productId, simulated: true });
|
posthog.capture('topup_purchased', { product_id: productId, simulated: true });
|
||||||
}
|
}
|
||||||
@@ -534,7 +549,7 @@ export default function BillingScreen() {
|
|||||||
posthog.capture('subscription_started', { product_id: productId });
|
posthog.capture('subscription_started', { product_id: productId });
|
||||||
posthog.capture('trial_started', { product_id: productId });
|
posthog.capture('trial_started', { product_id: productId });
|
||||||
setSubModalVisible(false);
|
setSubModalVisible(false);
|
||||||
setTimeout(() => router.replace('/(tabs)'), 0);
|
setTimeout(() => router.replace(postPurchaseRoute), 0);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const selectedProduct = topupProducts[productId];
|
const selectedProduct = topupProducts[productId];
|
||||||
|
|||||||
Reference in New Issue
Block a user