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

208 lines
7.9 KiB
TypeScript
Raw Normal View History

import { i18n } from "@lingui/core"
import { I18nProvider } from "@lingui/react"
2023-12-29 23:09:30 +01:00
import { MantineProvider } from "@mantine/core"
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/redirect/slice"
import { reloadServerInfos } from "app/server/thunks"
import { useAppDispatch, useAppSelector } from "app/store"
import { categoryUnreadCount } from "app/utils"
import { DisablePullToRefresh } from "components/DisablePullToRefresh"
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 { WelcomePage } from "pages/WelcomePage"
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"
import React, { useEffect } from "react"
import { isSafari } from "react-device-detect"
2023-05-03 20:29:12 +02:00
import ReactGA from "react-ga4"
import { Helmet } from "react-helmet"
2023-05-03 20:29:12 +02:00
import { HashRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"
import Tinycon from "tinycon"
function Providers(props: { children: React.ReactNode }) {
return (
<I18nProvider i18n={i18n}>
2023-12-29 23:09:30 +01:00
<MantineProvider
defaultColorScheme="auto"
2023-12-29 23:09:30 +01:00
theme={{
primaryColor: "orange",
fontFamily: "Open Sans",
colors: {
// keep using dark colors from mantine v6
// https://v6.mantine.dev/theming/colors/#default-colors
dark: [
"#C1C2C5",
"#A6A7AB",
"#909296",
"#5c5f66",
"#373A40",
"#2C2E33",
"#25262b",
"#1A1B1E",
"#141517",
"#101113",
],
},
}}
>
<ModalsProvider>
<Notifications position="bottom-right" zIndex={9999} />
<ErrorBoundary>{props.children}</ErrorBoundary>
</ModalsProvider>
</MantineProvider>
</I18nProvider>
)
}
2022-08-22 14:49:24 +02:00
// swagger-ui is very large, load only on-demand
2023-12-28 19:54:51 +01:00
const ApiDocumentationPage = React.lazy(async () => await import("pages/app/ApiDocumentationPage"))
2022-08-22 14:49:24 +02:00
function AppRoutes() {
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 />} sidebarVisible={sidebarVisible} />}>
<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
}
function CustomCode() {
return (
<Helmet>
<link rel="stylesheet" type="text/css" href="custom_css.css" />
<script type="text/javascript" src="custom_js.js" />
</Helmet>
)
}
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 />
<CustomCode />
{/* disable pull-to-refresh as it messes with vertical scrolling
safari behaves weirdly when overscroll-behavior is set to none so we disable it only for other browsers
https://github.com/Athou/commafeed/issues/1168
*/}
{!isSafari && <DisablePullToRefresh />}
</HashRouter>
</>
</Providers>
)
}