831 lines
25 KiB
TypeScript
831 lines
25 KiB
TypeScript
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
import { Picker } from "@react-native-picker/picker";
|
|
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,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
View,
|
|
} from "react-native";
|
|
|
|
// Types
|
|
interface User {
|
|
id: number;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
residence: string;
|
|
residenceStreet: string;
|
|
residenceNumber: string;
|
|
workplace: string;
|
|
workplaceStreet: string;
|
|
workplaceNumber: string;
|
|
transportMode: 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 [residenceStreet, setResidenceStreet] = useState("");
|
|
const [residenceNumber, setResidenceNumber] = useState("");
|
|
const [workplace, setWorkplace] = useState("");
|
|
const [workplaceStreet, setWorkplaceStreet] = useState("");
|
|
const [workplaceNumber, setWorkplaceNumber] = 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);
|
|
const [transportMode, setTransportMode] = useState("auto");
|
|
const [editMode, setEditMode] = useState(false);
|
|
const [showOnlyName, setShowOnlyName] = 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,
|
|
residenceStreet TEXT,
|
|
residenceNumber TEXT,
|
|
workplace TEXT,
|
|
workplaceStreet TEXT,
|
|
workplaceNumber TEXT,
|
|
transportMode 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 ||
|
|
!residenceStreet ||
|
|
!residenceNumber ||
|
|
!workplace ||
|
|
!workplaceStreet ||
|
|
!workplaceNumber
|
|
) {
|
|
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, residenceStreet, residenceNumber, workplace, workplaceStreet, workplaceNumber, transportMode, latitude, longitude, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
[
|
|
email,
|
|
firstName,
|
|
lastName,
|
|
password,
|
|
residence,
|
|
residenceStreet,
|
|
residenceNumber,
|
|
workplace,
|
|
workplaceStreet,
|
|
workplaceNumber,
|
|
transportMode,
|
|
latitude ?? "",
|
|
longitude ?? "",
|
|
createdAt,
|
|
]
|
|
);
|
|
|
|
const newUser: User = {
|
|
id: result.lastInsertRowId,
|
|
email,
|
|
firstName,
|
|
lastName,
|
|
residence,
|
|
residenceStreet,
|
|
residenceNumber,
|
|
workplace,
|
|
workplaceStreet,
|
|
workplaceNumber,
|
|
transportMode,
|
|
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,
|
|
residenceStreet: result.residenceStreet,
|
|
residenceNumber: result.residenceNumber,
|
|
workplaceStreet: result.workplaceStreet,
|
|
workplaceNumber: result.workplaceNumber,
|
|
transportMode: result.transportMode,
|
|
};
|
|
|
|
// 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,
|
|
residenceStreet: user.residenceStreet,
|
|
residenceNumber: user.residenceNumber,
|
|
workplaceStreet: user.workplaceStreet,
|
|
workplaceNumber: user.workplaceNumber,
|
|
transportMode: user.transportMode,
|
|
};
|
|
|
|
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.");
|
|
}
|
|
};
|
|
|
|
const handleSaveEdit = async () => {
|
|
if (!currentUser || !db) return;
|
|
await db.runAsync(
|
|
`UPDATE users SET firstName=?, lastName=?, residence=?, residenceStreet=?, residenceNumber=?, workplace=?, workplaceStreet=?, workplaceNumber=?, transportMode=?, latitude=?, longitude=? WHERE id=?`,
|
|
[
|
|
firstName,
|
|
lastName,
|
|
residence,
|
|
residenceStreet,
|
|
residenceNumber,
|
|
workplace,
|
|
workplaceStreet,
|
|
workplaceNumber,
|
|
transportMode,
|
|
latitude ?? "",
|
|
longitude ?? "",
|
|
currentUser.id,
|
|
]
|
|
);
|
|
setEditMode(false);
|
|
setShowOnlyName(true);
|
|
setCurrentUser({
|
|
...currentUser,
|
|
firstName,
|
|
lastName,
|
|
residence,
|
|
residenceStreet,
|
|
residenceNumber,
|
|
workplace,
|
|
workplaceStreet,
|
|
workplaceNumber,
|
|
transportMode,
|
|
latitude: latitude ?? "",
|
|
longitude: longitude ?? "",
|
|
});
|
|
Alert.alert("Erfolg", "Daten gespeichert.");
|
|
};
|
|
|
|
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}>
|
|
<ScrollView
|
|
contentContainerStyle={styles.scrollContent}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
<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}>
|
|
{showOnlyName ? (
|
|
<></>
|
|
) : editMode ? (
|
|
<>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={firstName}
|
|
onChangeText={setFirstName}
|
|
placeholder="Vorname"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={lastName}
|
|
onChangeText={setLastName}
|
|
placeholder="Nachname"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={residenceStreet}
|
|
onChangeText={setResidenceStreet}
|
|
placeholder="Straße (Wohnort)"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={residenceNumber}
|
|
onChangeText={setResidenceNumber}
|
|
placeholder="Hausnummer (Wohnort)"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={residence}
|
|
onChangeText={setResidence}
|
|
placeholder="Wohnort"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={workplaceStreet}
|
|
onChangeText={setWorkplaceStreet}
|
|
placeholder="Straße (Arbeit/Schule/Uni)"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={workplaceNumber}
|
|
onChangeText={setWorkplaceNumber}
|
|
placeholder="Hausnummer (Arbeit/Schule/Uni)"
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
value={workplace}
|
|
onChangeText={setWorkplace}
|
|
placeholder="Arbeitsplatz / Uni / Schule"
|
|
/>
|
|
<View style={{ marginBottom: 15 }}>
|
|
<Text style={{ marginBottom: 5, fontWeight: "600" }}>
|
|
Verkehrsmittel
|
|
</Text>
|
|
<Picker
|
|
selectedValue={transportMode}
|
|
onValueChange={setTransportMode}
|
|
style={{ backgroundColor: "#fff" }}
|
|
>
|
|
<Picker.Item label="Auto" value="auto" />
|
|
<Picker.Item label="Zug" value="zug" />
|
|
<Picker.Item label="Bus" value="bus" />
|
|
<Picker.Item label="Fahrrad" value="fahrrad" />
|
|
<Picker.Item label="Zu Fuß" value="zufuss" />
|
|
</Picker>
|
|
</View>
|
|
<TouchableOpacity
|
|
style={[styles.submitButton, { marginBottom: 10 }]}
|
|
onPress={handleSaveEdit}
|
|
>
|
|
<Text style={styles.submitButtonText}>Speichern</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={styles.logoutButton}
|
|
onPress={() => setEditMode(false)}
|
|
>
|
|
<Text style={styles.logoutButtonText}>Abbrechen</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Text style={styles.infoLabel}>E-Mail:</Text>
|
|
<Text style={styles.infoValue}>{currentUser.email}</Text>
|
|
<Text style={styles.infoLabel}>Wohnort:</Text>
|
|
<Text style={styles.infoValue}>
|
|
{currentUser.residenceStreet} {currentUser.residenceNumber},{" "}
|
|
{currentUser.residence}
|
|
</Text>
|
|
<Text style={styles.infoLabel}>Arbeit/Schule/Uni:</Text>
|
|
<Text style={styles.infoValue}>
|
|
{currentUser.workplaceStreet} {currentUser.workplaceNumber},{" "}
|
|
{currentUser.workplace}
|
|
</Text>
|
|
<Text style={styles.infoLabel}>Verkehrsmittel:</Text>
|
|
<Text style={styles.infoValue}>
|
|
{currentUser.transportMode}
|
|
</Text>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.submitButton,
|
|
{ marginTop: 10, marginBottom: 10 },
|
|
]}
|
|
onPress={() => setEditMode(true)}
|
|
>
|
|
<Text style={styles.submitButtonText}>Bearbeiten</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
)}
|
|
</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>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container}>
|
|
<KeyboardAvoidingView
|
|
style={styles.keyboardView}
|
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
>
|
|
<ScrollView
|
|
contentContainerStyle={styles.scrollContent}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
<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="Straße (Wohnort)"
|
|
value={residenceStreet}
|
|
onChangeText={setResidenceStreet}
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
placeholder="Hausnummer (Wohnort)"
|
|
value={residenceNumber}
|
|
onChangeText={setResidenceNumber}
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
placeholder="Wohnort"
|
|
value={residence}
|
|
onChangeText={setResidence}
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
placeholder="Straße (Arbeit/Schule/Uni)"
|
|
value={workplaceStreet}
|
|
onChangeText={setWorkplaceStreet}
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
placeholder="Hausnummer (Arbeit/Schule/Uni)"
|
|
value={workplaceNumber}
|
|
onChangeText={setWorkplaceNumber}
|
|
/>
|
|
<TextInput
|
|
style={styles.input}
|
|
placeholder="Arbeitsplatz / Uni / Schule"
|
|
value={workplace}
|
|
onChangeText={setWorkplace}
|
|
/>
|
|
<View style={{ marginBottom: 15 }}>
|
|
<Text style={{ marginBottom: 5, fontWeight: "600" }}>
|
|
Verkehrsmittel
|
|
</Text>
|
|
<Picker
|
|
selectedValue={transportMode}
|
|
onValueChange={setTransportMode}
|
|
style={{ backgroundColor: "#fff" }}
|
|
>
|
|
<Picker.Item label="Auto" value="auto" />
|
|
<Picker.Item label="Zug" value="zug" />
|
|
<Picker.Item label="Bus" value="bus" />
|
|
<Picker.Item label="Fahrrad" value="fahrrad" />
|
|
<Picker.Item label="Zu Fuß" value="zufuss" />
|
|
</Picker>
|
|
</View>
|
|
<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>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: "#f8f9fa",
|
|
},
|
|
keyboardView: {
|
|
flex: 1,
|
|
},
|
|
scrollContent: {
|
|
flexGrow: 1,
|
|
justifyContent: "center",
|
|
paddingVertical: 30,
|
|
},
|
|
authContainer: {
|
|
flex: 1,
|
|
justifyContent: "center",
|
|
paddingHorizontal: 30,
|
|
paddingBottom: 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",
|
|
},
|
|
});
|