sync: phase 1 backend + frontend from server

This commit is contained in:
2026-06-30 09:26:03 -04:00
parent d4cdd89544
commit 30e159cbdb
31 changed files with 4758 additions and 114 deletions
+120
View File
@@ -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,
],
},
}));
},
}));