add token renewal

pull/107/head
maxime 1 year ago
parent 87ddaacd97
commit a56b0454c7

@ -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 loadable from "@loadable/component"
import { Header } from "./pages/template/Header.tsx" 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 NewTacticPage = loadable(() => import("./pages/NewTacticPage.tsx"))
const Editor = loadable(() => import("./pages/Editor.tsx")) const Editor = loadable(() => import("./pages/Editor.tsx"))
import './style/app.css'
export default function App() { export default function App() {
return ( return (
<div style={{ height: "100vh", width: "100vw" }}> <div id="app">
<BrowserRouter> <BrowserRouter>
<Header/> <Outlet/>
<Routes> <Routes>
<Route path={"/"} element={<HomePage />} />
<Route path={"/login"} element={<LoginPage />} /> <Route path={"/login"} element={<LoginPage />} />
<Route path={"/register"} element={<RegisterPage />} /> <Route path={"/register"} element={<RegisterPage />} />
<Route path={"/team/new"} element={<CreateTeamPage />} />
<Route path={"/team/:teamId"} element={<TeamPanelPage />} />
<Route path={"/tactic/new"} element={<NewTacticPage />} />
<Route path={"/tactic/new/plain"} element={<Editor courtType={"PLAIN"} />} />
<Route path={"/tactic/new/half"} element={<Editor courtType={"HALF"} />} />
<Route path={"*"} element={<NotFoundPage />} /> <Route path={"/"} element={<AppLayout/>}>
<Route path={"/"} element={<HomePage />} />
<Route path={"/team/new"} element={<CreateTeamPage />} />
<Route path={"/team/:teamId"} element={<TeamPanelPage />} />
<Route path={"/tactic/new"} element={<NewTacticPage />} />
<Route path={"/tactic/new/plain"} element={<Editor courtType={"PLAIN"} />} />
<Route path={"/tactic/new/half"} element={<Editor courtType={"HALF"} />} />
<Route path={"*"} element={<NotFoundPage />} />
</Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
</div> </div>
) )
} }
function AppLayout() {
return <>
<Header/>
<Outlet/>
</>
}

@ -1,18 +1,18 @@
import { API, BASE } from "./Constants" import { API, BASE } from "./Constants"
import { Session } from "./api/session.ts" import { getSession, saveSession, Session } from "./api/session.ts"
export function redirect(url: string) { export function redirect(url: string) {
location.pathname = BASE + url location.pathname = BASE + url
} }
export function fetchAPI( export async function fetchAPI(
url: string, url: string,
payload: unknown, payload: unknown,
method = "POST", method = "POST",
session?: Session,
): Promise<Response> { ): Promise<Response> {
const session = getSession()
const token = session?.auth?.token const token = session?.auth?.token
const headers = { const headers = {
@ -24,19 +24,21 @@ export function fetchAPI(
headers.Authorization = token headers.Authorization = token
} }
return fetch(`${API}/${url}`, { const response = await fetch(`${API}/${url}`, {
method, method,
headers, headers,
body: JSON.stringify(payload), body: JSON.stringify(payload),
}) })
return await handleResponse(session, response)
} }
export function fetchAPIGet( export async function fetchAPIGet(
url: string, url: string,
session?: Session,
): Promise<Response> { ): Promise<Response> {
const session = getSession()
const token = session?.auth?.token const token = session?.auth?.token
const headers = { const headers = {
@ -48,10 +50,25 @@ export function fetchAPIGet(
headers.Authorization = token headers.Authorization = token
} }
return fetch(`${API}/${url}`, { const response = await fetch(`${API}/${url}`, {
method: "GET", method: "GET",
headers, headers,
}) })
return await handleResponse(session, response)
} }
async function handleResponse(session: Session, response: Response): Promise<Response> {
// 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
}

@ -1,12 +1,11 @@
import { createContext } from "react"
export interface Session { export interface Session {
auth?: Authentication auth?: Authentication
urlTarget?: string
} }
export interface Authentication { export interface Authentication {
token: string token: string
expirationDate: Date expirationDate: number
} }
const SESSION_KEY = "session" const SESSION_KEY = "session"

@ -49,6 +49,7 @@ import { Action, ActionKind } from "../model/tactic/Action"
import BallAction from "../components/actions/BallAction" import BallAction from "../components/actions/BallAction"
import { changePlayerBallState, getOrigin, removePlayer } from "../editor/PlayerDomains" import { changePlayerBallState, getOrigin, removePlayer } from "../editor/PlayerDomains"
import { CourtBall } from "../components/editor/CourtBall" import { CourtBall } from "../components/editor/CourtBall"
import { getSession } from "../api/session.ts"
const ERROR_STYLE: CSSProperties = { const ERROR_STYLE: CSSProperties = {
borderColor: "red", borderColor: "red",
@ -56,6 +57,7 @@ const ERROR_STYLE: CSSProperties = {
const GUEST_MODE_CONTENT_STORAGE_KEY = "guest_mode_content" const GUEST_MODE_CONTENT_STORAGE_KEY = "guest_mode_content"
const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title" const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title"
const DEFAULT_TACTIC_NAME = "Nouvelle tactique"
export interface EditorViewProps { export interface EditorViewProps {
tactic: Tactic tactic: Tactic
@ -70,12 +72,29 @@ export interface EditorPageProps {
} }
export default function EditorPage({ courtType }: EditorPageProps) { export default function EditorPage({ courtType }: EditorPageProps) {
return <Editor
id={-1} const [id, setId] = useState<number>()
courtType={courtType}
content={JSON.stringify({ components: [] })} useEffect(() => {
name={"Nouvelle Tactique"} 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 <Editor
id={id}
courtType={courtType}
content={JSON.stringify({ components: [] })}
name={DEFAULT_TACTIC_NAME}
/>
}
return <div>Loading Editor, please wait...</div>
} }
export interface EditorProps { export interface EditorProps {
@ -111,7 +130,7 @@ function Editor({ id, name, courtType, content }: EditorProps) {
) )
return SaveStates.Guest 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, 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) localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name)
return true //simulate that the name has been changed 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, (r) => r.ok,
) )
}} }}

@ -10,7 +10,7 @@ import { User } from "../model/User.ts"
interface Tactic { interface Tactic {
id: number id: number
name: string name: string
creationDate: Date creationDate: number
} }
interface Team { interface Team {
@ -37,7 +37,7 @@ export default function HomePage() {
} }
async function getUser() { async function getUser() {
const response = await fetchAPIGet("user-data", session) const response = await fetchAPIGet("user-data")
setInfo(await response.json()) 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 <Home teams={teams!} allTactics={tactics!} lastTactics={lastTactics}/> return <Home teams={teams!} allTactics={tactics!} lastTactics={lastTactics}/>
} }

@ -2,7 +2,7 @@ import { FormEvent, useRef, useState } from "react"
import { BASE } from "../Constants.ts" import { BASE } from "../Constants.ts"
import { fetchAPI, redirect } from "../Fetcher.ts" import { fetchAPI, redirect } from "../Fetcher.ts"
import { Failure } from "../api/failure.ts" import { Failure } from "../api/failure.ts"
import { saveSession } from "../api/session.ts" import { getSession, saveSession } from "../api/session.ts"
import "../style/form.css" import "../style/form.css"
export default function LoginApp() { export default function LoginApp() {
@ -22,9 +22,10 @@ export default function LoginApp() {
const response = await fetchAPI("auth/token", {email, password}) const response = await fetchAPI("auth/token", {email, password})
if (response.ok) { if (response.ok) {
const session = getSession()
const { token, expirationDate } = await response.json() const { token, expirationDate } = await response.json()
saveSession({ auth: { token, expirationDate } }) saveSession({...session, auth: { token, expirationDate } })
redirect("/") redirect(session.urlTarget ?? "/")
return return
} }

@ -4,7 +4,7 @@ import { BASE } from "../Constants.ts"
import "../style/form.css" import "../style/form.css"
import { Failure } from "../api/failure.ts" import { Failure } from "../api/failure.ts"
import { fetchAPI, redirect } from "../Fetcher.ts" import { fetchAPI, redirect } from "../Fetcher.ts"
import { saveSession } from "../api/session.ts" import { getSession, saveSession } from "../api/session.ts"
export default function RegisterPage() { export default function RegisterPage() {
@ -36,8 +36,9 @@ export default function RegisterPage() {
if (response.ok) { if (response.ok) {
const { token, expirationDate } = await response.json() const { token, expirationDate } = await response.json()
saveSession({ auth: { token, expirationDate } }) const session = getSession()
redirect("/") saveSession({...session, auth: { token, expirationDate } })
redirect(session.urlTarget ?? "/")
return return
} }

@ -3,20 +3,15 @@ import accountSvg from "../../assets/account.svg"
import "../../style/template/header.css" import "../../style/template/header.css"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { fetchAPIGet } from "../../Fetcher.ts" import { fetchAPIGet } from "../../Fetcher.ts"
import { getSession } from "../../api/session.ts"
/**
*
* @param param0 username
* @returns Header
*/
export function Header() { export function Header() {
const [username, setUsername] = useState("") const [username, setUsername] = useState("")
useEffect(() => { useEffect(() => {
async function getUsername() { async function getUsername() {
const response = await fetchAPIGet("user", getSession()) const response = await fetchAPIGet("user")
//TODO check if the response is ok and handle errors //TODO check if the response is ok and handle errors
const {name} = await response.json() const {name} = await response.json()
setUsername(name) setUsername(name)

@ -0,0 +1,6 @@
#app {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
}

@ -5,7 +5,6 @@
body { body {
/* background-color: #303030; */ /* background-color: #303030; */
overflow-x: hidden;
} }
#main { #main {

@ -2,6 +2,7 @@
text-align: center; text-align: center;
background-color: var(--home-main-color); background-color: var(--home-main-color);
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
@ -9,6 +10,7 @@
font-family: var(--font-title); font-family: var(--font-title);
height: 50px; height: 50px;
} }

Loading…
Cancel
Save