p-stream/src/setup/App.tsx
Pas f2b39b046c Add support bar and donation modal to homepage
Introduces a support bar component on the homepage to display project funding progress and encourage donations. Adds a modal with more information about supporting the project. Updates configuration to allow toggling the support bar and setting funding values. Updates links to the new donation page and adds related translations.
2025-12-09 12:04:46 -07:00

230 lines
8.2 KiB
TypeScript

import { ReactElement, Suspense, lazy, useEffect, useState } from "react";
import { lazyWithPreload } from "react-lazy-with-preload";
import {
Navigate,
Route,
Routes,
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
import { convertLegacyUrl, isLegacyUrl } from "@/backend/metadata/getmeta";
import { generateQuickSearchMediaUrl } from "@/backend/metadata/tmdb";
import { DetailsModal } from "@/components/overlays/detailsModal";
import { KeyboardCommandsModal } from "@/components/overlays/KeyboardCommandsModal";
import { NotificationModal } from "@/components/overlays/notificationsModal";
import { SupportInfoModal } from "@/components/overlays/SupportInfoModal";
import { useGlobalKeyboardEvents } from "@/hooks/useGlobalKeyboardEvents";
import { useOnlineListener } from "@/hooks/usePing";
import { AboutPage } from "@/pages/About";
import { AdminPage } from "@/pages/admin/AdminPage";
import { AllBookmarks } from "@/pages/bookmarks/AllBookmarks";
import VideoTesterView from "@/pages/developer/VideoTesterView";
import { DiscoverMore } from "@/pages/discover/AllMovieLists";
import { Discover } from "@/pages/discover/Discover";
import { MoreContent } from "@/pages/discover/MoreContent";
import MaintenancePage from "@/pages/errors/MaintenancePage";
import { NotFoundPage } from "@/pages/errors/NotFoundPage";
import { HomePage } from "@/pages/HomePage";
import { JipPage } from "@/pages/Jip";
import { LegalPage, shouldHaveLegalPage } from "@/pages/Legal";
import { LoginPage } from "@/pages/Login";
import { MigrationPage } from "@/pages/migration/Migration";
import { MigrationDirectPage } from "@/pages/migration/MigrationDirect";
import { MigrationDownloadPage } from "@/pages/migration/MigrationDownload";
import { MigrationUploadPage } from "@/pages/migration/MigrationUpload";
import { OnboardingPage } from "@/pages/onboarding/Onboarding";
import { OnboardingExtensionPage } from "@/pages/onboarding/OnboardingExtension";
import { OnboardingProxyPage } from "@/pages/onboarding/OnboardingProxy";
import { RegisterPage } from "@/pages/Register";
import { SupportPage } from "@/pages/Support";
import { Layout } from "@/setup/Layout";
import { useHistoryListener } from "@/stores/history";
import { useClearModalsOnNavigation } from "@/stores/interface/overlayStack";
import { LanguageProvider } from "@/stores/language";
const DeveloperPage = lazy(() => import("@/pages/DeveloperPage"));
const TestView = lazy(() => import("@/pages/developer/TestView"));
const PlayerView = lazyWithPreload(() => import("@/pages/PlayerView"));
const SettingsPage = lazyWithPreload(() => import("@/pages/Settings"));
PlayerView.preload();
SettingsPage.preload();
function LegacyUrlView({ children }: { children: ReactElement }) {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
const url = location.pathname;
if (!isLegacyUrl(url)) return;
convertLegacyUrl(location.pathname).then((convertedUrl) => {
navigate(convertedUrl ?? "/", { replace: true });
});
}, [location.pathname, navigate]);
if (isLegacyUrl(location.pathname)) return null;
return children;
}
function QuickSearch() {
const { query } = useParams<{ query: string }>();
const navigate = useNavigate();
useEffect(() => {
if (query) {
generateQuickSearchMediaUrl(query).then((url) => {
navigate(url ?? "/", { replace: true });
});
} else {
navigate("/", { replace: true });
}
}, [query, navigate]);
return null;
}
function QueryView() {
const { query } = useParams<{ query: string }>();
const navigate = useNavigate();
useEffect(() => {
if (query) {
navigate(`/browse/${encodeURIComponent(query)}`, { replace: true });
} else {
navigate("/", { replace: true });
}
}, [query, navigate]);
return null;
}
export const maintenanceTime = "March 31th 11:00 PM - 5:00 AM EST";
function App() {
useHistoryListener();
useOnlineListener();
useGlobalKeyboardEvents();
useClearModalsOnNavigation();
const maintenance = false; // Shows maintance page
const [showDowntime, setShowDowntime] = useState(maintenance);
const handleButtonClick = () => {
setShowDowntime(false);
};
useEffect(() => {
const sessionToken = sessionStorage.getItem("downtimeToken");
if (!sessionToken && maintenance) {
setShowDowntime(true);
sessionStorage.setItem("downtimeToken", "true");
}
}, [setShowDowntime, maintenance]);
return (
<Layout>
<LanguageProvider />
<NotificationModal id="notifications" />
<KeyboardCommandsModal id="keyboard-commands" />
<SupportInfoModal id="support-info" />
<DetailsModal id="details" />
<DetailsModal id="discover-details" />
<DetailsModal id="player-details" />
{!showDowntime && (
<Routes>
{/* functional routes */}
<Route path="/s/:query" element={<QuickSearch />} />
<Route path="/search/:type" element={<Navigate to="/browse" />} />
<Route path="/search/:type/:query?" element={<QueryView />} />
{/* pages */}
<Route
path="/media/:media"
element={
<LegacyUrlView>
<Suspense fallback={null}>
<PlayerView />
</Suspense>
</LegacyUrlView>
}
/>
<Route
path="/media/:media/:season/:episode"
element={
<LegacyUrlView>
<Suspense fallback={null}>
<PlayerView />
</Suspense>
</LegacyUrlView>
}
/>
<Route path="/browse/:query?" element={<HomePage />} />
<Route path="/" element={<HomePage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/onboarding" element={<OnboardingPage />} />
<Route
path="/onboarding/extension"
element={<OnboardingExtensionPage />}
/>
<Route path="/onboarding/proxy" element={<OnboardingProxyPage />} />
{/* Migration pages - awaiting import and export fixes */}
<Route path="/migration" element={<MigrationPage />} />
<Route path="/migration/direct" element={<MigrationDirectPage />} />
<Route
path="/migration/download"
element={<MigrationDownloadPage />}
/>
<Route path="/migration/upload" element={<MigrationUploadPage />} />
{shouldHaveLegalPage() ? (
<Route path="/legal" element={<LegalPage />} />
) : null}
{/* Support page */}
<Route path="/support" element={<SupportPage />} />
<Route path="/jip" element={<JipPage />} />
{/* Discover pages */}
<Route path="/discover" element={<Discover />} />
<Route
path="/discover/more/:contentType/:mediaType"
element={<MoreContent />}
/>
<Route
path="/discover/more/:contentType/:id/:mediaType"
element={<MoreContent />}
/>
<Route path="/discover/more/:category" element={<MoreContent />} />
<Route path="/discover/all" element={<DiscoverMore />} />
{/* Bookmarks page */}
<Route path="/bookmarks" element={<AllBookmarks />} />
{/* Settings page */}
<Route
path="/settings"
element={
<Suspense fallback={null}>
<SettingsPage />
</Suspense>
}
/>
{/* admin routes */}
<Route path="/admin" element={<AdminPage />} />
{/* other */}
<Route path="/dev" element={<DeveloperPage />} />
<Route path="/dev/video" element={<VideoTesterView />} />
{/* developer routes that can abuse workers are disabled in production */}
{process.env.NODE_ENV === "development" ? (
<Route path="/dev/test" element={<TestView />} />
) : null}
<Route path="*" element={<NotFoundPage />} />
</Routes>
)}
{showDowntime && (
<MaintenancePage onHomeButtonClick={handleButtonClick} />
)}
</Layout>
);
}
export default App;