feat(phase8.1): server groups — table, CRUD handler, collapsible channel sections

This commit is contained in:
2026-06-30 12:50:53 -04:00
parent 6d66d7775f
commit d663638387
7 changed files with 587 additions and 88 deletions
+37 -2
View File
@@ -7,6 +7,14 @@ interface CreateChannelModalProps {
onClose: () => void;
}
interface ServerGroup {
id: string;
server_id: string;
name: string;
position: number;
created_at: string;
}
interface ChannelApiResponse {
id: string;
server_id: string;
@@ -15,6 +23,7 @@ interface ChannelApiResponse {
category: string;
position: number;
slowmode_seconds: number;
group_id: string | null;
}
const SLOWMODE_OPTIONS = [
@@ -32,9 +41,17 @@ export function CreateChannelModal({ serverId, onClose }: CreateChannelModalProp
const [type, setType] = useState<'text' | 'voice' | 'forum' | 'calendar' | 'docs' | 'list'>('text');
const [category, setCategory] = useState('general');
const [slowmodeSeconds, setSlowmodeSeconds] = useState(0);
const [groups, setGroups] = useState<ServerGroup[]>([]);
const [selectedGroupId, setSelectedGroupId] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
api.get<ServerGroup[]>('/servers/' + serverId + '/groups')
.then(setGroups)
.catch(() => {});
}, [serverId]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
@@ -53,12 +70,16 @@ export function CreateChannelModal({ serverId, onClose }: CreateChannelModalProp
setLoading(true);
setError(null);
try {
const result = await api.post<ChannelApiResponse>("/servers/" + serverId + "/channels", {
const body: Record<string, unknown> = {
name: name.trim(),
type,
category: category.trim() || 'general',
slowmode_seconds: slowmodeSeconds,
});
};
if (selectedGroupId) {
body.group_id = selectedGroupId;
}
const result = await api.post<ChannelApiResponse>('/servers/' + serverId + '/channels', body);
const newChannel = {
id: result.id,
serverId: result.server_id,
@@ -67,6 +88,7 @@ export function CreateChannelModal({ serverId, onClose }: CreateChannelModalProp
category: result.category || null,
position: result.position,
slowmode_seconds: result.slowmode_seconds,
group_id: result.group_id,
};
useChannelStore.getState().addChannel(newChannel);
onClose();
@@ -118,6 +140,19 @@ export function CreateChannelModal({ serverId, onClose }: CreateChannelModalProp
<option value="list">list</option>
</select>
</div>
<div>
<label className="text-xs text-gb-fg-f block mb-1">GROUP:</label>
<select
value={selectedGroupId}
onChange={(e) => setSelectedGroupId(e.target.value)}
className="terminal-input w-full"
>
<option value="">(none use category)</option>
{groups.map((g) => (
<option key={g.id} value={g.id}>{g.name}</option>
))}
</select>
</div>
<div>
<label className="text-xs text-gb-fg-f block mb-1">CATEGORY:</label>
<input