import EnIcon from "@/assets/icons/en_icon.png"; import VnIcon from "@/assets/icons/vi_icon.png"; import RotateSwitch from "@/components/rotate-switch"; import ScanQRCode from "@/components/ScanQRCode"; import { ThemedText } from "@/components/themed-text"; import { ThemedView } from "@/components/themed-view"; import SliceSwitch from "@/components/ui/slice-switch"; import { DOMAIN, TOKEN } from "@/constants"; import { Colors } from "@/constants/theme"; import { login } from "@/controller/AuthController"; import { useI18n } from "@/hooks/use-i18n"; import { ColorScheme as ThemeColorScheme, useTheme, useThemeContext, } from "@/hooks/use-theme-context"; 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, useMemo, useState } from "react"; import { ActivityIndicator, Image, 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 [showPassword, setShowPassword] = useState(false); const [isShowingQRScanner, setIsShowingQRScanner] = useState(false); const { t, setLocale, locale } = useI18n(); const { colors, colorScheme } = useTheme(); const { setThemeMode } = useThemeContext(); const styles = useMemo( () => createStyles(colors, colorScheme), [colors, colorScheme] ); const placeholderColor = colors.textSecondary; const buttonTextColor = colorScheme === "dark" ? colors.text : colors.surface; const [isVNLang, setIsVNLang] = useState(false); const checkLogin = useCallback(async () => { const token = await getStorageItem(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); 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)"); } }, [router]); useEffect(() => { setIsVNLang(locale === "vi"); }, [locale]); useEffect(() => { checkLogin(); }, [checkLogin]); 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 (!user?.trim() || !pass?.trim()) { showErrorToast("Vui lòng nhập tài khoản và mật khẩu"); return; } setLoading(true); try { const body: Model.LoginRequestBody = { username: user, password: pass, }; 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) console.log("Login Token "); await setStorageItem(TOKEN, response.data.token); console.log("Token:", response.data.token); router.replace("/(tabs)"); } } catch (error) { showErrorToast( error instanceof Error ? error.message : "Đăng nhập thất bại" ); } finally { setLoading(false); } }; const handleSwitchLanguage = (isVN: boolean) => { if (isVN) { setLocale("vi"); } else { setLocale("en"); } setIsVNLang(isVN); }; return ( {/* Header */} {/* Logo */} {t("common.app_name")} {/* Form */} {/* Username Input */} {t("auth.username")} {/* Password Input */} {t("auth.password")} {/* Position absolute with top:0 and bottom:0 and justifyContent:center ensures the icon remains vertically centered inside the input */} 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 }} > {/* Login Button (3/4) + QR Scan (1/4) */} handleLogin()} disabled={loading} > {loading ? ( ) : ( {t("auth.login")} )} setIsShowingQRScanner(true)} disabled={loading} > {/* Language Switcher */} { setThemeMode(val ? "light" : "dark"); }} /> {/* Copyright */} © {new Date().getFullYear()} - {t("common.footer_text")} setIsShowingQRScanner(false)} onScanned={handleQRCodeScanned} /> ); } const createStyles = (colors: typeof Colors.light, scheme: ThemeColorScheme) => StyleSheet.create({ scrollContainer: { flexGrow: 1, justifyContent: "center", backgroundColor: colors.background, }, container: { flex: 1, paddingHorizontal: 20, justifyContent: "center", }, headerContainer: { marginBottom: 40, alignItems: "center", }, logo: { width: 120, height: 120, marginBottom: 20, }, title: { fontSize: 28, 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: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 12, fontSize: 16, backgroundColor: colors.surface, color: colors.text, }, loginButton: { backgroundColor: colors.primary, paddingVertical: 14, borderRadius: 8, alignItems: "center", marginTop: 16, }, loginButtonDisabled: { opacity: 0.6, }, loginButtonText: { fontSize: 16, fontWeight: "600", }, footerContainer: { marginTop: 16, alignItems: "center", }, footerText: { fontSize: 14, }, linkText: { color: colors.primary, fontWeight: "600", }, copyrightContainer: { marginTop: 20, alignItems: "center", }, copyrightText: { fontSize: 12, opacity: 0.6, textAlign: "center", color: colors.textSecondary, }, languageSwitcherContainer: { display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center", marginTop: 24, gap: 20, }, languageSwitcherLabel: { fontSize: 12, fontWeight: "600", textAlign: "center", opacity: 0.8, }, languageButtonsContainer: { flexDirection: "row", gap: 10, }, languageButton: { flex: 1, paddingVertical: 10, paddingHorizontal: 12, borderWidth: 1, borderColor: colors.border, borderRadius: 8, alignItems: "center", backgroundColor: colors.surface, }, languageButtonActive: { backgroundColor: colors.primary, borderColor: colors.primary, }, languageButtonText: { fontSize: 14, fontWeight: "500", color: colors.textSecondary, }, languageButtonTextActive: { color: scheme === "dark" ? colors.text : colors.surface, fontWeight: "600", }, });