import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, Platform, useWindowDimensions, TextInput, Modal, ActivityIndicator, Linking, Switch } from "react-native"; import { useState, useMemo, useEffect } from "react"; import { useAuthStore, useMatchStore, useMessageStore, usePacksStore, useSubscriptionStore } from "../../src/store"; import { Ionicons } from "@expo/vector-icons"; import { LinearGradient } from "expo-linear-gradient"; import Animated, { FadeInDown } from "react-native-reanimated"; import { supabase } from "../../src/lib/supabase"; import { resetSwipeTutorial } from "../../src/lib/swipeTutorialSeen"; import { isBiometricAvailable, isBiometricEnabled, setBiometricEnabled, getBiometricType, } from "../../src/lib/biometricAuth"; import { router } from "expo-router"; import { GradientBackground, GlassCard } from "../../src/components/ui"; import { FeedbackModal } from "../../src/components/FeedbackModal"; import { Paywall } from "../../src/components/Paywall"; import { colors, gradients, spacing, radius, typography, shadows } from "../../src/theme"; const MAX_CONTENT_WIDTH = 500; export default function ProfileScreen() { const { user, partner, couple, signOut, fetchCouple, fetchUser } = useAuthStore(); const { clearMatches } = useMatchStore(); const { clearMessages } = useMessageStore(); const { clearPacks, fetchPacks } = usePacksStore(); const { subscription, restorePurchases, isPurchasing } = useSubscriptionStore(); const { width } = useWindowDimensions(); const isWideScreen = width > MAX_CONTENT_WIDTH; const [showDeleteModal, setShowDeleteModal] = useState(false); const [deleteConfirmText, setDeleteConfirmText] = useState(""); const [isDeleting, setIsDeleting] = useState(false); const [showFeedbackModal, setShowFeedbackModal] = useState(false); const [showPaywall, setShowPaywall] = useState(false); const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true); const [isUpdatingExplicit, setIsUpdatingExplicit] = useState(false); const [biometricAvailable, setBiometricAvailable] = useState(false); const [biometricEnabled, setBiometricEnabledState] = useState(false); const [biometricType, setBiometricType] = useState("Face ID"); const [isUpdatingBiometric, setIsUpdatingBiometric] = useState(false); // Check if user or partner has premium access const hasPremiumAccess = useMemo(() => { return user?.is_premium || partner?.is_premium || subscription.isProUser; }, [user?.is_premium, partner?.is_premium, subscription.isProUser]); // Check biometric availability on mount useEffect(() => { const checkBiometric = async () => { const available = await isBiometricAvailable(); setBiometricAvailable(available); if (available) { const enabled = await isBiometricEnabled(); setBiometricEnabledState(enabled); const type = await getBiometricType(); setBiometricType(type); } }; checkBiometric(); }, []); // Check if it's the user's own subscription (vs partner's) const isOwnSubscription = user?.is_premium && subscription.isProUser; // Format expiration date const formatExpirationDate = (date: Date | null) => { if (!date) return "Never"; return date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); }; const handleRestorePurchases = async () => { const restored = await restorePurchases(); if (restored) { Alert.alert("Success", "Your purchases have been restored!"); } else { Alert.alert("No Purchases Found", "No previous purchases found to restore."); } }; const handleExplicitToggle = async (value: boolean) => { if (!user?.id) return; setShowExplicit(value); setIsUpdatingExplicit(true); try { const { error } = await supabase .from('profiles') .update({ show_explicit_content: value, updated_at: new Date().toISOString(), }) .eq('id', user.id); if (error) throw error; // Refresh user data and packs to reflect the change await fetchUser(); await fetchPacks(); } catch (error) { // Revert on error setShowExplicit(!value); Alert.alert("Error", "Failed to update preference. Please try again."); } finally { setIsUpdatingExplicit(false); } }; const handleBiometricToggle = async (value: boolean) => { setBiometricEnabledState(value); setIsUpdatingBiometric(true); try { await setBiometricEnabled(value); } catch (error) { // Revert on error setBiometricEnabledState(!value); Alert.alert("Error", `Failed to ${value ? "enable" : "disable"} ${biometricType}. Please try again.`); } finally { setIsUpdatingBiometric(false); } }; const handleManageSubscription = () => { Linking.openURL("https://apps.apple.com/account/subscriptions"); }; const handleDeleteRelationship = async () => { if (deleteConfirmText !== "DELETE") return; setIsDeleting(true); try { const { error } = await supabase.functions.invoke("delete-relationship", { method: "DELETE", }); if (error) throw error; // Clear local stores clearMatches(); clearMessages(); clearPacks(); // Refresh user data (will also clear couple/partner since couple_id is now null) await useAuthStore.getState().fetchUser(); setShowDeleteModal(false); setDeleteConfirmText(""); Alert.alert("Success", "All relationship data has been deleted."); } catch (error) { Alert.alert("Error", "Failed to delete relationship data. Please try again."); } finally { setIsDeleting(false); } }; const handleUnpair = async () => { Alert.alert( "Unpair Partner", "Are you sure you want to unpair? This cannot be undone.", [ { text: "Cancel", style: "cancel" }, { text: "Unpair", style: "destructive", onPress: async () => { try { await supabase.functions.invoke("manage-couple", { method: "DELETE", }); await fetchCouple(); } catch (error) { Alert.alert("Error", "Failed to unpair"); } }, }, ] ); }; const handleSignOut = () => { Alert.alert( "Sign Out", "Are you sure you want to sign out?", [ { text: "Cancel", style: "cancel" }, { text: "Sign Out", style: "destructive", onPress: signOut, }, ] ); }; return ( {/* Header */} Profile {/* Profile Card */} {user?.name?.[0]?.toUpperCase() || "U"} {user?.name || "User"} {user?.email} {/* Partner Section */} Partner {partner ? ( {partner.name || partner.email || 'Your partner'} Connected ) : couple ? ( router.push("/(app)/pairing")} activeOpacity={0.7} > Waiting for partner Tap to view invite code ) : ( router.push("/(app)/pairing")} activeOpacity={0.7} > Not paired yet Tap to connect )} {/* Subscription Section */} Subscription {hasPremiumAccess ? ( Pro Member {isOwnSubscription ? `Renews ${formatExpirationDate(subscription.expirationDate)}` : "Via partner's subscription" } {isOwnSubscription && ( Manage )} ) : ( setShowPaywall(true)} activeOpacity={0.7} > Free Plan Upgrade to unlock all packs Upgrade )} {/* Restore Purchases Link */} {!hasPremiumAccess && ( {isPurchasing ? "Restoring..." : "Restore Purchases"} )} {/* Preferences Section */} Preferences Explicit Content Show 18+ question packs {biometricAvailable && ( <> {biometricType} Require unlock when opening app )} {/* Account Section */} Account Sign Out {/* Support Section */} Support setShowFeedbackModal(true)} activeOpacity={0.7} > Send Feedback Report bugs or request features {/* Debug Section - Only show in development builds */} {__DEV__ && ( Debug { await resetSwipeTutorial(); Alert.alert("Success", "Swipe tutorial has been reset. It will show again next time you visit the swipe screen."); }} activeOpacity={0.7} > Reset Swipe Tutorial Show the tutorial again )} {/* Danger Zone - Only show if in a couple */} {couple && ( Danger Zone Delete All Data Permanently delete your relationship including all matches, chats, and shared photos. setShowDeleteModal(true)} activeOpacity={0.7} > Delete )} {/* Version */} Sauci v1.0.0 {/* Feedback Modal */} setShowFeedbackModal(false)} /> {/* Paywall Modal */} setShowPaywall(false)} /> {/* Delete Confirmation Modal */} !isDeleting && setShowDeleteModal(false)} > Delete All Data? This action cannot be undone. This will permanently delete: Your couple connection All matches and responses All chat messages All shared photos Type DELETE to confirm: { setShowDeleteModal(false); setDeleteConfirmText(""); }} disabled={isDeleting} activeOpacity={0.7} > Cancel {isDeleting ? ( ) : ( <> Delete )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, contentContainer: { paddingBottom: Platform.OS === 'ios' ? 100 : 80, }, contentContainerWide: { alignSelf: 'center', width: '100%', maxWidth: MAX_CONTENT_WIDTH, }, header: { paddingTop: 60, paddingHorizontal: spacing.lg, paddingBottom: spacing.lg, }, title: { ...typography.title1, color: colors.text, }, profileSection: { paddingHorizontal: spacing.lg, marginBottom: spacing.xl, }, profileContent: { alignItems: "center", paddingVertical: spacing.lg, }, avatarGradient: { width: 100, height: 100, borderRadius: 50, justifyContent: "center", alignItems: "center", ...shadows.lg, }, avatarInner: { width: 90, height: 90, borderRadius: 45, backgroundColor: colors.background, justifyContent: "center", alignItems: "center", }, avatarText: { fontSize: 36, fontWeight: "bold", color: colors.text, }, profileInfo: { alignItems: "center", marginTop: spacing.lg, }, name: { ...typography.title2, color: colors.text, marginBottom: spacing.xs, }, email: { ...typography.subhead, color: colors.textSecondary, }, section: { paddingHorizontal: spacing.lg, marginBottom: spacing.lg, }, sectionTitle: { ...typography.caption1, color: colors.textTertiary, textTransform: "uppercase", letterSpacing: 1.5, marginBottom: spacing.sm, marginLeft: spacing.xs, }, rowContainer: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", }, rowLeft: { flexDirection: "row", alignItems: "center", flex: 1, }, partnerIconGradient: { width: 44, height: 44, borderRadius: 22, justifyContent: "center", alignItems: "center", }, emptyPartnerIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: colors.glass.background, justifyContent: "center", alignItems: "center", }, rowTextContainer: { marginLeft: spacing.md, flex: 1, }, rowValue: { ...typography.body, color: colors.text, fontWeight: "600", }, rowValueMuted: { ...typography.body, color: colors.textSecondary, }, rowLabel: { ...typography.caption1, color: colors.textTertiary, marginTop: 2, }, preferencesDivider: { height: 1, backgroundColor: colors.glass.border, marginVertical: spacing.md, }, unlinkButton: { width: 36, height: 36, borderRadius: 18, backgroundColor: colors.glass.background, justifyContent: "center", alignItems: "center", }, chevronContainer: { width: 28, height: 28, borderRadius: 14, backgroundColor: colors.glass.background, justifyContent: "center", alignItems: "center", }, signOutRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", }, signOutIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: colors.errorLight, justifyContent: "center", alignItems: "center", }, signOutText: { ...typography.body, color: colors.error, fontWeight: "600", marginLeft: spacing.md, }, // Subscription styles manageButton: { paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.md, backgroundColor: colors.glass.background, borderWidth: 1, borderColor: colors.glass.border, }, manageButtonText: { ...typography.subhead, color: colors.primary, fontWeight: "600", }, upgradeButton: { paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.md, }, upgradeButtonText: { ...typography.subhead, color: colors.text, fontWeight: "600", }, restoreLink: { alignItems: "center", marginTop: spacing.sm, padding: spacing.sm, }, restoreLinkText: { ...typography.caption1, color: colors.textTertiary, }, versionContainer: { alignItems: "center", marginTop: spacing.lg, }, versionBadge: { flexDirection: "row", alignItems: "center", backgroundColor: colors.glass.background, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.full, gap: spacing.xs, }, version: { ...typography.caption1, color: colors.textTertiary, }, bottomSpacer: { height: spacing.lg, }, // Debug styles debugSectionTitle: { ...typography.caption1, color: colors.warning, textTransform: "uppercase", letterSpacing: 1.5, marginBottom: spacing.sm, marginLeft: spacing.xs, }, debugCard: { borderColor: 'rgba(243, 156, 18, 0.3)', borderWidth: 1, }, debugIcon: { width: 44, height: 44, borderRadius: 22, backgroundColor: colors.warningLight, justifyContent: "center", alignItems: "center", }, // Danger Zone styles dangerSectionTitle: { ...typography.caption1, color: colors.error, textTransform: "uppercase", letterSpacing: 1.5, marginBottom: spacing.sm, marginLeft: spacing.xs, }, dangerCard: { borderColor: 'rgba(231, 76, 60, 0.3)', borderWidth: 1, }, dangerContent: { gap: spacing.md, }, dangerInfo: { flexDirection: "row", alignItems: "flex-start", }, dangerIconContainer: { width: 44, height: 44, borderRadius: 22, backgroundColor: colors.errorLight, justifyContent: "center", alignItems: "center", }, dangerTextContainer: { flex: 1, marginLeft: spacing.md, }, dangerTitle: { ...typography.headline, color: colors.text, marginBottom: spacing.xs, }, dangerDescription: { ...typography.subhead, color: colors.textSecondary, lineHeight: 20, }, dangerButton: { flexDirection: "row", alignItems: "center", justifyContent: "center", backgroundColor: colors.errorLight, paddingVertical: spacing.sm, paddingHorizontal: spacing.md, borderRadius: radius.md, borderWidth: 1, borderColor: 'rgba(231, 76, 60, 0.3)', gap: spacing.xs, alignSelf: "flex-end", }, dangerButtonText: { ...typography.subhead, color: colors.error, fontWeight: "600", }, // Modal styles modalOverlay: { flex: 1, backgroundColor: "rgba(0, 0, 0, 0.8)", justifyContent: "center", alignItems: "center", padding: spacing.lg, }, modalContent: { backgroundColor: colors.backgroundLight, borderRadius: radius.xl, padding: spacing.lg, width: "100%", maxWidth: 400, borderWidth: 1, borderColor: colors.glass.border, }, modalHeader: { alignItems: "center", marginBottom: spacing.lg, }, modalIconContainer: { width: 64, height: 64, borderRadius: 32, backgroundColor: colors.errorLight, justifyContent: "center", alignItems: "center", marginBottom: spacing.md, }, modalTitle: { ...typography.title2, color: colors.text, textAlign: "center", }, modalDescription: { ...typography.body, color: colors.textSecondary, textAlign: "center", marginBottom: spacing.md, }, deleteList: { backgroundColor: colors.glass.background, borderRadius: radius.md, padding: spacing.md, marginBottom: spacing.lg, gap: spacing.sm, }, deleteListItem: { flexDirection: "row", alignItems: "center", gap: spacing.sm, }, deleteListText: { ...typography.subhead, color: colors.textSecondary, }, confirmLabel: { ...typography.subhead, color: colors.textSecondary, marginBottom: spacing.sm, }, confirmKeyword: { color: colors.error, fontWeight: "bold", }, confirmInput: { backgroundColor: colors.glass.background, borderRadius: radius.md, borderWidth: 1, borderColor: colors.glass.border, padding: spacing.md, ...typography.body, color: colors.text, marginBottom: spacing.lg, }, modalButtons: { flexDirection: "row", gap: spacing.md, }, cancelButton: { flex: 1, backgroundColor: colors.glass.background, paddingVertical: spacing.md, borderRadius: radius.md, alignItems: "center", borderWidth: 1, borderColor: colors.glass.border, }, cancelButtonText: { ...typography.body, color: colors.textSecondary, fontWeight: "600", }, confirmDeleteButton: { flex: 1, backgroundColor: colors.error, paddingVertical: spacing.md, borderRadius: radius.md, alignItems: "center", flexDirection: "row", justifyContent: "center", gap: spacing.xs, }, confirmDeleteButtonDisabled: { backgroundColor: colors.glass.background, opacity: 0.5, }, confirmDeleteButtonText: { ...typography.body, color: colors.text, fontWeight: "600", }, });