From ca395c76ae8411aff9377467b6b3622532121b2b Mon Sep 17 00:00:00 2001 From: Chneemann Date: Mon, 14 Sep 2026 18:46:15 +0200 Subject: [PATCH] feat(channels): add category creation modal and sidebar action --- components/modals/CreateCategoryModal.tsx | 133 ++++++++++++++++++++++ components/sidebar/ChannelSidebar.tsx | 36 ++++-- 2 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 components/modals/CreateCategoryModal.tsx diff --git a/components/modals/CreateCategoryModal.tsx b/components/modals/CreateCategoryModal.tsx new file mode 100644 index 0000000..e658a33 --- /dev/null +++ b/components/modals/CreateCategoryModal.tsx @@ -0,0 +1,133 @@ +/** + * @file components/modals/CreateCategoryModal.tsx + * @description Modal dialog component for creating a new channel category within a server. + */ + +"use client"; + +import { useState } from "react"; +import { X } from "lucide-react"; +import { useActiveServer } from "@/lib/context/ServerContext"; +import { ActionButton } from "../ui/ActionButton"; + +interface CreateCategoryModalProps { + isOpen: boolean; + onClose: () => void; + serverId: string; +} + +/** Renders a modal dialog allowing users to create a new category in a server. */ +export function CreateCategoryModal({ + isOpen, + onClose, + serverId, +}: CreateCategoryModalProps) { + const [name, setName] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const { addCategory } = useActiveServer(); + + if (!isOpen) return null; + + const isValid = name.trim().length !== 0; + const canSave = isValid && !isLoading; + + /** Handles form submission to create a new category via the API. */ + const handleSubmit = async (e: React.SubmitEvent) => { + e.preventDefault(); + if (!name.trim() || isLoading) return; + + try { + setIsLoading(true); + setError(null); + + const response = await fetch("/api/categories", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name, serverId }), + }); + + if (!response.ok) { + throw new Error("Failed to create category."); + } + + const newCategory = await response.json(); + + addCategory(newCategory); + setName(""); + onClose(); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : "Something went wrong."); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
e.stopPropagation()} + className="bg-surface border border-surface/50 rounded-2xl w-full max-w-md p-6 shadow-2xl relative animate-in fade-in zoom-in-95 duration-150" + > + + +

Create Category

+

+ Create a category to group channels together in this server. +

+ +
+
+ + setName(e.target.value)} + placeholder="NEW CATEGORY" + disabled={isLoading} + autoFocus + className="w-full bg-background border border-surface/80 rounded-xl px-3.5 py-2.5 text-sm text-white placeholder:text-muted focus:outline-none focus:ring-2 focus:ring-accent transition-all disabled:opacity-50" + /> +
+ + {error &&

{error}

} + +
+ + Cancel + + + + Save + +
+
+
+
+ ); +} diff --git a/components/sidebar/ChannelSidebar.tsx b/components/sidebar/ChannelSidebar.tsx index f086e70..c5e4751 100644 --- a/components/sidebar/ChannelSidebar.tsx +++ b/components/sidebar/ChannelSidebar.tsx @@ -10,10 +10,12 @@ import { useParams } from "next/navigation"; import { useActiveServer } from "@/lib/context/ServerContext"; import { useSidebarStore } from "@/lib/stores/useSidebarStore"; import { CreateChannelModal } from "@/components/modals/CreateChannelModal"; +import { CreateCategoryModal } from "@/components/modals/CreateCategoryModal"; import { EditChannelModal } from "@/components/modals/EditChannelModal"; import { ChevronDown, ChevronRight, + FolderPlus, PanelLeftClose, Plus, Settings, @@ -24,6 +26,8 @@ import Link from "next/link"; /** Renders the channel navigation sidebar for the active server. */ export function ChannelSidebar() { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [isCreateCategoryModalOpen, setIsCreateCategoryModalOpen] = + useState(false); const [editingChannel, setEditingChannel] = useState(null); const [selectedCategoryId, setSelectedCategoryId] = useState( null, @@ -119,14 +123,24 @@ export function ChannelSidebar() { {/* Server Header */}
{activeServer.name} - +
+ + +
{/* Channel List */} @@ -205,6 +219,12 @@ export function ChannelSidebar() { {/* Modals */} + setIsCreateCategoryModalOpen(false)} + serverId={activeServer.id} + /> + setIsCreateModalOpen(false)}