import Ionicons from '@expo/vector-icons/Ionicons'; import { useRouter } from 'expo-router'; import React, { useCallback, useMemo, useState } from 'react'; import { Alert, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; type PurchaseTab = 'subscription' | 'pack'; type PointsBundle = { id: string; points: number; price: 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 CURRENT_POINTS = 60; 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, price: 35 }, { id: 'bundle-2000', points: 2000, price: 140 }, { id: 'bundle-5000', points: 5000, price: 350 }, { id: 'bundle-10000', points: 10000, price: 700 }, ]; export default function PointsExchangeScreen() { const router = useRouter(); const insets = useSafeAreaInsets(); const [activeTab, setActiveTab] = useState('pack'); const [selectedBundleId, setSelectedBundleId] = useState(null); 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) => { setSelectedBundleId(bundleId); }, []); const handlePurchase = useCallback(() => { const bundle = POINT_BUNDLES.find(item => item.id === selectedBundleId); if (!bundle) { Alert.alert('Select a bundle', 'Please pick the bundle you wish to purchase.'); return; } Alert.alert( 'Confirm purchase', `You are purchasing ${bundle.points} points for ¥ ${bundle.price}.`, ); }, [selectedBundleId]); return ( Points Details {CURRENT_POINTS} {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 === 'pack' ? ( {visibleBundles.map(bundle => { const isSelected = bundle.id === selectedBundleId; return ( handleBundleSelect(bundle.id)} accessibilityRole="button" accessibilityState={{ selected: isSelected }} activeOpacity={0.88} > {bundle.points} ¥ {bundle.price} ); })} ) : ( Subscription No subscription tiers are available at the moment. )} 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, justifyContent: 'space-between', borderWidth: 1, borderColor: screenPalette.surfaceRaised, }, packCardSelected: { borderColor: screenPalette.accent, }, packHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, }, packPoints: { fontSize: 18, fontWeight: '700', color: screenPalette.primaryText, fontVariant: ['tabular-nums'], }, packPrice: { marginTop: 18, fontSize: 14, color: screenPalette.secondaryText, }, 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, }, bottomBar: { paddingHorizontal: 24, }, primaryButton: { height: 54, borderRadius: 27, backgroundColor: screenPalette.button, alignItems: 'center', justifyContent: 'center', }, primaryButtonLabel: { fontSize: 16, fontWeight: '700', color: screenPalette.buttonText, letterSpacing: 0.3, }, });