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 type React from "react" import { useEffect, useState } from "react" import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom" import Tinycon from "tinycon" 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 { useAppLoading } from "@/hooks/useAppLoading" 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" function Providers( props: Readonly<{ children: React.ReactNode }> ) { const primaryColor = useAppSelector(state => state.user.settings?.primaryColor) || Constants.theme.defaultPrimaryColor return ( {props.children} ) } 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 UnreadCountTitleHandler({ enabled, }: Readonly<{ enabled?: boolean }>) { const root = useAppSelector(state => state.tree.rootCategory) const unreadCount = categoryUnreadCount(root) return {enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"} } function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) { const root = useAppSelector(state => state.tree.rootCategory) const unreadCount = categoryUnreadCount(root) useEffect(() => { if (enabled && unreadCount > 0) { Tinycon.setBubble(unreadCount) } else { Tinycon.reset() } }, [unreadCount, enabled]) 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 CustomJsHandler() { const [scriptLoaded, setScriptLoaded] = useState(false) const { loading } = useAppLoading() useEffect(() => { if (scriptLoaded || loading) { return } const script = document.createElement("script") script.src = "custom_js.js" script.async = true document.body.appendChild(script) setScriptLoaded(true) }, [scriptLoaded, loading]) return null } function CustomCssHandler() { 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 unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle) const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon) const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh) const dispatch = useAppDispatch() useEffect(() => { dispatch(reloadServerInfos()) }, [dispatch]) return ( ) }