|
|
|
@ -1,129 +1,144 @@
|
|
|
|
|
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 { FormEvent, useCallback, 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)
|
|
|
|
|
|
|
|
|
|
export default function ProfileSettings() {
|
|
|
|
|
const fetcher = useAppFetcher()
|
|
|
|
|
const [user, setUser] = useUser()
|
|
|
|
|
|
|
|
|
|
const [errorMessages, setErrorMessages] = useState<string[]>([])
|
|
|
|
|
|
|
|
|
|
const [success, setSuccess] = useState(false)
|
|
|
|
|
|
|
|
|
|
const formRef = useRef<HTMLFormElement | null>(null)
|
|
|
|
|
|
|
|
|
|
const submitForm = useCallback(
|
|
|
|
|
async (e: FormEvent) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
const { name, email, password, confirmPassword } =
|
|
|
|
|
Object.fromEntries<string>(
|
|
|
|
|
new FormData(formRef.current!) as Iterable<
|
|
|
|
|
[PropertyKey, string]
|
|
|
|
|
>,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (password !== confirmPassword) {
|
|
|
|
|
setErrorMessages(["Les mots de passe ne correspondent pas !"])
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const req: AccountUpdateRequest = {
|
|
|
|
|
name: name,
|
|
|
|
|
email: email,
|
|
|
|
|
}
|
|
|
|
|
if (password && password.length !== 0) {
|
|
|
|
|
req.password = password
|
|
|
|
|
}
|
|
|
|
|
const errors = await updateAccount(fetcher, req)
|
|
|
|
|
if (errors.length !== 0) {
|
|
|
|
|
setErrorMessages(errors)
|
|
|
|
|
setSuccess(false)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setUser({ ...user!, email, name })
|
|
|
|
|
setSuccess(true)
|
|
|
|
|
formRef.current!.reset()
|
|
|
|
|
setErrorMessages([])
|
|
|
|
|
},
|
|
|
|
|
[fetcher, setUser, user],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const [modalShow, setModalShow] = useState(false)
|
|
|
|
|
const width = 150
|
|
|
|
|
|
|
|
|
|
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 id="settings-page">
|
|
|
|
|
<div id="settings-content">
|
|
|
|
|
{errorMessages.map((msg) => (
|
|
|
|
|
<div key={msg} className={"error-message"}>
|
|
|
|
|
{msg}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
{success && (
|
|
|
|
|
<p className={"success-message"}>
|
|
|
|
|
Modifications sauvegardées
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<div id="settings-inputs">
|
|
|
|
|
<div id="settings-profile-picture">
|
|
|
|
|
<div>
|
|
|
|
|
<div id="profile-picture">
|
|
|
|
|
<img
|
|
|
|
|
id="profile-picture-img"
|
|
|
|
|
src={profilePicture}
|
|
|
|
|
width={width}
|
|
|
|
|
height={width}
|
|
|
|
|
alt="profile-picture"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
className="settings-button"
|
|
|
|
|
onClick={() => setModalShow(true)}>
|
|
|
|
|
Changer photo de profil
|
|
|
|
|
</button>
|
|
|
|
|
<ProfileImageInputPopup
|
|
|
|
|
show={modalShow}
|
|
|
|
|
onHide={() => setModalShow(false)}
|
|
|
|
|
/>
|
|
|
|
|
</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
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<form
|
|
|
|
|
ref={formRef}
|
|
|
|
|
id="credentials-form"
|
|
|
|
|
onSubmit={submitForm}>
|
|
|
|
|
<p>Nom d'utilisateur</p>
|
|
|
|
|
<input
|
|
|
|
|
className="settings-input"
|
|
|
|
|
name="name"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder={"Nom d'utilisateur"}
|
|
|
|
|
defaultValue={user!.name}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<p>Addresse email</p>
|
|
|
|
|
<input
|
|
|
|
|
className="settings-input"
|
|
|
|
|
name="email"
|
|
|
|
|
type="email"
|
|
|
|
|
placeholder={"Addresse email"}
|
|
|
|
|
defaultValue={user!.email}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<p>Mot de passe</p>
|
|
|
|
|
<input
|
|
|
|
|
className="settings-input"
|
|
|
|
|
name="password"
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder={"Mot de passe"}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<p>Confirmez le mot de passe</p>
|
|
|
|
|
<input
|
|
|
|
|
className="settings-input"
|
|
|
|
|
name="confirmPassword"
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder={"Confirmation du mot de passe"}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
className="settings-button"
|
|
|
|
|
type="submit"
|
|
|
|
|
onClick={submitForm}>
|
|
|
|
|
Mettre à jour
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -134,13 +149,16 @@ interface AccountUpdateRequest {
|
|
|
|
|
password?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function updateAccount(fetcher: Fetcher, req: AccountUpdateRequest): Promise<string[]> {
|
|
|
|
|
async function updateAccount(
|
|
|
|
|
fetcher: Fetcher,
|
|
|
|
|
req: AccountUpdateRequest,
|
|
|
|
|
): Promise<string[]> {
|
|
|
|
|
const response = await fetcher.fetchAPI("user", req, "PUT")
|
|
|
|
|
if (response.ok)
|
|
|
|
|
return []
|
|
|
|
|
if (response.ok) return []
|
|
|
|
|
const body = await response.json()
|
|
|
|
|
return Object.entries(body)
|
|
|
|
|
.flatMap(([kind, messages]) => (messages as string[]).map(msg => `${kind}: ${msg}`))
|
|
|
|
|
return Object.entries(body).flatMap(([kind, messages]) =>
|
|
|
|
|
(messages as string[]).map((msg) => `${kind}: ${msg}`),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ProfileImageInputPopupProps {
|
|
|
|
@ -155,57 +173,76 @@ function ProfileImageInputPopup({ show, onHide }: ProfileImageInputPopupProps) {
|
|
|
|
|
const fetcher = useAppFetcher()
|
|
|
|
|
const [user, setUser] = useUser()
|
|
|
|
|
|
|
|
|
|
if (!show) return <></>
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
<div id="profile-picture-popup">
|
|
|
|
|
<div id="profile-picture-popup-content">
|
|
|
|
|
<div id="profile-picture-popup-header">
|
|
|
|
|
<p id="profile-picture-popup-title">
|
|
|
|
|
Nouvelle photo de profil
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{errorMessages?.map((msg) => (
|
|
|
|
|
<div key={msg} className="error-message">
|
|
|
|
|
{msg}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
<p id="profile-picture-popup-subtitle">
|
|
|
|
|
Saisissez le lien vers votre nouvelle photo de profil
|
|
|
|
|
</p>
|
|
|
|
|
<input
|
|
|
|
|
className={
|
|
|
|
|
`settings-input ` +
|
|
|
|
|
((errorMessages?.length ?? 0) === 0
|
|
|
|
|
? ""
|
|
|
|
|
: "invalid-input")
|
|
|
|
|
}
|
|
|
|
|
const errors = await updateAccount(fetcher, { profilePicture: url })
|
|
|
|
|
if (errors.length !== 0) {
|
|
|
|
|
setErrorMessages(errors)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
setUser({...user!, profilePicture: url})
|
|
|
|
|
|
|
|
|
|
onHide()
|
|
|
|
|
}
|
|
|
|
|
}>Valider</Button>
|
|
|
|
|
</Modal.Footer>
|
|
|
|
|
</Modal>
|
|
|
|
|
ref={urlRef}
|
|
|
|
|
type="input"
|
|
|
|
|
placeholder={"lien vers une image"}
|
|
|
|
|
/>
|
|
|
|
|
<div id="profile-picture-popup-footer">
|
|
|
|
|
<button className={"settings-button"} onClick={onHide}>
|
|
|
|
|
Annuler
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
className={"settings-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 })
|
|
|
|
|
setErrorMessages([])
|
|
|
|
|
onHide()
|
|
|
|
|
}}>
|
|
|
|
|
Valider
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function imageExists(imageLink: string) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(imageLink)
|
|
|
|
|
const response = await fetch(imageLink, { mode: "no-cors" })
|
|
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
const contentType = response.headers.get("content-type")
|
|
|
|
|
const contentType = response.headers.get("Content-type")
|
|
|
|
|
return contentType?.startsWith("image/") ?? false
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
@ -214,4 +251,3 @@ async function imageExists(imageLink: string) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|