Files
commafeed/commafeed-client/src/App.tsx

185 lines
7.3 KiB
TypeScript
Raw Normal View History

import { i18n } from "@lingui/core"
import { I18nProvider } from "@lingui/react"
import { ColorScheme, ColorSchemeProvider, MantineProvider } from "@mantine/core"
import { useColorScheme } from "@mantine/hooks"
import { ModalsProvider } from "@mantine/modals"
2023-05-08 14:54:16 +02:00
import { Notifications } from "@mantine/notifications"
import { Constants } from "app/constants"
import { redirectTo } from "app/slices/redirect"
import { reloadServerInfos } from "app/slices/server"
import { useAppDispatch, useAppSelector } from "app/store"
import { categoryUnreadCount } from "app/utils"
2022-08-14 21:05:22 +02:00
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"
2022-08-14 18:15:40 +02:00
import { MetricsPage } from "pages/admin/MetricsPage"
2022-08-15 13:26:45 +02:00
import { AboutPage } from "pages/app/AboutPage"
import { AddPage } from "pages/app/AddPage"
import { CategoryDetailsPage } from "pages/app/CategoryDetailsPage"
2023-05-26 08:28:23 +02:00
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"
2022-10-25 10:18:50 +02:00
import { TagDetailsPage } from "pages/app/TagDetailsPage"
import { LoginPage } from "pages/auth/LoginPage"
import { PasswordRecoveryPage } from "pages/auth/PasswordRecoveryPage"
import { RegistrationPage } from "pages/auth/RegistrationPage"
2023-05-05 14:55:03 +02:00
import { WelcomePage } from "pages/WelcomePage"
import React, { useEffect } from "react"
2023-05-03 20:29:12 +02:00
import ReactGA from "react-ga4"
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import Tinycon from "tinycon"
2023-05-03 20:29:12 +02:00
import useLocalStorage from "use-local-storage"
function Providers(props: { children: React.ReactNode }) {
const preferredColorScheme = useColorScheme()
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>("color-scheme", preferredColorScheme)
const toggleColorScheme = (value?: ColorScheme) => setColorScheme(value ?? (colorScheme === "dark" ? "light" : "dark"))
return (
<I18nProvider i18n={i18n}>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
primaryColor: "orange",
colorScheme,
fontFamily: "Open Sans",
}}
>
<ModalsProvider>
2023-05-08 14:54:16 +02:00
<Notifications position="bottom-right" zIndex={9999} />
<ErrorBoundary>{props.children}</ErrorBoundary>
</ModalsProvider>
</MantineProvider>
</ColorSchemeProvider>
</I18nProvider>
)
}
2022-08-22 14:49:24 +02:00
// swagger-ui is very large, load only on-demand
const ApiDocumentationPage = React.lazy(() => import("pages/app/ApiDocumentationPage"))
function AppRoutes() {
2023-06-16 21:24:34 +02:00
const sidebarWidth = useAppSelector(state => state.tree.sidebarWidth)
const sidebarVisible = useAppSelector(state => state.tree.sidebarVisible)
return (
<Routes>
2022-08-19 10:34:04 +02:00
<Route path="/" element={<Navigate to={`/app/category/${Constants.categories.all.id}`} replace />} />
2023-05-04 21:27:02 +02:00
<Route path="welcome" element={<WelcomePage />} />
<Route path="login" element={<LoginPage />} />
<Route path="register" element={<RegistrationPage />} />
<Route path="passwordRecovery" element={<PasswordRecoveryPage />} />
<Route path="api" element={<ApiDocumentationPage />} />
<Route path="app" element={<Layout header={<Header />} sidebar={<Tree />} sidebarWidth={sidebarVisible ? sidebarWidth : 0} />}>
<Route path="category">
<Route path=":id" element={<FeedEntriesPage sourceType="category" />} />
<Route path=":id/details" element={<CategoryDetailsPage />} />
</Route>
<Route path="feed">
<Route path=":id" element={<FeedEntriesPage sourceType="feed" />} />
<Route path=":id/details" element={<FeedDetailsPage />} />
</Route>
2022-10-25 10:18:50 +02:00
<Route path="tag">
<Route path=":id" element={<FeedEntriesPage sourceType="tag" />} />
<Route path=":id/details" element={<TagDetailsPage />} />
</Route>
<Route path="add" element={<AddPage />} />
<Route path="settings" element={<SettingsPage />} />
<Route path="admin">
<Route path="users" element={<AdminUsersPage />} />
2022-08-14 18:15:40 +02:00
<Route path="metrics" element={<MetricsPage />} />
</Route>
2022-08-15 13:26:45 +02:00
<Route path="about" element={<AboutPage />} />
2023-05-26 08:28:23 +02:00
<Route path="donate" element={<DonatePage />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
)
}
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
}
2023-05-03 20:29:12 +02:00
function GoogleAnalyticsHandler() {
const location = useLocation()
const googleAnalyticsCode = useAppSelector(state => state.server.serverInfos?.googleAnalyticsCode)
useEffect(() => {
if (googleAnalyticsCode) ReactGA.initialize(googleAnalyticsCode)
}, [googleAnalyticsCode])
useEffect(() => {
2023-05-17 13:43:25 +02:00
if (ReactGA.isInitialized) ReactGA.send({ hitType: "pageview", page: location.pathname })
2023-05-03 20:29:12 +02:00
}, [location])
return null
}
function FaviconHandler() {
const root = useAppSelector(state => state.tree.rootCategory)
useEffect(() => {
const unreadCount = categoryUnreadCount(root)
2023-06-16 21:24:34 +02:00
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
}
export function App() {
useI18n()
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(reloadServerInfos())
}, [dispatch])
return (
<Providers>
<>
<FaviconHandler />
<BrowserExtensionBadgeUnreadCountHandler />
<HashRouter>
2023-05-03 20:29:12 +02:00
<GoogleAnalyticsHandler />
<RedirectHandler />
<AppRoutes />
</HashRouter>
</>
</Providers>
)
}