diff --git a/backend/build.rs b/backend/build.rs index 5428137..113518c 100644 --- a/backend/build.rs +++ b/backend/build.rs @@ -4,6 +4,7 @@ extern crate prost_build; fn main() -> Result<(), std::io::Error> { println!("cargo:rerun-if-changed=../protobuf/items.proto"); + println!("cargo:rerun-if-changed=../frontend/src/"); let mut cfg = prost_build::Config::new(); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 23c7abc..2d9a9b8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,19 +5,18 @@ import { PersonList } from "./PersonList"; import { PersonDetails } from "./PersonDetails"; import { BrowserRouter, Routes, Route, Link } from "react-router-dom"; import "./App.css"; +import { apiFetch } from "./api"; function App() { const [people, setPeople] = useState([]); - const [token, setToken] = useState(""); + const [token, setToken] = useState( + localStorage.getItem("token") || "" + ); useEffect(() => { if (!token) return; - fetch("/api", { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + apiFetch("/api") .then((res) => res.arrayBuffer()) .then((buffer) => { const list = PersonListProto.decode(new Uint8Array(buffer)); @@ -26,13 +25,19 @@ function App() { .catch((err) => console.error("Failed to fetch people:", err)); }, [token]); + const handleLogin = (newToken: string) => { + setToken(newToken); + localStorage.setItem("token", newToken); + }; + const handleLogout = () => { setToken(""); setPeople([]); + localStorage.removeItem("token"); }; if (!token) { - return ; + return ; } return ( @@ -55,10 +60,7 @@ function App() { } /> - } - /> + } /> diff --git a/frontend/src/PersonDetails.tsx b/frontend/src/PersonDetails.tsx index ca6021a..2e5a1cc 100644 --- a/frontend/src/PersonDetails.tsx +++ b/frontend/src/PersonDetails.tsx @@ -1,12 +1,9 @@ import { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; import { Person, AddOpinionRequest } from "../items"; +import { apiFetch } from "./api"; -interface Props { - token: string; -} - -export const PersonDetails = ({ token }: Props) => { +export const PersonDetails = () => { const { name } = useParams<{ name: string }>(); const [person, setPerson] = useState(null); const [gameTitle, setGameTitle] = useState(""); @@ -14,9 +11,7 @@ export const PersonDetails = ({ token }: Props) => { useEffect(() => { if (name) { - fetch(`/api/${name}`, { - headers: { Authorization: `Bearer ${token}` }, - }) + apiFetch(`/api/${name}`) .then((res) => res.arrayBuffer()) .then((buffer) => { try { @@ -27,7 +22,7 @@ export const PersonDetails = ({ token }: Props) => { }) .catch(console.error); } - }, [name, token]); + }, [name]); const handleAddOpinion = async () => { if (!person) return; @@ -40,11 +35,10 @@ export const PersonDetails = ({ token }: Props) => { const buffer = AddOpinionRequest.encode(req).finish(); try { - const res = await fetch("/api/opinion", { + const res = await apiFetch("/api/opinion", { method: "POST", headers: { "Content-Type": "application/octet-stream", - Authorization: `Bearer ${token}`, }, body: buffer, }); diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 0000000..fdb3ef7 --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,24 @@ +export const apiFetch = async ( + url: string, + options: RequestInit = {} +): Promise => { + const token = localStorage.getItem("token"); + const headers = new Headers(options.headers); + + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + + const config = { + ...options, + headers, + }; + + const response = await fetch(url, config); + + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + + return response; +};