Files
bw-expo-app/app/(auth)/login.tsx
imeepos 7e3f94bae3 Initial commit: Expo app with Better Auth integration
- Complete Expo React Native app setup with TypeScript
- Better Auth authentication system integration
- Secure storage implementation for session tokens
- Authentication flow with login/logout functionality
- API client configuration for backend communication
- Responsive UI components with themed styling
- Expo Router navigation setup
- Development configuration and scripts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 10:32:52 +08:00

181 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ThemedText } from "@/components/themed-text";
import { ThemedView } from "@/components/themed-view";
import { useAuth } from "@/hooks/use-auth";
import { authClient } from "@/lib/auth/client";
import { router } from "expo-router";
import { useEffect, useState } from "react";
import {
ActivityIndicator,
Alert,
KeyboardAvoidingView,
Platform,
StyleSheet,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { storage } from '../../lib/storage';
export default function LoginScreen() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const { isAuthenticated, isLoading: authLoading } = useAuth();
useEffect(() => {
if (!authLoading && isAuthenticated) {
console.log("[Login] User authenticated, redirecting to tabs...");
router.replace("/(tabs)");
}
}, [isAuthenticated, authLoading]);
const handleLogin = async () => {
console.log("[Login] handleLogin called");
if (!username.trim() || !password.trim()) {
console.log("[Login] Validation failed: empty username or password");
Alert.alert("错误", "请输入用户名和密码");
return;
}
console.log("[Login] Starting login with username:", username.trim());
setIsLoading(true);
try {
console.log("[Login] Calling authClient.signIn.username...");
await new Promise<void>(async (resolve, reject) => {
await authClient.signIn.username({
username: username.trim(),
password,
}, {
onSuccess: async (ctx) => {
const authToken = ctx.response.headers.get("set-auth-token")
if (authToken) {
await storage.setItem(`bestaibest.better-auth.session_token`, authToken);
resolve()
} else {
console.error("Bearer token not set");
reject()
}
}
});
})
} catch (error: any) {
Alert.alert("登录失败", error?.message || "用户名或密码错误");
} finally {
setIsLoading(false);
console.log("[Login] handleLogin completed");
}
};
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
<ThemedView style={styles.content}>
<ThemedText type="title" style={styles.title}>
</ThemedText>
<ThemedText style={styles.subtitle}></ThemedText>
<View style={styles.form}>
<TextInput
style={styles.input}
placeholder="用户名"
value={username}
onChangeText={setUsername}
autoCapitalize="none"
autoCorrect={false}
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="密码"
value={password}
onChangeText={setPassword}
secureTextEntry
editable={!isLoading}
/>
<TouchableOpacity
style={[styles.button, isLoading && styles.buttonDisabled]}
onPress={handleLogin}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText style={styles.buttonText}></ThemedText>
)}
</TouchableOpacity>
<TouchableOpacity
style={styles.registerLink}
onPress={() => router.push("/(auth)/register")}
disabled={isLoading}
>
<ThemedText style={styles.registerText}></ThemedText>
</TouchableOpacity>
</View>
</ThemedView>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
padding: 24,
justifyContent: "center",
},
title: {
marginBottom: 8,
textAlign: "center",
},
subtitle: {
marginBottom: 32,
textAlign: "center",
opacity: 0.7,
},
form: {
gap: 16,
},
input: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: "#fff",
},
button: {
backgroundColor: "#007AFF",
padding: 16,
borderRadius: 8,
alignItems: "center",
marginTop: 8,
},
buttonDisabled: {
opacity: 0.6,
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
registerLink: {
alignItems: "center",
marginTop: 8,
},
registerText: {
color: "#007AFF",
fontSize: 14,
},
});