sync: phase 1 backend + frontend from server
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { create } from "zustand";
|
||||
import { api } from "../lib/api.ts";
|
||||
|
||||
export interface ConversationMember {
|
||||
id: string;
|
||||
username: string;
|
||||
display_name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string;
|
||||
type: "dm" | "group_dm";
|
||||
name: string;
|
||||
members: ConversationMember[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface ConversationMessage {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
author_id: string;
|
||||
author_username: string;
|
||||
author_display_name: string | null;
|
||||
content: string;
|
||||
created_at: string;
|
||||
edited_at: string | null;
|
||||
}
|
||||
|
||||
interface ConversationState {
|
||||
conversations: Conversation[];
|
||||
activeConversationId: string | null;
|
||||
messagesByConversation: Record<string, ConversationMessage[]>;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
fetchConversations: () => Promise<void>;
|
||||
createConversation: (userIds: string[]) => Promise<Conversation>;
|
||||
setActiveConversation: (id: string | null) => void;
|
||||
fetchMessages: (conversationId: string, before?: string) => Promise<void>;
|
||||
sendMessage: (conversationId: string, content: string) => Promise<ConversationMessage>;
|
||||
addMessage: (message: ConversationMessage) => void;
|
||||
}
|
||||
|
||||
export const useConversationStore = create<ConversationState>((set, _get) => ({
|
||||
conversations: [],
|
||||
activeConversationId: null,
|
||||
messagesByConversation: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
fetchConversations: async () => {
|
||||
try {
|
||||
const conversations = await api.get<Conversation[]>("/conversations");
|
||||
set({ conversations: Array.isArray(conversations) ? conversations : [] });
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : "Failed to fetch conversations",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
createConversation: async (userIds) => {
|
||||
const conversation = await api.post<Conversation>("/conversations", { user_ids: userIds });
|
||||
set((state) => ({
|
||||
conversations: [conversation, ...state.conversations],
|
||||
activeConversationId: conversation.id,
|
||||
}));
|
||||
return conversation;
|
||||
},
|
||||
|
||||
setActiveConversation: (id) => set({ activeConversationId: id }),
|
||||
|
||||
fetchMessages: async (conversationId, before) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const params = before ? "?before=" + encodeURIComponent(before) : "";
|
||||
const messages = await api.get<ConversationMessage[]>(
|
||||
`/conversations/${conversationId}/messages${params}`,
|
||||
);
|
||||
set((state) => ({
|
||||
messagesByConversation: {
|
||||
...state.messagesByConversation,
|
||||
[conversationId]: Array.isArray(messages) ? messages : [],
|
||||
},
|
||||
isLoading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
set({ isLoading: false, error: error instanceof Error ? error.message : "Failed" });
|
||||
}
|
||||
},
|
||||
|
||||
sendMessage: async (conversationId, content) => {
|
||||
const message = await api.post<ConversationMessage>(
|
||||
`/conversations/${conversationId}/messages`,
|
||||
{ content },
|
||||
);
|
||||
set((state) => ({
|
||||
messagesByConversation: {
|
||||
...state.messagesByConversation,
|
||||
[conversationId]: [
|
||||
...(state.messagesByConversation[conversationId] || []),
|
||||
message,
|
||||
],
|
||||
},
|
||||
}));
|
||||
return message;
|
||||
},
|
||||
|
||||
addMessage: (message) => {
|
||||
set((state) => ({
|
||||
messagesByConversation: {
|
||||
...state.messagesByConversation,
|
||||
[message.conversation_id]: [
|
||||
...(state.messagesByConversation[message.conversation_id] || []),
|
||||
message,
|
||||
],
|
||||
},
|
||||
}));
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,11 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface LayoutState {
|
||||
isDM: boolean;
|
||||
setDM: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const useLayoutStore = create<LayoutState>((set) => ({
|
||||
isDM: false,
|
||||
setDM: (value) => set({ isDM: value }),
|
||||
}));
|
||||
@@ -1,6 +1,15 @@
|
||||
import { create } from "zustand";
|
||||
import { api } from "../lib/api.ts";
|
||||
|
||||
export interface MessageEmbed {
|
||||
id?: string;
|
||||
url: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
image_url?: string;
|
||||
site_name?: string;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
channel_id: string;
|
||||
@@ -8,16 +17,24 @@ export interface Message {
|
||||
author_username: string;
|
||||
author_display_name: string | null;
|
||||
content: string;
|
||||
reply_to?: string | null;
|
||||
embeds?: MessageEmbed[];
|
||||
created_at: string;
|
||||
edited_at: string | null;
|
||||
}
|
||||
|
||||
export interface SearchResultMessage extends Message {
|
||||
channel_name?: string;
|
||||
}
|
||||
|
||||
interface MessageState {
|
||||
messagesByChannel: Record<string, Message[]>;
|
||||
searchResultsByChannel: Record<string, SearchResultMessage[]>;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
fetchMessages: (channelId: string, before?: string) => Promise<void>;
|
||||
sendMessage: (channelId: string, content: string) => Promise<Message>;
|
||||
sendMessage: (channelId: string, content: string, replyTo?: string) => Promise<Message>;
|
||||
searchMessages: (channelId: string, query: string) => Promise<SearchResultMessage[]>;
|
||||
addMessage: (message: Message) => void;
|
||||
updateMessage: (message: Message) => void;
|
||||
removeMessage: (channelId: string, messageId: string) => void;
|
||||
@@ -25,6 +42,7 @@ interface MessageState {
|
||||
|
||||
export const useMessageStore = create<MessageState>((set) => ({
|
||||
messagesByChannel: {},
|
||||
searchResultsByChannel: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
@@ -53,10 +71,25 @@ export const useMessageStore = create<MessageState>((set) => ({
|
||||
}
|
||||
},
|
||||
|
||||
sendMessage: async (channelId, content) => {
|
||||
searchMessages: async (channelId, query) => {
|
||||
const results = await api.get<SearchResultMessage[]>(
|
||||
`/channels/${channelId}/messages/search?q=${encodeURIComponent(query)}`,
|
||||
);
|
||||
set((state) => ({
|
||||
searchResultsByChannel: {
|
||||
...state.searchResultsByChannel,
|
||||
[channelId]: Array.isArray(results) ? results : [],
|
||||
},
|
||||
}));
|
||||
return Array.isArray(results) ? results : [];
|
||||
},
|
||||
|
||||
sendMessage: async (channelId, content, replyTo) => {
|
||||
const body: { content: string; reply_to?: string } = { content };
|
||||
if (replyTo) body.reply_to = replyTo;
|
||||
const message = await api.post<Message>(
|
||||
`/channels/${channelId}/messages`,
|
||||
{ content },
|
||||
body,
|
||||
);
|
||||
set((state) => {
|
||||
const list = state.messagesByChannel[channelId] || [];
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { create } from 'zustand';
|
||||
import { api } from '../lib/api.ts';
|
||||
|
||||
export interface ModerationState {
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
kickMember: (serverId: string, userId: string, reason?: string) => Promise<void>;
|
||||
banMember: (serverId: string, userId: string, reason?: string) => Promise<void>;
|
||||
unbanMember: (serverId: string, userId: string) => Promise<void>;
|
||||
muteMember: (serverId: string, userId: string, durationSeconds: number) => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useModerationStore = create<ModerationState>((set) => ({
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
kickMember: async (serverId, userId, reason) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
await api.post(`/servers/${serverId}/members/${userId}/kick`, { reason });
|
||||
set({ isLoading: false });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to kick member';
|
||||
set({ isLoading: false, error: message });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
banMember: async (serverId, userId, reason) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
await api.post(`/servers/${serverId}/members/${userId}/ban`, { reason });
|
||||
set({ isLoading: false });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to ban member';
|
||||
set({ isLoading: false, error: message });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
unbanMember: async (serverId, userId) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
await api.post(`/servers/${serverId}/members/${userId}/unban`, {});
|
||||
set({ isLoading: false });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to unban member';
|
||||
set({ isLoading: false, error: message });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
muteMember: async (serverId, userId, durationSeconds) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
await api.post(`/servers/${serverId}/members/${userId}/mute`, { duration_seconds: durationSeconds });
|
||||
set({ isLoading: false });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to mute member';
|
||||
set({ isLoading: false, error: message });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => set({ error: null }),
|
||||
}));
|
||||
@@ -12,6 +12,14 @@ export interface Server {
|
||||
interface ServerState {
|
||||
servers: Server[];
|
||||
activeServerId: string | null;
|
||||
userPermissions: {
|
||||
kick_members: boolean;
|
||||
ban_members: boolean;
|
||||
mute_members: boolean;
|
||||
manage_channels: boolean;
|
||||
manage_server: boolean;
|
||||
administrator: boolean;
|
||||
};
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
fetchServers: () => Promise<void>;
|
||||
@@ -19,11 +27,20 @@ interface ServerState {
|
||||
addServer: (server: Server) => void;
|
||||
updateServer: (server: Server) => void;
|
||||
removeServer: (id: string) => void;
|
||||
setUserPermissions: (perms: Partial<ServerState['userPermissions']>) => void;
|
||||
}
|
||||
|
||||
export const useServerStore = create<ServerState>((set) => ({
|
||||
servers: [],
|
||||
activeServerId: null,
|
||||
userPermissions: {
|
||||
kick_members: false,
|
||||
ban_members: false,
|
||||
mute_members: false,
|
||||
manage_channels: false,
|
||||
manage_server: false,
|
||||
administrator: false,
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
@@ -57,4 +74,9 @@ export const useServerStore = create<ServerState>((set) => ({
|
||||
servers: state.servers.filter((s) => s.id !== id),
|
||||
activeServerId: state.activeServerId === id ? null : state.activeServerId,
|
||||
})),
|
||||
|
||||
setUserPermissions: (perms) =>
|
||||
set((state) => ({
|
||||
userPermissions: { ...state.userPermissions, ...perms },
|
||||
})),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user