From ac8392569b07deca20a6d37215b730883bfff60e Mon Sep 17 00:00:00 2001 From: Chneemann Date: Sun, 30 Aug 2026 19:44:48 +0200 Subject: [PATCH] feat(chat): add POST /api/messages endpoint and update ChatInput to send messages --- .../[serverId]/channels/[channelId]/page.tsx | 6 +- app/api/messages/route.ts | 72 ++++++++++++++++ components/chat/ChatInput.tsx | 84 +++++++++++++++++-- 3 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 app/api/messages/route.ts diff --git a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx index 741f7cf..7a13b21 100644 --- a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx +++ b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx @@ -47,7 +47,11 @@ export default async function ChannelPage({ {/* Input Field */} - + ); } diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts new file mode 100644 index 0000000..9605755 --- /dev/null +++ b/app/api/messages/route.ts @@ -0,0 +1,72 @@ +/** + * @file app/api/messages/route.ts + * @description API route handler for creating new messages in a channel. + */ + +import { auth } from "@/auth"; +import { db } from "@/db"; +import { members, messages } from "@/db/schema"; +import { and, eq } from "drizzle-orm"; +import { NextResponse } from "next/server"; + +/** + * Handles POST requests to create a new message within a specific channel and server. + * + * @param {Request} req - The incoming HTTP request containing content, channelId, and serverId in the JSON body. + * @returns {Promise} JSON response containing the created message object or an error message. + */ +export async function POST(req: Request) { + try { + const session = await auth(); + + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { content, channelId, serverId } = await req.json(); + + if (!content?.trim() || !channelId || !serverId) { + return NextResponse.json( + { error: "Missing parameters" }, + { status: 400 }, + ); + } + + // Find member ID of the current user for this server + const [member] = await db + .select() + .from(members) + .where( + and( + eq(members.userId, session.user.id), + eq(members.serverId, serverId), + ), + ) + .limit(1); + + if (!member) { + return NextResponse.json( + { error: "Not a member of this server" }, + { status: 403 }, + ); + } + + // Insert message into the database + const [newMessage] = await db + .insert(messages) + .values({ + content: content.trim(), + channelId, + memberId: member.id, + }) + .returning(); + + return NextResponse.json(newMessage, { status: 201 }); + } catch (error) { + console.error("API Message POST error:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/components/chat/ChatInput.tsx b/components/chat/ChatInput.tsx index 973c514..79c43e6 100644 --- a/components/chat/ChatInput.tsx +++ b/components/chat/ChatInput.tsx @@ -1,21 +1,91 @@ /** * @file components/chat/ChatInput.tsx - * @description Client component providing an input field for composing and sending chat messages. + * @description Client component providing an input field to create messages via REST API. */ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + /** - * Renders the input container component for sending messages in a chat channel. + * Props for the ChatInput component. * - * @returns {JSX.Element} The rendered chat input UI element. + * @interface ChatInputProps + * @property {string} channelName - The name of the channel displayed in the input placeholder. + * @property {string} channelId - The ID of the channel where the message will be sent. + * @property {string} serverId - The ID of the server containing the channel. */ -export function ChatInput() { +interface ChatInputProps { + channelName: string; + channelId: string; + serverId: string; +} + +/** + * Renders an input field for sending chat messages within a channel. + * + * @param {ChatInputProps} props - Component properties. + * @returns {JSX.Element} The ChatInput component. + */ +export function ChatInput({ + channelName, + channelId, + serverId, +}: ChatInputProps) { + const [content, setContent] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + + /** + * Handles key press events, submitting the message on 'Enter' (without Shift). + * + * @param {React.KeyboardEvent} e - The keyboard event. + */ + const handleKeyDown = async (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + + if (!content.trim() || isLoading) return; + + try { + setIsLoading(true); + + const response = await fetch("/api/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + content, + channelId, + serverId, + }), + }); + + if (!response.ok) { + throw new Error("Error while sending"); + } + + setContent(""); + router.refresh(); + } catch (error) { + console.error("Error sending the message:", error); + } finally { + setIsLoading(false); + } + } + }; + return ( -
+
setContent(e.target.value)} + onKeyDown={handleKeyDown} + disabled={isLoading} + placeholder={`Message to #${channelName}`} + className="w-full bg-transparent outline-none text-foreground placeholder-muted text-sm disabled:opacity-50" />