feat: UI for server/channel creation, settings link, role management backend

Frontend:
- Settings link in header next to logout
- [+] button to create server (name + optional icon)
- [#] button to join server via invite code
- [+] button to create channel (name, type, category)
- Server name shown instead of UUID in channel list header
- /invites/:code route wired for JoinServer component

Backend:
- Wire role CRUD + member-role assignment routes (MANAGE_SERVER gated)
- Seed @everyone default role on server creation
This commit is contained in:
2026-06-29 12:50:46 -04:00
parent 3072cda051
commit 3f33859f4a
11 changed files with 778 additions and 27 deletions
+45 -21
View File
@@ -1,6 +1,8 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useServerStore } from '../stores/server.ts';
import { useChannelStore } from '../stores/channel.ts';
import { CreateServerModal } from './CreateServerModal.tsx';
import { JoinServerModal } from './JoinServerModal.tsx';
function getInitials(name: string): string {
const words = name.trim().split(/\s+/);
@@ -16,6 +18,8 @@ export function ServerBar() {
const fetchServers = useServerStore((state) => state.fetchServers);
const setActiveServer = useServerStore((state) => state.setActiveServer);
const setActiveChannel = useChannelStore((state) => state.setActiveChannel);
const [showCreate, setShowCreate] = useState(false);
const [showJoin, setShowJoin] = useState(false);
useEffect(() => {
fetchServers();
@@ -27,25 +31,45 @@ export function ServerBar() {
};
return (
<div className="h-full w-16 bg-gb-bg-h border-r border-gb-bg-t flex flex-col items-center py-3 gap-2 overflow-y-auto">
{servers.map((server) => (
<button
key={server.id}
onClick={() => handleSelect(server.id)}
className={`w-11 h-11 flex items-center justify-center font-mono text-sm border ${
server.id === activeServerId
? 'bg-gb-bg-t text-gb-orange border-gb-orange'
: 'bg-gb-bg-s text-gb-fg border-gb-bg-t hover:border-gb-fg-t hover:text-gb-aqua'
}`}
title={server.name}
>
{server.unread && server.id !== activeServerId ? (
<span className="text-gb-aqua">[{getInitials(server.name)}]</span>
) : (
`[${getInitials(server.name)}]`
)}
</button>
))}
</div>
<>
<div className="h-full w-16 bg-gb-bg-h border-r border-gb-bg-t flex flex-col items-center py-3 gap-2 overflow-y-auto">
{servers.map((server) => (
<button
key={server.id}
onClick={() => handleSelect(server.id)}
className={`w-11 h-11 flex items-center justify-center font-mono text-sm border ${
server.id === activeServerId
? 'bg-gb-bg-t text-gb-orange border-gb-orange'
: 'bg-gb-bg-s text-gb-fg border-gb-bg-t hover:border-gb-fg-t hover:text-gb-aqua'
}`}
title={server.name}
>
{server.unread && server.id !== activeServerId ? (
<span className="text-gb-aqua">[{getInitials(server.name)}]</span>
) : (
`[${getInitials(server.name)}]`
)}
</button>
))}
<div className="mt-2 flex flex-col gap-2 items-center">
<button
onClick={() => setShowCreate(true)}
className="w-11 h-11 flex items-center justify-center font-mono text-sm border bg-gb-bg-s text-gb-fg border-gb-bg-t hover:border-gb-fg-t hover:text-gb-aqua"
title="Create server"
>
[+]
</button>
<button
onClick={() => setShowJoin(true)}
className="w-11 h-11 flex items-center justify-center font-mono text-xs border bg-gb-bg-s text-gb-fg border-gb-bg-t hover:border-gb-fg-t hover:text-gb-aqua"
title="Join server"
>
[#]
</button>
</div>
</div>
{showCreate && <CreateServerModal onClose={() => setShowCreate(false)} />}
{showJoin && <JoinServerModal onClose={() => setShowJoin(false)} />}
</>
);
}