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,19 +1,30 @@
import { useEffect, useCallback } from 'react';
import { useEffect, useCallback, useRef } from 'react';
import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
import * as Device from 'expo-device';
import { deviceService, notificationService } from '@shieldai/mobile-api-client';
import { useSettingsStore } from '@/store/settingsStore';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export function usePushNotifications() {
const { preferences } = useSettingsStore();
const preferencesRef = useRef(useSettingsStore.getState().preferences);
useEffect(() => {
const subscription = useSettingsStore.subscribe((state) => {
preferencesRef.current = state.preferences;
});
return subscription;
}, []);
Notifications.setNotificationHandler({
handleNotification: async () => {
const prefs = preferencesRef.current;
return {
shouldShowAlert: prefs.pushNotifications,
shouldPlaySound: prefs.pushNotifications,
shouldSetBadge: false,
};
},
});
const registerForPushNotifications = useCallback(async () => {
try {
@@ -33,13 +44,17 @@ export function usePushNotifications() {
projectId: 'shieldai-project-id',
})).data;
await deviceService.registerDevice({
platform: Platform.OS === 'ios' ? 'ios' : 'android',
pushToken: token,
modelName: Platform.OS === 'ios' ? 'iPhone' : 'Android',
osVersion: Platform.Version.toString(),
appVersion: '1.0.0',
});
try {
await deviceService.registerDevice({
platform: Platform.OS === 'ios' ? 'ios' : 'android',
pushToken: token,
modelName: Platform.OS === 'ios' ? 'iPhone' : 'Android',
osVersion: Device.osVersion || '0',
appVersion: '1.0.0',
});
} catch (deviceError) {
console.warn('Device registration failed (will retry on next launch):', deviceError);
}
return token;
} catch (error) {
@@ -51,7 +66,7 @@ export function usePushNotifications() {
useEffect(() => {
const subscription = Notifications.addNotificationReceivedListener((notification) => {
const type = notification.request.content.data?.type;
const prefs = useSettingsStore.getState().preferences;
const prefs = preferencesRef.current;
if (type === 'darkwatch_alert' && !prefs.darkwatchAlert) return;
if (type === 'spam_blocked' && !prefs.spamBlocked) return;