Skip to content

Commit

Permalink
Merge pull request #57 from deepraj21/main
Browse files Browse the repository at this point in the history
added messsage feature
  • Loading branch information
deepraj21 authored Oct 20, 2024
2 parents ecc820b + bdb5d30 commit 4184121
Show file tree
Hide file tree
Showing 14 changed files with 426 additions and 21 deletions.
40 changes: 40 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {

Expand All @@ -19,6 +20,7 @@ const App = () => {
<Route path="/login" element={<Login />} />
<Route path="/home" element={<Home />} />
<Route path="/settings" element={<EditProfileForm />} />
<Route path="/message" element={<MessagePage/>} />
<Route path="/u/:username" element={<Profile />} />
<Route path="*" element={<div>404</div>} />
</Routes>
Expand Down
185 changes: 185 additions & 0 deletions client/src/components/Messages/Message.tsx
Original file line number Diff line number Diff line change
@@ -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<string>("");
const [searchTerm, setSearchTerm] = useState<string>("");
const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [message, setMessage] = useState<string>("");
const [chattedUsers, setChattedUsers] = useState<User[]>([]);
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);

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 (
<TooltipProvider delayDuration={0}>
<ResizablePanelGroup
direction="horizontal"
onLayout={(sizes: number[]) => {
document.cookie = `react-resizable-panels:layout:mail=${JSON.stringify(sizes)}`;
}}
className="h-full items-stretch"
>
<ResizablePanel minSize={30} className="flex flex-col">
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<form onSubmit={(e) => { e.preventDefault(); handleSearch(); }}>
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search users"
className="pl-8"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</form>
<ul className="mt-2">
{users.map(user => (
<li key={user.username} onClick={() => handleUserSelect(user)}>
{user.username}
</li>
))}
</ul>
</div>
<div className="flex-grow overflow-auto">
<h3>Chatted Users</h3>
<ul>
{chattedUsers.map(user => (
<li key={user.username} onClick={() => handleUserSelect(user)}>
{user.username}
</li>
))}
</ul>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel minSize={30} className="flex flex-col">
<div className="flex-grow overflow-auto">

{selectedUser ? (
<div>
<h3>Chat with {selectedUser.username}</h3>
<div className="flex-1 whitespace-pre-wrap p-4 text-sm">
{chatMessages.map((msg, index) => (
<div key={index}>
<strong>{msg.sender_username === currentUserId ? "You" : selectedUser.username}:</strong> {msg.message}
</div>
))}
</div>
<Separator className="mt-auto" />
<div className="p-4">
<form>
<div className="grid gap-4">
<Textarea
className="p-4"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type a message"
/>
<div className="flex items-center">
<Button
onClick={handleSendMessage}
size="sm"
className="ml-auto"
>
Send
</Button>
</div>
</div>
</form>
</div>
</div>
) : (
<div className="p-8 text-center text-muted-foreground">
No message selected
</div>
)}
</div>
</ResizablePanel>
</ResizablePanelGroup>
</TooltipProvider>
)
}
14 changes: 6 additions & 8 deletions client/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,10 @@ export default function DevhubSidebar() {
export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>) {
const navigate = useNavigate();

// Handle logout logic
const handleLogout = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault(); // Prevent the default anchor behavior
e.preventDefault();
localStorage.removeItem('devhub_username');
navigate('/login'); // Redirect to the login page
navigate('/login');
};

const sidebarLeftData = {
Expand All @@ -67,10 +66,9 @@ export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>)
icon: Sparkles,
},
{
title: "Inbox",
url: "#",
title: "Message",
url: "/message",
icon: Inbox,
badge: "10",
},
],
navSecondary: [
Expand All @@ -96,9 +94,9 @@ export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>)
},
{
title: "Logout",
url: "#", // Keep the url as # for now
url: "#",
icon: LogOut,
onClick: handleLogout, // Call the logout function on click
onClick: handleLogout,
}
]
};
Expand Down
43 changes: 43 additions & 0 deletions client/src/components/ui/resizable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"

import { cn } from "@/lib/utils"

const ResizablePanelGroup = ({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
<ResizablePrimitive.PanelGroup
className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className
)}
{...props}
/>
)

const ResizablePanel = ResizablePrimitive.Panel

const ResizableHandle = ({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean
}) => (
<ResizablePrimitive.PanelResizeHandle
className={cn(
"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",
className
)}
{...props}
>
{withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVertical className="h-2.5 w-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
)

export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
Loading

0 comments on commit 4184121

Please sign in to comment.