v3: added site-wide categories, added admin category management, various visual fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m52s

This commit is contained in:
khannurien
2026-06-28 16:23:44 +00:00
parent c8c7b05c25
commit fae25f3e6c
44 changed files with 2144 additions and 399 deletions

View File

@@ -1,5 +1,11 @@
import { lazy, Suspense } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router";
import {
BrowserRouter,
Navigate,
Route,
Routes,
useParams,
} from "react-router";
import { RestrictedGuest } from "./pages/RestrictedGuest.tsx";
import { RestrictedLoggedIn } from "./pages/RestrictedLoggedIn.tsx";
@@ -10,6 +16,7 @@ import { AuthProvider } from "./contexts/AuthProvider.tsx";
import { PlayerProvider } from "./contexts/PlayerProvider.tsx";
import { WSProvider } from "./contexts/WSProvider.tsx";
import { FollowProvider } from "./contexts/FollowProvider.tsx";
import { CategoriesProvider } from "./contexts/CategoriesProvider.tsx";
import { ThemeProvider } from "./contexts/ThemeProvider.tsx";
import { GlobalPlayer } from "./components/GlobalPlayer.tsx";
import { DumpFab } from "./components/DumpFab.tsx";
@@ -69,12 +76,29 @@ const NotFound = lazy(() =>
import("./pages/NotFound.tsx").then((m) => ({ default: m.NotFound }))
);
function IndexRedirect() {
// Default feed tab for the current viewer ("followed" needs a session).
function useResolvedDefaultTab() {
const { user } = useAuth();
const [preferredTab] = useDefaultFeedTab();
// "followed" requires a session, so fall back to "hot" for guests.
const tab = preferredTab === "followed" && !user ? "hot" : preferredTab;
return <Navigate to={`/~/${tab}`} replace />;
return preferredTab === "followed" && !user ? "hot" : preferredTab;
}
function IndexRedirect() {
return <Navigate to={`/~/${useResolvedDefaultTab()}`} replace />;
}
// Bare `/<slug>` lands on that category's default feed tab.
function CategoryRedirect() {
const { categorySlug } = useParams();
return <Navigate to={`/${categorySlug}/${useResolvedDefaultTab()}`} replace />;
}
// Both `/~/:feedTab` (all) and `/:categorySlug/:feedTab` render the same feed.
// Keying by scope remounts the feed when switching categories so it refetches,
// while switching tabs within a scope keeps it mounted (client-side re-sort).
function ScopedIndex() {
const { categorySlug } = useParams();
return <Index key={categorySlug ?? "~"} categorySlug={categorySlug} />;
}
function AppRoutes() {
@@ -83,7 +107,7 @@ function AppRoutes() {
<Suspense>
<Routes>
<Route path="/" element={<IndexRedirect />} />
<Route path="/~/:feedTab" element={<Index />} />
<Route path="/~/:feedTab" element={<ScopedIndex />} />
<Route path="/dumps/:selectedDump" element={<Dump />} />
<Route
path="/dumps/:selectedDump/edit"
@@ -132,6 +156,8 @@ function AppRoutes() {
</RestrictedLoggedIn>
}
/>
<Route path="/:categorySlug/:feedTab" element={<ScopedIndex />} />
<Route path="/:categorySlug" element={<CategoryRedirect />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
@@ -147,9 +173,11 @@ function App() {
<PlayerProvider>
<WSProvider>
<FollowProvider>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
<CategoriesProvider>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</CategoriesProvider>
</FollowProvider>
</WSProvider>
<GlobalPlayer />