add jwt authentication

pull/107/head
maxime 1 year ago
parent 970045446b
commit 9014a0e154

@ -1,2 +1,2 @@
VITE_API_ENDPOINT=/api
VITE_BASE=
VITE_API_ENDPOINT=http://localhost:5254
VITE_BASE=

@ -1,16 +1,11 @@
import {
BrowserRouter,
createBrowserRouter,
createRoutesFromElements,
Route,
RouterProvider,
Routes
} from "react-router-dom";
import { BrowserRouter, Route, Routes } from "react-router-dom"
import loadable from "@loadable/component";
import loadable from "@loadable/component"
const HomePage = loadable(() => import("./pages/Home.tsx"))
const HomePage = loadable(() => import("./pages/HomePage.tsx"))
const LoginPage = loadable(() => import("./pages/LoginPage.tsx"))
const RegisterPage = loadable(() => import("./pages/RegisterPage.tsx"))
const NotFoundPage = loadable(() => import("./pages/404.tsx"))
const CreateTeamPage = loadable(() => import("./pages/CreateTeamPage.tsx"))
const TeamPanelPage = loadable(() => import("./pages/TeamPanel.tsx"))
@ -19,17 +14,23 @@ const Editor = loadable(() => import("./pages/Editor.tsx"))
export default function App() {
return (
<div style={{height: "100vh", width: "100vw"}}>
<div style={{ height: "100vh", width: "100vw" }}>
<BrowserRouter>
<Routes>
<Route path={"/"} element={<HomePage/>}/>
<Route path={"*"} element={<NotFoundPage/>}/>
<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={<HomePage />} />
<Route path={"/login"} element={<LoginPage />} />
<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 />} />
</Routes>
</BrowserRouter>
</div>

@ -1,16 +1,57 @@
import { API } from "./Constants"
import { API, BASE } from "./Constants"
import { Session } from "./api/session.ts"
export function redirect(url: string) {
location.pathname = BASE + url
}
export function fetchAPI(
url: string,
payload: unknown,
method = "POST",
session?: Session,
): Promise<Response> {
const token = session?.auth?.token
const headers = {
"Accept": "application/json",
"Content-Type": "application/json",
}
if (token) {
headers.Authorization = token
}
return fetch(`${API}/${url}`, {
method,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
headers,
body: JSON.stringify(payload),
})
}
export function fetchAPIGet(
url: string,
session?: Session,
): Promise<Response> {
const token = session?.auth?.token
const headers = {
"Accept": "application/json",
"Content-Type": "application/json",
}
if (token) {
headers.Authorization = token
}
return fetch(`${API}/${url}`, {
method: "GET",
headers,
})
}

@ -0,0 +1,6 @@
export interface Failure {
type: string
messages: string[]
}

@ -0,0 +1,23 @@
import { createContext } from "react"
export interface Session {
auth?: Authentication
}
export interface Authentication {
token: string
expirationDate: Date
}
const SESSION_KEY = "session"
// export const SessionContext = createContext<Session>(getSession())
export function saveSession(session: Session) {
localStorage.setItem(SESSION_KEY, JSON.stringify(session))
}
export function getSession(): Session {
const json = localStorage.getItem(SESSION_KEY)
return json ? JSON.parse(json) : {}
}

@ -1,13 +1,17 @@
import "../style/home/home.css"
// import AccountSvg from "../assets/account.svg?react"
import {Header} from "./template/Header"
import {BASE} from "../Constants"
import { Header } from "./template/Header"
import { BASE } from "../Constants"
import { getSession } from "../api/session.ts"
import { fetchAPIGet, redirect } from "../Fetcher.ts"
import { useLayoutEffect, useState } from "react"
import { User } from "../model/User.ts"
interface Tactic {
id: number
name: string
creation_date: string
creationDate: Date
}
interface Team {
@ -21,8 +25,31 @@ interface Team {
export default function HomePage() {
console.log("HOME PAGE LOADED")
return <Home teams={[]} allTactics={[]} lastTactics={[]} username={"zizi city"} />
type UserDataResponse = {user?: User, tactics: Tactic[], teams: Team[]}
const [{ user, tactics, teams }, setInfo] = useState<UserDataResponse>({tactics: [], teams: []})
useLayoutEffect(() => {
const session = getSession()
if (!session.auth) {
redirect("/register")
return
}
async function getUser() {
const response = await fetchAPIGet("user-data", session)
setInfo(await response.json())
}
getUser()
}, [])
console.log(user)
const lastTactics = tactics!.sort((a, b) => a.creationDate.getMilliseconds() - b.creationDate.getMilliseconds()).slice(0, 5)
return <Home teams={teams!} allTactics={tactics!} lastTactics={lastTactics} username={user?.name ?? ""} />
}
function Home({

@ -0,0 +1,63 @@
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 "../style/form.css"
export default function LoginApp() {
const [errors, setErrors] = useState<Failure[]>([])
const emailRef = useRef<HTMLInputElement>(null)
const passwordRef = useRef<HTMLInputElement>(null)
async function handleSubmit(e: FormEvent) {
e.preventDefault()
const email = emailRef.current!.value
const password = passwordRef.current!.value
const response = await fetchAPI("auth/token", {email, password})
if (response.ok) {
const { token, expirationDate } = await response.json()
saveSession({ auth: { token, expirationDate } })
redirect("/")
return
}
try {
const failures = await response.json()
setErrors(Object.entries<string[]>(failures).map(([type, messages]) => ({ type, messages })))
} catch (e) {
setErrors([{ type: "internal error", messages: ["an internal error occurred."] }])
}
}
return <div className="container">
<center>
<h2>Se connecter</h2>
</center>
{errors.map(({ type, messages }) =>
messages.map(message => <p key={type} style={{ color: "red" }}>{type} : {message}</p>))}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="email">Email :</label>
<input ref={emailRef} type="text" id="email" name="email" required />
<label htmlFor="password">Mot de passe :</label>
<input ref={passwordRef} type="password" id="password" name="password" required />
<a href={BASE + "/register"} className="inscr">Vous n'avez pas de compte ?</a>
<br /><br />
<div id="buttons">
<input className="button" type="submit" value="Se connecter" />
</div>
</div>
</form>
</div>
}

@ -0,0 +1,92 @@
import { FormEvent, useRef, useState } from "react"
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"
export default function RegisterPage() {
const usernameField = useRef<HTMLInputElement>(null)
const passwordField = useRef<HTMLInputElement>(null)
const confirmpasswordField = useRef<HTMLInputElement>(null)
const emailField = useRef<HTMLInputElement>(null)
const [errors, setErrors] = useState<Failure[]>([])
async function handleSubmit(e: FormEvent) {
e.preventDefault()
const username = usernameField.current!.value
const password = passwordField.current!.value
const confirmpassword = confirmpasswordField.current!.value
const email = emailField.current!.value
if (confirmpassword !== password) {
setErrors([{
type: "password",
messages: ["le mot de passe et la confirmation du mot de passe ne sont pas equivalent."],
}])
return
}
const response = await fetchAPI("auth/register", { username, password, email })
if (response.ok) {
const { token, expirationDate } = await response.json()
saveSession({ auth: { token, expirationDate } })
redirect("/")
return
}
try {
const failures = await response.json()
setErrors(Object.entries<string[]>(failures).map(([type, messages]) => ({ type, messages })))
} catch (e) {
setErrors([{ type: "internal error", messages: ["an internal error occurred."] }])
}
}
return <div className="container">
<center>
<h2>S'enregistrer</h2>
</center>
<div>
{errors.map(({ type, messages }) =>
messages.map(message => <p key={type} style={{ color: "red" }}>{type} : {message}</p>))}
</div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="username">Nom d'utilisateur :</label>
<input ref={usernameField} type="text" id="username" name="username" required />
<label htmlFor="password">Mot de passe :</label>
<input ref={passwordField} type="password" id="password" name="password" required />
<label htmlFor="confirmpassword">Confirmer le mot de passe :</label>
<input ref={confirmpasswordField} type="password" id="confirmpassword" name="confirmpassword"
required />
<label htmlFor="email">Email :</label>
<input ref={emailField} type="text" id="email" name="email" required />
<label className="consentement">
<input type="checkbox" name="consentement" required />
En cochant cette case, j'accepte que mes données personnelles, tel que mon adresse e-mail, soient
collectées et traitées conformément à la politique de confidentialité de Sportify.
</label>
<a href={BASE + "/login"} className="inscr">Vous avez déjà un compte ?</a>
</div>
<div id="buttons">
<input className="button" type="submit" value="Créer votre compte" />
</div>
</form>
</div>
}

@ -0,0 +1,67 @@
body {
font-family: Arial, sans-serif;
background-color: #f1f1f1;
}
.container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="password"] {
width: 95%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.error-messages {
color: #ff331a;
font-style: italic;
}
.inscr{
font-size: small;
text-align: right;
}
.consentement{
font-size: small;
}
#buttons{
display: flex;
justify-content: center;
padding: 10px 20px;
}
.button{
background-color: #007bff;
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.button:hover{
background-color: #0056b3;
}
Loading…
Cancel
Save