Compare commits

..

34 Commits

Author SHA1 Message Date
Mekan1206
ceabc0079c Refactor ProfileScreen to improve account deletion UI; update styles for danger items and remove alert for app information. 2025-09-17 18:11:30 +05:00
Mekan1206
9b26ddaa37 Add PDF download button to CardRequisiteOrderDetailsScreen for enhanced functionality. 2025-09-17 15:05:31 +05:00
Mekan1206
0d12a01812 Refactor multiple screens to replace SafeAreaView imports with react-native-safe-area-context, enhancing layout consistency across the application. 2025-09-17 14:55:18 +05:00
388d5e9a82 Remove card year validation logic from EditProfileModal for simplification and future refactoring. 2025-09-11 16:28:20 +05:00
99bdb01644 Update VerificationScreen to use react-native-confirmation-code-field for enhanced OTP input; add bundleIdentifier in app.json and include new dependency in package.json and package-lock.json. 2025-09-11 15:37:38 +05:00
a41851dfcc Refactor VerificationScreen to include KeyboardAvoidingView for improved keyboard handling; update text in CardTransactionOrderDetailsScreen for better localization. 2025-09-11 15:28:40 +05:00
Mekan1206
27f43a77fd Update app.json to change splash screen background color to white; refactor MainNavigator and screens to utilize dynamic padding with useSafeAreaInsets, enhancing layout consistency across Home, Menu, and Profile screens. 2025-09-11 13:11:19 +05:00
Mekan1206
27f16f3c38 Refactor MenuScreen to use useSafeAreaInsets for dynamic padding, replacing SafeAreaView with View for improved layout handling. 2025-09-11 12:45:47 +05:00
Mekan1206
2eb41db2e5 AAA Refactor app structure to use SafeAreaProvider, update StatusBar configuration, and enhance tab navigator styles. Add expo-system-ui dependency for improved status bar handling. 2025-09-11 12:39:19 +05:00
Mekan1206
61bcb6aa3f Remove dark mode configuration from splash screen settings in app.json 2025-09-11 12:12:56 +05:00
66f5271640 Add expo-splash-screen dependency and configure splash screen settings in app.json 2025-09-11 10:47:38 +05:00
db6a9e2bc0 wip 2025-09-11 10:42:07 +05:00
14db29ab61 wip 2025-09-11 10:40:42 +05:00
5ca3bb4f17 Update .gitignore to include .android and .ios directories for better build management 2025-09-11 10:32:51 +05:00
61fb0b2d96 Update app configuration and dependencies: add splash screen settings, update expo and related packages, and remove unused splash icon. 2025-09-11 10:24:11 +05:00
daae154aee Fix loading state management in HomeScreen by ensuring loadingTransactions is set to false after fetching transactions. 2025-09-11 01:54:16 +05:00
6751cc506e Add subtitle to Kart hereketleri section in HomeScreen and update styles for improved layout 2025-09-11 01:50:26 +05:00
056b4a5627 Implement transaction fetching and display in HomeScreen, update TransactionList for localization and improved data handling 2025-09-11 01:19:11 +05:00
d9b8e3f7ac wip 2025-09-10 21:06:10 +05:00
5f59609d6f wip 2025-09-10 21:05:54 +05:00
b9b604167b Add copyright notice and link to ProfileScreen, including styling for copyright text and link. 2025-09-10 21:04:08 +05:00
4630b195b9 Refactor transaction display in HomeScreen by integrating TransactionList component and removing redundant code for rendering transactions. 2025-09-10 20:49:24 +05:00
4efaf2543e Add transaction display feature to HomeScreen 2025-09-10 20:34:38 +05:00
b925b48dd4 a 2025-07-16 16:30:55 +05:00
Mekan1206
3380c9d85b add android and ios folders to git ignore 2025-07-15 22:37:36 +05:00
6b9631dcfd Upgrade 2025-07-15 22:34:20 +05:00
d4b81ee3e0 wip 2025-07-15 11:09:12 +05:00
7f182d3a07 update packages 2025-07-15 10:55:52 +05:00
dc4bef93c1 fix scroll 2025-07-15 10:20:14 +05:00
452887148d wip 2025-07-11 00:23:27 +05:00
50d299fb24 Profile credit data maskd 2025-07-10 23:45:29 +05:00
8f16f14796 scroll bug fixed 2025-07-10 23:34:57 +05:00
3eb5442a14 loan orders delete functionalatiy 2025-07-10 23:27:41 +05:00
f16e8c19b6 add delete account button && homescrolling 2025-07-10 23:10:49 +05:00
45 changed files with 1684 additions and 559 deletions

5
.gitignore vendored
View File

@@ -35,3 +35,8 @@ yarn-error.*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
android/
ios/
.android/
.ios/

7
App.js
View File

@@ -2,15 +2,18 @@ import React from 'react';
import { AuthProvider } from './src/contexts/AuthContext'; import { AuthProvider } from './src/contexts/AuthContext';
import RootNavigator from './src/navigation/RootNavigator'; import RootNavigator from './src/navigation/RootNavigator';
import { BaseEnumsProvider } from './src/contexts/BaseEnumsContext'; import { BaseEnumsProvider } from './src/contexts/BaseEnumsContext';
import { StatusBar } from 'react-native'; import { StatusBar } from 'expo-status-bar';
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function App() { export default function App() {
return ( return (
<SafeAreaProvider>
<AuthProvider> <AuthProvider>
<BaseEnumsProvider> <BaseEnumsProvider>
<StatusBar barStyle="dark-content" backgroundColor="#fff"/> <StatusBar style="dark" />
<RootNavigator /> <RootNavigator />
</BaseEnumsProvider> </BaseEnumsProvider>
</AuthProvider> </AuthProvider>
</SafeAreaProvider>
); );
} }

View File

@@ -10,10 +10,14 @@
"splash": { "splash": {
"image": "./assets/splash-icon.png", "image": "./assets/splash-icon.png",
"resizeMode": "contain", "resizeMode": "contain",
"backgroundColor": "#17b69b" "backgroundColor": "#ffffff"
}, },
"ios": { "ios": {
"supportsTablet": true "supportsTablet": true,
"statusBar": {
"barStyle": "dark-content"
},
"bundleIdentifier": "com.nurmuhammet.ali.tbbankonline"
}, },
"android": { "android": {
"adaptiveIcon": { "adaptiveIcon": {
@@ -21,7 +25,11 @@
"backgroundColor": "#17b69b" "backgroundColor": "#17b69b"
}, },
"edgeToEdgeEnabled": true, "edgeToEdgeEnabled": true,
"package": "com.nurmuhammet.ali.tbbankonline" "package": "com.nurmuhammet.ali.tbbankonline",
"statusBar": {
"barStyle": "dark-content",
"backgroundColor": "#ffffff"
}
}, },
"web": { "web": {
"favicon": "./assets/favicon.png" "favicon": "./assets/favicon.png"
@@ -30,6 +38,18 @@
"eas": { "eas": {
"projectId": "280bed78-9335-4b73-a686-15a9f726a7ad" "projectId": "280bed78-9335-4b73-a686-15a9f726a7ad"
} }
},
"plugins": [
"expo-font",
[
"expo-splash-screen",
{
"backgroundColor": "#ffffff",
"image": "./assets/splash-icon.png",
"imageWidth": 200
} }
],
"expo-system-ui"
]
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 156 KiB

787
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,28 +4,33 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
"android": "expo start --android", "android": "expo run:android",
"ios": "expo start --ios", "ios": "expo run:ios",
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^14.1.0", "@expo/vector-icons": "^14.1.0",
"@react-native-async-storage/async-storage": "^2.2.0", "@react-native-async-storage/async-storage": "2.1.2",
"@react-native-community/datetimepicker": "^7.7.0", "@react-native-community/datetimepicker": "8.4.1",
"@react-native-picker/picker": "^2.4.12", "@react-native-picker/picker": "^2.4.12",
"@react-navigation/bottom-tabs": "^7.4.2", "@react-navigation/bottom-tabs": "^7.4.2",
"@react-navigation/native": "^7.1.14", "@react-navigation/native": "^7.1.14",
"@react-navigation/stack": "^7.4.2", "@react-navigation/stack": "^7.4.2",
"expo": "~53.0.16", "expo": "53.0.22",
"expo-font": "~13.3.2",
"expo-image-picker": "~16.1.4",
"expo-splash-screen": "^31.0.8",
"expo-status-bar": "~2.2.3", "expo-status-bar": "~2.2.3",
"expo-system-ui": "~5.0.11",
"react": "19.0.0", "react": "19.0.0",
"react-native": "0.79.5", "react-native": "0.79.5",
"react-native-confirmation-code-field": "^8.0.1",
"react-native-gesture-handler": "~2.24.0",
"react-native-modal-datetime-picker": "^15.0.1", "react-native-modal-datetime-picker": "^15.0.1",
"react-native-safe-area-context": "^5.5.1", "react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.11.1", "react-native-screens": "^4.11.1",
"react-native-svg": "^15.12.0", "react-native-svg": "15.11.2",
"expo-image-picker": "~16.1.4", "react-native-webview": "13.13.5"
"react-native-webview": "^13.11.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0" "@babel/core": "^7.20.0"

View File

@@ -45,6 +45,8 @@ const EditProfileModal = ({
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const [showPassportPicker, setShowPassportPicker] = useState(false); const [showPassportPicker, setShowPassportPicker] = useState(false);
const [showMonthPicker, setShowMonthPicker] = useState(false);
const [showYearPicker, setShowYearPicker] = useState(false);
const phoneInputRef = useRef(null); const phoneInputRef = useRef(null);
const passwordInputRef = useRef(null); const passwordInputRef = useRef(null);
@@ -88,9 +90,9 @@ const EditProfileModal = ({
newErrors.card_name = 'Kartyň ady 255 harpdan köp bolmaly däl'; newErrors.card_name = 'Kartyň ady 255 harpdan köp bolmaly däl';
} }
// Card number validation (optional, must be 16 digits) // Card number validation (optional, must be 16 digits and allow hyphens)
if (formData.card_number && !/^\d{16}$/.test(formData.card_number.trim())) { if (formData.card_number && !/^[0-9]{4}-?[0-9]{4}-?[0-9]{4}-?[0-9]{4}$/.test(formData.card_number.trim())) {
newErrors.card_number = 'Kart belgisi 16 sany sandan durmaly'; newErrors.card_number = 'Kart belgisi 16 sany sandan durmaly (mysal: 9999-9999-9999-9999)';
} }
// Card month validation (optional, 1-12) // Card month validation (optional, 1-12)
@@ -102,9 +104,9 @@ const EditProfileModal = ({
} }
// Card year validation (optional, 4 digits and reasonable year) // Card year validation (optional, 4 digits and reasonable year)
if (formData.card_year && !/^\d{4}$/.test(formData.card_year.trim())) { // if (formData.card_year) {
newErrors.card_year = 'Ýyl 4 sany sandan durmaly';
} // }
setErrors(newErrors); setErrors(newErrors);
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
@@ -147,7 +149,8 @@ const EditProfileModal = ({
} }
if (formData.card_number) { if (formData.card_number) {
updateData.card_number = formData.card_number.trim(); // Remove all non-digit characters before sending
updateData.card_number = formData.card_number.replace(/[^\d]/g, '');
} }
onSave(updateData); onSave(updateData);
@@ -167,6 +170,7 @@ const EditProfileModal = ({
card_number: initialData.card_number ? initialData.card_number.toString() : '', card_number: initialData.card_number ? initialData.card_number.toString() : '',
}); });
setErrors({}); setErrors({});
Keyboard.dismiss(); // Dismiss keyboard when modal closes
onClose(); onClose();
}; };
@@ -244,6 +248,129 @@ const EditProfileModal = ({
); );
}; };
const renderMonthPicker = () => {
if (!showMonthPicker) return null;
const months = Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, '0'));
return (
<Modal
visible={showMonthPicker}
transparent={true}
animationType="slide"
onRequestClose={() => setShowMonthPicker(false)}
>
<TouchableWithoutFeedback onPress={() => setShowMonthPicker(false)}>
<View style={styles.modalOverlay}>
<View style={styles.pickerContainer}>
<View style={styles.pickerHeader}>
<Text style={styles.pickerTitle}>Kart aýy</Text>
<TouchableOpacity onPress={() => setShowMonthPicker(false)}>
<Text style={styles.pickerDoneText}>Boldy</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.pickerList}>
<TouchableOpacity
style={styles.pickerItem}
onPress={() => {
updateFormData('card_month', '');
setShowMonthPicker(false);
}}
>
<Text style={[styles.pickerItemText, styles.placeholderText]}>
Saýlaň
</Text>
</TouchableOpacity>
{months.map((month) => (
<TouchableOpacity
key={month}
style={[
styles.pickerItem,
formData.card_month === month && styles.selectedPickerItem
]}
onPress={() => {
updateFormData('card_month', month);
setShowMonthPicker(false);
}}
>
<Text style={[
styles.pickerItemText,
formData.card_month === month && styles.selectedPickerItemText
]}>
{month}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
const renderYearPicker = () => {
if (!showYearPicker) return null;
const currentYear = new Date().getFullYear();
const years = Array.from({ length: 75 }, (_, i) => String(currentYear - 10 + i)); // +/- 10 years
return (
<Modal
visible={showYearPicker}
transparent={true}
animationType="slide"
onRequestClose={() => setShowYearPicker(false)}
>
<TouchableWithoutFeedback onPress={() => setShowYearPicker(false)}>
<View style={styles.modalOverlay}>
<View style={styles.pickerContainer}>
<View style={styles.pickerHeader}>
<Text style={styles.pickerTitle}>Kartyň senesi</Text>
<TouchableOpacity onPress={() => setShowYearPicker(false)}>
<Text style={styles.pickerDoneText}>Boldy</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.pickerList}>
<TouchableOpacity
style={styles.pickerItem}
onPress={() => {
updateFormData('card_year', '');
setShowYearPicker(false);
}}
>
<Text style={[styles.pickerItemText, styles.placeholderText]}>
Saýlaň
</Text>
</TouchableOpacity>
{years.map((year) => (
<TouchableOpacity
key={year}
style={[
styles.pickerItem,
formData.card_year === year && styles.selectedPickerItem
]}
onPress={() => {
updateFormData('card_year', year);
setShowYearPicker(false);
}}
>
<Text style={[
styles.pickerItemText,
formData.card_year === year && styles.selectedPickerItemText
]}>
{year}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};
return ( return (
<Modal <Modal
visible={visible} visible={visible}
@@ -256,7 +383,6 @@ const EditProfileModal = ({
behavior={Platform.OS === 'ios' ? 'padding' : undefined} behavior={Platform.OS === 'ios' ? 'padding' : undefined}
style={{ flex: 1 }} style={{ flex: 1 }}
> >
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.content}> <View style={styles.content}>
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
@@ -268,7 +394,13 @@ const EditProfileModal = ({
</View> </View>
{/* Form */} {/* Form */}
<ScrollView style={styles.form} showsVerticalScrollIndicator={false} keyboardShouldPersistTaps="handled" contentContainerStyle={{paddingBottom:80}}> <ScrollView
style={styles.form}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
contentContainerStyle={{paddingBottom:80}}
keyboardDismissMode="on-drag"
>
<View style={styles.formSection}> <View style={styles.formSection}>
<Text style={styles.sectionTitle}>Esasy maglumatlar</Text> <Text style={styles.sectionTitle}>Esasy maglumatlar</Text>
@@ -334,6 +466,7 @@ const EditProfileModal = ({
error={errors.passport_id} error={errors.passport_id}
keyboardType="numeric" keyboardType="numeric"
returnKeyType="next" returnKeyType="next"
maxLength={6}
onSubmitEditing={() => cardNameInputRef.current?.focus()} onSubmitEditing={() => cardNameInputRef.current?.focus()}
/> />
</View> </View>
@@ -356,36 +489,55 @@ const EditProfileModal = ({
ref={cardNumberInputRef} ref={cardNumberInputRef}
label="Kart belgisi" label="Kart belgisi"
value={formData.card_number} value={formData.card_number}
onChangeText={(value) => updateFormData('card_number', value)} onChangeText={(value) => {
const unmaskedValue = value.replace(/[^\d]/g, ''); // Remove non-digits
let maskedValue = '';
for (let i = 0; i < unmaskedValue.length; i++) {
if (i > 0 && i % 4 === 0) {
maskedValue += '-';
}
maskedValue += unmaskedValue[i];
}
updateFormData('card_number', maskedValue);
}}
error={errors.card_number} error={errors.card_number}
keyboardType="numeric" keyboardType="numeric"
maxLength={16} maxLength={19} // 16 digits + 3 hyphens
returnKeyType="next" returnKeyType="next"
onSubmitEditing={() => cardMonthInputRef.current?.focus()} onSubmitEditing={() => cardMonthInputRef.current?.focus()}
/> />
<Input <View style={styles.inputContainer}>
ref={cardMonthInputRef} <Text style={styles.label}>Kart aýy (MM)</Text>
label="Kart aýy (MM)" <TouchableOpacity
value={formData.card_month} style={[styles.pickerButton, errors.card_month && styles.inputError]}
onChangeText={(value) => updateFormData('card_month', value)} onPress={() => setShowMonthPicker(true)}
error={errors.card_month} >
keyboardType="numeric" <Text style={[styles.pickerButtonText, !formData.card_month && styles.placeholderText]}>
maxLength={2} {formData.card_month || 'Saýlaň'}
returnKeyType="next" </Text>
onSubmitEditing={() => cardYearInputRef.current?.focus()} <Ionicons name="chevron-down" size={20} color={COLORS.textSecondary} />
/> </TouchableOpacity>
{errors.card_month && (
<Text style={styles.errorText}>{errors.card_month}</Text>
)}
</View>
<Input <View style={styles.inputContainer}>
ref={cardYearInputRef} <Text style={styles.label}>Kartyň senesi (YYYY)</Text>
label="Kartyň senesi (YYYY)" <TouchableOpacity
value={formData.card_year} style={[styles.pickerButton, errors.card_year && styles.inputError]}
onChangeText={(value) => updateFormData('card_year', value)} onPress={() => setShowYearPicker(true)}
error={errors.card_year} >
keyboardType="numeric" <Text style={[styles.pickerButtonText, !formData.card_year && styles.placeholderText]}>
maxLength={4} {formData.card_year || 'Saýlaň'}
returnKeyType="done" </Text>
/> <Ionicons name="chevron-down" size={20} color={COLORS.textSecondary} />
</TouchableOpacity>
{errors.card_year && (
<Text style={styles.errorText}>{errors.card_year}</Text>
)}
</View>
</View> </View>
<View style={styles.note}> <View style={styles.note}>
@@ -413,10 +565,11 @@ const EditProfileModal = ({
)} )}
</View> </View>
</View> </View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView> </KeyboardAvoidingView>
{renderPassportSeriesPicker()} {renderPassportSeriesPicker()}
{renderMonthPicker()}
{renderYearPicker()}
</SafeAreaView> </SafeAreaView>
</Modal> </Modal>
); );

View File

@@ -0,0 +1,132 @@
import React from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { COLORS } from '../constants/colors';
const TransactionList = ({ transactions }) => {
if (!transactions || transactions.length === 0) {
return (
<View style={styles.container}>
<Text style={styles.emptyText}>Soňky 10 günde kart hereketleri ýok...</Text>
</View>
);
}
const groupedTransactions = transactions.reduce((acc, transaction) => {
const date = new Date(transaction.date).toLocaleDateString('tk', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
if (!acc[date]) {
acc[date] = [];
}
acc[date].push(transaction);
return acc;
}, {});
const renderTransactionItem = ({ item }) => (
<View style={styles.transactionItem}>
<View style={styles.transactionIcon}>
<Ionicons
name={item.type === 'credit' ? 'arrow-down' : 'arrow-up'}
size={20}
color={item.type === 'credit' ? COLORS.success : COLORS.danger}
/>
</View>
<View style={styles.transactionDetails}>
<Text style={styles.transactionTitle}>{item.description}</Text>
<Text style={styles.transactionDate}>{item.time ?? '-'}</Text>
</View>
<Text
style={[
styles.transactionAmount,
{ color: item.type === 'credit' ? COLORS.success : COLORS.textPrimary },
]}
>
{item.type === 'credit' ? '+' : '-'}
{item.amount} {item.currency}
</Text>
</View>
);
const renderTransactionSection = ({ item: date }) => (
<View>
<Text style={styles.transactionDateHeader}>{date}</Text>
<FlatList
data={groupedTransactions[date]}
renderItem={renderTransactionItem}
keyExtractor={(item) => item.id?.toString()}
scrollEnabled={false}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
</View>
);
return (
<FlatList
data={Object.keys(groupedTransactions)}
renderItem={renderTransactionSection}
keyExtractor={(date) => date}
scrollEnabled={false}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
},
emptyText: {
textAlign: 'center',
color: COLORS.textSecondary,
},
transactionItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
},
transactionIcon: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: COLORS.background,
alignItems: 'center',
justifyContent: 'center',
marginRight: 16,
},
transactionDetails: {
flex: 1,
},
transactionTitle: {
fontSize: 16,
fontWeight: '600',
color: COLORS.textPrimary,
marginBottom: 2,
},
transactionDate: {
fontSize: 13,
color: COLORS.textSecondary,
},
transactionAmount: {
fontSize: 16,
fontWeight: '700',
},
transactionDateHeader: {
fontSize: 14,
fontWeight: '600',
color: COLORS.textSecondary,
backgroundColor: COLORS.white,
paddingTop: 16,
paddingBottom: 8,
paddingHorizontal: 4,
},
separator: {
height: 1,
backgroundColor: COLORS.gray[200],
marginLeft: 60,
},
});
export default TransactionList;

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { COLORS } from '../constants/colors'; import { COLORS } from '../constants/colors';
import { View, ActivityIndicator, Platform, OS } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import HomeScreen from '../screens/Main/HomeScreen'; import HomeScreen from '../screens/Main/HomeScreen';
import MenuNavigator from './MenuNavigator'; import MenuNavigator from './MenuNavigator';
@@ -10,6 +12,8 @@ import ProfileScreen from '../screens/Main/ProfileScreen';
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
const MainNavigator = () => { const MainNavigator = () => {
const insets = useSafeAreaInsets();
return ( return (
<Tab.Navigator <Tab.Navigator
screenOptions={({ route }) => ({ screenOptions={({ route }) => ({
@@ -33,9 +37,17 @@ const MainNavigator = () => {
backgroundColor: COLORS.white, backgroundColor: COLORS.white,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: COLORS.gray[200], borderTopColor: COLORS.gray[200],
paddingBottom: 8, paddingBottom: (insets.bottom || 16),
paddingTop: 8, paddingTop: 8,
height: 88, height: Platform.OS === 'ios' ? 100 : (82 + (insets.bottom || 16)),
elevation: 8,
shadowColor: COLORS.gray[900],
shadowOffset: {
width: 0,
height: -2,
},
shadowOpacity: 0.1,
shadowRadius: 4,
}, },
tabBarLabelStyle: { tabBarLabelStyle: {
fontSize: 12, fontSize: 12,
@@ -43,7 +55,7 @@ const MainNavigator = () => {
marginTop: 4, marginTop: 4,
}, },
tabBarItemStyle: { tabBarItemStyle: {
paddingVertical: 4, paddingVertical: 2,
}, },
})} })}
> >

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import { SafeAreaView } from 'react-native-safe-area-context';
import MenuScreen from '../screens/Main/MenuScreen'; import MenuScreen from '../screens/Main/MenuScreen';
import LoanRemainingOrdersScreen from '../screens/Loan/LoanRemainingOrdersScreen'; import LoanRemainingOrdersScreen from '../screens/Loan/LoanRemainingOrdersScreen';
import CreateLoanRemainingOrderScreen from '../screens/Loan/CreateLoanRemainingOrderScreen'; import CreateLoanRemainingOrderScreen from '../screens/Loan/CreateLoanRemainingOrderScreen';

View File

@@ -3,7 +3,6 @@ import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
SafeAreaView,
KeyboardAvoidingView, KeyboardAvoidingView,
ScrollView, ScrollView,
Platform, Platform,
@@ -12,6 +11,7 @@ import {
TouchableWithoutFeedback, TouchableWithoutFeedback,
Keyboard, Keyboard,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import Button from '../../components/Button'; import Button from '../../components/Button';

View File

@@ -3,7 +3,6 @@ import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
SafeAreaView,
KeyboardAvoidingView, KeyboardAvoidingView,
ScrollView, ScrollView,
Platform, Platform,
@@ -11,6 +10,7 @@ import {
TouchableWithoutFeedback, TouchableWithoutFeedback,
Keyboard, Keyboard,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import Button from '../../components/Button'; import Button from '../../components/Button';

View File

@@ -3,27 +3,40 @@ import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
SafeAreaView,
Alert, Alert,
TouchableOpacity, TouchableOpacity,
TouchableWithoutFeedback, TouchableWithoutFeedback,
Keyboard, Keyboard,
BackHandler, BackHandler,
Platform, Platform,
KeyboardAvoidingView,
} from 'react-native'; } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { useFocusEffect } from '@react-navigation/native'; import { useFocusEffect } from '@react-navigation/native';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import Button from '../../components/Button'; import Button from '../../components/Button';
import Input from '../../components/Input'; import {
CodeField,
Cursor,
useBlurOnFulfill,
useClearByFocusCell,
} from 'react-native-confirmation-code-field';
import { COLORS } from '../../constants/colors'; import { COLORS } from '../../constants/colors';
const CELL_COUNT = 6;
const VerificationScreen = ({ navigation }) => { const VerificationScreen = ({ navigation }) => {
const [code, setCode] = useState(''); const [code, setCode] = useState('');
const [countdown, setCountdown] = useState(60); const [countdown, setCountdown] = useState(60);
const [canResend, setCanResend] = useState(false); const [canResend, setCanResend] = useState(false);
const { verify, isLoading, pendingVerification, clearPendingVerification } = useAuth(); const { verify, isLoading, pendingVerification, clearPendingVerification } = useAuth();
const countdownRef = useRef(null); const countdownRef = useRef(null);
const ref = useBlurOnFulfill({ value: code, cellCount: CELL_COUNT });
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
value: code,
setValue: setCode,
});
useEffect(() => { useEffect(() => {
if (!pendingVerification) { if (!pendingVerification) {
@@ -73,18 +86,18 @@ const VerificationScreen = ({ navigation }) => {
}, 1000); }, 1000);
}; };
const handleVerify = async () => { const handleVerify = async (filledCode) => {
if (!code.trim()) { if (!filledCode.trim()) {
Alert.alert('Ýalňyşlyk', 'Tassyklama koduny giriziň'); Alert.alert('Ýalňyşlyk', 'Tassyklama koduny giriziň');
return; return;
} }
if (!/^\d{6}$/.test(code.trim())) { if (!/^\d{6}$/.test(filledCode.trim())) {
Alert.alert('Ýalňyşlyk', 'Tassyklama kody 6 sanly bolmaly'); Alert.alert('Ýalňyşlyk', 'Tassyklama kody 6 sanly bolmaly');
return; return;
} }
const result = await verify(code.trim()); const result = await verify(filledCode.trim());
if (result.success) { if (result.success) {
// Navigation will be handled by AuthContext // Navigation will be handled by AuthContext
@@ -93,6 +106,8 @@ const VerificationScreen = ({ navigation }) => {
} }
}; };
const handleVerifyWrapper = () => handleVerify(code);
const handleResendCode = async () => { const handleResendCode = async () => {
if (!canResend) return; if (!canResend) return;
@@ -134,6 +149,10 @@ const VerificationScreen = ({ navigation }) => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.content}> <View style={styles.content}>
<View style={styles.logoContainer}> <View style={styles.logoContainer}>
@@ -148,22 +167,29 @@ const VerificationScreen = ({ navigation }) => {
</View> </View>
<View style={styles.formContainer}> <View style={styles.formContainer}>
<Input <CodeField
label="Tassyklama kody" ref={ref}
{...props}
value={code} value={code}
onChangeText={setCode} onChangeText={setCode}
placeholder="123456" cellCount={CELL_COUNT}
keyboardType="numeric" rootStyle={styles.otpContainer}
maxLength={6} keyboardType="number-pad"
style={styles.codeInput} textContentType="oneTimeCode"
textAlign="center" renderCell={({ index, symbol, isFocused }) => (
returnKeyType="done" <Text
onSubmitEditing={handleVerify} key={index}
style={[styles.otpInput, isFocused && styles.otpInputHighlight]}
onLayout={getCellOnLayoutHandler(index)}>
{symbol || (isFocused ? <Cursor/> : null)}
</Text>
)}
onSubmitEditing={handleVerifyWrapper}
/> />
<Button <Button
title="Tassykla" title="Tassykla"
onPress={handleVerify} onPress={handleVerifyWrapper}
loading={isLoading} loading={isLoading}
style={styles.verifyButton} style={styles.verifyButton}
/> />
@@ -182,6 +208,7 @@ const VerificationScreen = ({ navigation }) => {
</View> </View>
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
</KeyboardAvoidingView>
</SafeAreaView> </SafeAreaView>
); );
}; };
@@ -251,10 +278,25 @@ const styles = StyleSheet.create({
formContainer: { formContainer: {
alignItems: 'center', alignItems: 'center',
}, },
codeInput: { otpContainer: {
width: '100%', width: '90%',
marginBottom: 32, marginBottom: 32,
}, },
otpInput: {
width: 45,
height: 60,
lineHeight: 58,
borderWidth: 1,
borderRadius: 12,
borderColor: COLORS.gray[300],
color: COLORS.textPrimary,
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
},
otpInputHighlight: {
borderColor: COLORS.primary,
},
verifyButton: { verifyButton: {
width: '100%', width: '100%',
marginBottom: 32, marginBottom: 32,

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Modal } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, Modal } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, ScrollView, SafeAreaView, Image, Alert, Linking } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, ScrollView, Image, Alert, Linking } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
@@ -7,8 +8,8 @@ import apiService from '../../services/apiService';
import { COLORS } from '../../constants/colors'; import { COLORS } from '../../constants/colors';
import { useBaseEnums } from '../../contexts/BaseEnumsContext'; import { useBaseEnums } from '../../contexts/BaseEnumsContext';
const CARD_BG = '#EFF6FF'; const CARD_BG = '#F1F9F1';
const CIRCLE_BG = '#7FB3FF'; const CIRCLE_BG = '#A2E4A4';
const CardOrdersScreen = () => { const CardOrdersScreen = () => {
const navigation = useNavigation(); const navigation = useNavigation();

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Image, Linking } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, Image, Linking } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Image } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, Image } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
@@ -137,6 +138,9 @@ const CardRequisiteOrderDetailsScreen = () => {
</View> </View>
{/* Actions */} {/* Actions */}
<TouchableOpacity style={styles.actionBtn} onPress={handleDownload}>
<Text style={styles.actionText}>Ýükle (PDF)</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.deleteBtn} onPress={handleDelete}> <TouchableOpacity style={styles.deleteBtn} onPress={handleDelete}>
<Text style={styles.deleteText}>Poz</Text> <Text style={styles.deleteText}>Poz</Text>
</TouchableOpacity> </TouchableOpacity>

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback, useRef } from 'react'; import React, { useState, useCallback, useRef } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView, Modal } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, Modal } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
@@ -93,7 +94,7 @@ const CardTransactionOrderDetailsScreen = () => {
if (!order) { if (!order) {
return ( return (
<View style={styles.centered}> <View style={styles.centered}>
<Text>No data</Text> <Text>Maglumat tapylmady</Text>
</View> </View>
); );
} }

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView, Modal } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, Modal } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView, ScrollView } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, SafeAreaView, View } from 'react-native'; import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { COLORS } from '../../constants/colors'; import { COLORS } from '../../constants/colors';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView, SafeAreaView } from 'react-native'; import { Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { COLORS } from '../../constants/colors'; import { COLORS } from '../../constants/colors';

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
@@ -47,6 +48,32 @@ const LoanOrdersScreen = () => {
const amount = (item.loan_amount + ' TMT') || '-'; const amount = (item.loan_amount + ' TMT') || '-';
const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : ''; const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '';
const handleDelete = async (orderId) => {
Alert.alert(
"Sargyty pozmak",
"Siz hakykatdanam bu sargyty pozmak isleýärsiňizmi?",
[
{ text: "Ýok", style: "cancel" },
{
text: "Hawa",
onPress: async () => {
try {
const res = await apiService.deleteLoanOrder(orderId);
if (res.success) {
setOrders(prevOrders => prevOrders.filter(order => order.id !== orderId));
} else {
console.warn('Failed to delete order:', res.error);
}
} catch (e) {
console.error('Error deleting order:', e.message);
}
},
},
],
{ cancelable: true }
);
};
return ( return (
<TouchableOpacity style={styles.card} onPress={() => navigation.navigate('LoanOrderDetails', { orderId: item.id })}> <TouchableOpacity style={styles.card} onPress={() => navigation.navigate('LoanOrderDetails', { orderId: item.id })}>
<View style={styles.circle}> <View style={styles.circle}>
@@ -58,8 +85,11 @@ const LoanOrdersScreen = () => {
<Text style={{ color: COLORS.textPrimary }}>{amount}</Text> <Text style={{ color: COLORS.textPrimary }}>{amount}</Text>
</Text> </Text>
<Text style={styles.dateText}>{created}</Text> <Text style={styles.dateText}>Döredilen senesi: {created}</Text>
</View> </View>
<TouchableOpacity onPress={() => handleDelete(item.id)} style={styles.deleteButton}>
<Ionicons name="trash" size={24} color={COLORS.error} />
</TouchableOpacity>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
@@ -112,6 +142,7 @@ const styles = StyleSheet.create({
borderRadius: 12, borderRadius: 12,
padding: 16, padding: 16,
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between',
}, },
circle: { circle: {
width: 40, width: 40,
@@ -127,7 +158,7 @@ const styles = StyleSheet.create({
passportText: { fontWeight: '700', color: COLORS.textPrimary, marginBottom: 4 }, passportText: { fontWeight: '700', color: COLORS.textPrimary, marginBottom: 4 },
accountLabel: { color: COLORS.textSecondary, fontSize: 14 }, accountLabel: { color: COLORS.textSecondary, fontSize: 14 },
accountValue: { color: COLORS.textPrimary, marginBottom: 4 }, accountValue: { color: COLORS.textPrimary, marginBottom: 4 },
dateText: { color: COLORS.textSecondary, fontSize: 12, textAlign: 'right' }, dateText: { color: COLORS.textSecondary, fontSize: 12, textAlign: 'left' },
emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },
emptyText: { fontSize: 16, color: COLORS.textSecondary }, emptyText: { fontSize: 16, color: COLORS.textSecondary },
fab: { fab: {
@@ -142,6 +173,9 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
elevation: 4, elevation: 4,
}, },
deleteButton: {
padding: 8,
},
}); });
export default LoanOrdersScreen; export default LoanOrdersScreen;

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, SafeAreaView } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
@@ -47,6 +48,32 @@ const LoanPaidOffLetterOrdersScreen = () => {
const accountLine = `Karz hasaby:`; const accountLine = `Karz hasaby:`;
const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : ''; const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '';
const handleDelete = async (orderId) => {
Alert.alert(
"Sargyty pozmak",
"Siz hakykatdanam bu sargyty pozmak isleýärsiňizmi?",
[
{ text: "Ýok", style: "cancel" },
{
text: "Hawa",
onPress: async () => {
try {
const res = await apiService.deleteLoanPaidOffLetterOrder(orderId);
if (res.success) {
setOrders(prevOrders => prevOrders.filter(order => order.id !== orderId));
} else {
console.warn('Failed to delete order:', res.error);
}
} catch (e) {
console.error('Error deleting order:', e.message);
}
},
},
],
{ cancelable: true }
);
};
return ( return (
<TouchableOpacity style={styles.card} onPress={() => navigation.navigate('LoanPaidOffLetterOrderDetails', { orderId: item.id })}> <TouchableOpacity style={styles.card} onPress={() => navigation.navigate('LoanPaidOffLetterOrderDetails', { orderId: item.id })}>
<View style={styles.circle}> <View style={styles.circle}>
@@ -58,6 +85,9 @@ const LoanPaidOffLetterOrdersScreen = () => {
<Text style={styles.accountValue}>{item.loan_contract_number || '-'}</Text> <Text style={styles.accountValue}>{item.loan_contract_number || '-'}</Text>
<Text style={styles.dateText}>{created}</Text> <Text style={styles.dateText}>{created}</Text>
</View> </View>
<TouchableOpacity onPress={() => handleDelete(item.id)} style={styles.deleteButton}>
<Ionicons name="trash" size={24} color={COLORS.error} />
</TouchableOpacity>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
@@ -110,6 +140,7 @@ const styles = StyleSheet.create({
borderRadius: 12, borderRadius: 12,
padding: 16, padding: 16,
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between',
}, },
circle: { circle: {
width: 40, width: 40,
@@ -140,6 +171,9 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
elevation: 4, elevation: 4,
}, },
deleteButton: {
padding: 8,
},
}); });
export default LoanPaidOffLetterOrdersScreen; export default LoanPaidOffLetterOrdersScreen;

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity, Alert } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
@@ -63,7 +64,7 @@ const LoanRemainingOrderDetailsScreen = () => {
} }
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}> <TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
<Ionicons name="close" size={28} color={COLORS.textPrimary} /> <Ionicons name="close" size={28} color={COLORS.textPrimary} />
</TouchableOpacity> </TouchableOpacity>
@@ -82,7 +83,7 @@ const LoanRemainingOrderDetailsScreen = () => {
<TouchableOpacity style={styles.deleteBtn} onPress={handleDelete}> <TouchableOpacity style={styles.deleteBtn} onPress={handleDelete}>
<Text style={styles.deleteText}>Poz</Text> <Text style={styles.deleteText}>Poz</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </SafeAreaView>
); );
}; };

View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, Modal, ScrollView, Alert, SafeAreaView, Pressable } from 'react-native'; import { View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, RefreshControl, Modal, ScrollView, Alert, Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useFocusEffect } from '@react-navigation/native';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
@@ -65,6 +66,32 @@ const LoanRemainingOrdersScreen = () => {
const accountLabel = 'Karz hasaby:'; const accountLabel = 'Karz hasaby:';
const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : ''; const created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '';
const handleDelete = async (orderId) => {
Alert.alert(
"Sargyty pozmak",
"Siz hakykatdanam bu sargyty pozmak isleýärsiňizmi?",
[
{ text: "Ýok", style: "cancel" },
{
text: "Hawa",
onPress: async () => {
try {
const res = await apiService.deleteLoanRemainingOrder(orderId);
if (res.success) {
setOrders(prevOrders => prevOrders.filter(order => order.id !== orderId));
} else {
console.warn('Failed to delete order:', res.error);
}
} catch (e) {
console.error('Error deleting order:', e.message);
}
},
},
],
{ cancelable: true }
);
};
return ( return (
<TouchableOpacity style={styles.card} onPress={() => handleItemPress(item)}> <TouchableOpacity style={styles.card} onPress={() => handleItemPress(item)}>
<View style={styles.circle}> <View style={styles.circle}>
@@ -74,8 +101,11 @@ const LoanRemainingOrdersScreen = () => {
<Text style={styles.passportText}>{passportLine}</Text> <Text style={styles.passportText}>{passportLine}</Text>
<Text style={styles.accountLabel}>{accountLabel}</Text> <Text style={styles.accountLabel}>{accountLabel}</Text>
<Text style={styles.accountValue}>{item.account_number}</Text> <Text style={styles.accountValue}>{item.account_number}</Text>
<Text style={styles.dateText}>{created}</Text> <Text style={styles.dateText}>Döredilen senesi: {created}</Text>
</View> </View>
<TouchableOpacity onPress={() => handleDelete(item.id)} style={styles.deleteButton}>
<Ionicons name="trash" size={24} color={COLORS.error} />
</TouchableOpacity>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
@@ -104,7 +134,7 @@ const LoanRemainingOrdersScreen = () => {
renderItem={renderItem} renderItem={renderItem}
contentContainerStyle={orders.length === 0 && styles.emptyContainer} contentContainerStyle={orders.length === 0 && styles.emptyContainer}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
ListEmptyComponent={<Text style={styles.emptyText}>No orders yet</Text>} ListEmptyComponent={<Text style={styles.emptyText}>Sargyt ýok</Text>}
/> />
{/* Floating Action Button */} {/* Floating Action Button */}
@@ -204,6 +234,7 @@ const styles = StyleSheet.create({
borderRadius: 12, borderRadius: 12,
padding: 16, padding: 16,
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', // Added to push delete button to the right
}, },
circle: { circle: {
width: 40, width: 40,
@@ -318,6 +349,9 @@ const styles = StyleSheet.create({
fontWeight: '600', fontWeight: '600',
color: COLORS.textPrimary, color: COLORS.textPrimary,
}, },
deleteButton: {
padding: 8,
},
}); });
export default LoanRemainingOrdersScreen; export default LoanRemainingOrdersScreen;

View File

@@ -3,26 +3,116 @@ import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
SafeAreaView,
ScrollView, ScrollView,
TouchableOpacity, TouchableOpacity,
ActivityIndicator, ActivityIndicator,
RefreshControl,
FlatList,
} from 'react-native'; } from 'react-native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { COLORS } from '../../constants/colors'; import { COLORS } from '../../constants/colors';
import MetricCard from '../../components/MetricCard'; import MetricCard from '../../components/MetricCard';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
import TransactionList from '../../components/TransactionList';
const STATIC_TRANSACTIONS = [
{
id: 1,
type: 'debit',
description: 'Canva Design and Publishing',
date: '2025-09-07T10:30:00Z',
amount: 10.0,
currency: 'EUR',
},
{
id: 2,
type: 'debit',
description: 'Google',
date: '2025-09-07T11:00:00Z',
amount: 12.99,
currency: 'SAR',
},
{
id: 3,
type: 'debit',
description: 'Canva Design and Publishing',
date: '2025-09-05T14:15:00Z',
amount: 10.0,
currency: 'EUR',
},
{
id: 4,
type: 'credit',
description: 'Töleg alyňýan hasap',
date: '2025-09-03T09:00:00Z',
amount: 20.0,
currency: 'USD',
},
{
id: 5,
type: 'debit',
description: 'Комиссия за оплату',
date: '2025-09-03T09:01:00Z',
amount: 0.12,
currency: 'USD',
},
{
id: 6,
type: 'credit',
description: 'Hasaby doldurmak',
date: '2025-09-02T18:45:00Z',
amount: 10.0,
currency: 'USD',
},
{
id: 7,
type: 'debit',
description: 'Canva Design and Publishing',
date: '2025-09-01T12:00:00Z',
amount: 10.0,
currency: 'EUR',
},
{
id: 8,
type: 'debit',
description: 'Netflix',
date: '2025-08-28T16:00:00Z',
amount: 9.99,
currency: 'USD',
},
{
id: 9,
type: 'credit',
description: 'Bank Transfer',
date: '2025-08-25T08:20:00Z',
amount: 500.0,
currency: 'TMT',
},
{
id: 10,
type: 'debit',
description: 'Amazon',
date: '2025-08-23T19:55:00Z',
amount: 45.5,
currency: 'USD',
},
];
const HomeScreen = () => { const HomeScreen = () => {
const { user, logout } = useAuth(); const { user, logout } = useAuth();
const insets = useSafeAreaInsets();
const [metrics, setMetrics] = useState(null); const [metrics, setMetrics] = useState(null);
const [loadingMetrics, setLoadingMetrics] = useState(true); const [loadingMetrics, setLoadingMetrics] = useState(true);
const [cardBalance, setCardBalance] = useState(null); const [cardBalance, setCardBalance] = useState(null);
const [loadingCardBalance, setLoadingCardBalance] = useState(true); const [loadingCardBalance, setLoadingCardBalance] = useState(true);
const [cardBalanceError, setCardBalanceError] = useState(null); const [cardBalanceError, setCardBalanceError] = useState(null);
const [isBalanceVisible, setIsBalanceVisible] = useState(true); const [isBalanceVisible, setIsBalanceVisible] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [transactions, setTransactions] = useState([]);
const [loadingTransactions, setLoadingTransactions] = useState(true);
const showBalanceCard = !loadingCardBalance && cardBalanceError === null && cardBalance !== null; const showBalanceCard = !loadingCardBalance && cardBalanceError === null && cardBalance !== null;
@@ -48,16 +138,20 @@ const HomeScreen = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const fetchCardBalance = async () => { const fetchCardData = async () => {
// Ensure user has filled all required card & passport fields // Ensure user has filled all required card & passport fields
if (!user?.passport_serie || !user?.passport_id || !user?.card_number || !user?.card_month || !user?.card_year) { if (!user?.passport_serie || !user?.passport_id || !user?.card_number || !user?.card_month || !user?.card_year) {
setLoadingCardBalance(false); setLoadingCardBalance(false);
setLoadingTransactions(false);
return; return;
} }
setLoadingCardBalance(true);
setLoadingTransactions(true);
try { try {
const res = await apiService.getCardBalanceQuickCheck(); const res = await apiService.getCardBalanceQuickCheck();
if (res.success) { if (res.success) {
// Try common balance keys else fallback to raw
const raw = res.data; const raw = res.data;
let balanceValue = null; let balanceValue = null;
if (raw && typeof raw === 'object') { if (raw && typeof raw === 'object') {
@@ -71,21 +165,102 @@ const HomeScreen = () => {
} }
} catch (e) { } catch (e) {
console.warn('Error fetching card balance:', e); console.warn('Error fetching card balance:', e);
setCardBalanceError('Error fetching balance');
} finally { } finally {
setLoadingCardBalance(false); setLoadingCardBalance(false);
} }
try {
const transRes = await apiService.getCardTransactionsLastMonth();
if (transRes.success) {
setTransactions(transRes.data || []);
} else {
console.warn('Failed to fetch transactions:', transRes.error);
}
} catch (e) {
console.warn('Error fetching transactions:', e);
} finally {
setLoadingTransactions(false);
}
}; };
fetchCardBalance(); fetchCardData();
}, [user]); }, [user]);
const handleRefresh = async () => {
setRefreshing(true);
await Promise.all([
(async () => {
try {
const response = await apiService.getMetrics();
if (response.success) {
setMetrics(response.data);
} else {
console.warn('Failed to fetch metrics during refresh:', response.error);
}
} catch (error) {
console.warn('Error fetching metrics during refresh:', error);
}
})(),
(async () => {
if (!user?.passport_serie || !user?.passport_id || !user?.card_number || !user?.card_month || !user?.card_year) {
setCardBalance(null); // Clear previous balance if conditions are not met
setCardBalanceError(null);
setTransactions([]);
setLoadingTransactions(false);
return;
}
try {
const res = await apiService.getCardBalanceQuickCheck();
if (res.success) {
const raw = res.data;
let balanceValue = null;
if (raw && typeof raw === 'object') {
balanceValue = raw.balance ?? raw.card_balance ?? raw.amount ?? null;
}
setCardBalance(balanceValue);
setCardBalanceError(null);
} else {
console.warn('Failed to fetch card balance during refresh:', res.error);
setCardBalanceError(res.error || 'Error');
}
} catch (e) {
console.warn('Error fetching card balance during refresh:', e);
}
try {
const transRes = await apiService.getCardTransactionsLastMonth();
if (transRes.success) {
setTransactions(transRes.data || []);
} else {
console.warn('Failed to fetch transactions during refresh:', transRes.error);
}
} catch (e) {
console.warn('Error fetching transactions during refresh:', e);
} finally {
setLoadingTransactions(false);
}
})(),
]);
setRefreshing(false);
};
return ( return (
<SafeAreaView style={styles.container}> <View style={styles.container}>
<StatusBar style="dark" /> <StatusBar style="dark" />
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}> <ScrollView
style={styles.scrollView}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={COLORS.primary}
/>
}
>
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={[styles.header, { paddingTop: insets.top + 16 }]}>
<View> <View>
<Text style={styles.greeting}>Salam,</Text> <Text style={styles.greeting}>Salam,</Text>
<Text style={styles.userName}>{user?.name || 'Ulanyjy'}</Text> <Text style={styles.userName}>{user?.name || 'Ulanyjy'}</Text>
@@ -94,7 +269,7 @@ const HomeScreen = () => {
{/* Metrics */} {/* Metrics */}
<View style={styles.section}> <View style={styles.section}>
<Text style={styles.sectionTitle}>Metrics</Text> <Text style={styles.sectionTitle}>Görkezijiler</Text>
{loadingMetrics ? ( {loadingMetrics ? (
<ActivityIndicator color={COLORS.primary} style={{ marginTop: 16 }} /> <ActivityIndicator color={COLORS.primary} style={{ marginTop: 16 }} />
) : ( ) : (
@@ -126,8 +301,27 @@ const HomeScreen = () => {
</View> </View>
</View> </View>
)} )}
{/* Card Transactions */}
{showBalanceCard && (
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Kart hereketleri</Text>
<Text style={styles.sectionSubtitle}>Soňky 10 günde</Text>
<TouchableOpacity>
{/* <Text style={styles.seeAllText}>Hemmesi</Text> */}
</TouchableOpacity>
</View>
{loadingTransactions ? (
<ActivityIndicator color={COLORS.primary} style={{ marginTop: 16 }} />
) : (
<TransactionList transactions={transactions} />
)}
</View>
)}
</ScrollView> </ScrollView>
</SafeAreaView> </View>
); );
}; };
@@ -144,7 +338,6 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 24, paddingHorizontal: 24,
paddingTop: 16,
paddingBottom: 24, paddingBottom: 24,
backgroundColor: COLORS.white, backgroundColor: COLORS.white,
marginBottom: 16, marginBottom: 16,
@@ -218,16 +411,16 @@ const styles = StyleSheet.create({
padding: 20, padding: 20,
}, },
sectionHeader: { sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16, marginBottom: 16,
}, },
sectionTitle: { sectionTitle: {
fontSize: 18, fontSize: 18,
fontWeight: 'bold', fontWeight: 'bold',
color: COLORS.textPrimary, color: COLORS.textPrimary,
marginBottom: 16, },
sectionSubtitle: {
fontSize: 14,
color: COLORS.textSecondary,
}, },
seeAllText: { seeAllText: {
fontSize: 14, fontSize: 14,
@@ -255,58 +448,6 @@ const styles = StyleSheet.create({
color: COLORS.textSecondary, color: COLORS.textSecondary,
textAlign: 'center', textAlign: 'center',
}, },
transactionsList: {
gap: 16,
},
transactionItem: {
flexDirection: 'row',
alignItems: 'center',
},
transactionIcon: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: COLORS.backgroundSecondary,
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
transactionDetails: {
flex: 1,
},
transactionTitle: {
fontSize: 16,
fontWeight: '600',
color: COLORS.textPrimary,
},
transactionDate: {
fontSize: 14,
color: COLORS.textSecondary,
},
transactionAmount: {
fontSize: 16,
fontWeight: 'bold',
color: COLORS.success,
},
servicesGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
serviceItem: {
width: '48%',
padding: 16,
backgroundColor: COLORS.backgroundSecondary,
borderRadius: 12,
alignItems: 'center',
marginBottom: 12,
},
serviceText: {
fontSize: 12,
color: COLORS.textSecondary,
textAlign: 'center',
marginTop: 8,
},
metricsGrid: { metricsGrid: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',

View File

@@ -4,16 +4,17 @@ import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
SafeAreaView,
ScrollView, ScrollView,
TouchableOpacity, TouchableOpacity,
} from 'react-native'; } from 'react-native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { COLORS } from '../../constants/colors'; import { COLORS } from '../../constants/colors';
const MenuScreen = () => { const MenuScreen = () => {
const navigation = useNavigation(); const navigation = useNavigation();
const insets = useSafeAreaInsets();
const menuSections = [ const menuSections = [
{ {
@@ -66,10 +67,10 @@ const MenuScreen = () => {
}; };
return ( return (
<SafeAreaView style={styles.container}> <View style={styles.container}>
<StatusBar style="dark" /> <StatusBar style="dark" />
<View style={styles.header}> <View style={[styles.header, { paddingTop: insets.top + 16 }]}>
<Text style={styles.headerTitle}>Hyzmatlar</Text> <Text style={styles.headerTitle}>Hyzmatlar</Text>
</View> </View>
@@ -105,7 +106,7 @@ const MenuScreen = () => {
<View style={styles.bottomSpacing} /> <View style={styles.bottomSpacing} />
</ScrollView> </ScrollView>
</SafeAreaView> </View>
); );
}; };
@@ -116,7 +117,6 @@ const styles = StyleSheet.create({
}, },
header: { header: {
paddingHorizontal: 24, paddingHorizontal: 24,
paddingTop: 16,
paddingBottom: 24, paddingBottom: 24,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: COLORS.gray[200], borderBottomColor: COLORS.gray[200],

View File

@@ -3,15 +3,16 @@ import {
View, View,
Text, Text,
StyleSheet, StyleSheet,
SafeAreaView,
ScrollView, ScrollView,
TouchableOpacity, TouchableOpacity,
Alert, Alert,
ActivityIndicator, ActivityIndicator,
RefreshControl, RefreshControl,
Linking,
} from 'react-native'; } from 'react-native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import apiService from '../../services/apiService'; import apiService from '../../services/apiService';
import EditProfileModal from '../../components/EditProfileModal'; import EditProfileModal from '../../components/EditProfileModal';
@@ -19,6 +20,7 @@ import { COLORS } from '../../constants/colors';
const ProfileScreen = () => { const ProfileScreen = () => {
const { user, logout, fetchUserProfile } = useAuth(); const { user, logout, fetchUserProfile } = useAuth();
const insets = useSafeAreaInsets();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [profileData, setProfileData] = useState(null); const [profileData, setProfileData] = useState(null);
@@ -80,7 +82,8 @@ const ProfileScreen = () => {
items: [ items: [
// { id: 11, title: 'Ulanmak düzgünleri', icon: 'document-text', hasArrow: true }, // { id: 11, title: 'Ulanmak düzgünleri', icon: 'document-text', hasArrow: true },
// { id: 12, title: 'Gizlinlik syýasaty', icon: 'lock-open', hasArrow: true }, // { id: 12, title: 'Gizlinlik syýasaty', icon: 'lock-open', hasArrow: true },
{ id: 13, title: 'Programma barada', icon: 'information-circle', value: 'v1.0.0', hasArrow: true }, { id: 13, title: 'Programma barada', icon: 'information-circle', value: 'v1.0.0', hasArrow: false },
{ id: 14, title: 'Hasaby poz', icon: 'trash', hasArrow: false, danger: true },
], ],
}, },
]; ];
@@ -102,6 +105,12 @@ const ProfileScreen = () => {
case 5: // Security case 5: // Security
handleSecuritySettings(); handleSecuritySettings();
break; break;
case 13: // About App
// Alert.alert('Üns beriň', 'Programma barada maglumatlar açylýar...');
break;
case 14: // Delete Account
handleDeleteAccount();
break;
default: default:
// Alert.alert('Üns beriň', 'Bu funksiýa entek işlenok'); // Alert.alert('Üns beriň', 'Bu funksiýa entek işlenok');
} }
@@ -201,6 +210,40 @@ const ProfileScreen = () => {
} }
}; };
const handleDeleteAccount = () => {
Alert.alert(
'Hasaby pozmak',
'Hasabyňyzy pozmak isleýändigiňize ynamlymy? Bu amal yzyna gaýtarylmaz.',
[
{
text: 'Ýok',
style: 'cancel',
},
{
text: 'Hawa',
style: 'destructive',
onPress: async () => {
try {
setIsLoading(true);
const result = await apiService.deleteAccount();
if (result.message == 'user deleted successfully') {
Alert.alert('Üstünlik', 'Hasabyňyz üstünlikli pozuldy.');
logout(); // Log out the user after successful deletion
} else {
Alert.alert('Ýalňyşlyk', result.error || 'Hasaby pozmak amala aşmady.');
}
} catch (error) {
Alert.alert('Ýalňyşlyk', error.message || 'Hasaby pozmakda näsazlyk ýüze çykdy.');
} finally {
setIsLoading(false);
}
},
},
]
);
};
const handleNotificationSettings = () => { const handleNotificationSettings = () => {
Alert.alert('Bildirişler', 'Bildiriş sazlamalary sahypasy açylýar...'); Alert.alert('Bildirişler', 'Bildiriş sazlamalary sahypasy açylýar...');
}; };
@@ -237,10 +280,10 @@ const ProfileScreen = () => {
const currentUser = profileData || user; const currentUser = profileData || user;
return ( return (
<SafeAreaView style={styles.container}> <View style={styles.container}>
<StatusBar style="dark" /> <StatusBar style="dark" />
<View style={styles.header}> <View style={[styles.header, { paddingTop: insets.top + 16 }]}>
<Text style={styles.headerTitle}>Profil</Text> <Text style={styles.headerTitle}>Profil</Text>
</View> </View>
@@ -294,16 +337,16 @@ const ProfileScreen = () => {
> >
<View style={styles.profileItemLeft}> <View style={styles.profileItemLeft}>
<View style={styles.profileItemIcon}> <View style={styles.profileItemIcon}>
<Ionicons name={item.icon} size={20} color={COLORS.primary} /> <Ionicons name={item.icon} size={20} color={item.danger ? COLORS.error : COLORS.primary} />
</View> </View>
<Text style={styles.profileItemTitle}>{item.title}</Text> <Text style={item.danger ? styles.profileItemTitleDanger : styles.profileItemTitle}>{item.title}</Text>
</View> </View>
<View style={styles.profileItemRight}> <View style={styles.profileItemRight}>
{item.value && ( {item.value && (
<Text style={styles.profileItemValue}>{item.value}</Text> <Text style={item.danger ? styles.profileItemValueDanger : styles.profileItemValue}>{item.value}</Text>
)} )}
{item.hasArrow && ( {item.hasArrow && (
<Ionicons name="chevron-forward" size={16} color={COLORS.gray[400]} /> <Ionicons name="chevron-forward" size={16} color={item.danger ? COLORS.error : COLORS.gray[400]} />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@@ -321,6 +364,21 @@ const ProfileScreen = () => {
</View> </View>
<View style={styles.bottomSpacing} /> <View style={styles.bottomSpacing} />
{/* Copyright */}
<Text style={styles.copyrightText}>
Copyright{' '}
<Text
style={styles.link}
onPress={() => Linking.openURL('https://webulgam.com')}
>
Webulgam IT Company
</Text>{' '}
© {new Date().getFullYear()}.
</Text>
<Text style={styles.copyrightText}>
Ähli hukular goralan.
</Text>
</ScrollView> </ScrollView>
{/* Edit Profile Modal */} {/* Edit Profile Modal */}
@@ -331,7 +389,7 @@ const ProfileScreen = () => {
initialData={profileData || user} initialData={profileData || user}
isLoading={isUpdatingProfile} isLoading={isUpdatingProfile}
/> />
</SafeAreaView> </View>
); );
}; };
@@ -343,7 +401,6 @@ const styles = StyleSheet.create({
header: { header: {
backgroundColor: COLORS.white, backgroundColor: COLORS.white,
paddingHorizontal: 24, paddingHorizontal: 24,
paddingTop: 16,
paddingBottom: 24, paddingBottom: 24,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: COLORS.gray[200], borderBottomColor: COLORS.gray[200],
@@ -485,6 +542,24 @@ const styles = StyleSheet.create({
bottomSpacing: { bottomSpacing: {
height: 24, height: 24,
}, },
copyrightText: {
fontSize: 12,
color: COLORS.textSecondary,
textAlign: 'center',
},
link: {
color: COLORS.primary,
},
profileItemTitleDanger: {
fontSize: 16,
color: COLORS.error,
fontWeight: 'bold',
},
profileItemValueDanger: {
fontSize: 14,
color: COLORS.error,
fontWeight: 'bold',
},
}); });
export default ProfileScreen; export default ProfileScreen;

View File

@@ -67,6 +67,37 @@ class ApiService {
return authService.getTransactions(page, limit); return authService.getTransactions(page, limit);
} }
async getCardTransactionsLastMonth() {
try {
const response = await authService.getCardTransactionsLastMonth();
if (response && response.data && Array.isArray(response.data.transactions)) {
const mappedTransactions = response.data.transactions.map((t, index) => ({
id: `${t.rrn}-${index}`,
type: t.sign === '-' ? 'debit' : 'credit',
description: t.binfo || t.opername,
date: `${t.operdate}`,
time: t.trantime,
amount: t.currOperSum,
currency: t.currCode,
})).reverse();
return {
success: true,
data: mappedTransactions,
};
}
console.warn('Invalid transaction data structure:', response);
return {
success: false,
error: 'Could not parse transaction data.',
};
} catch (error) {
return {
success: false,
error: error.message,
};
}
}
// Transfer and payments // Transfer and payments
async transferMoney(data) { async transferMoney(data) {
return authService.transferMoney(data); return authService.transferMoney(data);

View File

@@ -162,6 +162,10 @@ class AuthService {
return this.makeRequest(`/user/cards/${cardId}/unblock`, null, true); return this.makeRequest(`/user/cards/${cardId}/unblock`, null, true);
} }
async getCardTransactionsLastMonth() {
return this.makeRequest('/card-transactions-last-month', null, true, 'GET');
}
async changePassword(currentPassword, newPassword) { async changePassword(currentPassword, newPassword) {
return this.makeRequest('/user/change-password', { return this.makeRequest('/user/change-password', {
current_password: currentPassword, current_password: currentPassword,
@@ -170,7 +174,7 @@ class AuthService {
} }
async deleteAccount() { async deleteAccount() {
return this.makeRequest('/user/delete-account', null, true, 'DELETE'); return this.makeRequest(API_CONFIG.ENDPOINTS.AUTH.DELETE_USER, null, true, 'POST');
} }
// ================================ // ================================