feat: scaffold ShieldAI React Native mobile app MVP (FRE-4572)

Build complete Expo/React Native mobile app with:
- Auth flow: email/password login, registration, biometric auth
- Dashboard: exposure summary, spam stats, voice protection status
- DarkWatch: watch list management, exposure feed, alert toggles
- SpamShield: call/text history, whitelist/blacklist management
- VoicePrint: family member enrollment, voice analysis
- Settings: tier management, notification preferences, security
- Push notification integration via FCM/APNs
- Offline-first state management with Zustand + AsyncStorage
- Integration with @shieldai/mobile-api-client for API services
- React Navigation with auth-aware routing (stack + bottom tabs)
- Dark theme with consistent design system (colors, spacing, typography)
- Network status monitoring and offline request queuing

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-17 10:12:46 -04:00
parent 7fb8b83810
commit a071aa736e
50 changed files with 3026 additions and 13 deletions

View File

@@ -0,0 +1,146 @@
import React, { useState } from 'react';
import { StyleSheet, Text, View, SafeAreaView, KeyboardAvoidingView, Platform, ScrollView, Alert } from 'react-native';
import { RouteProp, useRoute, useNavigation } 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;
App: undefined;
};
type LoginScreenRouteProp = RouteProp<RootStackParamList, 'Login'>;
export function LoginScreen() {
const route = useRoute<LoginScreenRouteProp>();
const navigation = useNavigation<any>();
const { login, isLoading, error, 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',
},
});