import { i18n } from "@lingui/core" import { I18nProvider } from "@lingui/react" import { MantineProvider } from "@mantine/core" import { ModalsProvider } from "@mantine/modals" import { Notifications } from "@mantine/notifications" import { Constants } from "app/constants" import { redirectTo } from "app/redirect/slice" import { reloadServerInfos } from "app/server/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { categoryUnreadCount } from "app/utils" import { DisablePullToRefresh } from "components/DisablePullToRefresh" import { ErrorBoundary } from "components/ErrorBoundary" import { Header } from "components/header/Header" import { Tree } from "components/sidebar/Tree" import { useBrowserExtension } from "hooks/useBrowserExtension" import { useI18n } from "i18n" import { WelcomePage } from "pages/WelcomePage" import { AdminUsersPage } from "pages/admin/AdminUsersPage" import { MetricsPage } from "pages/admin/MetricsPage" import { AboutPage } from "pages/app/AboutPage" import { AddPage } from "pages/app/AddPage" import { CategoryDetailsPage } from "pages/app/CategoryDetailsPage" import { DonatePage } from "pages/app/DonatePage" import { FeedDetailsPage } from "pages/app/FeedDetailsPage" import { FeedEntriesPage } from "pages/app/FeedEntriesPage" import Layout from "pages/app/Layout" import { SettingsPage } from "pages/app/SettingsPage" import { TagDetailsPage } from "pages/app/TagDetailsPage" import { LoginPage } from "pages/auth/LoginPage" import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage" import { RegistrationPage } from "pages/auth/RegistrationPage" import React, { useEffect } from "react" import { isSafari } from "react-device-detect" import ReactGA from "react-ga4" import { Helmet } from "react-helmet" import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom" import Tinycon from "tinycon" function Providers(props: { children: React.ReactNode }) { return ( {props.children} ) } // swagger-ui is very large, load only on-demand const ApiDocumentationPage = React.lazy(async () => await import("pages/app/ApiDocumentationPage")) function AppRoutes() { const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible) return ( } /> } /> } /> } /> } /> } /> } sidebar={} sidebarVisible={sidebarVisible} />}> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ) } function RedirectHandler() { const target = useAppSelector(state => state.redirect.to) const dispatch = useAppDispatch() const navigate = useNavigate() useEffect(() => { if (target) { // pages can subscribe to state.timestamp in order to refresh when navigating to an url matching the current page navigate(target, { state: { timestamp: new Date() } }) dispatch(redirectTo(undefined)) } }, [target, dispatch, navigate]) return null } function GoogleAnalyticsHandler() { const location = useLocation() const googleAnalyticsCode = useAppSelector(state => state.server.serverInfos?.googleAnalyticsCode) useEffect(() => { if (googleAnalyticsCode) ReactGA.initialize(googleAnalyticsCode) }, [googleAnalyticsCode]) useEffect(() => { if (ReactGA.isInitialized) ReactGA.send({ hitType: "pageview", page: location.pathname }) }, [location]) return null } function FaviconHandler() { const root = useAppSelector(state => state.tree.rootCategory) useEffect(() => { const unreadCount = categoryUnreadCount(root) if (unreadCount === 0) { Tinycon.reset() } else { Tinycon.setBubble(unreadCount) } }, [root]) return null } function BrowserExtensionBadgeUnreadCountHandler() { const root = useAppSelector(state => state.tree.rootCategory) const { setBadgeUnreadCount } = useBrowserExtension() useEffect(() => { if (!root) return const unreadCount = categoryUnreadCount(root) setBadgeUnreadCount(unreadCount) }, [root, setBadgeUnreadCount]) return null } function CustomCode() { return (