Files
dumpsterChat/web/src/stores/channel.ts
T
hobokenchicken bb5a56816b Phase 1 MVP: gateway, CRUD handlers, frontend components
Backend:
- WebSocket gateway (hub, client, events) with fanout broadcast
- Server CRUD handlers (create, list, get, update, delete)
- Channel CRUD handlers (create, list, get, update, delete)
- Message CRUD handlers (list with cursor pagination, create, update, delete)
- cmd/migrate standalone migration CLI (up/down)
- cmd/server wired to all handlers + WebSocket + static file serving

Frontend:
- Zustand stores: auth, server, channel, message, websocket
- API client with fetch wrapper
- Terminal-styled components: Layout, LoginForm, ChatArea, ChannelList, ServerBar, MemberList
- React Router with login and main routes
- Gruvbox dark palette throughout

Ops:
- Docker Compose with app service (multi-stage build)
- Caddyfile with WebSocket upgrade support
- Makefile for common tasks
2026-06-26 14:47:29 -04:00

85 lines
2.3 KiB
TypeScript

import { create } from 'zustand';
import { api } from '../lib/api.ts';
export type ChannelType = 'text' | 'voice';
export interface Channel {
id: string;
serverId: string;
name: string;
type: ChannelType;
category: string | null;
position: number;
}
interface ChannelState {
channelsByServer: Record<string, Channel[]>;
activeChannelId: string | null;
isLoading: boolean;
error: string | null;
fetchChannels: (serverId: string) => Promise<void>;
setActiveChannel: (id: string | null) => void;
addChannel: (channel: Channel) => void;
updateChannel: (channel: Channel) => void;
removeChannel: (id: string) => void;
}
export const useChannelStore = create<ChannelState>((set) => ({
channelsByServer: {},
activeChannelId: null,
isLoading: false,
error: null,
fetchChannels: async (serverId) => {
set({ isLoading: true, error: null });
try {
const channels = await api.get<Channel[]>(`/servers/${serverId}/channels`);
set((state) => ({
channelsByServer: { ...state.channelsByServer, [serverId]: channels },
isLoading: false,
}));
} catch (error) {
set({
isLoading: false,
error: error instanceof Error ? error.message : 'Failed to fetch channels',
});
}
},
setActiveChannel: (id) => set({ activeChannelId: id }),
addChannel: (channel) =>
set((state) => {
const list = state.channelsByServer[channel.serverId] || [];
return {
channelsByServer: {
...state.channelsByServer,
[channel.serverId]: [...list, channel],
},
};
}),
updateChannel: (channel) =>
set((state) => {
const list = state.channelsByServer[channel.serverId] || [];
return {
channelsByServer: {
...state.channelsByServer,
[channel.serverId]: list.map((c) => (c.id === channel.id ? channel : c)),
},
};
}),
removeChannel: (id) =>
set((state) => {
const next: Record<string, Channel[]> = {};
for (const serverId of Object.keys(state.channelsByServer)) {
next[serverId] = state.channelsByServer[serverId].filter((c) => c.id !== id);
}
return {
channelsByServer: next,
activeChannelId: state.activeChannelId === id ? null : state.activeChannelId,
};
}),
}));