diff --git a/.eslintrc.js b/.eslintrc.js index 1444f4aad..82fb0db88 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,9 +21,21 @@ module.exports = { 'sonarjs', 'jest' ], + parser: '@typescript-eslint/parser', parserOptions: { project: './tsconfig.json', }, + ignorePatterns: [ + "node_modules", + "*.config.js", + "docs", + "cli", + "android", + "ios", + "lint-staged.config.js", + "i18next-syntax-validation.js", + ".eslintrc.js" + ], rules: { 'import/no-duplicates': 'error', '@typescript-eslint/no-explicit-any': 'error', @@ -42,7 +54,7 @@ module.exports = { 'react/require-default-props': 'off', // Allow non-defined react props as undefined '@typescript-eslint/comma-dangle': 'off', // Avoid conflict rule between Eslint and Prettier '@typescript-eslint/consistent-type-imports': [ - 'warn', + 'error', { prefer: 'type-imports', fixStyle: 'inline-type-imports', @@ -81,7 +93,14 @@ module.exports = { 'object-shorthand': 'error', 'arrow-body-style': ["error", "as-needed"], 'no-console': ['error', {allow: ['error']}], - 'guard-for-in': 'error' + 'guard-for-in': 'error', + '@typescript-eslint/no-magic-numbers': ["error", + { ignoreArrayIndexes: true, + ignoreEnums: true, + ignore: [-1, 0, 1] + } + ], + '@typescript-eslint/prefer-nullish-coalescing': "error" }, overrides: [ // Configuration for translations files (i18next) diff --git a/cli/utils.js b/cli/utils.js index aa2a37674..b5df62f65 100755 --- a/cli/utils.js +++ b/cli/utils.js @@ -12,7 +12,7 @@ const execShellCommand = (cmd) => { console.warn(error); reject(error); } - resolve(stdout ? stdout : stderr); + resolve(stdout || stderr); }); }); }; diff --git a/src/app/(app)/_layout.tsx b/src/app/(app)/_layout.tsx index 32a8d0cd5..84e666aed 100644 --- a/src/app/(app)/_layout.tsx +++ b/src/app/(app)/_layout.tsx @@ -17,10 +17,11 @@ export default function TabLayout() { await SplashScreen.hideAsync(); }, []); useEffect(() => { + const TIMEOUT = 1000 if (status !== 'idle') { setTimeout(() => { hideSplash(); - }, 1000); + }, TIMEOUT); } }, [hideSplash, status]); diff --git a/src/app/feed/add-post.tsx b/src/app/feed/add-post.tsx index aa2021d7b..6d8b2d814 100644 --- a/src/app/feed/add-post.tsx +++ b/src/app/feed/add-post.tsx @@ -7,9 +7,11 @@ import { z } from 'zod'; import { useAddPost } from '@/api'; import { Button, ControlledInput, showErrorMessage, View } from '@/ui'; +const TITLE_MIN_CHARS = 10 +const BODY_MIN_CHARS = 120 const schema = z.object({ - title: z.string().min(10), - body: z.string().min(120), + title: z.string().min(TITLE_MIN_CHARS), + body: z.string().min(BODY_MIN_CHARS), }); type FormType = z.infer; diff --git a/src/components/login-form.tsx b/src/components/login-form.tsx index 9a636d894..22d151d77 100644 --- a/src/components/login-form.tsx +++ b/src/components/login-form.tsx @@ -6,6 +6,7 @@ import z from 'zod'; import { Button, ControlledInput, Text, View } from '@/ui'; +const MIN_CHARS = 6 const schema = z.object({ name: z.string().optional(), email: z @@ -17,7 +18,7 @@ const schema = z.object({ .string({ required_error: 'Password is required', }) - .min(6, 'Password must be at least 6 characters'), + .min(MIN_CHARS, 'Password must be at least 6 characters'), }); export type FormType = z.infer; diff --git a/src/core/i18n/index.tsx b/src/core/i18n/index.tsx index f43b0a3a7..91eaf2adc 100644 --- a/src/core/i18n/index.tsx +++ b/src/core/i18n/index.tsx @@ -9,7 +9,7 @@ export * from './utils'; i18n.use(initReactI18next).init({ resources, - lng: getLanguage() || locale, // TODO: if you are not supporting multiple languages or languages with multiple directions you can set the default value to `en` + lng: getLanguage() ?? locale, // TODO: if you are not supporting multiple languages or languages with multiple directions you can set the default value to `en` fallbackLng: 'en', compatibilityJSON: 'v3', // By default React Native projects does not support Intl diff --git a/src/ui/modal.tsx b/src/ui/modal.tsx index a704abc28..b2cec49c8 100644 --- a/src/ui/modal.tsx +++ b/src/ui/modal.tsx @@ -106,7 +106,7 @@ export const Modal = forwardRef( ref={modal.ref} index={0} snapPoints={snapPoints} - backdropComponent={props.backdropComponent || renderBackdrop} + backdropComponent={props.backdropComponent ?? renderBackdrop} handleComponent={renderHandleComponent} /> ); @@ -121,11 +121,13 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); const CustomBackdrop = ({ style }: BottomSheetBackdropProps) => { const { close } = useBottomSheet(); + const FADE_IN_DURATION = 50 + const FADE_OUT_DURATION = 20 return ( close()} - entering={FadeIn.duration(50)} - exiting={FadeOut.duration(20)} + entering={FadeIn.duration(FADE_IN_DURATION)} + exiting={FadeOut.duration(FADE_OUT_DURATION)} style={[style, { backgroundColor: 'rgba(0, 0, 0, 0.4)' }]} /> ); diff --git a/src/ui/select.tsx b/src/ui/select.tsx index 123443acd..238d9ff9c 100644 --- a/src/ui/select.tsx +++ b/src/ui/select.tsx @@ -8,8 +8,7 @@ import { useColorScheme } from 'nativewind'; import { forwardRef, memo, useCallback, useMemo } from 'react'; import type { FieldValues } from 'react-hook-form'; import { useController } from 'react-hook-form'; -import { Platform, TouchableOpacity, View } from 'react-native'; -import { Pressable, type PressableProps } from 'react-native'; +import { Platform, Pressable, type PressableProps,TouchableOpacity, View } from 'react-native'; import type { SvgProps } from 'react-native-svg'; import Svg, { Path } from 'react-native-svg'; import { tv } from 'tailwind-variants'; @@ -72,7 +71,9 @@ function keyExtractor(item: OptionType) { export const Options = forwardRef( ({ options, onSelect, value, testID }, ref) => { - const height = options.length * 70 + 100; + const HEIGHT_MARGIN = 100 + const OPTION_HEIGHT = 70 + const height = options.length * OPTION_HEIGHT + HEIGHT_MARGIN; const snapPoints = useMemo(() => [height], [height]); const { colorScheme } = useColorScheme(); const isDark = colorScheme === 'dark'; diff --git a/src/ui/text.tsx b/src/ui/text.tsx index 076bccbe2..7cc6acf2c 100644 --- a/src/ui/text.tsx +++ b/src/ui/text.tsx @@ -27,14 +27,14 @@ export const Text = ({ [className] ); - const nStyle = useMemo( + const nStyle: TextStyle = useMemo( () => StyleSheet.flatten([ { writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', }, style, - ]) as TextStyle, + ]), [style] ); return ( diff --git a/src/ui/utils.tsx b/src/ui/utils.tsx index 452e15487..0ed00aa5a 100644 --- a/src/ui/utils.tsx +++ b/src/ui/utils.tsx @@ -13,8 +13,8 @@ export const showError = (error: AxiosError) => { const description = extractError(error?.response?.data).trimEnd(); showMessage({ - message: 'Error', description, + message: 'Error', type: 'danger', duration: 4000, icon: 'danger',