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("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("react_to_message", (data: ReactToMessageRequest) => handleReactToMessage(io, socket, data));
socket.on("typing_start", (data: { roomId: string; username: string }) => handleTypingStart(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) => { const handleReactToMessage = async (io: Server, socket: AuthenticatedSocket, data: ReactToMessageRequest) => {
try { try {
const result = await MessageService.reactToMessage(socket.userId, data); const result = await MessageService.reactToMessage(socket.userId, data);

View File

@ -44,7 +44,7 @@ export const ChatApp: FC = () => {
</div> </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"> <div className="text-center">
<h2 className="text-xl font-semibold mb-2"> <h2 className="text-xl font-semibold mb-2">
Welcome to Chat App Welcome to Chat App
@ -57,4 +57,4 @@ export const ChatApp: FC = () => {
</div> </div>
</div> </div>
); );
}; };

View File

@ -2,8 +2,9 @@ import { cn } from '@/lib/utils';
import { useAuthStore } from '@/stores/authStore'; import { useAuthStore } from '@/stores/authStore';
import { useSocketStore } from '@/stores/socketStore'; import { useSocketStore } from '@/stores/socketStore';
import type { Message, MessageReaction } from '@/types'; 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 { useMemo, useRef, useState, type FC } from 'react';
import { toast } from 'sonner';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'; 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); 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) => { const formatTime = (dateString: string) => {
return new Date(dateString).toLocaleTimeString([], { return new Date(dateString).toLocaleTimeString([], {
hour: '2-digit', hour: '2-digit',
@ -157,6 +171,16 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
> >
<Smile className="h-5 w-5 text-yellow-500" /> <Smile className="h-5 w-5 text-yellow-500" />
</Button> </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> </PopoverContent>
</Popover> </Popover>
</div> </div>

View File

@ -14,7 +14,7 @@ type MessageListProps = {
}; };
export const MessageList: FC<MessageListProps> = ({ roomId }) => { export const MessageList: FC<MessageListProps> = ({ roomId }) => {
const { messages, setMessages } = useChatStore(); const { messages, setMessages, removeMessage } = useChatStore();
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { user } = useAuthStore(); const { user } = useAuthStore();
const [lastMessageId, setLastMessageId] = useState<string | null>(null); const [lastMessageId, setLastMessageId] = useState<string | null>(null);
@ -96,6 +96,25 @@ export const MessageList: FC<MessageListProps> = ({ roomId }) => {
} }
}, [socket, user, roomId, roomMessages]); }, [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) { if (isLoading) {
return ( return (
<div className="flex-1 flex items-center justify-center dark:bg-gray-900"> <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; setSelectedRoom: (roomId: string | null) => void;
setMessages: (roomId: string, messages: Message[]) => void; setMessages: (roomId: string, messages: Message[]) => void;
addMessage: (message: Message) => void; addMessage: (message: Message) => void;
removeMessage: (messageId: string, roomId: string) => void;
updateMessageReactions: ( updateMessageReactions: (
messageId: string, messageId: string,
reactions: MessageReaction[] 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) => { updateMessageReactions: (messageId, reactions) => {
set((state) => { set((state) => {
const newMessages = { ...state.messages }; const newMessages = { ...state.messages };