Skip to content

Commit 4184121

Browse files
authored
Merge pull request #57 from deepraj21/main
added messsage feature
2 parents ecc820b + bdb5d30 commit 4184121

File tree

14 files changed

+426
-21
lines changed

14 files changed

+426
-21
lines changed

client/package-lock.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@radix-ui/react-select": "^2.1.1",
2525
"@radix-ui/react-separator": "^1.1.0",
2626
"@radix-ui/react-slot": "^1.1.0",
27+
"@radix-ui/react-tabs": "^1.1.1",
2728
"@radix-ui/react-toast": "^1.2.1",
2829
"@radix-ui/react-tooltip": "^1.1.3",
2930
"@reduxjs/toolkit": "^2.2.7",
@@ -46,6 +47,7 @@
4647
"react-icons": "^5.3.0",
4748
"react-markdown": "^9.0.1",
4849
"react-redux": "^9.1.2",
50+
"react-resizable-panels": "^2.1.4",
4951
"react-router-dom": "^6.25.1",
5052
"react-wrap-balancer": "^1.1.1",
5153
"redux": "^5.0.1",

client/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Home from './pages/Home';
77
import Profile from './pages/Profile';
88
import { Toaster } from "@/components/ui/sonner";
99
import EditProfileForm from './pages/EditProfileForm';
10+
import { MessagePage } from './pages/MessagePage';
1011

1112
const App = () => {
1213

@@ -19,6 +20,7 @@ const App = () => {
1920
<Route path="/login" element={<Login />} />
2021
<Route path="/home" element={<Home />} />
2122
<Route path="/settings" element={<EditProfileForm />} />
23+
<Route path="/message" element={<MessagePage/>} />
2224
<Route path="/u/:username" element={<Profile />} />
2325
<Route path="*" element={<div>404</div>} />
2426
</Routes>
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { useEffect,useState } from "react"
2+
import {
3+
Search,
4+
} from "lucide-react"
5+
import { Input } from "@/components/ui/input"
6+
import {
7+
ResizableHandle,
8+
ResizablePanel,
9+
ResizablePanelGroup,
10+
} from "@/components/ui/resizable"
11+
import { TooltipProvider } from "@/components/ui/tooltip"
12+
import axios from "axios"
13+
import { Separator } from "@/components/ui/separator"
14+
import { Textarea } from "@/components/ui/textarea"
15+
import { Button } from "@/components/ui/button"
16+
17+
const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
18+
19+
interface User {
20+
username: string;
21+
}
22+
23+
interface ChatMessage {
24+
sender_username: string;
25+
message: string;
26+
}
27+
28+
export function Message() {
29+
const [currentUserId, setCurrentUserId] = useState<string>("");
30+
const [searchTerm, setSearchTerm] = useState<string>("");
31+
const [users, setUsers] = useState<User[]>([]);
32+
const [selectedUser, setSelectedUser] = useState<User | null>(null);
33+
const [message, setMessage] = useState<string>("");
34+
const [chattedUsers, setChattedUsers] = useState<User[]>([]);
35+
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
36+
37+
useEffect(() => {
38+
const username = localStorage.getItem('devhub_username') || "";
39+
setCurrentUserId(username);
40+
}, []);
41+
42+
const handleSearch = async () => {
43+
if (searchTerm) {
44+
try {
45+
const response = await axios.get(`${backendUrl}/search_users?username=${searchTerm}`);
46+
setUsers(response.data);
47+
} catch (error) {
48+
console.error("Error searching users:", error);
49+
}
50+
} else {
51+
setUsers([]); // Clear users if search term is empty
52+
}
53+
}
54+
55+
const handleSendMessage = async () => {
56+
if (selectedUser && message.trim() !== "") {
57+
try {
58+
await axios.post(`${backendUrl}/send_message`, {
59+
sender_username: currentUserId,
60+
receiver_username: selectedUser.username,
61+
message: message
62+
});
63+
setMessage("");
64+
fetchChatMessages();
65+
66+
if (!chattedUsers.some(user => user.username === selectedUser.username)) {
67+
setChattedUsers([...chattedUsers, selectedUser]);
68+
}
69+
} catch (error) {
70+
console.error("Error sending message:", error);
71+
}
72+
}
73+
}
74+
75+
const fetchChatMessages = async () => {
76+
if (selectedUser) {
77+
try {
78+
const response = await axios.get(`${backendUrl}/get_messages/${selectedUser.username}`);
79+
setChatMessages(response.data);
80+
} catch (error) {
81+
console.error("Error fetching messages:", error);
82+
}
83+
}
84+
}
85+
86+
useEffect(() => {
87+
handleSearch();
88+
}, [searchTerm]);
89+
90+
const handleUserSelect = (user: User) => {
91+
setSelectedUser(user);
92+
setUsers([]);
93+
setChatMessages([]);
94+
fetchChatMessages();
95+
}
96+
97+
return (
98+
<TooltipProvider delayDuration={0}>
99+
<ResizablePanelGroup
100+
direction="horizontal"
101+
onLayout={(sizes: number[]) => {
102+
document.cookie = `react-resizable-panels:layout:mail=${JSON.stringify(sizes)}`;
103+
}}
104+
className="h-full items-stretch"
105+
>
106+
<ResizablePanel minSize={30} className="flex flex-col">
107+
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
108+
<form onSubmit={(e) => { e.preventDefault(); handleSearch(); }}>
109+
<div className="relative">
110+
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
111+
<Input
112+
placeholder="Search users"
113+
className="pl-8"
114+
value={searchTerm}
115+
onChange={(e) => setSearchTerm(e.target.value)}
116+
/>
117+
</div>
118+
</form>
119+
<ul className="mt-2">
120+
{users.map(user => (
121+
<li key={user.username} onClick={() => handleUserSelect(user)}>
122+
{user.username}
123+
</li>
124+
))}
125+
</ul>
126+
</div>
127+
<div className="flex-grow overflow-auto">
128+
<h3>Chatted Users</h3>
129+
<ul>
130+
{chattedUsers.map(user => (
131+
<li key={user.username} onClick={() => handleUserSelect(user)}>
132+
{user.username}
133+
</li>
134+
))}
135+
</ul>
136+
</div>
137+
</ResizablePanel>
138+
<ResizableHandle withHandle />
139+
<ResizablePanel minSize={30} className="flex flex-col">
140+
<div className="flex-grow overflow-auto">
141+
142+
{selectedUser ? (
143+
<div>
144+
<h3>Chat with {selectedUser.username}</h3>
145+
<div className="flex-1 whitespace-pre-wrap p-4 text-sm">
146+
{chatMessages.map((msg, index) => (
147+
<div key={index}>
148+
<strong>{msg.sender_username === currentUserId ? "You" : selectedUser.username}:</strong> {msg.message}
149+
</div>
150+
))}
151+
</div>
152+
<Separator className="mt-auto" />
153+
<div className="p-4">
154+
<form>
155+
<div className="grid gap-4">
156+
<Textarea
157+
className="p-4"
158+
value={message}
159+
onChange={(e) => setMessage(e.target.value)}
160+
placeholder="Type a message"
161+
/>
162+
<div className="flex items-center">
163+
<Button
164+
onClick={handleSendMessage}
165+
size="sm"
166+
className="ml-auto"
167+
>
168+
Send
169+
</Button>
170+
</div>
171+
</div>
172+
</form>
173+
</div>
174+
</div>
175+
) : (
176+
<div className="p-8 text-center text-muted-foreground">
177+
No message selected
178+
</div>
179+
)}
180+
</div>
181+
</ResizablePanel>
182+
</ResizablePanelGroup>
183+
</TooltipProvider>
184+
)
185+
}

client/src/components/Sidebar/Sidebar.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,10 @@ export default function DevhubSidebar() {
5252
export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>) {
5353
const navigate = useNavigate();
5454

55-
// Handle logout logic
5655
const handleLogout = (e: React.MouseEvent<HTMLAnchorElement>) => {
57-
e.preventDefault(); // Prevent the default anchor behavior
56+
e.preventDefault();
5857
localStorage.removeItem('devhub_username');
59-
navigate('/login'); // Redirect to the login page
58+
navigate('/login');
6059
};
6160

6261
const sidebarLeftData = {
@@ -67,10 +66,9 @@ export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>)
6766
icon: Sparkles,
6867
},
6968
{
70-
title: "Inbox",
71-
url: "#",
69+
title: "Message",
70+
url: "/message",
7271
icon: Inbox,
73-
badge: "10",
7472
},
7573
],
7674
navSecondary: [
@@ -96,9 +94,9 @@ export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>)
9694
},
9795
{
9896
title: "Logout",
99-
url: "#", // Keep the url as # for now
97+
url: "#",
10098
icon: LogOut,
101-
onClick: handleLogout, // Call the logout function on click
99+
onClick: handleLogout,
102100
}
103101
]
104102
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { GripVertical } from "lucide-react"
2+
import * as ResizablePrimitive from "react-resizable-panels"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
const ResizablePanelGroup = ({
7+
className,
8+
...props
9+
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
10+
<ResizablePrimitive.PanelGroup
11+
className={cn(
12+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
13+
className
14+
)}
15+
{...props}
16+
/>
17+
)
18+
19+
const ResizablePanel = ResizablePrimitive.Panel
20+
21+
const ResizableHandle = ({
22+
withHandle,
23+
className,
24+
...props
25+
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
26+
withHandle?: boolean
27+
}) => (
28+
<ResizablePrimitive.PanelResizeHandle
29+
className={cn(
30+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
31+
className
32+
)}
33+
{...props}
34+
>
35+
{withHandle && (
36+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
37+
<GripVertical className="h-2.5 w-2.5" />
38+
</div>
39+
)}
40+
</ResizablePrimitive.PanelResizeHandle>
41+
)
42+
43+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }

0 commit comments

Comments
 (0)