parent
5a92cd77d1
commit
cb3c629c38
@ -1,68 +0,0 @@
|
|||||||
import { API } from "./Constants"
|
|
||||||
import { getSession, saveSession, Session } from "./api/session.ts"
|
|
||||||
|
|
||||||
export async function fetchAPI(
|
|
||||||
url: string,
|
|
||||||
payload: unknown,
|
|
||||||
method = "POST",
|
|
||||||
): Promise<Response> {
|
|
||||||
const session = getSession()
|
|
||||||
const token = session?.auth?.token
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
headers.Authorization = token
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${API}/${url}`, {
|
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
})
|
|
||||||
|
|
||||||
return await handleResponse(session, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchAPIGet(url: string): Promise<Response> {
|
|
||||||
const session = getSession()
|
|
||||||
const token = session?.auth?.token
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
headers.Authorization = token
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${API}/${url}`, {
|
|
||||||
method: "GET",
|
|
||||||
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.ok) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextToken = response.headers.get("Next-Authorization")!
|
|
||||||
const expirationDate = new Date(
|
|
||||||
response.headers.get("Next-Authorization-Expiration-Date")!,
|
|
||||||
)
|
|
||||||
if (nextToken && expirationDate)
|
|
||||||
saveSession({ ...session, auth: { token: nextToken, expirationDate } })
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
export interface Session {
|
|
||||||
auth?: Authentication
|
|
||||||
urlTarget?: string
|
|
||||||
username?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
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,87 @@
|
|||||||
|
import { API } from "../Constants.ts"
|
||||||
|
|
||||||
|
|
||||||
|
export interface Authentication {
|
||||||
|
token: string
|
||||||
|
expirationDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Fetcher {
|
||||||
|
|
||||||
|
private auth?: Authentication
|
||||||
|
|
||||||
|
public constructor(auth?: Authentication) {
|
||||||
|
this.auth = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchAPI(
|
||||||
|
url: string,
|
||||||
|
payload: unknown,
|
||||||
|
method = "POST",
|
||||||
|
): Promise<Response> {
|
||||||
|
const token = this.auth?.token
|
||||||
|
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = token
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API}/${url}`, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
|
||||||
|
return await this.handleResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchAPIGet(url: string): Promise<Response> {
|
||||||
|
const token = this.auth?.token
|
||||||
|
|
||||||
|
const headers: HeadersInit = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = token
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API}/${url}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
|
||||||
|
return await this.handleResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAuthentication(auth: Authentication) {
|
||||||
|
this.auth = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async handleResponse(
|
||||||
|
response: Response,
|
||||||
|
): Promise<Response> {
|
||||||
|
// if we provided a token but still unauthorized, the token has expired
|
||||||
|
if (!response.ok) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextToken = response.headers.get("Next-Authorization")!
|
||||||
|
const expirationDate = new Date(
|
||||||
|
response.headers.get("Next-Authorization-Expiration-Date")!,
|
||||||
|
)
|
||||||
|
if (nextToken && expirationDate) {
|
||||||
|
this.auth = { token: nextToken, expirationDate }
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,222 @@
|
|||||||
|
import "bootstrap/dist/css/bootstrap.min.css"
|
||||||
|
import Button from "react-bootstrap/Button"
|
||||||
|
import Form from "react-bootstrap/Form"
|
||||||
|
import Image from "react-bootstrap/Image"
|
||||||
|
import Container from "react-bootstrap/Container"
|
||||||
|
import Row from "react-bootstrap/Row"
|
||||||
|
import Col from "react-bootstrap/Col"
|
||||||
|
import Modal from "react-bootstrap/Modal"
|
||||||
|
import { Stack } from "react-bootstrap"
|
||||||
|
import { useRef, useState } from "react"
|
||||||
|
import "../style/settings.css"
|
||||||
|
import { useAppFetcher, useUser } from "../App.tsx"
|
||||||
|
import { Fetcher } from "../app/Fetcher.ts"
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
return <p>Hey !</p>
|
return (
|
||||||
|
<div id="settings-page">
|
||||||
|
<div id="content">
|
||||||
|
<AccountSettings />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function AccountSettings() {
|
||||||
|
return (
|
||||||
|
<div id="account">
|
||||||
|
<ProfilSettings/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProfilSettings() {
|
||||||
|
const nameRef = useRef<HTMLInputElement>(null)
|
||||||
|
const emailRef = useRef<HTMLInputElement>(null)
|
||||||
|
const passwordRef = useRef<HTMLInputElement>(null)
|
||||||
|
const confirmPasswordRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const fetcher = useAppFetcher()
|
||||||
|
const [user, setUser] = useUser()
|
||||||
|
|
||||||
|
const [errorMessages, setErrorMessages] = useState<string[]>([])
|
||||||
|
|
||||||
|
|
||||||
|
const [modalShow, setModalShow] = useState(false)
|
||||||
|
const width = 150
|
||||||
|
|
||||||
|
console.log(user)
|
||||||
|
|
||||||
|
const profilePicture = user!.profilePicture
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{errorMessages.map(msg => (<div key={msg} className={"error-message"}>{msg}</div>))}
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Stack>
|
||||||
|
<div id="profile-picture">
|
||||||
|
<Image src={profilePicture} width={width} height={width} roundedCircle />
|
||||||
|
</div>
|
||||||
|
<Button variant="outline-primary" onClick={() => setModalShow(true)}>
|
||||||
|
Changer photo de profil
|
||||||
|
</Button>
|
||||||
|
<ProfileImageInputPopup
|
||||||
|
show={modalShow}
|
||||||
|
onHide={() => setModalShow(false)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Form>
|
||||||
|
<Form.Group className="mb-3" controlId="formUsername">
|
||||||
|
<Form.Label className="content">Nom d'utilisateur</Form.Label>
|
||||||
|
<Form.Control ref={nameRef} size="sm" defaultValue={user!.name} />
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3" controlId="formEmail">
|
||||||
|
<Form.Label className="content">Adresse mail</Form.Label>
|
||||||
|
<Form.Control ref={emailRef} size="sm" defaultValue={user!.email} type="email"
|
||||||
|
placeholder="Addresse email" />
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group className="mb-3" controlId="formPassword">
|
||||||
|
<Form.Label className="content">Mot de passe</Form.Label>
|
||||||
|
<Form.Control ref={passwordRef}
|
||||||
|
size="sm"
|
||||||
|
type="password"
|
||||||
|
placeholder="Mot de passe" />
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3" controlId="formConfirmPassword">
|
||||||
|
<Form.Label className="content">Confirmez le mot de passe</Form.Label>
|
||||||
|
<Form.Control ref={confirmPasswordRef}
|
||||||
|
size="sm"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirmation du mot de passe" />
|
||||||
|
</Form.Group>
|
||||||
|
<Button variant="outline-primary" type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
const name = nameRef.current!.value
|
||||||
|
const email = emailRef.current!.value
|
||||||
|
const password = passwordRef.current?.value
|
||||||
|
const confirmPassword = confirmPasswordRef.current?.value
|
||||||
|
|
||||||
|
console.log(password, confirmPassword, name, email)
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
setErrorMessages(["Les mots de passe ne correspondent pas !"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const req: AccountUpdateRequest = {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
}
|
||||||
|
if (password && password.length !== 0) {
|
||||||
|
req.password = password
|
||||||
|
}
|
||||||
|
const errors = await updateAccount(fetcher, req)
|
||||||
|
if (errors.length !== 0) {
|
||||||
|
setErrorMessages(errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setUser({...user!, email, name})
|
||||||
|
}}>Mettre
|
||||||
|
à jour</Button>
|
||||||
|
</Form>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AccountUpdateRequest {
|
||||||
|
name?: string
|
||||||
|
email?: string
|
||||||
|
profilePicture?: string
|
||||||
|
password?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAccount(fetcher: Fetcher, req: AccountUpdateRequest): Promise<string[]> {
|
||||||
|
const response = await fetcher.fetchAPI("user", req, "PUT")
|
||||||
|
if (response.ok)
|
||||||
|
return []
|
||||||
|
const body = await response.json()
|
||||||
|
return Object.entries(body)
|
||||||
|
.flatMap(([kind, messages]) => (messages as string[]).map(msg => `${kind}: ${msg}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProfileImageInputPopupProps {
|
||||||
|
show: boolean
|
||||||
|
onHide: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProfileImageInputPopup({ show, onHide }: ProfileImageInputPopupProps) {
|
||||||
|
const urlRef = useRef<HTMLInputElement>(null)
|
||||||
|
const [errorMessages, setErrorMessages] = useState<string[]>()
|
||||||
|
|
||||||
|
const fetcher = useAppFetcher()
|
||||||
|
const [user, setUser] = useUser()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={show}
|
||||||
|
onHide={onHide}
|
||||||
|
size="lg"
|
||||||
|
aria-labelledby="title-modal"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title id="title-modal">
|
||||||
|
Nouvelle photo de profil
|
||||||
|
</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
|
||||||
|
<Modal.Body>
|
||||||
|
{errorMessages?.map(msg => <div key={msg} className="error-message">{msg}</div>)}
|
||||||
|
<Form.Label>Saisissez le lien vers votre nouvelle photo de profil</Form.Label>
|
||||||
|
<Form.Control isInvalid={errorMessages?.length !== 0} ref={urlRef} type="input"
|
||||||
|
placeholder={"lien vers une image"} />
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button onClick={onHide}>Annuler</Button>
|
||||||
|
<Button onClick={async () => {
|
||||||
|
const url = urlRef.current!.value
|
||||||
|
const exists = await imageExists(url)
|
||||||
|
setErrorMessages(["Le lien ne renvoie vers aucune image !"])
|
||||||
|
if (!exists) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const errors = await updateAccount(fetcher, { profilePicture: url })
|
||||||
|
if (errors.length !== 0) {
|
||||||
|
setErrorMessages(errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setUser({...user!, profilePicture: url})
|
||||||
|
|
||||||
|
onHide()
|
||||||
|
}
|
||||||
|
}>Valider</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function imageExists(imageLink: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(imageLink)
|
||||||
|
|
||||||
|
console.log(response)
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const contentType = response.headers.get("content-type")
|
||||||
|
return contentType?.startsWith("image/") ?? false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
.error-message {
|
||||||
|
color: red
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#profile-picture {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
Loading…
Reference in new issue