diff --git a/backend/src/app.js b/backend/src/app.js index 51311d1..63a4375 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -1,5 +1,5 @@ // Load the express module to create a web application - +// const path = require("path"); const express = require("express"); const app = express(); diff --git a/backend/src/controllers/authControllers.js b/backend/src/controllers/authControllers.js index b54d1ae..ac7d2b0 100644 --- a/backend/src/controllers/authControllers.js +++ b/backend/src/controllers/authControllers.js @@ -1,4 +1,4 @@ -const { hash, argon2 } = require("argon2"); +const { hash, verify, argon2id } = require("argon2"); const jwt = require("jsonwebtoken"); // Import access to database tables const tables = require("../tables"); @@ -9,7 +9,7 @@ const add = (req, res) => { // We create our hashing options const hashingOptions = { - type: argon2.argon2id, + type: argon2id, memoryCost: 2 ** 16, timeCost: 5, parallelism: 1, @@ -45,10 +45,7 @@ const login = async (req, res, next) => { return; } - const verified = await argon2.verify( - user.hashed_password, - req.body.password - ); + const verified = await verify(user.hashed_password, req.body.password); if (verified) { // Respond with the user in JSON format (but without the hashed password) diff --git a/backend/src/router.js b/backend/src/router.js index a941814..694f040 100644 --- a/backend/src/router.js +++ b/backend/src/router.js @@ -3,11 +3,11 @@ const express = require("express"); const router = express.Router(); const authControllers = require("./controllers/authControllers"); -const { checkDatas } = require("./services/validateLogin"); +// const { checkDatas } = require("./services/validateLogin"); const userControllers = require("./controllers/userControllers"); const { validateUser } = require("./services/validateUser"); -const { verifyToken, hashPassword } = require("./services/auth"); +const { hashPassword } = require("./services/auth"); const artworksControllers = require("./controllers/artworkControllers"); const { validateArtwork } = require("./services/validateArtwork"); @@ -24,7 +24,7 @@ const { validateCapture } = require("./services/validateCapture"); // Authentification routes router.post("/register", authControllers.add); -router.post("/login", checkDatas, verifyToken, authControllers.login); +router.post("/login", authControllers.login); // Routes of users router.get("/users", userControllers.browse); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cb8014f..1efdc85 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,11 +7,14 @@ "dependencies": { "@react-hook/media-query": "^1.1.1", "axios": "^1.6.5", + "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-html5-camera-photo": "^1.5.11", "react-leaflet": "^4.2.1", "react-router-dom": "^6.14.2", "sass": "^1.69.7" @@ -1274,6 +1277,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -2501,6 +2512,17 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3377,6 +3399,11 @@ "node": ">=4" } }, + "node_modules/jslib-html5-camera-photo": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/jslib-html5-camera-photo/-/jslib-html5-camera-photo-3.3.4.tgz", + "integrity": "sha512-qysjLnP4bud0+g0qs5uA/7i569x+6ID2ufgezf9XQ+BE3EvhYjz177vi9WXLEuq+V6C/WXEv73NUICvHm5VGmQ==" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3422,6 +3449,14 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4063,6 +4098,34 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, + "node_modules/react-html5-camera-photo": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/react-html5-camera-photo/-/react-html5-camera-photo-1.5.11.tgz", + "integrity": "sha512-5OpdW66UmwKwd0ZHvy/U9tEZj19GrcG6RDRXjs1bFBgJoWzLJoJ7YNd8rmdrizV/6go/z9GrwoWMovqCUyaJgQ==", + "dependencies": { + "jslib-html5-camera-photo": "3.3.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -4726,8 +4789,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.4.0", @@ -5877,6 +5939,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" + }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -6785,6 +6852,14 @@ "flat-cache": "^3.0.4" } }, + "file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "requires": { + "tslib": "^2.4.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -7372,6 +7447,11 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "jslib-html5-camera-photo": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/jslib-html5-camera-photo/-/jslib-html5-camera-photo-3.3.4.tgz", + "integrity": "sha512-qysjLnP4bud0+g0qs5uA/7i569x+6ID2ufgezf9XQ+BE3EvhYjz177vi9WXLEuq+V6C/WXEv73NUICvHm5VGmQ==" + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -7408,6 +7488,11 @@ "object.values": "^1.1.6" } }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7852,6 +7937,24 @@ "scheduler": "^0.23.0" } }, + "react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "requires": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + } + }, + "react-html5-camera-photo": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/react-html5-camera-photo/-/react-html5-camera-photo-1.5.11.tgz", + "integrity": "sha512-5OpdW66UmwKwd0ZHvy/U9tEZj19GrcG6RDRXjs1bFBgJoWzLJoJ7YNd8rmdrizV/6go/z9GrwoWMovqCUyaJgQ==", + "requires": { + "jslib-html5-camera-photo": "3.3.4" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -8308,8 +8411,7 @@ "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "type-check": { "version": "0.4.0", diff --git a/frontend/package.json b/frontend/package.json index 47ff050..6c9ffd4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,11 +7,14 @@ "dependencies": { "@react-hook/media-query": "^1.1.1", "axios": "^1.6.5", + "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-html5-camera-photo": "^1.5.11", "react-leaflet": "^4.2.1", "react-router-dom": "^6.14.2", "sass": "^1.69.7" diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 959e216..51ae67a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,8 @@ -import Home from "./pages/Home"; +import { Outlet } from "react-router-dom"; import "./styles/commons.scss"; function App() { - return ; + return ; } export default App; diff --git a/frontend/src/assets/Camera.svg b/frontend/src/assets/Camera.svg new file mode 100644 index 0000000..2b6f91d --- /dev/null +++ b/frontend/src/assets/Camera.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/Upload.svg b/frontend/src/assets/Upload.svg new file mode 100644 index 0000000..606312e --- /dev/null +++ b/frontend/src/assets/Upload.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/frontend/src/components/Camera.jsx b/frontend/src/components/Camera.jsx new file mode 100644 index 0000000..c8c6408 --- /dev/null +++ b/frontend/src/components/Camera.jsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import CameraReact from "react-html5-camera-photo"; +import "react-html5-camera-photo/build/css/index.css"; +import CameraSVG from "../assets/Camera.svg"; +import "./Camera.scss"; + +function Camera() { + const [isCameraOpen, setIsCameraOpen] = useState(false); + + const handleTakePhoto = (dataUri) => { + // Faites quelque chose avec l'image capturée, par exemple, affichez-la ou téléchargez-la. + console.info(dataUri); + setIsCameraOpen(false); + }; + + const openCamera = () => { + setIsCameraOpen(true); + }; + + const closeCamera = () => { + setIsCameraOpen(false); + }; + + return ( +
+ {isCameraOpen ? ( +
+ + +
+ ) : ( +
+ +
+ )} +
+ ); +} + +export default Camera; diff --git a/frontend/src/components/Camera.scss b/frontend/src/components/Camera.scss new file mode 100644 index 0000000..df0ab57 --- /dev/null +++ b/frontend/src/components/Camera.scss @@ -0,0 +1,17 @@ +@import "../styles/commons.scss"; + +.camera-button { + background: none; + border: none; + padding: 0; + margin: 0; + font: inherit; + cursor: pointer; + outline: inherit; +} + +.Camera-Icon { + width: 20vw; + margin-bottom: 30vh; + background-color: $deep-purple; +} diff --git a/frontend/src/components/DropZone.jsx b/frontend/src/components/DropZone.jsx new file mode 100644 index 0000000..8c7fbba --- /dev/null +++ b/frontend/src/components/DropZone.jsx @@ -0,0 +1,48 @@ +import { useDropzone } from "react-dropzone"; +import axios from "axios"; +import Upload from "../assets/Upload.svg"; + +import "./DropZone.scss"; + +function DropZone() { + const onDrop = async (acceptedFiles) => { + const file = acceptedFiles[0]; + + // Créez un objet FormData pour envoyer le fichier au serveur + const formData = new FormData(); + formData.append("picture", file); + + try { + // Envoyez le fichier au serveur + const response = await axios.post("URL_DU_ENDPOINT_API", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + // Faites quelque chose avec la réponse du serveur si nécessaire + console.info(response.data); + } catch (error) { + // Gérez les erreurs d'envoi + console.error("Erreur lors de l'envoi du fichier", error); + } + }; + + const { getRootProps, getInputProps } = useDropzone({ onDrop }); + return ( +
+ + Upload icon +

Envoyer ma photo

+
+ ); +} + +export default DropZone; diff --git a/frontend/src/components/DropZone.scss b/frontend/src/components/DropZone.scss new file mode 100644 index 0000000..bc912c2 --- /dev/null +++ b/frontend/src/components/DropZone.scss @@ -0,0 +1,17 @@ +@import "../styles/commons.scss"; + +.dropZone { + border: 2px solid $yellow; + padding: 1rem; + border-radius: 15px; + text-align: center; + width: 28rem; + margin: auto; +} +.Upload-Icon { + width: 4rem; + background-color: $yellow; + border-radius: 45%; + padding: 0.4rem; + margin-top: 1rem; +} diff --git a/frontend/src/components/Login.jsx b/frontend/src/components/Login.jsx index 7c84654..15ed147 100644 --- a/frontend/src/components/Login.jsx +++ b/frontend/src/components/Login.jsx @@ -1,16 +1,19 @@ -import { useState } from "react"; +import { useState, useContext } from "react"; +import axios from "axios"; import { Link, useNavigate } from "react-router-dom"; +import { AuthContext } from "../context/AuthContext"; + import "../styles/modals.scss"; -import axios from "axios"; + import CrossButton from "../assets/picto/yellow/cross_yell.svg"; function Login() { + const { handleAuth } = useContext(AuthContext); const [loginInfo, setLoginInfo] = useState({ email: "", password: "", }); - // const connect = axios.create(import.meta.env.VITE_BACKEND_URL); const navigate = useNavigate(); const handleLoginRegister = (event) => { @@ -29,26 +32,20 @@ function Login() { try { // Appel à l'API pour demander une connexion - const response = await axios.post( + const res = await axios.post( `${import.meta.env.VITE_BACKEND_URL}/api/login`, loginInfo ); - - // Redirection vers la page de connexion si la création réussit - if (response.status === 200) { - navigate("/Profile"); - } else { - // Log des détails de la réponse en cas d'échec - console.info(response); - } + await localStorage.setItem("token", res.data.token); + await handleAuth(); + await navigate("/profil"); } catch (err) { - // Log des erreurs possibles console.error(err); } }; return ( - +
@@ -72,7 +69,7 @@ function Login() { />
- +
); } diff --git a/frontend/src/components/MapForm.jsx b/frontend/src/components/MapForm.jsx index 7b8a39b..b2497e3 100644 --- a/frontend/src/components/MapForm.jsx +++ b/frontend/src/components/MapForm.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import download from "../assets/panel-admin/download-svgrepo-com.svg"; +import Dropzone from "./DropZone"; function MapForm() { const [userLogged, setUserLogged] = useState(false); @@ -12,9 +12,7 @@ function MapForm() { {userLogged ? ( <>

Ajouter une oeuvre

-

Logo de Téléchargement

-

Télécharger ma photo

- download +
diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx new file mode 100644 index 0000000..699d1a8 --- /dev/null +++ b/frontend/src/context/AuthContext.jsx @@ -0,0 +1,41 @@ +import { createContext, useMemo, useState } from "react"; +import axios from "axios"; +import { jwtDecode } from "jwt-decode"; + +const AuthContext = createContext(); + +// eslint-disable-next-line react/prop-types +function AuthContextProvider({ children }) { + const [user, setUser] = useState({ is_administrator: 0 }); + + const handleAuth = async () => { + const getToken = localStorage.getItem("token"); + if (getToken) { + const decodeToken = jwtDecode(getToken); + const userId = decodeToken.user_id; + + try { + const { data } = await axios.get( + `${import.meta.env.VITE_BACKEND_URL}/api/users/${userId}` + ); + setUser(data); + } catch (error) { + console.warn("Une erreur est survenue!", error); + } + } + }; + const userMemo = useMemo( + () => ({ + user, + setUser, + handleAuth, + }), + [user, setUser, handleAuth] + ); + + return ( + {children} + ); +} + +export { AuthContext, AuthContextProvider }; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 90124bf..f1d4443 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -3,15 +3,17 @@ import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import App from "./App"; +import Home from "./pages/Home"; import Map from "./pages/Map"; import Error from "./pages/Error"; import Register from "./components/Register"; // import RGPD from "./pages/RGPD"; import Login from "./components/Login"; -import Profile from "./pages/Profile"; import Admin from "./pages/Admin"; +import Profile from "./pages/Profile"; import Layout from "./pages/Layout"; +import { AuthContextProvider } from "./context/AuthContext"; +import Gallery from "./pages/Gallery"; const router = createBrowserRouter([ { @@ -21,7 +23,7 @@ const router = createBrowserRouter([ children: [ { path: "/", - element: , + element: , }, { path: "/carte", @@ -32,29 +34,36 @@ const router = createBrowserRouter([ element: , }, { - path: "/profile", + path: "/inscription", + element: , + }, + + { + path: "/connexion", + element: , + }, + { + path: "profil", element: , }, + // { + // path: "/RGPD", + // element: , + // }, + { + path: "/galerie", + element: , + }, ], }, - { - path: "/inscription", - element: , - }, - { - path: "/connexion", - element: , - }, ]); -// { -// path: "/RGPD", -// element: , -// }, const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + ); diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index d6e6bea..f971036 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -17,13 +17,14 @@ function admin() { const [activeSection, setActiveSection] = useState("dashboard"); const [activeComponent, setActiveComponent] = useState("captures"); const [users, setUsers] = useState([]); + const [userIndex, setUserIndex] = useState(0); + const [userSlideResult, setUserSlideResult] = useState(0); + const [currentSlide, setCurrentSlide] = useState(1); const dashboardRef = useRef(null); const usersRef = useRef(null); const streetArtRef = useRef(null); const artistsRef = useRef(null); - console.info(users); - useEffect(() => { const handleScroll = () => { const offset = window.scrollY; @@ -67,6 +68,7 @@ function admin() { (user) => !user.is_administrator ); setUsers(usersPlayer); + setUserSlideResult(Math.ceil(usersPlayer.length)); }) .catch((error) => { console.error( @@ -76,6 +78,24 @@ function admin() { }); }, []); + const userCurrent = users.slice(userIndex, userIndex + 6); + + const nextUsersSlide = () => { + setUserIndex((index) => Math.min(index + 1, users.length)); + }; + + const prevUsersSlide = () => { + setUserIndex((index) => Math.max(index - 1, 0)); + }; + + const nextCurrentSlide = () => { + setCurrentSlide((index) => Math.min(index + 1)); + }; + + const prevCurrentSlide = () => { + setCurrentSlide((index) => Math.max(index - 1)); + }; + return (
{isMobile ? ( @@ -114,7 +134,7 @@ function admin() {
- {users.map((user) => ( + {userCurrent.map((user) => (
avatar du profil ))}
+
+ {userIndex > 0 && ( + + )} +

+ Pages : {currentSlide} / {userSlideResult} +

+ {userIndex < users.length - 1 && ( + + )} +

diff --git a/frontend/src/pages/Admin.scss b/frontend/src/pages/Admin.scss index 559f3e2..681a539 100644 --- a/frontend/src/pages/Admin.scss +++ b/frontend/src/pages/Admin.scss @@ -152,6 +152,13 @@ } } +.uti-btn { + margin-top: 3%; + display: flex; + justify-content: center; + gap: 8%; +} + .sa-grid { display: grid; grid-template-columns: repeat(3, 1fr); diff --git a/frontend/src/pages/Gallery.jsx b/frontend/src/pages/Gallery.jsx new file mode 100644 index 0000000..aa802c7 --- /dev/null +++ b/frontend/src/pages/Gallery.jsx @@ -0,0 +1,33 @@ +import { useEffect, useState } from "react"; +import axios from "axios"; +import "./Gallery.scss"; + +function Gallery() { + const [artworks, setArtworks] = useState([]); + + useEffect(() => { + axios + .get(`${import.meta.env.VITE_BACKEND_URL}/api/artworks`) + .then((res) => setArtworks(res.data)) + .catch((err) => + console.error("Erreur lors de la récupération des données :", err) + ); + }, []); + + return ( + <> +

Street Art

+
+ {artworks + ?.filter((_, index) => index < 14) + .map((artwork) => ( + + {artwork.title} + + ))} +
+ + ); +} + +export default Gallery; diff --git a/frontend/src/pages/Gallery.scss b/frontend/src/pages/Gallery.scss new file mode 100644 index 0000000..8272de9 --- /dev/null +++ b/frontend/src/pages/Gallery.scss @@ -0,0 +1,81 @@ +@import "../styles/commons.scss"; + +.gallery-title { + margin: 5rem; +} + +.gallery { + margin: 5rem 2.5rem 10rem; + border-radius: 10px; + border: 3px solid $extra-light-yellow; + min-height: 50vh; + + display: grid; + gap: 0.375rem; + padding: 0.375rem; + grid-template-columns: 1fr 1fr; + grid-template-rows: 15rem 15rem 15rem 10.5rem 4.5rem 15rem 10.5rem 4.5rem 15rem 15rem; + grid-template-areas: + "g1 g2" + "g3 g3" + "g4 g4" + "g5 g6" + "g5 g7" + "g8 g8" + "g9 g10" + "g11 g10" + "g12 g12" + "g13 g13" + "g14 g14"; + + @media screen and (min-width: 840px) { + grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-rows: repeat(20, 3rem); + grid-template-areas: + "g1 g2 g3 g4" + "g1 g2 g3 g4" + "g1 g2 g3 g4" + "g1 g2 g3 g4" + "g1 g2 g5 g4" + "g1 g2 g5 g4" + "g1 g2 g5 g4" + "g1 g6 g5 g4" + "g1 g6 g5 g4" + "g1 g6 g5 g7" + "g8 g6 g5 g7" + "g8 g9 g5 g7" + "g8 g9 g10 g7" + "g11 g9 g10 g7" + "g11 g9 g10 g7" + "g11 g9 g10 g12" + "g11 g13 g14 g12" + "g11 g13 g14 g12" + "g11 g13 g14 g12" + "g11 g13 g14 g12"; + } + + @for $i from 1 through 14 { + picture:nth-of-type(#{$i}) { + grid-area: g#{$i}; + + opacity: 0; + filter: blur(10rem); + animation: 3s ease imgGalleryAnimation; + animation-delay: #{$i} / 2s; + animation-fill-mode: forwards; + } + } + + img { + height: 100%; + width: 100%; + object-fit: cover; + } + + @keyframes imgGalleryAnimation { + to { + opacity: 1; + filter: blur(0); + } + } +} diff --git a/frontend/src/pages/Map.jsx b/frontend/src/pages/Map.jsx index 50c1c94..956289a 100644 --- a/frontend/src/pages/Map.jsx +++ b/frontend/src/pages/Map.jsx @@ -10,6 +10,7 @@ import "./Map.scss"; import MarkerSVG from "../assets/Map-Pin.svg"; import MapForm from "../components/MapForm"; import InfoStreetArt from "../components/InfoStreetArt"; +import Camera from "../components/Camera"; function Map() { const ZOOM_LEVEL = 11; @@ -164,34 +165,37 @@ function Map() { return (
{isMobile ? ( - - - {markers.map((marker) => ( - handleMarkerClick(marker), - }} - > - - - - - ))} - + <> + + + {markers.map((marker) => ( + handleMarkerClick(marker), + }} + > + + + + + ))} + + + ) : ( <> { + handleAuth(); + }, []); + return

Bienvenue sur la page profil!

; }