Skip to Content
Code ReviewsCode Review — Dashboard Mobile App (apps/dashboard-mobile)

Code Review — Dashboard Mobile App (apps/dashboard-mobile)

Date: 2025-07
Reviewer: GitHub Copilot
Scope: Full source review of apps/dashboard-mobile/src/
Stack: Expo 55, React Native 0.83.4, React 19, TypeScript, TanStack Query v5, react-native-sse, Expo SecureStore, expo-local-authentication


Summary

The mobile app is a React Native / Expo client for the Leadmetrics dashboard. It covers home stats, activities, approvals, leads, AI chat, and notifications. The auth architecture is solid — JWT tokens in SecureStore, transparent refresh via a request-serialising singleton, and a reducer-based AuthContext. The test suite has good breadth (unit + component tests for nearly every screen).

All critical, major, and moderate issues from the original review have been resolved. One minor cosmetic issue remains before production builds.


Minor Issues

MIN-5: app.json missing app icon

File: app.json

The Android adaptiveIcon has only backgroundColor set, and no foregroundImage. The iOS icon field and the root icon field are both absent. This will use Expo’s default icon for any EAS build or native run, making the app indistinguishable from the template on a device. A proper icon should be added before any production release.


Positive Observations

  • Token storage in SecureStore — Tokens use expo-secure-store rather than AsyncStorage, giving hardware-backed encryption on both iOS (Keychain) and Android (Keystore). The right choice for JWT storage on mobile.

  • Refresh deduplication — The module-level refreshPromise singleton in client.ts ensures concurrent 401 responses trigger exactly one refresh rather than a race condition that would invalidate each other’s tokens. Well-implemented.

  • Request timeout in mobileFetchAbortController with a 30s default timeout prevents stalled connections from hanging the UI indefinitely on poor mobile networks.

  • Biometric unlock via AuthContext.unlockSession() — Successful biometric auth calls unlockSession() which dispatches RESTORE_SESSION, causing RootNavigator to transition to AppTabs correctly.

  • useInfiniteQuery for paginated lists — Both ActivitiesScreen and LeadsScreen use cursor-based infinite scroll with getNextPageParam, onEndReached, and ListFooterComponent loading indicators.

  • queryClient.ts offline caching — MMKV-backed persistence with a 24h gcTime and a maxAge purge on the persister gives good offline UX without unbounded cache growth. The exclusion of notifications from the persisted cache is correct (stale notification badges are confusing).

  • SSE stream cleanupsseRef.current?.close() before creating a new SSE connection in sendMessage() correctly prevents abandoned streams from accumulating. The isStreaming guard also prevents double-sends.

  • Good test coverage for screens — Nearly every screen has a corresponding test file. The client.test.ts covers the happy path, 401 refresh, and SESSION_EXPIRED scenarios. This is notably better test discipline than the web apps.

  • Zod + react-hook-form on LoginScreen — Proper schema validation with field-level error display and a Zod resolver, rather than manual if (!email) checks.


Issue Summary

IDSeverityFile(s)DescriptionStatus
MIN-5Minorapp.jsonMissing app icon (icon, adaptiveIcon.foregroundImage)Open

© 2026 Leadmetrics — Internal use only