504 lines
14 KiB
TypeScript
504 lines
14 KiB
TypeScript
/*
|
|
* File: ProfileScreen.tsx
|
|
* Description: Screen for displaying and managing user profile information, with editable name fields.
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
Image,
|
|
ScrollView,
|
|
TouchableOpacity,
|
|
Alert,
|
|
Switch,
|
|
Modal, // Import Modal
|
|
TextInput, // Import TextInput
|
|
} from 'react-native';
|
|
import Icon from 'react-native-vector-icons/Feather';
|
|
import { useProfile } from '../hooks/useProfile';
|
|
import {
|
|
Colors,
|
|
Spacing,
|
|
Typography,
|
|
Shadows,
|
|
BorderRadius,
|
|
} from '../../../../shared/src/theme';
|
|
import { Button } from '../../../../shared/src/components/Button';
|
|
import { useDispatch } from 'react-redux';
|
|
import { logout } from '../../Auth/redux/authSlice';
|
|
|
|
interface InfoRowProps {
|
|
icon: string;
|
|
label?: string;
|
|
value?: string;
|
|
onEdit?: () => void;
|
|
isNav?: boolean;
|
|
isLogout?: boolean;
|
|
}
|
|
|
|
const InfoRow: React.FC<InfoRowProps> = ({
|
|
icon,
|
|
label,
|
|
value,
|
|
onEdit,
|
|
isNav,
|
|
isLogout,
|
|
}) => (
|
|
<TouchableOpacity
|
|
style={[styles.infoCard, isLogout && styles.logoutCard]}
|
|
onPress={onEdit}
|
|
disabled={!onEdit}>
|
|
<Icon
|
|
name={icon}
|
|
size={22}
|
|
color={isLogout ? Colors.error : Colors.textSecondary}
|
|
style={styles.infoIcon}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.infoValue,
|
|
!value && styles.infoLabel,
|
|
isLogout && styles.logoutText,
|
|
]}>
|
|
{value || label}
|
|
</Text>
|
|
{onEdit && !isNav && !isLogout && (
|
|
<Icon name="edit-2" size={20} color={Colors.textMuted} />
|
|
)}
|
|
{isNav && (
|
|
<Icon name="chevron-right" size={22} color={Colors.textMuted} />
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
|
|
const SectionHeader: React.FC<{ title: string; icon?: string }> = ({ title, icon }) => (
|
|
<View style={styles.sectionHeader}>
|
|
{icon && <Icon name={icon} size={18} color={Colors.primary} style={{ marginRight: 8 }} />}
|
|
<Text style={styles.sectionHeaderText}>{title}</Text>
|
|
</View>
|
|
);
|
|
|
|
// New component for individual notification toggles
|
|
const NotificationToggle: React.FC<{
|
|
label: string;
|
|
isEnabled: boolean;
|
|
onToggle: (value: boolean) => void;
|
|
}> = ({ label, isEnabled, onToggle }) => (
|
|
<View style={styles.notificationRow}>
|
|
<Text style={styles.notificationLabel}>{label}</Text>
|
|
<Switch
|
|
value={isEnabled}
|
|
onValueChange={onToggle}
|
|
trackColor={{ false: Colors.inactiveState, true: Colors.success }}
|
|
thumbColor={Colors.background}
|
|
/>
|
|
</View>
|
|
);
|
|
|
|
const ProfileScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
|
const initialUser = useProfile() || {
|
|
user_id: '9fe5cd7f-e563-4c7f-93ec-6de64e12b83e',
|
|
email: 'radiologist@gmail.com',
|
|
first_name: 'Aman',
|
|
last_name: 'Kumar',
|
|
display_name: 'Aman Kumar',
|
|
dashboard_role: 'radiologist',
|
|
hospital_id: '0240c6d9-415f-49c9-ae24-7eba4927f939',
|
|
dashboard_settings: {
|
|
theme: 'light',
|
|
language: 'en',
|
|
timezone: 'UTC',
|
|
},
|
|
notification_preferences: {
|
|
critical_alerts: {
|
|
sms: true,
|
|
push: true,
|
|
email: true,
|
|
in_app: true,
|
|
},
|
|
system_notifications: {
|
|
push: true,
|
|
email: true,
|
|
in_app: true,
|
|
},
|
|
},
|
|
onboarding_completed: false,
|
|
onboarding_step: 0,
|
|
onboarding_message: 'You need to complete onboarding (change password and upload profile photo).',
|
|
onboarded: false,
|
|
profile_photo_url: null,
|
|
theme_color: null,
|
|
accent_color: null,
|
|
};
|
|
|
|
// State for user data, notifications, and the edit modal
|
|
const [user, setUser] = useState(initialUser);
|
|
const [notifications, setNotifications] = useState(user.notification_preferences);
|
|
const [isModalVisible, setModalVisible] = useState(false);
|
|
const [editingField, setEditingField] = useState('');
|
|
const [currentValue, setCurrentValue] = useState('');
|
|
const dispatch=useDispatch();
|
|
|
|
const handleLogout = () => {
|
|
Alert.alert('Log Out', 'Are you sure you want to log out?', [
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
{ text: 'OK', onPress: () => dispatch(logout()) },
|
|
]);
|
|
};
|
|
|
|
// Updated handleEdit to open the modal
|
|
const handleEdit = (field: string, value: string) => {
|
|
setEditingField(field);
|
|
setCurrentValue(value);
|
|
setModalVisible(true);
|
|
};
|
|
|
|
// New function to save changes from the modal
|
|
const handleSave = () => {
|
|
const updatedUser = { ...user };
|
|
if (editingField === 'First Name') {
|
|
updatedUser.first_name = currentValue;
|
|
} else if (editingField === 'Last Name') {
|
|
updatedUser.last_name = currentValue;
|
|
}
|
|
updatedUser.display_name = `${updatedUser.first_name} ${updatedUser.last_name}`;
|
|
setUser(updatedUser);
|
|
setModalVisible(false);
|
|
Alert.alert('Profile Updated', `${editingField} has been changed.`);
|
|
};
|
|
|
|
const handleNotificationToggle = (
|
|
category: 'critical_alerts' | 'system_notifications',
|
|
channel: string,
|
|
) => {
|
|
const newPrefs = { ...notifications };
|
|
// Asserting the channel type to fix TypeScript indexing error
|
|
(newPrefs[category] as any)[channel] = !(newPrefs[category] as any)[channel];
|
|
setNotifications(newPrefs);
|
|
// In a real app, you would dispatch an action here to save the new preferences.
|
|
Alert.alert(
|
|
'Preference Updated',
|
|
`${channel.toUpperCase()} for ${category.replace('_', ' ')} has been ${
|
|
(newPrefs[category] as any)[channel] ? 'enabled' : 'disabled'
|
|
}.`,
|
|
);
|
|
};
|
|
|
|
return (
|
|
<ScrollView
|
|
style={styles.container}
|
|
contentContainerStyle={styles.contentContainer}
|
|
showsVerticalScrollIndicator={false}>
|
|
{/* Profile Section */}
|
|
<View style={styles.header}>
|
|
<View style={styles.avatarContainer}>
|
|
<Image
|
|
source={{ uri: user.profile_photo_url || 'https://ui-avatars.com/api/?name=' + encodeURIComponent(user.display_name) }}
|
|
style={styles.avatar}
|
|
/>
|
|
<TouchableOpacity
|
|
style={styles.editIconContainer}
|
|
// onPress={() =>
|
|
// handleEdit('Profile Photo', user.profile_photo_url || '')
|
|
// }
|
|
>
|
|
<Icon name="edit-2" size={16} color={Colors.background} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
<Text style={styles.name}>{user.display_name}</Text>
|
|
<Text style={styles.role}>{user.dashboard_role}</Text>
|
|
<Text style={styles.email}>{user.email}</Text>
|
|
</View>
|
|
|
|
{/* Onboarding Section */}
|
|
{!user.onboarded && (
|
|
<View style={styles.onboardingSection}>
|
|
<SectionHeader title="Onboarding" icon="alert-circle" />
|
|
<Text style={styles.onboardingMessage}>{user.onboarding_message}</Text>
|
|
</View>
|
|
)}
|
|
|
|
{/* Personal Information Section - New */}
|
|
<View style={styles.infoSection}>
|
|
<SectionHeader title="Personal Information" icon="user" />
|
|
<InfoRow
|
|
icon="user"
|
|
label="First Name"
|
|
value={user.first_name}
|
|
onEdit={() => handleEdit('First Name', user.first_name)}
|
|
/>
|
|
<InfoRow
|
|
icon="user"
|
|
label="Last Name"
|
|
value={user.last_name}
|
|
onEdit={() => handleEdit('Last Name', user.last_name)}
|
|
/>
|
|
</View>
|
|
|
|
{/* Settings Section */}
|
|
{/* <View style={styles.infoSection}>
|
|
<SectionHeader title="Settings" icon="settings" />
|
|
<InfoRow icon="sun" label="Theme" value={user.dashboard_settings?.theme || '-'} />
|
|
<InfoRow icon="globe" label="Language" value={user.dashboard_settings?.language || '-'} />
|
|
<InfoRow icon="clock" label="Timezone" value={user.dashboard_settings?.timezone || '-'} />
|
|
</View> */}
|
|
|
|
{/* Notification Preferences Section - Updated with Toggles */}
|
|
<View style={styles.infoSection}>
|
|
<SectionHeader title="Notification Preferences" icon="bell" />
|
|
<View style={styles.notificationGroup}>
|
|
<Text style={styles.notificationCategory}>Critical Alerts</Text>
|
|
{Object.entries(notifications.critical_alerts).map(([channel, isEnabled]) => (
|
|
<NotificationToggle
|
|
key={`critical-${channel}`}
|
|
label={channel.toUpperCase()}
|
|
isEnabled={isEnabled}
|
|
onToggle={() => handleNotificationToggle('critical_alerts', channel)}
|
|
/>
|
|
))}
|
|
</View>
|
|
<View style={styles.notificationGroup}>
|
|
<Text style={styles.notificationCategory}>System Notifications</Text>
|
|
{Object.entries(notifications.system_notifications).map(
|
|
([channel, isEnabled]) => (
|
|
<NotificationToggle
|
|
key={`system-${channel}`}
|
|
label={channel.toUpperCase()}
|
|
isEnabled={isEnabled}
|
|
onToggle={() => handleNotificationToggle('system_notifications', channel)}
|
|
/>
|
|
),
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Actions Section */}
|
|
<View style={styles.actionsSection}>
|
|
<InfoRow
|
|
icon="help-circle"
|
|
label="Support"
|
|
isNav
|
|
onEdit={() => navigation.navigate('Support')}
|
|
/>
|
|
<InfoRow icon="log-out" label="Log Out" isLogout onEdit={handleLogout} />
|
|
</View>
|
|
|
|
{/* Edit Modal */}
|
|
<Modal
|
|
transparent={true}
|
|
visible={isModalVisible}
|
|
onRequestClose={() => setModalVisible(false)}>
|
|
<View style={styles.modalContainer}>
|
|
<View style={styles.modalContent}>
|
|
<Text style={styles.modalTitle}>Edit {editingField}</Text>
|
|
<TextInput
|
|
style={styles.modalInput}
|
|
value={currentValue}
|
|
onChangeText={setCurrentValue}
|
|
autoFocus
|
|
/>
|
|
<View style={styles.modalActions}>
|
|
<Button title="Cancel" onPress={() => setModalVisible(false)} style={styles.modalButton} />
|
|
<Button title="Save" onPress={handleSave} style={styles.modalButton} />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</Modal>
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: Colors.backgroundAlt,
|
|
},
|
|
contentContainer: {
|
|
paddingBottom: Spacing.xl,
|
|
},
|
|
header: {
|
|
alignItems: 'center',
|
|
paddingVertical: Spacing.xl,
|
|
paddingHorizontal: Spacing.lg,
|
|
},
|
|
avatarContainer: {
|
|
position: 'relative',
|
|
marginBottom: Spacing.md,
|
|
},
|
|
avatar: {
|
|
width: 100,
|
|
height: 100,
|
|
borderRadius: 50,
|
|
borderWidth: 3,
|
|
borderColor: Colors.primary,
|
|
},
|
|
editIconContainer: {
|
|
position: 'absolute',
|
|
bottom: 2,
|
|
right: 2,
|
|
backgroundColor: Colors.primary,
|
|
padding: Spacing.xs,
|
|
borderRadius: 15,
|
|
borderWidth: 2,
|
|
borderColor: Colors.background,
|
|
},
|
|
name: {
|
|
fontFamily: Typography.fontFamily.bold,
|
|
fontSize: Typography.fontSize.xl,
|
|
color: Colors.textPrimary,
|
|
textAlign: 'center',
|
|
},
|
|
role: {
|
|
fontFamily: Typography.fontFamily.regular,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.textSecondary,
|
|
textAlign: 'center',
|
|
marginBottom: Spacing.xs,
|
|
},
|
|
email: {
|
|
fontFamily: Typography.fontFamily.regular,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.textMuted,
|
|
textAlign: 'center',
|
|
marginBottom: Spacing.md,
|
|
},
|
|
onboardingSection: {
|
|
marginHorizontal: Spacing.lg,
|
|
marginBottom: Spacing.md,
|
|
backgroundColor: Colors.warning + '22',
|
|
borderRadius: BorderRadius.md,
|
|
padding: Spacing.md,
|
|
},
|
|
onboardingMessage: {
|
|
color: Colors.warning,
|
|
fontFamily: Typography.fontFamily.regular,
|
|
fontSize: Typography.fontSize.sm,
|
|
textAlign: 'center',
|
|
},
|
|
sectionHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: Spacing.xs,
|
|
marginTop: Spacing.md,
|
|
},
|
|
sectionHeaderText: {
|
|
fontFamily: Typography.fontFamily.bold,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.primary,
|
|
},
|
|
infoSection: {
|
|
marginHorizontal: Spacing.lg,
|
|
},
|
|
actionsSection: {
|
|
marginHorizontal: Spacing.lg,
|
|
marginTop: Spacing.lg,
|
|
borderTopWidth: 1,
|
|
borderTopColor: Colors.border,
|
|
paddingTop: Spacing.lg,
|
|
},
|
|
infoCard: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: Colors.cardBackground,
|
|
padding: Spacing.md,
|
|
borderRadius: BorderRadius.md,
|
|
marginBottom: Spacing.md,
|
|
...Shadows.sm,
|
|
},
|
|
infoIcon: {
|
|
marginRight: Spacing.md,
|
|
},
|
|
infoLabel: {
|
|
flex: 1,
|
|
fontFamily: Typography.fontFamily.bold,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.textPrimary,
|
|
},
|
|
infoValue: {
|
|
flex: 1,
|
|
fontFamily: Typography.fontFamily.regular,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.textSecondary,
|
|
},
|
|
logoutCard: {
|
|
backgroundColor: 'transparent',
|
|
...Shadows.none,
|
|
},
|
|
logoutText: {
|
|
color: Colors.error,
|
|
fontFamily: Typography.fontFamily.bold,
|
|
},
|
|
notificationGroup: {
|
|
backgroundColor: Colors.cardBackground,
|
|
borderRadius: BorderRadius.md,
|
|
padding: Spacing.md,
|
|
marginBottom: Spacing.md,
|
|
...Shadows.sm,
|
|
},
|
|
notificationCategory: {
|
|
fontFamily: Typography.fontFamily.bold,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.textPrimary,
|
|
marginBottom: Spacing.sm,
|
|
},
|
|
notificationRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingVertical: Spacing.sm,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: Colors.border,
|
|
},
|
|
notificationLabel: {
|
|
fontFamily: Typography.fontFamily.regular,
|
|
fontSize: Typography.fontSize.md,
|
|
color: Colors.textSecondary,
|
|
},
|
|
modalContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
},
|
|
modalContent: {
|
|
width: '80%',
|
|
backgroundColor: Colors.cardBackground,
|
|
borderRadius: BorderRadius.md,
|
|
padding: Spacing.lg,
|
|
...Shadows.card,
|
|
},
|
|
modalTitle: {
|
|
fontFamily: Typography.fontFamily.bold,
|
|
fontSize: Typography.fontSize.lg,
|
|
color: Colors.textPrimary,
|
|
marginBottom: Spacing.md,
|
|
},
|
|
modalInput: {
|
|
borderWidth: 1,
|
|
borderColor: Colors.border,
|
|
borderRadius: BorderRadius.sm,
|
|
padding: Spacing.sm,
|
|
marginBottom: Spacing.lg,
|
|
fontFamily: Typography.fontFamily.regular,
|
|
fontSize: Typography.fontSize.md,
|
|
},
|
|
modalActions: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'flex-end',
|
|
},
|
|
modalButton: {
|
|
marginLeft: Spacing.sm,
|
|
},
|
|
});
|
|
|
|
export default ProfileScreen;
|
|
|
|
/*
|
|
* End of File: ProfileScreen.tsx
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|