Add the feature to change theprofile Picture. Need to be secured
continuous-integration/drone/push Build is failing Details

d_yanis 1 year ago
parent 634b9cba65
commit 7877fb6e33

@ -13,113 +13,154 @@ import Col from 'react-bootstrap/Col';
import { updateSourceFile } from "typescript";
import { fetchAPI } from "../Fetcher";
import { fetchPOST } from "../Fetcher";
import axios from "axios";
import Modal from 'react-bootstrap/Modal';
import { Stack } from "react-bootstrap";
import * as http from 'follow-redirects/http';
import * as https from 'follow-redirects/https';
// import fetch from 'node-fetch';
export default function Settings({ user }: { user: User }) {
return (
<div id="main">
<Header username={user.name} />
<Body user={user} />
</div>
)
return (
<div id="main">
<Header username={user.name} />
<Body user={user} />
</div>
)
}
function Body({ user }: { user: User }) {
return (
<div id="body">
<div id="content">
<MainTitle title="Paramètres" id={null} />
<AccountSettings user={user} />
</div>
</div>
)
return (
<div id="body">
<div id="content">
<MainTitle title="Paramètres" id={null} />
<AccountSettings user={user} />
</div>
</div>
);
}
function AccountSettings({ user }: { user: User }) {
return (
<div id="account">
<SecondTitle title="Compte personnel" id={null} />
<ProfilSettings user={user} />
</div>
);
return (
<div id="account">
<SecondTitle title="Compte personnel" id={null} />
<ProfilSettings user={user} />
</div>
);
}
function ProfilSettings({ user }: { user: User }) {
// Utilisez useState pour gérer l'état du champ de saisie
// const [username, setUsername] = useState({user.username});
const nameRef = useRef<HTMLInputElement>(null);
const emailRef = useRef<HTMLInputElement>(null);
const [modalShow, setModalShow] = useState(false);
const width = 140;
const profilePicture = user.profilePicture;
console.log("profile :" + profilePicture);
return (
<Container>
<Row>
<Col>
<Stack>
<Image src={profilePicture} width={width} height={width} roundedCircle />
<Button variant="outline-primary" onClick={() => setModalShow(true)}>Changer photo de profil</Button>
<MyVerticallyCenteredModal
show={modalShow}
onHide={() => setModalShow(false)}
/>
</Stack>
// // Fonction pour mettre à jour l'état lorsqu'il y a un changement dans le champ de saisie
// const handleUsernameChange = (event : ChangeEvent<HTMLInputElement>) => {
// setUsername(event.target.value);
// };
</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} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" />
{/* <Form.Control readOnly onClick={() => alert("En cours de développement...")} ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" /> */}
</Form.Group>
<Button variant="outline-primary" type="button" onClick={() => updateAccountInfos(nameRef.current!.value, emailRef.current!.value)}>Mettre à jour</Button>
</Form>
</Col>
</Row>
</Container>
);
}
// return (
// <form id="account-content">
// <dl>
// <dt>Nom d'utilisateur</dt>
// {/* Utilisez la valeur de l'état et la fonction onChange */}
// <dd><input type="text" value={username} onChange={handleUsernameChange} /></dd>
// </dl>
// </form>
// );
function reload() {
fetchPOST("session/update", {});
location.reload();
}
const nameRef = useRef<HTMLInputElement>(null);
const emailRef = useRef<HTMLInputElement>(null);
function updateAccountInfos(name: string, email: string) {
fetchAPI("account/update/profile", {
name: name,
email: email
});
reload();
}
const width = 140;
const profilePicture = user.profilePicture;
console.log("profile :" + profilePicture);
return (
<Container>
<Row>
<Col>
<Image src={profilePicture} width={width} roundedCircle />
<Button variant="outline-primary" onClick={() => alert("En cours de développement...")}>Changer photo de profil</Button>
</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} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" />
{/* <Form.Control readOnly onClick={() => alert("En cours de développement...")} ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" /> */}
</Form.Group>
<Button variant="outline-primary" type="button" onClick={() => updateAccountInfos(nameRef.current!.value, emailRef.current!.value)}>Mettre à jour</Button>
</Form>
</Col>
function updateAccountPicture(lien: string) {
fetchAPI("account/update/profilePicture", {
lien: lien
});
reload();
}
</Row>
</Container>
// <div id="account">
// <div id="profil-picture">
// <Image src={profilePicture}roundedCircle />
// <Button variant="outline-primary">Changer photo de profil</Button>
// </div>
// <div id="account-infos">
// <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 readOnly onClick={() => alert("En cours de développement...")} ref={emailRef} id="control" size="sm" defaultValue={user.email} type="email" placeholder="Password" />
// </Form.Group>
// <Button variant="outline-primary" type="button" onClick={() => updateAccountInfos(nameRef.current!.value, emailRef.current!.value, user)}>Mettre à jour</Button>
// </Form>
// </div>
// </div>
);
function MyVerticallyCenteredModal(props: any) {
const urlRef = useRef<HTMLInputElement>(null);
return (
<Modal
{...props}
size="lg"
aria-labelledby="title-modal"
centered
>
<Modal.Header>
<Modal.Title id="title-modal" >
Nouvelle photo de profil
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Label>Nouvelle image</Form.Label>
<Form.Control ref={urlRef} type="input" />
</Modal.Body>
<Modal.Footer>
<Button onClick={props.onHide}>Annuler</Button>
<Button onClick={() => handleNewImage(urlRef.current!.value)}>Valider</Button>
</Modal.Footer>
</Modal>
);
}
function updateAccountInfos(name: string, email: string) {
fetchAPI("account/update/profile", {
name: name,
email: email
});
fetchPOST("account/update", {});
location.reload();
async function handleNewImage(lien: string) {
let exist = await testImage(lien);
console.log(exist);
if (exist) {
updateAccountPicture(lien);
}
}
async function testImage(lien: string) {
// try {
// const response = await axios.head(lien);
// console.log(response);
// // Vérifier le statut de la réponse (200 OK est considéré comme valide)
// if (response.status === 200) {
// // Vérifier le type de contenu pour s'assurer qu'il s'agit d'une image
// const contentType = response.headers['content-type'];
// if (contentType && contentType.startsWith('image/')) {
// return true;
// }
// return true;
// }
// return false;
// } catch (error) {
// console.error("Erreur lors de la requête HEAD:", error);
// return false;
// }
return true;
}

@ -10,7 +10,9 @@
"@types/node": "^16.18.59",
"@types/react": "^18.2.31",
"@types/react-dom": "^18.2.14",
"axios": "^1.6.7",
"bootstrap": "^5.3.2",
"node-fetch": "^3.3.2",
"react": "^18.2.0",
"react-bootstrap": "^2.10.0",
"react-dom": "^18.2.0",
@ -33,6 +35,7 @@
]
},
"devDependencies": {
"@types/follow-redirects": "^1.14.4",
"@vitejs/plugin-react": "^4.1.0",
"prettier": "^3.1.0",
"typescript": "^5.2.2",

@ -38,8 +38,9 @@ function getRoutes(): AltoRouter {
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc)));
$router->map("POST", "/account/update/profile", Action::auth(fn(Account $acc) => getAPIUserController()->updateProfile($acc)));
$router->map("POST", "/account/update/profilePicture", Action::auth(fn(Account $acc) => getAPIUserController()->updatePicture($acc)));
return $router;
return $router;
}
/**

@ -107,7 +107,7 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/team/[i:idTeam]/remove/[i:idMember]", Action::auth(fn(int $idTeam, int $idMember, SessionHandle $s) => getTeamController()->deleteMember($idTeam, $idMember, $s)));
$ar->map("GET", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->displayEditTeam($idTeam, $s)));
$ar->map("POST", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->editTeam($idTeam, $_POST, $s)));
$ar->map("POST", "/account/update", Action::auth(fn(SessionHandle $s) => getAuthController()->updateAccount($s)));
$ar->map("POST", "/session/update", Action::auth(fn(SessionHandle $s) => getAuthController()->updateAccount($s)));
return $ar;
}

@ -2,6 +2,7 @@
namespace IQBall\Api\Controller;
use Exception;
use IQBall\App\Control;
use IQBall\Core\Data\Account;
use IQBall\Core\Http\HttpCodes;
@ -31,7 +32,6 @@ class APIUserController {
* @return HttpResponse
*/
public function updateProfile(Account $account): HttpResponse {
error_log("Test");
return Control::runChecked([
"name" => [Validators::name()],
"email" => [Validators::email()]
@ -47,8 +47,50 @@ class APIUserController {
return HttpResponse::fromCode(HttpCodes::OK);
});
}
function testImage(string $lien) {
try {
$headers = get_headers($lien, 1);
// Vérifier le statut de la réponse (200 OK est considéré comme valide)
$statusCode = explode(' ', $headers[0])[1];
if ($statusCode === '200') {
// Vérifier le type de contenu pour s'assurer qu'il s'agit d'une image
$contentType = $headers['Content-Type'];
if ($contentType && strpos($contentType, 'image/') === 0) {
return true;
}
}
return false;
} catch (Exception $error) {
return false;
}
}
/**
* @param Account $account
* @return HttpResponse
*/
public function updatePicture(Account $account): HttpResponse {
return Control::runChecked([
"lien" => [Validators::everything()]
], function (HttpRequest $request) use ($account) {
error_log("test");
$failures = $this->model->updatePicture($request["lien"], $account->getUser()->getId());
if (!empty($failures)) {
//TODO find a system to handle Unauthorized error codes more easily from failures.
return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST);
}
return HttpResponse::fromCode(HttpCodes::OK);
});
// error_log("Test");
// return new HttpResponse(HttpCodes::OK, []);
}
}
}

@ -163,4 +163,16 @@ class AccountGateway {
':id' => [$id, PDO::PARAM_STR]
]);
}
public function changePicture(int $id, string $newLien) {
error_log($newLien);
$this->con->exec("
UPDATE Account
SET profilePicture = :lien
WHERE id = :id
", [
':lien' => [$newLien, PDO::PARAM_STR],
':id' => [$id, PDO::PARAM_STR]
]);
}
}

@ -70,6 +70,14 @@ class AuthModel {
return [ValidationFail::error("Account doesn't exist")];
}
public function updatePicture(string $lien, int $id) : array {
if(!empty($this->gateway->getAccountFromId($id))) {
$this->gateway->changePicture($id, $lien);
return [];
}
return [ValidationFail::error("Account doesn't exist")];
}
/**
* Generate a random base 64 string
* @return string
@ -97,4 +105,4 @@ class AuthModel {
public function updateAccount(MutableSessionHandle $session, string $token) {
$session->setAccount($this->gateway->getAccountFromToken($token));
}
}
}

@ -84,4 +84,16 @@ class Validators {
fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")]
);
}
}
public static function validateImageUrl(): Validator {
$urlPattern = '/\bhttps?:\/\/\S+?\.(?:jpg|jpeg|gif|png)\b/i';
return self::regex($urlPattern, "L'URL doit pointer vers une image (extension jpg, jpeg, gif, png).");
}
public static function everything(): Validator {
$pattern = '/.*/';
return self::regex($pattern, "L'URL doit pointer vers une image (extension jpg, jpeg, gif, png).");
}
}
Loading…
Cancel
Save