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
3,370 changes: 3,370 additions & 0 deletions .clinerules

Large diffs are not rendered by default.

3,370 changes: 3,370 additions & 0 deletions .cursorrules

Large diffs are not rendered by default.

3,370 changes: 3,370 additions & 0 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

3,370 changes: 3,370 additions & 0 deletions .windsurfrules

Large diffs are not rendered by default.

3,370 changes: 3,370 additions & 0 deletions AGENTS.md

Large diffs are not rendered by default.

3,370 changes: 3,370 additions & 0 deletions CLAUDE.md

Large diffs are not rendered by default.

15 changes: 4 additions & 11 deletions frontend/package-lock.json

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

110 changes: 105 additions & 5 deletions frontend/src/components/chat/messages/ValidChat.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Hash, Pencil, Trash2, Save, SendHorizontal, Loader2, AlertCircle } from "lucide-react";
import socket from "../../socket/Socket";
Expand Down Expand Up @@ -28,17 +28,33 @@ function ValidChat() {
const [all_messages, setall_messages] = useState([]);
const [editingTimestamp, setEditingTimestamp] = useState(null);
const [editingContent, setEditingContent] = useState("");
const [showEmojiPicker, setShowEmojiPicker] = useState(null);
const emojiPickerRef = useRef(null);

useEffect(() => {
setShowEmojiPicker(null);
}, [channel_id]);
Comment on lines +32 to +36
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
if(socket && channel_id){
if (socket && channel_id) {
const handleConnect = () => {
socket.emit("join_chat", {
channel_id: channel_id,
server_id: server_id
});
};
socket.on("connect", handleConnect);
socket.emit("join_chat", {
channel_id: channel_id,
server_id: server_id
})
});
return () => {
socket.off("connect", handleConnect);
};
}
}, [channel_id,server_id]);
}, [channel_id, server_id]);

const sendNow = async () => {
if (!chat_message.trim()) return;
Expand Down Expand Up @@ -151,6 +167,33 @@ function ValidChat() {
}
};

const toggleReaction = async (message, emoji) => {
const res = await fetch(`${url}/chat/toggle_reaction`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-auth-token": localStorage.getItem("token"),
},
body: JSON.stringify({
server_id,
channel_id,
timestamp: message.timestamp,
emoji,
}),
});
const data = await res.json();
if (data.status === 200 && data.reactions) {
setall_messages((currentMessages) =>
(currentMessages || []).map((entry) =>
String(entry.timestamp) === String(message.timestamp)
? { ...entry, reactions: data.reactions }
: entry
)
);
}
setShowEmojiPicker(null);
};

const deleteMessage = async (message) => {
const res = await fetch(`${url}/chat/delete_server_message`, {
method: "POST",
Expand Down Expand Up @@ -216,11 +259,23 @@ function ValidChat() {
);
};
//earlier it was server_message_receive which was wrong
const handleReactionUpdated = ({ timestamp, reactions }) => {
setall_messages((currentMessages) =>
(currentMessages || []).map((entry) =>
String(entry.timestamp) === String(timestamp)
? { ...entry, reactions }
: entry
)
);
};

socket.on("reaction_updated", handleReactionUpdated);
socket.on("server_message_received", handleReceiveMessage);
socket.on("server_message_updated", handleUpdatedMessage);
socket.on("server_message_deleted", handleDeletedMessage);

return () => {
socket.off("reaction_updated", handleReactionUpdated);
socket.off("server_message_received", handleReceiveMessage);
socket.off("server_message_updated", handleUpdatedMessage);
socket.off("server_message_deleted", handleDeletedMessage);
Expand Down Expand Up @@ -304,8 +359,34 @@ function ValidChat() {
<div className="text-[10px] leading-none text-white/35">
{timestamp}
</div>
<div className="ml-auto flex items-center gap-1 opacity-0 transition group-hover:opacity-100 group-focus-within:opacity-100">
<div className="relative">
<button
Comment on lines +362 to +364
type="button"
className="rounded-lg border border-white/10 bg-white/5 p-1.5 text-white/60 transition hover:bg-white/10 hover:text-white"
onClick={() => setShowEmojiPicker(showEmojiPicker === elem.timestamp ? null : elem.timestamp)}
title="React"
aria-label="React"
>
😊
</button>
{showEmojiPicker === elem.timestamp && (
<div ref={emojiPickerRef} className="absolute bottom-full right-0 z-50 mb-1 flex gap-1 rounded-xl border border-white/10 bg-[#1e1f22] p-2 shadow-xl">
{["👍","❤️","😂","😮","😢","🔥","🎉","👀"].map((emoji) => (
<button
key={emoji}
type="button"
className="rounded-lg p-1 text-lg transition hover:bg-white/10"
onClick={() => toggleReaction(elem, emoji)}
>
{emoji}
</button>
))}
</div>
)}
</div>
{mine ? (
<div className="ml-auto flex items-center gap-1 opacity-0 transition group-hover:opacity-100 group-focus-within:opacity-100">
<div className="flex items-center gap-1">
Comment on lines +387 to +389
<button
type="button"
className="rounded-lg border border-white/10 bg-white/5 p-1.5 text-white/60 transition hover:bg-white/10 hover:text-white"
Expand All @@ -329,8 +410,27 @@ function ValidChat() {
</button>
</div>
) : null}
</div>
Comment on lines 412 to +413
</div>

{(Array.isArray(elem.reactions) && elem.reactions.length > 0) && (
<div className="mt-1 flex flex-wrap gap-1">
{elem.reactions.map((r) => (
<button
key={r.emoji}
type="button"
onClick={() => toggleReaction(elem, r.emoji)}
className={`flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition ${
r.users.includes(id)
? "border-brand-300/50 bg-brand-300/20 text-brand-300"
: "border-white/10 bg-white/5 text-white/70 hover:bg-white/10"
}`}
>
{r.emoji} {r.users.length}
</button>
))}
</div>
)}
{isEditing ? (
<div className="mt-2 flex items-center gap-2">
<Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,14 @@ function Navbar2ChatValid({ onNavigate }) {
if (!server_id || server_id === "@me") {
return;
}
const handleConnect = () => {
socket.emit("join_server", server_id);
};
socket.on("connect", handleConnect);
socket.emit("join_server", server_id);
return () => {
socket.off("connect", handleConnect);
};
}, [server_id]);

useEffect(() => {
Expand Down
84 changes: 84 additions & 0 deletions frontend/src/components/directMessages/DirectMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function DirectMessage() {
const [input, setInput] = useState("");
const [editingTimestamp, setEditingTimestamp] = useState(null);
const [editingContent, setEditingContent] = useState("");
const [showEmojiPicker, setShowEmojiPicker] = useState(null);
const [friendIsTyping, setFriendIsTyping] = useState(false);
const typingTimeoutRef = useRef(null);
const isTypingRef = useRef(false);
Expand Down Expand Up @@ -129,12 +130,25 @@ function DirectMessage() {
}
};

const handleDmReactionUpdated = ({ timestamp, reactions, friend_id: fid }) => {
if (!activeFriend || fid !== activeFriend.id) return;
setMessages((currentMessages) =>
(currentMessages || []).map((entry) =>
String(entry.timestamp) === String(timestamp)
? { ...entry, reactions }
: entry
)
);
};

socket.on("dm_reaction_updated", handleDmReactionUpdated);
socket.on("direct_message_received", handleIncomingMessage);
socket.on("direct_message_updated", handleUpdatedMessage);
socket.on("direct_message_deleted", handleDeletedMessage);
socket.on("dm_typing", handleTyping);
socket.on("dm_stop_typing", handleStopTyping);
return () => {
socket.off("dm_reaction_updated", handleDmReactionUpdated);
socket.off("direct_message_received", handleIncomingMessage);
socket.off("direct_message_updated", handleUpdatedMessage);
socket.off("direct_message_deleted", handleDeletedMessage);
Expand Down Expand Up @@ -218,6 +232,32 @@ function DirectMessage() {
}
};

const toggleDmReaction = async (message, emoji) => {
const res = await fetch(`${url}/direct-messages/toggle_dm_reaction`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-auth-token": localStorage.getItem("token"),
},
body: JSON.stringify({
friend_id: activeFriend.id,
timestamp: message.timestamp,
emoji,
}),
});
const data = await res.json();
if (data.status === 200 && data.reactions) {
setMessages((currentMessages) =>
(currentMessages || []).map((entry) =>
String(entry.timestamp) === String(message.timestamp)
? { ...entry, reactions: data.reactions }
: entry
)
);
}
setShowEmojiPicker(null);
};

const deleteMessage = async (message) => {
const res = await fetch(`${url}/direct-messages/delete_direct_message`, {
method: "POST",
Expand Down Expand Up @@ -332,6 +372,50 @@ function DirectMessage() {
{message.content}
</div>

{(Array.isArray(message.reactions) && message.reactions.length > 0) && (
<div className="mt-1 flex flex-wrap gap-1">
{message.reactions.map((r) => (
<button
key={r.emoji}
type="button"
onClick={() => toggleDmReaction(message, r.emoji)}
className={`flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition ${
r.users.includes(currentUser.id)
? "border-brand-300/50 bg-brand-300/20 text-brand-300"
: "border-white/10 bg-white/5 text-white/70 hover:bg-white/10"
}`}
>
{r.emoji} {r.users.length}
</button>
))}
</div>
)}
<div className="relative">
<div className={["absolute -top-2 flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100", mine ? "right-10" : "left-0"].join(" ")}>
<button
type="button"
className="rounded-xl border border-white/10 bg-zinc-950/70 p-1.5 text-white/65 shadow-soft backdrop-blur transition hover:bg-zinc-950/85 hover:text-white"
onClick={() => setShowEmojiPicker(showEmojiPicker === message.timestamp ? null : message.timestamp)}
Comment on lines +394 to +398
title="React"
>
😊
</button>
</div>
{showEmojiPicker === message.timestamp && (
<div className="absolute bottom-full left-0 z-50 mb-1 flex gap-1 rounded-xl border border-white/10 bg-[#1e1f22] p-2 shadow-xl">
{["👍","❤️","😂","😮","😢","🔥","🎉","👀"].map((emoji) => (
<button
key={emoji}
type="button"
className="rounded-lg p-1 text-lg transition hover:bg-white/10"
onClick={() => toggleDmReaction(message, emoji)}
>
{emoji}
</button>
))}
</div>
)}
</div>
{mine ? (
<div
className={[
Expand Down
Loading