Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 290 additions & 35 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@react-spring/three": "^9.7.5",
"@react-three/drei": "^9.120.4",
"@react-three/fiber": "^8.17.10",
"@stomp/stompjs": "^7.0.0",
"@tabler/core": "^1.0.0-beta21",
"@tabler/icons-react": "^3.26.0",
"axios": "^1.7.9",
Expand All @@ -49,6 +50,7 @@
"react-icons": "^5.3.0",
"react-router-dom": "^6.28.0",
"react-toastify": "^11.0.2",
"sockjs-client": "^1.6.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"three": "^0.171.0",
Expand Down
Binary file modified public/backpack/backpack.bin
Binary file not shown.
Binary file modified public/boots/boots.bin
Binary file not shown.
Binary file modified src/assets/backpack/backpack.bin
Binary file not shown.
Binary file modified src/assets/boots/boots.bin
Binary file not shown.
75 changes: 56 additions & 19 deletions src/help/HelpAdmin.jsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
import React, { useState, useEffect } from 'react';
import api from '@/security/auth/Api';
import DiscussionsList from '@/help/components/DiscussionsList';
import MessagesPanel from '@/help/components/MessagesPanel';
import MessageInput from '@/help/components/MessageInput';
import React, { useState, useEffect } from "react";
import { Client } from "@stomp/stompjs";
import SockJS from "sockjs-client";
import api from "@/security/auth/Api";
import DiscussionsList from "@/help/components/DiscussionsList";
import MessagesPanel from "@/help/components/MessagesPanel";
import MessageInput from "@/help/components/MessageInput";

const HelpAdmin = () => {
const [discussions, setDiscussions] = useState([]);
const [activeChat, setActiveChat] = useState(null);
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState('');
const [newMessage, setNewMessage] = useState("");
const [lastMessageId, setLastMessageId] = useState(null);
const [currentUser, setCurrentUser] = useState(null);
const [stompClient, setStompClient] = useState(null);

useEffect(() => {
const fetchCurrentUser = async () => {
try {
const response = await api.get('/user');
const response = await api.get("/user");
setCurrentUser(response.data);
} catch (error) {
console.error('Error fetching current user:', error);
console.error("Error fetching current user:", error);
}
};

const fetchDiscussions = async () => {
try {
const response = await api.get('/support/discussions');
const response = await api.get("/support/discussions");
const formattedDiscussions = formatDiscussions(response.data);
setDiscussions(formattedDiscussions);
if (formattedDiscussions.length > 0) {
handleChatSelect(formattedDiscussions[0].chatId);
}
} catch (error) {
console.error('[HelpAdmin] Error fetching discussions:', error);
console.error("[HelpAdmin] Error fetching discussions:", error);
}
};

Expand All @@ -42,11 +45,13 @@ const HelpAdmin = () => {
const formatDiscussions = (data) => {
return Object.entries(data)
.map(([userKey, messages]) => {
const sortedMessages = messages.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
const sortedMessages = messages.sort(
(a, b) => new Date(b.timestamp) - new Date(a.timestamp)
);
const lastMessage = sortedMessages[0];
return {
chatId: lastMessage.sender.role === 'USER' ? lastMessage.sender.id : lastMessage.receivers[0].id,
userName: lastMessage.sender.role === 'USER'
chatId: lastMessage.sender.role === "USER" ? lastMessage.sender.id : lastMessage.receivers[0].id,
userName: lastMessage.sender.role === "USER"
? `${lastMessage.sender.firstName} ${lastMessage.sender.lastName}`
: `${lastMessage.receivers[0].firstName} ${lastMessage.receivers[0].lastName}`,
lastMessage: lastMessage.content,
Expand All @@ -62,13 +67,13 @@ const HelpAdmin = () => {
const response = await api.get(`/messages/user/${chatId}/admins`);
setMessages(response.data);
} catch (error) {
console.error('[HelpAdmin] Error fetching messages:', error);
console.error("[HelpAdmin] Error fetching messages:", error);
}
setLastMessageId(null);
};

const sendMessage = async () => {
if (newMessage.trim() === '' || !activeChat || !currentUser) return;
if (newMessage.trim() === "" || !activeChat || !currentUser) return;

try {
const messageData = {
Expand All @@ -77,21 +82,53 @@ const HelpAdmin = () => {
receivers: [{ id: activeChat }],
};

const response = await api.post('/messages', messageData);
const response = await api.post("/messages", messageData);
setMessages((prevMessages) => [
...prevMessages,
{ ...response.data },
]);
setNewMessage('');
setNewMessage("");
setLastMessageId(response.data.id);

const discussions = await api.get('/support/discussions');
const discussions = await api.get("/support/discussions");
setDiscussions(formatDiscussions(discussions.data));
} catch (error) {
console.error('[HelpAdmin] Error sending message:', error);
console.error("[HelpAdmin] Error sending message:", error);
}
};

useEffect(() => {
// Configurer WebSocket avec StompJS et SockJS
const client = new Client({
brokerURL: "ws://localhost:8080/ws", // L'URL de ton serveur WebSocket
connectHeaders: {},
debug: (str) => {
console.log(str);
},
onConnect: () => {
console.log("Connected to WebSocket");
// S'abonner à un topic spécifique pour recevoir les messages
client.subscribe(`/topic/messages/${activeChat}`, (messageOutput) => {
const message = JSON.parse(messageOutput.body);
setMessages((prevMessages) => [...prevMessages, message]);
});
},
onDisconnect: () => {
console.log("Disconnected from WebSocket");
},
reconnectDelay: 5000,
});

client.activate();
setStompClient(client);

return () => {
if (client) {
client.deactivate();
}
};
}, [activeChat]);

return (
<div className="h-[calc(100vh-72px)] overflow-hidden">
<div className="grid grid-cols-[25%_75%] h-full m-0 p-0">
Expand Down
83 changes: 50 additions & 33 deletions src/help/Helper.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import React, { useState, useEffect, useRef } from "react";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { useAuth } from "@/security/auth/AuthContext";
import api from "@/security/auth/Api";
import { Client } from "@stomp/stompjs";
import SockJS from "sockjs-client";

const Helper = () => {
const { getId, user } = useAuth(); // Récupère l'ID et les informations de l'utilisateur connecté
Expand All @@ -19,6 +15,7 @@ const Helper = () => {
const [lastMessageId, setLastMessageId] = useState(null); // ID du dernier message
const [loading, setLoading] = useState(false); // Indicateur de chargement
const messagesEndRef = useRef(null); // Référence pour le scroll automatique
const [stompClient, setStompClient] = useState(null);

// Charger les messages au montage du composant
useEffect(() => {
Expand All @@ -29,7 +26,6 @@ const Helper = () => {
setLoading(true);
try {
const response = await api.get(`/messages/user/${userId}/admins`); // Récupère les messages avec les admins
console.log("Messages fetched:", response.data);
setMessages(response.data);
scrollToBottom(); // Scrolle automatiquement en bas après le chargement
} catch (error) {
Expand All @@ -42,7 +38,14 @@ const Helper = () => {
fetchMessages();
}, [getId, user]);

// Envoyer un message à tous les administrateurs
// Fonction pour scroller automatiquement en bas
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
}
};

// Envoyer un message à tous les administrateurs via WebSocket
const sendMessage = async () => {
if (!newMessage.trim() || user.role !== "USER") return; // Seul le USER peut envoyer des messages

Expand All @@ -54,41 +57,58 @@ const Helper = () => {
};

try {
const response = await api.post("/messages/from-user", messagePayload); // Envoie le message à tous les admins
console.log("Response from server:", response.data);
// Envoie le message via WebSocket
stompClient.send("/app/message", {}, JSON.stringify(messagePayload));

// Met à jour les messages localement sans rechargement
const newMessages = Array.isArray(response.data) ? response.data : [response.data];
setMessages((prevMessages) => [
...prevMessages,
...newMessages.map((msg) => ({
...msg,
sender: msg.sender || { id: getId(), firstName: "You" }, // Affiche "You" si pas défini
})),
{ ...messagePayload, sender: { id: getId(), firstName: "You" } },
]);
setNewMessage(""); // Réinitialise le champ
setLastMessageId(response.data.id); // Définit l'ID du dernier message
scrollToBottom(); // Scrolle automatiquement après l'envoi
} catch (error) {
console.error("Error sending message to all admins:", error);
console.error("Error sending message:", error);
}
};

// Fonction pour scroller automatiquement en bas
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
}
};
useEffect(() => {
if (!user) return;

const client = new Client({
brokerURL: "ws://localhost:8080/ws", // L'URL de ton serveur WebSocket
connectHeaders: {},
debug: (str) => {
console.log(str);
},
onConnect: () => {
console.log("Connected to WebSocket");
// S'abonner au topic pour recevoir les messages des admins
client.subscribe(`/topic/messages/${getId()}`, (messageOutput) => {
const message = JSON.parse(messageOutput.body);
setMessages((prevMessages) => [...prevMessages, message]);
});
},
onDisconnect: () => {
console.log("Disconnected from WebSocket");
},
reconnectDelay: 5000,
});

client.activate();
setStompClient(client);

return () => {
if (client) {
client.deactivate();
}
};
}, [getId, user]);

return (
<Dialog>
{/* Le bouton qui ouvre la boîte de dialogue */}
<DialogTrigger asChild>
<Button className="px-7 py-2">Help</Button>
</DialogTrigger>

{/* Contenu de la boîte de dialogue */}
<DialogContent className="flex flex-col max-h-[600px]">
<DialogHeader>
<DialogTitle>Discussion</DialogTitle>
Expand All @@ -111,15 +131,12 @@ const Helper = () => {
message.sender.id === getId()
? "bg-green-500 text-white"
: "bg-gray-200 dark:bg-gray-600 text-gray-900 dark:text-white"
} ${
lastMessageId === message.id ? "animate-pop" : ""
}`} // Ajout de l'animation
} ${lastMessageId === message.id ? "animate-pop" : ""}`}
>
<p className="text-sm">{message.content}</p>
</div>
</div>
))}
{/* Référence pour scroller en bas */}
<div ref={messagesEndRef} />
</div>
)}
Expand All @@ -141,4 +158,4 @@ const Helper = () => {
);
};

export default Helper;
export default Helper;
1 change: 0 additions & 1 deletion src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'


createRoot(document.getElementById('root')).render(
<StrictMode>

Expand Down
19 changes: 14 additions & 5 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import path from "path"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
import path from "path";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs'
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
// Création d'un alias pour `global` qui pointe vers `window`
global: 'window', // Utilise `window` au lieu de `global`
},
},
})
define: {
// Ajout d'une définition globale pour "global"
global: 'window',
},
optimizeDeps: {
include: ['sockjs-client'], // Assure-toi que `sockjs-client` soit bien optimisé
},
});