amazing design
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, forwardRef } from 'react';
|
||||||
import { View, TextInput, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
import { View, TextInput, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { COLORS } from '../constants/colors';
|
import { COLORS } from '../constants/colors';
|
||||||
|
|
||||||
const Input = ({
|
const Input = forwardRef(({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
onChangeText,
|
onChangeText,
|
||||||
@@ -17,7 +17,7 @@ const Input = ({
|
|||||||
returnKeyType = 'done',
|
returnKeyType = 'done',
|
||||||
onSubmitEditing,
|
onSubmitEditing,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}, ref) => {
|
||||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ const Input = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<TextInput
|
<TextInput
|
||||||
|
ref={ref}
|
||||||
style={[styles.input, leftIcon && styles.inputWithLeftIcon]}
|
style={[styles.input, leftIcon && styles.inputWithLeftIcon]}
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={onChangeText}
|
onChangeText={onChangeText}
|
||||||
@@ -70,7 +71,7 @@ const Input = ({
|
|||||||
{error && <Text style={styles.errorText}>{error}</Text>}
|
{error && <Text style={styles.errorText}>{error}</Text>}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const AuthNavigator = () => {
|
|||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
|
gestureEnabled: true,
|
||||||
cardStyleInterpolator: ({ current, layouts }) => {
|
cardStyleInterpolator: ({ current, layouts }) => {
|
||||||
return {
|
return {
|
||||||
cardStyle: {
|
cardStyle: {
|
||||||
@@ -30,7 +31,7 @@ const AuthNavigator = () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
initialRouteName={pendingVerification ? 'Verification' : 'Login'}
|
initialRouteName={pendingVerification?.phone ? 'Verification' : 'Login'}
|
||||||
>
|
>
|
||||||
<Stack.Screen name="Login" component={LoginScreen} />
|
<Stack.Screen name="Login" component={LoginScreen} />
|
||||||
<Stack.Screen name="Register" component={RegisterScreen} />
|
<Stack.Screen name="Register" component={RegisterScreen} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -24,6 +24,7 @@ const LoginScreen = ({ navigation }) => {
|
|||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const { login, isLoading } = useAuth();
|
const { login, isLoading } = useAuth();
|
||||||
|
const passwordInputRef = useRef(null);
|
||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
@@ -76,6 +77,8 @@ const LoginScreen = ({ navigation }) => {
|
|||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
<View style={styles.content}>
|
||||||
<View style={styles.logoContainer}>
|
<View style={styles.logoContainer}>
|
||||||
<Logo width={100} height={100} />
|
<Logo width={100} height={100} />
|
||||||
<Text style={styles.appName}>TBBANK ONLINE</Text>
|
<Text style={styles.appName}>TBBANK ONLINE</Text>
|
||||||
@@ -97,9 +100,12 @@ const LoginScreen = ({ navigation }) => {
|
|||||||
leftIcon="call"
|
leftIcon="call"
|
||||||
error={errors.phone}
|
error={errors.phone}
|
||||||
maxLength={8}
|
maxLength={8}
|
||||||
|
returnKeyType="next"
|
||||||
|
onSubmitEditing={() => passwordInputRef.current?.focus()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
ref={passwordInputRef}
|
||||||
label="Parol"
|
label="Parol"
|
||||||
value={password}
|
value={password}
|
||||||
onChangeText={setPassword}
|
onChangeText={setPassword}
|
||||||
@@ -107,6 +113,8 @@ const LoginScreen = ({ navigation }) => {
|
|||||||
secureTextEntry
|
secureTextEntry
|
||||||
leftIcon="lock-closed"
|
leftIcon="lock-closed"
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
|
returnKeyType="done"
|
||||||
|
onSubmitEditing={handleLogin}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -126,6 +134,8 @@ const LoginScreen = ({ navigation }) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
@@ -144,6 +154,9 @@ const styles = StyleSheet.create({
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
},
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
logoContainer: {
|
logoContainer: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingTop: 60,
|
paddingTop: 60,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
Platform,
|
Platform,
|
||||||
Alert,
|
Alert,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
Keyboard,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
@@ -25,6 +27,9 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
});
|
});
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const { register, isLoading } = useAuth();
|
const { register, isLoading } = useAuth();
|
||||||
|
const nameInputRef = useRef(null);
|
||||||
|
const passwordInputRef = useRef(null);
|
||||||
|
const confirmPasswordInputRef = useRef(null);
|
||||||
|
|
||||||
const updateField = (field, value) => {
|
const updateField = (field, value) => {
|
||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
@@ -82,7 +87,7 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const navigateToLogin = () => {
|
const navigateToLogin = () => {
|
||||||
navigation.navigate('Login');
|
navigation.replace('Login');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -97,6 +102,8 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
|
<View style={styles.content}>
|
||||||
<View style={styles.logoContainer}>
|
<View style={styles.logoContainer}>
|
||||||
<Logo width={100} height={100} />
|
<Logo width={100} height={100} />
|
||||||
<Text style={styles.appName}>TBBANK ONLINE</Text>
|
<Text style={styles.appName}>TBBANK ONLINE</Text>
|
||||||
@@ -117,18 +124,24 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
leftIcon="call"
|
leftIcon="call"
|
||||||
error={errors.phone}
|
error={errors.phone}
|
||||||
maxLength={8}
|
maxLength={8}
|
||||||
|
returnKeyType="next"
|
||||||
|
onSubmitEditing={() => nameInputRef.current?.focus()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
ref={nameInputRef}
|
||||||
label="Ady"
|
label="Ady"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChangeText={(value) => updateField('name', value)}
|
onChangeText={(value) => updateField('name', value)}
|
||||||
placeholder="Doly adyňyzy giriziň"
|
placeholder="Doly adyňyzy giriziň"
|
||||||
leftIcon="person"
|
leftIcon="person"
|
||||||
error={errors.name}
|
error={errors.name}
|
||||||
|
returnKeyType="next"
|
||||||
|
onSubmitEditing={() => passwordInputRef.current?.focus()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
ref={passwordInputRef}
|
||||||
label="Parol"
|
label="Parol"
|
||||||
value={formData.password}
|
value={formData.password}
|
||||||
onChangeText={(value) => updateField('password', value)}
|
onChangeText={(value) => updateField('password', value)}
|
||||||
@@ -136,9 +149,12 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
secureTextEntry
|
secureTextEntry
|
||||||
leftIcon="lock-closed"
|
leftIcon="lock-closed"
|
||||||
error={errors.password}
|
error={errors.password}
|
||||||
|
returnKeyType="next"
|
||||||
|
onSubmitEditing={() => confirmPasswordInputRef.current?.focus()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
ref={confirmPasswordInputRef}
|
||||||
label="Paroly tassyklaň"
|
label="Paroly tassyklaň"
|
||||||
value={formData.confirmPassword}
|
value={formData.confirmPassword}
|
||||||
onChangeText={(value) => updateField('confirmPassword', value)}
|
onChangeText={(value) => updateField('confirmPassword', value)}
|
||||||
@@ -146,6 +162,8 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
secureTextEntry
|
secureTextEntry
|
||||||
leftIcon="lock-closed"
|
leftIcon="lock-closed"
|
||||||
error={errors.confirmPassword}
|
error={errors.confirmPassword}
|
||||||
|
returnKeyType="done"
|
||||||
|
onSubmitEditing={handleRegister}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -165,6 +183,8 @@ const RegisterScreen = ({ navigation }) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
@@ -183,6 +203,9 @@ const styles = StyleSheet.create({
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
},
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
logoContainer: {
|
logoContainer: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingTop: 60,
|
paddingTop: 60,
|
||||||
|
|||||||
@@ -6,8 +6,13 @@ import {
|
|||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
Alert,
|
Alert,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
Keyboard,
|
||||||
|
BackHandler,
|
||||||
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
|
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 Input from '../../components/Input';
|
||||||
@@ -35,6 +40,21 @@ const VerificationScreen = ({ navigation }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Handle Android hardware back button
|
||||||
|
useFocusEffect(
|
||||||
|
React.useCallback(() => {
|
||||||
|
const onBackPress = () => {
|
||||||
|
handleGoBack();
|
||||||
|
return true; // Prevent default behavior
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
||||||
|
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
|
||||||
|
}
|
||||||
|
}, [handleGoBack])
|
||||||
|
);
|
||||||
|
|
||||||
const startCountdown = () => {
|
const startCountdown = () => {
|
||||||
setCountdown(60);
|
setCountdown(60);
|
||||||
setCanResend(false);
|
setCanResend(false);
|
||||||
@@ -73,7 +93,7 @@ const VerificationScreen = ({ navigation }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResendCode = () => {
|
const handleResendCode = async () => {
|
||||||
if (!canResend) return;
|
if (!canResend) return;
|
||||||
|
|
||||||
// Here you would call the API to resend the code
|
// Here you would call the API to resend the code
|
||||||
@@ -82,15 +102,22 @@ const VerificationScreen = ({ navigation }) => {
|
|||||||
startCountdown();
|
startCountdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoBack = () => {
|
const handleGoBack = React.useCallback(() => {
|
||||||
clearPendingVerification();
|
clearPendingVerification();
|
||||||
|
|
||||||
|
// Check if we can go back, if not navigate to Login
|
||||||
|
if (navigation.canGoBack()) {
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
};
|
} else {
|
||||||
|
navigation.navigate('Login');
|
||||||
|
}
|
||||||
|
}, [clearPendingVerification, navigation]);
|
||||||
|
|
||||||
const formatPhoneNumber = (phone) => {
|
const formatPhoneNumber = (phone) => {
|
||||||
if (!phone) return '';
|
if (!phone) return '';
|
||||||
const phoneStr = phone.toString();
|
const phoneStr = phone.toString();
|
||||||
return `+993 ${phoneStr.slice(0, 2)} ${phoneStr.slice(2, 5)} ${phoneStr.slice(5)}`;
|
|
||||||
|
return `+993 ${phoneStr.slice(0, 2)} ${phoneStr.slice(2, 4)}-${phoneStr.slice(4, 6)}-${phoneStr.slice(6)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!pendingVerification) {
|
if (!pendingVerification) {
|
||||||
@@ -107,6 +134,7 @@ const VerificationScreen = ({ navigation }) => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<View style={styles.logoContainer}>
|
<View style={styles.logoContainer}>
|
||||||
<View style={styles.verificationIcon}>
|
<View style={styles.verificationIcon}>
|
||||||
@@ -129,6 +157,8 @@ const VerificationScreen = ({ navigation }) => {
|
|||||||
maxLength={6}
|
maxLength={6}
|
||||||
style={styles.codeInput}
|
style={styles.codeInput}
|
||||||
textAlign="center"
|
textAlign="center"
|
||||||
|
returnKeyType="done"
|
||||||
|
onSubmitEditing={handleVerify}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -151,6 +181,7 @@ const VerificationScreen = ({ navigation }) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user