Anmeldung und Kalendar

This commit is contained in:
Ismail Ali
2025-07-15 20:38:04 +02:00
parent 2a34091404
commit 1cc8951c12
51 changed files with 19961 additions and 177 deletions

View File

@@ -0,0 +1,66 @@
import React, { useState } from 'react';
import { View, TextInput, Button, Text, StyleSheet, Alert } from 'react-native';
import { authenticateUser } from '../services/auth';
import { storeUserSession } from '../utils/storage';
import { useNavigation } from '@react-navigation/native';
const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const navigation = useNavigation();
const handleLogin = async () => {
if (!email || !password) {
Alert.alert('Error', 'Please fill in all fields');
return;
}
try {
const response = await authenticateUser(email, password);
if (response.success) {
await storeUserSession(response.data);
navigation.navigate('WelcomeScreen');
} else {
Alert.alert('Error', response.message);
}
} catch (error) {
Alert.alert('Error', 'An error occurred during login');
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Login" onPress={handleLogin} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingHorizontal: 10,
},
});
export default LoginForm;

View File

@@ -0,0 +1,63 @@
import React, { useState } from 'react';
import { View, TextInput, Button, Text, StyleSheet } from 'react-native';
import { registerUser } from '../services/auth';
const RegisterForm = ({ navigation }) => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleRegister = async () => {
setError('');
try {
await registerUser(username, password);
navigation.navigate('WelcomeScreen');
} catch (err) {
setError(err.message);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Register</Text>
{error ? <Text style={styles.error}>{error}</Text> : null}
<TextInput
style={styles.input}
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
style={styles.input}
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
<Button title="Register" onPress={handleRegister} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
},
title: {
fontSize: 24,
marginBottom: 20,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingLeft: 8,
},
error: {
color: 'red',
marginBottom: 10,
},
});
export default RegisterForm;

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const WelcomeScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.welcomeText}>Willkommen in der App!</Text>
<Text style={styles.instructionText}>Sie können sich jetzt anmelden oder registrieren.</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
welcomeText: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
instructionText: {
fontSize: 16,
textAlign: 'center',
},
});
export default WelcomeScreen;

View File

@@ -0,0 +1,91 @@
import { createStackNavigator } from "@react-navigation/stack";
import React, { useEffect, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import AuthScreen from "../screens/AuthScreen";
import LoginScreen from "../screens/LoginScreen";
import RegisterScreen from "../screens/RegisterScreen";
import WelcomeScreen from "../screens/WelcomeScreen";
import AuthService from "../services/auth";
import { RootStackParamList } from "../types";
const Stack = createStackNavigator<RootStackParamList>();
const AppNavigator = () => {
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
initializeApp();
}, []);
const initializeApp = async () => {
try {
await AuthService.initializeAuth();
const loggedIn = await AuthService.isLoggedIn();
setIsAuthenticated(loggedIn);
} catch (error) {
console.error("App initialization failed:", error);
} finally {
setIsLoading(false);
}
};
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" color="#007AFF" />
</View>
);
}
return (
<Stack.Navigator
initialRouteName={isAuthenticated ? "WelcomeScreen" : "AuthScreen"}
screenOptions={{
headerStyle: {
backgroundColor: "#007AFF",
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
}}
>
<Stack.Screen
name="AuthScreen"
component={AuthScreen}
options={{
title: "Authentifizierung",
headerShown: false,
}}
/>
<Stack.Screen
name="LoginScreen"
component={LoginScreen}
options={{
title: "Anmelden",
headerBackTitleVisible: false,
}}
/>
<Stack.Screen
name="RegisterScreen"
component={RegisterScreen}
options={{
title: "Registrieren",
headerBackTitleVisible: false,
}}
/>
<Stack.Screen
name="WelcomeScreen"
component={WelcomeScreen}
options={{
title: "Willkommen",
headerLeft: () => null,
gestureEnabled: false,
}}
/>
</Stack.Navigator>
);
};
export default AppNavigator;

View File

@@ -0,0 +1,172 @@
import { StackNavigationProp } from "@react-navigation/stack";
import React, { useEffect, useState } from "react";
import {
Alert,
SafeAreaView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
import AuthService from "../services/auth";
import BiometricsService from "../services/biometrics";
import { RootStackParamList } from "../types";
import StorageService from "../utils/storage";
type AuthScreenNavigationProp = StackNavigationProp<
RootStackParamList,
"AuthScreen"
>;
interface Props {
navigation: AuthScreenNavigationProp;
}
const AuthScreen: React.FC<Props> = ({ navigation }) => {
const [biometricSupported, setBiometricSupported] = useState(false);
const [biometricEnabled, setBiometricEnabled] = useState(false);
useEffect(() => {
checkBiometricSupport();
}, []);
const checkBiometricSupport = async () => {
try {
const isSupported = await BiometricsService.isBiometricSupported();
const isEnabled = await StorageService.isBiometricEnabled();
setBiometricSupported(isSupported);
setBiometricEnabled(isEnabled);
} catch (error) {
console.error("Biometric check failed:", error);
}
};
const handleBiometricLogin = async () => {
try {
const result = await AuthService.biometricLogin();
if (result.success && result.user) {
navigation.navigate("WelcomeScreen", { user: result.user });
} else {
Alert.alert("Fehler", result.message);
}
} catch (error) {
Alert.alert("Fehler", "Biometrische Anmeldung fehlgeschlagen.");
}
};
const navigateToLogin = () => {
navigation.navigate("LoginScreen");
};
const navigateToRegister = () => {
navigation.navigate("RegisterScreen");
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Willkommen</Text>
<Text style={styles.subtitle}>
Melden Sie sich an oder erstellen Sie ein neues Konto
</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.primaryButton}
onPress={navigateToLogin}
>
<Text style={styles.primaryButtonText}>Anmelden</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.secondaryButton}
onPress={navigateToRegister}
>
<Text style={styles.secondaryButtonText}>Registrieren</Text>
</TouchableOpacity>
{biometricSupported && biometricEnabled && (
<TouchableOpacity
style={styles.biometricButton}
onPress={handleBiometricLogin}
>
<Text style={styles.biometricButtonText}>
Mit Face ID / Touch ID anmelden
</Text>
</TouchableOpacity>
)}
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
content: {
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 20,
},
title: {
fontSize: 32,
fontWeight: "bold",
color: "#333",
marginBottom: 10,
},
subtitle: {
fontSize: 16,
color: "#666",
textAlign: "center",
marginBottom: 40,
},
buttonContainer: {
width: "100%",
maxWidth: 300,
},
primaryButton: {
backgroundColor: "#007AFF",
paddingVertical: 15,
borderRadius: 10,
marginBottom: 15,
alignItems: "center",
},
primaryButtonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
secondaryButton: {
backgroundColor: "transparent",
paddingVertical: 15,
borderRadius: 10,
borderWidth: 2,
borderColor: "#007AFF",
marginBottom: 15,
alignItems: "center",
},
secondaryButtonText: {
color: "#007AFF",
fontSize: 18,
fontWeight: "600",
},
biometricButton: {
backgroundColor: "#4CAF50",
paddingVertical: 15,
borderRadius: 10,
marginTop: 20,
alignItems: "center",
},
biometricButtonText: {
color: "white",
fontSize: 16,
fontWeight: "600",
},
});
export default AuthScreen;

View File

@@ -0,0 +1,248 @@
import { StackNavigationProp } from "@react-navigation/stack";
import React, { useState } from "react";
import {
ActivityIndicator,
Alert,
KeyboardAvoidingView,
Platform,
SafeAreaView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import AuthService from "../services/auth";
import BiometricsService from "../services/biometrics";
import { LoginCredentials, RootStackParamList } from "../types";
import StorageService from "../utils/storage";
type LoginScreenNavigationProp = StackNavigationProp<
RootStackParamList,
"LoginScreen"
>;
interface Props {
navigation: LoginScreenNavigationProp;
}
const LoginScreen: React.FC<Props> = ({ navigation }) => {
const [credentials, setCredentials] = useState<LoginCredentials>({
email: "",
password: "",
});
const [loading, setLoading] = useState(false);
const [biometricAvailable, setBiometricAvailable] = useState(false);
React.useEffect(() => {
checkBiometricAvailability();
loadLastEmail();
}, []);
const checkBiometricAvailability = async () => {
try {
const isSupported = await BiometricsService.isBiometricSupported();
const isEnabled = await StorageService.isBiometricEnabled();
setBiometricAvailable(isSupported && isEnabled);
} catch (error) {
console.error("Biometric check failed:", error);
}
};
const loadLastEmail = async () => {
try {
const lastEmail = await StorageService.getLastLoginEmail();
if (lastEmail) {
setCredentials((prev) => ({ ...prev, email: lastEmail }));
}
} catch (error) {
console.error("Load last email failed:", error);
}
};
const handleLogin = async () => {
if (!credentials.email || !credentials.password) {
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
return;
}
setLoading(true);
try {
const result = await AuthService.login(credentials);
if (result.success && result.user) {
navigation.navigate("WelcomeScreen", { user: result.user });
} else {
Alert.alert("Anmeldung fehlgeschlagen", result.message);
}
} catch (error) {
Alert.alert("Fehler", "Ein unerwarteter Fehler ist aufgetreten.");
} finally {
setLoading(false);
}
};
const handleBiometricLogin = async () => {
try {
const result = await AuthService.biometricLogin();
if (result.success && result.user) {
navigation.navigate("WelcomeScreen", { user: result.user });
} else {
Alert.alert("Fehler", result.message);
}
} catch (error) {
Alert.alert("Fehler", "Biometrische Anmeldung fehlgeschlagen.");
}
};
const navigateToRegister = () => {
navigation.navigate("RegisterScreen");
};
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
style={styles.keyboardView}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<View style={styles.content}>
<Text style={styles.title}>Anmelden</Text>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="E-Mail-Adresse"
value={credentials.email}
onChangeText={(text) =>
setCredentials((prev) => ({ ...prev, email: text }))
}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
/>
<TextInput
style={styles.input}
placeholder="Passwort"
value={credentials.password}
onChangeText={(text) =>
setCredentials((prev) => ({ ...prev, password: text }))
}
secureTextEntry
autoCapitalize="none"
/>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleLogin}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>Anmelden</Text>
)}
</TouchableOpacity>
{biometricAvailable && (
<TouchableOpacity
style={styles.biometricButton}
onPress={handleBiometricLogin}
>
<Text style={styles.biometricButtonText}>
Mit Face ID / Touch ID anmelden
</Text>
</TouchableOpacity>
)}
<View style={styles.registerContainer}>
<Text style={styles.registerText}>Noch kein Konto? </Text>
<TouchableOpacity onPress={navigateToRegister}>
<Text style={styles.registerLink}>Registrieren</Text>
</TouchableOpacity>
</View>
</View>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
keyboardView: {
flex: 1,
},
content: {
flex: 1,
justifyContent: "center",
paddingHorizontal: 20,
},
title: {
fontSize: 28,
fontWeight: "bold",
color: "#333",
textAlign: "center",
marginBottom: 40,
},
formContainer: {
width: "100%",
},
input: {
backgroundColor: "white",
paddingHorizontal: 15,
paddingVertical: 15,
borderRadius: 10,
fontSize: 16,
marginBottom: 15,
borderWidth: 1,
borderColor: "#ddd",
},
button: {
backgroundColor: "#007AFF",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
marginTop: 10,
},
buttonDisabled: {
backgroundColor: "#ccc",
},
buttonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
biometricButton: {
backgroundColor: "#4CAF50",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
marginTop: 15,
},
biometricButtonText: {
color: "white",
fontSize: 16,
fontWeight: "600",
},
registerContainer: {
flexDirection: "row",
justifyContent: "center",
marginTop: 30,
},
registerText: {
fontSize: 16,
color: "#666",
},
registerLink: {
fontSize: 16,
color: "#007AFF",
fontWeight: "600",
},
});
export default LoginScreen;

View File

@@ -0,0 +1,272 @@
import { StackNavigationProp } from "@react-navigation/stack";
import React, { useState } from "react";
import {
ActivityIndicator,
Alert,
KeyboardAvoidingView,
Platform,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import AuthService from "../services/auth";
import BiometricsService from "../services/biometrics";
import { RegisterCredentials, RootStackParamList } from "../types";
type RegisterScreenNavigationProp = StackNavigationProp<
RootStackParamList,
"RegisterScreen"
>;
interface Props {
navigation: RegisterScreenNavigationProp;
}
const RegisterScreen: React.FC<Props> = ({ navigation }) => {
const [credentials, setCredentials] = useState<RegisterCredentials>({
email: "",
password: "",
firstName: "",
lastName: "",
});
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
const handleRegister = async () => {
if (
!credentials.email ||
!credentials.password ||
!credentials.firstName ||
!credentials.lastName
) {
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
return;
}
if (credentials.password !== confirmPassword) {
Alert.alert("Fehler", "Die Passwörter stimmen nicht überein.");
return;
}
if (credentials.password.length < 6) {
Alert.alert(
"Fehler",
"Das Passwort muss mindestens 6 Zeichen lang sein."
);
return;
}
setLoading(true);
try {
const result = await AuthService.register(credentials);
if (result.success && result.user) {
// Ask user if they want to enable biometric authentication
const biometricSupported =
await BiometricsService.isBiometricSupported();
if (biometricSupported) {
Alert.alert(
"Biometrische Authentifizierung",
"Möchten Sie Face ID / Touch ID für zukünftige Anmeldungen aktivieren?",
[
{
text: "Später",
style: "cancel",
onPress: () =>
navigation.navigate("WelcomeScreen", { user: result.user! }),
},
{
text: "Aktivieren",
onPress: async () => {
const enabled = await AuthService.enableBiometricAuth();
if (enabled) {
Alert.alert(
"Erfolg",
"Biometrische Authentifizierung wurde aktiviert."
);
}
navigation.navigate("WelcomeScreen", { user: result.user! });
},
},
]
);
} else {
navigation.navigate("WelcomeScreen", { user: result.user });
}
} else {
Alert.alert("Registrierung fehlgeschlagen", result.message);
}
} catch (error) {
Alert.alert("Fehler", "Ein unerwarteter Fehler ist aufgetreten.");
} finally {
setLoading(false);
}
};
const navigateToLogin = () => {
navigation.navigate("LoginScreen");
};
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
style={styles.keyboardView}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<ScrollView contentContainerStyle={styles.scrollContainer}>
<View style={styles.content}>
<Text style={styles.title}>Registrieren</Text>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Vorname"
value={credentials.firstName}
onChangeText={(text) =>
setCredentials((prev) => ({ ...prev, firstName: text }))
}
autoCapitalize="words"
/>
<TextInput
style={styles.input}
placeholder="Nachname"
value={credentials.lastName}
onChangeText={(text) =>
setCredentials((prev) => ({ ...prev, lastName: text }))
}
autoCapitalize="words"
/>
<TextInput
style={styles.input}
placeholder="E-Mail-Adresse"
value={credentials.email}
onChangeText={(text) =>
setCredentials((prev) => ({ ...prev, email: text }))
}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
/>
<TextInput
style={styles.input}
placeholder="Passwort"
value={credentials.password}
onChangeText={(text) =>
setCredentials((prev) => ({ ...prev, password: text }))
}
secureTextEntry
autoCapitalize="none"
/>
<TextInput
style={styles.input}
placeholder="Passwort bestätigen"
value={confirmPassword}
onChangeText={setConfirmPassword}
secureTextEntry
autoCapitalize="none"
/>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleRegister}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>Registrieren</Text>
)}
</TouchableOpacity>
<View style={styles.loginContainer}>
<Text style={styles.loginText}>Bereits ein Konto? </Text>
<TouchableOpacity onPress={navigateToLogin}>
<Text style={styles.loginLink}>Anmelden</Text>
</TouchableOpacity>
</View>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
keyboardView: {
flex: 1,
},
scrollContainer: {
flexGrow: 1,
},
content: {
flex: 1,
justifyContent: "center",
paddingHorizontal: 20,
paddingVertical: 20,
},
title: {
fontSize: 28,
fontWeight: "bold",
color: "#333",
textAlign: "center",
marginBottom: 40,
},
formContainer: {
width: "100%",
},
input: {
backgroundColor: "white",
paddingHorizontal: 15,
paddingVertical: 15,
borderRadius: 10,
fontSize: 16,
marginBottom: 15,
borderWidth: 1,
borderColor: "#ddd",
},
button: {
backgroundColor: "#007AFF",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
marginTop: 10,
},
buttonDisabled: {
backgroundColor: "#ccc",
},
buttonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
loginContainer: {
flexDirection: "row",
justifyContent: "center",
marginTop: 30,
},
loginText: {
fontSize: 16,
color: "#666",
},
loginLink: {
fontSize: 16,
color: "#007AFF",
fontWeight: "600",
},
});
export default RegisterScreen;

View File

@@ -0,0 +1,297 @@
import { RouteProp } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
import React, { useEffect, useState } from "react";
import {
Alert,
SafeAreaView,
StyleSheet,
Switch,
Text,
TouchableOpacity,
View,
} from "react-native";
import AuthService from "../services/auth";
import BiometricsService from "../services/biometrics";
import { RootStackParamList, User } from "../types";
import StorageService from "../utils/storage";
type WelcomeScreenNavigationProp = StackNavigationProp<
RootStackParamList,
"WelcomeScreen"
>;
type WelcomeScreenRouteProp = RouteProp<RootStackParamList, "WelcomeScreen">;
interface Props {
navigation: WelcomeScreenNavigationProp;
route: WelcomeScreenRouteProp;
}
const WelcomeScreen: React.FC<Props> = ({ navigation, route }) => {
const [user, setUser] = useState<User | null>(null);
const [biometricSupported, setBiometricSupported] = useState(false);
const [biometricEnabled, setBiometricEnabled] = useState(false);
useEffect(() => {
initializeScreen();
}, []);
const initializeScreen = async () => {
try {
// Get user from params or current session
const currentUser =
route.params?.user || (await AuthService.getCurrentUser());
setUser(currentUser);
// Check biometric support and status
const isSupported = await BiometricsService.isBiometricSupported();
const isEnabled = await StorageService.isBiometricEnabled();
setBiometricSupported(isSupported);
setBiometricEnabled(isEnabled);
} catch (error) {
console.error("Screen initialization failed:", error);
}
};
const handleLogout = async () => {
Alert.alert("Abmelden", "Möchten Sie sich wirklich abmelden?", [
{
text: "Abbrechen",
style: "cancel",
},
{
text: "Abmelden",
style: "destructive",
onPress: async () => {
try {
await AuthService.logout();
navigation.navigate("AuthScreen");
} catch (error) {
Alert.alert("Fehler", "Abmeldung fehlgeschlagen.");
}
},
},
]);
};
const toggleBiometric = async (value: boolean) => {
try {
if (value) {
const enabled = await AuthService.enableBiometricAuth();
if (enabled) {
setBiometricEnabled(true);
Alert.alert(
"Erfolg",
"Biometrische Authentifizierung wurde aktiviert."
);
} else {
Alert.alert(
"Fehler",
"Biometrische Authentifizierung konnte nicht aktiviert werden."
);
}
} else {
await AuthService.disableBiometricAuth();
setBiometricEnabled(false);
Alert.alert(
"Deaktiviert",
"Biometrische Authentifizierung wurde deaktiviert."
);
}
} catch (error) {
Alert.alert("Fehler", "Ein Fehler ist aufgetreten.");
}
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString("de-DE", {
year: "numeric",
month: "long",
day: "numeric",
});
};
if (!user) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<Text style={styles.errorText}>
Benutzerinformationen konnten nicht geladen werden.
</Text>
<TouchableOpacity style={styles.button} onPress={handleLogout}>
<Text style={styles.buttonText}>Zurück zur Anmeldung</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<View style={styles.header}>
<Text style={styles.welcomeTitle}>Willkommen zurück!</Text>
<Text style={styles.userName}>
{user.firstName} {user.lastName}
</Text>
</View>
<View style={styles.userInfo}>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>E-Mail:</Text>
<Text style={styles.infoValue}>{user.email}</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Mitglied seit:</Text>
<Text style={styles.infoValue}>{formatDate(user.createdAt)}</Text>
</View>
</View>
{biometricSupported && (
<View style={styles.settingsSection}>
<Text style={styles.sectionTitle}>Einstellungen</Text>
<View style={styles.settingRow}>
<Text style={styles.settingLabel}>
Biometrische Authentifizierung
</Text>
<Switch
value={biometricEnabled}
onValueChange={toggleBiometric}
thumbColor={biometricEnabled ? "#007AFF" : "#f4f3f4"}
trackColor={{ false: "#767577", true: "#81b0ff" }}
/>
</View>
</View>
)}
<View style={styles.footer}>
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
<Text style={styles.logoutButtonText}>Abmelden</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f5f5f5",
},
content: {
flex: 1,
padding: 20,
},
header: {
alignItems: "center",
marginBottom: 40,
paddingTop: 20,
},
welcomeTitle: {
fontSize: 28,
fontWeight: "bold",
color: "#333",
marginBottom: 10,
},
userName: {
fontSize: 20,
color: "#007AFF",
fontWeight: "600",
},
userInfo: {
backgroundColor: "white",
borderRadius: 10,
padding: 20,
marginBottom: 30,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 3.84,
elevation: 5,
},
infoRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 15,
},
infoLabel: {
fontSize: 16,
color: "#666",
fontWeight: "500",
},
infoValue: {
fontSize: 16,
color: "#333",
fontWeight: "400",
},
settingsSection: {
backgroundColor: "white",
borderRadius: 10,
padding: 20,
marginBottom: 30,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 3.84,
elevation: 5,
},
sectionTitle: {
fontSize: 18,
fontWeight: "bold",
color: "#333",
marginBottom: 15,
},
settingRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
settingLabel: {
fontSize: 16,
color: "#333",
},
footer: {
flex: 1,
justifyContent: "flex-end",
},
button: {
backgroundColor: "#007AFF",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
},
buttonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
logoutButton: {
backgroundColor: "#FF3B30",
paddingVertical: 15,
borderRadius: 10,
alignItems: "center",
},
logoutButtonText: {
color: "white",
fontSize: 18,
fontWeight: "600",
},
errorText: {
fontSize: 16,
color: "#FF3B30",
textAlign: "center",
marginBottom: 20,
},
});
export default WelcomeScreen;

View File

@@ -0,0 +1,204 @@
import {
AuthResponse,
LoginCredentials,
RegisterCredentials,
User,
} from "../types";
import StorageService from "../utils/storage";
import BiometricsService from "./biometrics";
import DatabaseService from "./database";
class AuthService {
async initializeAuth(): Promise<void> {
try {
await DatabaseService.initDatabase();
} catch (error) {
console.error("Auth initialization failed:", error);
throw error;
}
}
async register(credentials: RegisterCredentials): Promise<AuthResponse> {
try {
// Check if user already exists
const existingUser = await DatabaseService.getUserByEmail(
credentials.email
);
if (existingUser) {
return {
success: false,
message: "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.",
};
}
// Create new user
const newUser = await DatabaseService.registerUser(credentials);
// Save user session
await StorageService.saveUserSession(newUser);
await StorageService.saveLastLoginEmail(credentials.email);
return {
success: true,
message: "Registrierung erfolgreich!",
user: newUser,
};
} catch (error) {
console.error("Registration failed:", error);
return {
success: false,
message: "Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
};
}
}
async login(credentials: LoginCredentials): Promise<AuthResponse> {
try {
const user = await DatabaseService.loginUser(
credentials.email,
credentials.password
);
if (!user) {
return {
success: false,
message: "Ungültige E-Mail-Adresse oder Passwort.",
};
}
// Save user session
await StorageService.saveUserSession(user);
await StorageService.saveLastLoginEmail(credentials.email);
return {
success: true,
message: "Anmeldung erfolgreich!",
user,
};
} catch (error) {
console.error("Login failed:", error);
return {
success: false,
message: "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut.",
};
}
}
async biometricLogin(): Promise<AuthResponse> {
try {
const isBiometricEnabled = await StorageService.isBiometricEnabled();
if (!isBiometricEnabled) {
return {
success: false,
message: "Biometrische Authentifizierung ist nicht aktiviert.",
};
}
const lastLoginEmail = await StorageService.getLastLoginEmail();
if (!lastLoginEmail) {
return {
success: false,
message: "Keine gespeicherte Anmeldeinformation gefunden.",
};
}
const isAuthenticated =
await BiometricsService.authenticateWithBiometrics();
if (!isAuthenticated) {
return {
success: false,
message: "Biometrische Authentifizierung fehlgeschlagen.",
};
}
const user = await DatabaseService.getUserByEmail(lastLoginEmail);
if (!user) {
return {
success: false,
message: "Benutzer nicht gefunden.",
};
}
await StorageService.saveUserSession(user);
return {
success: true,
message: "Biometrische Anmeldung erfolgreich!",
user,
};
} catch (error) {
console.error("Biometric login failed:", error);
return {
success: false,
message: "Biometrische Anmeldung fehlgeschlagen.",
};
}
}
async logout(): Promise<void> {
try {
await StorageService.clearUserSession();
await StorageService.clearAuthToken();
} catch (error) {
console.error("Logout failed:", error);
throw error;
}
}
async isLoggedIn(): Promise<boolean> {
try {
const session = await StorageService.getUserSession();
return session !== null;
} catch (error) {
console.error("Check login status failed:", error);
return false;
}
}
async getCurrentUser(): Promise<User | null> {
try {
const session = await StorageService.getUserSession();
return session ? session.user : null;
} catch (error) {
console.error("Get current user failed:", error);
return null;
}
}
async enableBiometricAuth(): Promise<boolean> {
try {
const isSupported = await BiometricsService.isBiometricSupported();
if (!isSupported) {
return false;
}
const isAuthenticated =
await BiometricsService.authenticateWithBiometrics();
if (isAuthenticated) {
await StorageService.setBiometricEnabled(true);
return true;
}
return false;
} catch (error) {
console.error("Enable biometric auth failed:", error);
return false;
}
}
async disableBiometricAuth(): Promise<void> {
try {
await StorageService.setBiometricEnabled(false);
} catch (error) {
console.error("Disable biometric auth failed:", error);
throw error;
}
}
}
export default new AuthService();

View File

@@ -0,0 +1,77 @@
import { Platform } from "react-native";
import TouchID from "react-native-touch-id";
class BiometricsService {
async isBiometricSupported(): Promise<boolean> {
try {
const biometryType = await TouchID.isSupported();
return biometryType !== false;
} catch (error) {
console.error("Biometric support check failed:", error);
return false;
}
}
async getBiometricType(): Promise<string | null> {
try {
const biometryType = await TouchID.isSupported();
return biometryType;
} catch (error) {
console.error("Get biometric type failed:", error);
return null;
}
}
async authenticateWithBiometrics(): Promise<boolean> {
try {
const biometryType = await this.getBiometricType();
if (!biometryType) {
throw new Error("Biometric authentication not supported");
}
const config = {
title: "Biometrische Authentifizierung",
subTitle: "Verwenden Sie Ihren Fingerabdruck oder Face ID",
color: "#007AFF",
imageColor: "#007AFF",
imageErrorColor: "#FF0000",
sensorDescription: "Berühren Sie den Sensor",
sensorErrorDescription: "Fehlgeschlagen",
cancelText: "Abbrechen",
fallbackLabel:
Platform.OS === "ios" ? "Passcode verwenden" : "PIN verwenden",
unifiedErrors: false,
passcodeFallback: true,
};
await TouchID.authenticate(
"Authentifizierung für sicheren Zugang",
config
);
return true;
} catch (error) {
console.error("Biometric authentication failed:", error);
return false;
}
}
async showBiometricPrompt(
reason: string = "Für sicheren Zugang authentifizieren"
): Promise<boolean> {
try {
const isSupported = await this.isBiometricSupported();
if (!isSupported) {
throw new Error("Biometric authentication not available");
}
return await this.authenticateWithBiometrics();
} catch (error) {
console.error("Biometric prompt failed:", error);
return false;
}
}
}
export default new BiometricsService();

View File

@@ -0,0 +1,138 @@
import SQLite from "react-native-sqlite-storage";
import { RegisterCredentials, User } from "../types";
SQLite.DEBUG(true);
SQLite.enablePromise(true);
class DatabaseService {
private db: SQLite.SQLiteDatabase | null = null;
async initDatabase(): Promise<void> {
try {
this.db = await SQLite.openDatabase({
name: "AuthApp.db",
location: "default",
});
await this.db.executeSql(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
firstName TEXT NOT NULL,
lastName TEXT NOT NULL,
createdAt TEXT NOT NULL
)
`);
console.log("Database initialized successfully");
} catch (error) {
console.error("Database initialization failed:", error);
throw error;
}
}
async registerUser(credentials: RegisterCredentials): Promise<User> {
if (!this.db) {
throw new Error("Database not initialized");
}
try {
const createdAt = new Date().toISOString();
const result = await this.db.executeSql(
"INSERT INTO users (email, password, firstName, lastName, createdAt) VALUES (?, ?, ?, ?, ?)",
[
credentials.email,
credentials.password,
credentials.firstName,
credentials.lastName,
createdAt,
]
);
const userId = result[0].insertId;
return {
id: userId,
email: credentials.email,
password: credentials.password,
firstName: credentials.firstName,
lastName: credentials.lastName,
createdAt,
};
} catch (error) {
console.error("User registration failed:", error);
throw error;
}
}
async loginUser(email: string, password: string): Promise<User | null> {
if (!this.db) {
throw new Error("Database not initialized");
}
try {
const result = await this.db.executeSql(
"SELECT * FROM users WHERE email = ? AND password = ?",
[email, password]
);
if (result[0].rows.length > 0) {
const userData = result[0].rows.item(0);
return {
id: userData.id,
email: userData.email,
password: userData.password,
firstName: userData.firstName,
lastName: userData.lastName,
createdAt: userData.createdAt,
};
}
return null;
} catch (error) {
console.error("User login failed:", error);
throw error;
}
}
async getUserByEmail(email: string): Promise<User | null> {
if (!this.db) {
throw new Error("Database not initialized");
}
try {
const result = await this.db.executeSql(
"SELECT * FROM users WHERE email = ?",
[email]
);
if (result[0].rows.length > 0) {
const userData = result[0].rows.item(0);
return {
id: userData.id,
email: userData.email,
password: userData.password,
firstName: userData.firstName,
lastName: userData.lastName,
createdAt: userData.createdAt,
};
}
return null;
} catch (error) {
console.error("Get user by email failed:", error);
throw error;
}
}
async closeDatabase(): Promise<void> {
if (this.db) {
await this.db.close();
this.db = null;
}
}
}
export default new DatabaseService();

View File

@@ -0,0 +1,40 @@
export interface User {
id: number;
email: string;
password: string;
firstName: string;
lastName: string;
createdAt: string;
}
export interface AuthState {
isAuthenticated: boolean;
user: User | null;
loading: boolean;
}
export interface LoginCredentials {
email: string;
password: string;
}
export interface RegisterCredentials {
email: string;
password: string;
firstName: string;
lastName: string;
}
export interface AuthResponse {
success: boolean;
message: string;
user?: User;
}
export type RootStackParamList = {
AuthScreen: undefined;
LoginScreen: undefined;
RegisterScreen: undefined;
WelcomeScreen: { user: User };
MainApp: undefined;
};

View File

@@ -0,0 +1,122 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { User } from "../types";
const STORAGE_KEYS = {
USER_SESSION: "@user_session",
AUTH_TOKEN: "@auth_token",
BIOMETRIC_ENABLED: "@biometric_enabled",
LAST_LOGIN_EMAIL: "@last_login_email",
};
class StorageService {
async saveUserSession(user: User): Promise<void> {
try {
const sessionData = {
user,
loginTime: new Date().toISOString(),
};
const jsonValue = JSON.stringify(sessionData);
await AsyncStorage.setItem(STORAGE_KEYS.USER_SESSION, jsonValue);
} catch (error) {
console.error("Failed to save user session:", error);
throw error;
}
}
async getUserSession(): Promise<{ user: User; loginTime: string } | null> {
try {
const jsonValue = await AsyncStorage.getItem(STORAGE_KEYS.USER_SESSION);
return jsonValue ? JSON.parse(jsonValue) : null;
} catch (error) {
console.error("Failed to retrieve user session:", error);
return null;
}
}
async clearUserSession(): Promise<void> {
try {
await AsyncStorage.removeItem(STORAGE_KEYS.USER_SESSION);
} catch (error) {
console.error("Failed to clear user session:", error);
throw error;
}
}
async saveAuthToken(token: string): Promise<void> {
try {
await AsyncStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, token);
} catch (error) {
console.error("Failed to save auth token:", error);
throw error;
}
}
async getAuthToken(): Promise<string | null> {
try {
return await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
} catch (error) {
console.error("Failed to retrieve auth token:", error);
return null;
}
}
async clearAuthToken(): Promise<void> {
try {
await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
} catch (error) {
console.error("Failed to clear auth token:", error);
throw error;
}
}
async setBiometricEnabled(enabled: boolean): Promise<void> {
try {
await AsyncStorage.setItem(
STORAGE_KEYS.BIOMETRIC_ENABLED,
JSON.stringify(enabled)
);
} catch (error) {
console.error("Failed to save biometric setting:", error);
throw error;
}
}
async isBiometricEnabled(): Promise<boolean> {
try {
const value = await AsyncStorage.getItem(STORAGE_KEYS.BIOMETRIC_ENABLED);
return value ? JSON.parse(value) : false;
} catch (error) {
console.error("Failed to retrieve biometric setting:", error);
return false;
}
}
async saveLastLoginEmail(email: string): Promise<void> {
try {
await AsyncStorage.setItem(STORAGE_KEYS.LAST_LOGIN_EMAIL, email);
} catch (error) {
console.error("Failed to save last login email:", error);
throw error;
}
}
async getLastLoginEmail(): Promise<string | null> {
try {
return await AsyncStorage.getItem(STORAGE_KEYS.LAST_LOGIN_EMAIL);
} catch (error) {
console.error("Failed to retrieve last login email:", error);
return null;
}
}
async clearAllData(): Promise<void> {
try {
await AsyncStorage.multiRemove(Object.values(STORAGE_KEYS));
} catch (error) {
console.error("Failed to clear all storage data:", error);
throw error;
}
}
}
export default new StorageService();