refactor(channels): cap name length at 32 chars and fix sidebar truncation layout
This commit is contained in:
parent
2325c5725e
commit
be4e10893e
6 changed files with 36 additions and 20 deletions
|
|
@ -32,13 +32,21 @@ export async function PATCH(
|
||||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!name || !name.trim()) {
|
if (!name) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Channel name cannot be empty" },
|
{ error: "Channel name cannot be empty" },
|
||||||
{ status: 400 },
|
{ 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
|
const [existingChannel] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(channels)
|
.from(channels)
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,21 @@ export async function POST(req: Request) {
|
||||||
|
|
||||||
const { name, serverId } = await req.json();
|
const { name, serverId } = await req.json();
|
||||||
|
|
||||||
if (!name?.trim() || !serverId) {
|
if (!name || !serverId) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Name and server ID are required" },
|
{ error: "Name and server ID are required" },
|
||||||
{ status: 400 },
|
{ 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
|
// Check if the user is a member of the server
|
||||||
const [member] = await db
|
const [member] = await db
|
||||||
.select()
|
.select()
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ export function CreateChannelModal({
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
value={name}
|
value={name}
|
||||||
|
maxLength={32}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
placeholder="new-channel"
|
placeholder="new-channel"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
|
|
||||||
|
|
@ -82,16 +82,15 @@ export function EditChannelModal({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Fehler beim Aktualisieren des Channels.");
|
throw new Error("Error updating the channel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await response.json();
|
const updated = await response.json();
|
||||||
updateChannel(updated);
|
updateChannel(updated);
|
||||||
|
router.refresh();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(
|
setError(err instanceof Error ? err.message : "Something went wrong.");
|
||||||
err instanceof Error ? err.message : "Etwas ist schiefgelaufen.",
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +115,7 @@ export function EditChannelModal({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Fehler beim Löschen des Channels.");
|
throw new Error("Error deleting the channel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
removeChannel(channel.id);
|
removeChannel(channel.id);
|
||||||
|
|
@ -135,9 +134,7 @@ export function EditChannelModal({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(
|
setError(err instanceof Error ? err.message : "Something went wrong.");
|
||||||
err instanceof Error ? err.message : "Etwas ist schiefgelaufen.",
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +176,7 @@ export function EditChannelModal({
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
value={name}
|
value={name}
|
||||||
|
maxLength={32}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
placeholder="channel-name"
|
placeholder="channel-name"
|
||||||
disabled={isLoading || isDeleting}
|
disabled={isLoading || isDeleting}
|
||||||
|
|
|
||||||
|
|
@ -62,22 +62,22 @@ export function ChannelSidebar() {
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* 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>
|
<span className="truncate">{activeServer.name}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleNav}
|
onClick={toggleNav}
|
||||||
title="Collapse the sidebar"
|
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" />
|
<PanelLeftClose className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Channel List */}
|
{/* 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">
|
<div className="flex items-center justify-between text-xs font-semibold text-muted px-2 py-1 uppercase tracking-wider">
|
||||||
<span>Text Channels</span>
|
<span>Text Channels</span>
|
||||||
<button
|
<button
|
||||||
|
|
@ -90,7 +90,7 @@ export function ChannelSidebar() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5 w-full">
|
||||||
{activeServer.channels.map((channel) => {
|
{activeServer.channels.map((channel) => {
|
||||||
const isActive = currentChannelId === channel.id;
|
const isActive = currentChannelId === channel.id;
|
||||||
|
|
||||||
|
|
@ -100,14 +100,15 @@ export function ChannelSidebar() {
|
||||||
href={`/servers/${activeServer.id}/channels/${channel.id}`}
|
href={`/servers/${activeServer.id}/channels/${channel.id}`}
|
||||||
onClick={handleChannelClick}
|
onClick={handleChannelClick}
|
||||||
prefetch={false}
|
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
|
isActive
|
||||||
? "bg-accent/50 text-white font-medium"
|
? "bg-accent/50 text-white font-medium"
|
||||||
: "text-muted hover:bg-surface hover:text-white"
|
: "text-muted hover:bg-surface hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
{/* Text Area */}
|
||||||
<span className="text-muted group-hover:text-white text-base">
|
<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>
|
||||||
<span className="truncate">{channel.name}</span>
|
<span className="truncate">{channel.name}</span>
|
||||||
|
|
@ -118,7 +119,7 @@ export function ChannelSidebar() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => handleOpenSettings(e, channel)}
|
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"
|
aria-label="Channel Settings"
|
||||||
title="Channel Settings"
|
title="Channel Settings"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ export const members = pgTable("members", {
|
||||||
*/
|
*/
|
||||||
export const channels = pgTable("channels", {
|
export const channels = pgTable("channels", {
|
||||||
id: uuid("id").primaryKey().defaultRandom(),
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
name: text("name").notNull(),
|
name: varchar("name", { length: 32 }).notNull(),
|
||||||
serverId: uuid("server_id")
|
serverId: uuid("server_id")
|
||||||
.references(() => servers.id, { onDelete: "cascade" })
|
.references(() => servers.id, { onDelete: "cascade" })
|
||||||
.notNull(),
|
.notNull(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue