import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Image, Switch, Platform, ScrollView } from 'react-native'; import { storage } from '../../utils/storage'; import * as LocalAuthentication from 'expo-local-authentication'; import * as ImagePicker from 'expo-image-picker'; import { Feather } from '@expo/vector-icons'; import { useAuth } from '../../context/AuthContext'; import { useAppTheme } from '../../context/ThemeContext'; import { useToast } from '../../context/ToastContext'; import { useAppConfig } from '../../context/ConfigContext'; import { AppScreen } from '../../components/AppScreen'; import { AIButton, AIInput, AISectionHeader, AIPressable, AISkeleton } from '../../components/UI'; import { ApiService } from '../../services/api'; import { Popup } from '../../components/Popup'; import { DebugLogger } from '../../utils/logger'; import { AISuccess } from '../../components/UI'; import { useTranslation } from '../../context/LanguageContext'; import * as Haptics from 'expo-haptics'; import { ActionTracker } from '../../utils/actionTracker'; export default function ProfileScreen() { const { user, signOut, syncUser } = useAuth(); const { colors, isDark, setMode } = useAppTheme(); const { showToast } = useToast(); const { config } = useAppConfig(); const { t } = useTranslation(); const [loading, setLoading] = useState(true); const [editModalVisible, setEditModalVisible] = useState(false); const [logoutConfirmVisible, setLogoutConfirmVisible] = useState(false); const [tempName, setTempName] = useState(user?.name || config?.branding?.app_name || 'User'); const [tempAvatar, setTempAvatar] = useState(user?.avatar || config?.branding?.logo_url || `https://i.pravatar.cc/150?u=1`); const [debugClicks, setDebugClicks] = useState(0); const [logsModalVisible, setLogsModalVisible] = useState(false); const [logs, setLogs] = useState([]); const [updateSuccess, setUpdateSuccess] = useState(false); useEffect(() => { // Track engagement for review prompt ActionTracker.trackAction( config?.features?.min_actions_before_review, config?.features?.review_prompt_enabled ); if (user) { setTempName(user.name); if (user.avatar) { // Append timestamp to remote URL to bypass cache const cacheBuster = user.avatar.includes('?') ? `&t=${Date.now()}` : `?t=${Date.now()}`; setTempAvatar(`${user.avatar}${cacheBuster}`); } } const timer = setTimeout(() => setLoading(false), 1200); return () => clearTimeout(timer); }, [user]); const toggleTheme = () => { const next = !isDark; setMode(next ? 'dark' : 'light'); showToast(`${next ? 'Dark' : 'Light'} mode active`, 'info'); }; const handleLogout = () => { setLogoutConfirmVisible(false); showToast(t('logoutSafe'), 'info'); setTimeout(signOut, 1000); }; const handlePickImage = async () => { try { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== 'granted') { showToast('Permission to access gallery is required', 'error'); return; } const res = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, aspect: [1, 1], quality: 0.4, }); if (!res.canceled && res.assets[0].uri) { setLoading(true); showToast(t('uploadingAvatar'), 'info'); await ApiService.updateAvatar(res.assets[0].uri); await syncUser(); // Refresh global auth state if (res.assets[0].uri) setTempAvatar(res.assets[0].uri); showToast(t('avatarUpdated'), 'success'); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } } catch (error: any) { const errorMsg = error.message || t('uploadFailed') || 'Upload failed'; showToast(`Error: ${errorMsg}`, 'error'); console.error('[AvatarUpload]', error); DebugLogger.log(`Avatar upload error: ${errorMsg}`, 'error'); } finally { setLoading(false); } }; const handleUpdateProfile = async () => { if (!tempName.trim()) { showToast('Name cannot be empty', 'error'); return; } setLoading(true); try { await ApiService.updateProfile(tempName, user?.email || ''); await syncUser(); // Refresh global data setUpdateSuccess(true); showToast(t('profileUpdated'), 'success'); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } catch (error: any) { showToast(error.message || t('updateFailed') || 'Update failed', 'error'); } finally { setLoading(false); } }; const cardBg = colors.surface; const border = colors.border; const subText = colors.textSecondary; const renderSkeleton = () => ( ); if (loading) return {renderSkeleton()}; return ( {/* ── Profile header ── */} {tempName} {user?.email || `user@${config?.branding?.app_name || 'biiproject'}.com`} { setUpdateSuccess(false); setEditModalVisible(true); }} > {t('editProfile')} {/* ── Settings section ── */} } border={border} /> { const url = config?.support_social?.privacy_policy_url || 'https://biiproject.com/privacy'; require('react-native').Linking.openURL(url); }} border={border} /> { const url = config?.support_social?.privacy_policy_url || 'https://biiproject.com/terms'; require('react-native').Linking.openURL(url); }} border={border} isLast /> {/* ── Logout ── */} setLogoutConfirmVisible(true)} style={styles.logoutPressable}> {t('logout')} {/* ── App Version (Hidden Debug Trigger) ── */} { const next = debugClicks + 1; if (next >= 5) { setLogs(DebugLogger.getLogs()); setLogsModalVisible(true); setDebugClicks(0); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } else { setDebugClicks(next); } }} style={styles.versionContainer} > Version {config?.app_updates?.app_version || '2.0.0'} (Build 102) {/* ── Edit Profile Popup ── */} { setEditModalVisible(false); setUpdateSuccess(false); }} title={t('editProfile')} type="bottom"> {updateSuccess ? ( {t('profileUpdated')} { setEditModalVisible(false); setUpdateSuccess(false); }} style={{ width: '100%', marginTop: 20 }} /> ) : ( <> )} {/* ── Debug Logs Popup ── */} setLogsModalVisible(false)} title="System Logs" type="bottom"> {logs.length === 0 ? ( No logs recorded yet. ) : ( logs.map((log, i) => ( {log} )) )} { DebugLogger.clear(); setLogs([]); }} style={{ marginTop: 20 }} /> {/* ── Logout Confirm Popup ── */} setLogoutConfirmVisible(false)} title={t('logout')} type="center"> {t('confirmLogout')} setLogoutConfirmVisible(false)} style={{ flex: 1 }} textStyle={{ color: colors.text }} /> ); } function BiometricToggle({ t }: { t: any }) { const { colors, isDark } = useAppTheme(); const { showToast } = useToast(); const [enabled, setEnabled] = useState(false); useEffect(() => { storage.get('pref_biometrics').then(v => setEnabled(v === 'true')); }, []); const toggle = async () => { if (Platform.OS === 'web') { showToast('Biometrics not available in browser', 'info'); return; } const res = await LocalAuthentication.authenticateAsync({ promptMessage: 'Verify identity' }); if (res.success) { const next = !enabled; setEnabled(next); await storage.save('pref_biometrics', next ? 'true' : 'false'); showToast(`Biometrics ${next ? 'enabled' : 'disabled'}`, 'success'); } }; const border = colors.border; return ( } border={border} /> ); } function MenuRow({ icon, label, rightContent, onPress, border, isLast }: any) { const { colors, isDark } = useAppTheme(); const Wrapper: any = onPress ? TouchableOpacity : View; return ( {label} {rightContent || } ); } const styles = StyleSheet.create({ header: { alignItems: 'center', paddingTop: 20, paddingBottom: 28, paddingHorizontal: 24 }, avatarWrap: { position: 'relative', marginBottom: 16 }, avatar: { width: 100, height: 100, borderRadius: 50, borderWidth: 3 }, camBtn: { position: 'absolute', bottom: 4, right: 4, width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', elevation: 4 }, name: { fontSize: 26, fontFamily: 'Outfit_800ExtraBold', letterSpacing: -0.5 }, email: { fontSize: 13, fontFamily: 'Outfit_400Regular', marginTop: 4 }, editPill: { flexDirection: 'row', alignItems: 'center', marginTop: 20, paddingHorizontal: 20, paddingVertical: 10, borderRadius: 14, borderWidth: 1, gap: 8 }, editPillText: { fontSize: 13, fontFamily: 'Outfit_700Bold' }, menuCard: { marginHorizontal: 24, borderRadius: 24, borderWidth: 1, overflow: 'hidden', marginBottom: 16 }, menuRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingVertical: 16 }, menuLeft: { flexDirection: 'row', alignItems: 'center', gap: 14 }, menuIconBox: { width: 36, height: 36, borderRadius: 10, alignItems: 'center', justifyContent: 'center' }, menuLabel: { fontSize: 15, fontFamily: 'Outfit_600SemiBold' }, logoutPressable: { marginHorizontal: 24, marginTop: 12 }, logoutBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 16, borderRadius: 20, borderWidth: 1.5, borderStyle: 'dashed', gap: 10 }, logoutText: { fontSize: 15, fontFamily: 'Outfit_700Bold' }, popupBody: { paddingTop: 10 }, logoutIcon: { width: 80, height: 80, borderRadius: 24, alignItems: 'center', justifyContent: 'center', marginBottom: 16 }, confirmDesc: { fontSize: 15, fontFamily: 'Outfit_400Regular', textAlign: 'center', marginBottom: 28 }, confirmRow: { flexDirection: 'row', gap: 12, width: '100%' }, successText: { fontSize: 18, fontFamily: 'Outfit_700Bold', marginTop: 12 }, versionContainer: { alignItems: 'center', marginTop: 30, paddingVertical: 10 }, versionText: { fontSize: 11, fontFamily: 'Outfit_500Medium', opacity: 0.6 }, logRow: { paddingVertical: 10, borderBottomWidth: 1 }, logText: { fontSize: 12, fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace' }, });