diff --git a/.env b/.env
index 98ae12d..88ae8ef 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
-VITE_API_ENDPOINT=/api
-VITE_BASE=
\ No newline at end of file
+VITE_API_ENDPOINT=http://localhost:5254
+VITE_BASE=
diff --git a/src/App.tsx b/src/App.tsx
index 843568d..b7441b5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -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 (
-
+
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ } />
diff --git a/src/Fetcher.ts b/src/Fetcher.ts
index 4c483e9..dd2b2b8 100644
--- a/src/Fetcher.ts
+++ b/src/Fetcher.ts
@@ -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
{
+
+ 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 {
+
+ 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,
+ })
+}
+
+
diff --git a/src/api/failure.ts b/src/api/failure.ts
new file mode 100644
index 0000000..7219ecd
--- /dev/null
+++ b/src/api/failure.ts
@@ -0,0 +1,6 @@
+
+
+export interface Failure {
+ type: string
+ messages: string[]
+}
\ No newline at end of file
diff --git a/src/api/session.ts b/src/api/session.ts
new file mode 100644
index 0000000..506364c
--- /dev/null
+++ b/src/api/session.ts
@@ -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(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) : {}
+}
\ No newline at end of file
diff --git a/src/pages/Home.tsx b/src/pages/HomePage.tsx
similarity index 85%
rename from src/pages/Home.tsx
rename to src/pages/HomePage.tsx
index df27c11..c3bb8c6 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/HomePage.tsx
@@ -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
+ type UserDataResponse = {user?: User, tactics: Tactic[], teams: Team[]}
+ const [{ user, tactics, teams }, setInfo] = useState({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
}
function Home({
diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..43a3a28
--- /dev/null
+++ b/src/pages/LoginPage.tsx
@@ -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([])
+
+ const emailRef = useRef(null)
+ const passwordRef = useRef(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(failures).map(([type, messages]) => ({ type, messages })))
+ } catch (e) {
+ setErrors([{ type: "internal error", messages: ["an internal error occurred."] }])
+ }
+ }
+
+ return
+
+ Se connecter
+
+
+ {errors.map(({ type, messages }) =>
+ messages.map(message =>
{type} : {message}
))}
+
+
+
+}
\ No newline at end of file
diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx
new file mode 100644
index 0000000..6cb76a2
--- /dev/null
+++ b/src/pages/RegisterPage.tsx
@@ -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(null)
+ const passwordField = useRef(null)
+ const confirmpasswordField = useRef(null)
+ const emailField = useRef(null)
+
+
+ const [errors, setErrors] = useState([])
+
+ 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(failures).map(([type, messages]) => ({ type, messages })))
+ } catch (e) {
+ setErrors([{ type: "internal error", messages: ["an internal error occurred."] }])
+ }
+ }
+
+
+ return
+
+ S'enregistrer
+
+
+
+ {errors.map(({ type, messages }) =>
+ messages.map(message =>
{type} : {message}
))}
+
+
+
+
+}
\ No newline at end of file
diff --git a/src/style/form.css b/src/style/form.css
new file mode 100644
index 0000000..3db12c0
--- /dev/null
+++ b/src/style/form.css
@@ -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;
+}
\ No newline at end of file