Files
task-team/mobile/app/login.tsx
Admin db81100b5b React Native Expo app — full mobile client
- Expo SDK 54, expo-router, NativeWind
- 7 screens: login, tasks, calendar, goals, chat, settings, tabs
- API client (api.hasdo.info), SecureStore auth, AuthContext
- Tab navigation: Ukoly, Kalendar, Cile, Chat, Nastaveni
- Pull-to-refresh, FAB, group colors, priority dots
- Calendar grid with task dots
- AI chat with keyboard avoiding
- Web export verified (780 modules, 1.5MB bundle)
- Bundle ID: info.hasdo.taskteam

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 15:09:35 +00:00

257 lines
7.0 KiB
TypeScript

import { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
KeyboardAvoidingView,
Platform,
ScrollView,
Alert,
ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useAuthContext } from '../lib/AuthContext';
export default function LoginScreen() {
const { login, register } = useAuthContext();
const [mode, setMode] = useState<'login' | 'register'>('login');
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
if (!email.trim() || !password.trim()) {
Alert.alert('Chyba', 'Vyplnte vsechna pole');
return;
}
if (mode === 'register' && !name.trim()) {
Alert.alert('Chyba', 'Vyplnte jmeno');
return;
}
setLoading(true);
try {
if (mode === 'login') {
await login(email.trim(), password);
} else {
await register(email.trim(), name.trim(), password);
}
} catch (err: any) {
Alert.alert('Chyba', err.message || 'Nepodarilo se prihlasit');
} finally {
setLoading(false);
}
};
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<ScrollView
contentContainerStyle={styles.scrollContent}
keyboardShouldPersistTaps="handled"
>
{/* Logo */}
<View style={styles.logoSection}>
<View style={styles.logoCircle}>
<Ionicons name="checkbox" size={40} color="#fff" />
</View>
<Text style={styles.appName}>Task Team</Text>
<Text style={styles.appDesc}>
Spravujte ukoly, cile a tymovou spolupraci
</Text>
</View>
{/* Form */}
<View style={styles.form}>
{/* Mode toggle */}
<View style={styles.modeToggle}>
<TouchableOpacity
style={[styles.modeBtn, mode === 'login' && styles.modeBtnActive]}
onPress={() => setMode('login')}
>
<Text
style={[
styles.modeBtnText,
mode === 'login' && styles.modeBtnTextActive,
]}
>
Prihlaseni
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modeBtn, mode === 'register' && styles.modeBtnActive]}
onPress={() => setMode('register')}
>
<Text
style={[
styles.modeBtnText,
mode === 'register' && styles.modeBtnTextActive,
]}
>
Registrace
</Text>
</TouchableOpacity>
</View>
{mode === 'register' && (
<View style={styles.inputGroup}>
<Ionicons
name="person-outline"
size={20}
color="#94A3B8"
style={styles.inputIcon}
/>
<TextInput
style={styles.input}
placeholder="Jmeno"
value={name}
onChangeText={setName}
autoCapitalize="words"
/>
</View>
)}
<View style={styles.inputGroup}>
<Ionicons
name="mail-outline"
size={20}
color="#94A3B8"
style={styles.inputIcon}
/>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
/>
</View>
<View style={styles.inputGroup}>
<Ionicons
name="lock-closed-outline"
size={20}
color="#94A3B8"
style={styles.inputIcon}
/>
<TextInput
style={[styles.input, { flex: 1 }]}
placeholder="Heslo"
value={password}
onChangeText={setPassword}
secureTextEntry={!showPassword}
/>
<TouchableOpacity
onPress={() => setShowPassword(!showPassword)}
style={styles.eyeBtn}
>
<Ionicons
name={showPassword ? 'eye-off-outline' : 'eye-outline'}
size={20}
color="#94A3B8"
/>
</TouchableOpacity>
</View>
<TouchableOpacity
style={[styles.submitBtn, loading && styles.submitBtnDisabled]}
onPress={handleSubmit}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.submitBtnText}>
{mode === 'login' ? 'Prihlasit se' : 'Zaregistrovat se'}
</Text>
)}
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F8FAFC' },
scrollContent: {
flexGrow: 1,
justifyContent: 'center',
padding: 24,
},
logoSection: { alignItems: 'center', marginBottom: 40 },
logoCircle: {
width: 80,
height: 80,
borderRadius: 20,
backgroundColor: '#3B82F6',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 16,
},
appName: { fontSize: 28, fontWeight: '800', color: '#1E293B' },
appDesc: {
fontSize: 15,
color: '#64748B',
textAlign: 'center',
marginTop: 8,
},
form: {},
modeToggle: {
flexDirection: 'row',
backgroundColor: '#E2E8F0',
borderRadius: 12,
padding: 4,
marginBottom: 24,
},
modeBtn: {
flex: 1,
paddingVertical: 10,
borderRadius: 10,
alignItems: 'center',
},
modeBtnActive: { backgroundColor: '#fff' },
modeBtnText: { fontSize: 15, color: '#64748B', fontWeight: '500' },
modeBtnTextActive: { color: '#3B82F6', fontWeight: '700' },
inputGroup: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#E2E8F0',
marginBottom: 12,
paddingHorizontal: 14,
},
inputIcon: { marginRight: 10 },
input: {
flex: 1,
paddingVertical: 14,
fontSize: 16,
color: '#1E293B',
},
eyeBtn: { padding: 4 },
submitBtn: {
backgroundColor: '#3B82F6',
borderRadius: 12,
paddingVertical: 16,
alignItems: 'center',
marginTop: 8,
shadowColor: '#3B82F6',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 4,
},
submitBtnDisabled: { backgroundColor: '#94A3B8' },
submitBtnText: { color: '#fff', fontSize: 17, fontWeight: '700' },
});