diff --git a/.env.exemple b/.env.exemple index 3452b5c..5136f91 100644 --- a/.env.exemple +++ b/.env.exemple @@ -1 +1,11 @@ -VITE_API_URL= \ No newline at end of file +VITE_API_URL= + +BROADCASTER = reverb +REVERB_KEY = local +REVERB_HOST = 127.0.0.1 +REVERB_PORT = 8080 +FORCE_TLS = false +DISABLE_STATS = true +ENCRYPTED = false +CLUSTER = mt1 +ENABLED_TRANSPORTS = [ws, wss] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c714a7e..1381366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,10 @@ "name": "sae-front", "version": "0.0.0", "dependencies": { + "@laravel/echo-react": "^2.2.6", "chart.js": "^4.5.1", + "laravel-echo": "^2.2.7", + "pusher-js": "^8.4.0", "react": "^19.1.1", "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", @@ -1116,6 +1119,20 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@laravel/echo-react": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@laravel/echo-react/-/echo-react-2.2.6.tgz", + "integrity": "sha512-xI5ekwEBhxKfSu/qIv9efXPLoM9RrszI51llWpNY4o71C1l5R7teMV8BbXA+1UbI0ijSyAI7MHxlkP6Cy3AVLw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "pusher-js": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "socket.io-client": "*" + } + }, "node_modules/@mdx-js/react": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", @@ -1535,6 +1552,13 @@ "win32" ] }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT", + "peer": true + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -2906,6 +2930,70 @@ "node": ">=14" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -3800,6 +3888,19 @@ "node": ">=6" } }, + "node_modules/laravel-echo": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.2.7.tgz", + "integrity": "sha512-MgD3ZFXqH5OOVdRjxNHPyQ0ijRr5+nLr7MtyF2XP+kRfhl+Qaa7qVzbtCn1HMgXuTn4SWH6ivn4qWVLlvRl8kg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "pusher-js": "*", + "socket.io-client": "*" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4023,7 +4124,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4410,6 +4510,15 @@ "node": ">=6" } }, + "node_modules/pusher-js": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz", + "integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==", + "license": "MIT", + "dependencies": { + "tweetnacl": "^1.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4788,6 +4897,72 @@ "dev": true, "license": "MIT" }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5205,6 +5380,12 @@ "dev": true, "license": "0BSD" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5638,6 +5819,15 @@ } } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 996fc4e..9e4e83d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "test": "vitest" }, "dependencies": { + "@laravel/echo-react": "^2.2.6", "chart.js": "^4.5.1", + "laravel-echo": "^2.2.7", + "pusher-js": "^8.4.0", "react": "^19.1.1", "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 7c91356..9f7046f 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,25 +1,54 @@ import styles from "./Header.module.css" import { Link, NavLink } from "react-router"; -import { useEffect, useRef, useState } from "react"; +import {useContext, useEffect, useRef, useState} from "react"; import getUserNotifications from "../../utils/notifications/getUserNotifications.js"; import deleteNotificationUser from "../../utils/notifications/deleteNotificationUser.js"; +import initEcho from "../../utils/echo/initEcho.js" +import readNotifications from "../../utils/notifications/readNotifications.js"; +import {AuthContext} from "../../contexts/auth/AuthContext.js"; + function Header() { + const { update } = useContext(AuthContext); const [notificationMenu, setnotificationMenu] = useState(false); - const [unreadNotification, setunreadNotification] = useState(true); + const [unreadNotification, setunreadNotification] = useState(false); const [mobileMenu, setMobileMenu] = useState(false); const [notifications, setNotifications] = useState([]); const notificationRef = useRef(null); + useEffect(() => { + const echo = initEcho(); + + echo.private("admins-private") + .listen(".user.created", (event) => { + const newNotification = { + id: Date.now(), + content: `Nouvel demande d'inscription : ${event.user.name} ${event.user.lastname}`, + is_new: true + }; + setNotifications(prevNotifications => [newNotification, ...prevNotifications]); + setunreadNotification(true); + }); + + return () => { + echo.leave("admins-private"); + }; + }, []); useEffect(() => { + async function loadNotifications() { const notifData = await getUserNotifications(); + const data = notifData || []; setNotifications(notifData || []); + const hasUnread = data.some(n => n.pivot && n.pivot.unread === 1); + + setunreadNotification(hasUnread); } loadNotifications(); + const handleClickOutside = (event) => { if ( notificationRef.current && @@ -40,12 +69,25 @@ function Header() { + const toggleNotificationMenu = async () => { + try { + setnotificationMenu(!notificationMenu); + if (unreadNotification) { + const result = await readNotifications(); + if (result) { + setunreadNotification(false); + update(); + } + } + } catch (err) { + console.error('Erreur suppression notification :', err); + } + } + + async function deleteNotification(notificationId) { try { const result = await deleteNotificationUser(notificationId); - - console.log(result); - if (result) { setNotifications(prev => prev.filter(n => n.id !== notificationId)); } @@ -81,10 +123,7 @@ function Header() {