import { i18n } from "@lingui/core" import { I18nProvider } from "@lingui/react" import { MantineProvider } from "@mantine/core" import { useDidUpdate } from "@mantine/hooks" 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 { 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 { 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 { WelcomePage } from "pages/WelcomePage" import React, { useEffect, useRef } from "react" import ReactGA from "react-ga4" 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 CustomJs() { const scriptLoaded = useRef(false) // useDidUpdate is used instead of useEffect because we want to skip the first render // the first render is the render of react-router, the routes are actually loaded in a second render // we want the script to be executed when the first route is done loading useDidUpdate(() => { if (scriptLoaded.current) { return } const script = document.createElement("script") script.src = "custom_js.js" script.async = true document.body.appendChild(script) scriptLoaded.current = true }) return null } function CustomCss() { useEffect(() => { const link = document.createElement("link") link.rel = "stylesheet" link.type = "text/css" link.href = "custom_css.css" document.head.appendChild(link) }, []) return null } export function App() { useI18n() const dispatch = useAppDispatch() useEffect(() => { dispatch(reloadServerInfos()) }, [dispatch]) return ( <> ) }