Files
Kordant/packages/mobile/src/screens/auth/LoginScreen.tsx
Michael Freno 90a223bc79 fix: address code review findings for mobile app (FRE-4572)
P0 fixes:
- Replace crypto.randomUUID() with uuid v4 (not available in RN)
- Replace Platform.Version with expo-device osVersion
- Fix auth navigation types, remove unused App route

P1 fixes:
- Push notification handler respects user preferences (useRef pattern)
- Fix stale closure: use zustand subscribe + useRef for live preferences
- Add retry logging for device registration failures
- Replace emoji tab icons with @expo/vector-icons Ionicons
- Document API integration TODOs in all local-only stores

P2 fixes:
- Add __DEV__ global declaration (global.d.ts)
- Fix package.json main field to expo/AppEntry.js
- Add retry logging for push device registration
- Add z-index/elevation to LoadingOverlay
- Add visual indicator to EmptyState icon

P3 fixes:
- Type navigation with NavigationProp<RootStackParamList>
- Move getSeverityColor to theme.ts (single source of truth)
- Add useMemo for SpamShield filter computations
- Verified usesNonExemptEncryption: false is correct for expo-secure-store

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-17 10:51:14 -04:00

143 lines
3.7 KiB
TypeScript

import React, { useState } from 'react';
import { StyleSheet, Text, View, SafeAreaView, KeyboardAvoidingView, Platform, ScrollView, Alert } from 'react-native';
import { useNavigation, NavigationProp } from '@react-navigation/native';
import { useAuthStore } from '@/store/authStore';
import { Button, Input } from '@/components';
import { COLORS, FONT_SIZES, SPACING } from '@/constants/theme';
type RootStackParamList = {
Login: undefined;
Register: undefined;
};
export function LoginScreen() {
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const { login, isLoading, clearError } = useAuthStore();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [formError, setFormError] = useState('');
const handleLogin = async () => {
setFormError('');
clearError();
if (!email || !password) {
setFormError('Please fill in all fields');
return;
}
try {
await login(email, password);
} catch (err: any) {
setFormError(err.message || 'Login failed. Please try again.');
Alert.alert('Login Failed', err.message || 'Please check your credentials and try again.');
}
};
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}
>
<ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.header}>
<Text style={styles.logo}>ShieldAI</Text>
<Text style={styles.tagline}>Your digital protection suite</Text>
</View>
<View style={styles.form}>
{formError && <Text style={styles.error}>{formError}</Text>}
<Input
label="Email"
placeholder="you@example.com"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
autoComplete="email"
/>
<Input
label="Password"
placeholder="••••••••"
value={password}
onChangeText={setPassword}
secureTextEntry
autoComplete="password"
/>
<Button
title={isLoading ? 'Signing in...' : 'Sign In'}
onPress={handleLogin}
disabled={isLoading}
fullWidth
/>
<View style={styles.footer}>
<Text style={styles.footerText}>Don't have an account? </Text>
<Text style={styles.link} onPress={() => navigation.navigate('Register')}>
Sign Up
</Text>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.background,
},
keyboardView: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
justifyContent: 'center',
padding: SPACING.lg,
},
header: {
alignItems: 'center',
marginBottom: SPACING.xxl,
},
logo: {
color: COLORS.primary,
fontSize: FONT_SIZES.xxxl,
fontWeight: 'bold',
},
tagline: {
color: COLORS.textSecondary,
fontSize: FONT_SIZES.md,
marginTop: SPACING.sm,
},
form: {
width: '100%',
},
error: {
color: COLORS.danger,
fontSize: FONT_SIZES.sm,
marginBottom: SPACING.md,
textAlign: 'center',
},
footer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: SPACING.lg,
},
footerText: {
color: COLORS.textSecondary,
fontSize: FONT_SIZES.sm,
},
link: {
color: COLORS.primary,
fontSize: FONT_SIZES.sm,
fontWeight: '600',
},
});