+ {auth.user !== null ? (
+
auth.logout()}>
+ Log Out
+
+ ) : (
+
Log In
+ )}
,
},
+ {
+ path: "/login",
+ element:
,
+ },
]);
createRoot(document.getElementById("root")!).render(
-
+
+
+
,
);
diff --git a/apps/frontend/src/pages/login.tsx b/apps/frontend/src/pages/login.tsx
new file mode 100644
index 0000000..70c7d6c
--- /dev/null
+++ b/apps/frontend/src/pages/login.tsx
@@ -0,0 +1,54 @@
+import { useAuth } from "../providers/AuthProvider";
+import { useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+
+type InputType = {
+ label: string;
+ forLabel: string;
+ type: "password" | "email";
+};
+const Input = ({ label, forLabel, type }: InputType) => {
+ return (
+
+
+
+
+ );
+};
+
+export default function LogIn() {
+ const auth = useAuth();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (auth.user) navigate("/");
+ }, [auth.user]);
+
+ return (
+
+ );
+}
diff --git a/apps/frontend/src/providers/AuthProvider.tsx b/apps/frontend/src/providers/AuthProvider.tsx
new file mode 100644
index 0000000..c3a8552
--- /dev/null
+++ b/apps/frontend/src/providers/AuthProvider.tsx
@@ -0,0 +1,59 @@
+import {
+ createContext,
+ useContext,
+ useState,
+ useMemo,
+ type ReactNode,
+ useEffect,
+} from "react";
+
+interface User {
+ id: number;
+ email: string;
+ name: string;
+}
+interface AuthContextType {
+ user: User | null;
+ refetch: () => void;
+ logout: () => void;
+}
+const AuthContext = createContext
(null);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null);
+
+ const refetch = async () => {
+ const data = await fetch("/api/users/@me", {
+ credentials: "include",
+ });
+
+ if (data.ok) {
+ const refetchedUser = await data.json();
+ setUser(refetchedUser);
+ }
+ };
+
+ const logout = async () => {
+ const res = await fetch("/api/users/logout", {
+ credentials: "include",
+ method: "POST",
+ });
+
+ if (res.ok) {
+ setUser(null);
+ }
+ };
+
+ useEffect(() => {
+ refetch();
+ }, []);
+
+ const value = useMemo(() => ({ user, refetch, logout }), [user]);
+ return {children};
+}
+
+export function useAuth() {
+ const ctx = useContext(AuthContext);
+ if (!ctx) throw new Error("useAuth must be used inside of AuthProvider");
+ return ctx;
+}
diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts
index 521def0..9b97673 100644
--- a/apps/frontend/vite.config.ts
+++ b/apps/frontend/vite.config.ts
@@ -5,4 +5,13 @@ import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
+ server: {
+ proxy: {
+ "/api": {
+ target: "http://localhost:3000",
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, ""),
+ },
+ },
+ },
});