1064 lines
28 KiB
TypeScript
1064 lines
28 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
ScrollView,
|
|
StyleSheet,
|
|
TouchableOpacity,
|
|
RefreshControl,
|
|
ActivityIndicator,
|
|
Dimensions,
|
|
Platform,
|
|
ViewStyle,
|
|
TextStyle,
|
|
Modal,
|
|
TextInput,
|
|
KeyboardAvoidingView,
|
|
Alert,
|
|
} from 'react-native';
|
|
import { LinearGradient } from 'react-native-linear-gradient';
|
|
import {
|
|
AlertCircle,
|
|
UserCircle,
|
|
User,
|
|
Mail,
|
|
Phone,
|
|
Briefcase,
|
|
Building2,
|
|
Building,
|
|
Tag,
|
|
Network,
|
|
Globe,
|
|
Award,
|
|
MapPin,
|
|
Home,
|
|
Map,
|
|
Mailbox,
|
|
FileText,
|
|
CreditCard,
|
|
Type,
|
|
Calendar,
|
|
Receipt,
|
|
ClipboardCheck,
|
|
Star,
|
|
List,
|
|
Factory,
|
|
CheckCircle2,
|
|
CheckCircle,
|
|
CalendarClock,
|
|
ShieldCheck,
|
|
LucideIcon,
|
|
Edit,
|
|
X,
|
|
Save,
|
|
} from 'lucide-react-native';
|
|
|
|
import { useContractorProfile, useUpdateUserProfile } from '../../Services/BBPS/Hooks';
|
|
import colors from '../../Constant/Color';
|
|
|
|
const { width } = Dimensions.get('window');
|
|
|
|
// Type Definitions
|
|
interface ContractorData {
|
|
contractorId: number;
|
|
firstName: string;
|
|
middleName: string;
|
|
lastName: string;
|
|
fullName: string;
|
|
emailId: string;
|
|
mobileNo: string;
|
|
company: string;
|
|
contractorType: string;
|
|
dialCode: string;
|
|
twoFactorAuthEnabled: boolean;
|
|
colorCodeHSL: string | null;
|
|
isActive: string;
|
|
isEnabled: boolean | null;
|
|
role: string;
|
|
contractorStatus: boolean;
|
|
designation: string | null;
|
|
contOtherId: number;
|
|
contCompanyType: string | null;
|
|
contOrganization: string;
|
|
agentAddress: string;
|
|
agentPincode: string;
|
|
agentCity: string;
|
|
agentState: string | null;
|
|
agentWebsite: string;
|
|
agentRegno: string;
|
|
orgPAN: string;
|
|
orgPANdob: string;
|
|
orgPANname: string;
|
|
gistnNo: string;
|
|
comAadharNo: string;
|
|
contractorClass: string;
|
|
socialCategory: string;
|
|
wizardStepStatus: string;
|
|
registrationStatus: string;
|
|
indianOrgName: string;
|
|
expiryDate: string;
|
|
msmeStatus: string;
|
|
}
|
|
|
|
interface ProfileHeaderProps {
|
|
data: ContractorData;
|
|
}
|
|
|
|
interface QuickStatsProps {
|
|
data: ContractorData;
|
|
}
|
|
|
|
interface InfoSectionProps {
|
|
title: string;
|
|
IconComponent: LucideIcon;
|
|
children: React.ReactNode;
|
|
delay?: number;
|
|
onEdit?: () => void;
|
|
showEdit?: boolean;
|
|
}
|
|
|
|
interface InfoRowProps {
|
|
label: string;
|
|
value: string | null | undefined;
|
|
IconComponent: LucideIcon;
|
|
valueColor?: string;
|
|
}
|
|
|
|
interface StatItem {
|
|
Icon: LucideIcon;
|
|
label: string;
|
|
value: string;
|
|
color: string;
|
|
}
|
|
|
|
interface UpdateProfilePayload {
|
|
firstName: string;
|
|
middleName: string;
|
|
lastName: string;
|
|
mobileNo: string;
|
|
emailId: string;
|
|
fullname: string;
|
|
}
|
|
|
|
// Helper function to check if value is valid
|
|
const isValidValue = (value: any): boolean => {
|
|
if (value === null || value === undefined) return false;
|
|
if (typeof value === 'string') {
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 && trimmed.toLowerCase() !== 'n/a';
|
|
}
|
|
return true;
|
|
};
|
|
|
|
// Main Component
|
|
const ProfileTab: React.FC = () => {
|
|
const { data, refetch, isLoading, error } = useContractorProfile();
|
|
console.log("ioioi",data)
|
|
const [editModalVisible, setEditModalVisible] = useState(false);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={styles.centerContainer}>
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
<Text style={styles.loadingText}>Loading profile...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
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>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (!data) return null;
|
|
|
|
return (
|
|
<>
|
|
<ScrollView
|
|
style={styles.container}
|
|
contentContainerStyle={styles.contentContainer}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={isLoading}
|
|
onRefresh={refetch}
|
|
colors={[colors.primary]}
|
|
tintColor={colors.primary}
|
|
/>
|
|
}
|
|
showsVerticalScrollIndicator={false}
|
|
removeClippedSubviews={true}
|
|
maxToRenderPerBatch={10}
|
|
updateCellsBatchingPeriod={50}
|
|
initialNumToRender={8}
|
|
windowSize={10}
|
|
>
|
|
{/* Header with Gradient */}
|
|
<ProfileHeader data={data} />
|
|
|
|
{/* Quick Stats */}
|
|
<QuickStats data={data} />
|
|
|
|
{/* Personal Information */}
|
|
<InfoSection
|
|
title="Personal Information"
|
|
IconComponent={UserCircle}
|
|
delay={0}
|
|
showEdit={true}
|
|
onEdit={() => setEditModalVisible(true)}
|
|
>
|
|
<InfoRow label="Full Name" value={data.firstName+" "+data.middleName+" "+data.lastName} IconComponent={User} />
|
|
<InfoRow label="Email" value={data.emailId} IconComponent={Mail} />
|
|
<InfoRow
|
|
label="Mobile"
|
|
value={`${data.dialCode} ${data.mobileNo}`}
|
|
IconComponent={Phone}
|
|
/>
|
|
<InfoRow label="Designation" value={data.designation} IconComponent={Briefcase} />
|
|
</InfoSection>
|
|
|
|
{/* Company Details */}
|
|
<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 */}
|
|
<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 */}
|
|
<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 */}
|
|
<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>
|
|
|
|
<View style={{ height: 40 }} />
|
|
</ScrollView>
|
|
|
|
{/* Edit Modal */}
|
|
<EditProfileModal
|
|
visible={editModalVisible}
|
|
onClose={() => setEditModalVisible(false)}
|
|
data={data}
|
|
onSuccess={refetch}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
// Edit Profile Modal Component
|
|
interface EditProfileModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
data: ContractorData;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
const EditProfileModal: React.FC<EditProfileModalProps> = ({
|
|
visible,
|
|
onClose,
|
|
data,
|
|
onSuccess,
|
|
}) => {
|
|
const [formData, setFormData] = useState<UpdateProfilePayload>({
|
|
firstName: data.firstName || '',
|
|
middleName: data.middleName || '',
|
|
lastName: data.lastName || '',
|
|
mobileNo: data.mobileNo || '',
|
|
emailId: data.emailId || '',
|
|
fullname: data.fullName || '',
|
|
});
|
|
|
|
const [errors, setErrors] = useState<Partial<UpdateProfilePayload>>({});
|
|
|
|
const updateProfileMutation = useUpdateUserProfile();
|
|
|
|
// Reset form when modal opens
|
|
useEffect(() => {
|
|
if (visible) {
|
|
setFormData({
|
|
firstName: data.firstName || '',
|
|
middleName: data.middleName || '',
|
|
lastName: data.lastName || '',
|
|
mobileNo: data.mobileNo || '',
|
|
emailId: data.emailId || '',
|
|
fullname: data.fullName || '',
|
|
});
|
|
setErrors({});
|
|
}
|
|
}, [visible, data]);
|
|
|
|
const validateForm = (): boolean => {
|
|
const newErrors: Partial<UpdateProfilePayload> = {};
|
|
|
|
if (!formData.firstName.trim()) {
|
|
newErrors.firstName = 'First name is required';
|
|
}
|
|
|
|
if (!formData.lastName.trim()) {
|
|
newErrors.lastName = 'Last name is required';
|
|
}
|
|
|
|
if (!formData.emailId.trim()) {
|
|
newErrors.emailId = 'Email is required';
|
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.emailId)) {
|
|
newErrors.emailId = 'Invalid email format';
|
|
}
|
|
|
|
if (!formData.mobileNo.trim()) {
|
|
newErrors.mobileNo = 'Mobile number is required';
|
|
} else if (!/^\d{10}$/.test(formData.mobileNo.replace(/\s/g, ''))) {
|
|
newErrors.mobileNo = 'Invalid mobile number';
|
|
}
|
|
|
|
// Auto-generate fullname
|
|
const generatedFullName = `${formData.firstName} ${formData.middleName} ${formData.lastName}`.trim();
|
|
setFormData(prev => ({ ...prev, fullname: generatedFullName }));
|
|
|
|
setErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
if (!validateForm()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await updateProfileMutation.mutateAsync(formData);
|
|
Alert.alert('Success', 'Profile updated successfully');
|
|
onSuccess();
|
|
onClose();
|
|
} catch (error: any) {
|
|
Alert.alert('Error', error?.message || 'Failed to update profile');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
animationType="slide"
|
|
transparent={true}
|
|
onRequestClose={onClose}
|
|
>
|
|
<KeyboardAvoidingView
|
|
style={styles.modalOverlay}
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
>
|
|
<View style={styles.modalContent}>
|
|
{/* Header */}
|
|
<View style={styles.modalHeader}>
|
|
<Text style={styles.modalTitle}>Edit Personal Information</Text>
|
|
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
<X size={24} color={colors.darkText} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Form */}
|
|
<ScrollView style={styles.formContainer} showsVerticalScrollIndicator={false}>
|
|
{/* First Name */}
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.inputLabel}>First Name *</Text>
|
|
<TextInput
|
|
style={[styles.input, errors.firstName && styles.inputError]}
|
|
value={formData.firstName}
|
|
onChangeText={(text) => setFormData({ ...formData, firstName: text })}
|
|
placeholder="Enter first name"
|
|
placeholderTextColor={colors.grayText}
|
|
/>
|
|
{errors.firstName && (
|
|
<Text style={styles.errorText}>{errors.firstName}</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* Middle Name */}
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.inputLabel}>Middle Name</Text>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={formData.middleName}
|
|
onChangeText={(text) => setFormData({ ...formData, middleName: text })}
|
|
placeholder="Enter middle name"
|
|
placeholderTextColor={colors.grayText}
|
|
/>
|
|
</View>
|
|
|
|
{/* Last Name */}
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.inputLabel}>Last Name *</Text>
|
|
<TextInput
|
|
style={[styles.input, errors.lastName && styles.inputError]}
|
|
value={formData.lastName}
|
|
onChangeText={(text) => setFormData({ ...formData, lastName: text })}
|
|
placeholder="Enter last name"
|
|
placeholderTextColor={colors.grayText}
|
|
/>
|
|
{errors.lastName && (
|
|
<Text style={styles.errorText}>{errors.lastName}</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* Email */}
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.inputLabel}>Email *</Text>
|
|
<TextInput
|
|
style={[styles.input, errors.emailId && styles.inputError]}
|
|
value={formData.emailId}
|
|
onChangeText={(text) => setFormData({ ...formData, emailId: text })}
|
|
placeholder="Enter email"
|
|
placeholderTextColor={colors.grayText}
|
|
keyboardType="email-address"
|
|
autoCapitalize="none"
|
|
/>
|
|
{errors.emailId && (
|
|
<Text style={styles.errorText}>{errors.emailId}</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* Mobile Number */}
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.inputLabel}>Mobile Number *</Text>
|
|
<TextInput
|
|
style={[styles.input, errors.mobileNo && styles.inputError]}
|
|
value={formData.mobileNo}
|
|
onChangeText={(text) => setFormData({ ...formData, mobileNo: text })}
|
|
placeholder="Enter mobile number"
|
|
placeholderTextColor={colors.grayText}
|
|
keyboardType="phone-pad"
|
|
maxLength={10}
|
|
/>
|
|
{errors.mobileNo && (
|
|
<Text style={styles.errorText}>{errors.mobileNo}</Text>
|
|
)}
|
|
</View>
|
|
</ScrollView>
|
|
|
|
{/* Footer Buttons */}
|
|
<View style={styles.modalFooter}>
|
|
<TouchableOpacity
|
|
style={[styles.button, styles.cancelButton]}
|
|
onPress={onClose}
|
|
disabled={updateProfileMutation.isPending}
|
|
>
|
|
<Text style={styles.cancelButtonText}>Cancel</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.button, styles.saveButton]}
|
|
onPress={handleSave}
|
|
disabled={updateProfileMutation.isPending}
|
|
>
|
|
{updateProfileMutation.isPending ? (
|
|
<ActivityIndicator size="small" color={colors.white} />
|
|
) : (
|
|
<>
|
|
<Save size={18} color={colors.white} />
|
|
<Text style={styles.saveButtonText}>Save</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</KeyboardAvoidingView>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
// Profile Header Component - OPTIMIZED
|
|
const ProfileHeader: React.FC<ProfileHeaderProps> = React.memo(({ data }) => {
|
|
const getInitials = (name: string): string => {
|
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
|
const fallbackName = `${data.firstName || ''} ${data.lastName || ''}`.trim();
|
|
if (fallbackName) {
|
|
return fallbackName
|
|
.split(' ')
|
|
.filter((n) => n.length > 0)
|
|
.map((n) => n[0])
|
|
.join('')
|
|
.toUpperCase()
|
|
.substring(0, 2);
|
|
}
|
|
return 'NA';
|
|
}
|
|
|
|
return (
|
|
name
|
|
.split(' ')
|
|
.filter((n) => n.length > 0)
|
|
.map((n) => n[0])
|
|
.join('')
|
|
.toUpperCase()
|
|
.substring(0, 2) || 'NA'
|
|
);
|
|
};
|
|
|
|
const getStatusColor = (): string => {
|
|
if (data.isActive === 'Active') return colors.success;
|
|
return colors.error;
|
|
};
|
|
|
|
return (
|
|
<View>
|
|
<LinearGradient
|
|
colors={[colors.primary, colors.primaryLight]}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={styles.headerGradient}
|
|
>
|
|
<View style={styles.avatarContainer}>
|
|
<View style={styles.avatar}>
|
|
<Text style={styles.avatarText}>{getInitials(data.fullName)}</Text>
|
|
</View>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor() }]}>
|
|
<View style={styles.statusDot} />
|
|
</View>
|
|
</View>
|
|
|
|
<Text style={styles.headerName}>
|
|
{data.fullName ||
|
|
`${data.firstName} ${data.lastName}`.trim() ||
|
|
'Unknown User'}
|
|
</Text>
|
|
|
|
<View style={styles.roleBadge}>
|
|
<ShieldCheck size={16} color={colors.white} />
|
|
<Text style={styles.roleText}>{data.role}</Text>
|
|
</View>
|
|
</LinearGradient>
|
|
</View>
|
|
);
|
|
});
|
|
|
|
ProfileHeader.displayName = 'ProfileHeader';
|
|
|
|
// Quick Stats Component - OPTIMIZED
|
|
const QuickStats: React.FC<QuickStatsProps> = React.memo(({ data }) => {
|
|
const stats: StatItem[] = useMemo(
|
|
() => [
|
|
{
|
|
Icon: CheckCircle,
|
|
label: 'Status',
|
|
value: data.isActive,
|
|
color: colors.success,
|
|
},
|
|
{
|
|
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,
|
|
},
|
|
],
|
|
[data.isActive, data.twoFactorAuthEnabled, data.registrationStatus]
|
|
);
|
|
|
|
return (
|
|
<View style={styles.statsContainer}>
|
|
{stats.map((stat) => (
|
|
<View key={stat.label} 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>
|
|
);
|
|
});
|
|
|
|
QuickStats.displayName = 'QuickStats';
|
|
|
|
// Info Section Component - OPTIMIZED
|
|
const InfoSection: React.FC<InfoSectionProps> = React.memo(
|
|
({ title, IconComponent, children, onEdit, showEdit = false }) => {
|
|
return (
|
|
<View style={styles.section}>
|
|
<View style={styles.sectionHeader}>
|
|
<View style={styles.sectionHeaderLeft}>
|
|
<View style={styles.sectionIconContainer}>
|
|
<IconComponent size={24} color={colors.primary} />
|
|
</View>
|
|
<Text style={styles.sectionTitle}>{title}</Text>
|
|
</View>
|
|
{showEdit && onEdit && (
|
|
<TouchableOpacity style={styles.editButton} onPress={onEdit}>
|
|
<Edit size={20} color={colors.primary} />
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
<View style={styles.sectionContent}>{children}</View>
|
|
</View>
|
|
);
|
|
}
|
|
);
|
|
|
|
InfoSection.displayName = 'InfoSection';
|
|
|
|
// Info Row Component - OPTIMIZED
|
|
const InfoRow: React.FC<InfoRowProps> = React.memo(
|
|
({ label, value, IconComponent, valueColor }) => {
|
|
// Return null if value is not valid
|
|
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>
|
|
</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;
|
|
}
|
|
|
|
const styles = StyleSheet.create<Styles>({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: colors.lightGray,
|
|
},
|
|
contentContainer: {
|
|
paddingBottom: 20,
|
|
},
|
|
centerContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: colors.white,
|
|
padding: 20,
|
|
},
|
|
loadingText: {
|
|
marginTop: 16,
|
|
fontSize: 16,
|
|
color: colors.grayText,
|
|
fontWeight: '500',
|
|
},
|
|
errorText: {
|
|
marginTop: 16,
|
|
fontSize: 16,
|
|
color: colors.error,
|
|
textAlign: 'center',
|
|
fontWeight: '500',
|
|
},
|
|
retryButton: {
|
|
marginTop: 20,
|
|
backgroundColor: colors.primary,
|
|
paddingHorizontal: 32,
|
|
paddingVertical: 12,
|
|
borderRadius: 12,
|
|
elevation: 2,
|
|
shadowColor: colors.shadow,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 4,
|
|
},
|
|
retryButtonText: {
|
|
color: colors.white,
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
|
|
// Header Styles
|
|
headerGradient: {
|
|
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',
|
|
marginBottom: 16,
|
|
},
|
|
avatar: {
|
|
width: 100,
|
|
height: 100,
|
|
borderRadius: 50,
|
|
backgroundColor: colors.white,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
borderWidth: 4,
|
|
borderColor: colors.white,
|
|
elevation: 4,
|
|
shadowColor: colors.shadow,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 4,
|
|
},
|
|
avatarText: {
|
|
fontSize: 36,
|
|
fontWeight: '700',
|
|
color: colors.primary,
|
|
},
|
|
statusBadge: {
|
|
position: 'absolute',
|
|
bottom: 4,
|
|
right: 4,
|
|
width: 24,
|
|
height: 24,
|
|
borderRadius: 12,
|
|
borderWidth: 3,
|
|
borderColor: colors.white,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
statusDot: {
|
|
width: 10,
|
|
height: 10,
|
|
borderRadius: 5,
|
|
backgroundColor: colors.white,
|
|
},
|
|
headerName: {
|
|
fontSize: 26,
|
|
fontWeight: '700',
|
|
color: colors.white,
|
|
marginBottom: 8,
|
|
textAlign: 'center',
|
|
paddingHorizontal: 20,
|
|
},
|
|
roleBadge: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: 'rgba(255, 255, 255, 0.25)',
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 6,
|
|
borderRadius: 20,
|
|
gap: 6,
|
|
},
|
|
roleText: {
|
|
color: colors.white,
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
},
|
|
|
|
// Stats Styles
|
|
statsContainer: {
|
|
flexDirection: 'row',
|
|
marginHorizontal: 16,
|
|
marginTop: -30,
|
|
gap: 12,
|
|
zIndex: 10,
|
|
},
|
|
statCard: {
|
|
flex: 1,
|
|
backgroundColor: colors.white,
|
|
borderRadius: 16,
|
|
padding: 16,
|
|
alignItems: 'center',
|
|
elevation: 4,
|
|
shadowColor: colors.shadow,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.15,
|
|
shadowRadius: 6,
|
|
},
|
|
statValue: {
|
|
fontSize: 14,
|
|
fontWeight: '700',
|
|
color: colors.darkText,
|
|
marginTop: 8,
|
|
textAlign: 'center',
|
|
},
|
|
statLabel: {
|
|
fontSize: 12,
|
|
color: colors.grayText,
|
|
marginTop: 4,
|
|
},
|
|
|
|
// Section Styles
|
|
section: {
|
|
marginHorizontal: 16,
|
|
marginTop: 24,
|
|
backgroundColor: colors.white,
|
|
borderRadius: 20,
|
|
padding: 20,
|
|
elevation: 2,
|
|
shadowColor: colors.shadow,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 4,
|
|
},
|
|
sectionHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 16,
|
|
paddingBottom: 12,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: colors.border,
|
|
},
|
|
sectionHeaderLeft: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
flex: 1,
|
|
},
|
|
sectionIconContainer: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 12,
|
|
backgroundColor: colors.primaryHighlight,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
marginRight: 12,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
color: colors.darkText,
|
|
flex: 1,
|
|
},
|
|
sectionContent: {
|
|
gap: 12,
|
|
},
|
|
editButton: {
|
|
padding: 8,
|
|
borderRadius: 8,
|
|
backgroundColor: colors.primaryHighlight,
|
|
},
|
|
|
|
// Info Row Styles
|
|
infoRow: {
|
|
borderRadius: 12,
|
|
overflow: 'hidden',
|
|
},
|
|
infoRowTouchable: {
|
|
padding: 14,
|
|
backgroundColor: colors.lightestGray,
|
|
borderRadius: 12,
|
|
},
|
|
infoRowContent: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
},
|
|
infoIconContainer: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 8,
|
|
backgroundColor: colors.white,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
marginRight: 12,
|
|
marginTop: 2,
|
|
},
|
|
infoTextContainer: {
|
|
flex: 1,
|
|
gap: 4,
|
|
},
|
|
infoLabel: {
|
|
fontSize: 13,
|
|
color: colors.grayText,
|
|
fontWeight: '500',
|
|
lineHeight: 18,
|
|
},
|
|
infoValue: {
|
|
fontSize: 15,
|
|
color: colors.darkText,
|
|
fontWeight: '600',
|
|
lineHeight: 21,
|
|
},
|
|
|
|
// Modal Styles
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
justifyContent: 'flex-end',
|
|
},
|
|
modalContent: {
|
|
backgroundColor: colors.white,
|
|
borderTopLeftRadius: 24,
|
|
borderTopRightRadius: 24,
|
|
maxHeight: '90%',
|
|
paddingTop: 20,
|
|
},
|
|
modalHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 20,
|
|
paddingBottom: 16,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: colors.border,
|
|
},
|
|
modalTitle: {
|
|
fontSize: 20,
|
|
fontWeight: '700',
|
|
color: colors.darkText,
|
|
},
|
|
closeButton: {
|
|
padding: 4,
|
|
},
|
|
formContainer: {
|
|
padding: 20,
|
|
maxHeight: 500,
|
|
},
|
|
inputGroup: {
|
|
marginBottom: 20,
|
|
},
|
|
inputLabel: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: colors.darkText,
|
|
marginBottom: 8,
|
|
},
|
|
input: {
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
borderRadius: 12,
|
|
padding: 14,
|
|
fontSize: 15,
|
|
color: colors.darkText,
|
|
backgroundColor: colors.white,
|
|
},
|
|
inputError: {
|
|
borderColor: colors.error,
|
|
},
|
|
errorText: {
|
|
fontSize: 12,
|
|
color: colors.error,
|
|
marginTop: 4,
|
|
},
|
|
modalFooter: {
|
|
flexDirection: 'row',
|
|
padding: 20,
|
|
gap: 12,
|
|
borderTopWidth: 1,
|
|
borderTopColor: colors.border,
|
|
},
|
|
button: {
|
|
flex: 1,
|
|
paddingVertical: 14,
|
|
borderRadius: 12,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
flexDirection: 'row',
|
|
gap: 8,
|
|
},
|
|
cancelButton: {
|
|
backgroundColor: colors.lightestGray,
|
|
},
|
|
saveButton: {
|
|
backgroundColor: colors.primary,
|
|
},
|
|
cancelButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: colors.darkText,
|
|
},
|
|
saveButtonText: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: colors.white,
|
|
},
|
|
});
|
|
|
|
export default ProfileTab;
|