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>
This commit is contained in:
2026-05-17 10:51:14 -04:00
parent a071aa736e
commit 90a223bc79
16 changed files with 130 additions and 67 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Text, ViewStyle, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { DashboardScreen } from '@/screens/dashboard';
import { DarkWatchScreen } from '@/screens/darkwatch';
@@ -18,25 +19,29 @@ type MainTabParamList = {
const Tab = createBottomTabNavigator<MainTabParamList>();
const iconMap: Record<string, string> = {
Dashboard: '\u{1F6E1}\u{FE0F}',
DarkWatch: '\u{1F441}\u{FE0F}',
SpamShield: '\u{1F6AB}',
VoicePrint: '\u{1F399}\u{FE0F}',
Settings: '\u{2699}\u{FE0F}',
const iconMap: Record<string, keyof typeof Ionicons.glyphMap> = {
Dashboard: 'shield-outline',
DarkWatch: 'eye-outline',
SpamShield: 'ban-outline',
VoicePrint: 'mic-outline',
Settings: 'settings-outline',
};
function TabIcon({ routeName, color }: { routeName: string; color: string }) {
return (
<Text style={[styles.icon, { color }]}>{iconMap[routeName] || '\u{2022}'}</Text>
);
}
const iconActiveMap: Record<string, keyof typeof Ionicons.glyphMap> = {
Dashboard: 'shield',
DarkWatch: 'eye',
SpamShield: 'ban',
VoicePrint: 'mic',
Settings: 'settings',
};
const styles = StyleSheet.create({
icon: {
fontSize: 22,
},
});
function TabIcon({ routeName, color, focused }: { routeName: string; color: string; focused: boolean }) {
const iconName = focused
? (iconActiveMap[routeName] as keyof typeof Ionicons.glyphMap)
: (iconMap[routeName] as keyof typeof Ionicons.glyphMap);
return <Ionicons name={iconName} size={24} color={color} />;
}
export function MainTabNavigator() {
return (
@@ -75,7 +80,7 @@ export function MainTabNavigator() {
options={{
headerTitle: 'Dashboard',
tabBarLabel: 'Home',
tabBarIcon: ({ color }) => <TabIcon routeName="Dashboard" color={color} />,
tabBarIcon: ({ color, focused }) => <TabIcon routeName="Dashboard" color={color} focused={focused} />,
}}
/>
<Tab.Screen
@@ -84,7 +89,7 @@ export function MainTabNavigator() {
options={{
headerTitle: 'DarkWatch',
tabBarLabel: 'DarkWatch',
tabBarIcon: ({ color }) => <TabIcon routeName="DarkWatch" color={color} />,
tabBarIcon: ({ color, focused }) => <TabIcon routeName="DarkWatch" color={color} focused={focused} />,
}}
/>
<Tab.Screen
@@ -93,7 +98,7 @@ export function MainTabNavigator() {
options={{
headerTitle: 'SpamShield',
tabBarLabel: 'SpamShield',
tabBarIcon: ({ color }) => <TabIcon routeName="SpamShield" color={color} />,
tabBarIcon: ({ color, focused }) => <TabIcon routeName="SpamShield" color={color} focused={focused} />,
}}
/>
<Tab.Screen
@@ -102,7 +107,7 @@ export function MainTabNavigator() {
options={{
headerTitle: 'VoicePrint',
tabBarLabel: 'VoicePrint',
tabBarIcon: ({ color }) => <TabIcon routeName="VoicePrint" color={color} />,
tabBarIcon: ({ color, focused }) => <TabIcon routeName="VoicePrint" color={color} focused={focused} />,
}}
/>
<Tab.Screen
@@ -111,7 +116,7 @@ export function MainTabNavigator() {
options={{
headerTitle: 'Settings',
tabBarLabel: 'Settings',
tabBarIcon: ({ color }) => <TabIcon routeName="Settings" color={color} />,
tabBarIcon: ({ color, focused }) => <TabIcon routeName="Settings" color={color} focused={focused} />,
}}
/>
</Tab.Navigator>