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,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) : {}
|
||||
}
|
@ -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…
Reference in new issue