initial: add login
This commit is contained in:
235
app/auth/login.tsx
Normal file
235
app/auth/login.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import { ThemedText } from "@/components/themed-text";
|
||||
import { ThemedView } from "@/components/themed-view";
|
||||
import { showToastError } from "@/config";
|
||||
import { TOKEN } from "@/constants";
|
||||
import { login } from "@/controller/AuthController";
|
||||
import {
|
||||
getStorageItem,
|
||||
removeStorageItem,
|
||||
setStorageItem,
|
||||
} from "@/utils/storage";
|
||||
import { parseJwtToken } from "@/utils/token";
|
||||
import { useRouter } from "expo-router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
export default function LoginScreen() {
|
||||
const router = useRouter();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const checkLogin = useCallback(async () => {
|
||||
const token = await getStorageItem(TOKEN);
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const parsed = parseJwtToken(token);
|
||||
console.log("Parse Token: ", parsed);
|
||||
|
||||
const { exp } = parsed;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const oneHour = 60 * 60;
|
||||
if (exp - now < oneHour) {
|
||||
await removeStorageItem(TOKEN);
|
||||
} else {
|
||||
router.replace("/(tabs)");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
checkLogin();
|
||||
}, [checkLogin]);
|
||||
|
||||
const handleLogin = async () => {
|
||||
// Validate input
|
||||
if (!username.trim() || !password.trim()) {
|
||||
showToastError("Lỗi", "Vui lòng nhập tài khoản và mật khẩu");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const body: Model.LoginRequestBody = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
|
||||
const response = await login(body);
|
||||
|
||||
// Nếu thành công, lưu token và chuyển sang (tabs)
|
||||
console.log("Login thành công với data:", response.data);
|
||||
if (response?.data.token) {
|
||||
// Lưu token vào storage nếu cần (thêm logic này sau)
|
||||
await setStorageItem(TOKEN, response.data.token);
|
||||
console.log("Token:", response.data.token);
|
||||
router.replace("/(tabs)");
|
||||
}
|
||||
} catch (error) {
|
||||
showToastError(
|
||||
"Lỗi",
|
||||
error instanceof Error ? error.message : "Đăng nhập thất bại"
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||
<ThemedView style={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.headerContainer}>
|
||||
<ThemedText type="title" style={styles.title}>
|
||||
SGW App
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.subtitle}>
|
||||
Đăng nhập để tiếp tục
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
{/* Form */}
|
||||
<View style={styles.formContainer}>
|
||||
{/* Username Input */}
|
||||
<View style={styles.inputGroup}>
|
||||
<ThemedText style={styles.label}>Tài khoản</ThemedText>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Nhập tài khoản"
|
||||
placeholderTextColor="#999"
|
||||
value={username}
|
||||
onChangeText={setUsername}
|
||||
editable={!loading}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Password Input */}
|
||||
<View style={styles.inputGroup}>
|
||||
<ThemedText style={styles.label}>Mật khẩu</ThemedText>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Nhập mật khẩu"
|
||||
placeholderTextColor="#999"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
editable={!loading}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Login Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.loginButton,
|
||||
loading && styles.loginButtonDisabled,
|
||||
]}
|
||||
onPress={handleLogin}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#fff" size="small" />
|
||||
) : (
|
||||
<Text style={styles.loginButtonText}>Đăng nhập</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Footer text */}
|
||||
<View style={styles.footerContainer}>
|
||||
<ThemedText style={styles.footerText}>
|
||||
Chưa có tài khoản?{" "}
|
||||
<Text style={styles.linkText}>Đăng ký ngay</Text>
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollContainer: {
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 20,
|
||||
justifyContent: "center",
|
||||
},
|
||||
headerContainer: {
|
||||
marginBottom: 40,
|
||||
alignItems: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 8,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
opacity: 0.7,
|
||||
},
|
||||
formContainer: {
|
||||
gap: 16,
|
||||
},
|
||||
inputGroup: {
|
||||
gap: 8,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
},
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderColor: "#ddd",
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
fontSize: 16,
|
||||
backgroundColor: "#f5f5f5",
|
||||
color: "#000",
|
||||
},
|
||||
loginButton: {
|
||||
backgroundColor: "#007AFF",
|
||||
paddingVertical: 14,
|
||||
borderRadius: 8,
|
||||
alignItems: "center",
|
||||
marginTop: 16,
|
||||
},
|
||||
loginButtonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
loginButtonText: {
|
||||
color: "#fff",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
footerContainer: {
|
||||
marginTop: 16,
|
||||
alignItems: "center",
|
||||
},
|
||||
footerText: {
|
||||
fontSize: 14,
|
||||
},
|
||||
linkText: {
|
||||
color: "#007AFF",
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user