thêm quét QR đăng nhập, sửa lại logic để gọi api bằng ip thiết bị
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import ScanQRCode from "@/components/ScanQRCode";
|
||||
import { ThemedText } from "@/components/themed-text";
|
||||
import { ThemedView } from "@/components/themed-view";
|
||||
import { TOKEN } from "@/constants";
|
||||
import { DOMAIN, TOKEN } from "@/constants";
|
||||
import { login } from "@/controller/AuthController";
|
||||
import { showErrorToast } from "@/services/toast_service";
|
||||
import { showErrorToast, showWarningToast } from "@/services/toast_service";
|
||||
import {
|
||||
getStorageItem,
|
||||
removeStorageItem,
|
||||
setStorageItem,
|
||||
} from "@/utils/storage";
|
||||
import { parseJwtToken } from "@/utils/token";
|
||||
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
@@ -29,22 +31,30 @@ export default function LoginScreen() {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isShowingQRScanner, setIsShowingQRScanner] = useState(false);
|
||||
|
||||
const checkLogin = useCallback(async () => {
|
||||
const token = await getStorageItem(TOKEN);
|
||||
console.log("Token:", token);
|
||||
|
||||
const domain = await getStorageItem(DOMAIN);
|
||||
// console.log("Token:", token);
|
||||
// removeStorageItem(DOMAIN);
|
||||
console.log("Domain:", domain);
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
const parsed = parseJwtToken(token);
|
||||
console.log("Parse Token: ", parsed);
|
||||
// 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);
|
||||
await removeStorageItem(DOMAIN);
|
||||
} else {
|
||||
router.replace("/(tabs)");
|
||||
}
|
||||
@@ -54,9 +64,41 @@ export default function LoginScreen() {
|
||||
checkLogin();
|
||||
}, [checkLogin]);
|
||||
|
||||
const handleLogin = async () => {
|
||||
const handleQRCodeScanned = async (data: string) => {
|
||||
console.log("QR Code Scanned Data:", data);
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.username && parsed.password) {
|
||||
// update UI fields
|
||||
setUsername(parsed.username);
|
||||
setPassword(parsed.password);
|
||||
console.log("Domain: ", parsed.device_ip);
|
||||
|
||||
// close scanner so user sees the filled form
|
||||
await setStorageItem(DOMAIN, parsed.device_ip);
|
||||
|
||||
// // call login directly with scanned credentials to avoid waiting for state to update
|
||||
await handleLogin({
|
||||
username: parsed.username,
|
||||
password: parsed.password,
|
||||
});
|
||||
} else {
|
||||
showWarningToast("Mã QR không hợp lệ");
|
||||
}
|
||||
} catch (error) {
|
||||
showWarningToast("Mã QR không hợp lệ");
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async (creds?: {
|
||||
username: string;
|
||||
password: string;
|
||||
}) => {
|
||||
const user = creds?.username ?? username;
|
||||
const pass = creds?.password ?? password;
|
||||
|
||||
// Validate input
|
||||
if (!username.trim() || !password.trim()) {
|
||||
if (!user?.trim() || !pass?.trim()) {
|
||||
showErrorToast("Vui lòng nhập tài khoản và mật khẩu");
|
||||
return;
|
||||
}
|
||||
@@ -64,8 +106,8 @@ export default function LoginScreen() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const body: Model.LoginRequestBody = {
|
||||
username: username,
|
||||
password: password,
|
||||
username: user,
|
||||
password: pass,
|
||||
};
|
||||
|
||||
const response = await login(body);
|
||||
@@ -131,33 +173,87 @@ export default function LoginScreen() {
|
||||
{/* 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 className="relative">
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Nhập mật khẩu"
|
||||
placeholderTextColor="#999"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry={!showPassword}
|
||||
editable={!loading}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
{/* Position absolute with top:0 and bottom:0 and justifyContent:center
|
||||
ensures the icon remains vertically centered inside the input */}
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 12,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 4,
|
||||
}}
|
||||
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
||||
>
|
||||
<Ionicons
|
||||
name={showPassword ? "eye-off" : "eye"}
|
||||
size={22}
|
||||
color="#666"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Login Button */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.loginButton,
|
||||
loading && styles.loginButtonDisabled,
|
||||
]}
|
||||
onPress={handleLogin}
|
||||
disabled={loading}
|
||||
{/* Login Button (3/4) + QR Scan (1/4) */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#fff" size="small" />
|
||||
) : (
|
||||
<Text style={styles.loginButtonText}>Đăng nhập</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.loginButton,
|
||||
loading && styles.loginButtonDisabled,
|
||||
{ flex: 5, marginRight: 12, marginTop: 0 },
|
||||
]}
|
||||
onPress={() => handleLogin()}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color="#fff" size="small" />
|
||||
) : (
|
||||
<Text style={styles.loginButtonText}>Đăng nhập</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
marginTop: 0,
|
||||
borderColor: "#ddd",
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
backgroundColor: "transparent",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
onPress={() => setIsShowingQRScanner(true)}
|
||||
disabled={loading}
|
||||
>
|
||||
<MaterialIcons
|
||||
name="qr-code-scanner"
|
||||
size={28}
|
||||
color="#007AFF"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Footer text */}
|
||||
<View style={styles.footerContainer}>
|
||||
@@ -176,6 +272,11 @@ export default function LoginScreen() {
|
||||
</View>
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
<ScanQRCode
|
||||
visible={isShowingQRScanner}
|
||||
onClose={() => setIsShowingQRScanner(false)}
|
||||
onScanned={handleQRCodeScanned}
|
||||
/>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user