320 lines
9.7 KiB
TypeScript
320 lines
9.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Text,
|
|
View,
|
|
TouchableOpacity,
|
|
ScrollView,
|
|
Switch,
|
|
Image,
|
|
Alert,
|
|
} from 'react-native';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import Icon from 'react-native-vector-icons/Ionicons';
|
|
import { getStyles } from './profile.styles';
|
|
import { useTheme } from '../../theme';
|
|
import { ProfileInfoRow } from '@components';
|
|
import { useAppDispatch, useAppSelector, logout, RootState } from '@store';
|
|
import { NotificationService } from '@services';
|
|
|
|
export const ProfileScreen = () => {
|
|
const navigation = useNavigation<any>();
|
|
const { theme: colors, isDark, toggleTheme } = useTheme();
|
|
const styles = getStyles(colors);
|
|
const dispatch = useAppDispatch();
|
|
const [pushEnabled, setPushEnabled] = useState(true);
|
|
|
|
const userData = useAppSelector((state: RootState) => state.auth.user_data);
|
|
|
|
const handleLogout = async () => {
|
|
try {
|
|
const fcmToken = await NotificationService.getFCMToken();
|
|
console.log('fcmToken', fcmToken)
|
|
await dispatch(
|
|
logout({
|
|
id: userData?.staffid ?? '',
|
|
fcm_token: fcmToken ?? '',
|
|
}),
|
|
).unwrap();
|
|
navigation.reset({
|
|
index: 0,
|
|
routes: [{ name: 'AuthStack' }],
|
|
});
|
|
} catch (e: any) {
|
|
const message =
|
|
typeof e === 'string'
|
|
? e
|
|
: e?.message || 'Logout failed. Please try again.';
|
|
Alert.alert('Logout Failed', message);
|
|
}
|
|
};
|
|
|
|
// Derive user info
|
|
const fullName =
|
|
userData?.full_name ||
|
|
`${userData?.firstname || ''} ${userData?.lastname || ''}`.trim() ||
|
|
'User Profile';
|
|
|
|
const email = userData?.email || 'N/A';
|
|
const phone = userData?.phonenumber || 'N/A';
|
|
const staffId = userData?.staffid ? `#${userData.staffid}` : 'N/A';
|
|
const isAdmin = userData?.admin === '1';
|
|
const roleText = isAdmin ? 'Administrator' : 'Staff Member';
|
|
const isActive = userData?.active === '1';
|
|
const [profileImageUrl, setProfileImageUrl] = useState('');
|
|
|
|
useEffect(() => {
|
|
const resolveImageUrl = async () => {
|
|
if (!userData?.profile_image) {
|
|
setProfileImageUrl('');
|
|
return;
|
|
}
|
|
const rawImage = userData.profile_image.replace(/"/g, '').trim();
|
|
if (!rawImage) {
|
|
setProfileImageUrl('');
|
|
return;
|
|
}
|
|
if (
|
|
rawImage.startsWith('http') ||
|
|
rawImage.startsWith('file://') ||
|
|
rawImage.startsWith('content://')
|
|
) {
|
|
setProfileImageUrl(rawImage);
|
|
return;
|
|
}
|
|
const storedBaseUrl = await AsyncStorage.getItem('base_url');
|
|
const baseUrl = storedBaseUrl || 'https://demo-convexcrm.convexsol.co';
|
|
const cleanBase = baseUrl.replace(/\/+$/, '');
|
|
const cleanPath = rawImage.replace(/^\/+/, '');
|
|
setProfileImageUrl(`${cleanBase}/${cleanPath}`);
|
|
};
|
|
resolveImageUrl();
|
|
}, [userData?.profile_image]);
|
|
|
|
const hasProfileImage = !!profileImageUrl;
|
|
|
|
// Derive initials
|
|
const initials = fullName
|
|
.split(' ')
|
|
.filter(Boolean)
|
|
.map(n => n[0])
|
|
.join('')
|
|
.substring(0, 2)
|
|
.toUpperCase() || 'U';
|
|
|
|
const hasSocials =
|
|
Boolean(userData?.linkedin) ||
|
|
Boolean(userData?.facebook) ||
|
|
Boolean(userData?.skype);
|
|
|
|
return (
|
|
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
|
{/* ── Header Card ── */}
|
|
<View style={styles.headerCard}>
|
|
<View style={styles.avatarWrapper}>
|
|
{hasProfileImage ? (
|
|
<Image
|
|
source={{ uri: profileImageUrl }}
|
|
style={styles.avatarImage}
|
|
/>
|
|
) : (
|
|
<View style={styles.avatarCircle}>
|
|
<Text style={styles.avatarInitial}>{initials}</Text>
|
|
</View>
|
|
)}
|
|
{isActive && <View style={styles.onlineDot} />}
|
|
</View>
|
|
|
|
<Text style={styles.profileName}>{fullName}</Text>
|
|
<Text style={styles.profileEmail}>{email}</Text>
|
|
|
|
<View style={styles.roleBadge}>
|
|
<Text style={styles.roleBadgeText}>{roleText}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* ── Stats Summary Row ── */}
|
|
<View style={styles.statsRow}>
|
|
<View style={styles.statBox}>
|
|
<Text style={styles.statValue}>{userData?.staffid || '—'}</Text>
|
|
<Text style={styles.statLabel}>Staff ID</Text>
|
|
</View>
|
|
<View style={styles.statBox}>
|
|
<Text style={styles.statValue}>
|
|
{userData?.total_unfinished_todos ?? '0'}
|
|
</Text>
|
|
<Text style={styles.statLabel}>Pending Todos</Text>
|
|
</View>
|
|
<View style={styles.statBox}>
|
|
<Text style={styles.statValue}>
|
|
{userData?.total_unread_notifications ?? '0'}
|
|
</Text>
|
|
<Text style={styles.statLabel}>Unread Alerts</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* ── Personal Details ── */}
|
|
<View style={styles.sectionCard}>
|
|
<Text style={styles.sectionHeader}>Personal Details</Text>
|
|
<ProfileInfoRow
|
|
icon="person-outline"
|
|
label="Name"
|
|
value={fullName}
|
|
/>
|
|
<ProfileInfoRow
|
|
icon="card-outline"
|
|
label="Staff ID"
|
|
value={staffId}
|
|
/>
|
|
<ProfileInfoRow
|
|
icon="mail-outline"
|
|
label="Email"
|
|
value={email}
|
|
/>
|
|
<ProfileInfoRow
|
|
icon="call-outline"
|
|
label="Phone"
|
|
value={phone}
|
|
/>
|
|
<ProfileInfoRow
|
|
icon="shield-checkmark-outline"
|
|
label="Role"
|
|
value={roleText}
|
|
isLast
|
|
/>
|
|
</View>
|
|
|
|
{/* ── System & Account ── */}
|
|
<View style={styles.sectionCard}>
|
|
<Text style={styles.sectionHeader}>System & Account</Text>
|
|
<ProfileInfoRow
|
|
icon="checkmark-circle-outline"
|
|
label="Account Status"
|
|
value={isActive ? 'Active' : 'Inactive'}
|
|
valueColor={isActive ? '#10B981' : '#EF4444'}
|
|
isBadge
|
|
badgeBgColor={isActive ? '#10B98120' : '#EF444420'}
|
|
/>
|
|
<ProfileInfoRow
|
|
icon="key-outline"
|
|
label="Two-Factor Auth"
|
|
value={
|
|
userData?.two_factor_auth_enabled === '1'
|
|
? 'Enabled'
|
|
: 'Disabled'
|
|
}
|
|
/>
|
|
{userData?.default_language ? (
|
|
<ProfileInfoRow
|
|
icon="language-outline"
|
|
label="Default Language"
|
|
value={userData.default_language.toUpperCase()}
|
|
/>
|
|
) : null}
|
|
<ProfileInfoRow
|
|
icon="time-outline"
|
|
label="Last Login"
|
|
value={userData?.last_login || 'N/A'}
|
|
isLast
|
|
/>
|
|
</View>
|
|
|
|
{/* ── Social Profiles ── */}
|
|
{hasSocials && (
|
|
<View style={styles.sectionCard}>
|
|
<Text style={styles.sectionHeader}>Social Profiles</Text>
|
|
{userData?.linkedin ? (
|
|
<ProfileInfoRow
|
|
icon="logo-linkedin"
|
|
label="LinkedIn"
|
|
value={userData.linkedin}
|
|
/>
|
|
) : null}
|
|
{userData?.facebook ? (
|
|
<ProfileInfoRow
|
|
icon="logo-facebook"
|
|
label="Facebook"
|
|
value={userData.facebook}
|
|
/>
|
|
) : null}
|
|
{userData?.skype ? (
|
|
<ProfileInfoRow
|
|
icon="logo-skype"
|
|
label="Skype"
|
|
value={userData.skype}
|
|
isLast
|
|
/>
|
|
) : null}
|
|
</View>
|
|
)}
|
|
|
|
{/* ── Preferences ── */}
|
|
<View style={styles.sectionCard}>
|
|
<Text style={styles.sectionHeader}>Preferences</Text>
|
|
|
|
<View style={styles.preferenceRow}>
|
|
<View style={styles.preferenceLabelGroup}>
|
|
<View style={styles.iconWrapper}>
|
|
<Icon
|
|
name={isDark ? 'moon' : 'moon-outline'}
|
|
size={18}
|
|
color={colors.icon}
|
|
/>
|
|
</View>
|
|
<Text style={styles.preferenceText}>Dark Mode</Text>
|
|
</View>
|
|
<Switch
|
|
value={isDark}
|
|
onValueChange={toggleTheme}
|
|
trackColor={{ false: '#767577', true: colors.icon }}
|
|
thumbColor={isDark ? '#ffffff' : '#f4f3f4'}
|
|
/>
|
|
</View>
|
|
|
|
{/* <TouchableOpacity style={styles.preferenceRow} activeOpacity={0.7}>
|
|
<View style={styles.preferenceLabelGroup}>
|
|
<View style={styles.iconWrapper}>
|
|
<Icon
|
|
name="notifications-outline"
|
|
size={18}
|
|
color={colors.icon}
|
|
/>
|
|
</View>
|
|
<Text style={styles.preferenceText}>Push Notifications</Text>
|
|
</View>
|
|
<Icon name="chevron-forward" size={18} color={colors.textMuted} />
|
|
</TouchableOpacity> */}
|
|
|
|
<View style={[styles.preferenceRow, styles.noBorder]}>
|
|
<View style={styles.preferenceLabelGroup}>
|
|
<View style={styles.iconWrapper}>
|
|
<Icon name="notifications-outline" size={18} color={colors.icon} />
|
|
</View>
|
|
<Text style={styles.preferenceText}>Push Notifications</Text>
|
|
</View>
|
|
<Switch
|
|
value={pushEnabled}
|
|
onValueChange={setPushEnabled}
|
|
trackColor={{ false: '#767577', true: colors.icon }}
|
|
thumbColor={pushEnabled ? '#ffffff' : '#f4f3f4'}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* ── Sign Out ── */}
|
|
<TouchableOpacity
|
|
style={styles.signOutButton}
|
|
activeOpacity={0.8}
|
|
onPress={handleLogout}>
|
|
<Icon
|
|
name="log-out-outline"
|
|
size={20}
|
|
color="#FFFFFF"
|
|
style={styles.buttonIcon}
|
|
/>
|
|
<Text style={styles.signOutButtonText}>Sign Out</Text>
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
);
|
|
};
|