From 7e6a0846dd40a1b5378b86842fdea32bbd2bb76f Mon Sep 17 00:00:00 2001 From: Nurmuhammet Allanov Date: Tue, 8 Jul 2025 11:20:33 +0500 Subject: [PATCH] passports added --- package-lock.json | 20 +++++ package.json | 3 +- src/components/ImageInput.js | 95 +++++++++++++++++++++++ src/screens/Loan/CreateLoanOrderScreen.js | 28 ++++++- 4 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 src/components/ImageInput.js diff --git a/package-lock.json b/package-lock.json index dd83136..ec031c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@react-navigation/native": "^7.1.14", "@react-navigation/stack": "^7.4.2", "expo": "~53.0.16", + "expo-image-picker": "~16.1.4", "expo-status-bar": "~2.2.3", "react": "19.0.0", "react-native": "0.79.5", @@ -3984,6 +3985,25 @@ "react": "*" } }, + "node_modules/expo-image-loader": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-5.1.0.tgz", + "integrity": "sha512-sEBx3zDQIODWbB5JwzE7ZL5FJD+DK3LVLWBVJy6VzsqIA6nDEnSFnsnWyCfCTSvbGigMATs1lgkC2nz3Jpve1Q==", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-picker": { + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-16.1.4.tgz", + "integrity": "sha512-bTmmxtw1AohUT+HxEBn2vYwdeOrj1CLpMXKjvi9FKSoSbpcarT4xxI0z7YyGwDGHbrJqyyic3I9TTdP2J2b4YA==", + "dependencies": { + "expo-image-loader": "~5.1.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-keep-awake": { "version": "14.1.4", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.1.4.tgz", diff --git a/package.json b/package.json index 62762c1..918184c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "react-native-modal-datetime-picker": "^15.0.1", "react-native-safe-area-context": "^5.5.1", "react-native-screens": "^4.11.1", - "react-native-svg": "^15.12.0" + "react-native-svg": "^15.12.0", + "expo-image-picker": "~16.1.4" }, "devDependencies": { "@babel/core": "^7.20.0" diff --git a/src/components/ImageInput.js b/src/components/ImageInput.js new file mode 100644 index 0000000..b4aeddf --- /dev/null +++ b/src/components/ImageInput.js @@ -0,0 +1,95 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Image, Alert } from 'react-native'; +import * as ImagePicker from 'expo-image-picker'; +import { Ionicons } from '@expo/vector-icons'; +import { COLORS } from '../constants/colors'; + +const ImageInput = ({ label, image, onChange }) => { + const [hasPermission, setHasPermission] = useState(null); + + useEffect(() => { + (async () => { + const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); + setHasPermission(status === 'granted'); + })(); + }, []); + + const pickImage = async () => { + if (hasPermission === false) { + Alert.alert('Permission required', 'Please grant photo library access to select images.'); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaType, + quality: 0.7, + base64: true, + }); + + if (!result.canceled && result.assets && result.assets.length > 0) { + const asset = result.assets[0]; + const fileObj = { + uri: asset.uri, + name: asset.fileName || `photo_${Date.now()}.jpg`, + type: asset.mimeType || 'image/jpeg', + }; + onChange && onChange(fileObj); + } + }; + + return ( + + {label && {label}} + + {image ? ( + + ) : ( + + + Saýla + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginBottom: 20, + }, + label: { + fontSize: 14, + fontWeight: '600', + color: COLORS.textPrimary, + marginBottom: 8, + }, + box: { + borderWidth: 1, + borderColor: COLORS.gray[300], + borderRadius: 12, + backgroundColor: COLORS.white, + width: 160, + height: 120, + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', + alignSelf: 'flex-start', + }, + placeholder: { + justifyContent: 'center', + alignItems: 'center', + }, + placeholderText: { + marginTop: 6, + color: COLORS.gray[400], + fontSize: 14, + }, + preview: { + width: '100%', + height: '100%', + borderRadius: 12, + }, +}); + +export default ImageInput; \ No newline at end of file diff --git a/src/screens/Loan/CreateLoanOrderScreen.js b/src/screens/Loan/CreateLoanOrderScreen.js index c195a25..cbe3712 100644 --- a/src/screens/Loan/CreateLoanOrderScreen.js +++ b/src/screens/Loan/CreateLoanOrderScreen.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, SafeAreaView } from 'react-native'; +import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, SafeAreaView, View } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useNavigation } from '@react-navigation/native'; import { COLORS } from '../../constants/colors'; @@ -11,6 +11,7 @@ import apiService from '../../services/apiService'; import { useAuth } from '../../contexts/AuthContext'; import { useBaseEnums } from '../../contexts/BaseEnumsContext'; import { StatusBar } from 'expo-status-bar'; +import ImageInput from '../../components/ImageInput'; const CreateLoanOrderScreen = () => { const navigation = useNavigation(); @@ -60,6 +61,11 @@ const CreateLoanOrderScreen = () => { const [cardMonth, setCardMonth] = useState(''); const [cardYear, setCardYear] = useState(''); + const [passportOne, setPassportOne] = useState(null); + const [passportTwo, setPassportTwo] = useState(null); + const [passportThree, setPassportThree] = useState(null); + const [passportFour, setPassportFour] = useState(null); + const monthOptions = Array.from({ length: 12 }, (_, i) => { const val = (i + 1).toString().padStart(2, '0'); return { value: val, label: val }; @@ -122,7 +128,7 @@ const CreateLoanOrderScreen = () => { const branchOptions = region && branchesByRegion[region] ? branchesByRegion[region].map((b) => ({ label: b.name, value: b.id })) : []; const handleSubmit = async () => { - if (!loanType || !loanAmount || !region || !branchId || !customerName || !customerSurname || !passportSerie || !passportId || !bornAt || !phone || !education || !marriageStatus || !passportAddress || !realAddress || !cardNumber || !cardName || !cardMonth || !cardYear) { + if (!loanType || !loanAmount || !region || !branchId || !customerName || !customerSurname || !passportSerie || !passportId || !bornAt || !phone || !education || !marriageStatus || !passportAddress || !realAddress || !passportOne || !passportTwo || !passportThree || !passportFour || !cardNumber || !cardName || !cardMonth || !cardYear) { Alert.alert('Error', 'Fill all required fields'); return; } @@ -166,6 +172,10 @@ const CreateLoanOrderScreen = () => { card_name: cardName, card_month: cardMonth, card_year: cardYear, + passport_one: passportOne, + passport_two: passportTwo, + passport_three: passportThree, + passport_four: passportFour, }; setLoading(true); @@ -200,6 +210,7 @@ const CreateLoanOrderScreen = () => { + @@ -211,7 +222,6 @@ const CreateLoanOrderScreen = () => { - @@ -230,6 +240,13 @@ const CreateLoanOrderScreen = () => { + + + + + + + {loading ? : Ýatda sakla} @@ -244,6 +261,11 @@ const styles = StyleSheet.create({ title: { fontSize: 24, fontWeight: 'bold', color: COLORS.textPrimary, marginBottom: 24 }, submitBtn: { marginTop: 32, backgroundColor: COLORS.primary, paddingVertical: 16, borderRadius: 8, alignItems: 'center' }, submitText: { color: COLORS.white, fontSize: 16, fontWeight: '600' }, + passportGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + }, }); export default CreateLoanOrderScreen; \ No newline at end of file