remove msg

This commit is contained in:
Gal Podlipnik 2025-06-12 16:04:27 +02:00
parent 95757dd169
commit 9e05f941ab
6 changed files with 124 additions and 4 deletions

View File

@ -155,4 +155,45 @@ export class MessageService {
};
}
}
static async deleteMessage(userId: string, messageId: string, roomId: string): Promise<ApiResponse> {
try {
const message = await prisma.message.findFirst({
where: {
id: messageId,
userId: userId,
roomId: roomId,
},
});
if (!message) {
return {
success: false,
error: "Message not found or you don't have permission to delete it",
status: 403,
};
}
await prisma.message.delete({
where: {
id: messageId,
},
});
return {
success: true,
data: {
messageId,
roomId,
},
};
} catch (error) {
console.error("Delete message error:", error);
return {
success: false,
error: "Failed to delete message",
status: 500,
};
}
}
}

View File

@ -21,6 +21,8 @@ export const handleConnection = async (io: Server, socket: AuthenticatedSocket)
socket.on("send_message", (data: SendMessageRequest) => handleSendMessage(io, socket, data));
socket.on("delete_message", (data) => handleDeleteMessage(io, socket, data));
socket.on("react_to_message", (data: ReactToMessageRequest) => handleReactToMessage(io, socket, data));
socket.on("typing_start", (data: { roomId: string; username: string }) => handleTypingStart(io, socket, data));
@ -139,6 +141,24 @@ const handleSendMessage = async (io: Server, socket: AuthenticatedSocket, data:
}
};
const handleDeleteMessage = async (io: Server, socket: AuthenticatedSocket, data: { messageId: string; roomId: string }) => {
try {
const result = await MessageService.deleteMessage(socket.userId, data.messageId, data.roomId);
if (result.success) {
io.to(data.roomId).emit("message_deleted", {
messageId: data.messageId,
roomId: data.roomId,
});
} else {
socket.emit("error", { message: result.error });
}
} catch (error) {
console.error(`Error handling delete message: ${error}`);
socket.emit("error", { message: "Failed to delete message" });
}
};
const handleReactToMessage = async (io: Server, socket: AuthenticatedSocket, data: ReactToMessageRequest) => {
try {
const result = await MessageService.reactToMessage(socket.userId, data);

View File

@ -44,7 +44,7 @@ export const ChatApp: FC = () => {
</div>
</>
) : (
<div className="flex-1 flex items-center justify-center text-gray-500">
<div className="flex-1 flex items-center justify-center text-gray-500 dark:bg-gray-800 dark:text-gray-400">
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">
Welcome to Chat App

View File

@ -2,8 +2,9 @@ import { cn } from '@/lib/utils';
import { useAuthStore } from '@/stores/authStore';
import { useSocketStore } from '@/stores/socketStore';
import type { Message, MessageReaction } from '@/types';
import { Heart, Smile, ThumbsUp } from 'lucide-react';
import { Heart, Smile, ThumbsUp, Trash2 } from 'lucide-react';
import { useMemo, useRef, useState, type FC } from 'react';
import { toast } from 'sonner';
import { Button } from './ui/button';
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
@ -27,6 +28,19 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
return reactions.some((r) => r.type === type && r.userId === user?.id);
};
const handleDeleteMessage = () => {
if (confirm('Are you sure you want to delete this message?')) {
if (socket) {
socket.emit('delete_message', {
messageId: message.id,
roomId: message.roomId,
});
setShowReactions(false);
toast.info('Message deleted successfully');
}
}
};
const formatTime = (dateString: string) => {
return new Date(dateString).toLocaleTimeString([], {
hour: '2-digit',
@ -157,6 +171,16 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
>
<Smile className="h-5 w-5 text-yellow-500" />
</Button>
{isCurrentUser && (
<Button
size="sm"
variant="ghost"
className="rounded-full p-2"
onClick={handleDeleteMessage}
>
<Trash2 className="h-5 w-5 text-gray-500" />
</Button>
)}
</PopoverContent>
</Popover>
</div>

View File

@ -14,7 +14,7 @@ type MessageListProps = {
};
export const MessageList: FC<MessageListProps> = ({ roomId }) => {
const { messages, setMessages } = useChatStore();
const { messages, setMessages, removeMessage } = useChatStore();
const { socket } = useSocketStore();
const { user } = useAuthStore();
const [lastMessageId, setLastMessageId] = useState<string | null>(null);
@ -96,6 +96,25 @@ export const MessageList: FC<MessageListProps> = ({ roomId }) => {
}
}, [socket, user, roomId, roomMessages]);
useEffect(() => {
if (!socket) return;
const handleMessageDeleted = (data: {
messageId: string;
roomId: string;
}) => {
if (data.roomId === roomId) {
removeMessage(data.messageId, data.roomId);
}
};
socket.on('message_deleted', handleMessageDeleted);
return () => {
socket.off('message_deleted', handleMessageDeleted);
};
}, [socket, roomId, removeMessage]);
if (isLoading) {
return (
<div className="flex-1 flex items-center justify-center dark:bg-gray-900">

View File

@ -16,6 +16,7 @@ type ChatState = {
setSelectedRoom: (roomId: string | null) => void;
setMessages: (roomId: string, messages: Message[]) => void;
addMessage: (message: Message) => void;
removeMessage: (messageId: string, roomId: string) => void;
updateMessageReactions: (
messageId: string,
reactions: MessageReaction[]
@ -73,6 +74,21 @@ export const useChatStore = create<ChatState>()(
}));
},
removeMessage: (messageId, roomId) => {
set((state) => {
if (!state.messages[roomId]) return state;
return {
messages: {
...state.messages,
[roomId]: state.messages[roomId].filter(
(message) => message.id !== messageId
),
},
};
});
},
updateMessageReactions: (messageId, reactions) => {
set((state) => {
const newMessages = { ...state.messages };