feat: add AuthContext, RequireAuth guard, and Darkwatch app routing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Wood 2026-04-11 00:36:38 -04:00
parent 11714ecbe4
commit 075a9c5505
6 changed files with 105 additions and 10 deletions

View file

@ -1,22 +1,53 @@
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import RequireAuth from "./components/RequireAuth";
import CampaignList from "./pages/CampaignList";
import CampaignView from "./pages/CampaignView";
import LoginPage from "./pages/LoginPage";
import RegisterPage from "./pages/RegisterPage";
import JoinPage from "./pages/JoinPage";
import ThemeToggle from "./components/ThemeToggle";
import styles from "./App.module.css";
export default function App() {
return (
<BrowserRouter>
<div className={styles.app}>
<header className={styles.header}>
<h1>Shadowdark</h1>
<ThemeToggle />
</header>
<Routes>
<Route path="/" element={<CampaignList />} />
<Route path="/campaign/:id" element={<CampaignView />} />
</Routes>
</div>
<AuthProvider>
<div className={styles.app}>
<header className={styles.header}>
<h1>Darkwatch</h1>
<ThemeToggle />
</header>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route
path="/join/:token"
element={
<RequireAuth>
<JoinPage />
</RequireAuth>
}
/>
<Route
path="/"
element={
<RequireAuth>
<CampaignList />
</RequireAuth>
}
/>
<Route
path="/campaign/:id"
element={
<RequireAuth>
<CampaignView />
</RequireAuth>
}
/>
</Routes>
</div>
</AuthProvider>
</BrowserRouter>
);
}

View file

@ -0,0 +1,10 @@
import { Navigate } from "react-router-dom";
import type { ReactNode } from "react";
import { useAuth } from "../context/AuthContext";
export default function RequireAuth({ children }: { children: ReactNode }) {
const { user, loading } = useAuth();
if (loading) return null;
if (!user) return <Navigate to="/login" replace />;
return <>{children}</>;
}

View file

@ -0,0 +1,45 @@
import { createContext, useContext, useEffect, useState } from "react";
import type { ReactNode } from "react";
import { getMe, logout as apiLogout } from "../api";
import type { AuthUser } from "../api";
interface AuthContextValue {
user: AuthUser | null;
loading: boolean;
setUser: (user: AuthUser | null) => void;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextValue>({
user: null,
loading: true,
setUser: () => {},
logout: async () => {},
});
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
getMe()
.then(setUser)
.catch(() => setUser(null))
.finally(() => setLoading(false));
}, []);
async function logout() {
await apiLogout().catch(() => {});
setUser(null);
}
return (
<AuthContext.Provider value={{ user, loading, setUser, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}

View file

@ -0,0 +1,3 @@
export default function JoinPage() {
return <div>Join</div>;
}

View file

@ -0,0 +1,3 @@
export default function LoginPage() {
return <div>Login</div>;
}

View file

@ -0,0 +1,3 @@
export default function RegisterPage() {
return <div>Register</div>;
}