- Restructured profile screens location - Updated beneficiary detail page - Updated API service - Updated all scheme files (MainScheme, ENV API, Discussion, AppStore, SysAnal) - Added PageHeader component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
563 lines
17 KiB
TypeScript
563 lines
17 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Switch,
|
|
TouchableOpacity,
|
|
Alert,
|
|
TextInput,
|
|
Modal,
|
|
} from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { router } from 'expo-router';
|
|
import { useAuth } from '@/contexts/AuthContext';
|
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
|
import { PageHeader } from '@/components/PageHeader';
|
|
|
|
interface SecurityItemProps {
|
|
icon: keyof typeof Ionicons.glyphMap;
|
|
iconColor: string;
|
|
iconBgColor: string;
|
|
title: string;
|
|
description: string;
|
|
onPress: () => void;
|
|
rightElement?: React.ReactNode;
|
|
}
|
|
|
|
function SecurityItem({
|
|
icon,
|
|
iconColor,
|
|
iconBgColor,
|
|
title,
|
|
description,
|
|
onPress,
|
|
rightElement,
|
|
}: SecurityItemProps) {
|
|
return (
|
|
<TouchableOpacity style={styles.securityRow} onPress={onPress}>
|
|
<View style={[styles.iconContainer, { backgroundColor: iconBgColor }]}>
|
|
<Ionicons name={icon} size={20} color={iconColor} />
|
|
</View>
|
|
<View style={styles.securityContent}>
|
|
<Text style={styles.securityTitle}>{title}</Text>
|
|
<Text style={styles.securityDescription}>{description}</Text>
|
|
</View>
|
|
{rightElement || <Ionicons name="chevron-forward" size={20} color={AppColors.textMuted} />}
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
export default function PrivacyScreen() {
|
|
const { logout } = useAuth();
|
|
const [twoFactor, setTwoFactor] = useState(false);
|
|
const [biometric, setBiometric] = useState(false);
|
|
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
|
const [currentPassword, setCurrentPassword] = useState('');
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
|
|
const handleChangePassword = () => {
|
|
setShowPasswordModal(true);
|
|
};
|
|
|
|
const handlePasswordSubmit = () => {
|
|
if (!currentPassword || !newPassword || !confirmPassword) {
|
|
Alert.alert('Error', 'Please fill in all fields');
|
|
return;
|
|
}
|
|
if (newPassword !== confirmPassword) {
|
|
Alert.alert('Error', 'New passwords do not match');
|
|
return;
|
|
}
|
|
if (newPassword.length < 8) {
|
|
Alert.alert('Error', 'Password must be at least 8 characters');
|
|
return;
|
|
}
|
|
|
|
setShowPasswordModal(false);
|
|
setCurrentPassword('');
|
|
setNewPassword('');
|
|
setConfirmPassword('');
|
|
|
|
Alert.alert('Success', 'Your password has been changed successfully.');
|
|
};
|
|
|
|
const handleEnable2FA = (value: boolean) => {
|
|
if (value) {
|
|
Alert.alert(
|
|
'Enable Two-Factor Authentication',
|
|
'This will add an extra layer of security to your account. You will need to enter a code from your authenticator app when signing in.',
|
|
[
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{
|
|
text: 'Enable',
|
|
onPress: () => {
|
|
setTwoFactor(true);
|
|
Alert.alert(
|
|
'Scan QR Code',
|
|
'Open your authenticator app (Google Authenticator, Authy, etc.) and scan the QR code.',
|
|
[{ text: 'Done' }]
|
|
);
|
|
}
|
|
},
|
|
]
|
|
);
|
|
} else {
|
|
Alert.alert(
|
|
'Disable Two-Factor Authentication',
|
|
'Are you sure? This will make your account less secure.',
|
|
[
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{
|
|
text: 'Disable',
|
|
style: 'destructive',
|
|
onPress: () => setTwoFactor(false)
|
|
},
|
|
]
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleManageSessions = () => {
|
|
Alert.alert(
|
|
'Active Sessions',
|
|
'You are currently signed in on:\n\n' +
|
|
'• iPhone 14 Pro (This device)\n Last active: Just now\n\n' +
|
|
'• Chrome on MacBook Pro\n Last active: 2 hours ago\n\n' +
|
|
'• Safari on iPad\n Last active: 3 days ago',
|
|
[
|
|
{ text: 'Close' },
|
|
{
|
|
text: 'Sign Out All',
|
|
style: 'destructive',
|
|
onPress: () => {
|
|
Alert.alert(
|
|
'Sign Out All Devices',
|
|
'This will sign you out of all devices including this one.',
|
|
[
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{
|
|
text: 'Sign Out All',
|
|
style: 'destructive',
|
|
onPress: async () => {
|
|
await logout();
|
|
router.replace('/(auth)/login');
|
|
}
|
|
},
|
|
]
|
|
);
|
|
}
|
|
},
|
|
]
|
|
);
|
|
};
|
|
|
|
const handleExportData = () => {
|
|
Alert.alert(
|
|
'Export Your Data',
|
|
'We will prepare a downloadable file containing all your data. This may take a few minutes.\n\nYou will receive an email when your data is ready.',
|
|
[
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{
|
|
text: 'Request Export',
|
|
onPress: () => Alert.alert('Request Sent', 'You will receive an email when your data export is ready.')
|
|
},
|
|
]
|
|
);
|
|
};
|
|
|
|
const handleDeleteAccount = () => {
|
|
Alert.alert(
|
|
'Delete Account',
|
|
'Are you absolutely sure you want to delete your account?\n\n' +
|
|
'• All your data will be permanently deleted\n' +
|
|
'• You will lose access to all beneficiary data\n' +
|
|
'• This action cannot be undone',
|
|
[
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{
|
|
text: 'Delete Account',
|
|
style: 'destructive',
|
|
onPress: () => {
|
|
Alert.alert(
|
|
'Final Confirmation',
|
|
'Type "DELETE" to confirm account deletion.',
|
|
[{ text: 'Cancel', style: 'cancel' }]
|
|
);
|
|
}
|
|
},
|
|
]
|
|
);
|
|
};
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
|
<PageHeader title="Privacy & Security" />
|
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
{/* Password & Authentication */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Authentication</Text>
|
|
<View style={styles.card}>
|
|
<SecurityItem
|
|
icon="key"
|
|
iconColor="#3B82F6"
|
|
iconBgColor="#DBEAFE"
|
|
title="Change Password"
|
|
description="Update your account password"
|
|
onPress={handleChangePassword}
|
|
/>
|
|
<View style={styles.divider} />
|
|
<SecurityItem
|
|
icon="shield-checkmark"
|
|
iconColor="#10B981"
|
|
iconBgColor="#D1FAE5"
|
|
title="Two-Factor Authentication"
|
|
description={twoFactor ? 'Enabled' : 'Not enabled'}
|
|
onPress={() => handleEnable2FA(!twoFactor)}
|
|
rightElement={
|
|
<Switch
|
|
value={twoFactor}
|
|
onValueChange={handleEnable2FA}
|
|
trackColor={{ false: '#E5E7EB', true: AppColors.primaryLight }}
|
|
thumbColor={twoFactor ? AppColors.primary : '#9CA3AF'}
|
|
/>
|
|
}
|
|
/>
|
|
<View style={styles.divider} />
|
|
<SecurityItem
|
|
icon="finger-print"
|
|
iconColor="#8B5CF6"
|
|
iconBgColor="#EDE9FE"
|
|
title="Biometric Login"
|
|
description="Face ID / Touch ID"
|
|
onPress={() => setBiometric(!biometric)}
|
|
rightElement={
|
|
<Switch
|
|
value={biometric}
|
|
onValueChange={setBiometric}
|
|
trackColor={{ false: '#E5E7EB', true: AppColors.primaryLight }}
|
|
thumbColor={biometric ? AppColors.primary : '#9CA3AF'}
|
|
/>
|
|
}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Session Management */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Sessions</Text>
|
|
<View style={styles.card}>
|
|
<SecurityItem
|
|
icon="phone-portrait"
|
|
iconColor="#F59E0B"
|
|
iconBgColor="#FEF3C7"
|
|
title="Manage Sessions"
|
|
description="View and manage active devices"
|
|
onPress={handleManageSessions}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Data & Privacy */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Data & Privacy</Text>
|
|
<View style={styles.card}>
|
|
<SecurityItem
|
|
icon="download"
|
|
iconColor="#6366F1"
|
|
iconBgColor="#E0E7FF"
|
|
title="Export Your Data"
|
|
description="Download a copy of your data"
|
|
onPress={handleExportData}
|
|
/>
|
|
<View style={styles.divider} />
|
|
<SecurityItem
|
|
icon="eye-off"
|
|
iconColor="#EC4899"
|
|
iconBgColor="#FCE7F3"
|
|
title="Data Sharing"
|
|
description="Control who can see your data"
|
|
onPress={() => Alert.alert('Data Sharing', 'Your data is only shared with authorized caregivers and healthcare providers you designate.')}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Login History */}
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>Recent Activity</Text>
|
|
<View style={styles.card}>
|
|
<View style={styles.activityItem}>
|
|
<View style={styles.activityDot} />
|
|
<View style={styles.activityContent}>
|
|
<Text style={styles.activityTitle}>Login from iPhone</Text>
|
|
<Text style={styles.activityMeta}>Today at 2:34 PM • San Francisco, CA</Text>
|
|
</View>
|
|
</View>
|
|
<View style={styles.activityItem}>
|
|
<View style={[styles.activityDot, styles.activityDotOld]} />
|
|
<View style={styles.activityContent}>
|
|
<Text style={styles.activityTitle}>Login from MacBook</Text>
|
|
<Text style={styles.activityMeta}>Yesterday at 10:15 AM • San Francisco, CA</Text>
|
|
</View>
|
|
</View>
|
|
<View style={styles.activityItem}>
|
|
<View style={[styles.activityDot, styles.activityDotOld]} />
|
|
<View style={styles.activityContent}>
|
|
<Text style={styles.activityTitle}>Password changed</Text>
|
|
<Text style={styles.activityMeta}>Dec 1, 2024 at 4:22 PM</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Danger Zone */}
|
|
<View style={styles.section}>
|
|
<Text style={[styles.sectionTitle, { color: AppColors.error }]}>Danger Zone</Text>
|
|
<View style={styles.card}>
|
|
<TouchableOpacity style={styles.dangerButton} onPress={handleDeleteAccount}>
|
|
<Ionicons name="trash" size={20} color={AppColors.error} />
|
|
<Text style={styles.dangerButtonText}>Delete Account</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
|
|
{/* Password Change Modal */}
|
|
<Modal
|
|
visible={showPasswordModal}
|
|
animationType="slide"
|
|
presentationStyle="pageSheet"
|
|
onRequestClose={() => setShowPasswordModal(false)}
|
|
>
|
|
<SafeAreaView style={styles.modalContainer}>
|
|
<View style={styles.modalHeader}>
|
|
<TouchableOpacity onPress={() => setShowPasswordModal(false)}>
|
|
<Text style={styles.cancelText}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
<Text style={styles.modalTitle}>Change Password</Text>
|
|
<TouchableOpacity onPress={handlePasswordSubmit}>
|
|
<Text style={styles.saveText}>Save</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<ScrollView style={styles.modalContent}>
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>Current Password</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={currentPassword}
|
|
onChangeText={setCurrentPassword}
|
|
placeholder="Enter current password"
|
|
placeholderTextColor={AppColors.textMuted}
|
|
secureTextEntry
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>New Password</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={newPassword}
|
|
onChangeText={setNewPassword}
|
|
placeholder="Enter new password"
|
|
placeholderTextColor={AppColors.textMuted}
|
|
secureTextEntry
|
|
/>
|
|
<Text style={styles.hint}>Must be at least 8 characters</Text>
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>Confirm New Password</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={confirmPassword}
|
|
onChangeText={setConfirmPassword}
|
|
placeholder="Confirm new password"
|
|
placeholderTextColor={AppColors.textMuted}
|
|
secureTextEntry
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.passwordRequirements}>
|
|
<Text style={styles.requirementsTitle}>Password Requirements:</Text>
|
|
<Text style={styles.requirementItem}>• At least 8 characters</Text>
|
|
<Text style={styles.requirementItem}>• Mix of letters and numbers recommended</Text>
|
|
<Text style={styles.requirementItem}>• Special characters for extra security</Text>
|
|
</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
</Modal>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: AppColors.surface,
|
|
},
|
|
section: {
|
|
marginTop: Spacing.md,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: FontSizes.sm,
|
|
fontWeight: '600',
|
|
color: AppColors.textSecondary,
|
|
paddingHorizontal: Spacing.lg,
|
|
paddingVertical: Spacing.sm,
|
|
textTransform: 'uppercase',
|
|
},
|
|
card: {
|
|
backgroundColor: AppColors.background,
|
|
},
|
|
securityRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: Spacing.md,
|
|
paddingHorizontal: Spacing.lg,
|
|
},
|
|
iconContainer: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: BorderRadius.md,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
securityContent: {
|
|
flex: 1,
|
|
marginLeft: Spacing.md,
|
|
},
|
|
securityTitle: {
|
|
fontSize: FontSizes.base,
|
|
fontWeight: '500',
|
|
color: AppColors.textPrimary,
|
|
},
|
|
securityDescription: {
|
|
fontSize: FontSizes.xs,
|
|
color: AppColors.textMuted,
|
|
marginTop: 2,
|
|
},
|
|
divider: {
|
|
height: 1,
|
|
backgroundColor: AppColors.border,
|
|
marginLeft: Spacing.lg + 40 + Spacing.md,
|
|
},
|
|
activityItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingVertical: Spacing.md,
|
|
paddingHorizontal: Spacing.lg,
|
|
},
|
|
activityDot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: AppColors.success,
|
|
marginRight: Spacing.md,
|
|
},
|
|
activityDotOld: {
|
|
backgroundColor: AppColors.textMuted,
|
|
},
|
|
activityContent: {
|
|
flex: 1,
|
|
},
|
|
activityTitle: {
|
|
fontSize: FontSizes.sm,
|
|
fontWeight: '500',
|
|
color: AppColors.textPrimary,
|
|
},
|
|
activityMeta: {
|
|
fontSize: FontSizes.xs,
|
|
color: AppColors.textMuted,
|
|
marginTop: 2,
|
|
},
|
|
dangerButton: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: Spacing.md,
|
|
},
|
|
dangerButtonText: {
|
|
fontSize: FontSizes.base,
|
|
fontWeight: '500',
|
|
color: AppColors.error,
|
|
marginLeft: Spacing.sm,
|
|
},
|
|
// Modal styles
|
|
modalContainer: {
|
|
flex: 1,
|
|
backgroundColor: AppColors.surface,
|
|
},
|
|
modalHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingHorizontal: Spacing.lg,
|
|
paddingVertical: Spacing.md,
|
|
backgroundColor: AppColors.background,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: AppColors.border,
|
|
},
|
|
modalTitle: {
|
|
fontSize: FontSizes.lg,
|
|
fontWeight: '600',
|
|
color: AppColors.textPrimary,
|
|
},
|
|
cancelText: {
|
|
fontSize: FontSizes.base,
|
|
color: AppColors.textSecondary,
|
|
},
|
|
saveText: {
|
|
fontSize: FontSizes.base,
|
|
fontWeight: '600',
|
|
color: AppColors.primary,
|
|
},
|
|
modalContent: {
|
|
padding: Spacing.lg,
|
|
},
|
|
inputGroup: {
|
|
marginBottom: Spacing.lg,
|
|
},
|
|
label: {
|
|
fontSize: FontSizes.sm,
|
|
fontWeight: '600',
|
|
color: AppColors.textPrimary,
|
|
marginBottom: Spacing.xs,
|
|
},
|
|
input: {
|
|
backgroundColor: AppColors.background,
|
|
borderRadius: BorderRadius.md,
|
|
paddingHorizontal: Spacing.md,
|
|
paddingVertical: Spacing.md,
|
|
fontSize: FontSizes.base,
|
|
color: AppColors.textPrimary,
|
|
borderWidth: 1,
|
|
borderColor: AppColors.border,
|
|
},
|
|
hint: {
|
|
fontSize: FontSizes.xs,
|
|
color: AppColors.textMuted,
|
|
marginTop: Spacing.xs,
|
|
},
|
|
passwordRequirements: {
|
|
backgroundColor: AppColors.background,
|
|
borderRadius: BorderRadius.lg,
|
|
padding: Spacing.md,
|
|
marginTop: Spacing.md,
|
|
},
|
|
requirementsTitle: {
|
|
fontSize: FontSizes.sm,
|
|
fontWeight: '600',
|
|
color: AppColors.textSecondary,
|
|
marginBottom: Spacing.sm,
|
|
},
|
|
requirementItem: {
|
|
fontSize: FontSizes.sm,
|
|
color: AppColors.textMuted,
|
|
marginBottom: 4,
|
|
},
|
|
});
|