From 075a9c5505dd40b867d9ed762b9b615a48baf96b Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Sat, 11 Apr 2026 00:36:38 -0400 Subject: [PATCH] feat: add AuthContext, RequireAuth guard, and Darkwatch app routing Co-Authored-By: Claude Sonnet 4.6 --- client/src/App.tsx | 51 +++++++++++++++++++++------ client/src/components/RequireAuth.tsx | 10 ++++++ client/src/context/AuthContext.tsx | 45 +++++++++++++++++++++++ client/src/pages/JoinPage.tsx | 3 ++ client/src/pages/LoginPage.tsx | 3 ++ client/src/pages/RegisterPage.tsx | 3 ++ 6 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 client/src/components/RequireAuth.tsx create mode 100644 client/src/context/AuthContext.tsx create mode 100644 client/src/pages/JoinPage.tsx create mode 100644 client/src/pages/LoginPage.tsx create mode 100644 client/src/pages/RegisterPage.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 662a9a8..c2c7b52 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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 ( -
-
-

Shadowdark

- -
- - } /> - } /> - -
+ +
+
+

Darkwatch

+ +
+ + } /> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + +
+
); } diff --git a/client/src/components/RequireAuth.tsx b/client/src/components/RequireAuth.tsx new file mode 100644 index 0000000..3675670 --- /dev/null +++ b/client/src/components/RequireAuth.tsx @@ -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 ; + return <>{children}; +} diff --git a/client/src/context/AuthContext.tsx b/client/src/context/AuthContext.tsx new file mode 100644 index 0000000..f839a5a --- /dev/null +++ b/client/src/context/AuthContext.tsx @@ -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; +} + +const AuthContext = createContext({ + user: null, + loading: true, + setUser: () => {}, + logout: async () => {}, +}); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(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 ( + + {children} + + ); +} + +export function useAuth() { + return useContext(AuthContext); +} diff --git a/client/src/pages/JoinPage.tsx b/client/src/pages/JoinPage.tsx new file mode 100644 index 0000000..2a684e1 --- /dev/null +++ b/client/src/pages/JoinPage.tsx @@ -0,0 +1,3 @@ +export default function JoinPage() { + return
Join
; +} diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx new file mode 100644 index 0000000..ba5b453 --- /dev/null +++ b/client/src/pages/LoginPage.tsx @@ -0,0 +1,3 @@ +export default function LoginPage() { + return
Login
; +} diff --git a/client/src/pages/RegisterPage.tsx b/client/src/pages/RegisterPage.tsx new file mode 100644 index 0000000..102d218 --- /dev/null +++ b/client/src/pages/RegisterPage.tsx @@ -0,0 +1,3 @@ +export default function RegisterPage() { + return
Register
; +}