diff --git a/app/api/channels/route.ts b/app/api/channels/route.ts index 2aa4114..9066c04 100644 --- a/app/api/channels/route.ts +++ b/app/api/channels/route.ts @@ -5,18 +5,11 @@ import { auth } from "@/auth"; import { db } from "@/db"; -import { channels, members } from "@/db/schema"; +import { categories, channels, members } from "@/db/schema"; import { and, eq } from "drizzle-orm"; import { NextResponse } from "next/server"; -/** - * Handles the POST request to create a new channel within a specific server. - * - * @async - * @function POST - * @param {Request} req - The incoming HTTP request containing JSON payload with `name` and `serverId`. - * @returns {Promise} The created channel object with status 201, or an error response (401, 400, 403, 500). - */ +/** Handles the POST request to create a new channel within a specific server. */ export async function POST(req: Request) { try { const session = await auth(); @@ -25,7 +18,7 @@ export async function POST(req: Request) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - const { name, serverId } = await req.json(); + const { name, serverId, categoryId } = await req.json(); if (!name || !serverId) { return NextResponse.json( @@ -61,12 +54,31 @@ export async function POST(req: Request) { ); } + // Validate category existence and server association if provided + if (categoryId) { + const [category] = await db + .select() + .from(categories) + .where( + and(eq(categories.id, categoryId), eq(categories.serverId, serverId)), + ) + .limit(1); + + if (!category) { + return NextResponse.json( + { error: "Invalid category provided for this server" }, + { status: 400 }, + ); + } + } + // Create the channel in the database const [newChannel] = await db .insert(channels) .values({ name: name.trim().toLowerCase().replace(/\s+/g, "-"), serverId, + categoryId: categoryId || null, }) .returning(); diff --git a/components/modals/CreateChannelModal.tsx b/components/modals/CreateChannelModal.tsx index 677a9f0..2a8e0d3 100644 --- a/components/modals/CreateChannelModal.tsx +++ b/components/modals/CreateChannelModal.tsx @@ -7,59 +7,47 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import { X, Loader2 } from "lucide-react"; +import { X } from "lucide-react"; import { useActiveServer } from "@/lib/context/ServerContext"; import { ActionButton } from "../ui/ActionButton"; -/** - * Properties for the CreateChannelModal component. - * - * @interface CreateChannelModalProps - * @property {boolean} isOpen - Determines whether the modal is visible. - * @property {() => void} onClose - Callback function executed to close the modal. - * @property {string} serverId - The unique identifier of the target server where the channel is created. - */ +/** Properties for the CreateChannelModal component. */ interface CreateChannelModalProps { isOpen: boolean; onClose: () => void; serverId: string; + defaultCategoryId?: string | null; } -/** - * Renders a modal dialog allowing users to input a name and create a new server channel. - * - * @param {CreateChannelModalProps} props - The component props. - * @param {boolean} props.isOpen - Determines whether the modal is visible. - * @param {() => void} props.onClose - Callback function executed to close the modal. - * @param {string} props.serverId - The unique identifier of the target server where the channel is created. - * @returns {JSX.Element | null} The rendered modal component or null if closed. - */ +/** Renders a modal dialog allowing users to input a name and create a new server channel. */ export function CreateChannelModal({ isOpen, onClose, serverId, + defaultCategoryId = null, }: CreateChannelModalProps) { const [name, setName] = useState(""); + const [categoryId, setCategoryId] = useState( + defaultCategoryId, + ); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const router = useRouter(); const { addChannel } = useActiveServer(); + // Update categoryId if the defaultCategoryId changes when the page opens + useEffect(() => { + setCategoryId(defaultCategoryId); + }, [defaultCategoryId, isOpen]); + if (!isOpen) return null; const isValid = name.trim().length != 0; const canSave = isValid && !isLoading; - /** - * Handles form submission to create a new channel via the API. - * - * @async - * @function handleSubmit - * @param {React.FormEvent} e - The form submission event. - * @returns {Promise} Resolves when the channel creation request completes. - */ - const handleSubmit = async (e: React.FormEvent) => { + /** Handles form submission to create a new channel via the API. */ + const handleSubmit = async (e: React.SubmitEvent) => { e.preventDefault(); if (!name.trim() || isLoading) return; @@ -70,7 +58,11 @@ export function CreateChannelModal({ const response = await fetch("/api/channels", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name, serverId }), + body: JSON.stringify({ + name, + serverId, + categoryId: categoryId || null, + }), }); if (!response.ok) { diff --git a/components/sidebar/ChannelSidebar.tsx b/components/sidebar/ChannelSidebar.tsx index 3ef1637..f086e70 100644 --- a/components/sidebar/ChannelSidebar.tsx +++ b/components/sidebar/ChannelSidebar.tsx @@ -209,6 +209,7 @@ export function ChannelSidebar() { isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} serverId={activeServer.id} + defaultCategoryId={selectedCategoryId} />