diff --git a/.eslintrc.json b/.eslintrc.json index b257559e..85736559 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,8 +5,8 @@ }, "extends": [ "eslint:recommended", - "plugin:@typescript-eslint/recommended-type-checked", - "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:@typescript-eslint/strict", + "plugin:@typescript-eslint/stylistic", "plugin:react/recommended", "prettier" ], diff --git a/src/api/anonymous-api.ts b/src/api/anonymous-api.ts index 94b601b6..18ef6626 100644 --- a/src/api/anonymous-api.ts +++ b/src/api/anonymous-api.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Platform } from 'react-native' import packageInfo from '../../package.json' @@ -14,7 +21,7 @@ export class APIError extends Error { public data: object constructor(status: number, data: object) { - super(`${JSON.stringify(data)} (${status})`) + super(`${JSON.stringify(data)} (${status.toString()})`) this.status = status this.data = data } @@ -50,7 +57,7 @@ export class AnonymousAPIClient { try { return await resp.json() - } catch (e) { + } catch { throw new Error(`Response is not valid JSON (${await resp.text()})`) } } diff --git a/src/api/authenticated-api.ts b/src/api/authenticated-api.ts index 5b8c67b8..8dad99b7 100644 --- a/src/api/authenticated-api.ts +++ b/src/api/authenticated-api.ts @@ -1,3 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ + +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-unsafe-argument */ import { type Exams, @@ -38,6 +48,7 @@ export class AuthenticatedAPIClient extends AnonymousAPIClient { * @param {object} params Request data * @returns {object} */ + // eslint-disable-next-line @typescript-eslint/require-await async requestAuthenticated(params: object): Promise { console.debug(params) return this.sessionHandler(async (session: any) => { diff --git a/src/api/thi-session-handler.ts b/src/api/thi-session-handler.ts index 1b9b3312..942aed72 100644 --- a/src/api/thi-session-handler.ts +++ b/src/api/thi-session-handler.ts @@ -118,7 +118,7 @@ export async function callWithSession( await saveSecure('session', session) storage.set('sessionCreated', Date.now().toString()) storage.set('isStudent', isStudent.toString()) - } catch (e) { + } catch { throw new NoSessionError() } } @@ -145,7 +145,7 @@ export async function callWithSession( await saveSecure('session', session) storage.set('sessionCreated', Date.now().toString()) storage.set('isStudent', isStudent.toString()) - } catch (e) { + } catch { throw new NoSessionError() } return await method(session) diff --git a/src/app/(flow)/onboarding.tsx b/src/app/(flow)/onboarding.tsx index 0cd35be0..98a307ce 100644 --- a/src/app/(flow)/onboarding.tsx +++ b/src/app/(flow)/onboarding.tsx @@ -34,7 +34,7 @@ import Animated, { import { useSafeAreaInsets } from 'react-native-safe-area-context' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function OnboardingScreen(): JSX.Element { +export default function OnboardingScreen(): React.JSX.Element { const { t, i18n } = useTranslation('flow') const setOnboarded = useFlowStore((state) => state.setOnboarded) const toggleUpdated = useFlowStore((state) => state.toggleUpdated) @@ -73,7 +73,7 @@ export default function OnboardingScreen(): JSX.Element { }, ] - const ContinueButton = (): JSX.Element => { + const ContinueButton = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( { + const CardsElement = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( @@ -183,7 +183,7 @@ export default function OnboardingScreen(): JSX.Element { ) } - const LegalArea = (): JSX.Element => { + const LegalArea = (): React.JSX.Element => { const legalAnimatedStyle = useAnimatedStyle(() => ({ opacity: legalOpacity.value, transform: [{ translateY: legalTranslateY.value }], diff --git a/src/app/(flow)/whatsnew.tsx b/src/app/(flow)/whatsnew.tsx index 45d8fab9..cde9358a 100644 --- a/src/app/(flow)/whatsnew.tsx +++ b/src/app/(flow)/whatsnew.tsx @@ -22,7 +22,7 @@ import Animated, { } from 'react-native-reanimated' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function WhatsNewScreen(): JSX.Element { +export default function WhatsNewScreen(): React.JSX.Element { const { styles } = useStyles(stylesheet) const changelog: Changelog = changelogData as Changelog const { t, i18n } = useTranslation('flow') diff --git a/src/app/(screens)/about.tsx b/src/app/(screens)/about.tsx index c448da37..917623ee 100644 --- a/src/app/(screens)/about.tsx +++ b/src/app/(screens)/about.tsx @@ -27,7 +27,7 @@ import { } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function About(): JSX.Element { +export default function About(): React.JSX.Element { const router = useRouter() const { styles } = useStyles(stylesheet) const { t } = useTranslation(['settings']) @@ -97,17 +97,17 @@ export default function About(): JSX.Element { web: 'Mail', }, onPress: async () => - await Linking.openURL( + (await Linking.openURL( 'mailto:app-feedback@informatik.sexy?subject=Feedback%20Neuland-Next' - ), + )) as Promise, }, { title: 'App Website', icon: linkIcon, onPress: async () => - await Linking.openURL( + (await Linking.openURL( `https://next.neuland.app/${i18n.language === 'en' ? 'en/' : ''}` - ), + )) as Promise, }, { title: @@ -173,7 +173,7 @@ export default function About(): JSX.Element { preset: 'done', }) } - const isCollected = unlockedAppIcons?.includes('cat') + const isCollected = unlockedAppIcons.includes('cat') if (!isCollected) { trackEvent('EasterEgg', { easterEgg: 'aboutLogo' }) if (Platform.OS === 'ios') addUnlockedAppIcon('cat') @@ -184,9 +184,7 @@ export default function About(): JSX.Element { } const [pressCount, setPressCount] = useState(0) const handleWebsitePress = (): void => { - Linking.openURL('https://neuland-ingolstadt.de/').catch((err) => { - console.error('Failed to open URL:', err) - }) + void Linking.openURL('https://neuland-ingolstadt.de/') } const handleContributorsPress = (): void => { @@ -194,9 +192,7 @@ export default function About(): JSX.Element { 'https://next.neuland.app/' + (i18n.language === 'en' ? 'en/' : '') + 'about/contributors' - Linking.openURL(url).catch((err) => { - console.error('Failed to open URL:', err) - }) + void Linking.openURL(url) } return ( <> @@ -215,6 +211,7 @@ export default function About(): JSX.Element { > Neuland Next Logo state.accentColor) @@ -31,7 +31,7 @@ export default function Theme(): JSX.Element { }: { color: ColorBoxColor code: string - }): JSX.Element => { + }): React.JSX.Element => { const { styles } = useStyles(stylesheet) const themeAccentColor = UnistylesRuntime.themeName === 'dark' ? color.dark : color.light @@ -99,7 +99,9 @@ export default function Theme(): JSX.Element { }[] } - const ColorBoxMatrix = ({ colors }: ColorBoxMatrixProps): JSX.Element => { + const ColorBoxMatrix = ({ + colors, + }: ColorBoxMatrixProps): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/app/(screens)/appIcon.tsx b/src/app/(screens)/appIcon.tsx index 53a27889..a9808e40 100644 --- a/src/app/(screens)/appIcon.tsx +++ b/src/app/(screens)/appIcon.tsx @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +/* eslint-disable @typescript-eslint/no-require-imports */ import ErrorView from '@/components/Error/ErrorView' import Divider from '@/components/Universal/Divider' import PlatformIcon from '@/components/Universal/Icon' @@ -35,7 +38,7 @@ iconImages = { export const appIcons = Object.keys(iconImages) -export default function AppIconPicker(): JSX.Element { +export default function AppIconPicker(): React.JSX.Element { const { styles } = useStyles(stylesheet) const unlockedAppIcons = usePreferencesStore( (state) => state.unlockedAppIcons @@ -48,7 +51,7 @@ export default function AppIconPicker(): JSX.Element { } categories.exclusive = categories.exclusive.filter((icon) => { - if (unlockedAppIcons?.includes(icon)) { + if (unlockedAppIcons.includes(icon)) { return true } else { return false diff --git a/src/app/(screens)/calendar.tsx b/src/app/(screens)/calendar.tsx index abc8286f..2fa63a06 100644 --- a/src/app/(screens)/calendar.tsx +++ b/src/app/(screens)/calendar.tsx @@ -26,7 +26,7 @@ import { } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function CalendarPage(): JSX.Element { +export default function CalendarPage(): React.JSX.Element { const { userKind = USER_GUEST } = React.useContext(UserKindContext) const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') @@ -73,7 +73,7 @@ export default function CalendarPage(): JSX.Element { const scrollY = new Animated.Value(0) const pages = ['events', 'exams'] - const CalendarFooter = (): JSX.Element => { + const CalendarFooter = (): React.JSX.Element => { return ( diff --git a/src/app/(screens)/changelog.tsx b/src/app/(screens)/changelog.tsx index 97a57702..09999376 100644 --- a/src/app/(screens)/changelog.tsx +++ b/src/app/(screens)/changelog.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' import { Linking, ScrollView, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function Theme(): JSX.Element { +export default function Theme(): React.JSX.Element { const { styles } = useStyles(stylesheet) const changelog = changelogData as Changelog const { t, i18n } = useTranslation(['settings']) diff --git a/src/app/(screens)/clEvent.tsx b/src/app/(screens)/clEvent.tsx index 2da95571..ae70c155 100644 --- a/src/app/(screens)/clEvent.tsx +++ b/src/app/(screens)/clEvent.tsx @@ -3,7 +3,7 @@ import { linkIcon } from '@/components/Universal/Icon' import ShareHeaderButton from '@/components/Universal/ShareHeaderButton' import useCLParamsStore from '@/hooks/useCLParamsStore' import { type LanguageKey } from '@/localization/i18n' -import { type FormListSections } from '@/types/components' +import { type FormListSections, SectionGroup } from '@/types/components' import { formatFriendlyDateTime, formatFriendlyDateTimeRange, @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next' import { Linking, ScrollView, Share, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function ClEventDetail(): JSX.Element { +export default function ClEventDetail(): React.JSX.Element { const { styles } = useStyles(stylesheet) const navigation = useNavigation() const clEvent = useCLParamsStore((state) => state.selectedClEvent) @@ -23,7 +23,7 @@ export default function ClEventDetail(): JSX.Element { const { t, i18n } = useTranslation('common') const isMultiDayEvent = clEvent?.startDateTime != null && - clEvent?.endDateTime != null && + clEvent.endDateTime != null && new Date(clEvent.startDateTime).toDateString() !== new Date(clEvent.endDateTime).toDateString() @@ -71,32 +71,35 @@ export default function ClEventDetail(): JSX.Element { }, ] : [ - ...(clEvent?.startDateTime != null + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + ...(clEvent.startDateTime != null ? [ { title: t('pages.event.begin'), - value: formatFriendlyDateTime( - new Date(clEvent.startDateTime) - ), + value: + formatFriendlyDateTime( + new Date(clEvent.startDateTime) + ) ?? undefined, }, ] : []), - ...(clEvent?.endDateTime != null + ...(clEvent.endDateTime != null ? [ { title: t('pages.event.end'), - value: formatFriendlyDateTime( - new Date(clEvent.endDateTime) - ), + value: + formatFriendlyDateTime( + new Date(clEvent.endDateTime) + ) ?? undefined, }, ] : []), ]), - ...(clEvent?.location != null && clEvent?.location !== '' + ...(clEvent?.location != null && clEvent.location !== '' ? [ { title: t('pages.event.location'), - value: clEvent?.location, + value: clEvent.location, }, ] : []), @@ -139,7 +142,7 @@ export default function ClEventDetail(): JSX.Element { }, } : null, - ].filter((item) => item != null) as any, + ].filter((item) => item != null) as SectionGroup[], }, ] : []), @@ -147,7 +150,7 @@ export default function ClEventDetail(): JSX.Element { ? [ { header: t('pages.event.description'), - item: clEvent?.descriptions[i18n.language as LanguageKey], + item: clEvent.descriptions[i18n.language as LanguageKey], }, ] : []), @@ -168,7 +171,7 @@ export default function ClEventDetail(): JSX.Element { adjustsFontSizeToFit={true} numberOfLines={2} > - {clEvent?.titles[i18n.language as LanguageKey]} + {clEvent.titles[i18n.language as LanguageKey]} diff --git a/src/app/(screens)/clEvents.tsx b/src/app/(screens)/clEvents.tsx index 57038dba..26d317bc 100644 --- a/src/app/(screens)/clEvents.tsx +++ b/src/app/(screens)/clEvents.tsx @@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next' import { Animated, View, useWindowDimensions } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function Events(): JSX.Element { +export default function Events(): React.JSX.Element { const { t } = useTranslation('common') const { styles } = useStyles(stylesheet) const results = useQueries({ diff --git a/src/app/(screens)/dashboard.tsx b/src/app/(screens)/dashboard.tsx index ba435784..7e1685f0 100644 --- a/src/app/(screens)/dashboard.tsx +++ b/src/app/(screens)/dashboard.tsx @@ -26,7 +26,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' const { width } = Dimensions.get('window') -export default function DashboardEdit(): JSX.Element { +export default function DashboardEdit(): React.JSX.Element { const childrenHeight = 48 const { @@ -47,7 +47,7 @@ export default function DashboardEdit(): JSX.Element { useState([]) // add translation to shownDashboardEntries with new key transText - const transShownDashboardEntries = shownDashboardEntries?.map((item) => { + const transShownDashboardEntries = shownDashboardEntries.map((item) => { return { ...item, // @ts-expect-error cannot verify the type @@ -80,13 +80,13 @@ export default function DashboardEdit(): JSX.Element { ) }, [hiddenDashboardEntries, userKind, unavailableCards]) - const renderItem = (params: ExtendedCard): JSX.Element => { + const renderItem = (params: ExtendedCard): React.JSX.Element => { const onPressDelete = (): void => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) hideDashboardEntry(params.key) } const isLast = - shownDashboardEntries?.[shownDashboardEntries.length - 1].key === + shownDashboardEntries[shownDashboardEntries.length - 1].key === params.key return ( @@ -96,7 +96,7 @@ export default function DashboardEdit(): JSX.Element { isLast={isLast} isDragged={ draggedId !== null && - draggedId === transShownDashboardEntries?.indexOf(params) + draggedId === transShownDashboardEntries.indexOf(params) } /> ) @@ -111,7 +111,7 @@ export default function DashboardEdit(): JSX.Element { ) const handleReset = useCallback(() => { - resetOrder(userKind ?? 'guest') + resetOrder(userKind) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) if (Platform.OS === 'ios') { void Haptics.notificationAsync( @@ -133,14 +133,14 @@ export default function DashboardEdit(): JSX.Element { defaultHidden, // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions hiddenDashboardEntries - ?.filter(Boolean) + .filter(Boolean) .map((item) => item.key) || [] ) && arraysEqual( defaultShown, // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions shownDashboardEntries - ?.filter(Boolean) + .filter(Boolean) .map((item) => item.key) || [] ) ) @@ -206,7 +206,7 @@ export default function DashboardEdit(): JSX.Element { {t('dashboard.shown')} - {shownDashboardEntries?.length === 0 ? ( + {shownDashboardEntries.length === 0 ? ( item.key} + keyExtractor={(item) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + item.key as string + } dataSource={ transShownDashboardEntries ?? [] } @@ -253,7 +256,7 @@ export default function DashboardEdit(): JSX.Element { }) }} onDragging={( - _gestureState: any, + _gestureState: unknown, _left: number, _top: number, moveToIndex: number @@ -325,20 +328,20 @@ export default function DashboardEdit(): JSX.Element { ios={{ name: cardIcons[ item.key as keyof typeof cardIcons - ]?.ios, + ].ios, size: 17, }} android={{ name: cardIcons[ item.key as keyof typeof cardIcons - ]?.android, + ].android, size: 21, variant: 'outlined', }} web={{ name: cardIcons[ item.key as keyof typeof cardIcons - ]?.web, + ].web, size: 21, }} /> @@ -430,7 +433,7 @@ function RowItem({ onPressDelete, isLast, isDragged, -}: RowItemProps): JSX.Element { +}: RowItemProps): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const bottomWidth = isLast || isDragged ? 0 : 1 @@ -450,23 +453,21 @@ function RowItem({ ios={{ name: isDragged ? 'line.3.horizontal' - : cardIcons[item.key as keyof typeof cardIcons] - ?.ios, + : cardIcons[item.key as keyof typeof cardIcons].ios, size: 17, }} android={{ name: isDragged ? 'drag_handle' : cardIcons[item.key as keyof typeof cardIcons] - ?.android, + .android, size: 21, variant: 'outlined', }} web={{ name: isDragged ? 'GripHorizontal' - : cardIcons[item.key as keyof typeof cardIcons] - ?.web, + : cardIcons[item.key as keyof typeof cardIcons].web, size: 21, }} /> diff --git a/src/app/(screens)/exam.tsx b/src/app/(screens)/exam.tsx index cff19dc9..6757a424 100644 --- a/src/app/(screens)/exam.tsx +++ b/src/app/(screens)/exam.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { ScrollView, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function ExamDetail(): JSX.Element { +export default function ExamDetail(): React.JSX.Element { const { styles } = useStyles(stylesheet) const exam = useRouteParamsStore((state) => state.selectedExam) const { t } = useTranslation('common') diff --git a/src/app/(screens)/foodAllergens.tsx b/src/app/(screens)/foodAllergens.tsx index 86b83fa1..b5b60d91 100644 --- a/src/app/(screens)/foodAllergens.tsx +++ b/src/app/(screens)/foodAllergens.tsx @@ -2,7 +2,7 @@ import ItemsPickerScreen from '@/components/Food/ItemsPickerScreen' import WorkaroundStack from '@/components/Universal/WorkaroundStack' import React from 'react' -export default function Screen(): JSX.Element { +export default function Screen(): React.JSX.Element { return ( () @@ -56,7 +56,7 @@ export default function GradesSCreen(): JSX.Element { } else { throw new Error('Average grade is undefined or null') } - } catch (e: any) { + } catch { setAverageLoadingState(LoadingState.ERROR) } } @@ -219,7 +219,7 @@ export default function GradesSCreen(): JSX.Element { - {grades?.finished?.map((grade, index) => ( + {grades.finished.map((grade, index) => ( {index !== @@ -235,7 +235,7 @@ export default function GradesSCreen(): JSX.Element { {grades.missing.length !== 0 && ( - {grades?.missing?.map((grade, index) => ( + {grades.missing.map((grade, index) => ( {index !== diff --git a/src/app/(screens)/lecture.tsx b/src/app/(screens)/lecture.tsx index 7bb7e463..a405804b 100644 --- a/src/app/(screens)/lecture.tsx +++ b/src/app/(screens)/lecture.tsx @@ -20,7 +20,7 @@ import { Pressable, ScrollView, Share, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' import ViewShot, { captureRef } from 'react-native-view-shot' -export default function TimetableDetails(): JSX.Element { +export default function TimetableDetails(): React.JSX.Element { const router = useRouter() const navigation = useNavigation() const { styles } = useStyles(stylesheet) diff --git a/src/app/(screens)/lecturer.tsx b/src/app/(screens)/lecturer.tsx index 84a3cce8..44ac923a 100644 --- a/src/app/(screens)/lecturer.tsx +++ b/src/app/(screens)/lecturer.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { Linking, ScrollView, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function LecturerDetail(): JSX.Element { +export default function LecturerDetail(): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const lecturer = useRouteParamsStore((state) => state.selectedLecturer) const { t } = useTranslation('common') diff --git a/src/app/(screens)/lecturers.tsx b/src/app/(screens)/lecturers.tsx index 36df014d..d95321af 100644 --- a/src/app/(screens)/lecturers.tsx +++ b/src/app/(screens)/lecturers.tsx @@ -46,7 +46,7 @@ import { useStyles, } from 'react-native-unistyles' -export default function LecturersCard(): JSX.Element { +export default function LecturersCard(): React.JSX.Element { const router = useRouter() const [filteredLecturers, setFilteredLecturers] = useState< NormalizedLecturer[] @@ -273,7 +273,7 @@ export default function LecturersCard(): JSX.Element { error: Error | null isLoading: boolean isPersonal?: boolean - }): JSX.Element => { + }): React.JSX.Element => { return isPaused && !isSuccess ? ( { + const FilterSectionList = (): React.JSX.Element => { return allLecturersResult.isLoading ? ( diff --git a/src/app/(screens)/legal.tsx b/src/app/(screens)/legal.tsx index bad85519..c0bdd4d7 100644 --- a/src/app/(screens)/legal.tsx +++ b/src/app/(screens)/legal.tsx @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next' import { Linking, ScrollView, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function About(): JSX.Element { +export default function About(): React.JSX.Element { const router = useRouter() const { styles } = useStyles(stylesheet) const { t, i18n } = useTranslation(['settings']) @@ -20,12 +20,14 @@ export default function About(): JSX.Element { { title: t('legal.formlist.legal.privacy'), icon: linkIcon, - onPress: async () => await Linking.openURL(PRIVACY_URL), + onPress: async () => + (await Linking.openURL(PRIVACY_URL)) as Promise, }, { title: t('legal.formlist.legal.imprint'), icon: linkIcon, - onPress: async () => await Linking.openURL(IMPRINT_URL), + onPress: async () => + (await Linking.openURL(IMPRINT_URL)) as Promise, }, { title: t('navigation.licenses.title', { ns: 'navigation' }), @@ -43,7 +45,9 @@ export default function About(): JSX.Element { title: 'Neuland Ingolstadt e.V.', icon: linkIcon, onPress: async () => - await Linking.openURL('https://neuland-ingolstadt.de/'), + (await Linking.openURL( + 'https://neuland-ingolstadt.de/' + )) as Promise, }, { title: t('legal.formlist.us.source'), @@ -54,9 +58,9 @@ export default function About(): JSX.Element { }, onPress: async () => - await Linking.openURL( + (await Linking.openURL( 'https://github.com/neuland-ingolstadt/neuland.app-native' - ), + )) as Promise, }, { title: t('legal.formlist.us.faq'), @@ -67,9 +71,9 @@ export default function About(): JSX.Element { }, onPress: async () => - await Linking.openURL( + (await Linking.openURL( `https://next.neuland.app/${i18n.language === 'en' ? 'en/' : ''}app/faq` - ), + )) as Promise, }, ], }, diff --git a/src/app/(screens)/libraryCode.tsx b/src/app/(screens)/libraryCode.tsx index 3a5ff51d..fadec932 100644 --- a/src/app/(screens)/libraryCode.tsx +++ b/src/app/(screens)/libraryCode.tsx @@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next' import { Dimensions, Platform, Pressable, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function LibraryCode(): JSX.Element { +export default function LibraryCode(): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') const { userKind = USER_GUEST } = useContext(UserKindContext) @@ -63,6 +63,7 @@ export default function LibraryCode(): JSX.Element { if (Platform.OS === 'ios') { void (async () => { const { status } = await Brightness.requestPermissionsAsync() + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (status === 'granted') { const value = await Brightness.getSystemBrightnessAsync() setBrightness(value) @@ -107,7 +108,8 @@ export default function LibraryCode(): JSX.Element { ) : isError ? ( @@ -117,7 +119,7 @@ export default function LibraryCode(): JSX.Element { onRefresh={refetchByUser} refreshing={isRefetchingByUser} /> - ) : isSuccess && data?.bibnr !== null ? ( + ) : isSuccess && data.bibnr !== null ? ( @@ -133,7 +135,7 @@ export default function LibraryCode(): JSX.Element { > + ([, license]) => license.platform.includes(Platform.OS) || license.platform.includes('all') ) @@ -54,7 +54,7 @@ export default function Licenses(): JSX.Element { const licensesList = Object.entries(licensesCombined) .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) // also sort by search - .filter(([key, value]) => { + .filter(([key]) => { if (localSearch === '') { return true } diff --git a/src/app/(screens)/login.tsx b/src/app/(screens)/login.tsx index 1d63f9d6..ec7af0ba 100644 --- a/src/app/(screens)/login.tsx +++ b/src/app/(screens)/login.tsx @@ -1,6 +1,6 @@ import Login from '@/components/Flow/Login' import React from 'react' -export default function LoginScreen(): JSX.Element { +export default function LoginScreen(): React.JSX.Element { return } diff --git a/src/app/(screens)/meal.tsx b/src/app/(screens)/meal.tsx index 0407956a..7a067447 100644 --- a/src/app/(screens)/meal.tsx +++ b/src/app/(screens)/meal.tsx @@ -31,7 +31,7 @@ import { } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function FoodDetail(): JSX.Element { +export default function FoodDetail(): React.JSX.Element { const meal = useRouteParamsStore((state) => state.selectedMeal) const { styles, theme } = useStyles(stylesheet) diff --git a/src/app/(screens)/news.tsx b/src/app/(screens)/news.tsx index 45758257..3e37b020 100644 --- a/src/app/(screens)/news.tsx +++ b/src/app/(screens)/news.tsx @@ -21,7 +21,7 @@ import { } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function NewsScreen(): JSX.Element { +export default function NewsScreen(): React.JSX.Element { const { styles } = useStyles(stylesheet) const { data, error, isLoading, isError, isPaused, isSuccess, refetch } = useQuery({ diff --git a/src/app/(screens)/profile.tsx b/src/app/(screens)/profile.tsx index 44e8aee0..bf156775 100644 --- a/src/app/(screens)/profile.tsx +++ b/src/app/(screens)/profile.tsx @@ -31,7 +31,7 @@ import { } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function Profile(): JSX.Element { +export default function Profile(): React.JSX.Element { const router = useRouter() const { styles } = useStyles(stylesheet) const { toggleUserKind, userKind } = useContext(UserKindContext) @@ -185,7 +185,7 @@ export default function Profile(): JSX.Element { data?.pvers === 'k.A.' ? t('misc.unknown', { ns: 'common' }) : data?.pvers, - onPress: async () => { + onPress: () => { if ( data?.po_url !== undefined && data.po_url !== '' && @@ -203,7 +203,7 @@ export default function Profile(): JSX.Element { title: t('profile.formlist.user.status'), value: data?.rue === '1' - ? data?.rue_sem + ? data.rue_sem : t('profile.formlist.user.statusInactive'), }, ], @@ -287,7 +287,7 @@ export default function Profile(): JSX.Element { /> )} {isSuccess && - (data?.mtknr !== undefined ? ( + (data.mtknr !== undefined ? ( (null) useEffect(() => { - const fetchRooms = async (): Promise => { + const fetchRooms = (): void => { try { const validateDate = new Date(date) if (isNaN(validateDate.getTime())) { @@ -106,7 +106,7 @@ export default function AdvancedSearch(): JSX.Element { return } - const rooms = await filterRooms( + const rooms = filterRooms( data, date, time, @@ -127,7 +127,7 @@ export default function AdvancedSearch(): JSX.Element { setFilterState(LoadingState.LOADING) setTimeout(() => { - void fetchRooms() + fetchRooms() }) }, [date, time, building.value, duration.value, data]) @@ -230,6 +230,10 @@ export default function AdvancedSearch(): JSX.Element { name: 'update', size: 20, }} + web={{ + name: 'Sparkles', + size: 20, + }} /> {t('pages.rooms.modified.title')} diff --git a/src/app/(screens)/roomSearch.tsx b/src/app/(screens)/roomSearch.tsx index af34fc22..1194a70e 100644 --- a/src/app/(screens)/roomSearch.tsx +++ b/src/app/(screens)/roomSearch.tsx @@ -44,7 +44,7 @@ const DURATIONS = [ const ALL_BUILDINGS = [BUILDINGS_ALL, ...BUILDINGS] -export default function AdvancedSearch(): JSX.Element { +export default function AdvancedSearch(): React.React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const router = useRouter() const { t } = useTranslation('common') @@ -77,7 +77,7 @@ export default function AdvancedSearch(): JSX.Element { const [rooms, setRooms] = useState(null) useEffect(() => { - const fetchRooms = async (): Promise => { + const fetchRooms = (): void => { try { const validateDate = new Date(date) if (isNaN(validateDate.getTime())) { @@ -86,13 +86,7 @@ export default function AdvancedSearch(): JSX.Element { if (data === undefined) { return } - const rooms = await filterRooms( - data, - date, - time, - building, - duration - ) + const rooms = filterRooms(data, date, time, building, duration) if (rooms == null) { throw new Error('Error while filtering rooms') } else { @@ -107,7 +101,7 @@ export default function AdvancedSearch(): JSX.Element { setFilterState(LoadingState.LOADING) setTimeout(() => { - void fetchRooms() + fetchRooms() }) }, [date, time, building, duration, data]) diff --git a/src/app/(screens)/settings.tsx b/src/app/(screens)/settings.tsx index 0babe076..6bef7bb2 100644 --- a/src/app/(screens)/settings.tsx +++ b/src/app/(screens)/settings.tsx @@ -53,7 +53,7 @@ import Animated, { import { useSafeAreaInsets } from 'react-native-safe-area-context' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function Settings(): JSX.Element { +export default function Settings(): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const { userKind = USER_GUEST } = useContext(UserKindContext) diff --git a/src/app/(screens)/sportsEvent.tsx b/src/app/(screens)/sportsEvent.tsx index 0dfd25a8..3d9ecbdf 100644 --- a/src/app/(screens)/sportsEvent.tsx +++ b/src/app/(screens)/sportsEvent.tsx @@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next' import { Linking, ScrollView, Share, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function SportsEventDetail(): JSX.Element { +export default function SportsEventDetail(): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const sportsEvent = useCLParamsStore((state) => state.selectedSportsEvent) diff --git a/src/app/(screens)/theme.tsx b/src/app/(screens)/theme.tsx index 7b591d86..1ece8607 100644 --- a/src/app/(screens)/theme.tsx +++ b/src/app/(screens)/theme.tsx @@ -4,7 +4,7 @@ import { usePreferencesStore } from '@/hooks/usePreferencesStore' import React from 'react' import { useTranslation } from 'react-i18next' -export default function Theme(): JSX.Element { +export default function Theme(): React.JSX.Element { const theme = usePreferencesStore((state) => state.theme) const setTheme = usePreferencesStore((state) => state.setTheme) const { t } = useTranslation(['settings']) diff --git a/src/app/(screens)/webView.tsx b/src/app/(screens)/webView.tsx index b47fb897..63049949 100644 --- a/src/app/(screens)/webView.tsx +++ b/src/app/(screens)/webView.tsx @@ -8,7 +8,7 @@ import sanitizeHtml from 'sanitize-html' const PADDING = 4 -export default function NotesDetails(): JSX.Element { +export default function NotesDetails(): React.JSX.Element { const navigation = useNavigation() const { title, html } = useLocalSearchParams<{ title: string @@ -43,7 +43,7 @@ export default function NotesDetails(): JSX.Element { { + onLoadEnd={() => { if (!loaded) { setLoaded(true) } diff --git a/src/app/(screens)/webView.web.tsx b/src/app/(screens)/webView.web.tsx index 1e636038..5112a096 100644 --- a/src/app/(screens)/webView.web.tsx +++ b/src/app/(screens)/webView.web.tsx @@ -2,7 +2,7 @@ import ErrorView from '@/components/Error/ErrorView' import React from 'react' import { useTranslation } from 'react-i18next' -export default function NotesDetails(): JSX.Element { +export default function NotesDetails(): React.JSX.Element { const { t } = useTranslation('timetable') return ( diff --git a/src/app/(tabs)/(index)/_layout.tsx b/src/app/(tabs)/(index)/_layout.tsx index 18f1b607..570a307e 100644 --- a/src/app/(tabs)/(index)/_layout.tsx +++ b/src/app/(tabs)/(index)/_layout.tsx @@ -14,7 +14,7 @@ export const unstable_settings = { initialRouteName: '/', } -export default function Layout(): JSX.Element { +export default function Layout(): React.JSX.Element { const { styles } = useStyles(stylesheet) if (typeof window === 'undefined') return diff --git a/src/app/(tabs)/(index)/index.tsx b/src/app/(tabs)/(index)/index.tsx index ec7dd59c..7317c7a7 100644 --- a/src/app/(tabs)/(index)/index.tsx +++ b/src/app/(tabs)/(index)/index.tsx @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next' import { Dimensions, LayoutAnimation, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function HomeRootScreen(): JSX.Element { +export default function HomeRootScreen(): React.JSX.Element { const [isPageOpen, setIsPageOpen] = useState(false) useEffect(() => { setIsPageOpen(true) @@ -44,7 +44,7 @@ export default function HomeRootScreen(): JSX.Element { ) } -function HomeScreen(): JSX.Element { +function HomeScreen(): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const { shownDashboardEntries } = React.useContext(DashboardContext) const [orientation, setOrientation] = useState( diff --git a/src/app/(tabs)/(index)/library.tsx b/src/app/(tabs)/(index)/library.tsx index 7e2ce1d5..1766e653 100644 --- a/src/app/(tabs)/(index)/library.tsx +++ b/src/app/(tabs)/(index)/library.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { Linking, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function LibrarySreen(): JSX.Element { +export default function LibrarySreen(): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') diff --git a/src/app/(tabs)/(index)/links.tsx b/src/app/(tabs)/(index)/links.tsx index f98cc144..71d67bf3 100644 --- a/src/app/(tabs)/(index)/links.tsx +++ b/src/app/(tabs)/(index)/links.tsx @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next' import { Linking, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -const LinkScreen = (): JSX.Element => { +const LinkScreen = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') const addRecentQuicklink = usePreferencesStore( diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx index 19afbf72..3c549d4c 100644 --- a/src/app/(tabs)/_layout.tsx +++ b/src/app/(tabs)/_layout.tsx @@ -16,18 +16,27 @@ import React, { useContext, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Platform } from 'react-native' import { useMMKVBoolean, useMMKVString } from 'react-native-mmkv' -// @ts-expect-error no types +// @ts-expect-error no types available import Shortcuts, { type ShortcutItem } from 'rn-quick-actions' import { appIcons } from '../(screens)/appIcon' +interface ShortcutsType { + onShortcutPressed: (callback: (item: ShortcutItem) => void) => { + remove: () => void + } + setShortcuts: (shortcuts: ShortcutItem[]) => void +} + +const TypedShortcuts = Shortcuts as unknown as ShortcutsType + declare const process: { env: { EXPO_PUBLIC_APTABASE_KEY: string } } -export default function HomeLayout(): JSX.Element { +export default function HomeLayout(): React.JSX.Element { const router = useRouter() const { t } = useTranslation('navigation') @@ -79,7 +88,7 @@ export default function HomeLayout(): JSX.Element { if (allergens.length === 1 && allergens[0] === 'not-configured') { /* empty */ } else { - allergens?.forEach((allergen: string) => { + allergens.forEach((allergen: string) => { console.debug('Migrating allergen:', allergen) toggleSelectedAllergens(allergen) }) @@ -161,14 +170,15 @@ export default function HomeLayout(): JSX.Element { ] function processShortcut(item: ShortcutItem): void { router.navigate({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access pathname: item.data.path as RelativePathString, params: { fromAppShortcut: 'true' }, }) } const shortcutSubscription = - Shortcuts.onShortcutPressed(processShortcut) - Shortcuts.setShortcuts(shortcuts) + TypedShortcuts.onShortcutPressed(processShortcut) + TypedShortcuts.setShortcuts(shortcuts) return () => { if (shortcutSubscription != null) shortcutSubscription.remove() diff --git a/src/app/(tabs)/food.tsx b/src/app/(tabs)/food.tsx index 4fb6cf69..ab12fab0 100644 --- a/src/app/(tabs)/food.tsx +++ b/src/app/(tabs)/food.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useLayoutEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Platform } from 'react-native' -export default function FoodRootScreen(): JSX.Element { +export default function FoodRootScreen(): React.JSX.Element { const [isPageOpen, setIsPageOpen] = useState(false) const { t } = useTranslation('navigation') const navigation = useNavigation() diff --git a/src/app/(tabs)/map.tsx b/src/app/(tabs)/map.tsx index fb1884c3..d82c75ff 100644 --- a/src/app/(tabs)/map.tsx +++ b/src/app/(tabs)/map.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next' import { View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function MapRootScreen(): JSX.Element { +export default function MapRootScreen(): React.JSX.Element { const { t } = useTranslation(['navigation']) const { styles } = useStyles(stylesheet) const [isPageOpen, setIsPageOpen] = useState(false) diff --git a/src/app/(tabs)/timetable.tsx b/src/app/(tabs)/timetable.tsx index 5a1cae5d..b8530ace 100644 --- a/src/app/(tabs)/timetable.tsx +++ b/src/app/(tabs)/timetable.tsx @@ -5,7 +5,7 @@ import React from 'react' import { useTranslation } from 'react-i18next' import { Platform } from 'react-native' -export default function FoodRootScreen(): JSX.Element { +export default function FoodRootScreen(): React.JSX.Element { const { t } = useTranslation('navigation') if (Platform.OS === 'web') { diff --git a/src/app/[...unmachted].tsx b/src/app/[...unmachted].tsx index 6489abe8..63ab00c0 100644 --- a/src/app/[...unmachted].tsx +++ b/src/app/[...unmachted].tsx @@ -4,7 +4,7 @@ import { router, useNavigation, usePathname } from 'expo-router' import React, { useEffect, useLayoutEffect } from 'react' import { useTranslation } from 'react-i18next' -export default function Unmatched(): JSX.Element { +export default function Unmatched(): React.JSX.Element { const navigation = useNavigation() const cleanedPathname = usePathname().replace('/', '') const pathname = diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 094ceb48..a9571ce6 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -30,7 +30,7 @@ export const unstable_settings = { initialRouteName: '/', } -function RootLayout(): JSX.Element { +function RootLayout(): React.JSX.Element { const { t } = useTranslation(['navigation']) const isPad = DeviceInfo.isTablet() const savedLanguage = usePreferencesStore((state) => state.language) @@ -429,7 +429,7 @@ function RootLayout(): JSX.Element { ) } -const ProviderComponent = (): JSX.Element => { +const ProviderComponent = (): React.JSX.Element => { return ( diff --git a/src/app/index.tsx b/src/app/index.tsx index c0c9660f..3602e3b6 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -3,7 +3,7 @@ import Head from 'expo-router/head' import React from 'react' import { StyleSheet, View, useColorScheme } from 'react-native' -export default function App(): JSX.Element { +export default function App(): React.JSX.Element { // This initial page is only used to redirect to the actual app // This helps to avoid weird layout issues on Android where the tab bar is not displayed correctly const colorScheme = useColorScheme() diff --git a/src/components/Cards/BaseCard.tsx b/src/components/Cards/BaseCard.tsx index bb31ee2b..65788798 100644 --- a/src/components/Cards/BaseCard.tsx +++ b/src/components/Cards/BaseCard.tsx @@ -63,24 +63,29 @@ const BaseCard: React.FC = ({ title={t('cards.titles.' + title)} actions={actions} onPress={(e) => { - e.nativeEvent.name === t('contextMenu.settings') && + if (e.nativeEvent.name === t('contextMenu.settings')) { router.navigate('/dashboard') - e.nativeEvent.name === t('contextMenu.hide') && + } + if (e.nativeEvent.name === t('contextMenu.hide')) { hideDashboardEntry(title) - e.nativeEvent.name === t('contextMenu.reset') && + } + if (e.nativeEvent.name === t('contextMenu.reset')) { resetOrder(userKind ?? 'guest') + } }} onPreviewPress={() => { - onPressRoute != null && + if (onPressRoute != null) { router.navigate(onPressRoute as RelativePathString) + } }} disabled={Platform.OS === 'android'} > { - onPressRoute != null && + if (onPressRoute != null) { router.navigate(onPressRoute as RelativePathString) + } }} delayLongPress={300} onLongPress={() => { diff --git a/src/components/Cards/CalendarCard.tsx b/src/components/Cards/CalendarCard.tsx index 4ea53a9f..53c46670 100644 --- a/src/components/Cards/CalendarCard.tsx +++ b/src/components/Cards/CalendarCard.tsx @@ -16,7 +16,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import BaseCard from './BaseCard' -const CalendarCard = (): JSX.Element => { +const CalendarCard = (): React.JSX.Element => { type Combined = Calendar | CardExams const router = useRouter() const time = new Date() diff --git a/src/components/Cards/EventsCard.tsx b/src/components/Cards/EventsCard.tsx index 5e5c307a..076d3376 100644 --- a/src/components/Cards/EventsCard.tsx +++ b/src/components/Cards/EventsCard.tsx @@ -9,7 +9,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import BaseCard from './BaseCard' -const EventsCard = (): JSX.Element => { +const EventsCard = (): React.JSX.Element => { const { styles, theme } = useStyles(stylesheet) const { t, i18n } = useTranslation('navigation') diff --git a/src/components/Cards/FoodCard.tsx b/src/components/Cards/FoodCard.tsx index d1213392..c10ab49d 100644 --- a/src/components/Cards/FoodCard.tsx +++ b/src/components/Cards/FoodCard.tsx @@ -19,7 +19,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import BaseCard from './BaseCard' -const FoodCard = (): JSX.Element => { +const FoodCard = (): React.JSX.Element => { const { t, i18n } = useTranslation('food') const { styles, theme } = useStyles(stylesheet) diff --git a/src/components/Cards/LinkCard.tsx b/src/components/Cards/LinkCard.tsx index 6be39bad..6aefbc07 100644 --- a/src/components/Cards/LinkCard.tsx +++ b/src/components/Cards/LinkCard.tsx @@ -10,7 +10,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import PlatformIcon, { type LucideIcon } from '../Universal/Icon' import BaseCard from './BaseCard' -const LinkCard = (): JSX.Element => { +const LinkCard = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') diff --git a/src/components/Cards/LoginCard.tsx b/src/components/Cards/LoginCard.tsx index 980fbc1a..682972e9 100644 --- a/src/components/Cards/LoginCard.tsx +++ b/src/components/Cards/LoginCard.tsx @@ -5,7 +5,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import BaseCard from './BaseCard' -const LoginCard = (): JSX.Element => { +const LoginCard = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) const { t } = useTranslation('navigation') return ( diff --git a/src/components/Cards/NewsCard.tsx b/src/components/Cards/NewsCard.tsx index ff055d68..d0c1d429 100644 --- a/src/components/Cards/NewsCard.tsx +++ b/src/components/Cards/NewsCard.tsx @@ -54,7 +54,7 @@ const NewsCard: React.FC = () => { } }, [updateWidth]) - const renderEvent = (event: ThiNews): JSX.Element => { + const renderEvent = (event: ThiNews): React.JSX.Element => { return ( { event: FriendlyTimetableEntry, index: number, currentTime: Date - ): JSX.Element => { + ): React.JSX.Element => { const isSoon = event.startDate > currentTime && new Date(event.startDate) <= diff --git a/src/components/Dashboard/HeaderRight.tsx b/src/components/Dashboard/HeaderRight.tsx index 3c537f95..aec69bd9 100644 --- a/src/components/Dashboard/HeaderRight.tsx +++ b/src/components/Dashboard/HeaderRight.tsx @@ -20,7 +20,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import LoadingIndicator from '../Universal/LoadingIndicator' -export const IndexHeaderRight = (): JSX.Element => { +export const IndexHeaderRight = (): React.JSX.Element => { const { t } = useTranslation(['navigation', 'settings']) const router = useRouter() const { styles, theme } = useStyles(stylesheet) @@ -106,7 +106,7 @@ export const IndexHeaderRight = (): JSX.Element => { ) } - const IconComponent = (): JSX.Element => { + const IconComponent = (): React.JSX.Element => { return ( {userKind === USER_EMPLOYEE ? ( @@ -184,7 +184,7 @@ export const IndexHeaderRight = (): JSX.Element => { children, }: { children: JSX.Element - }): JSX.Element => { + }): React.JSX.Element => { return ( { +}): React.JSX.Element => { const { t } = useTranslation('common') const { styles } = useStyles(stylesheet) const platform = Platform.OS @@ -42,7 +42,7 @@ export const FeedbackButton = ({ ) } -export const StatusButton = (): JSX.Element => { +export const StatusButton = (): React.JSX.Element => { const { t } = useTranslation('common') const { styles } = useStyles(stylesheet) diff --git a/src/components/Error/CrashView.tsx b/src/components/Error/CrashView.tsx index 29280932..dd78faab 100644 --- a/src/components/Error/CrashView.tsx +++ b/src/components/Error/CrashView.tsx @@ -13,7 +13,7 @@ export const ErrorButton = ({ onPress, }: { onPress: () => void -}): JSX.Element => { +}): React.JSX.Element => { const { t } = useTranslation('common') const { styles } = useStyles(stylesheet) return ( @@ -30,7 +30,7 @@ export const ErrorButton = ({ export default function CrashView({ error, retry, -}: ErrorBoundaryProps): JSX.Element { +}: ErrorBoundaryProps): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const { t } = useTranslation('common') const path = usePathname() diff --git a/src/components/Error/ErrorView.tsx b/src/components/Error/ErrorView.tsx index 46386fd1..e61bdb7e 100644 --- a/src/components/Error/ErrorView.tsx +++ b/src/components/Error/ErrorView.tsx @@ -44,7 +44,7 @@ export default function ErrorView({ showPullLabel?: boolean inModal?: boolean isCritical?: boolean -}): JSX.Element { +}): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') const path = usePathname() @@ -122,7 +122,7 @@ export default function ErrorView({ } } - const ErrorButton = (): JSX.Element => { + const ErrorButton = (): React.JSX.Element => { const buttonAction = (): void => { switch (title) { case guestError: @@ -153,7 +153,7 @@ export default function ErrorView({ return (buttonProps != null || title === guestError) && title !== permissionError ? ( @@ -172,19 +172,15 @@ export default function ErrorView({ refreshControl={ refreshing != null && title !== guestError ? ( { - if (onRefresh) { - onRefresh().catch(console.error) - } - }} + refreshing={refreshing} + onRefresh={onRefresh} /> ) : ( <> ) } scrollEnabled={!inModal} - contentContainerStyle={styles.container(inModal ?? false)} + contentContainerStyle={styles.container(inModal)} > diff --git a/src/components/Events/ClEventsPage.tsx b/src/components/Events/ClEventsPage.tsx index 69b7dd93..764c0fd5 100644 --- a/src/components/Events/ClEventsPage.tsx +++ b/src/components/Events/ClEventsPage.tsx @@ -16,7 +16,7 @@ export default function ClEventsPage({ clEventsResult, }: { clEventsResult: UseQueryResult -}): JSX.Element { +}): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') diff --git a/src/components/Events/ClSportsPage.tsx b/src/components/Events/ClSportsPage.tsx index 1cce796b..b7a1f3d9 100644 --- a/src/components/Events/ClSportsPage.tsx +++ b/src/components/Events/ClSportsPage.tsx @@ -39,7 +39,7 @@ export default function ClSportsPage({ }[], Error > -}): JSX.Element { +}): React.JSX.Element { const { styles } = useStyles(stylesheet) const { userCampus } = useContext(UserKindContext) const [selectedLocation, setSelectedLocation] = @@ -81,7 +81,7 @@ export default function ClSportsPage({ title: WeekdayType data: UniversitySportsFieldsFragment[] }[] - }): JSX.Element => { + }): React.JSX.Element => { return ( {data.map((section, index) => ( @@ -103,7 +103,7 @@ export default function ClSportsPage({ }: { title: Lowercase data: UniversitySportsFieldsFragment[] - }): JSX.Element => { + }): React.JSX.Element => { const [collapsed, setCollapsed] = useState(false) return ( @@ -157,7 +157,7 @@ export default function ClSportsPage({ location, }: { location: string - }): JSX.Element => { + }): React.JSX.Element => { const isSelected = selectedLocation === location return ( diff --git a/src/components/Flow/ContextMenu.web.tsx b/src/components/Flow/ContextMenu.web.tsx index 31a5907b..992e010b 100644 --- a/src/components/Flow/ContextMenu.web.tsx +++ b/src/components/Flow/ContextMenu.web.tsx @@ -4,7 +4,7 @@ export default function ContextMenu({ children, }: { children: JSX.Element[] -}): JSX.Element { +}): React.JSX.Element { // TODO hook right click and show actions there return <>{children} } diff --git a/src/components/Flow/HomeBottomSheet.tsx b/src/components/Flow/HomeBottomSheet.tsx index c28731b6..17bb5ee8 100644 --- a/src/components/Flow/HomeBottomSheet.tsx +++ b/src/components/Flow/HomeBottomSheet.tsx @@ -3,7 +3,7 @@ import React from 'react' import { Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export const HomeBottomSheet = (): JSX.Element => { +export const HomeBottomSheet = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Flow/Login.tsx b/src/components/Flow/Login.tsx index 60c52778..ae6dce06 100644 --- a/src/components/Flow/Login.tsx +++ b/src/components/Flow/Login.tsx @@ -41,7 +41,7 @@ const useIsFloatingKeyboard = (): boolean => { return floating } -export default function Login(): JSX.Element { +export default function Login(): React.JSX.Element { const { styles } = useStyles(stylesheet) const floatingKeyboard = useIsFloatingKeyboard() const { t } = useTranslation('flow') diff --git a/src/components/Flow/Login.web.tsx b/src/components/Flow/Login.web.tsx index 80ec0b8b..1d3619fb 100644 --- a/src/components/Flow/Login.web.tsx +++ b/src/components/Flow/Login.web.tsx @@ -11,7 +11,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import LoginAnimatedText from './LoginAnimatedText' -export default function Login(): JSX.Element { +export default function Login(): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('flow') const { fromOnboarding } = useLocalSearchParams<{ diff --git a/src/components/Flow/LoginAnimatedText.tsx b/src/components/Flow/LoginAnimatedText.tsx index e2e52a31..819a789f 100644 --- a/src/components/Flow/LoginAnimatedText.tsx +++ b/src/components/Flow/LoginAnimatedText.tsx @@ -45,7 +45,7 @@ const textsDE = shuffleArray([ ]) const shouldVibrate = Platform.OS === 'ios' -function LoginAnimatedText(): JSX.Element { +function LoginAnimatedText(): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t, i18n } = useTranslation('flow') const [currentTextIndex, setCurrentTextIndex] = useState(0) diff --git a/src/components/Flow/OnboardingBox.tsx b/src/components/Flow/OnboardingBox.tsx index bd44184b..bd29f67e 100644 --- a/src/components/Flow/OnboardingBox.tsx +++ b/src/components/Flow/OnboardingBox.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -const OnboardingBox = ({ title }: { title: string }): JSX.Element => { +const OnboardingBox = ({ title }: { title: string }): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Flow/svgs/AnimatedLogoText.tsx b/src/components/Flow/svgs/AnimatedLogoText.tsx index 99c712da..884cf0d7 100644 --- a/src/components/Flow/svgs/AnimatedLogoText.tsx +++ b/src/components/Flow/svgs/AnimatedLogoText.tsx @@ -9,7 +9,7 @@ const AnimatedLogoText = ({ }: { dimensions: { logoWidth: number; logoHeight: number } speed: number -}): JSX.Element => { +}): React.JSX.Element => { const { theme } = useStyles() return ( diff --git a/src/components/Flow/svgs/AnimatedText.tsx b/src/components/Flow/svgs/AnimatedText.tsx index cd8698a2..89e7fcdb 100644 --- a/src/components/Flow/svgs/AnimatedText.tsx +++ b/src/components/Flow/svgs/AnimatedText.tsx @@ -29,7 +29,7 @@ const AnimatedText = ({ text: string textStyles: TextStyle disabled?: boolean -}): JSX.Element => { +}): React.JSX.Element => { const colorValue = useSharedValue(0) const { theme } = useStyles() useEffect(() => { diff --git a/src/components/Flow/svgs/logo.tsx b/src/components/Flow/svgs/logo.tsx index 3be232b5..c433bd20 100644 --- a/src/components/Flow/svgs/logo.tsx +++ b/src/components/Flow/svgs/logo.tsx @@ -2,7 +2,7 @@ import React from 'react' import { G, Path, Svg } from 'react-native-svg' import { useStyles } from 'react-native-unistyles' -export default function LogoSVG({ size }: { size: number }): JSX.Element { +export default function LogoSVG({ size }: { size: number }): React.JSX.Element { const { theme } = useStyles() return ( diff --git a/src/components/Flow/svgs/logoText.tsx b/src/components/Flow/svgs/logoText.tsx index fbb296b4..ec37f2a6 100644 --- a/src/components/Flow/svgs/logoText.tsx +++ b/src/components/Flow/svgs/logoText.tsx @@ -7,7 +7,7 @@ export default function LogoTextSVG({ }: { size: number color: string -}): JSX.Element { +}): React.JSX.Element { return ( { +}): React.JSX.Element => { const { t } = useTranslation('common') const initAllergenSelection = useFoodFilterStore( (state) => state.initAllergenSelection diff --git a/src/components/Food/FoodScreen.tsx b/src/components/Food/FoodScreen.tsx index 77cfbca9..7f711fd7 100644 --- a/src/components/Food/FoodScreen.tsx +++ b/src/components/Food/FoodScreen.tsx @@ -26,7 +26,7 @@ import { import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context' import { createStyleSheet, useStyles } from 'react-native-unistyles' -function FoodScreen(): JSX.Element { +function FoodScreen(): React.JSX.Element { const { styles } = useStyles(stylesheet) const [selectedDay, setSelectedDay] = useState(0) const selectedRestaurants = useFoodFilterStore( @@ -97,7 +97,7 @@ function FoodScreen(): JSX.Element { }: { day: Food index: number - }): JSX.Element => { + }): React.JSX.Element => { const date = new Date(day.timestamp) const { styles } = useStyles(stylesheet) @@ -166,9 +166,9 @@ function FoodScreen(): JSX.Element { ) : isError ? ( - {data.map((_: any, index: number) => ( + {data.map((_: unknown, index: number) => ( { +export const FoodHeaderRight = (): React.JSX.Element => { const { t } = useTranslation(['accessibility']) const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Food/ItemsPickerScreen.tsx b/src/components/Food/ItemsPickerScreen.tsx index d6066005..215f2918 100644 --- a/src/components/Food/ItemsPickerScreen.tsx +++ b/src/components/Food/ItemsPickerScreen.tsx @@ -20,7 +20,7 @@ import { */ const ItemsPickerScreen = (params: { route: { params: { type: string } } -}): JSX.Element => { +}): React.JSX.Element => { const type = params.route.params.type const data = type === 'allergens' ? allergenMap : flapMap const placeholderKey = diff --git a/src/components/Food/MealDay.tsx b/src/components/Food/MealDay.tsx index acf05d49..a7b5295a 100644 --- a/src/components/Food/MealDay.tsx +++ b/src/components/Food/MealDay.tsx @@ -17,7 +17,7 @@ const MealGroup = ({ group, }: { group: Record -}): JSX.Element => { +}): React.JSX.Element => { return ( <> {Object.entries(group).map(([key, value]) => ( @@ -39,7 +39,7 @@ const MealCategory = ({ }: { category: string meals: Meal[] -}): JSX.Element => { +}): React.JSX.Element => { const [collapsed, setCollapsed] = useState(false) /** @@ -106,7 +106,7 @@ export const MealDay = ({ }: { day: Food index: number -}): JSX.Element => { +}): React.JSX.Element => { /** * Filters an array of meals by restaurant name. * @param meals - An array of meals. @@ -159,6 +159,7 @@ export const MealDay = ({ restaurantName: string meals: Meal[] groupedMeals: Record + // eslint-disable-next-line @typescript-eslint/no-explicit-any styles: any } @@ -174,10 +175,11 @@ export const MealDay = ({ meals, groupedMeals, styles, - }: RestaurantProps): JSX.Element | null => { + }: RestaurantProps): React.JSX.Element | null => { if (meals.length > 0) { return ( + {/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access*/} {restaurantName} @@ -239,6 +241,7 @@ const stylesheet = createStyleSheet((theme) => ({ fontSize: 15, fontWeight: '500', }, + // eslint-disable-next-line react-native-unistyles/no-unused-styles dayRestaurantTitle: { color: theme.colors.text, fontSize: 18, diff --git a/src/components/Food/MealEntry.tsx b/src/components/Food/MealEntry.tsx index ca7528d2..4312b2a2 100644 --- a/src/components/Food/MealEntry.tsx +++ b/src/components/Food/MealEntry.tsx @@ -23,7 +23,7 @@ import { trackEvent } from '@aptabase/react-native' import Color from 'color' import { LinearGradient } from 'expo-linear-gradient' import { router } from 'expo-router' -import React, { useContext, useEffect, useState } from 'react' +import React, { useContext, useState } from 'react' import { useTranslation } from 'react-i18next' import { Platform, Pressable, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' @@ -40,7 +40,7 @@ export const MealEntry = ({ }: { meal: Meal index: number -}): JSX.Element => { +}): React.JSX.Element => { const preferencesSelection = useFoodFilterStore( (state) => state.preferencesSelection ) @@ -101,13 +101,13 @@ export const MealEntry = ({ mode="drag" scope="system" dragValue={t('details.share.message', { - meal: meal?.name[i18n.language as LanguageKey], - price: formatPrice(meal?.prices[userKind ?? 'guest']), + meal: meal.name[i18n.language as LanguageKey], + price: formatPrice(meal.prices[userKind ?? 'guest']), location: humanLocations[ meal.restaurant as keyof typeof humanLocations ], - id: meal?.id, + id: meal.id, })} > - {meal.variants?.length > 0 && ( + {meal.variants.length > 0 && ( - {userFlags?.map( + {userFlags.map( (flag: string, index: number) => ( void }> -): JSX.Element => { +): React.JSX.Element => { const [page, setPage] = useState(initialPage) useImperativeHandle( diff --git a/src/components/Layout/Tabbar.tsx b/src/components/Layout/Tabbar.tsx index 46a9769f..3a6c20bc 100644 --- a/src/components/Layout/Tabbar.tsx +++ b/src/components/Layout/Tabbar.tsx @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ + /* eslint-disable @typescript-eslint/no-unsafe-return */ import Color from 'color' import React from 'react' @@ -10,7 +12,7 @@ import { Tabs } from './NativeBottomTabs' export const useBottomTabBarHeight = _useBottomTabBarHeight -export default function TabLayout(): JSX.Element { +export default function TabLayout(): React.JSX.Element { const { theme } = useStyles() const { t } = useTranslation('navigation') const isAndroid = Platform.OS === 'android' diff --git a/src/components/Layout/Tabbar.web.tsx b/src/components/Layout/Tabbar.web.tsx index 6f99f6da..79a84754 100644 --- a/src/components/Layout/Tabbar.web.tsx +++ b/src/components/Layout/Tabbar.web.tsx @@ -9,7 +9,7 @@ export function useBottomTabBarHeight(): number { return 60 } -const DefaultTabs = (): JSX.Element => { +const DefaultTabs = (): React.JSX.Element => { const { theme: styleTheme } = useStyles() const { t } = useTranslation('navigation') const isMobile = Dimensions.get('window').width < 900 diff --git a/src/components/Library/LibraryCard.tsx b/src/components/Library/LibraryCard.tsx index a8007dbd..8d41a467 100644 --- a/src/components/Library/LibraryCard.tsx +++ b/src/components/Library/LibraryCard.tsx @@ -32,7 +32,7 @@ const LibraryCard = ({ iconProps, title, description, -}: LibraryCardProps): JSX.Element => { +}: LibraryCardProps): React.JSX.Element => { const { styles, theme } = useStyles(stylesheet) return ( diff --git a/src/components/Map/AvailableRoomsSuggestions.tsx b/src/components/Map/AvailableRoomsSuggestions.tsx index ecc4e78c..f0d30a8f 100644 --- a/src/components/Map/AvailableRoomsSuggestions.tsx +++ b/src/components/Map/AvailableRoomsSuggestions.tsx @@ -6,6 +6,7 @@ import { getContrastColor, roomNotFoundToast } from '@/utils/ui-utils' import { trackEvent } from '@aptabase/react-native' import { router } from 'expo-router' import { type FeatureCollection } from 'geojson' +import { type Position } from 'geojson' import React, { useContext } from 'react' import { useTranslation } from 'react-i18next' import { Pressable, Text, View } from 'react-native' @@ -95,18 +96,20 @@ const AvailableRoomsSuggestions: React.FC = ({ return } - const etage = details?.properties?.Ebene + const etage = details?.properties?.Ebene as + | string + | undefined setCurrentFloor({ - floor: (etage as string) ?? 'EG', + floor: etage ?? 'EG', manual: false, }) setClickedElement({ data: room.room, type: SEARCH_TYPES.ROOM, - center: - details?.properties?.center ?? - undefined, + center: details?.properties?.center as + | Position + | undefined, manual: false, }) trackEvent('Room', { diff --git a/src/components/Map/BottomSheetBackground.tsx b/src/components/Map/BottomSheetBackground.tsx index 43be73f1..85739538 100644 --- a/src/components/Map/BottomSheetBackground.tsx +++ b/src/components/Map/BottomSheetBackground.tsx @@ -7,7 +7,7 @@ import { useStyles, } from 'react-native-unistyles' -const BottomSheetBackground = (): JSX.Element => { +const BottomSheetBackground = (): React.JSX.Element => { const { styles } = useStyles(stylesheet) const dark = UnistylesRuntime.themeName === 'dark' const darkIos = 'rgba(0, 0, 0, 0.45)' diff --git a/src/components/Map/BottomSheetDetailModal.tsx b/src/components/Map/BottomSheetDetailModal.tsx index a34f7f7a..fb0d6544 100644 --- a/src/components/Map/BottomSheetDetailModal.tsx +++ b/src/components/Map/BottomSheetDetailModal.tsx @@ -84,7 +84,7 @@ export const BottomSheetDetailModal = ({ currentPositionModal, roomData, modalSection, -}: BottomSheetDetailModalProps): JSX.Element => { +}: BottomSheetDetailModalProps): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Map/BottomSheetMap.tsx b/src/components/Map/BottomSheetMap.tsx index be6f782f..94086138 100644 --- a/src/components/Map/BottomSheetMap.tsx +++ b/src/components/Map/BottomSheetMap.tsx @@ -74,7 +74,9 @@ const MapBottomSheet: React.FC = ({ keyboardBehavior="extend" onChange={(index) => { if (index <= 1) { - localSearch !== '' && setLocalSearch('') + if (localSearch !== '') { + setLocalSearch('') + } textInputRef.current?.blur() } }} diff --git a/src/components/Map/FloorPicker.tsx b/src/components/Map/FloorPicker.tsx index db5d8258..076822df 100644 --- a/src/components/Map/FloorPicker.tsx +++ b/src/components/Map/FloorPicker.tsx @@ -22,7 +22,7 @@ const FloorPicker: React.FC = ({ showAllFloors, toggleShowAllFloors, setCameraTriggerKey, -}): JSX.Element => { +}): React.JSX.Element => { const { styles } = useStyles(stylesheet) const { currentFloor, setCurrentFloor } = useContext(MapContext) @@ -81,6 +81,10 @@ const FloorPicker: React.FC = ({ name: 'cancel', size: 26, }} + web={{ + name: 'X', + size: 26, + }} style={styles.xIcon} /> @@ -145,6 +149,10 @@ const FloorPicker: React.FC = ({ size: 21, variant: 'outlined', }} + web={{ + name: 'Navigation', + size: 21, + }} /> diff --git a/src/components/Map/MapScreen.tsx b/src/components/Map/MapScreen.tsx index f721d7d8..6ed0efa5 100644 --- a/src/components/Map/MapScreen.tsx +++ b/src/components/Map/MapScreen.tsx @@ -42,13 +42,7 @@ import { ImageEntry } from '@maplibre/maplibre-react-native/lib/typescript/commo import { useQuery } from '@tanstack/react-query' import { toast } from 'burnt' import { router, useLocalSearchParams, useNavigation } from 'expo-router' -import { - type Feature, - type FeatureCollection, - type GeoJsonProperties, - type Geometry, - type Position, -} from 'geojson' +import { type Feature, type FeatureCollection, type Position } from 'geojson' import React, { useCallback, useContext, @@ -91,7 +85,7 @@ export function requestPermission(): void { const isIpadOS = Platform.OS === 'ios' && Platform.isPad -const MapScreen = (): JSX.Element => { +const MapScreen = (): React.JSX.Element => { const tabBarHeight = useBottomTabBarHeight() const navigation = useNavigation() const [mapLoadState, setMapLoadState] = useState(LoadingState.LOADING) @@ -195,7 +189,7 @@ const MapScreen = (): JSX.Element => { '"Time table does not exist" (-202)', 'Timetable is empty', ] - if (ignoreErrors.includes(error?.message)) { + if (ignoreErrors.includes(error.message)) { return false } return failureCount < 2 @@ -297,7 +291,7 @@ const MapScreen = (): JSX.Element => { .flat() const buildings = BUILDINGS.map((building) => { const buildingRooms = rooms.filter( - (room) => room.properties.Gebaeude === building + (room) => room.properties.Gebaeude === (building as Gebaeude) ) const floorCount = Array.from( new Set(buildingRooms.map((room) => room.properties.Ebene)) @@ -405,7 +399,7 @@ const MapScreen = (): JSX.Element => { }, [userFaculty]) useEffect(() => { - if (localSearch?.length === 1 && params.room != null) { + if (localSearch.length === 1 && params.room != null) { router.setParams(undefined) } }, [localSearch]) @@ -424,7 +418,7 @@ const MapScreen = (): JSX.Element => { }) useEffect(() => { - async function load(): Promise { + function load(): void { if (roomStatusData == null) { console.debug('No room status data') return @@ -433,7 +427,7 @@ const MapScreen = (): JSX.Element => { const dateObj = new Date() const date = formatISODate(dateObj) const time = formatISOTime(dateObj) - const rooms = await filterRooms(roomStatusData, date, time) + const rooms = filterRooms(roomStatusData, date, time) setAvailableRooms(rooms) } catch (e) { if ( @@ -447,7 +441,7 @@ const MapScreen = (): JSX.Element => { } } setAvailableRooms(null) - void load() + load() }, [userKind, roomStatusData]) useEffect(() => { @@ -462,7 +456,12 @@ const MapScreen = (): JSX.Element => { ? allRooms.features : Object.values(allRooms.features) ) - .map((room: any) => room.properties?.Ebene?.toString()) + .map((room) => { + const properties = (room as RoomData).properties + return typeof properties?.Ebene === 'string' + ? properties.Ebene + : '' + }) .filter((etage) => etage != null) ) ).sort( @@ -480,7 +479,7 @@ const MapScreen = (): JSX.Element => { // filter the filteredGeoJSON to only show available rooms const filterAvailableRooms = ( rooms: FeatureCollection | undefined - ): Feature[] => { + ): Feature[] => { if (rooms == null) { return [] } @@ -495,10 +494,7 @@ const MapScreen = (): JSX.Element => { } const filteredFeatures = filterAvailableRooms(filteredGeoJSON) - const availableFilteredGeoJSON: FeatureCollection< - Geometry, - GeoJsonProperties - > = { + const availableFilteredGeoJSON: FeatureCollection = { type: 'FeatureCollection', features: filteredFeatures, } @@ -510,9 +506,7 @@ const MapScreen = (): JSX.Element => { if (mapOverlay == null) { return } - const filterEtage = ( - etage: string - ): Feature[] => { + const filterEtage = (etage: string): Feature[] => { return allRooms.features.filter( (feature) => feature.properties?.Ebene === etage ) @@ -520,7 +514,7 @@ const MapScreen = (): JSX.Element => { const filteredFeatures = filterEtage(currentFloor?.floor ?? 'EG') - const newGeoJSON: FeatureCollection = { + const newGeoJSON: FeatureCollection = { ...mapOverlay, features: filteredFeatures, } @@ -552,19 +546,19 @@ const MapScreen = (): JSX.Element => { return roomData as RoomData } - const getBuildingData = (building: string): any => { + const getBuildingData = (building: string): RoomData => { const buildingDetails = allRooms.features.find( (x) => x.properties?.Gebaeude === building && - x.properties?.rtype === SEARCH_TYPES.BUILDING + x.properties.rtype === SEARCH_TYPES.BUILDING ) - const numberOfFreeRooms = availableRooms?.filter( - (x) => x.room[0] === building || x.room.slice(0, 1) === building + const numberOfFreeRooms = availableRooms?.filter((x) => + x.room.startsWith(building) ).length const numberOfRooms = allRooms.features.filter( (x) => x.properties?.Gebaeude === building && - x.properties?.rtype === SEARCH_TYPES.ROOM + x.properties.rtype === SEARCH_TYPES.ROOM ).length const buildingData = { title: building, @@ -572,7 +566,7 @@ const MapScreen = (): JSX.Element => { properties: buildingDetails?.properties, occupancies: { total: numberOfRooms, - available: numberOfFreeRooms, + available: numberOfFreeRooms ?? 0, }, type: SEARCH_TYPES.BUILDING, } @@ -589,9 +583,11 @@ const MapScreen = (): JSX.Element => { default: return { title: t('misc.unknown'), + subtitle: t('misc.unknown'), + type: SEARCH_TYPES.ROOM, properties: null, occupancies: null, - } + } as RoomData } }, [clickedElement]) @@ -774,7 +770,7 @@ const MapScreen = (): JSX.Element => { images={{ // https://iconduck.com/icons/71717/map-marker - License: Creative Commons Zero v1.0 Universal 'map-marker': - // eslint-disable-next-line @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports require('@/assets/map-marker.png') as ImageEntry, }} /> @@ -835,7 +831,8 @@ const MapScreen = (): JSX.Element => { shape={filteredGeoJSON} onPress={(e) => { setClickedElement({ - data: e.features[0].properties?.Raum, + data: e.features[0].properties + ?.Raum as string, type: SEARCH_TYPES.ROOM, center: e.features[0].properties?.center as | Position @@ -843,7 +840,8 @@ const MapScreen = (): JSX.Element => { manual: true, }) trackEvent('Room', { - room: e.features[0].properties?.Raum, + room: e.features[0].properties + ?.Raum as string, origin: 'MapClick', }) handlePresentModalPress() diff --git a/src/components/Map/MapScreen.web.tsx b/src/components/Map/MapScreen.web.tsx index 192efc6a..8d741fd8 100644 --- a/src/components/Map/MapScreen.web.tsx +++ b/src/components/Map/MapScreen.web.tsx @@ -2,7 +2,7 @@ import React from 'react' import ErrorView from '../Error/ErrorView' -export default function MapScreen(): JSX.Element { +export default function MapScreen(): React.JSX.Element { return ( = ({ name: 'delete', size: 24, }} + web={{ + name: 'Trash', + size: 24, + }} style={styles.toast} /> diff --git a/src/components/Map/SearchResultRow.tsx b/src/components/Map/SearchResultRow.tsx index 700e4079..1df882b8 100644 --- a/src/components/Map/SearchResultRow.tsx +++ b/src/components/Map/SearchResultRow.tsx @@ -1,8 +1,11 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { MapContext } from '@/contexts/map' -import { type SearchResult } from '@/types/map' +import { SEARCH_TYPES, type SearchResult } from '@/types/map' +import { MaterialIcon } from '@/types/material-icons' import { getContrastColor } from '@/utils/ui-utils' import { trackEvent } from '@aptabase/react-native' import { TouchableOpacity } from '@gorhom/bottom-sheet' +import { type Position } from 'geojson' import React, { useContext } from 'react' import { useTranslation } from 'react-i18next' import { Text, View } from 'react-native' @@ -20,7 +23,7 @@ const ResultRow: React.FC<{ index, handlePresentModalPress, updateSearchHistory, -}): JSX.Element => { +}): React.JSX.Element => { const { setClickedElement, setLocalSearch, setCurrentFloor } = useContext(MapContext) const { styles } = useStyles(stylesheet) @@ -31,11 +34,13 @@ const ResultRow: React.FC<{ key={index} style={styles.searchRowContainer} onPressOut={() => { - const center = result.item.properties?.center + const center = result.item.properties?.center as + | Position + | undefined updateSearchHistory(result) setClickedElement({ data: result.title, - type: result.item.properties?.rtype, + type: result.item.properties?.rtype as SEARCH_TYPES, center, manual: false, }) @@ -54,14 +59,19 @@ const ResultRow: React.FC<{ diff --git a/src/components/Map/SearchResuts.tsx b/src/components/Map/SearchResuts.tsx index 653f873a..2b3dd7a0 100644 --- a/src/components/Map/SearchResuts.tsx +++ b/src/components/Map/SearchResuts.tsx @@ -70,12 +70,12 @@ const SearchResults: React.FC = ({ const [searchResultsExact, searchResultsFuzzy] = useMemo(() => { const results = fuse.search(localSearch.trim().toUpperCase()) const roomResults = results.map((result) => ({ - title: result.item.properties?.Raum, - subtitle: result.item.properties?.Funktion_en, + title: result.item.properties?.Raum as string, + subtitle: result.item.properties?.Funktion_en as string, isExactMatch: Boolean( - result.item.properties?.Raum.toUpperCase().includes( - localSearch.toUpperCase() - ) + (result.item.properties?.Raum as string) + .toUpperCase() + .includes(localSearch.toUpperCase()) ), item: result.item, })) @@ -123,7 +123,7 @@ const SearchResults: React.FC = ({ ] : []), ]} - keyExtractor={(item, index) => item.title + index} + keyExtractor={(item, index) => `${item.title}${index}`} renderItem={({ item, index }) => ( { +const CalendarRow = ({ event }: { event: Calendar }): React.JSX.Element => { const { t, i18n } = useTranslation('common') const { styles } = useStyles(stylesheet) return ( @@ -55,7 +55,7 @@ const CalendarRow = ({ event }: { event: Calendar }): JSX.Element => { ) } -const ExamRow = ({ event }: { event: Exam }): JSX.Element => { +const ExamRow = ({ event }: { event: Exam }): React.JSX.Element => { const setExam = useRouteParamsStore((state) => state.setSelectedExam) const { styles } = useStyles(stylesheet) diff --git a/src/components/Rows/EventRow.tsx b/src/components/Rows/EventRow.tsx index ab14d05c..c0510ed9 100644 --- a/src/components/Rows/EventRow.tsx +++ b/src/components/Rows/EventRow.tsx @@ -18,7 +18,7 @@ const CLEventRow = ({ event, }: { event: CampusLifeEventFieldsFragment -}): JSX.Element => { +}): React.JSX.Element => { const { styles } = useStyles(stylesheet) const setSelectedClEvent = useCLParamsStore( (state) => state.setSelectedClEvent diff --git a/src/components/Rows/GradesRow.tsx b/src/components/Rows/GradesRow.tsx index 04ad996d..152bf136 100644 --- a/src/components/Rows/GradesRow.tsx +++ b/src/components/Rows/GradesRow.tsx @@ -6,7 +6,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import RowEntry from '../Universal/RowEntry' -const GradesRow = ({ item }: { item: Grade }): JSX.Element => { +const GradesRow = ({ item }: { item: Grade }): React.JSX.Element => { const { styles } = useStyles(stylesheet) const { t } = useTranslation('settings') if (item.titel === null || item.titel === '') { diff --git a/src/components/Rows/LecturerRow.tsx b/src/components/Rows/LecturerRow.tsx index 4f557149..5786983e 100644 --- a/src/components/Rows/LecturerRow.tsx +++ b/src/components/Rows/LecturerRow.tsx @@ -8,7 +8,11 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import RowEntry from '../Universal/RowEntry' -const LecturerRow = ({ item }: { item: NormalizedLecturer }): JSX.Element => { +const LecturerRow = ({ + item, +}: { + item: NormalizedLecturer +}): React.JSX.Element => { const { styles, theme } = useStyles(stylesheet) const setSelectedLecturer = useRouteParamsStore( (state) => state.setSelectedLecturer diff --git a/src/components/Rows/SportsRow.tsx b/src/components/Rows/SportsRow.tsx index c69d75e1..03a672af 100644 --- a/src/components/Rows/SportsRow.tsx +++ b/src/components/Rows/SportsRow.tsx @@ -15,7 +15,7 @@ const SportsRow = ({ event, }: { event: UniversitySportsFieldsFragment -}): JSX.Element => { +}): React.JSX.Element => { const { styles, theme } = useStyles(stylesheet) const setSelectedSportsEvent = useCLParamsStore( (state) => state.setSelectedSportsEvent diff --git a/src/components/Settings/Avatar.tsx b/src/components/Settings/Avatar.tsx index 14aca17f..fa6d08d9 100644 --- a/src/components/Settings/Avatar.tsx +++ b/src/components/Settings/Avatar.tsx @@ -17,7 +17,7 @@ const Avatar = ({ size?: number background?: ColorValue children: JSX.Element -}): JSX.Element => { +}): React.JSX.Element => { const { styles } = useStyles(stylesheet) return {children} diff --git a/src/components/Settings/GradesButton.tsx b/src/components/Settings/GradesButton.tsx index 131c17e3..0bc3f4e5 100644 --- a/src/components/Settings/GradesButton.tsx +++ b/src/components/Settings/GradesButton.tsx @@ -6,7 +6,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import PlatformIcon from '../Universal/Icon' -const GradesButton = (): JSX.Element => { +const GradesButton = (): React.JSX.Element => { const { t } = useTranslation('settings') const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Settings/NameBox.tsx b/src/components/Settings/NameBox.tsx index e7daba3c..cfd3a5e7 100644 --- a/src/components/Settings/NameBox.tsx +++ b/src/components/Settings/NameBox.tsx @@ -24,7 +24,7 @@ const NameBox = ({ title, subTitle1, subTitle2, -}: NameBoxProps): JSX.Element => { +}: NameBoxProps): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Timetable/DetailsBody.tsx b/src/components/Timetable/DetailsBody.tsx index 60154788..f361ae6e 100644 --- a/src/components/Timetable/DetailsBody.tsx +++ b/src/components/Timetable/DetailsBody.tsx @@ -4,8 +4,8 @@ import { StyleSheet, View } from 'react-native' export default function DetailsBody({ children, }: { - children: JSX.Element | JSX.Element[] -}): JSX.Element { + children: React.JSX.Element | JSX.Element[] +}): React.JSX.Element { return {children} } diff --git a/src/components/Timetable/DetailsRow.tsx b/src/components/Timetable/DetailsRow.tsx index 94356649..4d756864 100644 --- a/src/components/Timetable/DetailsRow.tsx +++ b/src/components/Timetable/DetailsRow.tsx @@ -5,7 +5,7 @@ export default function DetailsRow({ children, }: { children: JSX.Element[] -}): JSX.Element { +}): React.JSX.Element { return {children} } diff --git a/src/components/Timetable/DetailsSymbol.tsx b/src/components/Timetable/DetailsSymbol.tsx index b779b9e8..4f00bcc9 100644 --- a/src/components/Timetable/DetailsSymbol.tsx +++ b/src/components/Timetable/DetailsSymbol.tsx @@ -5,7 +5,7 @@ export default function DetailsSymbol({ children, }: { children: JSX.Element -}): JSX.Element { +}): React.JSX.Element { return {children} } diff --git a/src/components/Timetable/HeaderButtons.tsx b/src/components/Timetable/HeaderButtons.tsx index 358a9c81..59d87f3d 100644 --- a/src/components/Timetable/HeaderButtons.tsx +++ b/src/components/Timetable/HeaderButtons.tsx @@ -7,7 +7,7 @@ import { createStyleSheet, useStyles } from 'react-native-unistyles' import PlatformIcon from '../Universal/Icon' -export function HeaderLeft(): JSX.Element { +export function HeaderLeft(): React.JSX.Element { const { styles } = useStyles(stylesheet) const timetableMode = usePreferencesStore((state) => state.timetableMode) const setTimetableMode = usePreferencesStore( @@ -57,7 +57,7 @@ interface HeaderRightProps { setToday: () => void } -export function HeaderRight({ setToday }: HeaderRightProps): JSX.Element { +export function HeaderRight({ setToday }: HeaderRightProps): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation(['accessibility']) return ( diff --git a/src/components/Timetable/Separator.tsx b/src/components/Timetable/Separator.tsx index 11a341aa..aaba2cfa 100644 --- a/src/components/Timetable/Separator.tsx +++ b/src/components/Timetable/Separator.tsx @@ -2,7 +2,7 @@ import React from 'react' import { View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -export default function Separator(): JSX.Element { +export default function Separator(): React.JSX.Element { const { styles } = useStyles(stylesheet) return diff --git a/src/components/Timetable/ShareCard.tsx b/src/components/Timetable/ShareCard.tsx index 19f491bc..62952906 100644 --- a/src/components/Timetable/ShareCard.tsx +++ b/src/components/Timetable/ShareCard.tsx @@ -17,7 +17,9 @@ interface ShareCardProps { event: FriendlyTimetableEntry } -export default function ShareCard({ event }: ShareCardProps): JSX.Element { +export default function ShareCard({ + event, +}: ShareCardProps): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('timetable') diff --git a/src/components/Timetable/TimetableList.tsx b/src/components/Timetable/TimetableList.tsx index 003a2cd8..357c81c3 100644 --- a/src/components/Timetable/TimetableList.tsx +++ b/src/components/Timetable/TimetableList.tsx @@ -1,10 +1,15 @@ import ErrorView from '@/components/Error/ErrorView' -// @ts-expect-error no types +// @ts-expect-error no types available import DragDropView from '@/components/Exclusive/DragView' import Divider from '@/components/Universal/Divider' import useRouteParamsStore from '@/hooks/useRouteParamsStore' import { type ITimetableViewProps } from '@/types/timetable' -import { type Exam, type FriendlyTimetableEntry } from '@/types/utils' +import { + type Exam, + ExamEntry, + type FriendlyTimetableEntry, + TimetableEntry, +} from '@/types/utils' import { formatFriendlyDate, formatFriendlyDateTime, @@ -34,7 +39,7 @@ export default function TimetableList({ timetable, // eslint-disable-next-line react/prop-types exams, -}: ITimetableViewProps): JSX.Element { +}: ITimetableViewProps): React.JSX.Element { /** * Constants */ @@ -46,7 +51,7 @@ export default function TimetableList({ */ const router = useRouter() const navigation = useNavigation() - const listRef = useRef>(null) + const listRef = useRef>(null) const { t } = useTranslation('timetable') const { styles, theme } = useStyles(stylesheet) const setSelectedLecture = useRouteParamsStore( @@ -74,10 +79,6 @@ export default function TimetableList({ }) }, [navigation]) - /** - * Colors - */ - /** * Constants */ @@ -102,7 +103,7 @@ export default function TimetableList({ router.navigate('/lecture') } - function renderSectionHeader(title: Date): JSX.Element { + function renderSectionHeader(title: Date): React.JSX.Element { const isToday = formatISODate(title) === formatISODate(today) return ( @@ -115,23 +116,23 @@ export default function TimetableList({ ) } - function renderSectionFooter(): JSX.Element { + function renderSectionFooter(): React.JSX.Element { return } - function renderItemSeparator(): JSX.Element { + function renderItemSeparator(): React.JSX.Element { return } function renderTimetableItem({ item, }: { item: FriendlyTimetableEntry - }): JSX.Element { + }): React.JSX.Element { return ( - {item.rooms?.join(', ')} + {item.rooms.join(', ')} @@ -178,7 +179,7 @@ export default function TimetableList({ ) } - function renderExamItem({ exam }: { exam: Exam }): JSX.Element { + function renderExamItem({ exam }: { exam: Exam }): React.JSX.Element { const navigateToPage = (): void => { setSelectedExam(exam) router.navigate('/exam') @@ -231,7 +232,11 @@ export default function TimetableList({ ) } - function renderItem({ item }: { item: any }): JSX.Element { + function renderItem({ + item, + }: { + item: ExamEntry | TimetableEntry + }): React.JSX.Element { if (item.eventType === 'exam') { return renderExamItem({ exam: item }) } diff --git a/src/components/Timetable/TimetableScreen.tsx b/src/components/Timetable/TimetableScreen.tsx index 338b44f4..9ff26e6b 100644 --- a/src/components/Timetable/TimetableScreen.tsx +++ b/src/components/Timetable/TimetableScreen.tsx @@ -25,7 +25,7 @@ export const loadTimetable = async (): Promise => { return timetable } -function TimetableScreen(): JSX.Element { +function TimetableScreen(): React.JSX.Element { const { styles } = useStyles(stylesheet) const timetableMode = usePreferencesStore((state) => state.timetableMode) @@ -69,7 +69,7 @@ function TimetableScreen(): JSX.Element { const { isRefetchingByUser, refetchByUser } = useRefreshByUser(refetch) - const LoadingView = (): JSX.Element => { + const LoadingView = (): React.JSX.Element => { return ( diff --git a/src/components/Timetable/TimetableWeek.tsx b/src/components/Timetable/TimetableWeek.tsx index 2279692a..632a298f 100644 --- a/src/components/Timetable/TimetableWeek.tsx +++ b/src/components/Timetable/TimetableWeek.tsx @@ -26,7 +26,7 @@ import EventComponent from './WeekEventComponent' export default function TimetableWeek({ timetable, -}: ITimetableViewProps): JSX.Element { +}: ITimetableViewProps): React.JSX.Element { const { styles, theme } = useStyles(stylesheet) const today = moment().startOf('day').toDate() const firstElementeDate = timetable.find( diff --git a/src/components/Timetable/WeekEventComponent.tsx b/src/components/Timetable/WeekEventComponent.tsx index 304cd6a7..76f639db 100644 --- a/src/components/Timetable/WeekEventComponent.tsx +++ b/src/components/Timetable/WeekEventComponent.tsx @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ import { formatFriendlyTime } from '@/utils/date-utils' import { getContrastColor } from '@/utils/ui-utils' import { type PackedEvent } from '@howljs/calendar-kit' @@ -20,7 +23,7 @@ const EventComponent = ({ event: PackedEvent theme: UnistylesTheme isDark: boolean -}): JSX.Element | null => { +}): React.JSX.Element | null => { const { styles } = useStyles(stylesheet) if ( event.start.dateTime === undefined || @@ -33,15 +36,15 @@ const EventComponent = ({ const end = new Date(event.end.dateTime) const duration = end.getTime() - begin.getTime() const isOverflowing = duration < 1000 * 60 * 60 - const nameParts = event.shortName?.split('_')?.slice(1) + const nameParts = event.shortName?.split('_')?.slice(1) as string[] const background = eventBackgroundColor(theme.colors.primary, isDark) const fontColor = textColor(theme.colors.primary, background, isDark) - const eventName = event.name ?? '' + const eventName = event.name as string const nameToDisplay = eventName.length > 20 ? nameParts.join('_') !== '' ? nameParts.join('_') - : event.shortName + : (event.shortName as string) : eventName return ( @@ -100,7 +103,7 @@ const EventComponent = ({ color: fontColor, }} > - {event.rooms?.join(', ')} + {event.rooms?.join(', ') ?? ''} )} @@ -152,7 +155,7 @@ const lineColor = ( .hex() : eventBackgroundColor -const stylesheet = createStyleSheet((theme) => ({ +const stylesheet = createStyleSheet(() => ({ eventContainer: { borderRadius: 0, flex: 1, diff --git a/src/components/Universal/BottomSheetRootBackground.tsx b/src/components/Universal/BottomSheetRootBackground.tsx index 5fa38aa2..922062bd 100644 --- a/src/components/Universal/BottomSheetRootBackground.tsx +++ b/src/components/Universal/BottomSheetRootBackground.tsx @@ -11,7 +11,7 @@ import { useStyles, } from 'react-native-unistyles' -export const BottomSheetRootBackground = (): JSX.Element => { +export const BottomSheetRootBackground = (): React.JSX.Element => { const darkIos = 'rgba(39, 39, 39, 0.4)' const lightIos = 'rgba(255, 255, 255, 0.5)' const { styles } = useStyles(stylesheet) @@ -38,7 +38,7 @@ export const BottomSheetRootBackground = (): JSX.Element => { export const renderBackdrop = ( props: BottomSheetBackdropProps -): JSX.Element => ( +): React.JSX.Element => ( { +}: PlatformIconProps): React.JSX.Element => { const { styles, theme } = useStyles(stylesheet) const lucidFallback = if (Platform.OS === 'web') { if (web != null) { - const LucideIcon = icons[web?.name] + const LucideIcon = icons[web.name] return ( ) } else { @@ -90,9 +90,7 @@ const PlatformIcon = ({ } else if (Platform.OS === 'ios') { return (ios.fallback ?? false) ? ( @@ -135,7 +133,9 @@ const PlatformIcon = ({ > {communityIcons.includes(android.name) ? ( ({ +const stylesheet = createStyleSheet(() => ({ androidIcon: { paddingTop: 3, }, diff --git a/src/components/Universal/LoadingIndicator.tsx b/src/components/Universal/LoadingIndicator.tsx index 38d09c2f..036d91da 100644 --- a/src/components/Universal/LoadingIndicator.tsx +++ b/src/components/Universal/LoadingIndicator.tsx @@ -6,7 +6,9 @@ interface LoadingIndicatorProps { style?: ViewStyle } -const LoadingIndicator = ({ style }: LoadingIndicatorProps): JSX.Element => { +const LoadingIndicator = ({ + style, +}: LoadingIndicatorProps): React.JSX.Element => { const { theme } = useStyles() return ( void -}): JSX.Element => { +}): React.JSX.Element => { const ORIGINAL_ERROR_WRONG_CREDENTIALS = 'Wrong credentials' const ORGINAL_ERROR_MISSING = 'Wrong or missing parameter' const KNOWN_BACKEND_ERRORS = ['Response is not valid JSON'] @@ -114,7 +114,9 @@ const LoginForm = ({ ns: 'common', }), onPress: async () => - await Linking.openURL(STATUS_URL), + (await Linking.openURL( + STATUS_URL + )) as Promise, }, ] : []), @@ -146,7 +148,7 @@ const LoginForm = ({ useEffect(() => { // on iOS secure store is synced with iCloud, so we can prefill the login form if (Platform.OS === 'ios') { - const loadSavedData = async (): Promise => { + const loadSavedData = (): void => { const savedUsername = loadSecure('username') const savedPassword = loadSecure('password') if (savedUsername !== null && savedPassword !== null) { @@ -164,7 +166,7 @@ const LoginForm = ({ } } - void loadSavedData() + loadSavedData() } }, []) @@ -217,7 +219,7 @@ const LoginForm = ({ }} onSubmitEditing={() => { if (username !== '') { - login().catch((error: Error) => { + login().catch((error: unknown) => { console.debug(error) }) } @@ -235,7 +237,7 @@ const LoginForm = ({ { - login().catch((error: Error) => { + login().catch((error: unknown) => { console.debug(error) }) }} @@ -255,7 +257,7 @@ const LoginForm = ({ { - guestLogin().catch((error: Error) => { + guestLogin().catch((error: unknown) => { console.debug(error) }) }} diff --git a/src/components/Universal/RowEntry.tsx b/src/components/Universal/RowEntry.tsx index aec9a6ee..bb258f18 100644 --- a/src/components/Universal/RowEntry.tsx +++ b/src/components/Universal/RowEntry.tsx @@ -19,7 +19,7 @@ const RowEntry = ({ maxTitleWidth?: DimensionValue backgroundColor?: string icon?: JSX.Element -}): JSX.Element => { +}): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Universal/SectionsView.tsx b/src/components/Universal/SectionsView.tsx index 1fca0540..c7b39073 100644 --- a/src/components/Universal/SectionsView.tsx +++ b/src/components/Universal/SectionsView.tsx @@ -12,7 +12,7 @@ const SectionView = ({ footer?: string children: JSX.Element link?: { text: string; destination: () => void } -}): JSX.Element => { +}): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( <> diff --git a/src/components/Universal/ShareButton.tsx b/src/components/Universal/ShareButton.tsx index 2b03026c..418b99c7 100644 --- a/src/components/Universal/ShareButton.tsx +++ b/src/components/Universal/ShareButton.tsx @@ -11,7 +11,7 @@ interface ShareButtonProps { export default function ShareButton({ onPress, -}: ShareButtonProps): JSX.Element { +}: ShareButtonProps): React.JSX.Element { const { styles } = useStyles(stylesheet) const { t } = useTranslation('common') diff --git a/src/components/Universal/ShareHeaderButton.tsx b/src/components/Universal/ShareHeaderButton.tsx index e28d22c7..d8322704 100644 --- a/src/components/Universal/ShareHeaderButton.tsx +++ b/src/components/Universal/ShareHeaderButton.tsx @@ -10,7 +10,7 @@ interface ShareButtonProps { export default function ShareHeaderButton({ onPress, -}: ShareButtonProps): JSX.Element { +}: ShareButtonProps): React.JSX.Element { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Universal/ToggleRow.tsx b/src/components/Universal/ToggleRow.tsx index bb8ff3e0..0e638da9 100644 --- a/src/components/Universal/ToggleRow.tsx +++ b/src/components/Universal/ToggleRow.tsx @@ -10,7 +10,7 @@ const ToggleRow = ({ items: string[] selectedElement: number setSelectedElement: (element: number) => void -}): JSX.Element => { +}): React.JSX.Element => { const { styles } = useStyles(stylesheet) return ( diff --git a/src/components/Universal/WorkaroundStack.tsx b/src/components/Universal/WorkaroundStack.tsx index 1d4d1865..642e91ec 100644 --- a/src/components/Universal/WorkaroundStack.tsx +++ b/src/components/Universal/WorkaroundStack.tsx @@ -35,7 +35,7 @@ function WorkaroundStack({ headerSearchBarOptions = undefined, params = {}, androidFallback = false, -}: WorkaroundStackProps): JSX.Element { +}: WorkaroundStackProps): React.JSX.Element { const { t } = useTranslation('navigation') const Stack = createNativeStackNavigator() const StackAndroid = createStackNavigator() diff --git a/src/components/contexts.ts b/src/components/contexts.ts index c2235c30..0af2eb40 100644 --- a/src/components/contexts.ts +++ b/src/components/contexts.ts @@ -6,16 +6,28 @@ export const UserKindContext = createContext({ userKind: 'student', userFaculty: undefined, userCampus: undefined, - toggleUserKind: () => {}, + toggleUserKind: () => { + throw new Error('toggleUserKind not implemented') + }, }) export const DashboardContext = createContext({ shownDashboardEntries: [], hiddenDashboardEntries: [], - hideDashboardEntry: () => {}, - bringBackDashboardEntry: () => {}, - resetOrder: () => {}, - updateDashboardOrder: () => {}, + hideDashboardEntry: () => { + throw new Error('hideDashboardEntry not implemented') + }, + bringBackDashboardEntry: () => { + throw new Error('bringBackDashboardEntry not implemented') + }, + resetOrder: () => { + throw new Error('resetOrder not implemented') + }, + updateDashboardOrder: () => { + throw new Error('updateDashboardOrder not implemented') + }, hiddenAnnouncements: [], - hideAnnouncement: () => {}, + hideAnnouncement: () => { + throw new Error('hideAnnouncement not implemented') + }, }) diff --git a/src/components/provider.tsx b/src/components/provider.tsx index f076bd73..6c4ed62e 100644 --- a/src/components/provider.tsx +++ b/src/components/provider.tsx @@ -56,8 +56,7 @@ export const queryClient = new QueryClient({ */ export default function Provider({ children, - ...rest -}: ProviderProps): JSX.Element { +}: ProviderProps): React.JSX.Element { const userKind = useUserKind() const dashboard = useDashboard() const segments = useSegments() @@ -108,7 +107,7 @@ export default function Provider({ return } trackEvent('Theme', { - theme: theme ?? 'auto', + theme: theme, }) }, [accentColor, analyticsInitialized]) @@ -148,9 +147,10 @@ export default function Provider({ } const entries: Record = {} - dashboard.shownDashboardEntries?.forEach((entry, index) => { + dashboard.shownDashboardEntries.forEach((entry, index) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (entry !== undefined) { - entries[entry.key] = `Position ${index + 1}` + entries[entry.key] = `Position ${(index + 1).toString()}` } }) @@ -166,7 +166,7 @@ export default function Provider({ const entries: Record = {} - dashboard.hiddenDashboardEntries?.forEach((entry) => { + dashboard.hiddenDashboardEntries.forEach((entry) => { if (entry !== undefined) { entries[entry.key] = 'Card hidden' } @@ -213,7 +213,7 @@ export default function Provider({ try { const primary = accentColors[accentColor][scheme] return primary - } catch (e) { + } catch { return accentColors.blue[scheme] } } @@ -238,7 +238,9 @@ export default function Provider({ }, [accentColor]) useEffect(() => { - const subscription = Appearance.addChangeListener(() => {}) + const subscription = Appearance.addChangeListener(() => { + /* nothing to do here */ + }) const isFixedTheme = theme === 'dark' || theme === 'light' if (Platform.OS !== 'web') { diff --git a/src/styles/unistyles.ts b/src/styles/unistyles.ts index 0cb78c96..ede754f1 100644 --- a/src/styles/unistyles.ts +++ b/src/styles/unistyles.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ + /* eslint-disable @typescript-eslint/no-empty-interface */ import { UnistylesRegistry } from 'react-native-unistyles' diff --git a/src/types/map.ts b/src/types/map.ts index 59cb3bad..5dca7175 100644 --- a/src/types/map.ts +++ b/src/types/map.ts @@ -1,9 +1,4 @@ -import { - type Feature, - type GeoJsonProperties, - type Geometry, - type Position, -} from 'geojson' +import { type Feature, type GeoJsonProperties, type Position } from 'geojson' import { type AvailableRoom } from './utils' @@ -15,8 +10,8 @@ export enum SEARCH_TYPES { export interface RoomData { title: string subtitle: string - properties: GeoJsonProperties - occupancies: AvailableRoom | BuildingOccupancy + properties: GeoJsonProperties | undefined + occupancies: AvailableRoom | BuildingOccupancy | null type: SEARCH_TYPES } @@ -35,5 +30,5 @@ export interface SearchResult { title: string subtitle: string isExactMatch?: boolean - item: Feature + item: Feature } diff --git a/src/types/thi-api.ts b/src/types/thi-api.ts index 031ee05f..7c3e0f2f 100644 --- a/src/types/thi-api.ts +++ b/src/types/thi-api.ts @@ -11,6 +11,10 @@ export interface Announcements { announcement: string } +export interface TypeStunde extends Stunde { + type: Raumtyp +} + export interface SessionClose { date: string time: string @@ -141,7 +145,14 @@ export interface Rooms { export interface Rtype { raumtyp: Raumtyp - stunden: Stunden + stunden: Stunde[] +} + +export interface Stunde { + type: Raumtyp + raeume: [string, string, number, number][] + von: string + bis: string } export enum Raumtyp { @@ -150,8 +161,6 @@ export enum Raumtyp { Seminarraum40Plätze = 'Seminarraum (< 40 Plätze)', } -export type Stunden = Record - export interface RoomsStatus { von: Date bis: Date diff --git a/src/types/utils.ts b/src/types/utils.ts index 364777dd..10ed4565 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -12,6 +12,10 @@ export interface Exam { aids: string[] } +export interface ExamEntry extends Exam { + eventType: 'exam' +} + export interface FriendlyDateOptions { weekday?: 'short' | 'long' relative?: boolean @@ -74,6 +78,9 @@ export interface FriendlyTimetableEntry { literature: string | null } +export interface TimetableEntry extends FriendlyTimetableEntry { + eventType: 'timetable' +} export interface CalendarTimetableEntry extends FriendlyTimetableEntry { eventType: string color: string @@ -92,7 +99,7 @@ export interface ExamTimetableEntry extends Exam { export interface TimetableSections { title: Date - data: FriendlyTimetableEntry[] | Exam[] + data: TimetableEntry[] | ExamEntry[] } export interface CalendarEvent { diff --git a/src/utils/animation-utils.ts b/src/utils/animation-utils.ts index 7955ea68..b9a9cdfc 100644 --- a/src/utils/animation-utils.ts +++ b/src/utils/animation-utils.ts @@ -35,7 +35,10 @@ export function withBouncing( bottomBound: number, topBound: number, randomizeColor: () => void -): { onFrame: (state: AnimationState, now: number) => boolean; onStart: (state: AnimationState, _value: number, now: number) => void } { +): { + onFrame: (state: AnimationState, now: number) => boolean + onStart: (state: AnimationState, _value: number, now: number) => void +} { 'worklet' return defineAnimation( { @@ -43,7 +46,14 @@ export function withBouncing( lastTimestamp: Date.now(), direction: 1, }, - (): { onFrame: (state: AnimationState, now: number) => boolean; onStart: (state: AnimationState, _value: number, now: number) => void } => { + (): { + onFrame: (state: AnimationState, now: number) => boolean + onStart: ( + state: AnimationState, + _value: number, + now: number + ) => void + } => { 'worklet' const onFrame = (state: AnimationState, now: number): boolean => { const delta = (now - state.lastTimestamp) / 1000 diff --git a/src/utils/app-utils.ts b/src/utils/app-utils.ts index 113e2fed..14e1800f 100644 --- a/src/utils/app-utils.ts +++ b/src/utils/app-utils.ts @@ -1,4 +1,5 @@ import { + SecurityLevel, authenticateAsync, getEnrolledLevelAsync, } from 'expo-local-authentication' @@ -37,7 +38,7 @@ export function lowercaseFirstLetter(string: string): string { * @param arr2 - The second array. * @returns A boolean indicating whether the arrays are equal. */ -export function arraysEqual(arr1: any[], arr2: any[]): boolean { +export function arraysEqual(arr1: unknown[], arr2: unknown[]): boolean { if (arr1.length !== arr2.length) return false for (let i = 0; i < arr1.length; i++) { if (arr1[i] !== arr2[i]) return false @@ -51,7 +52,7 @@ export function arraysEqual(arr1: any[], arr2: any[]): boolean { */ export const handleBiometricAuth = async (path: string): Promise => { const securityLevel = await getEnrolledLevelAsync() - if (securityLevel === 0) { + if (securityLevel === SecurityLevel.NONE) { // no passcode or biometric auth set up router.navigate(path as RelativePathString) return diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts index e2846e5b..c755dedc 100644 --- a/src/utils/date-utils.ts +++ b/src/utils/date-utils.ts @@ -1,8 +1,10 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import i18n from '@/localization/i18n' import { type FriendlyDateOptions } from '@/types/utils' import moment from 'moment' import 'moment/locale/de' +// eslint-disable-next-line @typescript-eslint/no-explicit-any function t(...args: any): any { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return i18n.t(args, { ns: 'common' }) @@ -204,7 +206,7 @@ export function formatRelativeMinutes(datetime: Date | string): string { Math.floor((datetime.getTime() - Date.now()) / 60000), 0 ) - return `${minutes} min` + return `${minutes.toString()} min` } /** diff --git a/src/utils/food-utils.ts b/src/utils/food-utils.ts index 590ba565..70e80831 100644 --- a/src/utils/food-utils.ts +++ b/src/utils/food-utils.ts @@ -47,6 +47,7 @@ export async function loadFoodEntries( const isoDates = days.map((x) => formatISODate(x)) return isoDates.map((day) => { const dayEntries: Meal[] = data.flatMap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access (r: any) => r.find((x: Food) => x.timestamp === day)?.meals ?? [] ) as Meal[] // remove static meals if includeStatic is false. otherwise return all meals diff --git a/src/utils/map-utils.ts b/src/utils/map-utils.ts index deaa6b42..64a84f71 100644 --- a/src/utils/map-utils.ts +++ b/src/utils/map-utils.ts @@ -1,6 +1,6 @@ import { SEARCH_TYPES } from '@/types/map' import { type MaterialIcon } from '@/types/material-icons' -import { type Rooms } from '@/types/thi-api' +import { type Rooms, type TypeStunde } from '@/types/thi-api' import { type AvailableRoom } from '@/types/utils' import { trackEvent } from '@aptabase/react-native' import { type GeoJsonProperties, type Position } from 'geojson' @@ -127,19 +127,19 @@ export function getRoomOpenings(rooms: Rooms[], date: Date): RoomOpenings { // flatten time slots .flatMap((rtype) => Object.values(rtype.stunden).map((stunde) => ({ - type: rtype.raumtyp, ...stunde, + type: rtype.raumtyp, })) ) // flatten room list - .flatMap((stunde: any) => + .flatMap((stunde: TypeStunde) => stunde.raeume.map( ([, , room, capacity]: [string, string, number, number]) => ({ // 0 indicates that every room is free room: room === 0 ? ROOMS_ALL : room.toString(), - type: stunde.type.replace(/ \(.*\)$/, '').trim() ?? '', - from: new Date(stunde.von as string), - until: new Date(stunde.bis as string), + type: stunde.type.replace(/ \(.*\)$/, '').trim(), + from: new Date(stunde.von), + until: new Date(stunde.bis), capacity, }) ) @@ -219,13 +219,13 @@ export function getNextValidDate(): { startDate: Date; wasModified: boolean } { * @param {string} [duration] Minimum opening duration * @returns {Promise} */ -export async function filterRooms( +export function filterRooms( data: Rooms[], date: string, time: string, building: string = BUILDINGS_ALL, duration: string = DURATION_PRESET -): Promise { +): AvailableRoom[] { const beginDate = new Date(date + 'T' + time) const [durationHours, durationMinutes] = duration .split(':') @@ -239,7 +239,7 @@ export async function filterRooms( beginDate.getSeconds(), beginDate.getMilliseconds() ) - return await searchRooms(data, beginDate, endDate, building) + return searchRooms(data, beginDate, endDate, building) } /** @@ -249,12 +249,12 @@ export async function filterRooms( * @param {string} [building] Building name (e.g. `G`), defaults to all buildings * @returns {Promise} */ -export async function searchRooms( +export function searchRooms( data: Rooms[], beginDate: Date, endDate: Date, building: string = BUILDINGS_ALL -): Promise { +): AvailableRoom[] { const openings = getRoomOpenings(data, beginDate) return Object.keys(openings) .flatMap((room) => @@ -283,12 +283,12 @@ export async function searchRooms( */ export function getCenter(rooms: Position[][][]): Position { const getCenterPoint = (points: Position[][]): Position[] => { - const x = points[0].map((point: any) => point[0]) - const y = points[0].map((point: any) => point[1]) - const minX = Math.min(...(x as number[])) - const maxX = Math.max(...(x as number[])) - const minY = Math.min(...(y as number[])) - const maxY = Math.max(...(y as number[])) + const x = points[0].map((point: Position) => point[0]) + const y = points[0].map((point: Position) => point[1]) + const minX = Math.min(...x) + const maxX = Math.max(...x) + const minY = Math.min(...y) + const maxY = Math.max(...y) return [(minX + maxX) / 2, (minY + maxY) / 2] as unknown as Position[] } @@ -381,11 +381,8 @@ export const getIcon = ( const { Funktion_en: funktionEn, Raum: raum, - }: { Funktion_en: string; Raum: string } = (properties?.result.item - .properties as { Funktion_en: string; Raum: string }) ?? { - Funktion_en: '', - Raum: '', - } + }: { Funktion_en: string; Raum: string } = properties?.result.item + .properties as { Funktion_en: string; Raum: string } const food = ['M001', 'X001', 'F001'] switch (type) { case SEARCH_TYPES.BUILDING: diff --git a/src/utils/timetable-utils.ts b/src/utils/timetable-utils.ts index 09b0217f..4b882b8d 100644 --- a/src/utils/timetable-utils.ts +++ b/src/utils/timetable-utils.ts @@ -122,7 +122,7 @@ export function getGroupedTimetable( exams: Exam[] ): TimetableSections[] { const combinedData = [ - ...timetable, + ...timetable.map((lecture) => ({ ...lecture, eventType: 'timetable' })), ...exams.map((exam) => ({ ...exam, eventType: 'exam' })), ] const dates = [ @@ -169,7 +169,7 @@ export function generateKey( startDate: Date | string, room: string ): string { - return `${lectureName}-${new Date(startDate).getTime()}-${room}` + return `${lectureName}-${new Date(startDate).getTime().toString()}-${room}` } // This function checks if a given room string is valid based on the following criteria: