Files
ProcureLinc-Mobile-App/src/Screens/Profile/ProfileTab.tsx
T
2026-01-19 11:50:53 +05:30

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;