/* * File: EditProfileScreen.tsx * Description: Edit profile screen for updating user information * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TextInput, TouchableOpacity, Alert, KeyboardAvoidingView, Platform, } from 'react-native'; import Icon from 'react-native-vector-icons/Feather'; import { theme } from '../../../theme/theme'; import { SettingsHeader } from '../components/SettingsHeader'; import { useAppSelector, useAppDispatch } from '../../../store/hooks'; import { selectUserFirstName, selectUserLastName, selectUserDisplayName, selectUserEmail, selectUser, } from '../../Auth/redux/authSelectors'; import { updateUserProfileAsync } from '../../Auth/redux/authActions'; /** * EditProfileScreenProps Interface * * Purpose: Defines the props required by the EditProfileScreen component * * Props: * - navigation: React Navigation object for screen navigation */ interface EditProfileScreenProps { navigation: any; } /** * FormData Interface * * Purpose: Defines the structure of the profile form data */ interface FormData { firstName: string; lastName: string; displayName: string; } /** * FormErrors Interface * * Purpose: Defines the structure of form validation errors */ interface FormErrors { firstName?: string; lastName?: string; displayName?: string; } /** * EditProfileScreen Component * * Purpose: Allows users to edit their profile information including first name, * last name, and display name with proper validation * * Features: * 1. Pre-populated form with current user data * 2. Real-time validation * 3. Form submission with error handling * 4. Clean and intuitive user interface */ export const EditProfileScreen: React.FC = ({ navigation, }) => { // ============================================================================ // REDUX STATE // ============================================================================ const dispatch = useAppDispatch(); const user = useAppSelector(selectUser); const currentFirstName = useAppSelector(selectUserFirstName); const currentLastName = useAppSelector(selectUserLastName); const currentDisplayName = useAppSelector(selectUserDisplayName); const currentEmail = useAppSelector(selectUserEmail); // ============================================================================ // LOCAL STATE // ============================================================================ const [formData, setFormData] = useState({ firstName: '', lastName: '', displayName: '', }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [hasChanges, setHasChanges] = useState(false); // ============================================================================ // EFFECTS // ============================================================================ /** * Initialize form data with current user information */ useEffect(() => { setFormData({ firstName: currentFirstName || '', lastName: currentLastName || '', displayName: currentDisplayName || '', }); }, [currentFirstName, currentLastName, currentDisplayName]); /** * Check if form has unsaved changes */ useEffect(() => { const hasUnsavedChanges = formData.firstName !== currentFirstName || formData.lastName !== currentLastName || formData.displayName !== currentDisplayName; setHasChanges(hasUnsavedChanges); }, [formData, currentFirstName, currentLastName, currentDisplayName]); // ============================================================================ // VALIDATION FUNCTIONS // ============================================================================ /** * validateField Function * * Purpose: Validate individual form fields * * @param field - Field name to validate * @param value - Field value to validate * @returns Validation error message or undefined */ const validateField = (field: keyof FormData, value: string): string | undefined => { switch (field) { case 'firstName': if (!value.trim()) { return 'First name is required'; } if (value.trim().length < 2) { return 'First name must be at least 2 characters'; } if (value.trim().length > 50) { return 'First name must be less than 50 characters'; } if (!/^[a-zA-Z\s'-]+$/.test(value.trim())) { return 'First name can only contain letters, spaces, hyphens, and apostrophes'; } break; case 'lastName': if (!value.trim()) { return 'Last name is required'; } if (value.trim().length < 1) { return 'Last name must be at least 1 character'; } if (value.trim().length > 50) { return 'Last name must be less than 50 characters'; } if (!/^[a-zA-Z\s'-]+$/.test(value.trim())) { return 'Last name can only contain letters, spaces, hyphens, and apostrophes'; } break; case 'displayName': if (!value.trim()) { return 'Display name is required'; } if (value.trim().length < 2) { return 'Display name must be at least 2 characters'; } if (value.trim().length > 30) { return 'Display name must be less than 30 characters'; } break; } return undefined; }; /** * validateForm Function * * Purpose: Validate entire form and return validation errors * * @returns Object containing validation errors */ const validateForm = (): FormErrors => { const newErrors: FormErrors = {}; Object.keys(formData).forEach((field) => { const key = field as keyof FormData; const error = validateField(key, formData[key]); if (error) { newErrors[key] = error; } }); return newErrors; }; // ============================================================================ // EVENT HANDLERS // ============================================================================ /** * handleInputChange Function * * Purpose: Handle input field changes and clear field-specific errors * * @param field - Field name that changed * @param value - New field value */ const handleInputChange = (field: keyof FormData, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); // Clear field-specific error when user starts typing if (errors[field]) { setErrors(prev => ({ ...prev, [field]: undefined })); } }; /** * handleInputBlur Function * * Purpose: Validate field when user leaves the input * * @param field - Field name to validate */ const handleInputBlur = (field: keyof FormData) => { const error = validateField(field, formData[field]); setErrors(prev => ({ ...prev, [field]: error })); }; /** * handleSubmit Function * * Purpose: Handle form submission with validation and API call */ const handleSubmit = async () => { // Validate form const validationErrors = validateForm(); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); return; } setIsSubmitting(true); try { // Dispatch update action await dispatch(updateUserProfileAsync({ first_name: formData.firstName.trim(), last_name: formData.lastName.trim(), })).unwrap(); // Navigate back after successful profile update navigation.goBack(); } catch (error: any) { // Handle error - toast notification is already shown in the thunk console.error('Profile update error:', error); } finally { setIsSubmitting(false); } }; /** * handleCancel Function * * Purpose: Handle cancel action with unsaved changes warning */ const handleCancel = () => { if (hasChanges) { Alert.alert( 'Unsaved Changes', 'You have unsaved changes. Are you sure you want to leave?', [ { text: 'Cancel', style: 'cancel', }, { text: 'Leave', style: 'destructive', onPress: () => navigation.goBack(), }, ] ); } else { navigation.goBack(); } }; // ============================================================================ // MAIN RENDER // ============================================================================ return ( {/* Header with back button */} {/* Scrollable form content */} {/* Current email display (read-only) */} Account Information Email Address {currentEmail} Email address cannot be changed {/* Profile form section */} Personal Information {/* First Name Input */} First Name * handleInputChange('firstName', value)} onBlur={() => handleInputBlur('firstName')} placeholder="Enter your first name" placeholderTextColor={theme.colors.textMuted} autoCapitalize="words" autoCorrect={false} maxLength={50} /> {errors.firstName && ( {errors.firstName} )} {/* Last Name Input */} Last Name * handleInputChange('lastName', value)} onBlur={() => handleInputBlur('lastName')} placeholder="Enter your last name" placeholderTextColor={theme.colors.textMuted} autoCapitalize="words" autoCorrect={false} maxLength={50} /> {errors.lastName && ( {errors.lastName} )} {/* Display Name Input */} Display Name * handleInputChange('displayName', value)} onBlur={() => handleInputBlur('displayName')} placeholder="Enter your display name" placeholderTextColor={theme.colors.textMuted} autoCapitalize="words" autoCorrect={false} maxLength={30} /> {errors.displayName && ( {errors.displayName} )} {/* Action buttons */} {isSubmitting ? 'Updating...' : 'Update Profile'} Cancel {/* Bottom spacing for tab bar */} ); }; // ============================================================================ // STYLES SECTION // ============================================================================ const styles = StyleSheet.create({ // Main container container: { flex: 1, backgroundColor: theme.colors.background, }, // Scroll view styling scrollView: { flex: 1, }, // Scroll content styling scrollContent: { paddingHorizontal: theme.spacing.md, }, // Bottom spacing for tab bar bottomSpacing: { height: theme.spacing.xl, }, // Information sections infoSection: { backgroundColor: theme.colors.background, borderRadius: theme.borderRadius.medium, padding: theme.spacing.md, marginBottom: theme.spacing.md, ...theme.shadows.primary, }, sectionTitle: { fontSize: theme.typography.fontSize.displaySmall, fontFamily: theme.typography.fontFamily.bold, color: theme.colors.textPrimary, marginBottom: theme.spacing.md, }, // Read-only field styling readOnlyField: { paddingVertical: theme.spacing.sm, borderBottomColor: theme.colors.border, borderBottomWidth: 1, }, readOnlyLabel: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.medium, color: theme.colors.textMuted, marginBottom: theme.spacing.xs, }, emailContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: theme.spacing.xs, }, readOnlyValue: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textPrimary, flex: 1, }, lockIcon: { padding: theme.spacing.xs, backgroundColor: theme.colors.backgroundAlt, borderRadius: theme.borderRadius.small, }, readOnlyHint: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textMuted, fontStyle: 'italic', }, // Input container styling inputContainer: { marginBottom: theme.spacing.sm, backgroundColor: theme.colors.background, padding: theme.spacing.sm, borderRadius: theme.borderRadius.small, }, inputLabel: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.medium, color: theme.colors.textPrimary, marginBottom: theme.spacing.sm, fontWeight: '600', }, textInput: { borderWidth: 1, borderColor: theme.colors.border, borderRadius: theme.borderRadius.medium, paddingHorizontal: theme.spacing.md, paddingVertical: theme.spacing.md, fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textPrimary, backgroundColor: theme.colors.background, minHeight: 48, }, inputError: { borderColor: theme.colors.error, backgroundColor: theme.colors.error + '10', }, errorText: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.error, marginTop: theme.spacing.sm, marginLeft: theme.spacing.xs, paddingHorizontal: theme.spacing.sm, paddingVertical: theme.spacing.xs, backgroundColor: theme.colors.error + '10', borderRadius: theme.borderRadius.small, alignSelf: 'flex-start', }, // Button container buttonContainer: { marginTop: theme.spacing.lg, marginBottom: theme.spacing.md, }, submitButton: { backgroundColor: theme.colors.primary, borderRadius: theme.borderRadius.medium, paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.lg, alignItems: 'center', marginBottom: theme.spacing.md, ...theme.shadows.primary, }, submitButtonDisabled: { backgroundColor: theme.colors.border, shadowColor: 'transparent', shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0, shadowRadius: 0, elevation: 0, }, submitButtonText: { color: theme.colors.background, fontSize: theme.typography.fontSize.bodyLarge, fontFamily: theme.typography.fontFamily.bold, }, submitButtonTextDisabled: { color: theme.colors.textMuted, }, cancelButton: { backgroundColor: 'transparent', borderWidth: 1, borderColor: theme.colors.border, borderRadius: theme.borderRadius.medium, paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.lg, alignItems: 'center', }, cancelButtonText: { color: theme.colors.textSecondary, fontSize: theme.typography.fontSize.bodyLarge, fontFamily: theme.typography.fontFamily.medium, }, }); /* * End of File: EditProfileScreen.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */