updated code with dsc and device binding
This commit is contained in:
+191
-258
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
@@ -15,8 +14,11 @@ import {
|
||||
TextInput,
|
||||
KeyboardAvoidingView,
|
||||
Alert,
|
||||
StatusBar,
|
||||
Image,
|
||||
} from 'react-native';
|
||||
import { LinearGradient } from 'react-native-linear-gradient';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import Animated, { FadeInDown, FadeInUp, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
|
||||
import {
|
||||
AlertCircle,
|
||||
UserCircle,
|
||||
@@ -51,15 +53,18 @@ import {
|
||||
Edit,
|
||||
X,
|
||||
Save,
|
||||
ChevronRight,
|
||||
LogOut
|
||||
} from 'lucide-react-native';
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
||||
|
||||
import { useContractorProfile, useUpdateUserProfile, useESetuAdharVarification } from '../../Services/ApiCall/Hooks';
|
||||
import colors from '../../Constant/Color';
|
||||
import { useSelector } from 'react-redux';
|
||||
import ConfirmDialog from '../../Constant/Dialog/ConfirmDialog';
|
||||
import { generateTransactionId } from '../../Helper/uniqueId';
|
||||
import NavigationString from '../../Constant/NavigationString';
|
||||
import { useTheme } from '../../Hooks/useTheme';
|
||||
import AppText from '../../components/AppText';
|
||||
import ImagePath from '../../Constant/ImagePath';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
@@ -165,11 +170,14 @@ const hasSectionData = (values: any[]): boolean => {
|
||||
|
||||
// Main Component
|
||||
const ProfileTab: React.FC = () => {
|
||||
const { colors, spacing, shadows, isDarkMode } = useTheme();
|
||||
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||
const { data, refetch, isLoading, error } = useContractorProfile();
|
||||
const user = useSelector((state: any) => state.token?.token);
|
||||
console.log("User: ", user)
|
||||
const Navigation: any = useNavigation();
|
||||
const { mutateAsync: eSetuAdharVarification, isPending } = useESetuAdharVarification();
|
||||
|
||||
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
const [showDialog, setShowDialog] = useState<boolean>(false);
|
||||
const [isNavigating, setIsNavigating] = useState<boolean>(false);
|
||||
@@ -182,19 +190,19 @@ const ProfileTab: React.FC = () => {
|
||||
const displayMobile = isAuctionUser ? user?.mobileNo : data?.mobileNo;
|
||||
|
||||
const handleAadhaarPress = () => {
|
||||
if (user?.esetuVerified === 'Not Verified') {
|
||||
if (user?.esetuVerified === 'Not Verified' || user?.esetuVerified === null) {
|
||||
setShowDialog(true);
|
||||
}
|
||||
};
|
||||
|
||||
useFocusEffect(()=>{
|
||||
setIsNavigating(false)
|
||||
})
|
||||
useFocusEffect(() => {
|
||||
setIsNavigating(false)
|
||||
})
|
||||
|
||||
const handleProceedVerification = async () => {
|
||||
try {
|
||||
setIsNavigating(true);
|
||||
|
||||
|
||||
const payload = {
|
||||
contId: user.id,
|
||||
transactionId: generateTransactionId(),
|
||||
@@ -202,15 +210,15 @@ const ProfileTab: React.FC = () => {
|
||||
udf2: "77.34",
|
||||
udf3: "string",
|
||||
udf4: "string",
|
||||
responseUrl: "https://dev.procurelinc.in/"
|
||||
responseUrl: "http://dev.192.168.6.194:8090/"
|
||||
};
|
||||
|
||||
const res = await eSetuAdharVarification(payload);
|
||||
|
||||
|
||||
setShowDialog(false);
|
||||
|
||||
|
||||
Navigation.navigate(NavigationString.KycWebViewScreen, { res });
|
||||
|
||||
|
||||
} catch (err) {
|
||||
Alert.alert('Error', 'Failed to initiate verification. Please try again.');
|
||||
setIsNavigating(false);
|
||||
@@ -221,7 +229,7 @@ const ProfileTab: React.FC = () => {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
<Text style={styles.loadingText}>Loading profile...</Text>
|
||||
<AppText style={styles.loadingText}>Loading profile...</AppText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -230,9 +238,9 @@ const ProfileTab: React.FC = () => {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<AlertCircle size={64} color={colors.error} />
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={refetch}>
|
||||
<Text style={styles.retryButtonText}>Retry</Text>
|
||||
<AppText style={styles.screenErrorText}>{error instanceof Error ? error.message : String(error)}</AppText>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={() => refetch()}>
|
||||
<AppText style={styles.retryButtonText}>Retry</AppText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
@@ -243,7 +251,7 @@ const ProfileTab: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
@@ -254,11 +262,6 @@ const ProfileTab: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
showsVerticalScrollIndicator={false}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={10}
|
||||
updateCellsBatchingPeriod={50}
|
||||
initialNumToRender={8}
|
||||
windowSize={10}
|
||||
>
|
||||
{/* Header with Gradient */}
|
||||
<ProfileHeader data={data} displayName={displayName || data.fullName} />
|
||||
@@ -274,10 +277,10 @@ const ProfileTab: React.FC = () => {
|
||||
showEdit={!isAuctionUser}
|
||||
onEdit={() => setEditModalVisible(true)}
|
||||
>
|
||||
<InfoRow
|
||||
label="Full Name"
|
||||
value={isAuctionUser ? displayName : `${data.firstName} ${data.middleName} ${data.lastName}`.trim()}
|
||||
IconComponent={User}
|
||||
<InfoRow
|
||||
label="Full Name"
|
||||
value={isAuctionUser ? displayName : `${data.firstName} ${data.middleName} ${data.lastName}`.trim()}
|
||||
IconComponent={User}
|
||||
/>
|
||||
<InfoRow label="Email" value={data.emailId} IconComponent={Mail} />
|
||||
<InfoRow
|
||||
@@ -296,14 +299,14 @@ const ProfileTab: React.FC = () => {
|
||||
data.agentWebsite,
|
||||
data.agentRegno
|
||||
]) && (
|
||||
<InfoSection title="Company Details" IconComponent={Building2} delay={0}>
|
||||
<InfoRow label="Company" value={data.company} IconComponent={Building} />
|
||||
<InfoRow label="Type" value={data.contractorType} IconComponent={Tag} />
|
||||
<InfoRow label="Organization" value={data.contOrganization} IconComponent={Network} />
|
||||
<InfoRow label="Website" value={data.agentWebsite} IconComponent={Globe} />
|
||||
<InfoRow label="Registration No" value={data.agentRegno} IconComponent={Award} />
|
||||
</InfoSection>
|
||||
)}
|
||||
<InfoSection title="Company Details" IconComponent={Building2} delay={0}>
|
||||
<InfoRow label="Company" value={data.company} IconComponent={Building} />
|
||||
<InfoRow label="Type" value={data.contractorType} IconComponent={Tag} />
|
||||
<InfoRow label="Organization" value={data.contOrganization} IconComponent={Network} />
|
||||
<InfoRow label="Website" value={data.agentWebsite} IconComponent={Globe} />
|
||||
<InfoRow label="Registration No" value={data.agentRegno} IconComponent={Award} />
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{/* Address Information - Only show if has data */}
|
||||
{hasSectionData([
|
||||
@@ -312,13 +315,13 @@ const ProfileTab: React.FC = () => {
|
||||
data.agentState,
|
||||
data.agentPincode
|
||||
]) && (
|
||||
<InfoSection title="Address Information" IconComponent={MapPin} delay={0}>
|
||||
<InfoRow label="Address" value={data.agentAddress} IconComponent={Home} />
|
||||
<InfoRow label="City" value={data.agentCity} IconComponent={Building2} />
|
||||
<InfoRow label="State" value={data.agentState} IconComponent={Map} />
|
||||
<InfoRow label="Pincode" value={data.agentPincode} IconComponent={Mailbox} />
|
||||
</InfoSection>
|
||||
)}
|
||||
<InfoSection title="Address Information" IconComponent={MapPin} delay={0}>
|
||||
<InfoRow label="Address" value={data.agentAddress} IconComponent={Home} />
|
||||
<InfoRow label="City" value={data.agentCity} IconComponent={Building2} />
|
||||
<InfoRow label="State" value={data.agentState} IconComponent={Map} />
|
||||
<InfoRow label="Pincode" value={data.agentPincode} IconComponent={Mailbox} />
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{/* Tax & Legal - Only show if has data */}
|
||||
{hasSectionData([
|
||||
@@ -328,14 +331,14 @@ const ProfileTab: React.FC = () => {
|
||||
data.gistnNo,
|
||||
data.comAadharNo
|
||||
]) && (
|
||||
<InfoSection title="Tax & Legal Information" IconComponent={FileText} delay={0}>
|
||||
<InfoRow label="PAN Number" value={data.orgPAN} IconComponent={CreditCard} />
|
||||
<InfoRow label="PAN Name" value={data.orgPANname} IconComponent={Type} />
|
||||
<InfoRow label="PAN DOB" value={data.orgPANdob} IconComponent={Calendar} />
|
||||
<InfoRow label="GSTIN" value={data.gistnNo} IconComponent={Receipt} />
|
||||
<InfoRow label="Aadhar" value={data.comAadharNo} IconComponent={FileText} />
|
||||
</InfoSection>
|
||||
)}
|
||||
<InfoSection title="Tax & Legal Information" IconComponent={FileText} delay={0}>
|
||||
<InfoRow label="PAN Number" value={data.orgPAN} IconComponent={CreditCard} />
|
||||
<InfoRow label="PAN Name" value={data.orgPANname} IconComponent={Type} />
|
||||
<InfoRow label="PAN DOB" value={data.orgPANdob} IconComponent={Calendar} />
|
||||
<InfoRow label="GSTIN" value={data.gistnNo} IconComponent={Receipt} />
|
||||
<InfoRow label="Aadhar" value={data.comAadharNo} IconComponent={FileText} />
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{/* Registration Status - Only show if has data */}
|
||||
{hasSectionData([
|
||||
@@ -344,18 +347,18 @@ const ProfileTab: React.FC = () => {
|
||||
data.msmeStatus,
|
||||
data.expiryDate
|
||||
]) && (
|
||||
<InfoSection title="Registration Details" IconComponent={ClipboardCheck} delay={0}>
|
||||
<InfoRow label="Class" value={data.contractorClass} IconComponent={Star} />
|
||||
<InfoRow label="Category" value={data.socialCategory} IconComponent={List} />
|
||||
<InfoRow label="MSME Status" value={data.msmeStatus} IconComponent={Factory} />
|
||||
<InfoRow
|
||||
label="Expiry Date"
|
||||
value={data.expiryDate}
|
||||
IconComponent={CalendarClock}
|
||||
valueColor={colors.warning}
|
||||
/>
|
||||
</InfoSection>
|
||||
)}
|
||||
<InfoSection title="Registration Details" IconComponent={ClipboardCheck} delay={0}>
|
||||
<InfoRow label="Class" value={data.contractorClass} IconComponent={Star} />
|
||||
<InfoRow label="Category" value={data.socialCategory} IconComponent={List} />
|
||||
<InfoRow label="MSME Status" value={data.msmeStatus} IconComponent={Factory} />
|
||||
<InfoRow
|
||||
label="Expiry Date"
|
||||
value={data.expiryDate}
|
||||
IconComponent={CalendarClock}
|
||||
valueColor={colors.text}
|
||||
/>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
<View style={{ height: 40 }} />
|
||||
</ScrollView>
|
||||
@@ -401,6 +404,8 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||
data,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||
const [formData, setFormData] = useState<UpdateProfilePayload>({
|
||||
firstName: data.firstName || '',
|
||||
middleName: data.middleName || '',
|
||||
@@ -484,83 +489,83 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||
style={styles.modalOverlay}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
<View style={[styles.modalContent, { backgroundColor: colors.background }]}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>Edit Personal Information</Text>
|
||||
<AppText variant="h3" style={styles.modalTitle}>Edit Personal Information</AppText>
|
||||
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
||||
<X size={24} color={colors.darkText} />
|
||||
<X size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.inputLabel}>First Name *</Text>
|
||||
<AppText variant="bodySmall" style={styles.inputLabel}>First Name *</AppText>
|
||||
<TextInput
|
||||
style={[styles.input, errors.firstName && styles.inputError]}
|
||||
value={formData.firstName}
|
||||
onChangeText={(text) => setFormData({ ...formData, firstName: text })}
|
||||
placeholder="Enter first name"
|
||||
placeholderTextColor={colors.grayText}
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<Text style={styles.errorText}>{errors.firstName}</Text>
|
||||
<AppText variant="caption" style={styles.errorText}>{errors.firstName}</AppText>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.inputLabel}>Middle Name</Text>
|
||||
<AppText variant="bodySmall" style={styles.inputLabel}>Middle Name</AppText>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={formData.middleName}
|
||||
onChangeText={(text) => setFormData({ ...formData, middleName: text })}
|
||||
placeholder="Enter middle name"
|
||||
placeholderTextColor={colors.grayText}
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.inputLabel}>Last Name *</Text>
|
||||
<AppText variant="bodySmall" style={styles.inputLabel}>Last Name *</AppText>
|
||||
<TextInput
|
||||
style={[styles.input, errors.lastName && styles.inputError]}
|
||||
value={formData.lastName}
|
||||
onChangeText={(text) => setFormData({ ...formData, lastName: text })}
|
||||
placeholder="Enter last name"
|
||||
placeholderTextColor={colors.grayText}
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<Text style={styles.errorText}>{errors.lastName}</Text>
|
||||
<AppText variant="caption" style={styles.errorText}>{errors.lastName}</AppText>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.inputLabel}>Email *</Text>
|
||||
<AppText variant="bodySmall" style={styles.inputLabel}>Email *</AppText>
|
||||
<TextInput
|
||||
style={[styles.input, errors.emailId && styles.inputError]}
|
||||
value={formData.emailId}
|
||||
onChangeText={(text) => setFormData({ ...formData, emailId: text })}
|
||||
placeholder="Enter email"
|
||||
placeholderTextColor={colors.grayText}
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
{errors.emailId && (
|
||||
<Text style={styles.errorText}>{errors.emailId}</Text>
|
||||
<AppText variant="caption" style={styles.errorText}>{errors.emailId}</AppText>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.inputLabel}>Mobile Number *</Text>
|
||||
<AppText variant="bodySmall" style={styles.inputLabel}>Mobile Number *</AppText>
|
||||
<TextInput
|
||||
style={[styles.input, errors.mobileNo && styles.inputError]}
|
||||
value={formData.mobileNo}
|
||||
onChangeText={(text) => setFormData({ ...formData, mobileNo: text })}
|
||||
placeholder="Enter mobile number"
|
||||
placeholderTextColor={colors.grayText}
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
keyboardType="phone-pad"
|
||||
maxLength={10}
|
||||
/>
|
||||
{errors.mobileNo && (
|
||||
<Text style={styles.errorText}>{errors.mobileNo}</Text>
|
||||
<AppText variant="caption" style={styles.errorText}>{errors.mobileNo}</AppText>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -571,7 +576,7 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||
onPress={onClose}
|
||||
disabled={updateProfileMutation.isPending}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
<AppText variant="button" style={styles.cancelButtonText}>Cancel</AppText>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
@@ -580,11 +585,11 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||
disabled={updateProfileMutation.isPending}
|
||||
>
|
||||
{updateProfileMutation.isPending ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
<ActivityIndicator size="small" color={colors.primary} />
|
||||
) : (
|
||||
<>
|
||||
<Save size={18} color={colors.white} />
|
||||
<Text style={styles.saveButtonText}>Save</Text>
|
||||
<Save size={18} color={colors.buttonText} />
|
||||
<AppText variant="button" style={styles.saveButtonText}>Save</AppText>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
@@ -597,6 +602,9 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
||||
|
||||
// Profile Header Component - OPTIMIZED
|
||||
const ProfileHeader: React.FC<ProfileHeaderProps> = React.memo(({ data, displayName }) => {
|
||||
const { colors } = useTheme();
|
||||
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||
|
||||
const getInitials = (name: string): string => {
|
||||
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||
const fallbackName = `${data.firstName || ''} ${data.lastName || ''}`.trim();
|
||||
@@ -630,46 +638,44 @@ const ProfileHeader: React.FC<ProfileHeaderProps> = React.memo(({ data, displayN
|
||||
|
||||
return (
|
||||
<View>
|
||||
<LinearGradient
|
||||
colors={[colors.primary, colors.primaryLight]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
<View
|
||||
style={styles.headerGradient}
|
||||
>
|
||||
<View style={styles.avatarContainer}>
|
||||
<View style={styles.avatar}>
|
||||
<Text style={styles.avatarText}>{getInitials(displayName)}</Text>
|
||||
<AppText variant="h1" color={colors.text} style={styles.avatarText}>{getInitials(displayName)}</AppText>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: getStatusColor() }]}>
|
||||
<View style={styles.statusDot} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.headerName}>
|
||||
<AppText variant="h2" style={styles.headerName}>
|
||||
{displayName || 'Unknown User'}
|
||||
</Text>
|
||||
</AppText>
|
||||
|
||||
<View style={styles.roleBadge}>
|
||||
<ShieldCheck size={16} color={colors.white} />
|
||||
<Text style={styles.roleText}>{data.role}</Text>
|
||||
<ShieldCheck size={16} color={colors.text} />
|
||||
<AppText variant="caption" style={styles.roleText}>{data.role}</AppText>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
ProfileHeader.displayName = 'ProfileHeader';
|
||||
|
||||
// Quick Stats Component - OPTIMIZED WITH TOUCHABLE AADHAAR
|
||||
// Quick Stats Component - Glass Cards
|
||||
const QuickStats: React.FC<QuickStatsProps> = React.memo(({ data, onAadhaarPress }) => {
|
||||
const user = useSelector((state: any) => state.token?.token);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const stats: StatItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
Icon: CheckCircle,
|
||||
label: 'Aadhaar',
|
||||
value: user?.esetuVerified,
|
||||
value: user?.esetuVerified || 'Pending',
|
||||
color: user?.esetuVerified === 'Not Verified' ? colors.error : colors.success,
|
||||
onPress: onAadhaarPress,
|
||||
},
|
||||
@@ -677,169 +683,103 @@ const QuickStats: React.FC<QuickStatsProps> = React.memo(({ data, onAadhaarPress
|
||||
Icon: ShieldCheck,
|
||||
label: '2FA',
|
||||
value: data.twoFactorAuthEnabled ? 'Enabled' : 'Disabled',
|
||||
color: data.twoFactorAuthEnabled ? colors.success : colors.grayText,
|
||||
},
|
||||
{
|
||||
Icon: FileText,
|
||||
label: 'Registration',
|
||||
value: data.registrationStatus,
|
||||
color: colors.info,
|
||||
color: data.twoFactorAuthEnabled ? colors.success : colors.textSecondary,
|
||||
},
|
||||
// ...
|
||||
],
|
||||
[data.twoFactorAuthEnabled, data.registrationStatus, user?.esetuVerified, onAadhaarPress]
|
||||
[data.twoFactorAuthEnabled, user?.esetuVerified, onAadhaarPress, colors]
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.statsContainer}>
|
||||
{stats.map((stat) => {
|
||||
const CardContent = (
|
||||
<View style={styles.statCard}>
|
||||
<stat.Icon size={28} color={stat.color} />
|
||||
<Text style={styles.statValue} numberOfLines={2}>
|
||||
{stat.value}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>{stat.label}</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 24 }}>
|
||||
{stats.map((stat, index) => {
|
||||
const content = (
|
||||
<Animated.View
|
||||
entering={FadeInUp.duration(600).delay(index * 100)}
|
||||
style={{
|
||||
flex: 1, backgroundColor: colors.glass, borderColor: colors.overlay, borderWidth: 1,
|
||||
borderRadius: 20, padding: 12, alignItems: 'center', marginHorizontal: 4,
|
||||
minHeight: 100, justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: stat.color + '10', alignItems: 'center', justifyContent: 'center', marginBottom: 8 }}>
|
||||
<stat.Icon size={20} color={stat.color} />
|
||||
</View>
|
||||
<AppText variant="bodySmall" color={colors.text} numberOfLines={1} style={{ textAlign: 'center' }}>{stat.value}</AppText>
|
||||
<AppText variant="caption" color={colors.textSecondary}>{stat.label}</AppText>
|
||||
</Animated.View>
|
||||
);
|
||||
|
||||
if (stat.onPress) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={stat.label}
|
||||
activeOpacity={0.7}
|
||||
onPress={stat.onPress}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{CardContent}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
return <View key={stat.label} style={{ flex: 1 }}>{CardContent}</View>;
|
||||
return stat.onPress ? (
|
||||
<TouchableOpacity key={stat.label} style={{ flex: 1 }} onPress={stat.onPress}>{content}</TouchableOpacity>
|
||||
) : (
|
||||
<View key={stat.label} style={{ flex: 1 }}>{content}</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
QuickStats.displayName = 'QuickStats';
|
||||
|
||||
// Info Section Component - OPTIMIZED
|
||||
// Info Section Component - Glass Card Container
|
||||
const InfoSection: React.FC<InfoSectionProps> = React.memo(
|
||||
({ title, IconComponent, children, onEdit, showEdit = false }) => {
|
||||
({ title, IconComponent, children, onEdit, showEdit = false, delay = 0 }) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<View style={styles.sectionHeaderLeft}>
|
||||
<View style={styles.sectionIconContainer}>
|
||||
<IconComponent size={24} color={colors.primary} />
|
||||
<Animated.View
|
||||
entering={FadeInUp.duration(600).delay(delay)}
|
||||
style={{
|
||||
backgroundColor: colors.glass,
|
||||
borderColor: colors.overlay,
|
||||
borderWidth: 1,
|
||||
borderRadius: 24,
|
||||
padding: 20,
|
||||
marginBottom: 16
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<View style={{ padding: 8, backgroundColor: colors.primary + '10', borderRadius: 10, marginRight: 12 }}>
|
||||
<IconComponent size={20} color={colors.primary} />
|
||||
</View>
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
<AppText variant="h4" color={colors.text}>{title}</AppText>
|
||||
</View>
|
||||
{showEdit && onEdit && (
|
||||
<TouchableOpacity style={styles.editButton} onPress={onEdit}>
|
||||
<Edit size={20} color={colors.primary} />
|
||||
<TouchableOpacity onPress={onEdit} style={{ padding: 4 }}>
|
||||
<Edit size={18} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.sectionContent}>{children}</View>
|
||||
</View>
|
||||
<View>{children}</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
InfoSection.displayName = 'InfoSection';
|
||||
|
||||
// Info Row Component - OPTIMIZED
|
||||
// Info Row Component
|
||||
const InfoRow: React.FC<InfoRowProps> = React.memo(
|
||||
({ label, value, IconComponent, valueColor }) => {
|
||||
if (!isValidValue(value)) {
|
||||
return null;
|
||||
}
|
||||
const { colors } = useTheme();
|
||||
|
||||
if (!isValidValue(value)) return null;
|
||||
|
||||
return (
|
||||
<View style={styles.infoRow}>
|
||||
<View style={styles.infoRowTouchable}>
|
||||
<View style={styles.infoRowContent}>
|
||||
<View style={styles.infoIconContainer}>
|
||||
<IconComponent size={18} color={colors.primary} />
|
||||
</View>
|
||||
<View style={styles.infoTextContainer}>
|
||||
<Text style={styles.infoLabel}>{label}</Text>
|
||||
<Text
|
||||
style={[styles.infoValue, valueColor && { color: valueColor }]}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', marginBottom: 16, alignItems: 'flex-start' }}>
|
||||
<IconComponent size={16} color={colors.textSecondary} style={{ marginTop: 4, marginRight: 12 }} />
|
||||
<View style={{ flex: 1 }}>
|
||||
<AppText variant="caption" color={colors.textSecondary} style={{ marginBottom: 2 }}>{label}</AppText>
|
||||
<AppText variant='bodySmall' color={valueColor || colors.text}>{value}</AppText>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
InfoRow.displayName = 'InfoRow';
|
||||
|
||||
// Styles
|
||||
interface Styles {
|
||||
container: ViewStyle;
|
||||
contentContainer: ViewStyle;
|
||||
centerContainer: ViewStyle;
|
||||
loadingText: TextStyle;
|
||||
errorText: TextStyle;
|
||||
retryButton: ViewStyle;
|
||||
retryButtonText: TextStyle;
|
||||
headerGradient: ViewStyle;
|
||||
avatarContainer: ViewStyle;
|
||||
avatar: ViewStyle;
|
||||
avatarText: TextStyle;
|
||||
statusBadge: ViewStyle;
|
||||
statusDot: ViewStyle;
|
||||
headerName: TextStyle;
|
||||
roleBadge: ViewStyle;
|
||||
roleText: TextStyle;
|
||||
statsContainer: ViewStyle;
|
||||
statCard: ViewStyle;
|
||||
statValue: TextStyle;
|
||||
statLabel: TextStyle;
|
||||
section: ViewStyle;
|
||||
sectionHeader: ViewStyle;
|
||||
sectionHeaderLeft: ViewStyle;
|
||||
sectionIconContainer: ViewStyle;
|
||||
sectionTitle: TextStyle;
|
||||
sectionContent: ViewStyle;
|
||||
infoRow: ViewStyle;
|
||||
infoRowTouchable: ViewStyle;
|
||||
infoRowContent: ViewStyle;
|
||||
infoIconContainer: ViewStyle;
|
||||
infoTextContainer: ViewStyle;
|
||||
infoLabel: TextStyle;
|
||||
infoValue: TextStyle;
|
||||
editButton: ViewStyle;
|
||||
modalOverlay: ViewStyle;
|
||||
modalContent: ViewStyle;
|
||||
modalHeader: ViewStyle;
|
||||
modalTitle: TextStyle;
|
||||
closeButton: ViewStyle;
|
||||
formContainer: ViewStyle;
|
||||
inputGroup: ViewStyle;
|
||||
inputLabel: TextStyle;
|
||||
input: TextStyle;
|
||||
inputError: TextStyle;
|
||||
errorText: TextStyle;
|
||||
modalFooter: ViewStyle;
|
||||
button: ViewStyle;
|
||||
cancelButton: ViewStyle;
|
||||
saveButton: ViewStyle;
|
||||
cancelButtonText: TextStyle;
|
||||
saveButtonText: TextStyle;
|
||||
navigationOverlay: ViewStyle;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create<Styles>({
|
||||
const createStyles = (colors: any) => StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.lightGray,
|
||||
paddingHorizontal: 20
|
||||
},
|
||||
contentContainer: {
|
||||
paddingBottom: 20,
|
||||
@@ -848,16 +788,16 @@ const styles = StyleSheet.create<Styles>({
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.white,
|
||||
backgroundColor: colors.background,
|
||||
padding: 20,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 16,
|
||||
fontSize: 16,
|
||||
color: colors.grayText,
|
||||
color: colors.textSecondary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
errorText: {
|
||||
screenErrorText: {
|
||||
marginTop: 16,
|
||||
fontSize: 16,
|
||||
color: colors.error,
|
||||
@@ -886,13 +826,6 @@ const styles = StyleSheet.create<Styles>({
|
||||
paddingTop: Platform.OS === 'ios' ? 60 : 40,
|
||||
paddingBottom: 30,
|
||||
alignItems: 'center',
|
||||
borderBottomLeftRadius: 32,
|
||||
borderBottomRightRadius: 32,
|
||||
elevation: 8,
|
||||
shadowColor: colors.shadow,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
},
|
||||
avatarContainer: {
|
||||
position: 'relative',
|
||||
@@ -902,11 +835,11 @@ const styles = StyleSheet.create<Styles>({
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: 50,
|
||||
backgroundColor: colors.white,
|
||||
backgroundColor: colors.card,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: 4,
|
||||
borderColor: colors.white,
|
||||
borderColor: colors.card,
|
||||
elevation: 4,
|
||||
shadowColor: colors.shadow,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
@@ -926,7 +859,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
borderWidth: 3,
|
||||
borderColor: colors.white,
|
||||
borderColor: colors.card, // Updated
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
@@ -939,7 +872,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
headerName: {
|
||||
fontSize: 26,
|
||||
fontWeight: '700',
|
||||
color: colors.white,
|
||||
color: colors.text, // Updated
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 20,
|
||||
@@ -947,14 +880,14 @@ const styles = StyleSheet.create<Styles>({
|
||||
roleBadge: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.25)',
|
||||
backgroundColor: colors.primary + '20', // Updated opacity approach
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 20,
|
||||
gap: 6,
|
||||
},
|
||||
roleText: {
|
||||
color: colors.white,
|
||||
color: colors.primary, // Updated
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -968,7 +901,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
},
|
||||
statCard: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.white,
|
||||
backgroundColor: colors.card,
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
@@ -981,20 +914,20 @@ const styles = StyleSheet.create<Styles>({
|
||||
statValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
color: colors.darkText,
|
||||
color: colors.text,
|
||||
marginTop: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 12,
|
||||
color: colors.grayText,
|
||||
color: colors.textSecondary,
|
||||
marginTop: 4,
|
||||
},
|
||||
|
||||
section: {
|
||||
marginHorizontal: 16,
|
||||
marginTop: 24,
|
||||
backgroundColor: colors.white,
|
||||
backgroundColor: colors.card,
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
elevation: 2,
|
||||
@@ -1021,7 +954,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 12,
|
||||
backgroundColor: colors.primaryHighlight,
|
||||
backgroundColor: colors.primary + '10', // Updated
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 12,
|
||||
@@ -1029,7 +962,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: colors.darkText,
|
||||
color: colors.text,
|
||||
flex: 1,
|
||||
},
|
||||
sectionContent: {
|
||||
@@ -1038,7 +971,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
editButton: {
|
||||
padding: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: colors.primaryHighlight,
|
||||
backgroundColor: colors.primary + '10', // Updated
|
||||
},
|
||||
|
||||
infoRow: {
|
||||
@@ -1047,7 +980,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
},
|
||||
infoRowTouchable: {
|
||||
padding: 14,
|
||||
backgroundColor: colors.lightestGray,
|
||||
backgroundColor: colors.background, // Updated
|
||||
borderRadius: 12,
|
||||
},
|
||||
infoRowContent: {
|
||||
@@ -1058,7 +991,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 8,
|
||||
backgroundColor: colors.white,
|
||||
backgroundColor: colors.card, // Updated
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 12,
|
||||
@@ -1070,13 +1003,13 @@ const styles = StyleSheet.create<Styles>({
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: 13,
|
||||
color: colors.grayText,
|
||||
color: colors.textSecondary,
|
||||
fontWeight: '500',
|
||||
lineHeight: 18,
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 15,
|
||||
color: colors.darkText,
|
||||
color: colors.text,
|
||||
fontWeight: '600',
|
||||
lineHeight: 21,
|
||||
},
|
||||
@@ -1087,11 +1020,11 @@ const styles = StyleSheet.create<Styles>({
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: colors.white,
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
maxHeight: '90%',
|
||||
paddingTop: 20,
|
||||
borderRadius: 30
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
@@ -1105,7 +1038,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
color: colors.darkText,
|
||||
color: colors.text,
|
||||
},
|
||||
closeButton: {
|
||||
padding: 4,
|
||||
@@ -1120,7 +1053,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
inputLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: colors.darkText,
|
||||
color: colors.text,
|
||||
marginBottom: 8,
|
||||
},
|
||||
input: {
|
||||
@@ -1129,8 +1062,8 @@ const styles = StyleSheet.create<Styles>({
|
||||
borderRadius: 12,
|
||||
padding: 14,
|
||||
fontSize: 15,
|
||||
color: colors.darkText,
|
||||
backgroundColor: colors.white,
|
||||
color: colors.text,
|
||||
backgroundColor: colors.card, // Updated
|
||||
},
|
||||
inputError: {
|
||||
borderColor: colors.error,
|
||||
@@ -1157,7 +1090,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
gap: 8,
|
||||
},
|
||||
cancelButton: {
|
||||
backgroundColor: colors.lightestGray,
|
||||
backgroundColor: colors.background, // Updated
|
||||
},
|
||||
saveButton: {
|
||||
backgroundColor: colors.primary,
|
||||
@@ -1165,7 +1098,7 @@ const styles = StyleSheet.create<Styles>({
|
||||
cancelButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: colors.darkText,
|
||||
color: colors.text,
|
||||
},
|
||||
saveButtonText: {
|
||||
fontSize: 16,
|
||||
|
||||
Reference in New Issue
Block a user