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}

))} + +
+
+ + + + + + + Vous n'avez pas de compte ? +

+
+ +
+
+
+
+} \ 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}

))} +
+ +
+
+ + + + + + + + + + + + + + + + Vous avez déjà un compte ? +
+
+ +
+
+
+} \ 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