/* * File: ChangePasswordScreen.tsx * Description: Change password screen with comprehensive password validation * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */ import React, { useState } 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 { useAppDispatch } from '../../../store/hooks'; import { changePasswordAsync } from '../../Auth/redux/authActions'; /** * ChangePasswordScreenProps Interface * * Purpose: Defines the props required by the ChangePasswordScreen component * * Props: * - navigation: React Navigation object for screen navigation */ interface ChangePasswordScreenProps { navigation: any; } /** * FormData Interface * * Purpose: Defines the structure of the password change form data */ interface FormData { currentPassword: string; newPassword: string; confirmPassword: string; } /** * FormErrors Interface * * Purpose: Defines the structure of form validation errors */ interface FormErrors { currentPassword?: string; newPassword?: string; confirmPassword?: string; } /** * PasswordStrength Interface * * Purpose: Defines the structure of password strength information */ interface PasswordStrength { score: number; label: string; color: string; requirements: string[]; } /** * ChangePasswordScreen Component * * Purpose: Allows users to change their password with comprehensive validation * including current password verification, new password strength requirements, * and password confirmation * * Features: * 1. Current password verification * 2. New password strength validation * 3. Password confirmation matching * 4. Real-time password strength indicator * 5. Comprehensive error handling */ export const ChangePasswordScreen: React.FC = ({ navigation, }) => { // ============================================================================ // REDUX STATE // ============================================================================ const dispatch = useAppDispatch(); // ============================================================================ // LOCAL STATE // ============================================================================ const [formData, setFormData] = useState({ currentPassword: '', newPassword: '', confirmPassword: '', }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [showCurrentPassword, setShowCurrentPassword] = useState(false); const [showNewPassword, setShowNewPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [passwordStrength, setPasswordStrength] = useState({ score: 0, label: 'Very Weak', color: theme.colors.error, requirements: [], }); // ============================================================================ // PASSWORD STRENGTH VALIDATION // ============================================================================ /** * checkPasswordStrength Function * * Purpose: Analyze password strength and return strength information * * @param password - Password to analyze * @returns PasswordStrength object with score, label, color, and requirements */ const checkPasswordStrength = (password: string): PasswordStrength => { const requirements: string[] = []; let score = 0; // Length requirement if (password.length >= 8) { score += 1; requirements.push('✓ At least 8 characters'); } else { requirements.push('✗ At least 8 characters'); } // Uppercase requirement if (/[A-Z]/.test(password)) { score += 1; requirements.push('✓ Contains uppercase letter'); } else { requirements.push('✗ Contains uppercase letter'); } // Lowercase requirement if (/[a-z]/.test(password)) { score += 1; requirements.push('✓ Contains lowercase letter'); } else { requirements.push('✗ Contains lowercase letter'); } // Number requirement if (/\d/.test(password)) { score += 1; requirements.push('✓ Contains number'); } else { requirements.push('✗ Contains number'); } // Special character requirement if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) { score += 1; requirements.push('✓ Contains special character'); } else { requirements.push('✗ Contains special character'); } // Determine strength label and color let label: string; let color: string; if (score <= 1) { label = 'Very Weak'; color = theme.colors.error; } else if (score <= 2) { label = 'Weak'; color = theme.colors.warning; } else if (score <= 3) { label = 'Fair'; color = theme.colors.warning; } else if (score <= 4) { label = 'Good'; color = theme.colors.info; } else { label = 'Strong'; color = theme.colors.success; } return { score, label, color, requirements, }; }; // ============================================================================ // 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 'currentPassword': if (!value.trim()) { return 'Current password is required'; } if (value.trim().length < 6) { return 'Current password must be at least 6 characters'; } break; case 'newPassword': if (!value.trim()) { return 'New password is required'; } if (value.trim().length < 8) { return 'New password must be at least 8 characters'; } if (value === formData.currentPassword) { return 'New password must be different from current password'; } break; case 'confirmPassword': if (!value.trim()) { return 'Please confirm your new password'; } if (value !== formData.newPassword) { return 'Passwords do not match'; } 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 update password strength * * @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 })); } // Update password strength for new password field if (field === 'newPassword') { const strength = checkPasswordStrength(value); setPasswordStrength(strength); } }; /** * 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; } // Check password strength if (passwordStrength.score < 3) { Alert.alert( 'Weak Password', 'Please choose a stronger password that meets the requirements.', [{ text: 'OK' }] ); return; } setIsSubmitting(true); try { // Dispatch password change action await dispatch(changePasswordAsync({ currentPassword: formData.currentPassword, newPassword: formData.newPassword, })).unwrap(); // Navigate back after successful password change navigation.goBack(); } catch (error: any) { // Handle error - toast notification is already shown in the thunk console.error('Password change error:', error); } finally { setIsSubmitting(false); } }; /** * handleCancel Function * * Purpose: Handle cancel action */ const handleCancel = () => { navigation.goBack(); }; /** * togglePasswordVisibility Function * * Purpose: Toggle password visibility for specified field * * @param field - Field to toggle visibility for */ const togglePasswordVisibility = (field: 'current' | 'new' | 'confirm') => { switch (field) { case 'current': setShowCurrentPassword(!showCurrentPassword); break; case 'new': setShowNewPassword(!showNewPassword); break; case 'confirm': setShowConfirmPassword(!showConfirmPassword); break; } }; // ============================================================================ // MAIN RENDER // ============================================================================ return ( {/* Header with back button */} {/* Scrollable form content */} {/* Password requirements info */} Password Requirements Your new password must meet the following requirements to ensure security: • At least 8 characters long • Contains uppercase and lowercase letters • Contains at least one number • Contains at least one special character {/* Password change form */} Change Password {/* Current Password Input */} Current Password * handleInputChange('currentPassword', value)} onBlur={() => handleInputBlur('currentPassword')} placeholder="Enter your current password" placeholderTextColor={theme.colors.textMuted} secureTextEntry={!showCurrentPassword} autoCapitalize="none" autoCorrect={false} /> togglePasswordVisibility('current')} activeOpacity={0.7} > {errors.currentPassword && ( {errors.currentPassword} )} {/* New Password Input */} New Password * handleInputChange('newPassword', value)} onBlur={() => handleInputBlur('newPassword')} placeholder="Enter your new password" placeholderTextColor={theme.colors.textMuted} secureTextEntry={!showNewPassword} autoCapitalize="none" autoCorrect={false} /> togglePasswordVisibility('new')} activeOpacity={0.7} > {errors.newPassword && ( {errors.newPassword} )} {/* Password Strength Indicator */} {formData.newPassword.length > 0 && ( Password Strength: {passwordStrength.label} )} {/* Password Requirements Check */} {formData.newPassword.length > 0 && ( {passwordStrength.requirements.map((requirement, index) => ( {requirement} ))} )} {/* Confirm Password Input */} Confirm New Password * handleInputChange('confirmPassword', value)} onBlur={() => handleInputBlur('confirmPassword')} placeholder="Confirm your new password" placeholderTextColor={theme.colors.textMuted} secureTextEntry={!showConfirmPassword} autoCapitalize="none" autoCorrect={false} /> togglePasswordVisibility('confirm')} activeOpacity={0.7} > {errors.confirmPassword && ( {errors.confirmPassword} )} {/* Action buttons */} {isSubmitting ? 'Changing Password...' : 'Change Password'} 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, }, infoText: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, lineHeight: 22, marginBottom: theme.spacing.md, }, // Requirements list styling requirementsList: { marginTop: theme.spacing.sm, }, requirementItem: { fontSize: theme.typography.fontSize.bodyMedium, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, marginBottom: theme.spacing.xs, }, // 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', }, // Password input styling passwordInputContainer: { position: 'relative', }, 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, }, passwordInput: { paddingRight: theme.spacing.xl + theme.spacing.md, }, eyeIcon: { position: 'absolute', right: theme.spacing.md, top: theme.spacing.md, padding: theme.spacing.xs, }, 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', }, // Password strength styling strengthContainer: { marginBottom: theme.spacing.md, }, strengthLabel: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.medium, color: theme.colors.textPrimary, marginBottom: theme.spacing.xs, }, strengthBar: { height: 4, backgroundColor: theme.colors.border, borderRadius: 2, marginBottom: theme.spacing.xs, overflow: 'hidden', }, strengthProgress: { height: '100%', borderRadius: 2, }, strengthText: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.medium, textAlign: 'center', }, // Requirements check styling requirementsCheck: { marginBottom: theme.spacing.md, padding: theme.spacing.sm, backgroundColor: theme.colors.backgroundAlt, borderRadius: theme.borderRadius.small, }, requirementCheckItem: { fontSize: theme.typography.fontSize.bodySmall, fontFamily: theme.typography.fontFamily.regular, color: theme.colors.textSecondary, marginBottom: theme.spacing.xs, }, // 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: ChangePasswordScreen.tsx * Design & Developed by Tech4Biz Solutions * Copyright (c) Spurrin Innovations. All rights reserved. */