import { useBalance } from '@/hooks/use-balance'; import { usePricing } from '@/hooks/use-pricing'; import Ionicons from '@expo/vector-icons/Ionicons'; import { useRouter } from 'expo-router'; import React, { useCallback, useMemo, useState } from 'react'; import { ActivityIndicator, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Alert } from '@/utils/alert'; type PurchaseTab = 'subscription' | 'pack'; type PointsBundle = { id: string; points: number; }; const screenPalette = { background: '#050505', surface: '#101010', surfaceRaised: '#1A1B1E', primaryText: '#F5F5F5', secondaryText: '#7F7F7F', mutedText: '#4C4C4C', accent: '#FEB840', energyHalo: 'rgba(254, 184, 64, 0.22)', divider: '#1C1C1C', button: '#D1FE17', buttonText: '#101010', }; const SUBSCRIPTION_TAGLINE = 'No active subscription plans'; const TABS: { key: PurchaseTab; label: string }[] = [ { key: 'subscription', label: 'Subscription' }, { key: 'pack', label: 'points pack' }, ]; const POINT_BUNDLES: PointsBundle[] = [ { id: 'bundle_500', points: 500 }, { id: 'bundle_1000', points: 1000 }, { id: 'bundle_2000', points: 2000 }, { id: 'bundle_5000', points: 5000 }, ]; export default function PointsExchangeScreen() { const router = useRouter(); const insets = useSafeAreaInsets(); const [activeTab, setActiveTab] = useState('subscription'); const [selectedBundleId, setSelectedBundleId] = useState(null); const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0); const [customAmount, setCustomAmount] = useState('500'); const { balance, isLoading: isBalanceLoading, refresh } = useBalance(); const { stripePricingData, isStripePricingLoading, stripePricingError, creditPlans, hasActiveSubscription, hasCanceledButActiveSubscription, currentSubscriptionCredits, activeAuthSubscription, handleSubscriptionAction, formatCredits, createSubscriptionPending, upgradeSubscriptionPending, restoreSubscriptionPending, rechargeToken, rechargeTokenPending, } = usePricing(); const visibleBundles = useMemo( () => (activeTab === 'pack' ? POINT_BUNDLES : []), [activeTab], ); const changeTab = useCallback((nextTab: PurchaseTab) => { setActiveTab(nextTab); if (nextTab !== 'pack') { setSelectedBundleId(null); } }, []); const handleClose = useCallback(() => { router.back(); }, [router]); const openPointsDetails = useCallback(() => { router.push('/points'); }, [router]); const handleBundleSelect = useCallback((bundleId: string) => { const bundle = POINT_BUNDLES.find(b => b.id === bundleId); if (bundle) { setSelectedBundleId(bundleId); setCustomAmount(bundle.points.toString()); } }, []); const handleCustomAmountChange = useCallback((value: string) => { setCustomAmount(value); setSelectedBundleId(null); }, []); const handleSubscriptionSelect = useCallback((index: number) => { setSelectedSubscriptionIndex(index); }, []); const handlePurchase = useCallback(() => { if (activeTab === 'subscription') { // 订阅购买 if (selectedSubscriptionIndex !== null) { handleSubscriptionAction(selectedSubscriptionIndex); } else { Alert.alert('Select a plan', 'Please select a subscription plan first.'); } } else { // 积分包购买 const amount = parseInt(customAmount, 10); if (isNaN(amount) || amount < 500) { Alert.alert('Invalid Amount', 'Please enter a valid amount (minimum 500 points).'); return; } rechargeToken(amount); } }, [ activeTab, selectedSubscriptionIndex, customAmount, handleSubscriptionAction, rechargeToken, ]); return ( {isBalanceLoading ? ( ) : ( <> {balance.remainingTokenBalance} {SUBSCRIPTION_TAGLINE} {TABS.map(tab => { const isActive = tab.key === activeTab; return ( changeTab(tab.key)} accessibilityRole="tab" accessibilityState={{ selected: isActive }} activeOpacity={0.8} > {tab.label} ); })} {activeTab === 'subscription' ? ( <> {/* 订阅套餐列表 */} {isStripePricingLoading ? ( Loading plans... ) : stripePricingError ? ( Error {stripePricingError} ) : stripePricingData?.pricing_table_items && stripePricingData.pricing_table_items.length > 0 ? ( {stripePricingData.pricing_table_items .filter((item: any) => item.recurring?.interval === 'month') .map((item: any, index: number) => { const plan = creditPlans[index]; const isSelected = selectedSubscriptionIndex === index; const priceInDollars = parseInt(item.amount) / 100; const grantToken = item.metadata?.grant_token || '0'; const isCurrentSubscription = hasActiveSubscription && activeAuthSubscription?.plan?.toLowerCase() === item.name?.toLowerCase(); const isCanceledSubscription = hasCanceledButActiveSubscription && activeAuthSubscription?.plan?.toLowerCase() === item.name?.toLowerCase(); const isHighlight = item.is_highlight || item.highlight_text === 'most_popular'; return ( handleSubscriptionSelect(index)} accessibilityRole="button" accessibilityState={{ selected: isSelected }} activeOpacity={0.88} > {/* 套餐头部 */} {item.name} {isHighlight && ( Popular )} {/* 价格 */} ${priceInDollars} /mo {/* 积分信息 */} {formatCredits(Number(grantToken))} credits per month {/* 状态徽章 */} {isCurrentSubscription && ( Current Plan )} {isCanceledSubscription && ( Canceled )} ); })} ) : ( No Plans Available No subscription tiers are available at the moment. )} ) : ( <> {/* 预设套餐 */} {visibleBundles.map(bundle => { const isSelected = bundle.id === selectedBundleId; return ( handleBundleSelect(bundle.id)} accessibilityRole="button" accessibilityState={{ selected: isSelected }} activeOpacity={0.88} > {bundle.points} ); })} {/* 自定义金额输入 */} )} )} {createSubscriptionPending || upgradeSubscriptionPending || restoreSubscriptionPending || rechargeTokenPending ? ( ) : ( {activeTab === 'subscription' ? 'Subscribe' : 'Purchase points'} )} ); } const styles = StyleSheet.create({ screen: { flex: 1, backgroundColor: screenPalette.background, }, headerRow: { paddingHorizontal: 24, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, iconButton: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', }, secondaryPill: { paddingHorizontal: 16, height: 34, borderRadius: 17, backgroundColor: screenPalette.surface, borderWidth: StyleSheet.hairlineWidth, borderColor: screenPalette.divider, justifyContent: 'center', alignItems: 'center', }, secondaryPillLabel: { fontSize: 14, fontWeight: '600', color: screenPalette.primaryText, letterSpacing: 0.2, }, content: { flex: 1, }, contentContainer: { paddingHorizontal: 24, }, balanceCluster: { alignItems: 'center', marginTop: 32, marginBottom: 36, gap: 10, }, energyOrb: { width: 58, height: 58, borderRadius: 29, backgroundColor: screenPalette.energyHalo, alignItems: 'center', justifyContent: 'center', shadowColor: screenPalette.accent, shadowOpacity: 0.28, shadowRadius: 14, shadowOffset: { width: 0, height: 8 }, elevation: 6, }, balanceValue: { fontSize: 44, fontWeight: '700', color: screenPalette.primaryText, letterSpacing: 0.5, fontVariant: ['tabular-nums'], }, balanceSubtitle: { fontSize: 13, color: screenPalette.secondaryText, letterSpacing: 0.2, }, tabBar: { flexDirection: 'row', gap: 32, paddingHorizontal: 4, }, tabButton: { flex: 1, alignItems: 'center', paddingBottom: 12, }, tabLabel: { fontSize: 14, color: screenPalette.mutedText, fontWeight: '500', letterSpacing: 0.2, textTransform: 'capitalize', }, tabLabelActive: { color: screenPalette.primaryText, }, tabIndicator: { marginTop: 10, height: 2, width: '100%', backgroundColor: 'transparent', borderRadius: 1, }, tabIndicatorActive: { backgroundColor: screenPalette.accent, }, tabDivider: { marginTop: 12, height: StyleSheet.hairlineWidth, backgroundColor: screenPalette.divider, }, packGrid: { marginTop: 28, flexDirection: 'row', flexWrap: 'wrap', gap: 18, }, packCard: { width: '47%', backgroundColor: screenPalette.surfaceRaised, borderRadius: 24, paddingHorizontal: 18, paddingVertical: 24, alignItems: 'center', justifyContent: 'center', borderWidth: 2, borderColor: 'transparent', }, packCardSelected: { borderColor: screenPalette.accent, backgroundColor: 'rgba(254, 184, 64, 0.05)', }, packHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, }, packPoints: { fontSize: 18, fontWeight: '700', color: screenPalette.primaryText, fontVariant: ['tabular-nums'], }, subscriptionEmpty: { marginTop: 64, backgroundColor: screenPalette.surface, borderRadius: 22, paddingVertical: 40, paddingHorizontal: 32, alignItems: 'center', gap: 12, }, emptyTitle: { fontSize: 16, fontWeight: '600', color: screenPalette.primaryText, }, emptySubtitle: { fontSize: 13, color: screenPalette.secondaryText, textAlign: 'center', lineHeight: 20, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', minHeight: 300, }, loadingText: { marginTop: 12, fontSize: 14, color: screenPalette.secondaryText, }, subscriptionGrid: { marginTop: 28, gap: 16, }, subscriptionCard: { backgroundColor: screenPalette.surfaceRaised, borderRadius: 20, padding: 24, borderWidth: 2, borderColor: 'transparent', position: 'relative', }, subscriptionCardSelected: { borderColor: screenPalette.accent, backgroundColor: 'rgba(254, 184, 64, 0.05)', }, subscriptionCardHighlight: { borderColor: screenPalette.button, }, subscriptionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, subscriptionName: { fontSize: 24, fontWeight: '700', color: screenPalette.primaryText, }, subscriptionPriceContainer: { flexDirection: 'row', alignItems: 'baseline', marginBottom: 20, }, subscriptionPrice: { fontSize: 40, fontWeight: '700', color: screenPalette.accent, fontVariant: ['tabular-nums'], }, subscriptionInterval: { fontSize: 16, color: screenPalette.secondaryText, marginLeft: 4, }, subscriptionCreditsBox: { backgroundColor: 'rgba(254, 184, 64, 0.1)', borderRadius: 12, padding: 16, alignItems: 'center', }, creditsRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4, }, subscriptionCreditsValue: { fontSize: 28, fontWeight: '700', color: screenPalette.accent, fontVariant: ['tabular-nums'], }, creditsLabel: { fontSize: 13, color: screenPalette.secondaryText, fontWeight: '500', }, popularBadge: { backgroundColor: screenPalette.button, paddingHorizontal: 12, paddingVertical: 6, borderRadius: 12, }, popularBadgeText: { fontSize: 12, fontWeight: '700', color: screenPalette.buttonText, }, currentBadge: { marginTop: 16, flexDirection: 'row', alignItems: 'center', gap: 6, backgroundColor: 'rgba(34, 197, 94, 0.15)', paddingHorizontal: 12, paddingVertical: 8, borderRadius: 10, alignSelf: 'flex-start', }, currentBadgeText: { fontSize: 13, fontWeight: '600', color: '#22c55e', }, canceledBadge: { marginTop: 16, flexDirection: 'row', alignItems: 'center', gap: 6, backgroundColor: 'rgba(251, 146, 60, 0.15)', paddingHorizontal: 12, paddingVertical: 8, borderRadius: 10, alignSelf: 'flex-start', }, canceledBadgeText: { fontSize: 13, fontWeight: '600', color: '#fb923c', }, customAmountContainer: { marginTop: 24, backgroundColor: screenPalette.surfaceRaised, borderRadius: 22, paddingVertical: 20, paddingHorizontal: 24, borderWidth: 2, borderColor: 'transparent', }, customAmountContainerActive: { borderColor: screenPalette.accent, backgroundColor: 'rgba(254, 184, 64, 0.05)', }, customAmountInput: { backgroundColor: screenPalette.surface, borderRadius: 12, paddingHorizontal: 16, paddingVertical: 14, fontSize: 16, color: screenPalette.primaryText, borderWidth: 1, borderColor: screenPalette.divider, }, bottomBar: { paddingHorizontal: 24, }, primaryButton: { height: 54, borderRadius: 27, backgroundColor: screenPalette.button, alignItems: 'center', justifyContent: 'center', }, primaryButtonDisabled: { opacity: 0.5, }, primaryButtonLabel: { fontSize: 16, fontWeight: '700', color: screenPalette.buttonText, letterSpacing: 0.3, }, });