diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..9d79d7178
Binary files /dev/null and b/.DS_Store differ
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..6b665aaa0
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "liveServer.settings.port": 5501
+}
diff --git a/Backend/database.sqlite b/Backend/database.sqlite
index f6bd01cd1..77804f1a9 100644
Binary files a/Backend/database.sqlite and b/Backend/database.sqlite differ
diff --git a/FrontEnd/assets/style.css b/FrontEnd/assets/style.css
index 7bca0ed1a..a90982af3 100644
--- a/FrontEnd/assets/style.css
+++ b/FrontEnd/assets/style.css
@@ -3,186 +3,562 @@
License: none (public domain)
*/
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-b, u, i, center,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td,
-article, aside, canvas, details, embed,
-figure, figcaption, footer, header, hgroup,
-menu, nav, output, ruby, section, summary,
-time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
-article, aside, details, figcaption, figure,
-footer, header, hgroup, menu, nav, section {
- display: block;
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
}
body {
- line-height: 1;
+ line-height: 1;
}
-ol, ul {
- list-style: none;
+ol,
+ul {
+ list-style: none;
}
-blockquote, q {
- quotes: none;
+blockquote,
+q {
+ quotes: none;
}
-blockquote:before, blockquote:after,
-q:before, q:after {
- content: '';
- content: none;
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+ content: "";
+ content: none;
}
table {
- border-collapse: collapse;
- border-spacing: 0;
+ border-collapse: collapse;
+ border-spacing: 0;
}
/** end reset css**/
body {
- max-width: 1140px;
- margin:auto;
- font-family: 'Work Sans' ;
- font-size: 14px;
+ max-width: 1140px;
+ margin: auto;
+ font-family: "Work Sans";
+ font-size: 14px;
+ background-color: #fffef8;
}
header {
- display: flex;
- justify-content: space-between;
- margin: 50px 0
+ display: flex;
+ justify-content: space-between;
+ margin: 50px 0;
}
section {
- margin: 50px 0
+ margin: 50px 0;
}
-h1{
- display: flex;
- flex-direction: column;
- font-family: 'Syne';
- font-size: 22px;
- font-weight: 800;
- color: #B1663C
+h1 {
+ display: flex;
+ flex-direction: column;
+ font-family: "Syne";
+ font-size: 22px;
+ font-weight: 800;
+ color: #b1663c;
}
h1 > span {
- font-family: 'Work Sans';
- font-size:10px;
- letter-spacing: 0.1em;
-;
+ font-family: "Work Sans";
+ font-size: 10px;
+ letter-spacing: 0.1em;
}
-h2{
- font-family: 'Syne';
- font-weight: 700;
- font-size: 30px;
- color: #1D6154
+h2 {
+ font-family: "Syne";
+ font-weight: 700;
+ font-size: 30px;
+ color: #1d6154;
}
-nav ul {
- display: flex;
- align-items: center;
- list-style-type: none;
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+/* Barre d'édition admin */
+.admin-bar {
+ box-sizing: border-box;
+ background-color: #000000;
+ color: white;
+ padding: 10px;
+ margin: 0;
+
+ /* position: absolute; */
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ column-gap: 25px;
+}
+
+.hidden {
+ display: none;
+}
+
+#edit-mode-btn {
+ background: transparent;
+ color: white;
+ border: none;
+ font-size: 16px;
+ column-gap: 10px;
+ display: flex;
+ cursor: pointer;
+}
+
+nav ul {
+ display: flex;
+ align-items: center;
+ list-style-type: none;
}
nav li {
- padding: 0 10px;
- font-size: 1.2em;
+ padding: 0 10px;
+ font-size: 1.2em;
}
li:hover {
- color: #B1663C;
+ color: #b1663c;
}
#introduction {
- display: flex;
- align-items: center;
+ display: flex;
+ align-items: center;
}
#introduction figure {
- flex: 1
+ flex: 1;
}
#introduction img {
- display: block;
- margin: auto;
- width: 80%;
+ display: block;
+ margin: auto;
+ width: 80%;
}
#introduction article {
- flex: 1
+ flex: 1;
}
#introduction h2 {
- margin-bottom: 1em;
+ margin-bottom: 1em;
}
#introduction p {
- margin-bottom: 0.5em;
-}
-#portfolio h2 {
- text-align: center;
- margin-bottom: 1em;
-}
-.gallery {
- width: 100%;
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- grid-column-gap: 20px;
- grid-row-gap: 20px;
+ margin-bottom: 0.5em;
}
-.gallery img {
- width: 100%;
+.filtres {
+ display: flex;
+ justify-content: center;
+ margin: 4rem 0;
+}
+.filtres button {
+ border-radius: 60px;
+ border: 1px solid #1d6154;
+ background-color: transparent;
+ font-size: 1.2rem;
+ font-family: "Syne";
+ padding: 0.5rem 1rem;
+ margin: 0rem 1rem;
+}
+.filtres button:hover {
+ background-color: #1d6154;
+ color: white;
}
+
#contact {
- width: 50%;
-margin: auto;
+ width: 50%;
+ margin: auto;
}
#contact > * {
- text-align: center;
-
+ text-align: center;
}
-#contact h2{
- margin-bottom: 20px;
+#contact h2 {
+ margin-bottom: 20px;
}
#contact form {
- text-align: left;
- margin-top:30px;
- display: flex;
- flex-direction: column;
+ text-align: left;
+ margin-top: 30px;
+ display: flex;
+ flex-direction: column;
}
#contact input {
- height: 50px;
- font-size: 1.2em;
- border: none;
- box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+ height: 50px;
+ font-size: 1.2em;
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
}
#contact label {
- margin: 2em 0 1em 0;
+ margin: 2em 0 1em 0;
}
#contact textarea {
- border: none;
- box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
}
-input[type="submit"]{
- font-family: 'Syne';
- font-weight: 700;
- color: white;
- background-color: #1D6154;
- margin : 2em auto ;
- width: 180px;
- text-align: center;
- border-radius: 60px ;
+input[type="submit"] {
+ font-family: "Syne";
+ font-weight: 700;
+ color: white;
+ background-color: #1d6154;
+ margin: 2em auto;
+ width: 180px;
+ text-align: center;
+ border-radius: 60px;
}
footer nav ul {
- display: flex;
- justify-content: flex-end;
- margin: 2em
+ display: flex;
+ justify-content: flex-end;
+ margin: 2em;
+}
+
+/******* JAVASCRIPT PROJETS *********/
+.projets-section h2 {
+ text-align: center;
+ margin-bottom: 1rem;
+ margin-top: 8rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ column-gap: 20px;
+}
+
+.add-project-btn {
+ display: inline-block;
+ background: transparent;
+ border: none;
+ display: flex;
+ column-gap: 10px;
+ margin-top: 5px;
+}
+.projets {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.projets {
+ margin-top: 60px;
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-column-gap: 20px;
+ grid-row-gap: 20px;
+}
+
+.projets img {
+ width: 100%;
+}
+
+/********** login.html **********/
+
+#login {
+ height: 50vh;
+ width: 25%;
+ margin: auto;
+ margin: 12rem auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+#login h2 {
+ text-align: center;
+ margin-bottom: 3rem;
+}
+
+#login form {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: left;
+}
+
+#login input {
+ height: 50px;
+ font-size: 1.2em;
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+}
+
+#login label {
+ margin: 2em 0 1em 0;
+ text-align: left;
+}
+
+#forgot-password {
+ text-align: center;
+ margin-top: 1rem;
+}
+
+.error-message {
+ color: red;
+ font-weight: bold;
+ margin-top: 10px;
}
+/********** modale **********/
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.7);
+ z-index: 9999;
+}
+
+.modal-content {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #fff;
+ padding: 50px 80px;
+ max-height: 730px;
+ max-width: 630px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ overflow-y: auto;
+}
+
+.modal-content h3 {
+ color: #000;
+
+ font-size: 1.625rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+}
+
+.modal-content button {
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ align-self: flex-end;
+ margin-right: -10%;
+}
+
+#existing-projects {
+ width: 100%;
+ border-bottom: #b3b3b3 solid 1px;
+}
+
+#existing-projects {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
+ grid-column-gap: 10px;
+ grid-row-gap: 10px;
+ margin-top: 30px;
+ padding-bottom: 30px;
+}
+
+#existing-projects img {
+ width: 100%;
+}
+
+#add-photo {
+ font-family: "Syne";
+ font-weight: 700;
+ font-size: 0.8rem;
+ color: white;
+ background-color: #1d6154;
+ margin: 30px auto;
+ padding: 10px 50px;
+ width: 240px;
+ text-align: center;
+ border-radius: 60px;
+}
+
+.hidden {
+ display: none;
+}
+
+.img-container {
+ position: relative;
+}
+
+.delete-icon {
+ position: absolute;
+ top: 0;
+ right: 10px;
+
+ cursor: pointer;
+}
+
+.delete-icon i {
+ font-size: 1rem;
+ color: white;
+ padding: 0.25rem;
+ background-color: black;
+ border: 1px solid black;
+ border-radius: 3px;
+}
+
+.modal-content-form {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #fff;
+ padding: 50px 80px;
+ max-height: 730px;
+ width: 630px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+.hidden {
+ display: none;
+}
+
+/* #contact {
+ width: 50%;
+ margin: auto;
+} */
+.modal-content-form > * {
+ text-align: center;
+}
+.modal-content-form h3 {
+ color: #000;
+ font-size: 1.625rem;
+ font-style: normal;
+ font-weight: 400;
+ line-height: normal;
+}
+
+.modal-content-form form {
+ text-align: left;
+ margin-top: 30px;
+ display: flex;
+ flex-direction: column;
+}
+
+image-upload {
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+}
+.modal-content-form input {
+ height: 50px;
+ font-size: 1.2em;
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+}
+.modal-content-form label {
+ margin: 2em 0 1em 0;
+}
+.modal-content-form text {
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+}
+
+.modal-content-form button {
+ font-family: "Syne";
+ font-weight: 700;
+ color: white;
+ background-color: #1d6154;
+ margin: 2em auto;
+ width: 180px;
+ text-align: center;
+ border-radius: 60px;
+}
+
+select {
+ border: none;
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.09);
+}
diff --git a/FrontEnd/complet.txt b/FrontEnd/complet.txt
new file mode 100644
index 000000000..ded0c7017
--- /dev/null
+++ b/FrontEnd/complet.txt
@@ -0,0 +1,457 @@
+main.js
+
+import { initWorks, deleteWorks } from "./works2.js";
+import { initLoginForm, checkTokenLogin } from "./login2.js";
+import { toggleModal, populateModalWithExistingProjects } from "./modal.js";
+
+// Vérifier l'état de connexion de l'utilisateur
+document.addEventListener("DOMContentLoaded", checkTokenLogin);
+
+initWorks();
+
+// // ---------------MODAL----------------
+// Ajout d'un écouteur d'événements pour le bouton "Édition"
+const editingBtn = document.getElementById("edit-mode-btn");
+editingBtn.addEventListener("click", function () {
+ toggleModal(true);
+ populateModalWithExistingProjects();
+});
+
+// Ajout d'un écouteur d'événements pour le bouton de fermeture de la fenêtre modale
+document
+ .getElementById("close-modal")
+ main2.js
+
+ .addEventListener("click", () => toggleModal(false));
+
+// Fermer la fenêtre modale en cliquant en dehors de la zone de contenu
+document
+ .getElementById("edit-modal")
+ .addEventListener("click", function (event) {
+ const modalContent = document.querySelector(".modal-content");
+ if (!modalContent.contains(event.target)) {
+ toggleModal(false);
+ }
+ });
+
+// // ---------------DELETE----------------
+deleteWorks();
+
+// // ---------------LOGIN----------------
+initLoginForm();
+
+
+
+works2.js
+
+// Factory functions pour la création d'éléments DOM
+function createFigure(projet) {
+ const projetFigure = document.createElement("figure");
+ projetFigure.dataset.id = projet.id;
+ // console.log(projet.id);
+ const projetImg = document.createElement("img");
+ projetImg.src = projet.imageUrl;
+ projetFigure.appendChild(projetImg);
+
+ const projetCaption = document.createElement("figcaption");
+ projetCaption.innerText = projet.title;
+ projetFigure.appendChild(projetCaption);
+
+ return projetFigure;
+}
+
+function createFilterButton(categoryName, callback) {
+ const btn = document.createElement("button");
+ btn.innerText = categoryName;
+ btn.addEventListener("click", callback);
+ return btn;
+}
+
+// Requete pour récupérer les données de l'API
+export async function fetchWorks() {
+ try {
+ const response = await fetch("http://localhost:5678/api/works");
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+// Affichage des projets
+export function displayWorks(works, container) {
+ container.innerHTML = ""; // Effacer tout le contenu actuel
+ works.forEach((projet) => {
+ const projetFigure = createFigure(projet);
+ container.appendChild(projetFigure);
+ });
+}
+
+// Setup des boutons de filtre
+export function setupButtons(works, filterContainer, displayContainer) {
+ const categories = works.map((item) => item.category.name);
+ const uniqueCategories = [...new Set(categories)];
+
+ const btnAll = createFilterButton("Tous", () =>
+ displayWorks(works, displayContainer)
+ );
+ filterContainer.appendChild(btnAll);
+
+ uniqueCategories.forEach((categoryName) => {
+ const btn = createFilterButton(categoryName, () => {
+ const filteredWorks = works.filter(
+ (item) => item.category.name === categoryName
+ );
+ displayWorks(filteredWorks, displayContainer);
+ });
+ filterContainer.appendChild(btn);
+ });
+}
+
+// // ---------------DELETE----------------
+export function deleteWorks() {
+ const deleteExistingProjects = document.getElementById("existing-projects");
+
+ deleteExistingProjects.addEventListener("click", async function (event) {
+ event.preventDefault();
+ // console.log("Event triggered", event.target);OK
+ const imgContainer = event.target.closest(".img-container");
+ const deleteIcon = event.target.closest(".delete-icon");
+ // // console.log(deleteIcon); OK
+ // // console.log(imgContainer);OK
+ if (deleteIcon && imgContainer) {
+ const projetId = imgContainer.dataset.id;
+ const token = localStorage.getItem("token");
+
+ // Supprimez le projet de la base de données via AJAX
+
+ const response = await fetch(
+ `http://localhost:5678/api/works/${projetId}`,
+ {
+ method: "DELETE",
+ headers: {
+ Authorization: `Bearer ${token}`, // Ajoutez le token d'authentification dans le header
+ },
+ }
+ );
+
+ if (response.ok) {
+ // Supprimez le projet du DOM dans la fenêtre modale et dans la div .projets
+ document
+ .querySelector(`.projets figure[data-id="${projetId}"]`)
+ .remove();
+ document
+ .querySelector(`#existing-projects figure[data-id="${projetId}"]`)
+ .remove();
+ }
+ }
+ });
+}
+
+// Exécution
+
+export async function initWorks() {
+ const works = await fetchWorks();
+
+ const sectionProjet = document.querySelector(".projets");
+ displayWorks(works, sectionProjet);
+
+ const filtresDiv = document.querySelector(".filtres");
+ setupButtons(works, filtresDiv, sectionProjet);
+}
+
+login2.js
+
+// Factory pour créer une fonction de récupération de valeur d'élément DOM
+function createDOMValueGetter(selector) {
+ return function () {
+ const element = document.querySelector(selector);
+ return element ? element.value : null;
+ };
+}
+
+// Factory pour la requête HTTP
+function createAPIPostRequest(url) {
+ return async function (body) {
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ const data = await response.json();
+ return { data, status: response.status };
+ } catch (error) {
+ console.error("Une erreur est survenue", error);
+ return null;
+ }
+ };
+}
+
+// Gestion de la soumission du formulaire
+async function handleFormSubmission(event) {
+ event.preventDefault();
+
+ const getEmail = createDOMValueGetter("#login-email");
+ const getPassword = createDOMValueGetter("#login-password");
+ const loginUser = createAPIPostRequest(
+ "http://localhost:5678/api/users/login"
+ );
+
+ const email = getEmail();
+ const password = getPassword();
+ const response = await loginUser({ email, password });
+
+ if (response && response.status === 200) {
+ localStorage.setItem("user", JSON.stringify(response.data.userId));
+ localStorage.setItem("token", response.data.token);
+ location.href = "index.html";
+ } else {
+ const errorMessageElement = document.getElementById("error-message");
+ errorMessageElement.textContent = "Identifiant ou mot de passe incorrect";
+ }
+}
+
+// Vérifier l'état de connexion de l'utilisateur
+function checkTokenLogin() {
+ const tokenAuth = localStorage.getItem("token");
+ const loginLink = document.getElementById("login-link");
+ const adminBar = document.getElementById("admin-bar");
+ const allFilterBtn = document.querySelector(".filtres");
+ const modifierBtn = document.getElementById("add-project-btn");
+
+ if (tokenAuth) {
+ loginLink.textContent = "logout";
+ adminBar.classList.remove("hidden");
+
+ allFilterBtn.classList.remove("filtres");
+ } else {
+ loginLink.textContent = "login";
+ adminBar.classList.add("hidden");
+ modifierBtn.parentNode.removeChild(modifierBtn);
+ }
+}
+
+// Ajout de l'écouteur d'événements pour la soumission du formulaire
+function initLoginForm() {
+ const form = document.getElementById("login");
+ form.addEventListener("submit", handleFormSubmission);
+}
+
+// Vérification de l'état de connexion à l'initialisation
+export { checkTokenLogin, initLoginForm };
+
+
+modal.js
+
+// Fonction pour basculer la visibilité de la fenêtre modale
+export function toggleModal(isVisible) {
+ const modal = document.getElementById("edit-modal");
+ modal.classList.toggle("hidden", !isVisible);
+}
+
+// Fonction pour copier les projets existants dans la fenêtre modale
+export function populateModalWithExistingProjects() {
+ const existingProjects = document.querySelector(".projets").cloneNode(true);
+ const modalProjects = document.getElementById("existing-projects");
+ modalProjects.innerHTML = "";
+ // Filter pour ne garder que les images
+ const images = existingProjects.querySelectorAll("img");
+ images.forEach((img) => {
+ const imgContainer = document.createElement("div");
+ imgContainer.classList.add("img-container");
+ const originalFigure = img.closest("figure");
+ if (originalFigure && originalFigure.dataset.id) {
+ imgContainer.dataset.id = originalFigure.dataset.id;
+ }
+ const imgClone = img.cloneNode(true);
+ imgContainer.appendChild(imgClone);
+
+ const deleteIcon = document.createElement("button");
+ deleteIcon.classList.add("delete-icon");
+ deleteIcon.innerHTML = ' ';
+
+ imgContainer.appendChild(deleteIcon);
+
+ modalProjects.appendChild(imgContainer);
+ });
+}
+
+// ------------------------------------------------------------------------------------------------------------------
+
+
+
+index.html
+
+
+
+
+ Sophie Bluel - Architecte d'intérieur
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer d'espace
+
+ Je raconte votre histoire, je valorise vos idées. Je vous accompagne
+ de la conception à la livraison finale du chantier.
+
+
+ Chaque projet sera étudié en commun, de façon à mettre en valeur les
+ volumes, les matières et les couleurs dans le respect de l’esprit
+ des lieux et le choix adapté des matériaux. Le suivi du chantier
+ sera assuré dans le souci du détail, le respect du planning et du
+ budget.
+
+
+ En cas de besoin, une équipe pluridisciplinaire peut-être constituée
+ : architecte DPLG, décorateur(trice)
+
+
+
+
+
+
+ Mes projets
+
+
+ modifier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Galerie Photo
+
+
+ Ajouter une photo
+
+
+
+
+
+
+
+
+
+login.html
+
+
+
+
+
+ Sophie Bluel - Architecte d'intérieur - login
+
+
+
+
+
+
+
+
+
+ Sophie Bluel Architecte d'inteérieur
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FrontEnd/index.html b/FrontEnd/index.html
index 3f4a16298..0edc1f84e 100644
--- a/FrontEnd/index.html
+++ b/FrontEnd/index.html
@@ -1,109 +1,134 @@
-
-
- Sophie Bluel - Architecte d'intérieur
-
-
-
-
-
-
-
-
-
- Sophie Bluel Architecte d'inteérieur
-
-
- projets
- contact
- login
-
-
-
-
-
-
-
-
-
-
- Designer d'espace
- Je raconte votre histoire, je valorise vos idées. Je vous accompagne de la conception à la livraison finale du chantier.
- Chaque projet sera étudié en commun, de façon à mettre en valeur les volumes, les matières et les couleurs dans le respect de l’esprit des lieux et le choix adapté des matériaux. Le suivi du chantier sera assuré dans le souci du détail, le respect du planning et du budget.
- En cas de besoin, une équipe pluridisciplinaire peut-être constituée : architecte DPLG, décorateur(trice)
-
-
-
- Mes Projets
-
-
-
- Abajour Tahina
-
-
-
- Appartement Paris V
-
-
-
- Restaurant Sushisen - Londres
-
-
-
- Villa “La Balisiere” - Port Louis
-
-
-
- Structures Thermopolis
-
-
-
- Appartement Paris X
-
-
-
- Pavillon “Le coteau” - Cassis
-
-
-
- Villa Ferneze - Isola d’Elba
-
-
-
- Appartement Paris XVIII
-
-
-
- Bar “Lullaby” - Paris
-
-
-
- Hotel First Arte - New Delhi
-
-
-
-
-
+
+
+ Sophie Bluel - Architecte d'intérieur
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer d'espace
+
+ Je raconte votre histoire, je valorise vos idées. Je vous accompagne
+ de la conception à la livraison finale du chantier.
+
+
+ Chaque projet sera étudié en commun, de façon à mettre en valeur les
+ volumes, les matières et les couleurs dans le respect de l’esprit
+ des lieux et le choix adapté des matériaux. Le suivi du chantier
+ sera assuré dans le souci du détail, le respect du planning et du
+ budget.
+
+
+ En cas de besoin, une équipe pluridisciplinaire peut-être constituée
+ : architecte DPLG, décorateur(trice)
+
+
+
-
-
+
+
+ Mes projets
+
+
+ modifier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Galerie Photo
+
+
+ Ajouter une photo
+
+
+
+
+
+
+
+
+
diff --git a/FrontEnd/login.html b/FrontEnd/login.html
new file mode 100644
index 000000000..8c741e5b5
--- /dev/null
+++ b/FrontEnd/login.html
@@ -0,0 +1,53 @@
+
+
+
+
+ Sophie Bluel - Architecte d'intérieur - login
+
+
+
+
+
+
+
+
+
+ Sophie Bluel Architecte d'inteérieur
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FrontEnd/login2.js b/FrontEnd/login2.js
new file mode 100644
index 000000000..4d382fe48
--- /dev/null
+++ b/FrontEnd/login2.js
@@ -0,0 +1,86 @@
+// Factory pour créer une fonction de récupération de valeur d'élément DOM
+function createDOMValueGetter(selector) {
+ return function () {
+ const element = document.querySelector(selector);
+ return element ? element.value : null;
+ };
+}
+
+// Factory pour la requête HTTP
+function createAPIPostRequest(url) {
+ return async function (body) {
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ const data = await response.json();
+ return { data, status: response.status };
+ } catch (error) {
+ console.error("Une erreur est survenue", error);
+ return null;
+ }
+ };
+}
+
+// Gestion de la soumission du formulaire
+async function handleFormSubmission(event) {
+ event.preventDefault();
+
+ const getEmail = createDOMValueGetter("#login-email");
+ const getPassword = createDOMValueGetter("#login-password");
+ const loginUser = createAPIPostRequest(
+ "http://localhost:5678/api/users/login"
+ );
+
+ const email = getEmail();
+ const password = getPassword();
+ const response = await loginUser({ email, password });
+
+ if (response && response.status === 200) {
+ localStorage.setItem("user", JSON.stringify(response.data.userId));
+ localStorage.setItem("token", response.data.token);
+ location.href = "index.html";
+ } else {
+ const errorMessageElement = document.getElementById("error-message");
+ errorMessageElement.textContent = "Identifiant ou mot de passe incorrect";
+ }
+}
+
+// Vérifier l'état de connexion de l'utilisateur
+export function checkTokenLogin() {
+ const tokenAuth = localStorage.getItem("token");
+ const loginLink = document.getElementById("login-link");
+ const adminBar = document.getElementById("admin-bar");
+ const allFilterBtn = document.querySelector(".filtres");
+ const modifierBtn = document.getElementById("add-project-btn");
+
+ if (tokenAuth) {
+ loginLink.textContent = "logout";
+ if (adminBar) {
+ adminBar.classList.remove("hidden");
+ allFilterBtn.classList.add("hidden");
+ }
+ } else {
+ loginLink.textContent = "login";
+ if (adminBar) {
+ adminBar.classList.add("hidden");
+ modifierBtn.parentNode.removeChild(modifierBtn);
+ }
+ }
+}
+
+// Ajout de l'écouteur d'événements pour la soumission du formulaire
+export function initLoginForm() {
+ const form = document.getElementById("login");
+ if (form) {
+ form.addEventListener("submit", handleFormSubmission);
+ }
+}
+// ----------------------------
+document.addEventListener("DOMContentLoaded", checkTokenLogin);
+initLoginForm();
diff --git a/FrontEnd/main2.js b/FrontEnd/main2.js
new file mode 100644
index 000000000..3614c915c
--- /dev/null
+++ b/FrontEnd/main2.js
@@ -0,0 +1,29 @@
+import {
+ fetchWorks,
+ displayWorks,
+ setupButtons,
+ deleteWorks,
+} from "./works2.js";
+import { initLoginForm, checkTokenLogin } from "./login2.js";
+// import { toggleModal, populateModalWithExistingProjects } from "./modal.js";
+
+// // ---------------LOGIN----------------
+checkTokenLogin;
+
+initLoginForm();
+
+(async () => {
+ const works = await fetchWorks();
+
+ const sectionProjet = document.querySelector(".projets");
+ displayWorks(works, sectionProjet);
+
+ const filtresDiv = document.querySelector(".filtres");
+ setupButtons(works, filtresDiv, sectionProjet);
+})();
+
+deleteWorks();
+// Vérifier l'état de connexion de l'utilisateur
+
+// // ---------------DELETE----------------
+// deleteWorks();
diff --git a/FrontEnd/modal.js b/FrontEnd/modal.js
new file mode 100644
index 000000000..3974c07be
--- /dev/null
+++ b/FrontEnd/modal.js
@@ -0,0 +1,119 @@
+// Fonction pour basculer la visibilité de la fenêtre modale
+function toggleModal(isVisible) {
+ const modal = document.getElementById("edit-modal");
+ modal.classList.toggle("hidden", !isVisible);
+}
+
+// Fonction pour copier les projets existants dans la fenêtre modale
+function importModalWithExistingProjects() {
+ const existingProjects = document.querySelector(".projets").cloneNode(true);
+ const modalProjects = document.getElementById("existing-projects");
+ modalProjects.innerHTML = "";
+ // Filter pour ne garder que les images
+ const images = existingProjects.querySelectorAll("img");
+ images.forEach((img) => {
+ const imgContainer = document.createElement("div");
+ imgContainer.classList.add("img-container");
+ const originalFigure = img.closest("figure");
+ if (originalFigure && originalFigure.dataset.id) {
+ imgContainer.dataset.id = originalFigure.dataset.id;
+ }
+ const imgClone = img.cloneNode(true);
+ imgContainer.appendChild(imgClone);
+
+ const deleteIcon = document.createElement("button");
+ deleteIcon.classList.add("delete-icon");
+ deleteIcon.innerHTML = ' ';
+
+ imgContainer.appendChild(deleteIcon);
+
+ modalProjects.appendChild(imgContainer);
+ });
+}
+// // ---------------MODAL----------------
+// Ajout d'un écouteur d'événements pour le bouton "Édition"
+const modalContent = document.querySelector(".modal-content");
+const modalContentForm = document.querySelector(".modal-content-form");
+const editingBtn = document.getElementById("edit-mode-btn");
+
+if (editingBtn) {
+ editingBtn.addEventListener("click", function () {
+ toggleModal(true);
+ importModalWithExistingProjects();
+ modalContent.classList.remove("hidden");
+ modalContentForm.classList.add("hidden");
+ });
+}
+
+// Ajout d'un écouteur d'événements pour le bouton de fermeture de la fenêtre modale
+
+document
+ .getElementById("close-modal")
+ .addEventListener("click", () => toggleModal(false));
+
+// Fermer la fenêtre modale en cliquant en dehors de la zone de contenu
+
+document
+ .getElementById("edit-modal")
+ .addEventListener("click", function (event) {
+ if (
+ !modalContent.contains(event.target) &&
+ !modalContentForm.contains(event.target)
+ ) {
+ toggleModal(false);
+ }
+ });
+
+// ---------------------Formulaire envoi photo---------------------------------------------------
+
+const addPhotoBtn = document.getElementById("add-photo");
+
+addPhotoBtn.addEventListener("click", function () {
+ modalContent.classList.add("hidden");
+ modalContentForm.classList.remove("hidden");
+});
+
+const addPhotoForm = document.getElementById("add-photo-form");
+// console.log(addPhotoForm);
+
+if (addPhotoForm)
+ addPhotoForm.addEventListener("submit", async function (event) {
+ event.preventDefault();
+
+ const imageUpload = document.getElementById("image-upload").files[0];
+ const projectTitle = document.getElementById("project-title").value;
+ const projectCategory = document.getElementById("project-category").value;
+
+ // Validation
+ if (!imageUpload || !projectTitle || !projectCategory) {
+ document.getElementById("form-error-message").innerText =
+ "Veuillez remplir tous les champs.";
+ return;
+ }
+
+ // Création de l'objet FormData pour envoyer le fichier et les autres données
+ const formData = new FormData();
+ // const title = formData.get("title"); // Récupérer une valeur
+ formData.append("image", imageUpload);
+ formData.append("title", projectTitle);
+ formData.append("categoryId", projectCategory);
+
+ // Envoi à l'API
+ const token = localStorage.getItem("token");
+ const response = await fetch("http://localhost:5678/api/works", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ body: formData,
+ });
+
+ if (response.ok) {
+ // Réponse de l'API si le formulaire est correctement envoyé
+ alert("Projet ajouté avec succès!");
+ location.reload(); // Recharger la page pour voir le nouveau projet
+ } else {
+ // Message d'erreur
+ alert("Une erreur s'est produite. Veuillez réessayer.");
+ }
+ });
diff --git a/FrontEnd/works2.js b/FrontEnd/works2.js
new file mode 100644
index 000000000..80d3b01fc
--- /dev/null
+++ b/FrontEnd/works2.js
@@ -0,0 +1,108 @@
+// Factory functions pour la création d'éléments DOM
+function createFigure(projet) {
+ const projetFigure = document.createElement("figure");
+ projetFigure.dataset.id = projet.id;
+ // console.log(projet.id);
+ const projetImg = document.createElement("img");
+ projetImg.src = projet.imageUrl;
+ projetFigure.appendChild(projetImg);
+
+ const projetCaption = document.createElement("figcaption");
+ projetCaption.innerText = projet.title;
+ projetFigure.appendChild(projetCaption);
+
+ return projetFigure;
+}
+
+function createFilterButton(categoryName, callback) {
+ const btn = document.createElement("button");
+ btn.innerText = categoryName;
+ btn.addEventListener("click", callback);
+ return btn;
+}
+
+// Requete pour récupérer les données de l'API
+export async function fetchWorks() {
+ try {
+ const response = await fetch("http://localhost:5678/api/works");
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+// Affichage des projets
+export function displayWorks(works, container) {
+ container.innerHTML = ""; // Effacer tout le contenu actuel
+ works.forEach((projet) => {
+ const projetFigure = createFigure(projet);
+ container.appendChild(projetFigure);
+ });
+}
+
+// Setup des boutons de filtre
+export function setupButtons(works, filterContainer, displayContainer) {
+ const categories = works.map((item) => item.category.name);
+ const uniqueCategories = [...new Set(categories)];
+
+ const btnAll = createFilterButton("Tous", () =>
+ displayWorks(works, displayContainer)
+ );
+
+ filterContainer.appendChild(btnAll);
+
+ uniqueCategories.forEach((categoryName) => {
+ const btn = createFilterButton(categoryName, () => {
+ const filteredWorks = works.filter(
+ (item) => item.category.name === categoryName
+ );
+ displayWorks(filteredWorks, displayContainer);
+ });
+ filterContainer.appendChild(btn);
+ });
+}
+
+// ---------------DELETE----------------
+export function deleteWorks() {
+ const deleteExistingProjects = document.getElementById("existing-projects");
+ console.log("deleteWorks tourne");
+
+ if (deleteExistingProjects)
+ deleteExistingProjects.addEventListener("click", async function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ // console.log("Event triggered", event.target);
+ const imgContainer = event.target.closest(".img-container");
+ const deleteIcon = event.target.closest(".delete-icon");
+ // // console.log(deleteIcon); OK
+ // // console.log(imgContainer);OK
+ if (deleteIcon && imgContainer) {
+ const projetId = imgContainer.dataset.id;
+ const token = localStorage.getItem("token");
+
+ // Supprimez le projet de la base de données via AJAX
+
+ const response = await fetch(
+ `http://localhost:5678/api/works/${projetId}`,
+ {
+ method: "DELETE",
+ headers: {
+ Authorization: `Bearer ${token}`, // Ajoutez le token d'authentification dans le header
+ },
+ }
+ );
+
+ if (response.ok) {
+ // Supprimer le projet du DOM dans la fenêtre modale et dans la div .projets
+ document
+ .querySelector(`.projets figure[data-id="${projetId}"]`)
+ .remove();
+ document
+ .querySelector(`#existing-projects figure[data-id="${projetId}"]`)
+ .remove();
+ }
+ }
+ });
+}
+// Exécution
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..5b279149c
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "Portfolio-architecte-sophie-bluel",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}