diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index 3bbc86a..748928e 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -1,4 +1,5 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { Auth } from "@/components/auth-provider"; import { ThemeProvider } from "@/components/theme-provider"; import Index from "./Index"; import Test from "./Test"; @@ -6,12 +7,14 @@ import Test from "./Test"; function App() { return ( - - - } /> - } /> - - + + + + } /> + } /> + + + ); } diff --git a/packages/frontend/src/Index.tsx b/packages/frontend/src/Index.tsx index 8b5a9e3..be9f97d 100644 --- a/packages/frontend/src/Index.tsx +++ b/packages/frontend/src/Index.tsx @@ -1,11 +1,17 @@ -import type { IssueResponse, ProjectResponse } from "@issue/shared"; +import type { IssueResponse, ProjectResponse, UserRecord } from "@issue/shared"; import { useEffect, useRef, useState } from "react"; import { IssueDetailPane } from "@/components/issue-detail-pane"; import { IssuesTable } from "@/components/issues-table"; +import SmallUserDisplay from "@/components/small-user-display"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import SmallUserDisplay from "./components/small-user-display"; +import LogOutButton from "./components/log-out-button"; +import { getAuthHeaders } from "./lib/utils"; function Index() { + const serverURL = import.meta.env.SERVER_URL?.trim() || "http://localhost:3000"; + + const user = JSON.parse(localStorage.getItem("user") || "{}") as UserRecord; + const [selectedProject, setSelectedProject] = useState(null); const [projects, setProjects] = useState([]); const projectsRef = useRef(false); @@ -14,7 +20,7 @@ function Index() { if (projectsRef.current) return; projectsRef.current = true; - fetch("http://localhost:3000/projects/all") + fetch(`${serverURL}/projects/all`, { headers: getAuthHeaders() }) .then((res) => res.json()) .then((data: ProjectResponse[]) => { setProjects(data); @@ -27,12 +33,10 @@ function Index() { const [selectedIssue, setSelectedIssue] = useState(null); const [issuesData, setIssues] = useState([]); - const serverURL = import.meta.env.SERVER_URL?.trim() || "http://localhost:3000"; - useEffect(() => { if (!selectedProject) return; - fetch(`${serverURL}/issues/${selectedProject.Project.blob}`) + fetch(`${serverURL}/issues/${selectedProject.Project.blob}`, { headers: getAuthHeaders() }) .then((res) => res.json()) .then((data: IssueResponse[]) => { setIssues(data); @@ -45,42 +49,51 @@ function Index() { return (
{/* header area */} -
- { + if (value === "NONE") { + setSelectedProject(null); + setSelectedIssue(null); + setIssues([]); } - /> - - - {projects.map((project) => ( - - {project.Project.name} - - ))} - - - {selectedProject && ( + const project = projects.find((p) => p.Project.id === Number(value)); + if (!project) { + // TODO: toast here + console.error(`NO PROJECT FOUND FOR ID: ${value}`); + return; + } + setSelectedProject(project); + setSelectedIssue(null); + }} + > + + + + + {projects.map((project) => ( + + {project.Project.name} + + ))} + + + {selectedProject && ( +
+ Owner: +
+ )} +
+ + {user && (
- Owner: + You: +
)} @@ -109,6 +122,8 @@ function Index() { )} + +
); } diff --git a/packages/frontend/src/components/auth-provider.tsx b/packages/frontend/src/components/auth-provider.tsx new file mode 100644 index 0000000..2dd595a --- /dev/null +++ b/packages/frontend/src/components/auth-provider.tsx @@ -0,0 +1,52 @@ +import type { UserRecord } from "@issue/shared"; +import { useEffect, useRef, useState } from "react"; +import LogInForm from "./login-form"; + +type AuthProviderProps = { + children: React.ReactNode; + loggedInDefault?: boolean; +}; + +export function Auth({ children }: AuthProviderProps) { + const serverURL = import.meta.env.SERVER_URL?.trim() || "http://localhost:3000"; + + const [loggedIn, setLoggedIn] = useState(); + const fetched = useRef(false); + + useEffect(() => { + if (fetched.current) return; + fetched.current = true; + const token = localStorage.getItem("token"); + if (!token) { + return setLoggedIn(false); + } + fetch(`${serverURL}/auth/me`, { + headers: { Authorization: `Bearer ${token}` }, + }) + .then((res) => res.json()) + .then((data: UserRecord) => { + if (data) { + setLoggedIn(true); + localStorage.setItem("user", JSON.stringify(data)); + } + }) + .catch((err) => { + setLoggedIn(false); + localStorage.removeItem("token"); + localStorage.removeItem("user"); + console.error("user not logged in:", err); + }); + }, []); + + if (loggedIn) { + return <>{children}; + } + if (loggedIn === false) + return ( +
+ +
+ ); + + return <>loading...; +}