diff options
Diffstat (limited to 'app/(auth)/signin.tsx')
| -rw-r--r-- | app/(auth)/signin.tsx | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/app/(auth)/signin.tsx b/app/(auth)/signin.tsx new file mode 100644 index 0000000..1eac63b --- /dev/null +++ b/app/(auth)/signin.tsx @@ -0,0 +1,165 @@ +import { Ionicons } from "@expo/vector-icons"; +import { router } from "expo-router"; +import { useState } from "react"; +import { + KeyboardAvoidingView, + Platform, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useAuth } from "../../lib/auth"; +import { t } from "../../lib/locales"; +import { colors } from "../../lib/theme"; +import { Button, H1, Muted, TextInput } from "../../lib/ui"; + +export default function SignIn() { + const { signIn } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + + const validateForm = () => { + let valid = true; + setEmailError(""); + setPasswordError(""); + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + setEmailError(t("invalidEmail")); + valid = false; + } + + if (!password) { + setPasswordError(t("passwordRequired")); + valid = false; + } + + return valid; + }; + + const handleSignIn = async () => { + if (!validateForm()) return; + + setLoading(true); + setError(""); + + try { + const result = await signIn(email, password); + if (!result.success) { + setError(result.reason || t("signInError")); + } + } catch (e) { + setError(t("signInError")); + } finally { + setLoading(false); + } + }; + + return ( + <SafeAreaView style={styles.container}> + <KeyboardAvoidingView + style={styles.keyboardView} + behavior={Platform.OS === "ios" ? "padding" : "height"} + > + <ScrollView + contentContainerStyle={styles.scrollContent} + keyboardShouldPersistTaps="handled" + > + <TouchableOpacity + style={styles.backButton} + onPress={() => router.back()} + > + <Ionicons name="arrow-back" size={24} color={colors.onBackground} /> + </TouchableOpacity> + + <View style={styles.header}> + <H1>{t("signIn")}</H1> + </View> + + <View style={styles.form}> + <TextInput + label={t("email")} + value={email} + onChangeText={setEmail} + placeholder={t("emailPlaceholder")} + keyboardType="email-address" + autoCapitalize="none" + error={emailError} + /> + + <TextInput + label={t("password")} + value={password} + onChangeText={setPassword} + placeholder={t("passwordPlaceholder")} + secureTextEntry + error={passwordError} + /> + + {error ? <Text style={styles.error}>{error}</Text> : null} + + <Button + title={t("signIn")} + onPress={handleSignIn} + loading={loading} + disabled={loading} + /> + </View> + + <View style={styles.footer}> + <Muted>{t("dontHaveAccount")}</Muted> + <TouchableOpacity onPress={() => router.replace("/(auth)/signup")}> + <Text style={styles.link}>{t("signUp")}</Text> + </TouchableOpacity> + </View> + </ScrollView> + </KeyboardAvoidingView> + </SafeAreaView> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + keyboardView: { + flex: 1, + }, + scrollContent: { + flexGrow: 1, + padding: 24, + }, + backButton: { + marginBottom: 16, + }, + header: { + marginBottom: 32, + }, + form: { + gap: 20, + }, + error: { + color: colors.primary, + fontSize: 14, + textAlign: "center", + }, + footer: { + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + gap: 8, + marginTop: 32, + }, + link: { + color: colors.primary, + fontWeight: "700", + }, +}); |