rebase
This commit is contained in:
@@ -1,102 +1,160 @@
|
||||
import { useEffect } from "react";
|
||||
import { Routes, Route, Navigate } from "react-router-dom";
|
||||
import { useAuthStore } from "@/store/auth";
|
||||
import { useUiStore } from "@/store/ui";
|
||||
import {
|
||||
BrowserRouter,
|
||||
Routes,
|
||||
Route,
|
||||
Navigate,
|
||||
useLocation,
|
||||
} from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
import { Navbar } from "@/components/ui/Navbar";
|
||||
import { HomePage } from "@/pages/HomePage";
|
||||
import { ToastViewport } from "@/components/ui/Toast";
|
||||
import { ProtectedRoute } from "@/components/auth/ProtectedRoute";
|
||||
import { LoginPage } from "@/pages/LoginPage";
|
||||
import { RegisterPage } from "@/pages/RegisterPage";
|
||||
import { AdminSetupPage } from "@/pages/AdminSetupPage";
|
||||
import { DashboardPage } from "@/pages/DashboardPage";
|
||||
import { WorldCreatePage } from "@/pages/WorldCreatePage";
|
||||
import { AdminRegisterPage } from "@/pages/AdminRegisterPage";
|
||||
import { WorldsListPage } from "@/pages/WorldsListPage";
|
||||
import { WorldBuilderPage } from "@/pages/WorldBuilderPage";
|
||||
import { WorldEditPage } from "@/pages/WorldEditPage";
|
||||
import { SessionPage } from "@/pages/SessionPage";
|
||||
import { AdminPanelPage } from "@/pages/AdminPanelPage";
|
||||
|
||||
function PrivateRoute({ children }: { children: JSX.Element }) {
|
||||
const token = useAuthStore((s) => s.token);
|
||||
if (!token) return <Navigate to="/login" replace />;
|
||||
return children;
|
||||
}
|
||||
|
||||
function AdminRoute({ children }: { children: JSX.Element }) {
|
||||
const { token, user } = useAuthStore();
|
||||
if (!token) return <Navigate to="/login" replace />;
|
||||
if (!user?.is_admin) return <Navigate to="/dashboard" replace />;
|
||||
return children;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
// Load public UI settings (logo URL, etc.) once on app boot. These are
|
||||
// unauthenticated and cached by the api layer, so subsequent navigations
|
||||
// do not re-fetch. The admin panel calls load(true) after saving to
|
||||
// pick up a new logo URL without a full page reload.
|
||||
const loadUi = useUiStore((s) => s.load);
|
||||
useEffect(() => {
|
||||
loadUi();
|
||||
}, [loadUi]);
|
||||
import { PlayPage } from "@/pages/PlayPage";
|
||||
import { AdminPage } from "@/pages/AdminPage";
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<div className="min-h-screen bg-bg text-fg">
|
||||
<Navbar />
|
||||
<main className="flex-1">
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
<Route path="/admin/setup" element={<AdminSetupPage />} />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<DashboardPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/worlds/new"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<WorldCreatePage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/worlds/:id/edit"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<WorldEditPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/worlds/builder"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<WorldBuilderPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/sessions/:id"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<SessionPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<AdminRoute>
|
||||
<AdminPanelPage />
|
||||
</AdminRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</main>
|
||||
{children}
|
||||
<ToastViewport />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RootRedirect() {
|
||||
const status = useAuthStore((s) => s.status);
|
||||
const user = useAuthStore((s) => s.user);
|
||||
if (status === "authenticated" && user) return <Navigate to="/worlds" replace />;
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
function PublicOnly({ children }: { children: React.ReactNode }) {
|
||||
const status = useAuthStore((s) => s.status);
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const location = useLocation();
|
||||
if (status === "authenticated" && user) {
|
||||
const from = (location.state as { from?: string } | null)?.from;
|
||||
return <Navigate to={from || "/worlds"} replace />;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function ScrollToTop() {
|
||||
const { pathname } = useLocation();
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [pathname]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
document.title = t("common.app_name");
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ScrollToTop />
|
||||
<Routes>
|
||||
<Route path="/" element={<RootRedirect />} />
|
||||
|
||||
{/* Public auth routes — full layout but no navbar redirect */}
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<PublicOnly>
|
||||
<Layout>
|
||||
<LoginPage />
|
||||
</Layout>
|
||||
</PublicOnly>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/register"
|
||||
element={
|
||||
<PublicOnly>
|
||||
<Layout>
|
||||
<RegisterPage />
|
||||
</Layout>
|
||||
</PublicOnly>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/register/admin"
|
||||
element={
|
||||
<PublicOnly>
|
||||
<Layout>
|
||||
<AdminRegisterPage />
|
||||
</Layout>
|
||||
</PublicOnly>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Protected routes */}
|
||||
<Route
|
||||
path="/worlds"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Layout>
|
||||
<WorldsListPage />
|
||||
</Layout>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/worlds/new"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Layout>
|
||||
<WorldBuilderPage />
|
||||
</Layout>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/worlds/:id/edit"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Layout>
|
||||
<WorldEditPage />
|
||||
</Layout>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/worlds/:id/play"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Layout>
|
||||
<PlayPage />
|
||||
</Layout>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<ProtectedRoute requireAdmin>
|
||||
<Layout>
|
||||
<AdminPage />
|
||||
</Layout>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Fallback */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user