|
@@ -0,0 +1,337 @@
|
|
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
|
|
+import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, Image, ScrollView } from 'react-native';
|
|
|
|
|
+import { useIAPContext } from 'src/contexts/IAPContext';
|
|
|
|
|
+import { useNavigation } from '@react-navigation/native';
|
|
|
|
|
+import { AntDesign } from '@expo/vector-icons';
|
|
|
|
|
+import { Colors } from 'src/theme';
|
|
|
|
|
+import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
|
+
|
|
|
|
|
+const PremiumPaywallScreen = () => {
|
|
|
|
|
+ const { subscriptions, purchaseSubscription, restorePurchases, loading, isPremium } = useIAPContext();
|
|
|
|
|
+ const navigation = useNavigation();
|
|
|
|
|
+
|
|
|
|
|
+ const [selectedSku, setSelectedSku] = useState<string | null>(null);
|
|
|
|
|
+ const [displaySubs, setDisplaySubs] = useState<any[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (subscriptions.length > 0) {
|
|
|
|
|
+ const subs = [...subscriptions];
|
|
|
|
|
+ if (subs.length === 1) {
|
|
|
|
|
+ subs.push({
|
|
|
|
|
+ id: 'dummy_premium_monthly',
|
|
|
|
|
+ title: 'NM Premium Monthly (Dummy)',
|
|
|
|
|
+ description: 'NM Premium',
|
|
|
|
|
+ price: 9.99,
|
|
|
|
|
+ currency: 'USD',
|
|
|
|
|
+ displayPrice: '9,99 USD'
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ subs.sort((a, b) => (b.price || 0) - (a.price || 0));
|
|
|
|
|
+
|
|
|
|
|
+ setDisplaySubs(subs);
|
|
|
|
|
+ if (!selectedSku) {
|
|
|
|
|
+ setSelectedSku(subs[0].productId || subs[0].id);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [subscriptions]);
|
|
|
|
|
+
|
|
|
|
|
+ const handlePurchase = async (sku: string) => {
|
|
|
|
|
+ const success = await purchaseSubscription(sku);
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ Alert.alert('Success', 'Welcome to NomadMania Premium!');
|
|
|
|
|
+ if (navigation.canGoBack()) {
|
|
|
|
|
+ navigation.goBack();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Alert.alert('Error', 'Purchase failed or was canceled.');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleRestore = async () => {
|
|
|
|
|
+ const success = await restorePurchases();
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ Alert.alert('Success', 'Purchases restored successfully.');
|
|
|
|
|
+ if (navigation.canGoBack()) {
|
|
|
|
|
+ navigation.goBack();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Alert.alert('Notice', 'No previous purchases found to restore.');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderProduct = (product: any) => {
|
|
|
|
|
+ const productId = product.productId || product.id;
|
|
|
|
|
+ const title = product.title || product.displayName || productId;
|
|
|
|
|
+ const desc = product.description;
|
|
|
|
|
+ const price = product.localizedPrice || product.displayPrice || `${product.price} ${product.currency}`;
|
|
|
|
|
+ const isSelected = selectedSku === productId;
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ key={productId}
|
|
|
|
|
+ style={[styles.productCard, isSelected ? styles.productCardSelected : styles.productCardUnselected]}
|
|
|
|
|
+ onPress={() => setSelectedSku(productId)}
|
|
|
|
|
+ disabled={loading}
|
|
|
|
|
+ >
|
|
|
|
|
+ <View style={styles.productInfo}>
|
|
|
|
|
+ <Text style={styles.productTitle}>{title}</Text>
|
|
|
|
|
+ <Text style={styles.productDesc}>{desc}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ <View style={styles.priceContainer}>
|
|
|
|
|
+ <Text style={styles.productPrice}>{price}</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ );
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <SafeAreaView style={styles.safeArea}>
|
|
|
|
|
+ <ScrollView style={styles.container}>
|
|
|
|
|
+ <View style={styles.headerRow}>
|
|
|
|
|
+ <TouchableOpacity style={styles.closeButton} onPress={() => navigation.goBack()}>
|
|
|
|
|
+ <AntDesign name="close" size={20} color={Colors.LIGHT_GRAY} />
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ <TouchableOpacity style={styles.restoreHeaderButton} onPress={handleRestore} disabled={loading}>
|
|
|
|
|
+ <Text style={styles.restoreHeaderText}>Restore Purchases</Text>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ <View style={{ width: 30 }}></View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <Text style={styles.title}>NomadMania Premium</Text>
|
|
|
|
|
+ <Text style={styles.subtitle}>Join Premium and shape the future of NomadMania</Text>
|
|
|
|
|
+
|
|
|
|
|
+ {isPremium && (
|
|
|
|
|
+ <View style={styles.premiumBadge}>
|
|
|
|
|
+ <Text style={styles.premiumText}>You are currently a Premium user!</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <Image source={require('../../../assets/premium/premium.png')} style={styles.premiumBadgeImage} />
|
|
|
|
|
+
|
|
|
|
|
+ <View style={styles.infoBox}>
|
|
|
|
|
+ <Text style={styles.infoBoxTitle}>The current Premium version includes:</Text>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/badge.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>A Premium badge on your profile and ranking page</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/location-filter.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>Friends and Countries filter in the nomads location sharing tool</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/trusted.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>Automatically become a Trusted User with all benefits</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/series.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>View specific Series visited by other members</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/data-export.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>Profile data export in the web</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/discounts.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>Loyalty Programme - discounts for Tours and other Events</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Image source={require('../../../assets/premium/offline-maps.png')} style={styles.infoBoxImage} />
|
|
|
|
|
+ <Text style={styles.infoBoxText}>Offline maps in the app</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </ScrollView>
|
|
|
|
|
+
|
|
|
|
|
+ <View style={styles.productsContainer}>
|
|
|
|
|
+ {displaySubs.length > 0 ? (
|
|
|
|
|
+ displaySubs.map(renderProduct)
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <View style={styles.emptyContainer}>
|
|
|
|
|
+ {loading ? (
|
|
|
|
|
+ <ActivityIndicator size="large" color={Colors.ORANGE} />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Text style={styles.emptyText}>No subscriptions available right now.</Text>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ style={styles.joinButton}
|
|
|
|
|
+ onPress={() => {
|
|
|
|
|
+ if (selectedSku) handlePurchase(selectedSku);
|
|
|
|
|
+ }}
|
|
|
|
|
+ disabled={loading || !selectedSku}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.joinButtonText}>Join NomadMania Premium</Text>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+
|
|
|
|
|
+ {loading && (
|
|
|
|
|
+ <View style={styles.loadingOverlay}>
|
|
|
|
|
+ <ActivityIndicator size="large" color="#ffffff" />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </SafeAreaView>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const styles = StyleSheet.create({
|
|
|
|
|
+ safeArea: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ backgroundColor: Colors.WHITE,
|
|
|
|
|
+ },
|
|
|
|
|
+ container: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ padding: 16,
|
|
|
|
|
+ backgroundColor: Colors.WHITE,
|
|
|
|
|
+ },
|
|
|
|
|
+ headerRow: {
|
|
|
|
|
+ flexDirection: 'row',
|
|
|
|
|
+ justifyContent: 'space-between',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ marginBottom: 8,
|
|
|
|
|
+ },
|
|
|
|
|
+ closeButton: {
|
|
|
|
|
+ padding: 5,
|
|
|
|
|
+ },
|
|
|
|
|
+ restoreHeaderButton: {
|
|
|
|
|
+ padding: 5,
|
|
|
|
|
+ },
|
|
|
|
|
+ restoreHeaderText: {
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ color: Colors.TEXT_GRAY,
|
|
|
|
|
+ fontWeight: '600',
|
|
|
|
|
+ },
|
|
|
|
|
+ title: {
|
|
|
|
|
+ fontSize: 20,
|
|
|
|
|
+ color: Colors.ORANGE,
|
|
|
|
|
+ marginBottom: 8,
|
|
|
|
|
+ textAlign: 'center',
|
|
|
|
|
+ textTransform: 'uppercase',
|
|
|
|
|
+ fontFamily: 'montserrat-700'
|
|
|
|
|
+ },
|
|
|
|
|
+ subtitle: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: '600',
|
|
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
|
|
+ textAlign: 'center',
|
|
|
|
|
+ marginBottom: 20,
|
|
|
|
|
+ },
|
|
|
|
|
+ premiumBadgeImage: {
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ alignSelf: 'center',
|
|
|
|
|
+ marginBottom: 16,
|
|
|
|
|
+ borderRadius: 6,
|
|
|
|
|
+ },
|
|
|
|
|
+ infoBox: {
|
|
|
|
|
+ backgroundColor: Colors.ORANGE,
|
|
|
|
|
+ padding: 16,
|
|
|
|
|
+ borderRadius: 6,
|
|
|
|
|
+ marginBottom: 16,
|
|
|
|
|
+ gap: 16
|
|
|
|
|
+ },
|
|
|
|
|
+ infoBoxTitle: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontFamily: 'montserrat-700',
|
|
|
|
|
+ color: Colors.WHITE,
|
|
|
|
|
+ textAlign: 'center',
|
|
|
|
|
+ },
|
|
|
|
|
+ infoBoxImage: {
|
|
|
|
|
+ alignSelf: 'center',
|
|
|
|
|
+ marginBottom: 8,
|
|
|
|
|
+ },
|
|
|
|
|
+ infoBoxText: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: '700',
|
|
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
|
|
+ textAlign: 'center',
|
|
|
|
|
+ },
|
|
|
|
|
+ productsContainer: {
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ paddingHorizontal: 16,
|
|
|
|
|
+ paddingTop: 8,
|
|
|
|
|
+ marginBottom: 16,
|
|
|
|
|
+ gap: 8,
|
|
|
|
|
+ },
|
|
|
|
|
+ productCard: {
|
|
|
|
|
+ flexDirection: 'row',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ backgroundColor: Colors.WHITE,
|
|
|
|
|
+ borderWidth: 2,
|
|
|
|
|
+ borderRadius: 12,
|
|
|
|
|
+ padding: 12,
|
|
|
|
|
+ },
|
|
|
|
|
+ productCardSelected: {
|
|
|
|
|
+ borderColor: Colors.ORANGE,
|
|
|
|
|
+ },
|
|
|
|
|
+ productCardUnselected: {
|
|
|
|
|
+ borderColor: Colors.FILL_LIGHT,
|
|
|
|
|
+ },
|
|
|
|
|
+ productInfo: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ },
|
|
|
|
|
+ productTitle: {
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontFamily: 'montserrat-700',
|
|
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
|
|
+ marginBottom: 4,
|
|
|
|
|
+ },
|
|
|
|
|
+ productDesc: {
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
|
|
+ },
|
|
|
|
|
+ priceContainer: {
|
|
|
|
|
+ marginLeft: 15,
|
|
|
|
|
+ },
|
|
|
|
|
+ productPrice: {
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ color: Colors.ORANGE,
|
|
|
|
|
+ },
|
|
|
|
|
+ joinButton: {
|
|
|
|
|
+ backgroundColor: Colors.ORANGE,
|
|
|
|
|
+ padding: 16,
|
|
|
|
|
+ borderRadius: 8,
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ marginHorizontal: 16,
|
|
|
|
|
+ },
|
|
|
|
|
+ joinButtonText: {
|
|
|
|
|
+ color: Colors.WHITE,
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontFamily: 'montserrat-700',
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ loadingOverlay: {
|
|
|
|
|
+ ...StyleSheet.absoluteFillObject,
|
|
|
|
|
+ backgroundColor: 'rgba(0,0,0,0.5)',
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ },
|
|
|
|
|
+ premiumBadge: {
|
|
|
|
|
+ padding: 15,
|
|
|
|
|
+ marginBottom: 20,
|
|
|
|
|
+ },
|
|
|
|
|
+ premiumText: {
|
|
|
|
|
+ color: Colors.DARK_BLUE,
|
|
|
|
|
+ textAlign: 'center',
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ emptyContainer: {
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ padding: 20,
|
|
|
|
|
+ },
|
|
|
|
|
+ emptyText: {
|
|
|
|
|
+ color: Colors.TEXT_GRAY,
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+export default PremiumPaywallScreen;
|