Files
tbbank-react-native-mobile/src/components/EditProfileModal.js
2025-07-04 19:23:36 +05:00

470 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useRef } from 'react';
import {
View,
Text,
Modal,
StyleSheet,
SafeAreaView,
TouchableOpacity,
Alert,
ActivityIndicator,
ScrollView,
TouchableWithoutFeedback,
Keyboard,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import Input from './Input';
import Button from './Button';
import { COLORS } from '../constants/colors';
const PASSPORT_SERIES = [
'I-AS', 'I-MR', 'II-MR', 'I-AH', 'II-AH',
'I-LB', 'II-LB', 'I-BN', 'II-BN', 'I-DZ', 'II-DZ'
];
const EditProfileModal = ({
visible,
onClose,
onSave,
initialData = {},
isLoading = false
}) => {
const [formData, setFormData] = useState({
name: initialData.name || '',
phone: initialData.phone ? initialData.phone.toString() : '',
password: '',
passport_serie: initialData.passport_serie || '',
passport_id: initialData.passport_id ? initialData.passport_id.toString() : '',
});
const [errors, setErrors] = useState({});
const [showPassportPicker, setShowPassportPicker] = useState(false);
const phoneInputRef = useRef(null);
const passwordInputRef = useRef(null);
const passportIdInputRef = useRef(null);
const validateForm = () => {
const newErrors = {};
// Name validation (required, max 255 characters)
if (!formData.name.trim()) {
newErrors.name = 'At gerek';
} else if (formData.name.length > 255) {
newErrors.name = 'At 255 harpdan köp bolmaly däl';
}
// Phone validation (required, should be number)
if (!formData.phone.trim()) {
newErrors.phone = 'Telefon belgisi gerek';
} else if (!/^\d+$/.test(formData.phone)) {
newErrors.phone = 'Telefon belgisi diňe sanlardan durmalı';
} else if (formData.phone.length < 8) {
newErrors.phone = 'Telefon belgisi azyndan 8 san bolmaly';
}
// Password validation (optional, but if provided should be strong)
if (formData.password && formData.password.length < 6) {
newErrors.password = 'Parol azyndan 6 harp bolmaly';
}
// Passport ID validation (optional, but if provided should be number)
if (formData.passport_id && !/^\d+$/.test(formData.passport_id.trim())) {
newErrors.passport_id = 'Passport ID diňe sanlardan durmalı';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSave = () => {
if (!validateForm()) {
return;
}
// Prepare data for API call
const updateData = {
name: formData.name.trim(),
phone: parseInt(formData.phone),
};
// Only include optional fields if they're provided
if (formData.password) {
updateData.password = formData.password;
}
if (formData.passport_serie) {
updateData.passport_serie = formData.passport_serie;
}
if (formData.passport_id) {
updateData.passport_id = formData.passport_id.trim();
}
onSave(updateData);
};
const handleClose = () => {
// Reset form data and errors
setFormData({
name: initialData.name || '',
phone: initialData.phone ? initialData.phone.toString() : '',
password: '',
passport_serie: initialData.passport_serie || '',
passport_id: initialData.passport_id ? initialData.passport_id.toString() : '',
});
setErrors({});
onClose();
};
const updateFormData = (field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
// Clear error for this field if it exists
if (errors[field]) {
setErrors(prev => ({
...prev,
[field]: null
}));
}
};
const renderPassportSeriesPicker = () => {
if (!showPassportPicker) return null;
return (
<Modal
visible={showPassportPicker}
transparent={true}
animationType="slide"
onRequestClose={() => setShowPassportPicker(false)}
>
<TouchableWithoutFeedback onPress={() => setShowPassportPicker(false)}>
<View style={styles.modalOverlay}>
<View style={styles.pickerContainer}>
<View style={styles.pickerHeader}>
<Text style={styles.pickerTitle}>Passport seriýasy</Text>
<TouchableOpacity onPress={() => setShowPassportPicker(false)}>
<Text style={styles.pickerDoneText}>Boldy</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.pickerList}>
<TouchableOpacity
style={styles.pickerItem}
onPress={() => {
updateFormData('passport_serie', '');
setShowPassportPicker(false);
}}
>
<Text style={[styles.pickerItemText, styles.placeholderText]}>
Saýlaň
</Text>
</TouchableOpacity>
{PASSPORT_SERIES.map((series) => (
<TouchableOpacity
key={series}
style={[
styles.pickerItem,
formData.passport_serie === series && styles.selectedPickerItem
]}
onPress={() => {
updateFormData('passport_serie', series);
setShowPassportPicker(false);
}}
>
<Text style={[
styles.pickerItemText,
formData.passport_serie === series && styles.selectedPickerItemText
]}>
{series}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
return (
<Modal
visible={visible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={handleClose}
>
<SafeAreaView style={styles.container}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.content}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
<Ionicons name="close" size={24} color={COLORS.text} />
</TouchableOpacity>
<Text style={styles.title}>Şahsy maglumatlar</Text>
<View style={styles.placeholder} />
</View>
{/* Form */}
<ScrollView style={styles.form} showsVerticalScrollIndicator={false}>
<View style={styles.formSection}>
<Text style={styles.sectionTitle}>Esasy maglumatlar</Text>
<Input
label="Ady *"
value={formData.name}
onChangeText={(value) => updateFormData('name', value)}
error={errors.name}
maxLength={255}
returnKeyType="next"
onSubmitEditing={() => phoneInputRef.current?.focus()}
/>
<Input
ref={phoneInputRef}
label="Telefon belgisi *"
value={formData.phone}
onChangeText={(value) => updateFormData('phone', value)}
error={errors.phone}
keyboardType="numeric"
maxLength={8}
returnKeyType="next"
onSubmitEditing={() => passwordInputRef.current?.focus()}
/>
<Input
ref={passwordInputRef}
label="Täze parol"
value={formData.password}
onChangeText={(value) => updateFormData('password', value)}
error={errors.password}
secureTextEntry
placeholder="Parol üýtgetmezlik üçin boş goýuň"
returnKeyType="next"
onSubmitEditing={() => passportIdInputRef.current?.focus()}
/>
</View>
<View style={styles.formSection}>
<Text style={styles.sectionTitle}>Passport maglumatlary</Text>
<View style={styles.inputContainer}>
<Text style={styles.label}>Passport seriýasy</Text>
<TouchableOpacity
style={[styles.pickerButton, errors.passport_serie && styles.inputError]}
onPress={() => setShowPassportPicker(true)}
>
<Text style={[styles.pickerButtonText, !formData.passport_serie && styles.placeholderText]}>
{formData.passport_serie || 'Saýlaň'}
</Text>
<Ionicons name="chevron-down" size={20} color={COLORS.textSecondary} />
</TouchableOpacity>
{errors.passport_serie && (
<Text style={styles.errorText}>{errors.passport_serie}</Text>
)}
</View>
<Input
ref={passportIdInputRef}
label="Passport ID"
value={formData.passport_id}
onChangeText={(value) => updateFormData('passport_id', value)}
error={errors.passport_id}
keyboardType="numeric"
returnKeyType="done"
/>
</View>
<View style={styles.note}>
<Ionicons name="information-circle" size={16} color={COLORS.textSecondary} />
<Text style={styles.noteText}>
* belgisi bolan meýdanlar hökmany doldurulmaly
</Text>
</View>
</ScrollView>
{/* Save Button */}
<View style={styles.footer}>
<Button
title="Ýatda sakla"
onPress={handleSave}
disabled={isLoading}
style={styles.saveButton}
/>
{isLoading && (
<ActivityIndicator
size="small"
color={COLORS.primary}
style={styles.loader}
/>
)}
</View>
</View>
</TouchableWithoutFeedback>
{renderPassportSeriesPicker()}
</SafeAreaView>
</Modal>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
content: {
flex: 1,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: COLORS.border,
},
closeButton: {
padding: 4,
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: COLORS.text,
},
placeholder: {
width: 32,
},
form: {
flex: 1,
paddingHorizontal: 20,
},
formSection: {
marginTop: 24,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: COLORS.text,
marginBottom: 16,
},
inputContainer: {
marginBottom: 16,
},
label: {
fontSize: 16,
fontWeight: '500',
color: COLORS.text,
marginBottom: 8,
},
pickerButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
borderWidth: 1,
borderColor: COLORS.border,
borderRadius: 8,
backgroundColor: COLORS.surface,
},
pickerButtonText: {
fontSize: 16,
color: COLORS.text,
},
placeholderText: {
color: COLORS.textSecondary,
},
inputError: {
borderColor: COLORS.error,
},
errorText: {
fontSize: 14,
color: COLORS.error,
marginTop: 4,
},
note: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 16,
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: COLORS.surface,
borderRadius: 8,
},
noteText: {
fontSize: 14,
color: COLORS.textSecondary,
marginLeft: 8,
flex: 1,
},
footer: {
padding: 20,
flexDirection: 'row',
alignItems: 'center',
},
saveButton: {
flex: 1,
},
loader: {
marginLeft: 12,
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'flex-end',
},
pickerContainer: {
backgroundColor: COLORS.surface,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
maxHeight: 400,
},
pickerHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: COLORS.border,
},
pickerTitle: {
fontSize: 16,
fontWeight: '600',
color: COLORS.text,
},
pickerDoneText: {
fontSize: 16,
color: COLORS.primary,
fontWeight: '600',
},
pickerList: {
maxHeight: 300,
},
pickerItem: {
paddingHorizontal: 20,
paddingVertical: 16,
borderBottomWidth: 1,
borderBottomColor: COLORS.border,
},
selectedPickerItem: {
backgroundColor: COLORS.primary + '10',
},
pickerItemText: {
fontSize: 16,
color: COLORS.text,
},
selectedPickerItemText: {
color: COLORS.primary,
fontWeight: '600',
},
});
export default EditProfileModal;