diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b7b1726..2f728d6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,12 +1,18 @@ -import { Routes, Route } from "react-router-dom"; +import { Navigate, Route, Routes } from "react-router-dom"; +import AppLayout from "./components/AppLayout"; +import LoginPage from "./pages/Login"; import UploadPage from "./pages/Upload"; import StatPage from "./pages/Stats"; function App() { return ( - } /> - } /> + }> + } /> + } /> + } /> + } /> + ); } diff --git a/frontend/src/components/AppLayout.tsx b/frontend/src/components/AppLayout.tsx new file mode 100644 index 0000000..272539c --- /dev/null +++ b/frontend/src/components/AppLayout.tsx @@ -0,0 +1,125 @@ +import { useCallback, useEffect, useState } from "react"; +import axios from "axios"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import StatsStyling from "../styles/stats_styling"; + +type ProfileResponse = { + user?: Record; +}; + +const styles = StatsStyling; +const API_BASE_URL = "http://localhost:5000"; + +const getUserLabel = (user: Record | null) => { + if (!user) { + return "Signed in"; + } + + const username = user.username; + if (typeof username === "string" && username.length > 0) { + return username; + } + + const email = user.email; + if (typeof email === "string" && email.length > 0) { + return email; + } + + return "Signed in"; +}; + +const AppLayout = () => { + const location = useLocation(); + const navigate = useNavigate(); + const [isSignedIn, setIsSignedIn] = useState(false); + const [currentUser, setCurrentUser] = useState | null>(null); + + const syncAuthState = useCallback(async () => { + const token = localStorage.getItem("access_token"); + + if (!token) { + setIsSignedIn(false); + setCurrentUser(null); + delete axios.defaults.headers.common.Authorization; + return; + } + + axios.defaults.headers.common.Authorization = `Bearer ${token}`; + + try { + const response = await axios.get(`${API_BASE_URL}/profile`); + setIsSignedIn(true); + setCurrentUser(response.data.user ?? null); + } catch { + localStorage.removeItem("access_token"); + delete axios.defaults.headers.common.Authorization; + setIsSignedIn(false); + setCurrentUser(null); + } + }, []); + + useEffect(() => { + void syncAuthState(); + }, [location.pathname, syncAuthState]); + + const onAuthButtonClick = () => { + if (isSignedIn) { + localStorage.removeItem("access_token"); + delete axios.defaults.headers.common.Authorization; + setIsSignedIn(false); + setCurrentUser(null); + navigate("/login", { replace: true }); + return; + } + + navigate("/login"); + }; + + return ( +
+
+
+
+ + Ethnograph View + + + {isSignedIn ? `Signed in: ${getUserLabel(currentUser)}` : "Not signed in"} + +
+ +
+ +
+
+
+ + +
+ ); +}; + +export default AppLayout; diff --git a/server/app.py b/server/app.py index a92f4a7..49feae8 100644 --- a/server/app.py +++ b/server/app.py @@ -15,7 +15,6 @@ from flask_jwt_extended import ( ) from server.analysis.stat_gen import StatGen -from server.analysis.enrichment import DatasetEnrichment from server.exceptions import NotAuthorisedException, NonExistentDatasetException from server.db.database import PostgresConnector from server.core.auth import AuthManager