- 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>
257 lines
7.0 KiB
TypeScript
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' },
|
|
});
|