refactor(channels): cap name length at 32 chars and fix sidebar truncation layout

This commit is contained in:
Chneemann 2026-08-31 12:08:16 +02:00
parent 2325c5725e
commit be4e10893e
No known key found for this signature in database
6 changed files with 36 additions and 20 deletions

View file

@ -32,13 +32,21 @@ export async function PATCH(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!name || !name.trim()) {
if (!name) {
return NextResponse.json(
{ error: "Channel name cannot be empty" },
{ status: 400 },
);
}
// Validate maximum length for channel name
if (name.length > 32) {
return NextResponse.json(
{ error: "Channel name cannot exceed 32 characters" },
{ status: 400 },
);
}
const [existingChannel] = await db
.select()
.from(channels)

View file

@ -27,13 +27,21 @@ export async function POST(req: Request) {
const { name, serverId } = await req.json();
if (!name?.trim() || !serverId) {
if (!name || !serverId) {
return NextResponse.json(
{ error: "Name and server ID are required" },
{ status: 400 },
);
}
// Validate maximum length for channel name
if (name.length > 32) {
return NextResponse.json(
{ error: "Channel name cannot exceed 32 characters" },
{ status: 400 },
);
}
// Check if the user is a member of the server
const [member] = await db
.select()

View file

@ -124,6 +124,7 @@ export function CreateChannelModal({
type="text"
required
value={name}
maxLength={32}
onChange={(e) => setName(e.target.value)}
placeholder="new-channel"
disabled={isLoading}

View file

@ -82,16 +82,15 @@ export function EditChannelModal({
});
if (!response.ok) {
throw new Error("Fehler beim Aktualisieren des Channels.");
throw new Error("Error updating the channel.");
}
const updated = await response.json();
updateChannel(updated);
router.refresh();
onClose();
} catch (err: unknown) {
setError(
err instanceof Error ? err.message : "Etwas ist schiefgelaufen.",
);
setError(err instanceof Error ? err.message : "Something went wrong.");
} finally {
setIsLoading(false);
}
@ -116,7 +115,7 @@ export function EditChannelModal({
});
if (!response.ok) {
throw new Error("Fehler beim Löschen des Channels.");
throw new Error("Error deleting the channel.");
}
removeChannel(channel.id);
@ -135,9 +134,7 @@ export function EditChannelModal({
}
}
} catch (err: unknown) {
setError(
err instanceof Error ? err.message : "Etwas ist schiefgelaufen.",
);
setError(err instanceof Error ? err.message : "Something went wrong.");
} finally {
setIsDeleting(false);
}
@ -179,6 +176,7 @@ export function EditChannelModal({
type="text"
required
value={name}
maxLength={32}
onChange={(e) => setName(e.target.value)}
placeholder="channel-name"
disabled={isLoading || isDeleting}

View file

@ -62,22 +62,22 @@ export function ChannelSidebar() {
return (
<>
<div className="flex-1 w-full md:w-72 bg-surface/50 border-r border-background flex flex-col h-full shrink-0">
<div className="flex-1 w-full bg-surface/50 border-r border-background flex flex-col h-full min-w-0 overflow-hidden">
{/* Server Header */}
<div className="h-14 border-b border-background flex items-center justify-between px-4 font-bold text-white shadow-sm">
<div className="h-14 border-b border-background flex items-center justify-between px-4 font-bold text-white shadow-sm shrink-0">
<span className="truncate">{activeServer.name}</span>
<button
type="button"
onClick={toggleNav}
title="Collapse the sidebar"
className="p-1.5 rounded-md text-muted hover:text-white hover:bg-surface transition-colors cursor-pointer"
className="p-1.5 rounded-md text-muted hover:text-white hover:bg-surface transition-colors cursor-pointer shrink-0"
>
<PanelLeftClose className="w-5 h-5" />
</button>
</div>
{/* Channel List */}
<div className="flex-1 overflow-y-auto p-3 space-y-1">
<div className="flex-1 overflow-y-auto p-3 space-y-1 min-w-0">
<div className="flex items-center justify-between text-xs font-semibold text-muted px-2 py-1 uppercase tracking-wider">
<span>Text Channels</span>
<button
@ -90,7 +90,7 @@ export function ChannelSidebar() {
</button>
</div>
<div className="space-y-0.5">
<div className="space-y-0.5 w-full">
{activeServer.channels.map((channel) => {
const isActive = currentChannelId === channel.id;
@ -100,14 +100,15 @@ export function ChannelSidebar() {
href={`/servers/${activeServer.id}/channels/${channel.id}`}
onClick={handleChannelClick}
prefetch={false}
className={`flex items-center justify-between px-2 py-1.5 rounded-md text-sm transition-all group ${
className={`flex items-center justify-between w-full px-2 py-1.5 rounded-md text-sm transition-all group min-w-0 overflow-hidden ${
isActive
? "bg-accent/50 text-white font-medium"
: "text-muted hover:bg-surface hover:text-white"
}`}
>
<div className="flex items-center gap-2 min-w-0">
<span className="text-muted group-hover:text-white text-base">
{/* Text Area */}
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
<span className="text-muted group-hover:text-white text-base shrink-0">
#
</span>
<span className="truncate">{channel.name}</span>
@ -118,7 +119,7 @@ export function ChannelSidebar() {
<button
type="button"
onClick={(e) => handleOpenSettings(e, channel)}
className="opacity-0 group-hover:opacity-100 p-1 text-muted hover:text-white focus:outline-none transition-all cursor-pointer"
className="opacity-0 group-hover:opacity-100 p-1 text-muted hover:text-white focus:outline-none transition-all cursor-pointer shrink-0 ml-2"
aria-label="Channel Settings"
title="Channel Settings"
>

View file

@ -85,7 +85,7 @@ export const members = pgTable("members", {
*/
export const channels = pgTable("channels", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
name: varchar("name", { length: 32 }).notNull(),
serverId: uuid("server_id")
.references(() => servers.id, { onDelete: "cascade" })
.notNull(),