feat(channels): allow assigning category when creating channel
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 59s

This commit is contained in:
Chneemann 2026-09-13 16:04:12 +02:00
parent 2321551518
commit 0f5f7c75f2
No known key found for this signature in database
3 changed files with 43 additions and 38 deletions

View file

@ -5,18 +5,11 @@
import { auth } from "@/auth"; import { auth } from "@/auth";
import { db } from "@/db"; import { db } from "@/db";
import { channels, members } from "@/db/schema"; import { categories, channels, members } from "@/db/schema";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
/** /** Handles the POST request to create a new channel within a specific 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<NextResponse>} The created channel object with status 201, or an error response (401, 400, 403, 500).
*/
export async function POST(req: Request) { export async function POST(req: Request) {
try { try {
const session = await auth(); const session = await auth();
@ -25,7 +18,7 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const { name, serverId } = await req.json(); const { name, serverId, categoryId } = await req.json();
if (!name || !serverId) { if (!name || !serverId) {
return NextResponse.json( 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 // Create the channel in the database
const [newChannel] = await db const [newChannel] = await db
.insert(channels) .insert(channels)
.values({ .values({
name: name.trim().toLowerCase().replace(/\s+/g, "-"), name: name.trim().toLowerCase().replace(/\s+/g, "-"),
serverId, serverId,
categoryId: categoryId || null,
}) })
.returning(); .returning();

View file

@ -7,59 +7,47 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { X, Loader2 } from "lucide-react"; import { X } from "lucide-react";
import { useActiveServer } from "@/lib/context/ServerContext"; import { useActiveServer } from "@/lib/context/ServerContext";
import { ActionButton } from "../ui/ActionButton"; import { ActionButton } from "../ui/ActionButton";
/** /** Properties for the CreateChannelModal component. */
* 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.
*/
interface CreateChannelModalProps { interface CreateChannelModalProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
serverId: string; serverId: string;
defaultCategoryId?: string | null;
} }
/** /** Renders a modal dialog allowing users to input a name and create a new server channel. */
* 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.
*/
export function CreateChannelModal({ export function CreateChannelModal({
isOpen, isOpen,
onClose, onClose,
serverId, serverId,
defaultCategoryId = null,
}: CreateChannelModalProps) { }: CreateChannelModalProps) {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [categoryId, setCategoryId] = useState<string | null>(
defaultCategoryId,
);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const { addChannel } = useActiveServer(); const { addChannel } = useActiveServer();
// Update categoryId if the defaultCategoryId changes when the page opens
useEffect(() => {
setCategoryId(defaultCategoryId);
}, [defaultCategoryId, isOpen]);
if (!isOpen) return null; if (!isOpen) return null;
const isValid = name.trim().length != 0; const isValid = name.trim().length != 0;
const canSave = isValid && !isLoading; const canSave = isValid && !isLoading;
/** /** Handles form submission to create a new channel via the API. */
* Handles form submission to create a new channel via the API. const handleSubmit = async (e: React.SubmitEvent) => {
*
* @async
* @function handleSubmit
* @param {React.FormEvent} e - The form submission event.
* @returns {Promise<void>} Resolves when the channel creation request completes.
*/
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim() || isLoading) return; if (!name.trim() || isLoading) return;
@ -70,7 +58,11 @@ export function CreateChannelModal({
const response = await fetch("/api/channels", { const response = await fetch("/api/channels", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, serverId }), body: JSON.stringify({
name,
serverId,
categoryId: categoryId || null,
}),
}); });
if (!response.ok) { if (!response.ok) {

View file

@ -209,6 +209,7 @@ export function ChannelSidebar() {
isOpen={isCreateModalOpen} isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)} onClose={() => setIsCreateModalOpen(false)}
serverId={activeServer.id} serverId={activeServer.id}
defaultCategoryId={selectedCategoryId}
/> />
<EditChannelModal <EditChannelModal