From a56b0454c70f62e14d239a41ccb913d437f6c4bb Mon Sep 17 00:00:00 2001 From: maxime Date: Thu, 15 Feb 2024 19:27:04 +0100 Subject: [PATCH] add token renewal --- src/App.tsx | 36 +++++++++++++++++++++++------------ src/Fetcher.ts | 31 +++++++++++++++++++++++------- src/api/session.ts | 5 ++--- src/pages/Editor.tsx | 35 ++++++++++++++++++++++++++-------- src/pages/HomePage.tsx | 6 +++--- src/pages/LoginPage.tsx | 7 ++++--- src/pages/RegisterPage.tsx | 7 ++++--- src/pages/template/Header.tsx | 9 ++------- src/style/app.css | 6 ++++++ src/style/home/home.css | 1 - src/style/template/header.css | 2 ++ 11 files changed, 98 insertions(+), 47 deletions(-) create mode 100644 src/style/app.css diff --git a/src/App.tsx b/src/App.tsx index 13633cc..79534b3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { BrowserRouter, Route, Routes } from "react-router-dom" +import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom" import loadable from "@loadable/component" import { Header } from "./pages/template/Header.tsx" @@ -13,29 +13,41 @@ const TeamPanelPage = loadable(() => import("./pages/TeamPanel.tsx")) const NewTacticPage = loadable(() => import("./pages/NewTacticPage.tsx")) const Editor = loadable(() => import("./pages/Editor.tsx")) +import './style/app.css' export default function App() { - - return ( -
+
-
+ - } /> + } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> + }> + } /> + + } /> + } /> + } /> + } /> + } /> + + } /> + +
) +} + +function AppLayout() { + return <> +
+ + } \ No newline at end of file diff --git a/src/Fetcher.ts b/src/Fetcher.ts index dd2b2b8..b496894 100644 --- a/src/Fetcher.ts +++ b/src/Fetcher.ts @@ -1,18 +1,18 @@ import { API, BASE } from "./Constants" -import { Session } from "./api/session.ts" +import { getSession, saveSession, Session } from "./api/session.ts" export function redirect(url: string) { location.pathname = BASE + url } -export function fetchAPI( +export async function fetchAPI( url: string, payload: unknown, method = "POST", - session?: Session, ): Promise { + const session = getSession() const token = session?.auth?.token const headers = { @@ -24,19 +24,21 @@ export function fetchAPI( headers.Authorization = token } - return fetch(`${API}/${url}`, { + const response = await fetch(`${API}/${url}`, { method, headers, body: JSON.stringify(payload), }) + + return await handleResponse(session, response) } -export function fetchAPIGet( +export async function fetchAPIGet( url: string, - session?: Session, ): Promise { + const session = getSession() const token = session?.auth?.token const headers = { @@ -48,10 +50,25 @@ export function fetchAPIGet( headers.Authorization = token } - return fetch(`${API}/${url}`, { + const response = await fetch(`${API}/${url}`, { method: "GET", headers, }) + + return await handleResponse(session, response) } +async function handleResponse(session: Session, response: Response): Promise { + // if we provided a token but still unauthorized, the token has expired + if (response.status == 401) { + redirect("/login") + saveSession({...session, urlTarget: location.pathname}) + return response + } + const nextToken = response.headers.get("Next-Authorization")! + const expirationDate = Date.parse(response.headers.get("Next-Authorization-Expiration-Date")!) + saveSession({...session, auth: {token: nextToken, expirationDate}}) + + return response +} diff --git a/src/api/session.ts b/src/api/session.ts index 506364c..7e32776 100644 --- a/src/api/session.ts +++ b/src/api/session.ts @@ -1,12 +1,11 @@ -import { createContext } from "react" - export interface Session { auth?: Authentication + urlTarget?: string } export interface Authentication { token: string - expirationDate: Date + expirationDate: number } const SESSION_KEY = "session" diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 288194c..c8da3fd 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -49,6 +49,7 @@ import { Action, ActionKind } from "../model/tactic/Action" import BallAction from "../components/actions/BallAction" import { changePlayerBallState, getOrigin, removePlayer } from "../editor/PlayerDomains" import { CourtBall } from "../components/editor/CourtBall" +import { getSession } from "../api/session.ts" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -56,6 +57,7 @@ const ERROR_STYLE: CSSProperties = { const GUEST_MODE_CONTENT_STORAGE_KEY = "guest_mode_content" const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title" +const DEFAULT_TACTIC_NAME = "Nouvelle tactique" export interface EditorViewProps { tactic: Tactic @@ -70,12 +72,29 @@ export interface EditorPageProps { } export default function EditorPage({ courtType }: EditorPageProps) { - return + + const [id, setId] = useState() + + useEffect(() => { + async function initialize() { + const response = await fetchAPI("tactics", { name: DEFAULT_TACTIC_NAME }, "POST", getSession()) + const { id } = await response.json() + setId(id) + } + + initialize() + }, []) + + if (id) { + return + } + + return
Loading Editor, please wait...
} export interface EditorProps { @@ -111,7 +130,7 @@ function Editor({ id, name, courtType, content }: EditorProps) { ) return SaveStates.Guest } - return fetchAPI(`tactic/${id}/save`, { content }).then((r) => + return fetchAPI(`tactics/${id}/1`, { content }, "PUT", getSession()).then((r) => r.ok ? SaveStates.Ok : SaveStates.Err, ) }} @@ -120,7 +139,7 @@ function Editor({ id, name, courtType, content }: EditorProps) { localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name) return true //simulate that the name has been changed } - return fetchAPI(`tactic/${id}/edit/name`, { name }).then( + return fetchAPI(`tactics/${id}/name`, { name }, "PUT", getSession()).then( (r) => r.ok, ) }} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index ce26803..d7249a8 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -10,7 +10,7 @@ import { User } from "../model/User.ts" interface Tactic { id: number name: string - creationDate: Date + creationDate: number } interface Team { @@ -37,7 +37,7 @@ export default function HomePage() { } async function getUser() { - const response = await fetchAPIGet("user-data", session) + const response = await fetchAPIGet("user-data") setInfo(await response.json()) } @@ -45,7 +45,7 @@ export default function HomePage() { }, []) - const lastTactics = tactics!.sort((a, b) => a.creationDate.getMilliseconds() - b.creationDate.getMilliseconds()).slice(0, 5) + const lastTactics = tactics!.sort((a, b) => a.creationDate - b.creationDate).slice(0, 5) return } diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 43a3a28..231b75b 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -2,7 +2,7 @@ import { FormEvent, useRef, useState } from "react" import { BASE } from "../Constants.ts" import { fetchAPI, redirect } from "../Fetcher.ts" import { Failure } from "../api/failure.ts" -import { saveSession } from "../api/session.ts" +import { getSession, saveSession } from "../api/session.ts" import "../style/form.css" export default function LoginApp() { @@ -22,9 +22,10 @@ export default function LoginApp() { const response = await fetchAPI("auth/token", {email, password}) if (response.ok) { + const session = getSession() const { token, expirationDate } = await response.json() - saveSession({ auth: { token, expirationDate } }) - redirect("/") + saveSession({...session, auth: { token, expirationDate } }) + redirect(session.urlTarget ?? "/") return } diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx index 6cb76a2..f3eeebb 100644 --- a/src/pages/RegisterPage.tsx +++ b/src/pages/RegisterPage.tsx @@ -4,7 +4,7 @@ import { BASE } from "../Constants.ts" import "../style/form.css" import { Failure } from "../api/failure.ts" import { fetchAPI, redirect } from "../Fetcher.ts" -import { saveSession } from "../api/session.ts" +import { getSession, saveSession } from "../api/session.ts" export default function RegisterPage() { @@ -36,8 +36,9 @@ export default function RegisterPage() { if (response.ok) { const { token, expirationDate } = await response.json() - saveSession({ auth: { token, expirationDate } }) - redirect("/") + const session = getSession() + saveSession({...session, auth: { token, expirationDate } }) + redirect(session.urlTarget ?? "/") return } diff --git a/src/pages/template/Header.tsx b/src/pages/template/Header.tsx index 38ca4f4..0ea4a67 100644 --- a/src/pages/template/Header.tsx +++ b/src/pages/template/Header.tsx @@ -3,20 +3,15 @@ import accountSvg from "../../assets/account.svg" import "../../style/template/header.css" import { useEffect, useState } from "react" import { fetchAPIGet } from "../../Fetcher.ts" -import { getSession } from "../../api/session.ts" -/** - * - * @param param0 username - * @returns Header - */ + export function Header() { const [username, setUsername] = useState("") useEffect(() => { async function getUsername() { - const response = await fetchAPIGet("user", getSession()) + const response = await fetchAPIGet("user") //TODO check if the response is ok and handle errors const {name} = await response.json() setUsername(name) diff --git a/src/style/app.css b/src/style/app.css new file mode 100644 index 0000000..09e66b9 --- /dev/null +++ b/src/style/app.css @@ -0,0 +1,6 @@ +#app { + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/style/home/home.css b/src/style/home/home.css index bc1bf4a..3af258a 100644 --- a/src/style/home/home.css +++ b/src/style/home/home.css @@ -5,7 +5,6 @@ body { /* background-color: #303030; */ - overflow-x: hidden; } #main { diff --git a/src/style/template/header.css b/src/style/template/header.css index e0fb2c5..78d1289 100644 --- a/src/style/template/header.css +++ b/src/style/template/header.css @@ -2,6 +2,7 @@ text-align: center; background-color: var(--home-main-color); margin: 0; + display: flex; flex-direction: row; align-items: center; @@ -9,6 +10,7 @@ font-family: var(--font-title); + height: 50px; }