parent
62be8f2a0b
commit
6738ddcb67
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +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() {
|
||||
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