diff --git a/client/package-lock.json b/client/package-lock.json index 11cb999..97b0444 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -22,6 +22,7 @@ "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.3", "@reduxjs/toolkit": "^2.2.7", @@ -44,6 +45,7 @@ "react-icons": "^5.3.0", "react-markdown": "^9.0.1", "react-redux": "^9.1.2", + "react-resizable-panels": "^2.1.4", "react-router-dom": "^6.25.1", "react-wrap-balancer": "^1.1.1", "redux": "^5.0.1", @@ -1840,6 +1842,35 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", + "integrity": "sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", @@ -6364,6 +6395,15 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.4.tgz", + "integrity": "sha512-kzue8lsoSBdyyd2IfXLQMMhNujOxRoGVus+63K95fQqleGxTfvgYLTzbwYMOODeAHqnkjb3WV/Ks7f5+gDYZuQ==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-router": { "version": "6.26.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", diff --git a/client/package.json b/client/package.json index bbab0aa..41db929 100644 --- a/client/package.json +++ b/client/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.3", "@reduxjs/toolkit": "^2.2.7", @@ -46,6 +47,7 @@ "react-icons": "^5.3.0", "react-markdown": "^9.0.1", "react-redux": "^9.1.2", + "react-resizable-panels": "^2.1.4", "react-router-dom": "^6.25.1", "react-wrap-balancer": "^1.1.1", "redux": "^5.0.1", diff --git a/client/src/App.tsx b/client/src/App.tsx index b2a0db5..420dc14 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -7,6 +7,7 @@ import Home from './pages/Home'; import Profile from './pages/Profile'; import { Toaster } from "@/components/ui/sonner"; import EditProfileForm from './pages/EditProfileForm'; +import { MessagePage } from './pages/MessagePage'; const App = () => { @@ -19,6 +20,7 @@ const App = () => { } /> } /> } /> + } /> } /> 404} /> diff --git a/client/src/components/Messages/Message.tsx b/client/src/components/Messages/Message.tsx new file mode 100644 index 0000000..1e63a15 --- /dev/null +++ b/client/src/components/Messages/Message.tsx @@ -0,0 +1,185 @@ +import { useEffect,useState } from "react" +import { + Search, +} from "lucide-react" +import { Input } from "@/components/ui/input" +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable" +import { TooltipProvider } from "@/components/ui/tooltip" +import axios from "axios" +import { Separator } from "@/components/ui/separator" +import { Textarea } from "@/components/ui/textarea" +import { Button } from "@/components/ui/button" + +const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000'; + +interface User { + username: string; +} + +interface ChatMessage { + sender_username: string; + message: string; +} + +export function Message() { + const [currentUserId, setCurrentUserId] = useState(""); + const [searchTerm, setSearchTerm] = useState(""); + const [users, setUsers] = useState([]); + const [selectedUser, setSelectedUser] = useState(null); + const [message, setMessage] = useState(""); + const [chattedUsers, setChattedUsers] = useState([]); + const [chatMessages, setChatMessages] = useState([]); + + useEffect(() => { + const username = localStorage.getItem('devhub_username') || ""; + setCurrentUserId(username); + }, []); + + const handleSearch = async () => { + if (searchTerm) { + try { + const response = await axios.get(`${backendUrl}/search_users?username=${searchTerm}`); + setUsers(response.data); + } catch (error) { + console.error("Error searching users:", error); + } + } else { + setUsers([]); // Clear users if search term is empty + } + } + + const handleSendMessage = async () => { + if (selectedUser && message.trim() !== "") { + try { + await axios.post(`${backendUrl}/send_message`, { + sender_username: currentUserId, + receiver_username: selectedUser.username, + message: message + }); + setMessage(""); + fetchChatMessages(); + + if (!chattedUsers.some(user => user.username === selectedUser.username)) { + setChattedUsers([...chattedUsers, selectedUser]); + } + } catch (error) { + console.error("Error sending message:", error); + } + } + } + + const fetchChatMessages = async () => { + if (selectedUser) { + try { + const response = await axios.get(`${backendUrl}/get_messages/${selectedUser.username}`); + setChatMessages(response.data); + } catch (error) { + console.error("Error fetching messages:", error); + } + } + } + + useEffect(() => { + handleSearch(); + }, [searchTerm]); + + const handleUserSelect = (user: User) => { + setSelectedUser(user); + setUsers([]); + setChatMessages([]); + fetchChatMessages(); + } + + return ( + + { + document.cookie = `react-resizable-panels:layout:mail=${JSON.stringify(sizes)}`; + }} + className="h-full items-stretch" + > + +
+
{ e.preventDefault(); handleSearch(); }}> +
+ + setSearchTerm(e.target.value)} + /> +
+
+
    + {users.map(user => ( +
  • handleUserSelect(user)}> + {user.username} +
  • + ))} +
+
+
+

Chatted Users

+
    + {chattedUsers.map(user => ( +
  • handleUserSelect(user)}> + {user.username} +
  • + ))} +
+
+
+ + +
+ + {selectedUser ? ( +
+

Chat with {selectedUser.username}

+
+ {chatMessages.map((msg, index) => ( +
+ {msg.sender_username === currentUserId ? "You" : selectedUser.username}: {msg.message} +
+ ))} +
+ +
+
+
+