diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs index 96a45d26d..9d6af54b6 100644 --- a/app/.eslintrc.cjs +++ b/app/.eslintrc.cjs @@ -1,11 +1,23 @@ module.exports = { root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2021, + sourceType: 'module', + ecmaFeatures: { jsx: true }, + }, extends: [ '@react-native', - 'plugin:prettier/recommended', + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', 'plugin:jest/recommended', + 'plugin:prettier/recommended', ], - plugins: ['header', 'jest', 'prettier', 'simple-import-sort'], + plugins: ['header', 'simple-import-sort', 'import'], ignorePatterns: [ 'ios/', 'android/', @@ -13,12 +25,28 @@ module.exports = { 'node_modules/', 'web/dist/', '.tamagui/*', + '*.js.map', + '*.d.ts', 'metro.config.cjs', ], + settings: { + react: { version: 'detect' }, + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + 'import/ignore': ['react-native'], + }, rules: { - // Import sorting rules - 'simple-import-sort/imports': 'warn', + // Import/Export Rules + 'import/order': 'off', + 'no-duplicate-imports': 'off', 'simple-import-sort/exports': 'warn', + 'simple-import-sort/imports': 'warn', + + // Header rule 'header/header': [ 2, 'line', @@ -27,21 +55,35 @@ module.exports = { ], // Add prettier rule to show prettier errors as ESLint errors - 'prettier/prettier': [ - 'warn', - { - // Fix for TypeScript union types indentation - typescriptBracketSpacing: true, - typeAssertionStyle: 'as', - }, - { usePrettierrc: true }, - ], + 'prettier/prettier': ['warn', {}, { usePrettierrc: true }], - // Preserve project-specific rule exemptions + // React Core Rules + 'react/no-unescaped-entities': 'off', + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', 'react-native/no-inline-styles': 'off', - 'react-hooks/exhaustive-deps': 'off', - // Override any ESLint rules that conflict with the TypeScript union type formatting + // React Hooks Rules + 'react-hooks/exhaustive-deps': 'warn', + + // General JavaScript Rules + // Warn on common issues but don't block development + 'no-console': 'warn', + 'no-empty-pattern': 'off', + 'prefer-const': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'no-redeclare': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-namespace': 'off', + 'no-case-declarations': 'off', + 'react/no-children-prop': 'off', + 'import/no-unresolved': 'error', + '@typescript-eslint/ban-ts-comment': 'off', + 'no-empty': 'off', + + // Override rules conflicting with TypeScript union formatting '@typescript-eslint/indent': 'off', }, overrides: [ diff --git a/app/env.ts b/app/env.ts index 2ebb85beb..36dbb4d33 100644 --- a/app/env.ts +++ b/app/env.ts @@ -5,6 +5,8 @@ */ export const IS_TEST_BUILD = process.env.IS_TEST_BUILD === 'true'; +export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID = + process.env.GOOGLE_SIGNIN_ANDROID_CLIENT_ID; export const GOOGLE_SIGNIN_WEB_CLIENT_ID = process.env.GOOGLE_SIGNIN_WEB_CLIENT_ID; export const SENTRY_DSN = process.env.SENTRY_DSN; diff --git a/app/index.js b/app/index.js index 4921b7193..290cf446a 100644 --- a/app/index.js +++ b/app/index.js @@ -4,6 +4,7 @@ * @format */ import './src/utils/ethers'; +import 'react-native-gesture-handler'; import { config } from '@tamagui/config/v2-native'; import React from 'react'; diff --git a/app/package.json b/app/package.json index 6ed4463f3..e596352ac 100644 --- a/app/package.json +++ b/app/package.json @@ -153,10 +153,14 @@ "@types/react-native-sqlite-storage": "^6.0.5", "@types/react-native-web": "^0", "@types/react-test-renderer": "^18", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react-swc": "^3.10.2", "eslint": "^8.19.0", - "eslint-config-prettier": "^10.1.2", + "eslint-config-prettier": "10.1.8", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^28.11.1", "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-simple-import-sort": "^12.1.1", diff --git a/app/scripts/mobile-deploy-confirm.cjs b/app/scripts/mobile-deploy-confirm.cjs index 928c2616e..b5124d0d1 100755 --- a/app/scripts/mobile-deploy-confirm.cjs +++ b/app/scripts/mobile-deploy-confirm.cjs @@ -278,9 +278,8 @@ function hasUncommittedChanges() { /** * Displays the header and platform information * @param {string} platform - Target platform - * @param {Object} versions - Version information object */ -function displayDeploymentHeader(platform, versions) { +function displayDeploymentHeader(platform) { console.log(`\n${CONSOLE_SYMBOLS.MOBILE} Mobile App Deployment Confirmation`); console.log('====================================='); console.log(`${CONSOLE_SYMBOLS.ROCKET} Platform: ${platform.toUpperCase()}`); @@ -351,7 +350,7 @@ function displayWarningsAndGitStatus() { * @param {string} deploymentMethod - The deployment method to use */ function displayFullConfirmation(platform, versions, deploymentMethod) { - displayDeploymentHeader(platform, versions); + displayDeploymentHeader(platform); displayDeploymentMethod(deploymentMethod); displayPlatformVersions(platform, versions); displayWarningsAndGitStatus(); diff --git a/app/src/components/ErrorBoundary.tsx b/app/src/components/ErrorBoundary.tsx index 0d6444d34..ae6545908 100644 --- a/app/src/components/ErrorBoundary.tsx +++ b/app/src/components/ErrorBoundary.tsx @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React from 'react'; +import React, { Component } from 'react'; import { Text, View } from 'react-native'; import analytics from '../utils/analytics'; @@ -15,7 +15,7 @@ interface State { hasError: boolean; } -class ErrorBoundary extends React.Component { +class ErrorBoundary extends Component { constructor(props: Props) { super(props); this.state = { hasError: false }; diff --git a/app/src/components/Mnemonic.tsx b/app/src/components/Mnemonic.tsx index aae3a3795..50925c36c 100644 --- a/app/src/components/Mnemonic.tsx +++ b/app/src/components/Mnemonic.tsx @@ -63,7 +63,7 @@ const Mnemonic = ({ words = REDACTED, onRevealWords }: MnemonicProps) => { Clipboard.setString(words.join(' ')); setCopied(true); setTimeout(() => setCopied(false), 2500); - }, [words, revealWords]); + }, [onRevealWords, revealWords, setHasViewedRecoveryPhrase, words]); return ( @@ -98,7 +98,7 @@ const Mnemonic = ({ words = REDACTED, onRevealWords }: MnemonicProps) => { borderTopWidth={0} borderBottomLeftRadius="$5" borderBottomRightRadius="$5" - py="$2" + paddingVertical={16} onPress={copyToClipboardOrReveal} width="100%" textAlign="center" diff --git a/app/src/components/NavBar/BaseNavBar.tsx b/app/src/components/NavBar/BaseNavBar.tsx index 8d1b6bab8..4259d5799 100644 --- a/app/src/components/NavBar/BaseNavBar.tsx +++ b/app/src/components/NavBar/BaseNavBar.tsx @@ -39,7 +39,7 @@ export const LeftAction: React.FC = ({ onPress, ...props }) => { - let children: React.ReactNode = useMemo(() => { + const children: React.ReactNode = useMemo(() => { switch (component) { case 'back': return ( @@ -69,7 +69,7 @@ export const LeftAction: React.FC = ({ ); } - }, [component]); + }, [color, component, onPress]); if (!children) { return null; diff --git a/app/src/components/native/PassportCamera.web.tsx b/app/src/components/native/PassportCamera.web.tsx index 2b11e6af0..a6eee2d51 100644 --- a/app/src/components/native/PassportCamera.web.tsx +++ b/app/src/components/native/PassportCamera.web.tsx @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { extractMRZInfo } from '../../utils/utils'; @@ -27,7 +27,7 @@ export const PassportCamera: React.FC = ({ }, [onPassportRead, isMounted]); // Web stub - no functionality yet - React.useEffect(() => { + useEffect(() => { // Simulate that the component is not ready for web if (isMounted) { console.warn('PassportCamera: Web implementation not yet available'); diff --git a/app/src/components/native/RCTFragment.tsx b/app/src/components/native/RCTFragment.tsx index 350f52e40..30a7c1e9a 100644 --- a/app/src/components/native/RCTFragment.tsx +++ b/app/src/components/native/RCTFragment.tsx @@ -1,8 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React, { useEffect, useRef } from 'react'; -import { NativeSyntheticEvent, requireNativeComponent } from 'react-native'; -import { findNodeHandle, UIManager } from 'react-native'; +import { + findNodeHandle, + NativeSyntheticEvent, + requireNativeComponent, + UIManager, +} from 'react-native'; export interface RCTFragmentViewManagerProps { RCTFragmentViewManager: ReturnType; diff --git a/app/src/hooks/useAesopRedesign.ts b/app/src/hooks/useAesopRedesign.ts index 23c4ae7c0..4188532ba 100644 --- a/app/src/hooks/useAesopRedesign.ts +++ b/app/src/hooks/useAesopRedesign.ts @@ -3,7 +3,7 @@ import { IS_TEST_BUILD } from '@env'; export const shouldShowAesopRedesign = (): boolean => { - return JSON.parse(IS_TEST_BUILD ?? 'false'); + return JSON.parse(String(IS_TEST_BUILD ?? 'false')); }; export const useAesopRedesign = (): boolean => { diff --git a/app/src/hooks/useConnectionModal.ts b/app/src/hooks/useConnectionModal.ts index cd99ddd60..ad854eee2 100644 --- a/app/src/hooks/useConnectionModal.ts +++ b/app/src/hooks/useConnectionModal.ts @@ -56,7 +56,7 @@ export default function useConnectionModal() { }, 2000); return () => clearTimeout(timeoutId); - }, [hasNoConnection, dismissModal, visible, navigationRef.isReady()]); + }, [dismissModal, hasNoConnection, hideNetworkModal, showModal, visible]); return { visible, diff --git a/app/src/hooks/useMnemonic.ts b/app/src/hooks/useMnemonic.ts index 29572abbb..e9b978634 100644 --- a/app/src/hooks/useMnemonic.ts +++ b/app/src/hooks/useMnemonic.ts @@ -16,7 +16,7 @@ export default function useMnemonic() { } const { entropy } = storedMnemonic.data; setMnemonic(ethers.Mnemonic.fromEntropy(entropy).phrase.split(' ')); - }, []); + }, [getOrCreateMnemonic]); return { loadMnemonic, diff --git a/app/src/layouts/ExpandableBottomLayout.tsx b/app/src/layouts/ExpandableBottomLayout.tsx index e49a7dbc4..c82199985 100644 --- a/app/src/layouts/ExpandableBottomLayout.tsx +++ b/app/src/layouts/ExpandableBottomLayout.tsx @@ -104,7 +104,7 @@ const BottomSection: React.FC = ({ ...props }) => { const { bottom: safeAreaBottom } = useSafeAreaInsets(); - const incomingBottom = props.paddingBottom ?? props.pb ?? 0; + const incomingBottom = props.paddingBottom ?? 0; const minBottom = safeAreaBottom + extraYPadding; const totalBottom = typeof incomingBottom === 'number' ? minBottom + incomingBottom : minBottom; diff --git a/app/src/layouts/SimpleScrolledTitleLayout.tsx b/app/src/layouts/SimpleScrolledTitleLayout.tsx index a5db90302..13ed0b104 100644 --- a/app/src/layouts/SimpleScrolledTitleLayout.tsx +++ b/app/src/layouts/SimpleScrolledTitleLayout.tsx @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ScrollView, YStack } from 'tamagui'; @@ -11,7 +11,7 @@ import { white } from '../utils/colors'; import { ExpandableBottomLayout } from './ExpandableBottomLayout'; interface DetailListProps - extends React.PropsWithChildren<{ + extends PropsWithChildren<{ title: string; onDismiss: () => void; secondaryButtonText?: string; @@ -44,7 +44,7 @@ export default function SimpleScrolledTitleLayout({ {footer && {footer}} {secondaryButtonText && onSecondaryButtonPress && ( - + {secondaryButtonText} )} diff --git a/app/src/mocks/react-native-gesture-handler.ts b/app/src/mocks/react-native-gesture-handler.ts index fafabfc8d..01af87d84 100644 --- a/app/src/mocks/react-native-gesture-handler.ts +++ b/app/src/mocks/react-native-gesture-handler.ts @@ -4,14 +4,14 @@ * Web-compatible mock for react-native-gesture-handler */ -import React from 'react'; +import React, { createElement } from 'react'; // Mock GestureHandlerRootView as a simple wrapper export const GestureHandlerRootView: React.FC<{ children: React.ReactNode; [key: string]: any; }> = ({ children, ...props }) => { - return React.createElement('div', props, children); + return createElement('div', props, children); }; const returnValue = { @@ -44,7 +44,7 @@ export const GestureDetector: React.FC<{ children: React.ReactNode; gesture?: any; }> = ({ children, gesture: _gesture }) => { - return React.createElement('div', {}, children); + return createElement('div', {}, children); }; // Mock other commonly used exports diff --git a/app/src/mocks/react-native-safe-area-context.js b/app/src/mocks/react-native-safe-area-context.js index 2b455d7a6..765d76c97 100644 --- a/app/src/mocks/react-native-safe-area-context.js +++ b/app/src/mocks/react-native-safe-area-context.js @@ -1,10 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React from 'react'; +import { createContext, createElement, Fragment } from 'react'; // On web we dont need safe context area since we will be inside another app. (and it doesnt work) export function SafeAreaProvider({ children }) { - return React.createElement(React.Fragment, null, children); + return createElement(Fragment, null, children); } export function useSafeAreaInsets() { @@ -16,7 +16,7 @@ export function useSafeAreaFrame() { } export function SafeAreaView(props) { - return React.createElement('div', props, props.children); + return createElement('div', props, props.children); } export const initialWindowMetrics = { @@ -34,9 +34,9 @@ export const initialWindowMetrics = { }, }; -export const SafeAreaContext = React.createContext(initialWindowMetrics); +export const SafeAreaContext = createContext(initialWindowMetrics); -export const SafeAreaInsetsContext = React.createContext({ +export const SafeAreaInsetsContext = createContext({ top: 0, bottom: 0, left: 0, diff --git a/app/src/navigation/index.tsx b/app/src/navigation/index.tsx index a66e8a665..cfe0b3bde 100644 --- a/app/src/navigation/index.tsx +++ b/app/src/navigation/index.tsx @@ -1,14 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import 'react-native-gesture-handler'; - import { createNavigationContainerRef, createStaticNavigation, StaticParamList, } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import React from 'react'; +import React, { useEffect } from 'react'; import { Platform } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -75,7 +73,7 @@ const NavigationWithTracking = () => { }; // Setup universal link handling at the navigation level - React.useEffect(() => { + useEffect(() => { const cleanup = setupUniversalLinkListenerInNavigation(); return () => { diff --git a/app/src/providers/authProvider.tsx b/app/src/providers/authProvider.tsx index 052bfd498..f71d260f5 100644 --- a/app/src/providers/authProvider.tsx +++ b/app/src/providers/authProvider.tsx @@ -220,7 +220,7 @@ export const AuthProvider = ({ trackEvent(AuthEvents.AUTHENTICATION_TIMEOUT); }, authenticationTimeoutinMs); }); - }, [isAuthenticatingPromise]); + }, [authenticationTimeoutinMs, isAuthenticatingPromise]); const getOrCreateMnemonic = useCallback( () => _getSecurely(loadOrCreateMnemonic, str => JSON.parse(str)), @@ -246,7 +246,13 @@ export const AuthProvider = ({ checkBiometricsAvailable, _getSecurely, }), - [isAuthenticated, isAuthenticatingPromise, loginWithBiometrics], + [ + getOrCreateMnemonic, + isAuthenticated, + isAuthenticatingPromise, + loginWithBiometrics, + restoreAccountFromMnemonic, + ], ); return {children}; diff --git a/app/src/providers/passportDataProvider.tsx b/app/src/providers/passportDataProvider.tsx index 1f4e6075a..d92b21377 100644 --- a/app/src/providers/passportDataProvider.tsx +++ b/app/src/providers/passportDataProvider.tsx @@ -39,13 +39,13 @@ */ import { + brutforceSignatureAlgorithmDsc, DocumentCategory, + parseCertificateSimple, + PassportData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, } from '@selfxyz/common'; -import { parseCertificateSimple } from '@selfxyz/common'; -import { brutforceSignatureAlgorithmDsc } from '@selfxyz/common'; -import { PassportData } from '@selfxyz/common'; import { sha256 } from 'js-sha256'; import React, { createContext, @@ -56,8 +56,7 @@ import React, { } from 'react'; import Keychain from 'react-native-keychain'; -import { unsafe_getPrivateKey } from '../providers/authProvider'; -import { useAuth } from './authProvider'; +import { unsafe_getPrivateKey, useAuth } from '../providers/authProvider'; interface DocumentMetadata { id: string; // contentHash as ID for deduplication documentType: string; // passport, mock_passport, id_card, etc. diff --git a/app/src/screens/dev/DevFeatureFlagsScreen.tsx b/app/src/screens/dev/DevFeatureFlagsScreen.tsx index 9c702e747..f69c47c45 100644 --- a/app/src/screens/dev/DevFeatureFlagsScreen.tsx +++ b/app/src/screens/dev/DevFeatureFlagsScreen.tsx @@ -194,6 +194,7 @@ const DevFeatureFlagsScreen: React.FC = () => { }); }; // only clean up on unmount + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const hasLocalOverrides = featureFlags.some( @@ -221,16 +222,16 @@ const DevFeatureFlagsScreen: React.FC = () => { handleToggleFlag(flag.key, flag.value as boolean) } disabled={isTogglingFlag === flag.key} - bg={flag.value ? '$green7Light' : '$gray4'} + backgroundColor={flag.value ? '$green7Light' : '$gray4'} style={{ minWidth: 48, minHeight: 36, alignSelf: 'flex-end' }} > - + ); case 'string': case 'number': return ( - + { @@ -248,14 +249,14 @@ const DevFeatureFlagsScreen: React.FC = () => { borderRadius={12} borderWidth={1} borderColor={inputErrors[flag.key] ? '$red6' : '$gray6'} - bg="$gray2" - px="$3" - py="$2" + backgroundColor="$gray2" + paddingHorizontal="$3" + paddingVertical="$2" fontSize="$4" style={{ minHeight: 36 }} /> {inputErrors[flag.key] && ( - + {inputErrors[flag.key]} )} @@ -267,8 +268,13 @@ const DevFeatureFlagsScreen: React.FC = () => { }; return ( - - + + - + Birth Date (YYYY/MM/DD) = ({}) => { borderColor={borderColor} borderWidth={1} borderRadius="$4" - p="$2" + padding="$2" disabled={isInOfacList} opacity={isInOfacList ? 0.7 : 1} /> - + Passport expires in - + - + {expiryYears} years