feat(phase3): forum channels + tags + card UI

This commit is contained in:
2026-06-30 10:44:52 -04:00
parent f3f03df710
commit 368172e3d6
12 changed files with 407 additions and 87 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
import { create } from 'zustand';
import { api } from '../lib/api.ts';
export type ChannelType = 'text' | 'voice';
export type ChannelType = 'text' | 'voice' | 'forum';
export interface Channel {
id: string;
@@ -10,6 +10,7 @@ export interface Channel {
type: ChannelType;
category: string | null;
position: number;
slowmode_seconds?: number;
}
interface ChannelState {
+42
View File
@@ -15,15 +15,26 @@ export interface Thread {
message_count: number;
last_message_at: string | null;
created_at: string;
tag_ids?: string[];
}
export interface ForumTag {
id: string;
tag_id: string;
name: string;
emoji: string | null;
color: string | null;
}
export interface CreateThreadData {
name: string;
message_id?: string;
tag_ids?: string[];
}
interface ThreadState {
threadsByParent: Record<string, Thread[]>;
tagsByForum: Record<string, ForumTag[]>;
activeThreadId: string | null;
messagesByThread: Record<string, Message[]>;
isLoading: boolean;
@@ -35,10 +46,14 @@ interface ThreadState {
fetchThreadMessages: (threadId: string, before?: string) => Promise<void>;
sendThreadMessage: (threadId: string, content: string) => Promise<Message>;
addThreadMessage: (message: Message) => void;
fetchForumTags: (forumChannelId: string) => Promise<void>;
createForumTag: (forumChannelId: string, data: { name: string; emoji?: string; color?: string }) => Promise<ForumTag>;
deleteForumTag: (tagId: string) => Promise<void>;
}
export const useThreadStore = create<ThreadState>((set) => ({
threadsByParent: {},
tagsByForum: {},
activeThreadId: null,
messagesByThread: {},
isLoading: false,
@@ -113,4 +128,31 @@ export const useThreadStore = create<ThreadState>((set) => ({
if (list.some((m) => m.id === message.id)) return state;
return { messagesByThread: { ...state.messagesByThread, [message.channel_id]: [...list, message] } };
}),
fetchForumTags: async (forumChannelId) => {
const tags = await api.get<ForumTag[]>(`/channels/${forumChannelId}/forum-tags`);
set((state) => ({
tagsByForum: { ...state.tagsByForum, [forumChannelId]: Array.isArray(tags) ? tags : [] },
}));
},
createForumTag: async (forumChannelId, data) => {
const tag = await api.post<ForumTag>(`/channels/${forumChannelId}/forum-tags`, data);
set((state) => {
const list = state.tagsByForum[forumChannelId] || [];
return { tagsByForum: { ...state.tagsByForum, [forumChannelId]: [...list, tag] } };
});
return tag;
},
deleteForumTag: async (tagId) => {
await api.delete(`/forum-tags/${tagId}`);
set((state) => {
const next: Record<string, ForumTag[]> = {};
for (const forumId of Object.keys(state.tagsByForum)) {
next[forumId] = state.tagsByForum[forumId].filter((t) => t.id !== tagId && t.tag_id !== tagId);
}
return { tagsByForum: next };
});
},
}));