Initial Mobile

This commit is contained in:
2026-02-19 18:29:06 +01:00
parent 844af30b18
commit c782492ab5
49 changed files with 15375 additions and 33 deletions

164
mobile/app/login.tsx Normal file
View File

@@ -0,0 +1,164 @@
import { useState } from 'react';
import { Redirect, router } from 'expo-router';
import {
ActivityIndicator,
KeyboardAvoidingView,
Platform,
Pressable,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { API_BASE_URL } from '@/src/config/env';
import { useAuth, useTokenStorageInfo } from '@/src/providers/auth-provider';
export default function LoginScreen() {
const { token, requiresTenantSelection, login } = useAuth();
const storageInfo = useTokenStorageInfo();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
if (token) {
return <Redirect href={requiresTenantSelection ? '/tenant-select' : '/(tabs)'} />;
}
async function onSubmit() {
setIsSubmitting(true);
setError(null);
try {
await login(email.trim(), password);
router.replace('/');
} catch (err) {
setError(err instanceof Error ? err.message : 'Login fehlgeschlagen.');
} finally {
setIsSubmitting(false);
}
}
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
style={styles.container}>
<View style={styles.card}>
<Text style={styles.title}>FEDEO Mobile</Text>
<Text style={styles.subtitle}>Login mit anschliessender Tenant-Auswahl</Text>
<Text style={styles.label}>E-Mail</Text>
<TextInput
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
placeholder="name@firma.de"
placeholderTextColor="#9ca3af"
style={styles.input}
value={email}
onChangeText={setEmail}
/>
<Text style={styles.label}>Passwort</Text>
<TextInput
secureTextEntry
placeholder="••••••••"
placeholderTextColor="#9ca3af"
style={styles.input}
value={password}
onChangeText={setPassword}
/>
{error ? <Text style={styles.error}>{error}</Text> : null}
<Pressable
style={[styles.button, isSubmitting ? styles.buttonDisabled : null]}
onPress={onSubmit}
disabled={isSubmitting || !email || !password}>
{isSubmitting ? <ActivityIndicator color="#ffffff" /> : <Text style={styles.buttonText}>Anmelden</Text>}
</Pressable>
<View style={styles.metaBox}>
<Text style={styles.metaText}>API: {API_BASE_URL}</Text>
<Text style={styles.metaText}>Token Storage: {storageInfo.mode}</Text>
</View>
</View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
backgroundColor: '#f3f4f6',
},
card: {
backgroundColor: '#ffffff',
borderRadius: 16,
padding: 20,
gap: 10,
shadowColor: '#111827',
shadowOpacity: 0.08,
shadowRadius: 16,
shadowOffset: { width: 0, height: 8 },
elevation: 3,
},
title: {
fontSize: 24,
fontWeight: '700',
color: '#111827',
},
subtitle: {
color: '#6b7280',
marginBottom: 8,
},
label: {
fontSize: 14,
color: '#374151',
fontWeight: '500',
},
input: {
borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
fontSize: 16,
color: '#111827',
},
button: {
marginTop: 6,
backgroundColor: '#16a34a',
borderRadius: 10,
minHeight: 44,
alignItems: 'center',
justifyContent: 'center',
},
buttonDisabled: {
backgroundColor: '#86efac',
},
buttonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
error: {
color: '#dc2626',
fontSize: 13,
},
metaBox: {
marginTop: 8,
paddingTop: 8,
borderTopWidth: 1,
borderTopColor: '#e5e7eb',
gap: 4,
},
metaText: {
fontSize: 12,
color: '#6b7280',
},
});