diff --git a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx index 7a13b21..b255f5b 100644 --- a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx +++ b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx @@ -3,6 +3,7 @@ * @description Dynamic page component for displaying a specific channel within a server, including its messages and chat input. */ +import { auth } from "@/auth"; import { redirect } from "next/navigation"; import { AppHeader } from "@/components/layout/AppHeader"; import { ChatInput } from "@/components/chat/ChatInput"; @@ -14,6 +15,7 @@ import { getServerById } from "@/lib/services/server.service"; /** * Renders the channel view by fetching server, channel, and message details in parallel based on route parameters. * + * @async * @param {Object} props - The component props. * @param {Promise<{ serverId: string; channelId: string }>} props.params - A promise resolving to the route parameters containing serverId and channelId. * @returns {Promise} The rendered channel page interface. @@ -23,6 +25,7 @@ export default async function ChannelPage({ }: { params: Promise<{ serverId: string; channelId: string }>; }) { + const session = await auth(); const { serverId, channelId } = await params; // Parallel loading of server, channel, and messages @@ -44,7 +47,11 @@ export default async function ChannelPage({ /> {/* Messages Feed */} - + {/* Input Field */} } context.params - A promise resolving to the route parameters containing the message ID. + * @returns {Promise} JSON response indicating success or failure with appropriate HTTP status codes. + */ +export async function DELETE( + req: Request, + { params }: { params: Promise<{ messageId: string }> }, +) { + try { + const { messageId } = await params; + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Retrieve a message and its associated member + const [existingMessage] = await db + .select({ + id: messages.id, + memberId: messages.memberId, + userId: members.userId, + }) + .from(messages) + .innerJoin(members, eq(messages.memberId, members.id)) + .where(eq(messages.id, messageId)) + .limit(1); + + if (!existingMessage) { + return NextResponse.json({ error: "Message not found" }, { status: 404 }); + } + + // Check permissions (Is the user the creator of the message?) + if (existingMessage.userId !== session.user.id) { + return NextResponse.json( + { error: "You do not have permission to delete this message" }, + { status: 403 }, + ); + } + + // 3. Nachricht löschen + await db.delete(messages).where(eq(messages.id, messageId)); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("API Message DELETE error:", error); + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 }, + ); + } +} diff --git a/components/chat/ChatItem.tsx b/components/chat/ChatItem.tsx index 6a04350..0f14274 100644 --- a/components/chat/ChatItem.tsx +++ b/components/chat/ChatItem.tsx @@ -1,15 +1,24 @@ /** * @file components/chat/ChatItem.tsx - * @description Single message row component. + * @description Single message row component with support for user avatars, metadata, and deletion handling. */ "use client"; import type { Message, Member, User } from "@/db/schema"; import { UserAvatar } from "../ui/UserAvatar"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Trash2 } from "lucide-react"; /** * Composite message type extending base database Message with populated member and user relation. + * + * @interface MessageWithMember + * @property {string} id - The unique identifier of the message. + * @property {string} content - The text content of the message. + * @property {string} createdAt - The timestamp when the message was created. + * @property {Member & { user: User }} member - The associated member and user relational data. */ export type MessageWithMember = Message & { member: Member & { @@ -17,16 +26,34 @@ export type MessageWithMember = Message & { }; }; +/** + * Props for the ChatItem component. + * + * @interface ChatItemProps + * @property {MessageWithMember} message - The message object containing member and user relational data. + * @property {string} [currentUserId] - The unique identifier of the currently logged-in user. + */ +interface ChatItemProps { + message: MessageWithMember; + currentUserId?: string; +} + /** * Renders an individual chat message row displaying user avatar, sender name, timestamp, and text content. * - * @param {Object} props - The component props. + * @async + * @param {ChatItemProps} props - The component props. * @param {MessageWithMember} props.message - The message object containing member and user relational data. + * @param {string} [props.currentUserId] - The unique identifier of the currently logged-in user. * @returns {JSX.Element} The rendered single chat message item. */ -export function ChatItem({ message }: { message: MessageWithMember }) { +export function ChatItem({ message, currentUserId }: ChatItemProps) { + const router = useRouter(); + const [isDeleting, setIsDeleting] = useState(false); + const user = message.member?.user; const fullName = user ? user.username.trim() : "Deleted Member"; + const isOwner = user?.id === currentUserId; const formattedTime = new Date(message.createdAt).toLocaleTimeString( "de-DE", @@ -36,20 +63,64 @@ export function ChatItem({ message }: { message: MessageWithMember }) { }, ); + /** + * Handles the asynchronous deletion of the chat message. + * + * @async + * @function handleDelete + * @returns {Promise} Resolves when the deletion process completes or fails. + */ + const handleDelete = async () => { + if (isDeleting) return; + + try { + setIsDeleting(true); + const response = await fetch(`/api/messages/${message.id}`, { + method: "DELETE", + }); + + if (!response.ok) { + throw new Error("Error while deleting"); + } + + router.refresh(); + } catch (error) { + console.error("Error deleting the message:", error); + } finally { + setIsDeleting(false); + } + }; + return (
{/* Avatar Component */} - {/* Message Header & Content */} + {/* Message Content & Top Bar */}
-
- - {fullName} - - {formattedTime} +
+
+ + {fullName} + + {formattedTime} +
+ + {/* Delete Button */} + {isOwner && ( + + )}
-

+ +

{message.content}

diff --git a/components/chat/ChatMessages.tsx b/components/chat/ChatMessages.tsx index 022e3ba..825a8bc 100644 --- a/components/chat/ChatMessages.tsx +++ b/components/chat/ChatMessages.tsx @@ -13,19 +13,28 @@ import { ChatItem, type MessageWithMember } from "./ChatItem"; * @interface ChatMessagesProps * @property {string} channelName - The name of the active chat channel to display in the header greeting. * @property {MessageWithMember[]} messages - Array of message objects, each containing message details and associated member information. + * @property {string} [currentUserId] - The unique identifier of the currently logged-in user. */ interface ChatMessagesProps { channelName: string; messages: MessageWithMember[]; + currentUserId?: string; } /** * Renders the scrollable message list along with a welcoming channel header. * * @param {ChatMessagesProps} props - The component props. + * @param {string} props.channelName - The name of the active chat channel to display in the header greeting. + * @param {MessageWithMember[]} props.messages - Array of message objects, each containing message details and associated member information. + * @param {string} [props.currentUserId] - The unique identifier of the currently logged-in user. * @returns {JSX.Element} The rendered chat messages container. */ -export function ChatMessages({ channelName, messages }: ChatMessagesProps) { +export function ChatMessages({ + channelName, + messages, + currentUserId, +}: ChatMessagesProps) { return (
@@ -39,7 +48,11 @@ export function ChatMessages({ channelName, messages }: ChatMessagesProps) {
{messages.map((message) => ( - + ))}