Files
heval/app/(tabs)/index.tsx
2025-07-15 21:41:23 +02:00

591 lines
15 KiB
TypeScript

import AsyncStorage from "@react-native-async-storage/async-storage";
import * as LocalAuthentication from "expo-local-authentication";
import * as Location from "expo-location";
import { useRouter } from "expo-router";
import * as SQLite from "expo-sqlite";
import React, { useEffect, useState } from "react";
import {
ActivityIndicator,
Alert,
KeyboardAvoidingView,
Platform,
SafeAreaView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
// Types
interface User {
id: number;
email: string;
firstName: string;
lastName: string;
residence: string;
workplace: string;
latitude: string;
longitude: string;
createdAt: string;
}
// Simple Auth Component
export default function AuthScreen() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
const [currentUser, setCurrentUser] = useState<User | null>(null);
const [authMode, setAuthMode] = useState<"login" | "register">("login");
// Form states
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [residence, setResidence] = useState("");
const [workplace, setWorkplace] = useState("");
const [latitude, setLatitude] = useState<string | null>(null);
const [longitude, setLongitude] = useState<string | null>(null);
const [biometricSupported, setBiometricSupported] = useState(false);
const [showCalendar, setShowCalendar] = useState(false);
// Database
const [db, setDb] = useState<SQLite.SQLiteDatabase | null>(null);
const router = useRouter();
useEffect(() => {
initApp();
}, []);
const initApp = async () => {
try {
// Initialize database
const database = await SQLite.openDatabaseAsync("AuthApp.db");
setDb(database);
await database.execAsync(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
firstName TEXT NOT NULL,
lastName TEXT NOT NULL,
password TEXT NOT NULL,
residence TEXT,
workplace TEXT,
latitude TEXT,
longitude TEXT,
createdAt TEXT NOT NULL
)
`);
// Check biometric support
const compatible = await LocalAuthentication.hasHardwareAsync();
const enrolled = await LocalAuthentication.isEnrolledAsync();
setBiometricSupported(compatible && enrolled);
// Check if user is logged in
const session = await AsyncStorage.getItem("@user_session");
if (session) {
const userData = JSON.parse(session);
setCurrentUser(userData.user);
setIsLoggedIn(true);
}
} catch (error) {
console.error("App initialization failed:", error);
} finally {
setLoading(false);
}
};
const handleRegister = async () => {
if (
!email ||
!password ||
!firstName ||
!lastName ||
!residence ||
!workplace
) {
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
return;
}
if (password.length < 6) {
Alert.alert(
"Fehler",
"Das Passwort muss mindestens 6 Zeichen lang sein."
);
return;
}
if (!db) return;
try {
setLoading(true);
const createdAt = new Date().toISOString();
const result = await db.runAsync(
"INSERT INTO users (email, firstName, lastName, password, residence, workplace, latitude, longitude, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
[
email,
firstName,
lastName,
password,
residence,
workplace,
latitude ?? "",
longitude ?? "",
createdAt,
]
);
const newUser: User = {
id: result.lastInsertRowId,
email,
firstName,
lastName,
residence,
workplace,
latitude: latitude ?? "",
longitude: longitude ?? "",
createdAt,
};
// Save session
await AsyncStorage.setItem(
"@user_session",
JSON.stringify({ user: newUser })
);
setCurrentUser(newUser);
setIsLoggedIn(true);
Alert.alert("Erfolg", "Registrierung erfolgreich!");
} catch (error: any) {
if (error.message?.includes("UNIQUE constraint failed")) {
Alert.alert(
"Fehler",
"Ein Benutzer mit dieser E-Mail existiert bereits."
);
} else {
Alert.alert("Fehler", "Registrierung fehlgeschlagen.");
}
} finally {
setLoading(false);
}
};
const handleLogin = async () => {
if (!email || !password) {
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
return;
}
if (!db) return;
try {
setLoading(true);
const result = (await db.getFirstAsync(
"SELECT * FROM users WHERE email = ? AND password = ?",
[email, password]
)) as any;
if (result) {
const user: User = {
id: result.id,
email: result.email,
firstName: result.firstName,
lastName: result.lastName,
residence: result.residence,
workplace: result.workplace,
latitude: result.latitude,
longitude: result.longitude,
createdAt: result.createdAt,
};
// Save session
await AsyncStorage.setItem("@user_session", JSON.stringify({ user }));
setCurrentUser(user);
setIsLoggedIn(true);
Alert.alert("Erfolg", "Anmeldung erfolgreich!");
} else {
Alert.alert("Fehler", "Ungültige E-Mail oder Passwort.");
}
} catch (error) {
Alert.alert("Fehler", "Anmeldung fehlgeschlagen.");
} finally {
setLoading(false);
}
};
const handleBiometricAuth = async () => {
try {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: "Biometrische Authentifizierung",
cancelLabel: "Abbrechen",
fallbackLabel: "Passcode verwenden",
});
if (result.success) {
// For demo, auto-login first user
if (!db) return;
const user = (await db.getFirstAsync(
"SELECT * FROM users LIMIT 1"
)) as any;
if (user) {
const userData: User = {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
residence: user.residence,
workplace: user.workplace,
latitude: user.latitude,
longitude: user.longitude,
createdAt: user.createdAt,
};
await AsyncStorage.setItem(
"@user_session",
JSON.stringify({ user: userData })
);
setCurrentUser(userData);
setIsLoggedIn(true);
}
}
} catch (error) {
Alert.alert("Fehler", "Biometrische Authentifizierung fehlgeschlagen.");
}
};
const handleLogout = async () => {
try {
await AsyncStorage.removeItem("@user_session");
setCurrentUser(null);
setIsLoggedIn(false);
setEmail("");
setPassword("");
setFirstName("");
setLastName("");
setResidence("");
setWorkplace("");
setLatitude(null);
setLongitude(null);
} catch (error) {
Alert.alert("Fehler", "Abmeldung fehlgeschlagen.");
}
};
const handleUseLocation = async () => {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") {
Alert.alert("Fehler", "Standortberechtigung nicht erteilt.");
return;
}
const loc = await Location.getCurrentPositionAsync({});
setLatitude(loc.coords.latitude.toString());
setLongitude(loc.coords.longitude.toString());
const addr = await Location.reverseGeocodeAsync({
latitude: loc.coords.latitude,
longitude: loc.coords.longitude,
});
if (addr && addr.length > 0) {
setResidence(addr[0].city || addr[0].region || "");
}
} catch (e) {
Alert.alert("Fehler", "Standort konnte nicht ermittelt werden.");
}
};
if (loading) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loadingText}>Lade App...</Text>
</View>
</SafeAreaView>
);
}
if (isLoggedIn && currentUser) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.welcomeContainer}>
<Text style={styles.welcomeTitle}>Willkommen zurück!</Text>
<Text style={styles.userName}>
{currentUser.firstName} {currentUser.lastName}
</Text>
<View style={styles.userInfo}>
<Text style={styles.infoLabel}>E-Mail:</Text>
<Text style={styles.infoValue}>{currentUser.email}</Text>
<Text style={styles.infoLabel}>Mitglied seit:</Text>
<Text style={styles.infoValue}>
{new Date(currentUser.createdAt).toLocaleDateString("de-DE")}
</Text>
</View>
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
<Text style={styles.logoutButtonText}>Abmelden</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.submitButton, { marginTop: 20 }]}
onPress={() => router.push("/calendar")}
>
<Text style={styles.submitButtonText}>Zum Kalender</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
style={styles.keyboardView}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<View style={styles.authContainer}>
<Text style={styles.title}>
{authMode === "login" ? "Anmelden" : "Registrieren"}
</Text>
{authMode === "register" && (
<>
<TextInput
style={styles.input}
placeholder="Vorname"
value={firstName}
onChangeText={setFirstName}
autoCapitalize="words"
/>
<TextInput
style={styles.input}
placeholder="Nachname"
value={lastName}
onChangeText={setLastName}
autoCapitalize="words"
/>
<TextInput
style={styles.input}
placeholder="Wohnort"
value={residence}
onChangeText={setResidence}
/>
<TextInput
style={styles.input}
placeholder="Arbeitsplatz / Uni / Schule"
value={workplace}
onChangeText={setWorkplace}
/>
<TouchableOpacity
style={[
styles.submitButton,
{ backgroundColor: "#34C759", marginBottom: 10 },
]}
onPress={handleUseLocation}
>
<Text style={styles.submitButtonText}>
Aktuellen Standort verwenden
</Text>
</TouchableOpacity>
</>
)}
<TextInput
style={styles.input}
placeholder="E-Mail"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
<TextInput
style={styles.input}
placeholder="Passwort"
value={password}
onChangeText={setPassword}
secureTextEntry
autoCapitalize="none"
/>
<TouchableOpacity
style={styles.submitButton}
onPress={authMode === "login" ? handleLogin : handleRegister}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.submitButtonText}>
{authMode === "login" ? "Anmelden" : "Registrieren"}
</Text>
)}
</TouchableOpacity>
{biometricSupported && authMode === "login" && (
<TouchableOpacity
style={styles.biometricButton}
onPress={handleBiometricAuth}
>
<Text style={styles.biometricButtonText}>
Mit Face ID / Touch ID anmelden
</Text>
</TouchableOpacity>
)}
<TouchableOpacity
style={styles.switchButton}
onPress={() =>
setAuthMode(authMode === "login" ? "register" : "login")
}
>
<Text style={styles.switchButtonText}>
{authMode === "login"
? "Noch kein Konto? Registrieren"
: "Bereits ein Konto? Anmelden"}
</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f8f9fa",
},
keyboardView: {
flex: 1,
},
authContainer: {
flex: 1,
justifyContent: "center",
paddingHorizontal: 30,
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
loadingText: {
marginTop: 10,
fontSize: 16,
color: "#666",
},
welcomeContainer: {
flex: 1,
justifyContent: "center",
paddingHorizontal: 30,
},
welcomeTitle: {
fontSize: 28,
fontWeight: "bold",
color: "#333",
textAlign: "center",
marginBottom: 10,
},
userName: {
fontSize: 20,
color: "#666",
textAlign: "center",
marginBottom: 40,
},
userInfo: {
backgroundColor: "white",
padding: 20,
borderRadius: 10,
marginBottom: 30,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
infoLabel: {
fontSize: 14,
color: "#666",
marginTop: 10,
},
infoValue: {
fontSize: 16,
color: "#333",
fontWeight: "500",
marginBottom: 5,
},
title: {
fontSize: 32,
fontWeight: "bold",
color: "#333",
textAlign: "center",
marginBottom: 40,
},
input: {
backgroundColor: "white",
paddingHorizontal: 15,
paddingVertical: 15,
borderRadius: 10,
fontSize: 16,
marginBottom: 15,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
submitButton: {
backgroundColor: "#007AFF",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
marginTop: 10,
},
submitButtonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
biometricButton: {
backgroundColor: "#34C759",
paddingVertical: 15,
borderRadius: 10,
marginTop: 15,
alignItems: "center",
},
biometricButtonText: {
color: "white",
fontSize: 16,
fontWeight: "600",
},
switchButton: {
marginTop: 20,
alignItems: "center",
},
switchButtonText: {
color: "#007AFF",
fontSize: 16,
},
logoutButton: {
backgroundColor: "#FF3B30",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
},
logoutButtonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
});