feat: add AuthContext, RequireAuth guard, and Darkwatch app routing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
11714ecbe4
commit
075a9c5505
6 changed files with 105 additions and 10 deletions
|
|
@ -1,22 +1,53 @@
|
||||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
|
import { AuthProvider } from "./context/AuthContext";
|
||||||
|
import RequireAuth from "./components/RequireAuth";
|
||||||
import CampaignList from "./pages/CampaignList";
|
import CampaignList from "./pages/CampaignList";
|
||||||
import CampaignView from "./pages/CampaignView";
|
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 ThemeToggle from "./components/ThemeToggle";
|
||||||
import styles from "./App.module.css";
|
import styles from "./App.module.css";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className={styles.app}>
|
<AuthProvider>
|
||||||
<header className={styles.header}>
|
<div className={styles.app}>
|
||||||
<h1>Shadowdark</h1>
|
<header className={styles.header}>
|
||||||
<ThemeToggle />
|
<h1>Darkwatch</h1>
|
||||||
</header>
|
<ThemeToggle />
|
||||||
<Routes>
|
</header>
|
||||||
<Route path="/" element={<CampaignList />} />
|
<Routes>
|
||||||
<Route path="/campaign/:id" element={<CampaignView />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
</Routes>
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
</div>
|
<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>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
client/src/components/RequireAuth.tsx
Normal file
10
client/src/components/RequireAuth.tsx
Normal 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}</>;
|
||||||
|
}
|
||||||
45
client/src/context/AuthContext.tsx
Normal file
45
client/src/context/AuthContext.tsx
Normal 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);
|
||||||
|
}
|
||||||
3
client/src/pages/JoinPage.tsx
Normal file
3
client/src/pages/JoinPage.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function JoinPage() {
|
||||||
|
return <div>Join</div>;
|
||||||
|
}
|
||||||
3
client/src/pages/LoginPage.tsx
Normal file
3
client/src/pages/LoginPage.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function LoginPage() {
|
||||||
|
return <div>Login</div>;
|
||||||
|
}
|
||||||
3
client/src/pages/RegisterPage.tsx
Normal file
3
client/src/pages/RegisterPage.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function RegisterPage() {
|
||||||
|
return <div>Register</div>;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue